Skip to content

feat: Add schema.Collection#2593

Merged
ntucker merged 1 commit into
masterfrom
collections
Apr 26, 2023
Merged

feat: Add schema.Collection#2593
ntucker merged 1 commit into
masterfrom
collections

Conversation

@ntucker

@ntucker ntucker commented Apr 18, 2023

Copy link
Copy Markdown
Collaborator

Fixes #2566, #1548, #2602, #2553, #2554, #428, #398, #358 .

Motivation

Atomic mutations are one of the core values of Rest Hooks. However, while update was in the first pre-release, and delete quickly added for version 1, create has always been a bit awkward.

Often creating a new element you are on a view where you want to see it added to a list somewhere.

One of the challenges here is that there can be many distinct lists you view. Furthermore, where something is added to a list isn't always the same.

Previously

https://resthooks.io/docs/concepts/atomic-mutations#create

const createUser = new Endpoint(postToUserFunction, {
  schema: User,
  update: (newUserId: string) => ({
    [userList.key()]: (users = []) => [newUserId, ...users],
  }),
});

Problems:

  • Very awkward code we force users to write
  • Users only had access to the pk, rather than full entity
  • Only works on top level results (doesn't work with nesting)
  • Forces users to enumerate all lists they might want to add - even if they aren't in store
    • Which means they may create a list that hasn't been populated

TODO

  • Test Values
  • Test addWith
  • Test pagination
  • Add simpler cursor name pagination
  • Test nested collections
  • Unions
  • .insert

Solution

Collections: Entities but for Arrays and Values instead of classes.

They literally are entities, so they get normalized as such. They just have different lifecycle implementations.

Inspiration: Backbone collections

Usage

class Todo extends IDEntity {
  userId = 0;
  title = '';
  completed = false;

  static key = 'Todo';
}

class User extends IDEntity {
  name = '';
  username = '';
  email = '';
  todos: Todo[] = [];

  static key = 'User';
  static schema = {
    todos: new schema.Collection([Todo], {
      nestKey: (parent, key) => ({
        userId: parent.id,
      }),
    }),
  };
}

const UserResource = createResource({
  path: '/users/:id',
  schema: User,
});
const TodoResource = createResource({
  path: '/todos/:id',
  searchParams: {} as { userId?: string } | undefined,
  schema: Todo,
});
const handleCreate = data => ctrl.fetch(TodoResource.getList.push, { userId: currentuser.id }, data);

This will add to these if they are already in store (but only if they already exist):

  • User({ id: currentUser.id }).todos
  • getTodo({ userId: currentUser.id })
  • getTodo()

Collection

construction

Takes Array or Values as first arg. Second arg is options object.

const userTodos = new schema.Collection(new schema.Array(Todo), {
  argsKey: ({ userId }: { userId: string }) => ({
    userId,
  }),
});

Options:

Must provide argsKey or nestKey to compute a serializable object to be used in PK table. argsKey takes ...args and nestKey takes (parent,key) as inputs.

createCollectionFilter is optional and can be used to override the default filter algorithm. This is used by the "adders" as they inherit from the collection.

const defaultFilter =
  (urlParams: Record<string, any>, body?: Record<string, any>) =>
  (collectionKey: Record<string, any>) =>
    Object.entries(collectionKey).every(
      ([key, value]) =>
        key.startsWith('order') ||
        urlParams[key] === value ||
        body?.[key] === value,
    );

addWith(merge, createCollectionFilter)

This is the general collection extender method. All specializations simply call this method. This is also used in the paginator as it has a custom algorithm.

merge overrides the Entity.merge() algorithm. This is used to determine where new values are placed (end, beginning, somewhere in the middle?)

createCollectionFilter is optional and will override the inherited function from the Collection itself.

push/unshift/assign

These all call addWith() with variations on merge to place newly created objects at the beginning or end respectively for Arrays. assign is for Values() and simply merges (there is no concept of order in Values)

RestEndpoint adder specializations

For a RestEndpoint containing a collection schema, the extenders push/unshift/assign can be used as convenience to create an appropriate endpoint. They use the parent schema, but us their respective addWith() variations. Additionally this sets the method to "POST".

Pagination

.paginated() now detects whether there is a collection or array. With collections it will use a custom extender. Arrays will work via update.

new .paginated('cursor') - 99% of paginations simply extract one field from the args. Let the user specify that field name rather than writing a whole function.

const handleLoadNextPage = () => ctrl.fetch(TodoResource.getList.paginated('cursor'), {cursor});

Normalizr changes

args are now part of both normalization and denormalization. Of course these will default to an empty array in case users don't need args. However, they will need to be passed on my implementations such as schema.normalize(). This shouldn't be a problem as schema implementations without it wont be expecting it anyway.

pk() now takes a fourth argument - args.

next/createResource

  • Uses collections instead of arrays
    • create uses getList.push
  • Added new options
    • searchParams
      • searchParams only applies to getList and create.
    • body
      • body only applies to update,create,partialUpdate
    • requestInit
    • getHeaders
    • getRequestInit
    • fetchResponse
    • parseResponse

@changeset-bot

changeset-bot Bot commented Apr 18, 2023

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 62d48bb

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercel Bot commented Apr 18, 2023

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
rest-hooks ✅ Ready (Inspect) Visit Preview 💬 Add feedback Apr 26, 2023 8:40pm

} else {
if (typeof schema.denormalizeOnly === 'function') {
localCacheKey[pk] = schema.denormalizeOnly(entityCopy, unvisit);
localCacheKey[pk] = schema.denormalizeOnly(entityCopy, args, unvisit);

Check warning

Code scanning / CodeQL

Prototype-polluting assignment

This assignment may alter Object.prototype if a malicious '__proto__' string is injected from [library input](1). This assignment may alter Object.prototype if a malicious '__proto__' string is injected from [library input](2). This assignment may alter Object.prototype if a malicious '__proto__' string is injected from [library input](3). This assignment may alter Object.prototype if a malicious '__proto__' string is injected from [library input](4). This assignment may alter Object.prototype if a malicious '__proto__' string is injected from [library input](5). This assignment may alter Object.prototype if a malicious '__proto__' string is injected from [library input](6). This assignment may alter Object.prototype if a malicious '__proto__' string is injected from [library input](7). This assignment may alter Object.prototype if a malicious '__proto__' string is injected from [library input](8). This assignment may alter Object.prototype if a malicious '__proto__' string is injected from [library input](9). This assignment may alter Object.prototype if a malicious '__proto__' string is injected from [library input](10). This assignment may alter Object.prototype if a malicious '__proto__' string is injected from [library input](11).
@ntucker
ntucker force-pushed the collections branch 3 times, most recently from f190842 to dcda13e Compare April 20, 2023 17:37
@ntucker
ntucker force-pushed the collections branch 2 times, most recently from 8bbe78e to 6c0f8ae Compare April 20, 2023 20:29
@codecov

codecov Bot commented Apr 20, 2023

Copy link
Copy Markdown

Codecov Report

Patch coverage: 97.93% and project coverage change: -0.08 ⚠️

Comparison is base (950a982) 98.06% compared to head (62d48bb) 97.98%.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #2593      +/-   ##
==========================================
- Coverage   98.06%   97.98%   -0.08%     
==========================================
  Files         131      135       +4     
  Lines        2165     2287     +122     
  Branches      328      357      +29     
==========================================
+ Hits         2123     2241     +118     
  Misses         19       19              
- Partials       23       27       +4     
Impacted Files Coverage Δ
packages/core/src/controller/BaseController.ts 95.37% <ø> (ø)
packages/core/src/state/reducer/setReducer.ts 94.11% <ø> (ø)
packages/endpoint/src/schemas/Entity.ts 96.92% <ø> (ø)
packages/endpoint/src/schemas/Union.ts 100.00% <ø> (ø)
packages/endpoint/src/schemas/Values.ts 100.00% <ø> (ø)
packages/normalizr/src/schemas/ImmutableUtils.ts 92.85% <ø> (ø)
packages/normalizr/src/denormalize/globalCache.ts 98.07% <66.66%> (-1.93%) ⬇️
packages/endpoint/src/schemas/Collection.ts 95.16% <96.61%> (ø)
packages/endpoint/src/queryEndpoint.ts 100.00% <100.00%> (ø)
packages/endpoint/src/schemas/All.ts 100.00% <100.00%> (ø)
... and 15 more

☔ View full report in Codecov by Sentry.
📢 Do you have feedback about the report comment? Let us know in this issue.

@ntucker
ntucker force-pushed the collections branch 2 times, most recently from 4c6bcf1 to 23d2d7f Compare April 21, 2023 01:48
@ntucker
ntucker force-pushed the collections branch 2 times, most recently from 23de592 to 34a5e08 Compare April 24, 2023 00:16
@ntucker
ntucker force-pushed the collections branch 5 times, most recently from 890c1ff to 7eadbab Compare April 24, 2023 03:40
@ntucker

ntucker commented Apr 26, 2023

Copy link
Copy Markdown
Collaborator Author

Will update with insertions in a follow up PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants