Giter Club home page Giter Club logo

denokv-graphql's People

Contributors

vwkd avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

denokv-graphql's Issues

Pagination

Support pagination for lists. Probably Relay's pagination spec, first / last and cursor. But nodes / connections add two nesting levels to schema, increases complexity.

Delete without versionstamp?

Make versionstamp field in delete input optional? Would allow to delete inconsistently, result can't be null then, but can't declare result type non-null in general...

Delete should cascade

Currently references aren't updated upon deletion and left dangling.

How to do this? How to find all references to and from deleted row? What to do with circular dependencies?

Or leave finding ids to user, instead accept multiple ids and versionstamps, make one atomic transaction like in #5

Improve versionstamp logic

Improve versionstamp logic

  • could circular reference break it, since may overwrite existing versionstamp?

Eventually consistent queries

Should expose option for eventually consistent queries? Would be for nested queries?

What would eventually consistent mean for assembled rows? Can only read root eventually consistent?

Add update mutation

Should update existing record, fail if it doesn't yet exist. Allow partial update, i.e. in schema all columns are optional, but at query time at least one must be provided or it throws invalid input.

  • should accept versionstamps for consistency check

How should it update reference columns? Replace entry/ies or add to it? Should keep references in separate table?

Support more GraphQL features

Maybe allow

  • allow other types like interfaces?
  • allow fields with lists of scalars?
  • support subscriptions? @defer and @stream?
  • ...

Probably disallow

  • arguments on table columns
  • circular references of table columns

Reference validation in insert mutation

Insert mutation should validate reference ids either are set in the same transaction or exist already. Currently, it allows to insert reference ids to non-existent rows which would then throw a database corruption error when queried.

Storage implementation

  • Store the primitive value of a column at a key [tableName, rowId, columnName] instead of the whole row as an object as [tableName, rowId]? Would allow geting only columns that actually needs, but would require multiple gets for even the simplest queries.
  • Store references in keys with null values, e.g. [tableName, rowId, columnName, 1n]? Could use Deno KVs list to natively do pagination, e.g. start, end, limit. But would need more reads, and separate reads make the whole query not atomically consistent within itself.

Store references in separate table allows to be updated. With update mutation could only replace whole entry, not change references. How would indicate reference table in schema? With directive?

type Book {
  id: ID!
  title: String
  author: Author @reference(table: "BookAuthor", source: "bookId", target: "authorId")
}

type BookAuthor {
  id: ID!
  bookId: ID!
  authorId: ID!
}

Query multiple entries

Support querying multiple entries in single query? Should do multiple ids instead of single id? How to be atomically consistent? Should do search / filter without knowing ids?

Support secondary indeces

Alternative to id. Would require to maintain the mapping, update for each mutation, increases complexity. How to specify in schema? Using directive? How to specify in query which index (id, or secondary) is passed as argument?

Add upsert mutation

Updates existing row or inserts new one if doesn't exist.

  • Can't allow partial updates? Since can't make all columns optional because it could be insert
  • Which id should the row get if it inserts new one? Should take id from argument and possibly leave "holes" between ids or should auto-generate next id?
  • should accept versionstamps for consistency check

Add sum/min/max mutations

Implement atomic sum/min/max mutations from Deno KV?

This could be expanded to allow arbitrary mutations on existing data by letting the user provide a transformer function. Would need to check that is function, wrap in try catch block, call and await in case it's a promise. Would need to verify output is what expects.

Improve error messages

  • explain why restrictions are necessary: non-null query return type is error because database might return null for non-existent id or if throws error, e.g. bookById: Book!
  • maybe validate returned data ourselves instead of relying on GraphQL? Could provide more specific database corruption error, e.g. if row doesn't conform to schema. But adding lots of checks would increase complexity, maintenance burden to update with every change creates friction, etc.
  • throw errors uniformly if concurrent change, e.g. currently returns null if concurrent change in mutation, but throws if concurrent change in query

Better approach

  • are resolvers the right way to do this?
  • better way to add resolvers than to walk schema tree to create separate resolvers map that's then merged back again?
  • better way to extend schema than string concatenating source string? Should use TypeSystemExtension or extend schema?
  • use graphql-js APIs directly instead of graphql-tools?
  • create resolver tree tail call recursively without mutating global state
  • specify table of query using directive to be consistent with mutation?

Add more tests

  • add missing tests for execution, e.g. insert reference, insert references, etc.
  • add missing tests for pagination and sub-keys
  • add tests for concurrent changes

Improve consistency

  • which versionstamp to return in query when joining across tables? since reads separately and ends up with multiple versionstamps for each object... just use lowest/highest versionstamp?
  • make query across tables consistent, use atomic().check() to guarantee that previous value didn't change (next value might have), or could use previous value's versionstamp to also guarantee that next wasn't updated in meantime? how to retry resolver chain if fails? would need to backtrack to root again.
  • expose check option in mutations and accept versionstring
  • expose concurrency option in queries
  • delete should accept versionstamps
  • consistency of list for large number of entries (>500) with multiple batches could be inconsistent...

Insert across tables

Insert across tables in one atomically consistent set. Receives deep object as input, walks from deepest leaves to root, inserts each object into corresponding table.

But how should report the ids and versionstamps of the nested rows? Array of Result with additional table name entry?

Deep insert is complex to implement, needs to pick apart deep input object, generate the ids and match them up, just to stitch it back together at query time later. It largely defeats the purpose of relational data storage, since can't link new data to existing data, must always insert together, could almost just store the whole data as JSON if not for ability to query only part of it. Also cumbersome for user to define duplicate type hierarchy for input types and output types.

Better to let user build the relations. Just somehow ensure consistency... Would need to give up generating ids for user, instead let the user generate random UUIDs in advance, allow to insert multiple at same time, throw if any id already exists.

Could accept multiple rows in a mutation, and multiple mutations in a request. The mutations must all be committed in one atomic transaction. Order doesn't matter since there are no foreign key constraints in Deno KV, and atomicity makes either all succeed or all fail. Inserting valid references is up to the user?

mutation {
  createBooks(data: [ { id: "xxx", ..., author: "bbb" }, { id: "yyy", ..., author: "aaa"} ]) Result @insert(table: "Book")
  createAuthors(data: [ { id: "aaa", ... }, { id: "bbb", ... } ]) Result @insert(table: "Author")
}

Improve `isReference`

isReference should only check name ends in Connection, then validate that is non-null object type

  • would need to rewrite logic
  • also in mutation
  • e.g. something like isType(type, type => type.name.endsWith("Connection"))

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.