@agentshouse/mdruntime 0.1.0-ce531b51ff088 → 0.1.0-cebcd51f70ed1
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/AGENTS.md +57 -20
- package/README.md +10 -10
- package/dist/adapter.d.ts +52 -25
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js +1 -9
- package/dist/attached-model.d.ts +11 -0
- package/dist/attached-model.d.ts.map +1 -0
- package/dist/attached-model.js +34 -0
- package/dist/bootstrap.d.ts +5 -3
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +3 -2
- package/dist/client.d.ts +3 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +3 -10
- package/dist/connection.d.ts +10 -12
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +501 -512
- package/dist/conversation.d.ts +18 -6
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +179 -78
- package/dist/host.d.ts +7 -1
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +34 -31
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/memory.d.ts +11 -5
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +78 -26
- package/dist/model.d.ts +4 -11
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +4 -3
- package/dist/observation.d.ts +2 -0
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +24 -14
- package/dist/paths.d.ts +0 -1
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +0 -16
- package/dist/query.d.ts +8 -3
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +104 -36
- package/dist/reach.d.ts +40 -0
- package/dist/reach.d.ts.map +1 -0
- package/dist/reach.js +270 -0
- package/dist/react.d.ts +32 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +62 -0
- package/dist/read-side.d.ts +4 -0
- package/dist/read-side.d.ts.map +1 -1
- package/dist/read-side.js +116 -54
- package/dist/refresh.d.ts +2 -1
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +7 -3
- package/dist/replica.d.ts +4 -4
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +12 -13
- package/dist/room-draft.d.ts +3 -0
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +6 -4
- package/dist/rows.d.ts +2 -1
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +75 -38
- package/dist/save.d.ts +1 -2
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +90 -48
- package/dist/store.d.ts +1 -1
- package/dist/store.d.ts.map +1 -1
- package/dist/wire.d.ts +51 -16
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +50 -59
- package/package.json +14 -2
package/dist/query.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { ARRAY_INDEX, itemFacts, memberFacts, pointerSegments, } from '@agentshouse/mdmodel';
|
|
2
|
+
import { referenceField } from './model.js';
|
|
1
3
|
import { normalize } from './paths.js';
|
|
2
4
|
export class QueryError extends Error {
|
|
3
5
|
constructor(code, at, problem) {
|
|
@@ -11,6 +13,7 @@ const RELATED = ['types', 'where', 'pathPrefix', 'field'];
|
|
|
11
13
|
const PARTS = ['fields', 'sections', 'preamble', 'markdown', 'backlinks', 'change'];
|
|
12
14
|
const ORDER = ['by', 'direction', 'nulls', 'stringSort', 'locale', 'localeOptions'];
|
|
13
15
|
const CALENDAR_DATE = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
16
|
+
const STRAY_ESCAPE = /~(?![01])/u;
|
|
14
17
|
export function invalid(at, problem) {
|
|
15
18
|
throw new QueryError('invalid-query', at, problem);
|
|
16
19
|
}
|
|
@@ -68,23 +71,29 @@ function calendarDate(value) {
|
|
|
68
71
|
const date = new Date(Date.UTC(year, month - 1, day));
|
|
69
72
|
return (date.getUTCFullYear() === year && date.getUTCMonth() === month - 1 && date.getUTCDate() === day);
|
|
70
73
|
}
|
|
71
|
-
function
|
|
72
|
-
|
|
73
|
-
|
|
74
|
+
function itemOf(facts) {
|
|
75
|
+
return facts.type === 'array' ? facts.items : facts;
|
|
76
|
+
}
|
|
77
|
+
function fits(facts, value) {
|
|
78
|
+
const item = itemOf(facts);
|
|
79
|
+
if (item?.type === undefined)
|
|
80
|
+
return true;
|
|
81
|
+
if (item.values !== undefined)
|
|
82
|
+
return item.values.includes(value);
|
|
83
|
+
if (item.type === 'number')
|
|
74
84
|
return typeof value === 'number';
|
|
75
|
-
if (
|
|
85
|
+
if (item.type === 'integer')
|
|
86
|
+
return Number.isSafeInteger(value);
|
|
87
|
+
if (item.type === 'boolean')
|
|
88
|
+
return typeof value === 'boolean';
|
|
89
|
+
if (item.type !== 'string' || typeof value !== 'string')
|
|
76
90
|
return false;
|
|
77
|
-
if (item.
|
|
78
|
-
return item.values.includes(value);
|
|
79
|
-
if (item.kind === 'date')
|
|
80
|
-
return calendarDate(value);
|
|
81
|
-
if (item.kind === 'reference')
|
|
91
|
+
if (item.reference !== undefined)
|
|
82
92
|
return value !== '' && normalize(value) === value;
|
|
93
|
+
if (item.format === 'date')
|
|
94
|
+
return calendarDate(value);
|
|
83
95
|
return true;
|
|
84
96
|
}
|
|
85
|
-
function reference(spec) {
|
|
86
|
-
return spec.kind === 'reference' || (spec.kind === 'list' && spec.of.kind === 'reference');
|
|
87
|
-
}
|
|
88
97
|
function typing(facts, rooms, types, at) {
|
|
89
98
|
const read = rooms === null ? facts.rooms : facts.rooms.filter((room) => rooms.includes(room.locator));
|
|
90
99
|
const acquiring = !facts.listed &&
|
|
@@ -102,22 +111,75 @@ function typing(facts, rooms, types, at) {
|
|
|
102
111
|
types: types === null ? declared : declared.filter((type) => types.includes(type.name)),
|
|
103
112
|
};
|
|
104
113
|
}
|
|
105
|
-
function
|
|
106
|
-
|
|
114
|
+
function pointer(input, at) {
|
|
115
|
+
if (typeof input !== 'string' || !input.startsWith('/') || STRAY_ESCAPE.test(input)) {
|
|
116
|
+
invalid(at, 'expects a JSON Pointer');
|
|
117
|
+
}
|
|
118
|
+
return input;
|
|
119
|
+
}
|
|
120
|
+
function stepped({ facts, through, conditional }, token) {
|
|
121
|
+
if (facts.type === 'array' && ARRAY_INDEX.test(token)) {
|
|
122
|
+
const item = itemFacts(facts, Number(token));
|
|
123
|
+
return item === undefined ? undefined : { facts: item, through, conditional };
|
|
124
|
+
}
|
|
125
|
+
let reached = facts;
|
|
126
|
+
let across = through;
|
|
127
|
+
while (reached?.type === 'array') {
|
|
128
|
+
reached = reached.items;
|
|
129
|
+
across = true;
|
|
130
|
+
}
|
|
131
|
+
const member = reached === undefined ? undefined : memberFacts(reached, token);
|
|
132
|
+
return member === undefined
|
|
133
|
+
? undefined
|
|
134
|
+
: { facts: member, through: across, conditional: conditional || member.conditional === true };
|
|
135
|
+
}
|
|
136
|
+
function specAt(type, field) {
|
|
137
|
+
let spec = {
|
|
138
|
+
facts: { ...type.frontmatter, properties: type.fields },
|
|
139
|
+
through: false,
|
|
140
|
+
conditional: false,
|
|
141
|
+
};
|
|
142
|
+
for (const token of pointerSegments(field)) {
|
|
143
|
+
if (spec === undefined)
|
|
144
|
+
return undefined;
|
|
145
|
+
spec = stepped(spec, token);
|
|
146
|
+
}
|
|
147
|
+
return spec;
|
|
148
|
+
}
|
|
149
|
+
function specs(typed, input, at) {
|
|
150
|
+
const field = pointer(input, at);
|
|
151
|
+
const declared = typed.types.flatMap((type) => {
|
|
152
|
+
const spec = specAt(type, field);
|
|
153
|
+
return spec === undefined ? [] : [spec];
|
|
154
|
+
});
|
|
107
155
|
if (declared.length === 0 && !typed.untyped) {
|
|
108
156
|
throw new QueryError('unknown-field', at, `no MD Type it reads declares ${field}`);
|
|
109
157
|
}
|
|
110
|
-
|
|
158
|
+
if (!declared.some((spec) => spec.conditional))
|
|
159
|
+
return declared;
|
|
160
|
+
return declared.map((spec) => ({
|
|
161
|
+
facts: { prose: false },
|
|
162
|
+
through: several(spec),
|
|
163
|
+
conditional: true,
|
|
164
|
+
}));
|
|
165
|
+
}
|
|
166
|
+
function several(spec) {
|
|
167
|
+
return spec.through || spec.facts.type === 'array';
|
|
168
|
+
}
|
|
169
|
+
function single(spec) {
|
|
170
|
+
return spec.facts.type !== undefined && spec.facts.type !== 'array';
|
|
171
|
+
}
|
|
172
|
+
function every(declared, holds) {
|
|
173
|
+
return declared.length > 0 && declared.every(holds);
|
|
111
174
|
}
|
|
112
175
|
function kind(declared, checked, at) {
|
|
113
|
-
if (checked === null ||
|
|
176
|
+
if (checked === null ||
|
|
177
|
+
declared.length === 0 ||
|
|
178
|
+
declared.some((spec) => fits(spec.facts, checked))) {
|
|
114
179
|
return;
|
|
115
180
|
}
|
|
116
181
|
throw new QueryError('wrong-kind', at, `${JSON.stringify(checked)} is not of its field's kind`);
|
|
117
182
|
}
|
|
118
|
-
function listed(declared, list) {
|
|
119
|
-
return declared.length > 0 && declared.every((spec) => (spec.kind === 'list') === list);
|
|
120
|
-
}
|
|
121
183
|
function test(declared, input, at) {
|
|
122
184
|
if (!plain(input)) {
|
|
123
185
|
const equal = value(input, at);
|
|
@@ -131,8 +193,11 @@ function test(declared, input, at) {
|
|
|
131
193
|
const stray = operators.find((operator) => !RANGE.includes(operator));
|
|
132
194
|
if (stray !== undefined)
|
|
133
195
|
invalid(place(at, stray), 'cannot join a range');
|
|
134
|
-
if (
|
|
196
|
+
if (every(declared, (spec) => spec.facts.type === 'array'))
|
|
135
197
|
invalid(at, 'a range on a list');
|
|
198
|
+
if (every(declared, (spec) => spec.facts.type === 'boolean' || spec.facts.type === 'object')) {
|
|
199
|
+
throw new QueryError('wrong-kind', at, 'its field does not compare in order');
|
|
200
|
+
}
|
|
136
201
|
return {
|
|
137
202
|
op: 'range',
|
|
138
203
|
bounds: RANGE.filter((operator) => operators.includes(operator)).map((operator) => {
|
|
@@ -165,7 +230,7 @@ function test(declared, input, at) {
|
|
|
165
230
|
return { op: 'nin', values: compared };
|
|
166
231
|
if (compared.includes(null))
|
|
167
232
|
invalid(where, 'a list holds no null');
|
|
168
|
-
if (
|
|
233
|
+
if (every(declared, single))
|
|
169
234
|
invalid(where, 'the field is not a list');
|
|
170
235
|
return { op: 'all', values: compared };
|
|
171
236
|
}
|
|
@@ -173,7 +238,7 @@ function test(declared, input, at) {
|
|
|
173
238
|
if (!Number.isSafeInteger(operand) || operand < 0) {
|
|
174
239
|
invalid(where, 'expects a nonnegative integer');
|
|
175
240
|
}
|
|
176
|
-
if (
|
|
241
|
+
if (every(declared, single))
|
|
177
242
|
invalid(where, 'the field is not a list');
|
|
178
243
|
return { op: 'size', size: operand };
|
|
179
244
|
}
|
|
@@ -186,7 +251,7 @@ function condition(typed, input, at) {
|
|
|
186
251
|
invalid(at, 'expects an object of field predicates');
|
|
187
252
|
const conditions = [];
|
|
188
253
|
for (const name of Object.keys(input).sort()) {
|
|
189
|
-
const where = place(at, name);
|
|
254
|
+
const where = name.startsWith('/') ? `${at}${name}` : place(at, name);
|
|
190
255
|
const entry = input[name];
|
|
191
256
|
if (name === '$and' || name === '$or') {
|
|
192
257
|
const nested = list(entry, where).map((item, index) => condition(typed, item, `${where}[${index}]`));
|
|
@@ -215,19 +280,19 @@ function scope(facts, rooms, input, at) {
|
|
|
215
280
|
pathPrefix: input.pathPrefix === undefined ? null : directory(input.pathPrefix, place(at, 'pathPrefix')),
|
|
216
281
|
};
|
|
217
282
|
}
|
|
218
|
-
function
|
|
219
|
-
if (typeof field !== 'string')
|
|
220
|
-
invalid(at, 'expects a field name');
|
|
283
|
+
function referenceName(typed, field, at) {
|
|
221
284
|
const declared = specs(typed, field, at);
|
|
222
|
-
if (declared.length > 0 &&
|
|
285
|
+
if (declared.length > 0 &&
|
|
286
|
+
!declared.some((spec) => spec.conditional || referenceField(spec.facts))) {
|
|
223
287
|
invalid(at, 'not a reference field');
|
|
288
|
+
}
|
|
224
289
|
return field;
|
|
225
290
|
}
|
|
226
291
|
function hopOf(facts, rooms, root, input) {
|
|
227
292
|
if (!plain(input))
|
|
228
293
|
invalid('hop', 'expects an object');
|
|
229
294
|
known(input, RELATED, 'hop');
|
|
230
|
-
const field =
|
|
295
|
+
const field = referenceName(root, input.field, 'hop.field');
|
|
231
296
|
const { types, where, pathPrefix } = scope(facts, rooms, input, 'hop');
|
|
232
297
|
return { types, where, pathPrefix, field };
|
|
233
298
|
}
|
|
@@ -236,14 +301,16 @@ function backlinksOf(facts, rooms, input) {
|
|
|
236
301
|
invalid('backlinks', 'expects an object');
|
|
237
302
|
known(input, RELATED, 'backlinks');
|
|
238
303
|
const { typed, types, where, pathPrefix } = scope(facts, rooms, input, 'backlinks');
|
|
239
|
-
const field = input.field === undefined ? null :
|
|
304
|
+
const field = input.field === undefined ? null : referenceName(typed, input.field, 'backlinks.field');
|
|
240
305
|
return { types, where, pathPrefix, field };
|
|
241
306
|
}
|
|
242
307
|
function keyPlan(typed, input, at, path) {
|
|
243
308
|
if (input === 'room' || input === 'type' || (path && input === 'path'))
|
|
244
309
|
return { of: input };
|
|
245
|
-
if (plain(input) &&
|
|
246
|
-
specs(typed, input.field, place(at, 'field'))
|
|
310
|
+
if (plain(input) && Object.hasOwn(input, 'field') && Object.keys(input).length === 1) {
|
|
311
|
+
if (specs(typed, input.field, place(at, 'field')).some(several)) {
|
|
312
|
+
invalid(place(at, 'field'), 'a path that can hold several values');
|
|
313
|
+
}
|
|
247
314
|
return { of: 'field', field: input.field };
|
|
248
315
|
}
|
|
249
316
|
return invalid(at, path ? 'expects room, path, type or a field' : 'expects room, type or a field');
|
|
@@ -301,10 +368,10 @@ function parts(typed, input, at, fields) {
|
|
|
301
368
|
return 'all';
|
|
302
369
|
const named = list(input, at);
|
|
303
370
|
named.forEach((name, index) => {
|
|
304
|
-
if (typeof name !== 'string')
|
|
305
|
-
invalid(`${at}[${index}]`, 'expects a name');
|
|
306
371
|
if (fields)
|
|
307
372
|
specs(typed, name, `${at}[${index}]`);
|
|
373
|
+
else if (typeof name !== 'string')
|
|
374
|
+
invalid(`${at}[${index}]`, 'expects a name');
|
|
308
375
|
});
|
|
309
376
|
return named;
|
|
310
377
|
}
|
|
@@ -372,7 +439,7 @@ function groupShape(typed, input) {
|
|
|
372
439
|
});
|
|
373
440
|
return { kind: 'groups', groupBy, orderBy };
|
|
374
441
|
}
|
|
375
|
-
export function planOf(input, facts) {
|
|
442
|
+
export function planOf(input, facts, reach = null) {
|
|
376
443
|
if (!plain(input))
|
|
377
444
|
invalid('query', 'expects a query object');
|
|
378
445
|
const grouped = input.groupBy !== undefined;
|
|
@@ -386,6 +453,7 @@ export function planOf(input, facts) {
|
|
|
386
453
|
const { typed, ...root } = scope(facts, rooms, input, '');
|
|
387
454
|
return {
|
|
388
455
|
rooms,
|
|
456
|
+
reach,
|
|
389
457
|
root,
|
|
390
458
|
hop: input.hop === undefined ? null : hopOf(facts, rooms, typed, input.hop),
|
|
391
459
|
backlinks: input.backlinks === undefined ? null : backlinksOf(facts, rooms, input.backlinks),
|
|
@@ -401,11 +469,11 @@ export function limitOf(input) {
|
|
|
401
469
|
}
|
|
402
470
|
return input;
|
|
403
471
|
}
|
|
404
|
-
export function filterOf(input, facts) {
|
|
472
|
+
export function filterOf(input, facts, reach = null) {
|
|
405
473
|
if (!plain(input))
|
|
406
474
|
invalid('documents', 'expects a filter object');
|
|
407
475
|
known(input, FILTER, '');
|
|
408
|
-
return planOf({ ...input, select: {} }, facts);
|
|
476
|
+
return planOf({ ...input, select: {} }, facts, reach);
|
|
409
477
|
}
|
|
410
478
|
function canonical(value) {
|
|
411
479
|
if (Array.isArray(value))
|
package/dist/reach.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { MdRuntime, RuntimeTopic } from './connection.js';
|
|
2
|
+
import type { Plan, PlanReach, QueryResult } from './query.js';
|
|
3
|
+
import type { SearchResult } from './search.js';
|
|
4
|
+
export type ReachedDocument = {
|
|
5
|
+
readonly room: string;
|
|
6
|
+
readonly path: string;
|
|
7
|
+
};
|
|
8
|
+
export type PortReach = {
|
|
9
|
+
readonly rooms: readonly string[];
|
|
10
|
+
readonly documents: readonly ReachedDocument[];
|
|
11
|
+
readonly conversations: boolean;
|
|
12
|
+
};
|
|
13
|
+
export type Deliver = (message: unknown) => void;
|
|
14
|
+
export type Watch = (topic: RuntimeTopic, input: readonly unknown[], deliver: Deliver) => () => void;
|
|
15
|
+
export type RuntimeView = {
|
|
16
|
+
md: MdRuntime;
|
|
17
|
+
watch: Watch;
|
|
18
|
+
};
|
|
19
|
+
export type NarrowedView = RuntimeView & {
|
|
20
|
+
narrow(reach: PortReach | null): void;
|
|
21
|
+
};
|
|
22
|
+
export type Reachable = RuntimeView & {
|
|
23
|
+
plan(input: unknown, reach: PortReach | null): Plan;
|
|
24
|
+
query(plan: Plan): Promise<QueryResult>;
|
|
25
|
+
watchPlan(plan: Plan, deliver: (message: unknown) => void): () => void;
|
|
26
|
+
search(input: unknown, reach: PortReach | null): Promise<SearchResult>;
|
|
27
|
+
};
|
|
28
|
+
export declare function holds(reach: PortReach | null, room: string): boolean;
|
|
29
|
+
export declare function governs(reach: PortReach | null, room: {
|
|
30
|
+
readonly locator: string;
|
|
31
|
+
readonly typedSelection: {
|
|
32
|
+
roots: readonly string[];
|
|
33
|
+
} | null;
|
|
34
|
+
}): {
|
|
35
|
+
readonly collection: boolean;
|
|
36
|
+
readonly selection: boolean;
|
|
37
|
+
};
|
|
38
|
+
export declare function planReach(reach: PortReach | null): PlanReach | null;
|
|
39
|
+
export declare function narrowed(runtime: Reachable, given: PortReach | null): NarrowedView;
|
|
40
|
+
//# sourceMappingURL=reach.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reach.d.ts","sourceRoot":"","sources":["../src/reach.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,SAAS,EAAe,YAAY,EAAc,MAAM,iBAAiB,CAAC;AAMjG,OAAO,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAI/D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGhD,MAAM,MAAM,eAAe,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAE/E,MAAM,MAAM,SAAS,GAAG;IACtB,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,SAAS,EAAE,SAAS,eAAe,EAAE,CAAC;IAC/C,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,OAAO,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;AAEjD,MAAM,MAAM,KAAK,GAAG,CAClB,KAAK,EAAE,YAAY,EACnB,KAAK,EAAE,SAAS,OAAO,EAAE,EACzB,OAAO,EAAE,OAAO,KACb,MAAM,IAAI,CAAC;AAEhB,MAAM,MAAM,WAAW,GAAG;IAAE,EAAE,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,KAAK,CAAA;CAAE,CAAC;AAE1D,MAAM,MAAM,YAAY,GAAG,WAAW,GAAG;IAAE,MAAM,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI,GAAG,IAAI,CAAA;CAAE,CAAC;AAEnF,MAAM,MAAM,SAAS,GAAG,WAAW,GAAG;IACpC,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,GAAG,IAAI,GAAG,IAAI,CAAC;IACpD,KAAK,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IACxC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IACvE,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,GAAG,IAAI,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;CACxE,CAAC;AA2BF,wBAAgB,KAAK,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAMpE;AAMD,wBAAgB,OAAO,CACrB,KAAK,EAAE,SAAS,GAAG,IAAI,EACvB,IAAI,EAAE;IAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,cAAc,EAAE;QAAE,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;KAAE,GAAG,IAAI,CAAA;CAAE,GAC/F;IAAE,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAU/D;AAcD,wBAAgB,SAAS,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI,CASnE;AA8ED,wBAAgB,QAAQ,CAAC,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,GAAG,IAAI,GAAG,YAAY,CAgKlF"}
|
package/dist/reach.js
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { unheldView } from './conversation.js';
|
|
2
|
+
import { modelTypes, NO_MODEL } from './model.js';
|
|
3
|
+
import { applied, observing } from './observation.js';
|
|
4
|
+
import { beneath } from './paths.js';
|
|
5
|
+
import { reconciled } from './read-side.js';
|
|
6
|
+
import { moveTarget, readParts } from './room-draft.js';
|
|
7
|
+
import { documentKey } from './rows.js';
|
|
8
|
+
const UNHELD_TYPES = { collection: modelTypes(NO_MODEL), typedSelection: null };
|
|
9
|
+
const UNDECLARED = { kind: 'undeclared' };
|
|
10
|
+
function undeclared() {
|
|
11
|
+
return Promise.resolve(UNDECLARED);
|
|
12
|
+
}
|
|
13
|
+
const NO_CONVERSATION = {
|
|
14
|
+
list: () => Promise.resolve([]),
|
|
15
|
+
read: (conversation) => Promise.resolve(unheldView(conversation)),
|
|
16
|
+
create: undeclared,
|
|
17
|
+
send: undeclared,
|
|
18
|
+
answer: undeclared,
|
|
19
|
+
interrupt: undeclared,
|
|
20
|
+
kill: undeclared,
|
|
21
|
+
cancel: undeclared,
|
|
22
|
+
delete: undeclared,
|
|
23
|
+
};
|
|
24
|
+
const NO_CHAT_DRAFT = {
|
|
25
|
+
write: () => Promise.resolve(),
|
|
26
|
+
discard: () => Promise.resolve(),
|
|
27
|
+
};
|
|
28
|
+
export function holds(reach, room) {
|
|
29
|
+
return (reach === null ||
|
|
30
|
+
reach.rooms.includes(room) ||
|
|
31
|
+
reach.documents.some((document) => document.room === room));
|
|
32
|
+
}
|
|
33
|
+
function wholly(reach, room) {
|
|
34
|
+
return reach === null || reach.rooms.includes(room);
|
|
35
|
+
}
|
|
36
|
+
export function governs(reach, room) {
|
|
37
|
+
if (wholly(reach, room.locator)) {
|
|
38
|
+
return { collection: true, selection: room.typedSelection !== null };
|
|
39
|
+
}
|
|
40
|
+
const selected = (path) => room.typedSelection?.roots.some((root) => beneath(root, path)) ?? false;
|
|
41
|
+
const paths = reach.documents
|
|
42
|
+
.filter((document) => document.room === room.locator)
|
|
43
|
+
.map(({ path }) => path);
|
|
44
|
+
return { collection: paths.some((path) => !selected(path)), selection: paths.some(selected) };
|
|
45
|
+
}
|
|
46
|
+
function covers(reach, room, path) {
|
|
47
|
+
return (reach === null ||
|
|
48
|
+
reach.rooms.includes(room) ||
|
|
49
|
+
reach.documents.some((document) => document.room === room && document.path === path));
|
|
50
|
+
}
|
|
51
|
+
function conversing(reach) {
|
|
52
|
+
return reach === null || reach.conversations;
|
|
53
|
+
}
|
|
54
|
+
export function planReach(reach) {
|
|
55
|
+
return reach === null
|
|
56
|
+
? null
|
|
57
|
+
: {
|
|
58
|
+
rooms: [...new Set(reach.rooms)].sort(),
|
|
59
|
+
documents: [
|
|
60
|
+
...new Set(reach.documents.map(({ room, path }) => documentKey(room, path))),
|
|
61
|
+
].sort(),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function roomsWithin(reach, rooms) {
|
|
65
|
+
return rooms.filter((room) => wholly(reach, room.locator));
|
|
66
|
+
}
|
|
67
|
+
function typesWithin(reach, room, types) {
|
|
68
|
+
if (room === undefined)
|
|
69
|
+
return structuredClone(UNHELD_TYPES);
|
|
70
|
+
const governed = governs(reach, room);
|
|
71
|
+
return {
|
|
72
|
+
collection: governed.collection ? types.collection : modelTypes(NO_MODEL),
|
|
73
|
+
typedSelection: governed.selection ? types.typedSelection : null,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function moves(reach, room, move) {
|
|
77
|
+
return move === undefined || covers(reach, room, moveTarget(move));
|
|
78
|
+
}
|
|
79
|
+
function draftWithin(reach, draft) {
|
|
80
|
+
return covers(reach, draft.room, draft.path) && moves(reach, draft.room, draft.parts.move);
|
|
81
|
+
}
|
|
82
|
+
function draftsWithin(reach, drafts) {
|
|
83
|
+
return drafts.filter((draft) => draftWithin(reach, draft));
|
|
84
|
+
}
|
|
85
|
+
function shownDraft(reach, draft) {
|
|
86
|
+
return draft !== null && draftWithin(reach, draft) ? draft : null;
|
|
87
|
+
}
|
|
88
|
+
function writeWithin(reach, write) {
|
|
89
|
+
return write.outcome === 'written'
|
|
90
|
+
? { outcome: 'written', roomDraft: shownDraft(reach, write.roomDraft) }
|
|
91
|
+
: { outcome: 'stale', current: shownDraft(reach, write.current) };
|
|
92
|
+
}
|
|
93
|
+
function savedWithin(reach, room, result) {
|
|
94
|
+
if (reach === null || result.outcome === 'saved')
|
|
95
|
+
return result;
|
|
96
|
+
const { file, location, referrers, ...refusal } = result;
|
|
97
|
+
const hidden = file !== undefined && !covers(reach, room, file);
|
|
98
|
+
return {
|
|
99
|
+
...refusal,
|
|
100
|
+
...(file === undefined || hidden ? {} : { file }),
|
|
101
|
+
...(location === undefined || hidden ? {} : { location }),
|
|
102
|
+
...(referrers === undefined
|
|
103
|
+
? {}
|
|
104
|
+
: { referrers: referrers.filter((path) => covers(reach, room, path)) }),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function statusWithin(reach, status) {
|
|
108
|
+
return {
|
|
109
|
+
...status,
|
|
110
|
+
attentionRequired: status.attentionRequired.filter((entry) => covers(reach, entry.room, entry.path)),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
function queueWithin(reach, room, entries) {
|
|
114
|
+
return entries.filter(({ batch }) => batch.changes.every((change) => covers(reach, room, change.path) &&
|
|
115
|
+
(change.kind !== 'move' || covers(reach, room, change.to))));
|
|
116
|
+
}
|
|
117
|
+
function unreached(room) {
|
|
118
|
+
return new Error(`No Local Replica for ${room}`);
|
|
119
|
+
}
|
|
120
|
+
export function narrowed(runtime, given) {
|
|
121
|
+
const { md, watch } = runtime;
|
|
122
|
+
let reach = given;
|
|
123
|
+
const open = new Set();
|
|
124
|
+
function following(begin) {
|
|
125
|
+
let stop = begin();
|
|
126
|
+
const restart = () => {
|
|
127
|
+
stop();
|
|
128
|
+
stop = begin();
|
|
129
|
+
};
|
|
130
|
+
open.add(restart);
|
|
131
|
+
return () => {
|
|
132
|
+
open.delete(restart);
|
|
133
|
+
stop();
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
const within = (topic, input, deliver) => {
|
|
137
|
+
if (topic === 'rooms') {
|
|
138
|
+
return following(() => watch(topic, input, (rooms) => deliver(roomsWithin(reach, rooms))));
|
|
139
|
+
}
|
|
140
|
+
if (topic === 'sync-status') {
|
|
141
|
+
return following(() => watch(topic, input, (status) => deliver(statusWithin(reach, status))));
|
|
142
|
+
}
|
|
143
|
+
if (topic === 'room-drafts') {
|
|
144
|
+
return following(() => watch(topic, input, (drafts) => deliver(draftsWithin(reach, drafts))));
|
|
145
|
+
}
|
|
146
|
+
if (topic === 'queue') {
|
|
147
|
+
const room = input[0];
|
|
148
|
+
return following(() => {
|
|
149
|
+
if (!holds(reach, room)) {
|
|
150
|
+
deliver([]);
|
|
151
|
+
return () => { };
|
|
152
|
+
}
|
|
153
|
+
return watch(topic, input, (entries) => deliver(queueWithin(reach, room, entries)));
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
if (topic === 'query') {
|
|
157
|
+
const plan = runtime.plan(input[0], reach);
|
|
158
|
+
let held = null;
|
|
159
|
+
return following(() => {
|
|
160
|
+
let first = true;
|
|
161
|
+
return runtime.watchPlan({ ...plan, reach: planReach(reach) }, (message) => {
|
|
162
|
+
if (!first) {
|
|
163
|
+
held = applied(held, message);
|
|
164
|
+
deliver(message);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
first = false;
|
|
168
|
+
if (held === null) {
|
|
169
|
+
held = message;
|
|
170
|
+
deliver(message);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
const next = reconciled(held, message);
|
|
174
|
+
if (next === null)
|
|
175
|
+
return;
|
|
176
|
+
held = next.keyed;
|
|
177
|
+
deliver(next.change);
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
if (topic === 'conversation') {
|
|
182
|
+
return following(() => {
|
|
183
|
+
if (conversing(reach))
|
|
184
|
+
return watch(topic, input, deliver);
|
|
185
|
+
deliver(unheldView(input[0]));
|
|
186
|
+
return () => { };
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
if (topic === 'conversations') {
|
|
190
|
+
return following(() => {
|
|
191
|
+
if (conversing(reach))
|
|
192
|
+
return watch(topic, input, deliver);
|
|
193
|
+
deliver([]);
|
|
194
|
+
return () => { };
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
return watch(topic, input, deliver);
|
|
198
|
+
};
|
|
199
|
+
const conversation = () => (conversing(reach) ? md.conversation : NO_CONVERSATION);
|
|
200
|
+
const chatDraft = () => (conversing(reach) ? md.chatDraft : NO_CHAT_DRAFT);
|
|
201
|
+
const view = {
|
|
202
|
+
...md,
|
|
203
|
+
rooms: () => roomsWithin(reach, md.rooms()),
|
|
204
|
+
query: ((input) => runtime.query(runtime.plan(input, reach))),
|
|
205
|
+
search: (input) => runtime.search(input, reach),
|
|
206
|
+
types(room) {
|
|
207
|
+
if (wholly(reach, room))
|
|
208
|
+
return md.types(room);
|
|
209
|
+
if (!holds(reach, room))
|
|
210
|
+
return structuredClone(UNHELD_TYPES);
|
|
211
|
+
const listed = md.rooms().find(({ locator }) => locator === room);
|
|
212
|
+
return typesWithin(reach, listed, md.types(room));
|
|
213
|
+
},
|
|
214
|
+
async save(room, roomDrafts, writtenBy) {
|
|
215
|
+
if (!holds(reach, room))
|
|
216
|
+
throw unreached(room);
|
|
217
|
+
const outside = roomDrafts.find(({ path }) => !covers(reach, room, path) ||
|
|
218
|
+
!moves(reach, room, md.roomDraft.read(room, path)?.parts.move));
|
|
219
|
+
if (outside !== undefined)
|
|
220
|
+
return { outcome: 'refused', rule: 'stale', file: outside.path };
|
|
221
|
+
return savedWithin(reach, room, await md.save(room, roomDrafts, writtenBy));
|
|
222
|
+
},
|
|
223
|
+
history: (room, path, query) => covers(reach, room, path)
|
|
224
|
+
? md.history(room, path, query)
|
|
225
|
+
: Promise.resolve({ kind: 'room-not-readable', locator: room }),
|
|
226
|
+
reached: (room, roomVersion, path) => (path === undefined ? wholly(reach, room) : covers(reach, room, path)) &&
|
|
227
|
+
md.reached(room, roomVersion, path),
|
|
228
|
+
log: () => (reach === null ? md.log() : ''),
|
|
229
|
+
roomDraft: {
|
|
230
|
+
list: () => md.roomDraft.list().then((drafts) => draftsWithin(reach, drafts)),
|
|
231
|
+
read: (room, path) => covers(reach, room, path) ? shownDraft(reach, md.roomDraft.read(room, path)) : null,
|
|
232
|
+
async write(room, path, revision, parts) {
|
|
233
|
+
if (!covers(reach, room, path) || !moves(reach, room, readParts(parts).move)) {
|
|
234
|
+
throw unreached(room);
|
|
235
|
+
}
|
|
236
|
+
return writeWithin(reach, await md.roomDraft.write(room, path, revision, parts));
|
|
237
|
+
},
|
|
238
|
+
async discard(room, path, revision) {
|
|
239
|
+
if (!covers(reach, room, path))
|
|
240
|
+
return { outcome: 'stale', current: null };
|
|
241
|
+
return writeWithin(reach, await md.roomDraft.discard(room, path, revision));
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
conversation: {
|
|
245
|
+
list: () => conversation().list(),
|
|
246
|
+
read: (id, options) => conversation().read(id, options),
|
|
247
|
+
create: (input) => conversation().create(input),
|
|
248
|
+
send: (id) => conversation().send(id),
|
|
249
|
+
answer: (id, answer) => conversation().answer(id, answer),
|
|
250
|
+
interrupt: (id) => conversation().interrupt(id),
|
|
251
|
+
kill: (id) => conversation().kill(id),
|
|
252
|
+
cancel: (id, turn) => conversation().cancel(id, turn),
|
|
253
|
+
delete: (id) => conversation().delete(id),
|
|
254
|
+
},
|
|
255
|
+
chatDraft: {
|
|
256
|
+
write: (id, draft) => chatDraft().write(id, draft),
|
|
257
|
+
discard: (id) => chatDraft().discard(id),
|
|
258
|
+
},
|
|
259
|
+
observe: observing(within),
|
|
260
|
+
};
|
|
261
|
+
return {
|
|
262
|
+
md: view,
|
|
263
|
+
watch: within,
|
|
264
|
+
narrow(next) {
|
|
265
|
+
reach = next;
|
|
266
|
+
for (const restart of [...open])
|
|
267
|
+
restart();
|
|
268
|
+
},
|
|
269
|
+
};
|
|
270
|
+
}
|
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { MdRuntimeClient } from './client.js';
|
|
2
|
+
import type { AppRoom, ConnectionRefusal, SaveResult } from './connection.js';
|
|
3
|
+
import type { ServiceMode } from './adapter.js';
|
|
4
|
+
import type { ConversationEntry, ConversationOptions, ConversationView } from './conversation.js';
|
|
5
|
+
import type { QueueEntry } from './outcome.js';
|
|
6
|
+
import type { DocumentQuery, DocumentResult, GroupQuery, GroupResult } from './query.js';
|
|
7
|
+
import type { RoomDraft, RoomDraftParts } from './room-draft.js';
|
|
8
|
+
import type { SyncStatus } from './status.js';
|
|
9
|
+
type Client = MdRuntimeClient | null;
|
|
10
|
+
export declare function useObserve(client: Client, topic: 'deployment-facts'): unknown;
|
|
11
|
+
export declare function useObserve(client: Client, topic: 'rooms'): readonly AppRoom[] | undefined;
|
|
12
|
+
export declare function useObserve(client: Client, topic: 'connection-refusal'): ConnectionRefusal | null | undefined;
|
|
13
|
+
export declare function useObserve(client: Client, topic: 'storage-loss'): string | null | undefined;
|
|
14
|
+
export declare function useObserve(client: Client, topic: 'sync-status'): SyncStatus | undefined;
|
|
15
|
+
export declare function useObserve(client: Client, topic: 'service-mode'): ServiceMode | undefined;
|
|
16
|
+
export declare function useObserve(client: Client, topic: 'query', query: DocumentQuery): DocumentResult | undefined;
|
|
17
|
+
export declare function useObserve(client: Client, topic: 'query', query: GroupQuery): GroupResult | undefined;
|
|
18
|
+
export declare function useObserve(client: Client, topic: 'queue', locator: string): readonly QueueEntry[] | undefined;
|
|
19
|
+
export declare function useObserve(client: Client, topic: 'room-drafts'): readonly RoomDraft[] | undefined;
|
|
20
|
+
export declare function useObserve(client: Client, topic: 'presentation'): unknown;
|
|
21
|
+
export declare function useObserve(client: Client, topic: 'conversation', conversation: string, options: ConversationOptions): ConversationView | undefined;
|
|
22
|
+
export declare function useObserve(client: Client, topic: 'conversations'): readonly ConversationEntry[] | undefined;
|
|
23
|
+
export type Saving = {
|
|
24
|
+
readonly busy: boolean;
|
|
25
|
+
readonly outcome: SaveResult | null;
|
|
26
|
+
readonly failure: Error | null;
|
|
27
|
+
save(room: string, roomDrafts: ReadonlyMap<string, RoomDraftParts>): Promise<SaveResult>;
|
|
28
|
+
};
|
|
29
|
+
export declare function saveRoomDrafts(client: MdRuntimeClient, room: string, roomDrafts: ReadonlyMap<string, RoomDraftParts>): Promise<SaveResult>;
|
|
30
|
+
export declare function useSave(client: MdRuntimeClient): Saving;
|
|
31
|
+
export {};
|
|
32
|
+
//# sourceMappingURL=react.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.ts"],"names":[],"mappings":"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"}
|