@agentshouse/mdruntime-families 0.1.0-cdecd9be0238f → 0.1.0-ce42360f45634

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -40,12 +40,15 @@ compare for equality, a list for membership, and the suffixes `_gt`, `_gte`,
40
40
  `lt`, `gt`, `lte`, `gte`, `in`, `nin`, `or` and `and`. A field the Room schema
41
41
  declares a reference, at any depth, holds the referenced record's id; `id` and
42
42
  a reference compare only for equality and membership. Pages and totals are
43
- counted over every matching record. react-admin's `update` writes the fields
43
+ counted over every matching record, and a page reads only its own window of
44
+ the Room; a list asked for without a page reads every record, window by
45
+ window. react-admin's `update` writes the fields
44
46
  that differ from `previousData`, based on it, so the Save of a field another
45
47
  writer changed meanwhile is refused; Refine's `update` and either `updateMany`
46
48
  compare with the stored record. A list stays current: react-admin's queries of
47
- a resource are invalidated, and Refine's live mode refetches, when a document
48
- of the resource changes.
49
+ a resource are invalidated, and Refine's live mode refetches, when a record a
50
+ page or a read shows changes, a record a filter names starts or stops matching
51
+ it, or a page's total changes.
49
52
 
50
53
  ## License
51
54
 
@@ -1 +1 @@
1
- {"version":3,"file":"react-admin.d.ts","sourceRoot":"","sources":["../src/react-admin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,KAAK,EAEV,YAAY,EAYb,MAAM,SAAS,CAAC;AACjB,OAAO,EAAiD,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAE7F,YAAY,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAExD,MAAM,MAAM,YAAY,GAAG;IACzB,iBAAiB,CAAC,OAAO,EAAE;QAAE,QAAQ,EAAE,SAAS,OAAO,EAAE,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAChF,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,WAAW,EAAE,YAAY,CAAC;CACpC,CAAC;AAiCF,wBAAgB,YAAY,CAAC,MAAM,EAAE,eAAe,EAAE,OAAO,EAAE,mBAAmB,GAAG,YAAY,CAoFhG"}
1
+ {"version":3,"file":"react-admin.d.ts","sourceRoot":"","sources":["../src/react-admin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,KAAK,EAEV,YAAY,EAYb,MAAM,SAAS,CAAC;AACjB,OAAO,EAAgD,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAE5F,YAAY,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAExD,MAAM,MAAM,YAAY,GAAG;IACzB,iBAAiB,CAAC,OAAO,EAAE;QAAE,QAAQ,EAAE,SAAS,OAAO,EAAE,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAChF,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,WAAW,EAAE,YAAY,CAAC;CACpC,CAAC;AAgCF,wBAAgB,YAAY,CAAC,MAAM,EAAE,eAAe,EAAE,OAAO,EAAE,mBAAmB,GAAG,YAAY,CAqFhG"}
@@ -17,11 +17,10 @@ function clause(key, value) {
17
17
  value,
18
18
  };
19
19
  }
20
- function paged(entries, pagination) {
20
+ function pageOf(pagination) {
21
21
  if (pagination === undefined)
22
- return [...entries];
23
- const start = (pagination.page - 1) * pagination.perPage;
24
- return entries.slice(start, start + pagination.perPage);
22
+ return null;
23
+ return { start: (pagination.page - 1) * pagination.perPage, size: pagination.perPage };
25
24
  }
26
25
  export function dataProvider(client, options) {
27
26
  const held = records(client, options.resources);
@@ -40,10 +39,10 @@ export function dataProvider(client, options) {
40
39
  async function listed(resource, { filter, sort, pagination }) {
41
40
  await watched(resource);
42
41
  const named = Object.entries(filter ?? {});
43
- const entries = await held.list(resource, named.length === 0 ? null : { every: named.map(([key, value]) => clause(key, value)) }, sort === undefined
42
+ const { entries, total } = await held.list(resource, named.length === 0 ? null : { every: named.map(([key, value]) => clause(key, value)) }, sort === undefined
44
43
  ? []
45
- : [{ field: sort.field, direction: sort.order === 'DESC' ? 'desc' : 'asc' }]);
46
- return { data: paged(entries, pagination), total: entries.length };
44
+ : [{ field: sort.field, direction: sort.order === 'DESC' ? 'desc' : 'asc' }], pageOf(pagination));
45
+ return { data: [...entries], total };
47
46
  }
48
47
  return {
49
48
  getList: async (resource, params) => (await listed(resource, params)),
package/dist/records.d.ts CHANGED
@@ -24,8 +24,16 @@ export type Clause = {
24
24
  readonly operator: Operator;
25
25
  readonly value: unknown;
26
26
  };
27
+ export type Page = {
28
+ readonly start: number;
29
+ readonly size: number;
30
+ };
31
+ export type Listed = {
32
+ readonly entries: readonly Entry[];
33
+ readonly total: number;
34
+ };
27
35
  export declare function records(client: MdRuntimeClient, resources: Resources): {
28
- list(name: string, clause: Clause | null, order: readonly Order[]): Promise<Entry[]>;
36
+ list(name: string, clause: Clause | null, order: readonly Order[], page: Page | null): Promise<Listed>;
29
37
  many(name: string, ids: readonly Key[]): Promise<Entry[]>;
30
38
  one(name: string, id: Key): Promise<Entry>;
31
39
  create(name: string, data: Fields): Promise<Entry>;
@@ -1 +1 @@
1
- {"version":3,"file":"records.d.ts","sourceRoot":"","sources":["../src/records.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAGrE,MAAM,MAAM,QAAQ,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE1E,MAAM,MAAM,SAAS,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;AAE3D,MAAM,MAAM,GAAG,GAAG,MAAM,GAAG,MAAM,CAAC;AAElC,MAAM,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAEvD,MAAM,MAAM,KAAK,GAAG;IAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,CAAC;AAE/E,MAAM,MAAM,KAAK,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,KAAK,GAAG,MAAM,CAAA;CAAE,CAAC;AAEnF,MAAM,MAAM,QAAQ,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,CAAC;AAExF,MAAM,MAAM,MAAM,GACd;IAAE,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GACrC;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GACpC;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC;AA8JrF,wBAAgB,OAAO,CAAC,MAAM,EAAE,eAAe,EAAE,SAAS,EAAE,SAAS;IAkJ3D,IAAI,OAAO,MAAM,UAAU,MAAM,GAAG,IAAI,SAAS,SAAS,KAAK,EAAE,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;IAcpF,IAAI,OAAO,MAAM,OAAO,SAAS,GAAG,EAAE,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;IAOzD,GAAG,OAAO,MAAM,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC;IAM1C,MAAM,OAAO,MAAM,QAAQ,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;IAiBlD,MAAM,OACJ,MAAM,OACP,SAAS,GAAG,EAAE,QACb,MAAM,SACL,MAAM,GAAG,IAAI,GACnB,OAAO,CAAC,KAAK,EAAE,CAAC;IAab,MAAM,OAAO,MAAM,OAAO,SAAS,GAAG,EAAE,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;IAiB3D,KAAK,OAAO,MAAM,SAAS,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,IAAI,CAAC;EAWpE"}
1
+ {"version":3,"file":"records.d.ts","sourceRoot":"","sources":["../src/records.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAErE,MAAM,MAAM,QAAQ,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE1E,MAAM,MAAM,SAAS,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;AAE3D,MAAM,MAAM,GAAG,GAAG,MAAM,GAAG,MAAM,CAAC;AAElC,MAAM,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAEvD,MAAM,MAAM,KAAK,GAAG;IAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,CAAC;AAE/E,MAAM,MAAM,KAAK,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,KAAK,GAAG,MAAM,CAAA;CAAE,CAAC;AAEnF,MAAM,MAAM,QAAQ,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,CAAC;AAExF,MAAM,MAAM,MAAM,GACd;IAAE,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GACrC;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GACpC;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC;AAErF,MAAM,MAAM,IAAI,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAErE,MAAM,MAAM,MAAM,GAAG;IAAE,QAAQ,CAAC,OAAO,EAAE,SAAS,KAAK,EAAE,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAiTpF,wBAAgB,OAAO,CAAC,MAAM,EAAE,eAAe,EAAE,SAAS,EAAE,SAAS;IAob3D,IAAI,OACF,MAAM,UACJ,MAAM,GAAG,IAAI,SACd,SAAS,KAAK,EAAE,QACjB,IAAI,GAAG,IAAI,GAChB,OAAO,CAAC,MAAM,CAAC;IAuBZ,IAAI,OAAO,MAAM,OAAO,SAAS,GAAG,EAAE,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;IAOzD,GAAG,OAAO,MAAM,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC;IAM1C,MAAM,OAAO,MAAM,QAAQ,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;IAqBlD,MAAM,OACJ,MAAM,OACP,SAAS,GAAG,EAAE,QACb,MAAM,SACL,MAAM,GAAG,IAAI,GACnB,OAAO,CAAC,KAAK,EAAE,CAAC;IAab,MAAM,OAAO,MAAM,OAAO,SAAS,GAAG,EAAE,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;IAkB3D,KAAK,OAAO,MAAM,SAAS,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,IAAI,CAAC;EAmBpE"}
package/dist/records.js CHANGED
@@ -1,9 +1,11 @@
1
1
  import { ARRAY_INDEX, equalValue, escapePointer, itemFacts, memberFacts, pointerSegments, references, relative, target, } from '@agentshouse/mdmodel';
2
- import { saveRoomDrafts } from '@agentshouse/mdruntime/react';
3
2
  const FIELDS = { fields: 'all' };
4
3
  const REMOVABLE = { fields: 'all', markdown: true };
5
4
  const BY_ID = [{ field: 'id', direction: 'asc' }];
6
5
  const RESERVED = new Set(['id', 'type']);
6
+ const TRUE = { kind: 'true' };
7
+ const FALSE = { kind: 'false' };
8
+ const WATCHED = new WeakMap();
7
9
  const EQUALITY = {
8
10
  $eq: true,
9
11
  $in: true,
@@ -124,7 +126,128 @@ function byRoom(items) {
124
126
  }
125
127
  return rooms;
126
128
  }
129
+ function complete(answer) {
130
+ return !('pending' in answer);
131
+ }
132
+ function placeKey({ room, path }) {
133
+ return JSON.stringify([room, path]);
134
+ }
135
+ function joined(kind, items) {
136
+ const absorbing = kind === 'and' ? FALSE : TRUE;
137
+ const neutral = kind === 'and' ? TRUE : FALSE;
138
+ const kept = items
139
+ .flatMap((item) => (item.kind === kind ? item.items : [item]))
140
+ .filter((item) => item.kind !== neutral.kind);
141
+ if (kept.some((item) => item.kind === absorbing.kind))
142
+ return absorbing;
143
+ if (kept.length === 0)
144
+ return neutral;
145
+ return kept.length === 1 ? kept[0] : { kind, items: kept };
146
+ }
147
+ function formulaOf(shelf, room, clause) {
148
+ if ('every' in clause) {
149
+ return joined('and', clause.every.map((one) => formulaOf(shelf, room, one)));
150
+ }
151
+ if ('some' in clause) {
152
+ return joined('or', clause.some.map((one) => formulaOf(shelf, room, one)));
153
+ }
154
+ const { field, operator, value } = clause;
155
+ const reference = [...shelf.definitions.values()].some((definition) => referring(factsAt(definition.frontmatter, field)));
156
+ if (field !== 'id' && !reference) {
157
+ return { kind: 'where', where: { [pointer(field)]: { [operator]: value } } };
158
+ }
159
+ const equal = equality(field, operator);
160
+ const places = listed(value).map((id) => (id === null ? null : placeOf(shelf, id)));
161
+ const paths = places.flatMap((place) => place !== null && place.room === room ? [place.path] : []);
162
+ if (field === 'id') {
163
+ if (paths.length === 0)
164
+ return equal ? FALSE : TRUE;
165
+ return { kind: 'path', paths: new Set(paths), holds: equal };
166
+ }
167
+ const values = [...paths, ...(places.includes(null) ? [null] : [])];
168
+ if (values.length === 0 || !referring(factsAt(shelf.definitions.get(room)?.frontmatter, field))) {
169
+ return equal ? FALSE : TRUE;
170
+ }
171
+ return { kind: 'where', where: { [pointer(field)]: { [equal ? '$in' : '$nin']: values } } };
172
+ }
173
+ function namedPaths(formula) {
174
+ if (formula.kind === 'path')
175
+ return [...formula.paths];
176
+ if (formula.kind === 'and' || formula.kind === 'or') {
177
+ return [...new Set(formula.items.flatMap(namedPaths))];
178
+ }
179
+ return [];
180
+ }
181
+ function decided(formula, path) {
182
+ if (formula.kind === 'path') {
183
+ return (path !== null && formula.paths.has(path)) === formula.holds ? TRUE : FALSE;
184
+ }
185
+ if (formula.kind === 'and' || formula.kind === 'or') {
186
+ return joined(formula.kind, formula.items.map((item) => decided(item, path)));
187
+ }
188
+ return formula;
189
+ }
190
+ function whereOf(formula) {
191
+ if (formula.kind === 'where')
192
+ return formula.where;
193
+ if (formula.kind === 'and')
194
+ return { $and: formula.items.map((item) => whereOf(item) ?? {}) };
195
+ if (formula.kind === 'or')
196
+ return { $or: formula.items.map((item) => whereOf(item) ?? {}) };
197
+ return undefined;
198
+ }
199
+ function filtered(where) {
200
+ return where === undefined ? {} : { where };
201
+ }
202
+ function afterOf(after) {
203
+ return after === undefined ? {} : { after };
204
+ }
205
+ function watchedOf(client) {
206
+ const held = WATCHED.get(client);
207
+ if (held !== undefined)
208
+ return held;
209
+ const fresh = {
210
+ walks: new Map(),
211
+ listeners: new Map(),
212
+ read: new Map(),
213
+ watching: new Map(),
214
+ };
215
+ WATCHED.set(client, fresh);
216
+ return fresh;
217
+ }
127
218
  export function records(client, resources) {
219
+ const { walks, listeners, read, watching } = watchedOf(client);
220
+ async function answerOf(query) {
221
+ const answer = await client.query(query);
222
+ if (complete(answer))
223
+ return answer;
224
+ const observation = await client.observe('query', query);
225
+ try {
226
+ return await new Promise((resolve) => {
227
+ const settle = (value) => {
228
+ if (complete(value))
229
+ resolve(value);
230
+ };
231
+ observation.subscribe(settle);
232
+ settle(observation.current());
233
+ });
234
+ }
235
+ finally {
236
+ observation.close();
237
+ }
238
+ }
239
+ async function everyOf(query) {
240
+ const walked = { documents: [], windows: [] };
241
+ let after;
242
+ do {
243
+ const window = { ...query, ...afterOf(after) };
244
+ const answer = await answerOf(window);
245
+ walked.windows.push(window);
246
+ walked.documents.push(...answer.documents);
247
+ after = answer.next;
248
+ } while (after !== undefined);
249
+ return walked;
250
+ }
128
251
  function resourceOf(name) {
129
252
  const resource = Object.hasOwn(resources, name) ? resources[name] : undefined;
130
253
  if (resource === undefined)
@@ -137,7 +260,7 @@ export function records(client, resources) {
137
260
  const definitions = new Map();
138
261
  for (const room of rooms) {
139
262
  const schema = (await client.types(room)).collection;
140
- if (schema.status === 'unavailable')
263
+ if (schema.types === null)
141
264
  continue;
142
265
  const definition = schema.types.find((type) => type.name === resource.type);
143
266
  if (definition === undefined)
@@ -146,98 +269,308 @@ export function records(client, resources) {
146
269
  }
147
270
  return { resource, rooms, definitions };
148
271
  }
149
- async function keys(shelf, query) {
150
- const { documents } = await client.query({
151
- ...query,
152
- types: [shelf.resource.type],
153
- pathPrefix: shelf.resource.folder,
154
- select: {},
272
+ function folderOf(shelf) {
273
+ return { types: [shelf.resource.type], pathPrefix: shelf.resource.folder };
274
+ }
275
+ function closed(observations) {
276
+ void observations?.then((all) => {
277
+ for (const observation of all)
278
+ observation.close();
279
+ }, () => { });
280
+ }
281
+ function touched(name) {
282
+ walks.delete(name);
283
+ read.delete(name);
284
+ const held = watching.get(name);
285
+ if (held !== undefined) {
286
+ watching.set(name, new Map());
287
+ for (const observations of held.values())
288
+ closed(observations);
289
+ }
290
+ for (const heard of listeners.get(name) ?? [])
291
+ heard();
292
+ }
293
+ function observed(name, view, queries) {
294
+ const held = watching.get(name);
295
+ if (held === undefined)
296
+ return;
297
+ const before = held.get(view);
298
+ const opening = Promise.all(queries.map(async (query) => {
299
+ const observation = await client.observe('query', query);
300
+ observation.subscribe(() => {
301
+ if (watching.get(name)?.get(view) === opening)
302
+ touched(name);
303
+ });
304
+ return observation;
305
+ }));
306
+ held.set(view, opening);
307
+ opening.catch(() => {
308
+ if (watching.get(name)?.get(view) === opening)
309
+ held.delete(view);
155
310
  });
156
- return new Set(documents.map((document) => keyOf(shelf, document)));
311
+ closed(before);
157
312
  }
158
- async function referenced(shelf, field, places) {
159
- const kept = new Set();
313
+ function remember(name, view, queries) {
314
+ const views = read.get(name) ?? new Map();
315
+ views.set(view, queries);
316
+ read.set(name, views);
317
+ observed(name, view, queries);
318
+ }
319
+ async function selectionOf(shelf, clause) {
320
+ const streams = new Map();
321
+ const named = [];
322
+ const lookups = [];
160
323
  for (const room of shelf.rooms) {
161
- const paths = places.flatMap((place) => place === null ? [null] : place.room === room ? [place.path] : []);
162
- const definition = shelf.definitions.get(room);
163
- if (paths.length === 0 || !referring(factsAt(definition?.frontmatter, field)))
164
- continue;
165
- const where = { [pointer(field)]: { $in: paths } };
166
- for (const key of await keys(shelf, { rooms: [room], where }))
167
- kept.add(key);
168
- }
169
- return kept;
170
- }
171
- async function matching(shelf, all, clause) {
172
- if ('every' in clause) {
173
- let kept = new Set(all.map((document) => keyOf(shelf, document)));
174
- for (const one of clause.every) {
175
- const next = await matching(shelf, all, one);
176
- kept = new Set([...kept].filter((key) => next.has(key)));
324
+ const formula = clause === null ? TRUE : formulaOf(shelf, room, clause);
325
+ const paths = namedPaths(formula);
326
+ const rest = decided(formula, null);
327
+ if (rest.kind !== 'false') {
328
+ const where = whereOf(rest);
329
+ const key = JSON.stringify(where ?? null);
330
+ const stream = streams.get(key) ?? { rooms: [], where, excluded: new Set() };
331
+ stream.rooms.push(room);
332
+ for (const path of paths)
333
+ stream.excluded.add(placeKey({ room, path }));
334
+ streams.set(key, stream);
335
+ }
336
+ const each = new Map();
337
+ for (const path of paths) {
338
+ const kept = decided(formula, path);
339
+ if (kept.kind === 'false')
340
+ continue;
341
+ const where = whereOf(kept);
342
+ const key = JSON.stringify(where ?? null);
343
+ const held = each.get(key) ?? { where, paths: [] };
344
+ held.paths.push(path);
345
+ each.set(key, held);
177
346
  }
178
- return kept;
347
+ for (const { where, paths: these } of each.values()) {
348
+ const lookup = {
349
+ ...folderOf(shelf),
350
+ rooms: [room],
351
+ paths: these,
352
+ ...filtered(where),
353
+ select: {},
354
+ };
355
+ const found = await everyOf(lookup);
356
+ lookups.push(...found.windows);
357
+ named.push(...found.documents.map(({ room: at, path }) => ({ room: at, path })));
358
+ }
359
+ }
360
+ return { streams: [...streams.values()], named, lookups };
361
+ }
362
+ function streamQuery(shelf, stream, orderBy) {
363
+ return {
364
+ ...folderOf(shelf),
365
+ rooms: stream.rooms,
366
+ ...filtered(stream.where),
367
+ orderBy,
368
+ select: {},
369
+ };
370
+ }
371
+ function walkOf(name, query) {
372
+ const fresh = { total: null, cursors: new Map([[0, undefined]]) };
373
+ if (!watching.has(name))
374
+ return fresh;
375
+ const walked = walks.get(name) ?? new Map();
376
+ walks.set(name, walked);
377
+ const key = JSON.stringify(query);
378
+ const walk = walked.get(key) ?? fresh;
379
+ walked.set(key, walk);
380
+ return walk;
381
+ }
382
+ async function cursorAt({ cursors }, query, start) {
383
+ let at = Math.max(...[...cursors.keys()].filter((one) => one <= start));
384
+ let after = cursors.get(at);
385
+ while (at < start) {
386
+ const window = await answerOf({ ...query, limit: start - at, ...afterOf(after) });
387
+ if (window.next === undefined)
388
+ return null;
389
+ at += window.documents.length;
390
+ after = window.next;
391
+ cursors.set(at, after);
179
392
  }
180
- if ('some' in clause) {
181
- const kept = new Set();
182
- for (const one of clause.some) {
183
- for (const key of await matching(shelf, all, one))
184
- kept.add(key);
393
+ return after;
394
+ }
395
+ function prefixOf({ cursors }, query, start) {
396
+ const at = [...cursors.keys()]
397
+ .filter((one) => one <= start)
398
+ .sort((left, right) => left - right);
399
+ return at.slice(1).map((to, index) => ({
400
+ ...query,
401
+ limit: to - at[index],
402
+ ...afterOf(cursors.get(at[index])),
403
+ }));
404
+ }
405
+ async function paged(name, shelf, view, query, { start, size }, again = true) {
406
+ const walk = walkOf(name, query);
407
+ const after = await cursorAt(walk, query, start);
408
+ const window = {
409
+ ...query,
410
+ ...(after === null ? { limit: 0 } : { select: FIELDS, limit: size, ...afterOf(after) }),
411
+ total: true,
412
+ };
413
+ const page = await answerOf(window);
414
+ const total = page.total ?? 0;
415
+ if (walk.total !== null && walk.total !== total && again) {
416
+ walks.get(name)?.delete(JSON.stringify(query));
417
+ return paged(name, shelf, view, query, { start, size }, false);
418
+ }
419
+ walk.total = total;
420
+ if (page.next !== undefined && after !== null) {
421
+ walk.cursors.set(start + page.documents.length, page.next);
422
+ }
423
+ remember(name, view, [
424
+ ...(start === 0 || after === null ? [] : prefixOf(walk, query, start)),
425
+ after === null ? { ...query, limit: start, total: true } : window,
426
+ ]);
427
+ return { entries: page.documents.map((document) => entryOf(shelf, document)), total };
428
+ }
429
+ async function sortedPlaces(shelf, places, orderBy) {
430
+ if (places.length <= 1)
431
+ return [...places];
432
+ const wanted = new Set(places.map(placeKey));
433
+ const sorted = await everyOf({
434
+ ...folderOf(shelf),
435
+ rooms: [...new Set(places.map(({ room }) => room))],
436
+ paths: [...new Set(places.map(({ path }) => path))],
437
+ orderBy,
438
+ select: {},
439
+ });
440
+ return sorted.documents.flatMap(({ room, path }) => wanted.has(placeKey({ room, path })) ? [{ room, path }] : []);
441
+ }
442
+ function streamSource(query, excluded) {
443
+ let after;
444
+ const source = {
445
+ buffer: [],
446
+ more: true,
447
+ windows: [],
448
+ async read(limit) {
449
+ const asked = { ...query, ...(limit === null ? {} : { limit }), ...afterOf(after) };
450
+ const window = await answerOf(asked);
451
+ after = window.next;
452
+ source.more = after !== undefined;
453
+ source.windows.push(asked);
454
+ source.buffer.push(...window.documents.flatMap(({ room, path }) => excluded.has(placeKey({ room, path })) ? [] : [{ room, path }]));
455
+ },
456
+ };
457
+ return source;
458
+ }
459
+ async function mergedPlaces(shelf, selection, orderBy, end) {
460
+ const queries = selection.streams.map((stream) => streamQuery(shelf, stream, orderBy));
461
+ const streams = selection.streams.map((stream, at) => streamSource(queries[at], stream.excluded));
462
+ const sources = [
463
+ ...streams,
464
+ {
465
+ buffer: await sortedPlaces(shelf, selection.named, orderBy),
466
+ more: false,
467
+ windows: [],
468
+ read: async () => { },
469
+ },
470
+ ];
471
+ const merged = [];
472
+ while (merged.length < end) {
473
+ const wanted = end - merged.length;
474
+ for (const source of sources) {
475
+ while (source.buffer.length === 0 && source.more) {
476
+ await source.read(Number.isFinite(wanted) ? wanted : null);
477
+ }
185
478
  }
186
- return kept;
479
+ const filled = sources.filter((source) => source.buffer.length > 0);
480
+ if (filled.length === 0)
481
+ break;
482
+ const order = filled.length === 1
483
+ ? [...filled[0].buffer]
484
+ : await sortedPlaces(shelf, filled.flatMap((source) => source.buffer), orderBy);
485
+ const at = order.map(placeKey);
486
+ const cut = Math.min(order.length - 1, ...filled
487
+ .filter((source) => source.more)
488
+ .map((source) => at.indexOf(placeKey(source.buffer[source.buffer.length - 1]))));
489
+ const emitted = order.slice(0, cut + 1);
490
+ const gone = new Set(emitted.map(placeKey));
491
+ for (const source of filled) {
492
+ source.buffer = source.buffer.filter((place) => !gone.has(placeKey(place)));
493
+ }
494
+ merged.push(...emitted);
187
495
  }
188
- const { field, operator, value } = clause;
189
- const reference = [...shelf.definitions.values()].some((definition) => referring(factsAt(definition.frontmatter, field)));
190
- if (field !== 'id' && !reference) {
191
- return keys(shelf, { where: { [pointer(field)]: { [operator]: value } } });
496
+ return {
497
+ places: merged.slice(0, end),
498
+ windows: streams.flatMap((source, at) => [
499
+ { ...queries[at], limit: 0, total: true },
500
+ ...source.windows,
501
+ ]),
502
+ };
503
+ }
504
+ async function totalOf(shelf, selection, orderBy) {
505
+ let total = selection.named.length;
506
+ for (const stream of selection.streams) {
507
+ const query = streamQuery(shelf, stream, orderBy);
508
+ total += (await answerOf({ ...query, limit: 0, total: true })).total ?? 0;
509
+ const excluded = new Map();
510
+ for (const key of stream.excluded) {
511
+ const [room, path] = JSON.parse(key);
512
+ excluded.set(room, [...(excluded.get(room) ?? []), path]);
513
+ }
514
+ for (const [room, paths] of excluded) {
515
+ const counted = { ...query, rooms: [room], paths, limit: 0, total: true };
516
+ total -= (await answerOf(counted)).total ?? 0;
517
+ }
192
518
  }
193
- const equal = equality(field, operator);
194
- const places = listed(value).map((id) => (id === null ? null : placeOf(shelf, id)));
195
- const matched = field === 'id'
196
- ? new Set(places.flatMap((place) => place === null ? [] : [idOf(shelf, place.room, place.path)]))
197
- : await referenced(shelf, field, places);
198
- return new Set(all.map((document) => keyOf(shelf, document)).filter((key) => matched.has(key) === equal));
199
- }
200
- async function found(shelf, ids, select) {
201
- const places = ids.map((id) => placeOf(shelf, id));
519
+ return total;
520
+ }
521
+ async function placed(shelf, places, select) {
522
+ const queries = [];
202
523
  const documents = new Map();
203
- for (const room of new Set(places.map((place) => place.room))) {
204
- const answer = await client.query({
524
+ const rooms = new Map();
525
+ for (const { room, path } of places) {
526
+ rooms.set(room, (rooms.get(room) ?? new Set()).add(path));
527
+ }
528
+ for (const [room, paths] of rooms) {
529
+ const walked = await everyOf({
530
+ ...folderOf(shelf),
205
531
  rooms: [room],
206
- types: [shelf.resource.type],
207
- pathPrefix: shelf.resource.folder,
532
+ paths: [...paths],
208
533
  select,
209
534
  });
210
- for (const document of answer.documents)
211
- documents.set(keyOf(shelf, document), document);
535
+ queries.push(...walked.windows);
536
+ for (const document of walked.documents)
537
+ documents.set(placeKey(document), document);
212
538
  }
213
- return places.map((place) => ({
214
- place,
215
- document: documents.get(idOf(shelf, place.room, place.path)) ?? null,
216
- }));
539
+ return { queries, documents };
540
+ }
541
+ async function merged(name, shelf, view, selection, orderBy, page) {
542
+ const end = page === null ? Number.POSITIVE_INFINITY : page.start + page.size;
543
+ const { places, windows } = await mergedPlaces(shelf, selection, orderBy, end);
544
+ const shown = page === null ? places : places.slice(page.start);
545
+ const { queries, documents } = await placed(shelf, shown, FIELDS);
546
+ remember(name, view, [...windows, ...selection.lookups, ...queries]);
547
+ return {
548
+ entries: shown.flatMap((place) => {
549
+ const document = documents.get(placeKey(place));
550
+ return document === undefined ? [] : [entryOf(shelf, document)];
551
+ }),
552
+ total: page === null ? places.length : await totalOf(shelf, selection, orderBy),
553
+ };
217
554
  }
218
- async function held(shelf, ids, select) {
219
- return (await found(shelf, ids, select)).map(({ place, document }) => {
555
+ async function found(name, shelf, ids, select) {
556
+ const places = ids.map((id) => placeOf(shelf, id));
557
+ const { queries, documents } = await placed(shelf, places, select);
558
+ remember(name, JSON.stringify(['named', places.map(placeKey)]), queries);
559
+ return places.map((place) => ({ place, document: documents.get(placeKey(place)) ?? null }));
560
+ }
561
+ async function held(name, shelf, ids, select) {
562
+ return (await found(name, shelf, ids, select)).map(({ place, document }) => {
220
563
  if (document === null)
221
564
  throw new Error(`no ${shelf.resource.type} is held at ${place.path}`);
222
565
  return { place, document };
223
566
  });
224
567
  }
225
- async function dropped(room, paths) {
226
- for (const path of paths) {
227
- const left = await client.roomDraft.read(room, path);
228
- if (left !== null)
229
- await client.roomDraft.discard(room, path, left.revision);
230
- }
231
- }
232
- async function saved(room, parts) {
568
+ async function saved(name, room, parts) {
233
569
  if (parts.size === 0)
234
570
  return;
235
- const result = await saveRoomDrafts(client, room, parts).catch(async (error) => {
236
- await dropped(room, parts.keys());
237
- throw error;
238
- });
571
+ walks.delete(name);
572
+ const result = await client.saveParts(room, [...parts].map(([path, one]) => ({ path, parts: one })));
239
573
  if (result.outcome === 'refused') {
240
- await dropped(room, parts.keys());
241
574
  throw Object.assign(new Error(`the Save was refused as ${result.code}`, { cause: result }), {
242
575
  code: result.code,
243
576
  retryable: result.retryable,
@@ -245,26 +578,34 @@ export function records(client, resources) {
245
578
  }
246
579
  }
247
580
  return {
248
- async list(name, clause, order) {
581
+ async list(name, clause, order, page) {
249
582
  const shelf = await shelfOf(name);
250
- const { documents } = await client.query({
251
- types: [shelf.resource.type],
252
- pathPrefix: shelf.resource.folder,
253
- orderBy: (order.length === 0 ? BY_ID : order).flatMap((one) => sorting(shelf, one)),
254
- select: FIELDS,
255
- });
256
- const kept = clause === null ? null : await matching(shelf, documents, clause);
257
- return documents
258
- .filter((document) => kept === null || kept.has(keyOf(shelf, document)))
259
- .map((document) => entryOf(shelf, document));
583
+ const selection = await selectionOf(shelf, clause);
584
+ const orderBy = (order.length === 0 ? BY_ID : order).flatMap((one) => sorting(shelf, one));
585
+ const view = JSON.stringify(['list', clause, order]);
586
+ const [stream] = selection.streams;
587
+ if (selection.streams.length === 1 &&
588
+ stream.excluded.size === 0 &&
589
+ selection.named.length === 0) {
590
+ const query = streamQuery(shelf, stream, orderBy);
591
+ if (page !== null)
592
+ return paged(name, shelf, view, query, page);
593
+ const all = await everyOf({ ...query, select: FIELDS });
594
+ remember(name, view, all.windows);
595
+ return {
596
+ entries: all.documents.map((document) => entryOf(shelf, document)),
597
+ total: all.documents.length,
598
+ };
599
+ }
600
+ return merged(name, shelf, view, selection, orderBy, page);
260
601
  },
261
602
  async many(name, ids) {
262
603
  const shelf = await shelfOf(name);
263
- return (await found(shelf, ids, FIELDS)).flatMap(({ document }) => document === null ? [] : [entryOf(shelf, document)]);
604
+ return (await found(name, shelf, ids, FIELDS)).flatMap(({ document }) => document === null ? [] : [entryOf(shelf, document)]);
264
605
  },
265
606
  async one(name, id) {
266
607
  const shelf = await shelfOf(name);
267
- const [{ document }] = await held(shelf, [id], FIELDS);
608
+ const [{ document }] = await held(name, shelf, [id], FIELDS);
268
609
  return entryOf(shelf, document);
269
610
  },
270
611
  async create(name, data) {
@@ -278,28 +619,28 @@ export function records(client, resources) {
278
619
  const fields = Object.fromEntries(Object.entries(data)
279
620
  .filter(([field, value]) => !RESERVED.has(field) && value !== undefined)
280
621
  .map(([field, value]) => [field, written(shelf, place, field, value)]));
281
- await saved(room, new Map([[place.path, { creation: { type, fields, sections: {} } }]]));
282
- const [{ document }] = await held(shelf, [idOf(shelf, room, place.path)], FIELDS);
622
+ await saved(name, room, new Map([[place.path, { creation: { type, fields, sections: {} } }]]));
623
+ const [{ document }] = await held(name, shelf, [idOf(shelf, room, place.path)], FIELDS);
283
624
  return entryOf(shelf, document);
284
625
  },
285
626
  async update(name, ids, data, shown) {
286
627
  const shelf = await shelfOf(name);
287
- for (const [room, inRoom] of byRoom(await held(shelf, ids, FIELDS))) {
628
+ for (const [room, inRoom] of byRoom(await held(name, shelf, ids, FIELDS))) {
288
629
  const parts = new Map();
289
630
  for (const one of inRoom) {
290
631
  const part = changed(shelf, one, data, shown);
291
632
  if (part !== null)
292
633
  parts.set(one.place.path, part);
293
634
  }
294
- await saved(room, parts);
635
+ await saved(name, room, parts);
295
636
  }
296
- return (await held(shelf, ids, FIELDS)).map(({ document }) => entryOf(shelf, document));
637
+ return (await held(name, shelf, ids, FIELDS)).map(({ document }) => entryOf(shelf, document));
297
638
  },
298
639
  async remove(name, ids) {
299
640
  const shelf = await shelfOf(name);
300
- const removed = await held(shelf, ids, REMOVABLE);
641
+ const removed = await held(name, shelf, ids, REMOVABLE);
301
642
  for (const [room, inRoom] of byRoom(removed)) {
302
- await saved(room, new Map(inRoom.map(({ place, document }) => [
643
+ await saved(name, room, new Map(inRoom.map(({ place, document }) => [
303
644
  place.path,
304
645
  { removal: { base: document.markdown ?? null } },
305
646
  ])));
@@ -307,14 +648,25 @@ export function records(client, resources) {
307
648
  return removed.map(({ document }) => entryOf(shelf, document));
308
649
  },
309
650
  async watch(name, heard) {
310
- const { type, folder } = resourceOf(name);
311
- const observation = await client.observe('query', {
312
- types: [type],
313
- pathPrefix: folder,
314
- select: { markdown: true },
315
- });
316
- observation.subscribe(heard);
317
- return () => observation.close();
651
+ resourceOf(name);
652
+ const hearing = listeners.get(name) ?? new Set();
653
+ listeners.set(name, hearing);
654
+ hearing.add(heard);
655
+ if (!watching.has(name)) {
656
+ watching.set(name, new Map());
657
+ for (const [view, queries] of read.get(name) ?? [])
658
+ observed(name, view, queries);
659
+ }
660
+ return () => {
661
+ hearing.delete(heard);
662
+ if (hearing.size > 0)
663
+ return;
664
+ listeners.delete(name);
665
+ const observations = watching.get(name);
666
+ watching.delete(name);
667
+ for (const one of observations?.values() ?? [])
668
+ closed(one);
669
+ };
318
670
  },
319
671
  };
320
672
  }
@@ -1 +1 @@
1
- {"version":3,"file":"refine.d.ts","sourceRoot":"","sources":["../src/refine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,KAAK,EAMV,YAAY,EAMZ,YAAY,EAIb,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAML,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAEtB,YAAY,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAExD,MAAM,MAAM,eAAe,GAAG;IAAE,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAA;CAAE,CAAC;AAgChE,wBAAgB,YAAY,CAAC,MAAM,EAAE,eAAe,EAAE,OAAO,EAAE,eAAe,GAAG,YAAY,CAgF5F;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,eAAe,EAAE,OAAO,EAAE,eAAe,GAAG,YAAY,CAW5F"}
1
+ {"version":3,"file":"refine.d.ts","sourceRoot":"","sources":["../src/refine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,KAAK,EAMV,YAAY,EAMZ,YAAY,EAIb,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAML,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAEtB,YAAY,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAExD,MAAM,MAAM,eAAe,GAAG;IAAE,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAA;CAAE,CAAC;AA+BhE,wBAAgB,YAAY,CAAC,MAAM,EAAE,eAAe,EAAE,OAAO,EAAE,eAAe,GAAG,YAAY,CAiF5F;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,eAAe,EAAE,OAAO,EAAE,eAAe,GAAG,YAAY,CAW5F"}
package/dist/refine.js CHANGED
@@ -21,19 +21,18 @@ function clause(filter) {
21
21
  throw new Error(`the ${filter.operator} filter has no condition`);
22
22
  return { field: filter.field, operator, value: filter.value };
23
23
  }
24
- function paged(entries, pagination) {
24
+ function pageOf(pagination) {
25
25
  const { currentPage = 1, pageSize = 10, mode = 'server' } = pagination ?? {};
26
26
  if (mode !== 'server')
27
- return [...entries];
28
- const start = (currentPage - 1) * pageSize;
29
- return entries.slice(start, start + pageSize);
27
+ return null;
28
+ return { start: (currentPage - 1) * pageSize, size: pageSize };
30
29
  }
31
30
  export function dataProvider(client, options) {
32
31
  const held = records(client, options.resources);
33
32
  return {
34
33
  getList: async ({ resource, filters, sorters, pagination, }) => {
35
- const entries = await held.list(resource, filters === undefined || filters.length === 0 ? null : { every: filters.map(clause) }, (sorters ?? []).map(({ field, order }) => ({ field, direction: order })));
36
- return { data: paged(entries, pagination), total: entries.length };
34
+ const { entries, total } = await held.list(resource, filters === undefined || filters.length === 0 ? null : { every: filters.map(clause) }, (sorters ?? []).map(({ field, order }) => ({ field, direction: order })), pageOf(pagination));
35
+ return { data: [...entries], total };
37
36
  },
38
37
  getMany: async ({ resource, ids, }) => ({ data: await held.many(resource, ids) }),
39
38
  getOne: async ({ resource, id }) => ({ data: await held.one(resource, id) }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentshouse/mdruntime-families",
3
- "version": "0.1.0-cdecd9be0238f",
3
+ "version": "0.1.0-ce42360f45634",
4
4
  "description": "react-admin and Refine data providers over the MD Runtime client",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -28,8 +28,8 @@
28
28
  "LICENSE"
29
29
  ],
30
30
  "dependencies": {
31
- "@agentshouse/mdmodel": "0.1.0-c6e675bb19980",
32
- "@agentshouse/mdruntime": "0.1.0-cfb2ae0107054"
31
+ "@agentshouse/mdmodel": "0.1.0-ccf1367190878",
32
+ "@agentshouse/mdruntime": "0.1.0-c13854b9249b6"
33
33
  },
34
34
  "peerDependencies": {
35
35
  "@refinedev/core": ">=5",