@agentshouse/mdruntime-families 0.0.0-stage → 0.1.0-c043f1f493f7e

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Anton (trof3039)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,52 @@
1
- # Temporary Holding Version
1
+ # `@agentshouse/mdruntime-families`
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ react-admin and Refine data providers over the
4
+ [`@agentshouse/mdruntime`](https://www.npmjs.com/package/@agentshouse/mdruntime)
5
+ client, so an App built on either framework reads and writes its Rooms.
6
+
7
+ ## Install
8
+
9
+ ```sh
10
+ npm install @agentshouse/mdruntime-families @agentshouse/mdruntime
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ Each resource names the MD Type of its records and the folder they live in.
16
+ A record's id is its Path in the Room, prefixed by the Room's locator and `/`
17
+ when the App reaches several Rooms; a new record is created in the first.
18
+
19
+ ```ts
20
+ import { QueryClient } from '@tanstack/react-query';
21
+ import { dataProvider } from '@agentshouse/mdruntime-families/react-admin';
22
+
23
+ const resources = { contacts: { type: 'contact', folder: 'contacts' } };
24
+ const queryClient = new QueryClient();
25
+ const provider = dataProvider(runtime, { resources, queryClient });
26
+ // <Admin dataProvider={provider} queryClient={queryClient}>
27
+ ```
28
+
29
+ ```ts
30
+ import { dataProvider, liveProvider } from '@agentshouse/mdruntime-families/refine';
31
+
32
+ // <Refine dataProvider={dataProvider(runtime, { resources })}
33
+ // liveProvider={liveProvider(runtime, { resources })} options={{ liveMode: 'auto' }}>
34
+ ```
35
+
36
+ `runtime` is the client the host hands the App. A filter or sort field such
37
+ as `address.city` reads the value at `/address/city`. react-admin filters
38
+ compare for equality, a list for membership, and the suffixes `_gt`, `_gte`,
39
+ `_lt`, `_lte` and `_neq` compare as named; Refine filters take `eq`, `ne`,
40
+ `lt`, `gt`, `lte`, `gte`, `in`, `nin`, `or` and `and`. A field the Room schema
41
+ declares a reference, at any depth, holds the referenced record's id; `id` and
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
44
+ that differ from `previousData`, based on it, so the Save of a field another
45
+ writer changed meanwhile is refused; Refine's `update` and either `updateMany`
46
+ 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
+
50
+ ## License
51
+
52
+ MIT
@@ -0,0 +1,15 @@
1
+ import type { MdRuntimeClient } from '@agentshouse/mdruntime/client';
2
+ import type { DataProvider } from 'ra-core';
3
+ import { type Resources } from './records.js';
4
+ export type { Resource, Resources } from './records.js';
5
+ export type Invalidating = {
6
+ invalidateQueries(filters: {
7
+ queryKey: readonly unknown[];
8
+ }): Promise<unknown>;
9
+ };
10
+ export type DataProviderOptions = {
11
+ readonly resources: Resources;
12
+ readonly queryClient: Invalidating;
13
+ };
14
+ export declare function dataProvider(client: MdRuntimeClient, options: DataProviderOptions): DataProvider;
15
+ //# sourceMappingURL=react-admin.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,80 @@
1
+ import { records } from './records.js';
2
+ const OPERATORS = [
3
+ ['_gte', '$gte'],
4
+ ['_gt', '$gt'],
5
+ ['_lte', '$lte'],
6
+ ['_lt', '$lt'],
7
+ ['_neq', '$ne'],
8
+ ];
9
+ function clause(key, value) {
10
+ const list = Array.isArray(value);
11
+ const operator = OPERATORS.find(([suffix]) => key.endsWith(suffix));
12
+ if (operator === undefined)
13
+ return { field: key, operator: list ? '$in' : '$eq', value };
14
+ return {
15
+ field: key.slice(0, -operator[0].length),
16
+ operator: list && operator[1] === '$ne' ? '$nin' : operator[1],
17
+ value,
18
+ };
19
+ }
20
+ function paged(entries, pagination) {
21
+ if (pagination === undefined)
22
+ return [...entries];
23
+ const start = (pagination.page - 1) * pagination.perPage;
24
+ return entries.slice(start, start + pagination.perPage);
25
+ }
26
+ export function dataProvider(client, options) {
27
+ const held = records(client, options.resources);
28
+ const live = new Map();
29
+ function watched(resource) {
30
+ const known = live.get(resource);
31
+ if (known !== undefined)
32
+ return known;
33
+ const watching = held.watch(resource, () => {
34
+ void options.queryClient.invalidateQueries({ queryKey: [resource] });
35
+ });
36
+ live.set(resource, watching);
37
+ watching.catch(() => live.delete(resource));
38
+ return watching;
39
+ }
40
+ async function listed(resource, { filter, sort, pagination }) {
41
+ await watched(resource);
42
+ 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
44
+ ? []
45
+ : [{ field: sort.field, direction: sort.order === 'DESC' ? 'desc' : 'asc' }]);
46
+ return { data: paged(entries, pagination), total: entries.length };
47
+ }
48
+ return {
49
+ getList: async (resource, params) => (await listed(resource, params)),
50
+ getOne: async (resource, { id }) => {
51
+ await watched(resource);
52
+ return { data: await held.one(resource, id) };
53
+ },
54
+ getMany: async (resource, { ids }) => {
55
+ await watched(resource);
56
+ return { data: await held.many(resource, ids) };
57
+ },
58
+ getManyReference: async (resource, { target, id, filter, ...listing }) => (await listed(resource, {
59
+ ...listing,
60
+ filter: { ...filter, [target]: id },
61
+ })),
62
+ create: async (resource, { data }) => ({ data: await held.create(resource, data) }),
63
+ update: async (resource, { id, data, previousData }) => {
64
+ const [entry] = await held.update(resource, [id], data, previousData);
65
+ return { data: entry };
66
+ },
67
+ updateMany: async (resource, { ids, data }) => {
68
+ await held.update(resource, ids, data, null);
69
+ return { data: ids };
70
+ },
71
+ delete: async (resource, { id }) => {
72
+ const [entry] = await held.remove(resource, [id]);
73
+ return { data: entry };
74
+ },
75
+ deleteMany: async (resource, { ids }) => {
76
+ await held.remove(resource, ids);
77
+ return { data: ids };
78
+ },
79
+ };
80
+ }
@@ -0,0 +1,36 @@
1
+ import type { MdRuntimeClient } from '@agentshouse/mdruntime/client';
2
+ export type Resource = {
3
+ readonly type: string;
4
+ readonly folder: string;
5
+ };
6
+ export type Resources = Readonly<Record<string, Resource>>;
7
+ export type Key = string | number;
8
+ export type Fields = Readonly<Record<string, unknown>>;
9
+ export type Entry = {
10
+ readonly id: string;
11
+ readonly [field: string]: unknown;
12
+ };
13
+ export type Order = {
14
+ readonly field: string;
15
+ readonly direction: 'asc' | 'desc';
16
+ };
17
+ export type Operator = '$eq' | '$ne' | '$gt' | '$gte' | '$lt' | '$lte' | '$in' | '$nin';
18
+ export type Clause = {
19
+ readonly every: readonly Clause[];
20
+ } | {
21
+ readonly some: readonly Clause[];
22
+ } | {
23
+ readonly field: string;
24
+ readonly operator: Operator;
25
+ readonly value: unknown;
26
+ };
27
+ export declare function records(client: MdRuntimeClient, resources: Resources): {
28
+ list(name: string, clause: Clause | null, order: readonly Order[]): Promise<Entry[]>;
29
+ many(name: string, ids: readonly Key[]): Promise<Entry[]>;
30
+ one(name: string, id: Key): Promise<Entry>;
31
+ create(name: string, data: Fields): Promise<Entry>;
32
+ update(name: string, ids: readonly Key[], data: Fields, shown: Fields | null): Promise<Entry[]>;
33
+ remove(name: string, ids: readonly Key[]): Promise<Entry[]>;
34
+ watch(name: string, heard: () => void): Promise<() => void>;
35
+ };
36
+ //# sourceMappingURL=records.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"records.d.ts","sourceRoot":"","sources":["../src/records.ts"],"names":[],"mappings":"AAmBA,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;AA6KrF,wBAAgB,OAAO,CAAC,MAAM,EAAE,eAAe,EAAE,SAAS,EAAE,SAAS;IA+I3D,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"}
@@ -0,0 +1,329 @@
1
+ import { ARRAY_INDEX, escapePointer, itemFacts, memberFacts, pointerSegments, references, relative, target, } from '@agentshouse/mdmodel';
2
+ import { saveRoomDrafts } from '@agentshouse/mdruntime/react';
3
+ const FIELDS = { fields: 'all' };
4
+ const REMOVABLE = { fields: 'all', markdown: true };
5
+ const BY_ID = [{ field: 'id', direction: 'asc' }];
6
+ const RESERVED = new Set(['id', 'type']);
7
+ const EQUALITY = {
8
+ $eq: true,
9
+ $in: true,
10
+ $ne: false,
11
+ $nin: false,
12
+ };
13
+ function pointer(field) {
14
+ return field
15
+ .split('.')
16
+ .map((segment) => `/${escapePointer(segment)}`)
17
+ .join('');
18
+ }
19
+ function listed(value) {
20
+ return Array.isArray(value) ? value : [value];
21
+ }
22
+ function same(left, right) {
23
+ if (Object.is(left, right))
24
+ return true;
25
+ if (typeof left !== 'object' || typeof right !== 'object' || left === null || right === null) {
26
+ return false;
27
+ }
28
+ if (Array.isArray(left) !== Array.isArray(right))
29
+ return false;
30
+ const named = Object.keys(left);
31
+ return (named.length === Object.keys(right).length &&
32
+ named.every((name) => same(left[name], right[name])));
33
+ }
34
+ function factsAt(facts, field) {
35
+ let reached = facts;
36
+ for (const segment of field.split('.')) {
37
+ if (reached?.type === 'array' && ARRAY_INDEX.test(segment)) {
38
+ reached = itemFacts(reached, Number(segment));
39
+ continue;
40
+ }
41
+ while (reached?.type === 'array')
42
+ reached = reached.items;
43
+ reached = reached === undefined ? undefined : memberFacts(reached, segment);
44
+ }
45
+ return reached;
46
+ }
47
+ function referring(facts) {
48
+ return facts?.reference !== undefined || facts?.items?.reference !== undefined;
49
+ }
50
+ function relinked(fields, definition, each) {
51
+ const copy = structuredClone(fields);
52
+ if (definition === undefined)
53
+ return copy;
54
+ const document = { content: '', type: undefined, fields, preamble: '', sections: {} };
55
+ for (const { pointer, value } of references(document, definition)) {
56
+ const segments = pointerSegments(pointer);
57
+ const last = segments.pop();
58
+ let held = copy;
59
+ for (const segment of segments)
60
+ held = held[segment];
61
+ if (last !== undefined)
62
+ held[last] = each(value);
63
+ }
64
+ return copy;
65
+ }
66
+ function idOf(shelf, room, path) {
67
+ return shelf.rooms.length > 1 ? `${room}/${path}` : path;
68
+ }
69
+ function keyOf(shelf, document) {
70
+ return idOf(shelf, document.room, document.path);
71
+ }
72
+ function placeOf(shelf, id) {
73
+ const text = String(id);
74
+ if (shelf.rooms.length > 1) {
75
+ const cut = text.indexOf('/');
76
+ if (cut <= 0)
77
+ throw new Error(`${text} names no Room`);
78
+ return { room: text.slice(0, cut), path: text.slice(cut + 1) };
79
+ }
80
+ const [room] = shelf.rooms;
81
+ if (room === undefined)
82
+ throw new Error('no Room is reached');
83
+ return { room, path: text };
84
+ }
85
+ function equality(field, operator) {
86
+ const equal = EQUALITY[operator];
87
+ if (equal === undefined)
88
+ throw new Error(`${field} compares only for equality`);
89
+ return equal;
90
+ }
91
+ function sorting(shelf, { field, direction }) {
92
+ if (field !== 'id')
93
+ return [{ by: { field: pointer(field) }, direction }];
94
+ const path = { by: 'path', direction };
95
+ return shelf.rooms.length > 1 ? [{ by: 'room', direction }, path] : [path];
96
+ }
97
+ function entryOf(shelf, document) {
98
+ const fields = Object.fromEntries(Object.entries(document.fields ?? {}).filter(([field]) => !RESERVED.has(field)));
99
+ const read = relinked(fields, shelf.definitions.get(document.room), (link) => {
100
+ const found = target(document.path, link);
101
+ return found === null ? link : idOf(shelf, document.room, found);
102
+ });
103
+ return { ...read, id: keyOf(shelf, document) };
104
+ }
105
+ function written(shelf, place, field, value) {
106
+ return relinked({ [field]: value }, shelf.definitions.get(place.room), (id) => {
107
+ const to = placeOf(shelf, id);
108
+ if (to.room !== place.room)
109
+ throw new Error(`${field} names a record in another Room`);
110
+ return relative(place.path, to.path);
111
+ })[field];
112
+ }
113
+ function changed(shelf, { place, document }, data, shown) {
114
+ const current = entryOf(shelf, document);
115
+ const prior = shown ?? current;
116
+ const stored = document.fields ?? {};
117
+ const fields = {};
118
+ for (const [field, value] of Object.entries(data)) {
119
+ if (RESERVED.has(field) || value === undefined || same(value, prior[field]))
120
+ continue;
121
+ const base = same(prior[field], current[field])
122
+ ? Object.hasOwn(stored, field)
123
+ ? { value: stored[field] }
124
+ : null
125
+ : prior[field] === undefined
126
+ ? null
127
+ : { value: written(shelf, place, field, prior[field]) };
128
+ fields[field] = { value: written(shelf, place, field, value), base };
129
+ }
130
+ return Object.keys(fields).length === 0 ? null : { fields };
131
+ }
132
+ function byRoom(items) {
133
+ const rooms = new Map();
134
+ for (const item of items) {
135
+ rooms.set(item.place.room, [...(rooms.get(item.place.room) ?? []), item]);
136
+ }
137
+ return rooms;
138
+ }
139
+ export function records(client, resources) {
140
+ function resourceOf(name) {
141
+ const resource = Object.hasOwn(resources, name) ? resources[name] : undefined;
142
+ if (resource === undefined)
143
+ throw new Error(`${name} is not a configured resource`);
144
+ return resource;
145
+ }
146
+ async function shelfOf(name) {
147
+ const resource = resourceOf(name);
148
+ const rooms = (await client.rooms()).map((room) => room.locator);
149
+ const definitions = new Map();
150
+ for (const room of rooms) {
151
+ const schema = (await client.types(room)).collection;
152
+ if (schema.status === 'unavailable')
153
+ continue;
154
+ const definition = schema.types.find((type) => type.name === resource.type);
155
+ if (definition === undefined)
156
+ continue;
157
+ definitions.set(room, definition);
158
+ }
159
+ return { resource, rooms, definitions };
160
+ }
161
+ async function keys(shelf, query) {
162
+ const { documents } = await client.query({
163
+ ...query,
164
+ types: [shelf.resource.type],
165
+ pathPrefix: shelf.resource.folder,
166
+ select: {},
167
+ });
168
+ return new Set(documents.map((document) => keyOf(shelf, document)));
169
+ }
170
+ async function referenced(shelf, field, places) {
171
+ const kept = new Set();
172
+ for (const room of shelf.rooms) {
173
+ const paths = places.flatMap((place) => place === null ? [null] : place.room === room ? [place.path] : []);
174
+ const definition = shelf.definitions.get(room);
175
+ if (paths.length === 0 || !referring(factsAt(definition?.frontmatter, field)))
176
+ continue;
177
+ const where = { [pointer(field)]: { $in: paths } };
178
+ for (const key of await keys(shelf, { rooms: [room], where }))
179
+ kept.add(key);
180
+ }
181
+ return kept;
182
+ }
183
+ async function matching(shelf, all, clause) {
184
+ if ('every' in clause) {
185
+ let kept = new Set(all.map((document) => keyOf(shelf, document)));
186
+ for (const one of clause.every) {
187
+ const next = await matching(shelf, all, one);
188
+ kept = new Set([...kept].filter((key) => next.has(key)));
189
+ }
190
+ return kept;
191
+ }
192
+ if ('some' in clause) {
193
+ const kept = new Set();
194
+ for (const one of clause.some) {
195
+ for (const key of await matching(shelf, all, one))
196
+ kept.add(key);
197
+ }
198
+ return kept;
199
+ }
200
+ const { field, operator, value } = clause;
201
+ const reference = [...shelf.definitions.values()].some((definition) => referring(factsAt(definition.frontmatter, field)));
202
+ if (field !== 'id' && !reference) {
203
+ return keys(shelf, { where: { [pointer(field)]: { [operator]: value } } });
204
+ }
205
+ const equal = equality(field, operator);
206
+ const places = listed(value).map((id) => (id === null ? null : placeOf(shelf, id)));
207
+ const matched = field === 'id'
208
+ ? new Set(places.flatMap((place) => place === null ? [] : [idOf(shelf, place.room, place.path)]))
209
+ : await referenced(shelf, field, places);
210
+ return new Set(all.map((document) => keyOf(shelf, document)).filter((key) => matched.has(key) === equal));
211
+ }
212
+ async function found(shelf, ids, select) {
213
+ const places = ids.map((id) => placeOf(shelf, id));
214
+ const documents = new Map();
215
+ for (const room of new Set(places.map((place) => place.room))) {
216
+ const answer = await client.query({
217
+ rooms: [room],
218
+ types: [shelf.resource.type],
219
+ pathPrefix: shelf.resource.folder,
220
+ select,
221
+ });
222
+ for (const document of answer.documents)
223
+ documents.set(keyOf(shelf, document), document);
224
+ }
225
+ return places.map((place) => ({
226
+ place,
227
+ document: documents.get(idOf(shelf, place.room, place.path)) ?? null,
228
+ }));
229
+ }
230
+ async function held(shelf, ids, select) {
231
+ return (await found(shelf, ids, select)).map(({ place, document }) => {
232
+ if (document === null)
233
+ throw new Error(`no ${shelf.resource.type} is held at ${place.path}`);
234
+ return { place, document };
235
+ });
236
+ }
237
+ async function dropped(room, paths) {
238
+ for (const path of paths) {
239
+ const left = await client.roomDraft.read(room, path);
240
+ if (left !== null)
241
+ await client.roomDraft.discard(room, path, left.revision);
242
+ }
243
+ }
244
+ async function saved(room, parts) {
245
+ if (parts.size === 0)
246
+ return;
247
+ const result = await saveRoomDrafts(client, room, parts).catch(async (error) => {
248
+ await dropped(room, parts.keys());
249
+ throw error;
250
+ });
251
+ if (result.outcome === 'refused') {
252
+ await dropped(room, parts.keys());
253
+ throw new Error(`the Save was refused as ${result.rule}`, { cause: result });
254
+ }
255
+ }
256
+ return {
257
+ async list(name, clause, order) {
258
+ const shelf = await shelfOf(name);
259
+ const { documents } = await client.query({
260
+ types: [shelf.resource.type],
261
+ pathPrefix: shelf.resource.folder,
262
+ orderBy: (order.length === 0 ? BY_ID : order).flatMap((one) => sorting(shelf, one)),
263
+ select: FIELDS,
264
+ });
265
+ const kept = clause === null ? null : await matching(shelf, documents, clause);
266
+ return documents
267
+ .filter((document) => kept === null || kept.has(keyOf(shelf, document)))
268
+ .map((document) => entryOf(shelf, document));
269
+ },
270
+ async many(name, ids) {
271
+ const shelf = await shelfOf(name);
272
+ return (await found(shelf, ids, FIELDS)).flatMap(({ document }) => document === null ? [] : [entryOf(shelf, document)]);
273
+ },
274
+ async one(name, id) {
275
+ const shelf = await shelfOf(name);
276
+ const [{ document }] = await held(shelf, [id], FIELDS);
277
+ return entryOf(shelf, document);
278
+ },
279
+ async create(name, data) {
280
+ const shelf = await shelfOf(name);
281
+ const [room] = shelf.rooms;
282
+ if (room === undefined)
283
+ throw new Error('no Room is reached');
284
+ const file = `${crypto.randomUUID()}.md`;
285
+ const { type, folder } = shelf.resource;
286
+ const place = { room, path: folder === '' ? file : `${folder}/${file}` };
287
+ const fields = Object.fromEntries(Object.entries(data)
288
+ .filter(([field, value]) => !RESERVED.has(field) && value !== undefined)
289
+ .map(([field, value]) => [field, written(shelf, place, field, value)]));
290
+ await saved(room, new Map([[place.path, { creation: { type, fields, sections: {} } }]]));
291
+ const [{ document }] = await held(shelf, [idOf(shelf, room, place.path)], FIELDS);
292
+ return entryOf(shelf, document);
293
+ },
294
+ async update(name, ids, data, shown) {
295
+ const shelf = await shelfOf(name);
296
+ for (const [room, inRoom] of byRoom(await held(shelf, ids, FIELDS))) {
297
+ const parts = new Map();
298
+ for (const one of inRoom) {
299
+ const part = changed(shelf, one, data, shown);
300
+ if (part !== null)
301
+ parts.set(one.place.path, part);
302
+ }
303
+ await saved(room, parts);
304
+ }
305
+ return (await held(shelf, ids, FIELDS)).map(({ document }) => entryOf(shelf, document));
306
+ },
307
+ async remove(name, ids) {
308
+ const shelf = await shelfOf(name);
309
+ const removed = await held(shelf, ids, REMOVABLE);
310
+ for (const [room, inRoom] of byRoom(removed)) {
311
+ await saved(room, new Map(inRoom.map(({ place, document }) => [
312
+ place.path,
313
+ { removal: { base: document.markdown ?? null } },
314
+ ])));
315
+ }
316
+ return removed.map(({ document }) => entryOf(shelf, document));
317
+ },
318
+ async watch(name, heard) {
319
+ const { type, folder } = resourceOf(name);
320
+ const observation = await client.observe('query', {
321
+ types: [type],
322
+ pathPrefix: folder,
323
+ select: { markdown: true },
324
+ });
325
+ observation.subscribe(heard);
326
+ return () => observation.close();
327
+ },
328
+ };
329
+ }
@@ -0,0 +1,10 @@
1
+ import type { MdRuntimeClient } from '@agentshouse/mdruntime/client';
2
+ import type { DataProvider, LiveProvider } from '@refinedev/core';
3
+ import { type Resources } from './records.js';
4
+ export type { Resource, Resources } from './records.js';
5
+ export type ProviderOptions = {
6
+ readonly resources: Resources;
7
+ };
8
+ export declare function dataProvider(client: MdRuntimeClient, options: ProviderOptions): DataProvider;
9
+ export declare function liveProvider(client: MdRuntimeClient, options: ProviderOptions): LiveProvider;
10
+ //# sourceMappingURL=refine.d.ts.map
@@ -0,0 +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"}
package/dist/refine.js ADDED
@@ -0,0 +1,66 @@
1
+ import { records, } from './records.js';
2
+ const OPERATORS = {
3
+ eq: '$eq',
4
+ ne: '$ne',
5
+ lt: '$lt',
6
+ gt: '$gt',
7
+ lte: '$lte',
8
+ gte: '$gte',
9
+ in: '$in',
10
+ nin: '$nin',
11
+ };
12
+ function clause(filter) {
13
+ if (!('field' in filter)) {
14
+ const each = filter.value.map(clause);
15
+ return filter.operator === 'or' ? { some: each } : { every: each };
16
+ }
17
+ const operator = Object.hasOwn(OPERATORS, filter.operator)
18
+ ? OPERATORS[filter.operator]
19
+ : undefined;
20
+ if (operator === undefined)
21
+ throw new Error(`the ${filter.operator} filter has no condition`);
22
+ return { field: filter.field, operator, value: filter.value };
23
+ }
24
+ function paged(entries, pagination) {
25
+ const { currentPage = 1, pageSize = 10, mode = 'server' } = pagination ?? {};
26
+ if (mode !== 'server')
27
+ return [...entries];
28
+ const start = (currentPage - 1) * pageSize;
29
+ return entries.slice(start, start + pageSize);
30
+ }
31
+ export function dataProvider(client, options) {
32
+ const held = records(client, options.resources);
33
+ return {
34
+ 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 };
37
+ },
38
+ getMany: async ({ resource, ids, }) => ({ data: await held.many(resource, ids) }),
39
+ getOne: async ({ resource, id }) => ({ data: await held.one(resource, id) }),
40
+ create: async ({ resource, variables, }) => ({ data: await held.create(resource, variables) }),
41
+ update: async ({ resource, id, variables, }) => {
42
+ const [entry] = await held.update(resource, [id], variables, null);
43
+ return { data: entry };
44
+ },
45
+ updateMany: async ({ resource, ids, variables, }) => ({
46
+ data: await held.update(resource, ids, variables, null),
47
+ }),
48
+ deleteOne: async ({ resource, id, }) => {
49
+ const [entry] = await held.remove(resource, [id]);
50
+ return { data: entry };
51
+ },
52
+ deleteMany: async ({ resource, ids, }) => ({ data: await held.remove(resource, ids) }),
53
+ getApiUrl: () => '',
54
+ };
55
+ }
56
+ export function liveProvider(client, options) {
57
+ const held = records(client, options.resources);
58
+ return {
59
+ subscribe: ({ channel, params, callback }) => held.watch(String(params?.resource), () => {
60
+ callback({ channel, type: '*', payload: {}, date: new Date() });
61
+ }),
62
+ unsubscribe: (subscription) => {
63
+ void subscription.then((close) => close());
64
+ },
65
+ };
66
+ }
package/package.json CHANGED
@@ -1,6 +1,47 @@
1
1
  {
2
2
  "name": "@agentshouse/mdruntime-families",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0-c043f1f493f7e",
4
+ "description": "react-admin and Refine data providers over the MD Runtime client",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "sideEffects": false,
8
+ "publishConfig": {
9
+ "access": "public"
10
+ },
11
+ "engines": {
12
+ "node": ">=24.21.0"
13
+ },
14
+ "exports": {
15
+ "./react-admin": {
16
+ "types": "./dist/react-admin.d.ts",
17
+ "import": "./dist/react-admin.js"
18
+ },
19
+ "./refine": {
20
+ "types": "./dist/refine.d.ts",
21
+ "import": "./dist/refine.js"
22
+ },
23
+ "./package.json": "./package.json"
24
+ },
25
+ "files": [
26
+ "dist",
27
+ "README.md",
28
+ "LICENSE"
29
+ ],
30
+ "dependencies": {
31
+ "@agentshouse/mdmodel": "0.1.0-c896b3d9398b1",
32
+ "@agentshouse/mdruntime": "0.1.0-c389df724fd2f"
33
+ },
34
+ "peerDependencies": {
35
+ "@refinedev/core": ">=5",
36
+ "ra-core": ">=5",
37
+ "react": ">=18"
38
+ },
39
+ "peerDependenciesMeta": {
40
+ "@refinedev/core": {
41
+ "optional": true
42
+ },
43
+ "ra-core": {
44
+ "optional": true
45
+ }
46
+ }
47
+ }