@agentshouse/mdruntime 0.1.0-cd50a4607cb7d → 0.1.0-cd9e28a590f84

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