@agentshouse/mdruntime 0.1.0-c698c0ebd65bd → 0.1.0-c6dd3e87e3f5e

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.
Files changed (229) hide show
  1. package/README.md +18 -15
  2. package/dist/adapter.d.ts +125 -50
  3. package/dist/adapter.d.ts.map +1 -1
  4. package/dist/attached-model.d.ts +5 -1
  5. package/dist/attached-model.d.ts.map +1 -1
  6. package/dist/attached-model.js +38 -4
  7. package/dist/binary.d.ts +0 -5
  8. package/dist/binary.d.ts.map +1 -1
  9. package/dist/binary.js +1 -1
  10. package/dist/bootstrap.d.ts +27 -11
  11. package/dist/bootstrap.d.ts.map +1 -1
  12. package/dist/bootstrap.js +184 -85
  13. package/dist/chain.d.ts +11 -0
  14. package/dist/chain.d.ts.map +1 -0
  15. package/dist/chain.js +64 -0
  16. package/dist/change-set.d.ts +9 -14
  17. package/dist/change-set.d.ts.map +1 -1
  18. package/dist/change-set.js +11 -63
  19. package/dist/change.d.ts +31 -1
  20. package/dist/change.d.ts.map +1 -1
  21. package/dist/change.js +10 -1
  22. package/dist/client.d.ts +25 -14
  23. package/dist/client.d.ts.map +1 -1
  24. package/dist/client.js +97 -11
  25. package/dist/collection.d.ts +10 -7
  26. package/dist/collection.d.ts.map +1 -1
  27. package/dist/collection.js +38 -79
  28. package/dist/compile.d.ts +16 -0
  29. package/dist/compile.d.ts.map +1 -0
  30. package/dist/compile.js +786 -0
  31. package/dist/connection.d.ts +44 -17
  32. package/dist/connection.d.ts.map +1 -1
  33. package/dist/connection.js +3496 -976
  34. package/dist/conversation.d.ts +33 -14
  35. package/dist/conversation.d.ts.map +1 -1
  36. package/dist/conversation.js +571 -242
  37. package/dist/digest.d.ts +7 -0
  38. package/dist/digest.d.ts.map +1 -0
  39. package/dist/digest.js +113 -0
  40. package/dist/durability.d.ts +4 -1
  41. package/dist/durability.d.ts.map +1 -1
  42. package/dist/durability.js +5 -0
  43. package/dist/entry.d.ts +6 -3
  44. package/dist/entry.d.ts.map +1 -1
  45. package/dist/entry.js +18 -23
  46. package/dist/file.d.ts +2 -19
  47. package/dist/file.d.ts.map +1 -1
  48. package/dist/file.js +7 -122
  49. package/dist/fulltext.d.ts +27 -0
  50. package/dist/fulltext.d.ts.map +1 -0
  51. package/dist/fulltext.js +370 -0
  52. package/dist/host.d.ts +12 -3
  53. package/dist/host.d.ts.map +1 -1
  54. package/dist/host.js +125 -19
  55. package/dist/identity.d.ts +1 -0
  56. package/dist/identity.d.ts.map +1 -1
  57. package/dist/identity.js +3 -0
  58. package/dist/index.d.ts +16 -10
  59. package/dist/index.d.ts.map +1 -1
  60. package/dist/index.js +6 -0
  61. package/dist/live.d.ts +22 -0
  62. package/dist/live.d.ts.map +1 -0
  63. package/dist/live.js +123 -0
  64. package/dist/media.d.ts +8 -0
  65. package/dist/media.d.ts.map +1 -0
  66. package/dist/media.js +218 -0
  67. package/dist/memory.d.ts +14 -6
  68. package/dist/memory.d.ts.map +1 -1
  69. package/dist/memory.js +198 -94
  70. package/dist/messages.d.ts +28 -0
  71. package/dist/messages.d.ts.map +1 -0
  72. package/dist/messages.js +90 -0
  73. package/dist/model.d.ts +9 -2
  74. package/dist/model.d.ts.map +1 -1
  75. package/dist/model.js +8 -2
  76. package/dist/name.d.ts +4 -0
  77. package/dist/name.d.ts.map +1 -0
  78. package/dist/name.js +122 -0
  79. package/dist/observation.d.ts +20 -2
  80. package/dist/observation.d.ts.map +1 -1
  81. package/dist/observation.js +113 -12
  82. package/dist/opened.d.ts +22 -0
  83. package/dist/opened.d.ts.map +1 -0
  84. package/dist/opened.js +107 -0
  85. package/dist/outcome.d.ts +45 -7
  86. package/dist/outcome.d.ts.map +1 -1
  87. package/dist/outcome.js +39 -0
  88. package/dist/overlay.d.ts +3 -4
  89. package/dist/overlay.d.ts.map +1 -1
  90. package/dist/overlay.js +100 -182
  91. package/dist/paths.d.ts +1 -0
  92. package/dist/paths.d.ts.map +1 -1
  93. package/dist/paths.js +4 -0
  94. package/dist/query.d.ts +66 -4
  95. package/dist/query.d.ts.map +1 -1
  96. package/dist/query.js +48 -7
  97. package/dist/reach.d.ts +18 -6
  98. package/dist/reach.d.ts.map +1 -1
  99. package/dist/reach.js +220 -73
  100. package/dist/react.d.ts +8 -6
  101. package/dist/react.d.ts.map +1 -1
  102. package/dist/react.js +7 -17
  103. package/dist/readiness.d.ts +52 -0
  104. package/dist/readiness.d.ts.map +1 -0
  105. package/dist/readiness.js +27 -0
  106. package/dist/recovery.d.ts +26 -0
  107. package/dist/recovery.d.ts.map +1 -0
  108. package/dist/recovery.js +224 -0
  109. package/dist/refresh.d.ts +16 -14
  110. package/dist/refresh.d.ts.map +1 -1
  111. package/dist/refresh.js +115 -51
  112. package/dist/replica.d.ts +55 -33
  113. package/dist/replica.d.ts.map +1 -1
  114. package/dist/replica.js +297 -154
  115. package/dist/room-draft.d.ts +34 -4
  116. package/dist/room-draft.d.ts.map +1 -1
  117. package/dist/room-draft.js +105 -31
  118. package/dist/rows.d.ts +37 -37
  119. package/dist/rows.d.ts.map +1 -1
  120. package/dist/rows.js +173 -104
  121. package/dist/save.d.ts +53 -9
  122. package/dist/save.d.ts.map +1 -1
  123. package/dist/save.js +537 -170
  124. package/dist/search.d.ts +39 -28
  125. package/dist/search.d.ts.map +1 -1
  126. package/dist/search.js +37 -105
  127. package/dist/seed.d.ts +16 -0
  128. package/dist/seed.d.ts.map +1 -0
  129. package/dist/seed.js +75 -0
  130. package/dist/shared.d.ts +15 -0
  131. package/dist/shared.d.ts.map +1 -0
  132. package/dist/shared.js +421 -0
  133. package/dist/status.d.ts +5 -0
  134. package/dist/status.d.ts.map +1 -1
  135. package/dist/status.js +3 -1
  136. package/dist/store.d.ts +96 -80
  137. package/dist/store.d.ts.map +1 -1
  138. package/dist/store.js +306 -523
  139. package/dist/stored.d.ts +130 -0
  140. package/dist/stored.d.ts.map +1 -0
  141. package/dist/stored.js +560 -0
  142. package/dist/tables.d.ts +325 -0
  143. package/dist/tables.d.ts.map +1 -0
  144. package/dist/tables.js +578 -0
  145. package/dist/transfer.d.ts +0 -5
  146. package/dist/transfer.d.ts.map +1 -1
  147. package/dist/turn.d.ts +9 -0
  148. package/dist/turn.d.ts.map +1 -0
  149. package/dist/turn.js +75 -0
  150. package/dist/validation.d.ts +29 -0
  151. package/dist/validation.d.ts.map +1 -0
  152. package/dist/validation.js +101 -0
  153. package/dist/windows.d.ts +22 -0
  154. package/dist/windows.d.ts.map +1 -0
  155. package/dist/windows.js +89 -0
  156. package/dist/wire.d.ts +164 -32
  157. package/dist/wire.d.ts.map +1 -1
  158. package/dist/wire.js +55 -31
  159. package/dist/words.d.ts +19 -0
  160. package/dist/words.d.ts.map +1 -0
  161. package/dist/words.js +156 -0
  162. package/package.json +18 -19
  163. package/AGENTS.md +0 -299
  164. package/dist/chat/acts.d.ts +0 -33
  165. package/dist/chat/acts.d.ts.map +0 -1
  166. package/dist/chat/acts.js +0 -36
  167. package/dist/chat/context.d.ts +0 -3
  168. package/dist/chat/context.d.ts.map +0 -1
  169. package/dist/chat/context.js +0 -10
  170. package/dist/chat/input.d.ts +0 -9
  171. package/dist/chat/input.d.ts.map +0 -1
  172. package/dist/chat/input.js +0 -99
  173. package/dist/chat/kit.d.ts +0 -60
  174. package/dist/chat/kit.d.ts.map +0 -1
  175. package/dist/chat/kit.js +0 -123
  176. package/dist/chat/lasting.d.ts +0 -6
  177. package/dist/chat/lasting.d.ts.map +0 -1
  178. package/dist/chat/lasting.js +0 -19
  179. package/dist/chat/list.d.ts +0 -26
  180. package/dist/chat/list.d.ts.map +0 -1
  181. package/dist/chat/list.js +0 -62
  182. package/dist/chat/markdown.d.ts +0 -20
  183. package/dist/chat/markdown.d.ts.map +0 -1
  184. package/dist/chat/markdown.js +0 -188
  185. package/dist/chat/message.d.ts +0 -6
  186. package/dist/chat/message.d.ts.map +0 -1
  187. package/dist/chat/message.js +0 -192
  188. package/dist/chat/messages.d.ts +0 -21
  189. package/dist/chat/messages.d.ts.map +0 -1
  190. package/dist/chat/messages.js +0 -151
  191. package/dist/chat/name.d.ts +0 -6
  192. package/dist/chat/name.d.ts.map +0 -1
  193. package/dist/chat/name.js +0 -37
  194. package/dist/chat/opened.d.ts +0 -11
  195. package/dist/chat/opened.d.ts.map +0 -1
  196. package/dist/chat/opened.js +0 -17
  197. package/dist/chat/overlay.d.ts +0 -31
  198. package/dist/chat/overlay.d.ts.map +0 -1
  199. package/dist/chat/overlay.js +0 -97
  200. package/dist/chat/parts.d.ts +0 -57
  201. package/dist/chat/parts.d.ts.map +0 -1
  202. package/dist/chat/parts.js +0 -119
  203. package/dist/chat/process.d.ts +0 -20
  204. package/dist/chat/process.d.ts.map +0 -1
  205. package/dist/chat/process.js +0 -32
  206. package/dist/chat/provider.d.ts +0 -63
  207. package/dist/chat/provider.d.ts.map +0 -1
  208. package/dist/chat/provider.js +0 -620
  209. package/dist/chat/question.d.ts +0 -9
  210. package/dist/chat/question.d.ts.map +0 -1
  211. package/dist/chat/question.js +0 -80
  212. package/dist/chat/questions.d.ts +0 -31
  213. package/dist/chat/questions.d.ts.map +0 -1
  214. package/dist/chat/questions.js +0 -111
  215. package/dist/chat/read.d.ts +0 -5
  216. package/dist/chat/read.d.ts.map +0 -1
  217. package/dist/chat/read.js +0 -13
  218. package/dist/chat/ready.d.ts +0 -33
  219. package/dist/chat/ready.d.ts.map +0 -1
  220. package/dist/chat/ready.js +0 -91
  221. package/dist/chat/style.d.ts +0 -4
  222. package/dist/chat/style.d.ts.map +0 -1
  223. package/dist/chat/style.js +0 -302
  224. package/dist/chat.d.ts +0 -14
  225. package/dist/chat.d.ts.map +0 -1
  226. package/dist/chat.js +0 -8
  227. package/dist/read-side.d.ts +0 -18
  228. package/dist/read-side.d.ts.map +0 -1
  229. package/dist/read-side.js +0 -440
package/dist/query.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { pointerFacts, } from '@agentshouse/mdmodel';
2
2
  import { referenceField } from './model.js';
3
3
  import { normalize } from './paths.js';
4
+ import { decodedCursor } from './windows.js';
4
5
  export class QueryError extends Error {
5
6
  code;
6
7
  retryable = false;
@@ -11,9 +12,18 @@ export class QueryError extends Error {
11
12
  }
12
13
  const RANGE = ['$gt', '$gte', '$lt', '$lte'];
13
14
  const SINGLE = ['$eq', '$ne', '$in', '$nin', '$all', '$size', '$exists'];
14
- const FILTER = ['rooms', 'types', 'where', 'pathPrefix', 'hop', 'backlinks'];
15
+ const FILTER = ['rooms', 'types', 'where', 'pathPrefix', 'folder', 'paths', 'hop', 'backlinks'];
15
16
  const RELATED = ['types', 'where', 'pathPrefix', 'field'];
16
- const PARTS = ['fields', 'sections', 'preamble', 'markdown', 'backlinks', 'change'];
17
+ const PARTS = [
18
+ 'fields',
19
+ 'sections',
20
+ 'preamble',
21
+ 'markdown',
22
+ 'backlinks',
23
+ 'change',
24
+ 'fileId',
25
+ 'revision',
26
+ ];
17
27
  const ORDER = ['by', 'direction', 'nulls', 'stringSort', 'locale', 'localeOptions'];
18
28
  const CALENDAR_DATE = /^(\d{4})-(\d{2})-(\d{2})$/;
19
29
  const STRAY_ESCAPE = /~(?![01])/u;
@@ -60,6 +70,13 @@ function names(input, at) {
60
70
  invalid(at, 'expects a list of names');
61
71
  return [...new Set(listed)].sort();
62
72
  }
73
+ function pathsOf(input) {
74
+ const listed = list(input, 'paths');
75
+ if (listed.some((item) => typeof item !== 'string' || item === '' || normalize(item) !== item)) {
76
+ invalid('paths', 'expects a list of Paths');
77
+ }
78
+ return [...new Set(listed)].sort();
79
+ }
63
80
  function directory(input, at) {
64
81
  if (typeof input !== 'string' || normalize(input) !== input) {
65
82
  invalid(at, 'expects a normalized directory');
@@ -279,15 +296,16 @@ function backlinksOf(facts, rooms, input) {
279
296
  return { types, where, pathPrefix, field };
280
297
  }
281
298
  function keyPlan(typed, input, at, path) {
282
- if (input === 'room' || input === 'type' || (path && input === 'path'))
299
+ if (input === 'room' || input === 'type' || input === 'folder' || (path && input === 'path')) {
283
300
  return { of: input };
301
+ }
284
302
  if (plain(input) && Object.hasOwn(input, 'field') && Object.keys(input).length === 1) {
285
303
  if (specs(typed, input.field, place(at, 'field')).some(several)) {
286
304
  invalid(place(at, 'field'), 'a path that can hold several values');
287
305
  }
288
306
  return { of: 'field', field: input.field };
289
307
  }
290
- return invalid(at, path ? 'expects room, path, type or a field' : 'expects room, type or a field');
308
+ return invalid(at, path ? 'expects room, path, type, folder or a field' : 'expects room, type, folder or a field');
291
309
  }
292
310
  function direction(input, at) {
293
311
  if (input === undefined)
@@ -349,7 +367,7 @@ function parts(typed, input, at, fields) {
349
367
  });
350
368
  return named;
351
369
  }
352
- function flag(input, at) {
370
+ export function flag(input, at) {
353
371
  if (input === undefined)
354
372
  return false;
355
373
  if (input !== true)
@@ -377,6 +395,8 @@ function documentShape(typed, input) {
377
395
  markdown: true,
378
396
  backlinks: true,
379
397
  change: true,
398
+ fileId: true,
399
+ revision: true,
380
400
  orderBy,
381
401
  };
382
402
  }
@@ -391,6 +411,8 @@ function documentShape(typed, input) {
391
411
  markdown: flag(select.markdown, 'select.markdown'),
392
412
  backlinks: flag(select.backlinks, 'select.backlinks'),
393
413
  change: flag(select.change, 'select.change'),
414
+ fileId: flag(select.fileId, 'select.fileId'),
415
+ revision: flag(select.revision, 'select.revision'),
394
416
  orderBy,
395
417
  };
396
418
  }
@@ -421,20 +443,36 @@ export function planOf(input, facts, reach = null) {
421
443
  invalid('select', 'a grouped query selects nothing');
422
444
  if (!grouped && input.select === undefined)
423
445
  invalid('select', 'a Document query must select');
424
- known(input, [...FILTER, 'orderBy', 'limit', grouped ? 'groupBy' : 'select'], '');
446
+ known(input, [...FILTER, 'orderBy', 'limit', 'after', 'total', grouped ? 'groupBy' : 'select'], '');
425
447
  const limit = limitOf(input.limit);
426
448
  const rooms = input.rooms === undefined ? null : names(input.rooms, 'rooms');
427
449
  const { typed, ...root } = scope(facts, rooms, input, '');
450
+ const shape = grouped ? groupShape(typed, input) : documentShape(typed, input);
428
451
  return {
452
+ key: queryKey(input),
429
453
  rooms,
430
454
  reach,
455
+ locked: [],
431
456
  root,
457
+ folder: input.folder === undefined ? null : directory(input.folder, 'folder'),
458
+ paths: input.paths === undefined ? null : pathsOf(input.paths),
432
459
  hop: input.hop === undefined ? null : hopOf(facts, rooms, typed, input.hop),
433
460
  backlinks: input.backlinks === undefined ? null : backlinksOf(facts, rooms, input.backlinks),
434
- shape: grouped ? groupShape(typed, input) : documentShape(typed, input),
461
+ shape,
435
462
  limit,
463
+ after: continuationOf(input.after, shape.orderBy.length + 1),
464
+ total: flag(input.total, 'total'),
436
465
  };
437
466
  }
467
+ function continuationOf(input, arity) {
468
+ if (input === undefined)
469
+ return null;
470
+ const values = typeof input === 'string' ? decodedCursor(input) : null;
471
+ if (values === null || (values.length !== 0 && values.length !== arity)) {
472
+ invalid('after', 'expects the next an earlier result of this query returned');
473
+ }
474
+ return { text: input, values: values.length === 0 ? null : values };
475
+ }
438
476
  export function limitOf(input) {
439
477
  if (input === undefined)
440
478
  return null;
@@ -449,6 +487,9 @@ export function filterOf(input, facts, reach = null) {
449
487
  known(input, FILTER, '');
450
488
  return planOf({ ...input, select: {} }, facts, reach);
451
489
  }
490
+ export function queryKey(query) {
491
+ return canonical(query);
492
+ }
452
493
  function canonical(value) {
453
494
  if (Array.isArray(value))
454
495
  return `[${value.map(canonical).join(',')}]`;
package/dist/reach.d.ts CHANGED
@@ -1,14 +1,20 @@
1
- import type { ReferrerScope } from './adapter.js';
2
- import type { MdRuntime, RuntimeTopic } from './connection.js';
1
+ import type { AppRoom, MdRuntime, ReadinessPort, RuntimeTopic } from './connection.js';
3
2
  import type { Plan, PlanReach, QueryResult } from './query.js';
4
- import type { SearchResult } from './search.js';
3
+ import type { Readiness } from './readiness.js';
4
+ import { type SearchResult } from './search.js';
5
5
  export type ReachedDocument = {
6
6
  readonly room: string;
7
7
  readonly path: string;
8
8
  };
9
+ export type ReachedArea = {
10
+ readonly room: string;
11
+ readonly path: string;
12
+ readonly open: readonly string[];
13
+ };
9
14
  export type PortReach = {
10
15
  readonly rooms: readonly string[];
11
16
  readonly documents: readonly ReachedDocument[];
17
+ readonly areas?: readonly ReachedArea[];
12
18
  readonly conversations: boolean;
13
19
  };
14
20
  export type Deliver = (message: unknown) => void;
@@ -19,13 +25,19 @@ export type RuntimeView = {
19
25
  };
20
26
  export type NarrowedView = RuntimeView & {
21
27
  narrow(reach: PortReach | null): void;
28
+ facts(port: ReadinessPort): void;
22
29
  };
23
30
  export type Reachable = RuntimeView & {
24
31
  plan(input: unknown, reach: PortReach | null): Plan;
25
32
  query(plan: Plan): Promise<QueryResult>;
26
33
  watchPlan(plan: Plan, deliver: (message: unknown) => void): () => void;
27
- search(input: unknown, reach: PortReach | null): Promise<SearchResult>;
28
- referrerScope(referrer: unknown): ReferrerScope | null;
34
+ search(input: unknown, reach: PortReach | null, locked: readonly string[]): Promise<SearchResult>;
35
+ watchReadiness(port: ReadinessPort, reach: PortReach | null, deliver: (readiness: Readiness) => void): () => void;
36
+ locked(room: string): boolean;
37
+ lockedRooms(): readonly string[];
38
+ watchLocks(notify: () => void): () => void;
39
+ located(room: string, path: string, destination: string): ReachedDocument | null;
40
+ room(locator: string): AppRoom | undefined;
29
41
  };
30
42
  export declare function holds(reach: PortReach | null, room: string): boolean;
31
43
  export declare function governs(reach: PortReach | null, room: {
@@ -38,5 +50,5 @@ export declare function governs(reach: PortReach | null, room: {
38
50
  readonly selection: boolean;
39
51
  };
40
52
  export declare function planReach(reach: PortReach | null): PlanReach | null;
41
- export declare function narrowed(runtime: Reachable, given: PortReach | null): NarrowedView;
53
+ export declare function narrowed(runtime: Reachable, given: PortReach | null, told: ReadinessPort): NarrowedView;
42
54
  //# sourceMappingURL=reach.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"reach.d.ts","sourceRoot":"","sources":["../src/reach.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,EAAW,SAAS,EAAe,YAAY,EAAc,MAAM,iBAAiB,CAAC;AAajG,OAAO,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAU/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;IACvE,aAAa,CAAC,QAAQ,EAAE,OAAO,GAAG,aAAa,GAAG,IAAI,CAAC;CACxD,CAAC;AAyBF,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;AA2BD,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,CA4KlF"}
1
+ {"version":3,"file":"reach.d.ts","sourceRoot":"","sources":["../src/reach.ts"],"names":[],"mappings":"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"}
package/dist/reach.js CHANGED
@@ -1,12 +1,12 @@
1
1
  import { unheldView } from './conversation.js';
2
2
  import { modelTypes, NO_MODEL } from './model.js';
3
- import { applied, observing } from './observation.js';
3
+ import { changeOf, digestApplied, digestOf, observing, } from './observation.js';
4
4
  import { runtimeError } from './failure.js';
5
- import { failure, undeclared, } from './outcome.js';
5
+ import { failure, lockedRoom, ROOM_LOCKED, undeclared, UNREADABLE, } from './outcome.js';
6
6
  import { beneath } from './paths.js';
7
- import { reconciled } from './read-side.js';
8
7
  import { moveTarget, readParts, staleWrite, } from './room-draft.js';
9
8
  import { documentKey } from './rows.js';
9
+ import { undeclaredSearch } from './search.js';
10
10
  const UNHELD_TYPES = { collection: modelTypes(NO_MODEL), typedSelection: null };
11
11
  function unconversed(name) {
12
12
  return () => Promise.resolve(undeclared(name));
@@ -14,18 +14,26 @@ function unconversed(name) {
14
14
  const NO_CONVERSATION = {
15
15
  list: () => Promise.resolve([]),
16
16
  read: (conversation) => Promise.resolve(unheldView(conversation)),
17
- create: unconversed('conversation.create'),
18
- send: unconversed('conversation.send'),
19
- answer: unconversed('conversation.answer'),
20
- interrupt: unconversed('conversation.interrupt'),
21
- kill: unconversed('conversation.kill'),
22
- cancel: unconversed('conversation.cancel'),
23
- delete: unconversed('conversation.delete'),
17
+ create: unconversed('conversations/create'),
18
+ send: unconversed('conversations/send'),
19
+ answer: unconversed('conversations/interaction/answer'),
20
+ interrupt: unconversed('conversations/interrupt'),
21
+ kill: unconversed('conversations/kill'),
22
+ delete: unconversed('conversations/delete'),
24
23
  };
25
24
  const NO_CHAT_DRAFT = {
26
25
  write: () => Promise.resolve(),
26
+ prepend: () => Promise.resolve(),
27
27
  discard: () => Promise.resolve(),
28
28
  };
29
+ function areasOf(reach) {
30
+ return reach.areas ?? [];
31
+ }
32
+ function fenced(reach, room, path) {
33
+ return areasOf(reach).some((area) => area.room === room &&
34
+ beneath(area.path, path) &&
35
+ !area.open.some((open) => beneath(open, path)));
36
+ }
29
37
  export function holds(reach, room) {
30
38
  return (reach === null ||
31
39
  reach.rooms.includes(room) ||
@@ -46,26 +54,13 @@ export function governs(reach, room) {
46
54
  }
47
55
  function covers(reach, room, path) {
48
56
  return (reach === null ||
49
- reach.rooms.includes(room) ||
50
- reach.documents.some((document) => document.room === room && document.path === path));
57
+ (!fenced(reach, room, path) &&
58
+ (reach.rooms.includes(room) ||
59
+ reach.documents.some((document) => document.room === room && document.path === path))));
51
60
  }
52
61
  function conversing(reach) {
53
62
  return reach === null || reach.conversations;
54
63
  }
55
- function refers(reach, scope) {
56
- if (reach === null)
57
- return true;
58
- if (scope === null)
59
- return false;
60
- if (scope.kind === 'conversations')
61
- return reach.conversations;
62
- return scope.path === undefined
63
- ? wholly(reach, scope.locator)
64
- : covers(reach, scope.locator, scope.path);
65
- }
66
- function unreachedReference(fileRef) {
67
- return { kind: 'absent', code: 'binary-file-absent', fields: { fileRef }, retryable: false };
68
- }
69
64
  export function planReach(reach) {
70
65
  return reach === null
71
66
  ? null
@@ -74,6 +69,9 @@ export function planReach(reach) {
74
69
  documents: [
75
70
  ...new Set(reach.documents.map(({ room, path }) => documentKey(room, path))),
76
71
  ].sort(),
72
+ areas: areasOf(reach)
73
+ .map(({ room, path, open }) => ({ room, path, open: [...new Set(open)].sort() }))
74
+ .sort((left, right) => documentKey(left.room, left.path) < documentKey(right.room, right.path) ? -1 : 1),
77
75
  };
78
76
  }
79
77
  function roomsWithin(reach, rooms) {
@@ -89,7 +87,7 @@ function typesWithin(reach, room, types) {
89
87
  };
90
88
  }
91
89
  function moves(reach, room, move) {
92
- return move === undefined || covers(reach, room, moveTarget(move));
90
+ return move === undefined || covers(reach, move.room ?? room, moveTarget(move));
93
91
  }
94
92
  function draftWithin(reach, draft) {
95
93
  return covers(reach, draft.room, draft.path) && moves(reach, draft.room, draft.parts.move);
@@ -123,75 +121,127 @@ function statusWithin(reach, status) {
123
121
  return {
124
122
  ...status,
125
123
  attentionRequired: status.attentionRequired.filter((entry) => covers(reach, entry.room, entry.path)),
124
+ unsent: status.unsent.filter((change) => covers(reach, change.room, change.path)),
126
125
  };
127
126
  }
128
127
  function queueWithin(reach, room, entries) {
129
- return entries.filter(({ batch }) => batch.changes.every((change) => covers(reach, room, change.path) &&
130
- (change.kind !== 'move' || covers(reach, room, change.to))));
128
+ return entries
129
+ .filter(({ batch }) => batch.changes.every((change) => covers(reach, room, change.path) &&
130
+ (change.kind !== 'move' || covers(reach, room, change.to))))
131
+ .map((entry) => {
132
+ const { between, ...batch } = entry.batch;
133
+ if (between === undefined)
134
+ return entry;
135
+ return wholly(reach, between.source.room) && wholly(reach, between.destination.room)
136
+ ? entry
137
+ : { ...entry, batch };
138
+ });
131
139
  }
132
140
  function unreached(room) {
133
141
  return runtimeError('room-not-held', `No Local Replica for ${room}`);
134
142
  }
135
- export function narrowed(runtime, given) {
143
+ function lockedError(room) {
144
+ return runtimeError(ROOM_LOCKED, `${room} is locked until House confirms its access`, true);
145
+ }
146
+ function deviceUnreached() {
147
+ return runtimeError('device-unreached', 'Only the panel cleans up this device');
148
+ }
149
+ const ATTACH_REQUIRED = 'attach-required';
150
+ function attachRequired() {
151
+ return runtimeError(ATTACH_REQUIRED, 'App code takes a file in with binaryFile.attach');
152
+ }
153
+ function carriesFile(parts) {
154
+ return parts !== null && typeof parts === 'object' && Object.hasOwn(parts, 'file');
155
+ }
156
+ export function narrowed(runtime, given, told) {
136
157
  const { md, watch } = runtime;
137
158
  let reach = given;
159
+ let port = told;
138
160
  const open = new Set();
139
- function following(begin) {
161
+ function following(begin, locking = false) {
140
162
  let stop = begin();
141
163
  const restart = () => {
142
164
  stop();
143
165
  stop = begin();
144
166
  };
145
167
  open.add(restart);
168
+ const unwatch = locking ? runtime.watchLocks(restart) : () => { };
146
169
  return () => {
147
170
  open.delete(restart);
171
+ unwatch();
148
172
  stop();
149
173
  };
150
174
  }
175
+ const unlocking = (rooms) => {
176
+ const locked = (rooms ?? []).filter((room) => runtime.locked(room));
177
+ return locked.length === 0
178
+ ? null
179
+ : { pending: locked.map((room) => ({ room, awaits: 'unlock' })) };
180
+ };
181
+ const unlocked = (entries) => entries.filter((entry) => !runtime.locked(entry.room));
182
+ const intoLocked = (held) => held.some((move) => move?.room !== undefined && runtime.locked(move.room));
151
183
  const within = (topic, input, deliver) => {
152
184
  if (topic === 'rooms') {
153
185
  return following(() => watch(topic, input, (rooms) => deliver(roomsWithin(reach, rooms))));
154
186
  }
187
+ if (topic === 'readiness') {
188
+ return following(() => runtime.watchReadiness(port, reach, deliver));
189
+ }
155
190
  if (topic === 'sync-status') {
156
- return following(() => watch(topic, input, (status) => deliver(statusWithin(reach, status))));
191
+ return following(() => watch(topic, input, (status) => {
192
+ const within = statusWithin(reach, status);
193
+ deliver({ ...within, attentionRequired: unlocked(within.attentionRequired) });
194
+ }), true);
157
195
  }
158
196
  if (topic === 'room-drafts') {
159
- return following(() => watch(topic, input, (drafts) => deliver(draftsWithin(reach, drafts))));
197
+ return following(() => watch(topic, input, (drafts) => deliver(unlocked(draftsWithin(reach, drafts)))), true);
160
198
  }
161
199
  if (topic === 'queue') {
162
200
  const room = input[0];
163
201
  return following(() => {
164
- if (!holds(reach, room)) {
202
+ if (!holds(reach, room) || runtime.locked(room)) {
165
203
  deliver([]);
166
204
  return () => { };
167
205
  }
168
206
  return watch(topic, input, (entries) => deliver(queueWithin(reach, room, entries)));
169
- });
207
+ }, true);
170
208
  }
171
209
  if (topic === 'query') {
172
210
  const plan = runtime.plan(input[0], reach);
173
211
  let held = null;
174
212
  return following(() => {
213
+ const locked = unlocking(plan.rooms);
214
+ if (locked !== null) {
215
+ const answer = { result: locked, keys: [] };
216
+ if (held === null || changeOf(held, answer) !== null)
217
+ deliver(answer);
218
+ held = digestOf(answer);
219
+ return () => { };
220
+ }
175
221
  let first = true;
176
- return runtime.watchPlan({ ...plan, reach: planReach(reach) }, (message) => {
222
+ let open = true;
223
+ const heard = (message) => {
177
224
  if (!first) {
178
- held = applied(held, message);
225
+ held = digestApplied(held, message);
179
226
  deliver(message);
180
227
  return;
181
228
  }
182
229
  first = false;
183
- if (held === null) {
184
- held = message;
185
- deliver(message);
186
- return;
187
- }
188
- const next = reconciled(held, message);
189
- if (next === null)
190
- return;
191
- held = next.keyed;
192
- deliver(next.change);
230
+ const change = held === null ? message : changeOf(held, message);
231
+ held = digestOf(message);
232
+ if (change !== null)
233
+ deliver(change);
234
+ };
235
+ const current = { ...plan, reach: planReach(reach), locked: runtime.lockedRooms() };
236
+ const stop = runtime.watchPlan(current, (message) => {
237
+ if (open)
238
+ heard(message);
193
239
  });
194
- });
240
+ return () => {
241
+ open = false;
242
+ stop();
243
+ };
244
+ }, true);
195
245
  }
196
246
  if (topic === 'conversation') {
197
247
  return following(() => {
@@ -211,57 +261,149 @@ export function narrowed(runtime, given) {
211
261
  }
212
262
  return watch(topic, input, deliver);
213
263
  };
264
+ const files = new Map();
265
+ let handed = 0;
266
+ async function hand(reading) {
267
+ const answer = await reading;
268
+ if (answer.kind !== 'binary')
269
+ return answer;
270
+ handed += 1;
271
+ const file = String(handed);
272
+ files.set(file, answer.file);
273
+ return { ...answer, file };
274
+ }
214
275
  const conversation = () => (conversing(reach) ? md.conversation : NO_CONVERSATION);
215
276
  const chatDraft = () => (conversing(reach) ? md.chatDraft : NO_CHAT_DRAFT);
216
277
  const view = {
217
278
  ...md,
218
- rooms: () => roomsWithin(reach, md.rooms()),
219
- query: ((input) => runtime.query(runtime.plan(input, reach))),
220
- search: (input) => runtime.search(input, reach),
279
+ rooms: () => md.rooms().then((rooms) => roomsWithin(reach, rooms)),
280
+ query: ((input) => {
281
+ const plan = runtime.plan(input, reach);
282
+ return Promise.resolve(unlocking(plan.rooms) ?? runtime.query({ ...plan, locked: runtime.lockedRooms() }));
283
+ }),
284
+ search: (input) => port.search === 'required'
285
+ ? runtime.search(input, reach, runtime.lockedRooms())
286
+ : Promise.resolve(undeclaredSearch()),
221
287
  types(room) {
288
+ if (holds(reach, room) && runtime.locked(room))
289
+ throw lockedError(room);
222
290
  if (wholly(reach, room))
223
291
  return md.types(room);
224
292
  if (!holds(reach, room))
225
293
  return structuredClone(UNHELD_TYPES);
226
- const listed = md.rooms().find(({ locator }) => locator === room);
227
- return typesWithin(reach, listed, md.types(room));
294
+ return typesWithin(reach, runtime.room(room), md.types(room));
228
295
  },
229
296
  async save(room, roomDrafts, writtenBy) {
230
297
  if (!holds(reach, room))
231
298
  throw unreached(room);
232
- const outside = roomDrafts.find(({ path }) => !covers(reach, room, path) ||
233
- !moves(reach, room, md.roomDraft.read(room, path)?.parts.move));
299
+ if (runtime.locked(room))
300
+ return { outcome: 'refused', code: ROOM_LOCKED, retryable: true };
301
+ const held = (await Promise.all(roomDrafts.map(({ path }) => md.roomDraft.read(room, path)))).map((draft) => draft?.parts.move);
302
+ const outside = roomDrafts.find(({ path }, at) => !covers(reach, room, path) || !moves(reach, room, held[at]));
234
303
  if (outside !== undefined) {
235
304
  return { outcome: 'refused', code: 'stale', retryable: false, file: outside.path };
236
305
  }
306
+ if (intoLocked(held))
307
+ return { outcome: 'refused', code: ROOM_LOCKED, retryable: true };
237
308
  return savedWithin(reach, room, await md.save(room, roomDrafts, writtenBy));
238
309
  },
239
- history: (room, path, query) => covers(reach, room, path)
240
- ? md.history(room, path, query)
241
- : Promise.resolve(failure({ kind: 'room-not-readable', locator: room })),
310
+ async saveParts(room, parts, writtenBy) {
311
+ if (!holds(reach, room))
312
+ throw unreached(room);
313
+ const filed = reach === null ? undefined : parts.find(({ parts: one }) => carriesFile(one));
314
+ if (filed !== undefined) {
315
+ return { outcome: 'refused', code: ATTACH_REQUIRED, retryable: false, file: filed.path };
316
+ }
317
+ if (runtime.locked(room))
318
+ return { outcome: 'refused', code: ROOM_LOCKED, retryable: true };
319
+ const held = parts.map(({ parts: one }) => readParts(one).move);
320
+ if (parts.some(({ path }, at) => !covers(reach, room, path) || !moves(reach, room, held[at]))) {
321
+ throw unreached(room);
322
+ }
323
+ if (intoLocked(held))
324
+ return { outcome: 'refused', code: ROOM_LOCKED, retryable: true };
325
+ return savedWithin(reach, room, await md.saveParts(room, parts, writtenBy));
326
+ },
327
+ resolve(room, path, destination) {
328
+ const at = runtime.located(room, path, destination);
329
+ return at !== null && !covers(reach, at.room, at.path)
330
+ ? Promise.resolve(UNREADABLE)
331
+ : md.resolve(room, path, destination);
332
+ },
333
+ history: (room, path, query) => !covers(reach, room, path)
334
+ ? Promise.resolve(failure({ kind: 'room-not-readable', locator: room }))
335
+ : runtime.locked(room)
336
+ ? Promise.resolve(lockedRoom(room))
337
+ : md.history(room, path, query),
242
338
  binaryFile: {
243
- read: (room, path, revision) => covers(reach, room, path)
244
- ? md.binaryFile.read(room, path, revision)
245
- : Promise.resolve(failure({ kind: 'room-not-readable', locator: room })),
246
- readReference: (reference) => refers(reach, runtime.referrerScope(reference.referrer))
247
- ? md.binaryFile.readReference(reference)
248
- : Promise.resolve(unreachedReference(reference.fileRef)),
339
+ read: (room, path, revision) => !covers(reach, room, path)
340
+ ? Promise.resolve(failure({ kind: 'room-not-readable', locator: room }))
341
+ : runtime.locked(room)
342
+ ? Promise.resolve(lockedRoom(room))
343
+ : hand(md.binaryFile.read(room, path, revision)),
344
+ link: (room, path, revision) => !covers(reach, room, path)
345
+ ? Promise.resolve(failure({ kind: 'room-not-readable', locator: room }))
346
+ : runtime.locked(room)
347
+ ? Promise.resolve(lockedRoom(room))
348
+ : md.binaryFile.link(room, path, revision),
349
+ show: (room, path, revision) => !covers(reach, room, path)
350
+ ? Promise.resolve(failure({ kind: 'room-not-readable', locator: room }))
351
+ : runtime.locked(room)
352
+ ? Promise.resolve(lockedRoom(room))
353
+ : hand(md.binaryFile.show(room, path, revision)),
354
+ attach: (room, file) => !wholly(reach, room)
355
+ ? Promise.resolve(failure({ kind: 'room-not-readable', locator: room }))
356
+ : runtime.locked(room)
357
+ ? Promise.resolve(lockedRoom(room))
358
+ : md.binaryFile.attach(room, file),
359
+ async part(file, part) {
360
+ const key = files.get(file);
361
+ if (key === undefined)
362
+ return null;
363
+ const bytes = await md.binaryFile.part(key, part);
364
+ if (bytes === null)
365
+ files.delete(file);
366
+ return bytes;
367
+ },
249
368
  },
250
- reached: (room, roomVersion, path) => (path === undefined ? wholly(reach, room) : covers(reach, room, path)) &&
251
- md.reached(room, roomVersion, path),
369
+ reached: async (room, roomVersion, path) => (path === undefined ? wholly(reach, room) : covers(reach, room, path)) &&
370
+ !runtime.locked(room) &&
371
+ (await md.reached(room, roomVersion, path)),
252
372
  log: () => (reach === null ? md.log() : ''),
373
+ cleanUp: () => (reach === null ? md.cleanUp() : Promise.reject(deviceUnreached())),
253
374
  roomDraft: {
254
- list: () => md.roomDraft.list().then((drafts) => draftsWithin(reach, drafts)),
255
- read: (room, path) => covers(reach, room, path) ? shownDraft(reach, md.roomDraft.read(room, path)) : null,
256
- async write(room, path, revision, parts) {
257
- if (!covers(reach, room, path) || !moves(reach, room, readParts(parts).move)) {
258
- throw unreached(room);
375
+ list: () => md.roomDraft.list().then((drafts) => unlocked(draftsWithin(reach, drafts))),
376
+ async read(room, path) {
377
+ if (!covers(reach, room, path))
378
+ return null;
379
+ if (runtime.locked(room))
380
+ throw lockedError(room);
381
+ return shownDraft(reach, await md.roomDraft.read(room, path));
382
+ },
383
+ write(room, path, revision, parts, next) {
384
+ try {
385
+ if (reach !== null && carriesFile(parts))
386
+ throw attachRequired();
387
+ const move = readParts(parts).move;
388
+ if (!covers(reach, room, path) || !moves(reach, room, move))
389
+ throw unreached(room);
390
+ if (runtime.locked(room))
391
+ throw lockedError(room);
392
+ if (intoLocked([move]))
393
+ throw lockedError(move.room);
259
394
  }
260
- return writeWithin(reach, await md.roomDraft.write(room, path, revision, parts));
395
+ catch (error) {
396
+ return Promise.reject(error);
397
+ }
398
+ return md.roomDraft
399
+ .write(room, path, revision, parts, next)
400
+ .then((written) => writeWithin(reach, written));
261
401
  },
262
402
  async discard(room, path, revision) {
263
403
  if (!covers(reach, room, path))
264
404
  return staleWrite(null);
405
+ if (runtime.locked(room))
406
+ throw lockedError(room);
265
407
  return writeWithin(reach, await md.roomDraft.discard(room, path, revision));
266
408
  },
267
409
  },
@@ -273,11 +415,11 @@ export function narrowed(runtime, given) {
273
415
  answer: (id, answer) => conversation().answer(id, answer),
274
416
  interrupt: (id) => conversation().interrupt(id),
275
417
  kill: (id) => conversation().kill(id),
276
- cancel: (id, turn) => conversation().cancel(id, turn),
277
418
  delete: (id) => conversation().delete(id),
278
419
  },
279
420
  chatDraft: {
280
421
  write: (id, draft, revision) => chatDraft().write(id, draft, revision),
422
+ prepend: (id, draft) => chatDraft().prepend(id, draft),
281
423
  discard: (id) => chatDraft().discard(id),
282
424
  },
283
425
  observe: observing(within),
@@ -290,5 +432,10 @@ export function narrowed(runtime, given) {
290
432
  for (const restart of [...open])
291
433
  restart();
292
434
  },
435
+ facts(next) {
436
+ port = next;
437
+ for (const restart of [...open])
438
+ restart();
439
+ },
293
440
  };
294
441
  }