@agentshouse/mdruntime 0.1.0-ca6edc1abd618 → 0.1.0-ca92b54804c33

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 (149) hide show
  1. package/README.md +2 -2
  2. package/dist/adapter.d.ts +101 -70
  3. package/dist/adapter.d.ts.map +1 -1
  4. package/dist/attached-model.d.ts +3 -10
  5. package/dist/attached-model.d.ts.map +1 -1
  6. package/dist/attached-model.js +29 -47
  7. package/dist/bootstrap.d.ts +16 -14
  8. package/dist/bootstrap.d.ts.map +1 -1
  9. package/dist/bootstrap.js +272 -129
  10. package/dist/chain.d.ts +2 -1
  11. package/dist/chain.d.ts.map +1 -1
  12. package/dist/change.d.ts +1 -0
  13. package/dist/change.d.ts.map +1 -1
  14. package/dist/client.d.ts +13 -9
  15. package/dist/client.d.ts.map +1 -1
  16. package/dist/client.js +54 -6
  17. package/dist/collection.d.ts +7 -4
  18. package/dist/collection.d.ts.map +1 -1
  19. package/dist/collection.js +23 -17
  20. package/dist/compile.d.ts +1 -6
  21. package/dist/compile.d.ts.map +1 -1
  22. package/dist/compile.js +108 -243
  23. package/dist/connection.d.ts +14 -11
  24. package/dist/connection.d.ts.map +1 -1
  25. package/dist/connection.js +2264 -1933
  26. package/dist/context.d.ts +2 -0
  27. package/dist/context.d.ts.map +1 -0
  28. package/dist/context.js +7 -0
  29. package/dist/conversation.d.ts +21 -17
  30. package/dist/conversation.d.ts.map +1 -1
  31. package/dist/conversation.js +405 -253
  32. package/dist/durability.d.ts +6 -1
  33. package/dist/durability.d.ts.map +1 -1
  34. package/dist/entry.d.ts +3 -2
  35. package/dist/entry.d.ts.map +1 -1
  36. package/dist/entry.js +18 -8
  37. package/dist/file.d.ts +2 -11
  38. package/dist/file.d.ts.map +1 -1
  39. package/dist/file.js +6 -4
  40. package/dist/host.d.ts +3 -7
  41. package/dist/host.d.ts.map +1 -1
  42. package/dist/host.js +26 -29
  43. package/dist/identity.d.ts +0 -1
  44. package/dist/identity.d.ts.map +1 -1
  45. package/dist/identity.js +0 -3
  46. package/dist/index.d.ts +12 -9
  47. package/dist/index.d.ts.map +1 -1
  48. package/dist/index.js +4 -1
  49. package/dist/job.d.ts +14 -0
  50. package/dist/job.d.ts.map +1 -0
  51. package/dist/job.js +28 -0
  52. package/dist/live.d.ts +5 -4
  53. package/dist/live.d.ts.map +1 -1
  54. package/dist/live.js +62 -20
  55. package/dist/media.d.ts +8 -0
  56. package/dist/media.d.ts.map +1 -0
  57. package/dist/media.js +218 -0
  58. package/dist/memory.d.ts +12 -5
  59. package/dist/memory.d.ts.map +1 -1
  60. package/dist/memory.js +216 -85
  61. package/dist/messages.d.ts +3 -3
  62. package/dist/messages.d.ts.map +1 -1
  63. package/dist/messages.js +20 -19
  64. package/dist/model.d.ts +8 -10
  65. package/dist/model.d.ts.map +1 -1
  66. package/dist/model.js +5 -17
  67. package/dist/observation.d.ts +3 -2
  68. package/dist/observation.d.ts.map +1 -1
  69. package/dist/observation.js +8 -3
  70. package/dist/outcome.d.ts +13 -5
  71. package/dist/outcome.d.ts.map +1 -1
  72. package/dist/outcome.js +16 -2
  73. package/dist/overlay.d.ts +1 -0
  74. package/dist/overlay.d.ts.map +1 -1
  75. package/dist/overlay.js +11 -0
  76. package/dist/paths.d.ts +1 -0
  77. package/dist/paths.d.ts.map +1 -1
  78. package/dist/paths.js +4 -0
  79. package/dist/query.d.ts +8 -207
  80. package/dist/query.d.ts.map +1 -1
  81. package/dist/query.js +3 -505
  82. package/dist/reach.d.ts +18 -11
  83. package/dist/reach.d.ts.map +1 -1
  84. package/dist/reach.js +195 -126
  85. package/dist/react.d.ts +3 -1
  86. package/dist/react.d.ts.map +1 -1
  87. package/dist/readiness.d.ts +3 -17
  88. package/dist/readiness.d.ts.map +1 -1
  89. package/dist/readiness.js +4 -10
  90. package/dist/recovery.d.ts +1 -0
  91. package/dist/recovery.d.ts.map +1 -1
  92. package/dist/recovery.js +35 -1
  93. package/dist/refresh.d.ts +7 -5
  94. package/dist/refresh.d.ts.map +1 -1
  95. package/dist/refresh.js +33 -5
  96. package/dist/replica.d.ts +10 -8
  97. package/dist/replica.d.ts.map +1 -1
  98. package/dist/replica.js +52 -20
  99. package/dist/room-draft.d.ts +21 -5
  100. package/dist/room-draft.d.ts.map +1 -1
  101. package/dist/room-draft.js +98 -51
  102. package/dist/rows.d.ts +18 -16
  103. package/dist/rows.d.ts.map +1 -1
  104. package/dist/rows.js +45 -153
  105. package/dist/save.d.ts +2 -0
  106. package/dist/save.d.ts.map +1 -1
  107. package/dist/save.js +22 -13
  108. package/dist/search.d.ts +12 -63
  109. package/dist/search.d.ts.map +1 -1
  110. package/dist/search.js +9 -52
  111. package/dist/shared.d.ts +0 -1
  112. package/dist/shared.d.ts.map +1 -1
  113. package/dist/shared.js +0 -10
  114. package/dist/status.d.ts +4 -5
  115. package/dist/status.d.ts.map +1 -1
  116. package/dist/status.js +3 -7
  117. package/dist/store.d.ts +101 -61
  118. package/dist/store.d.ts.map +1 -1
  119. package/dist/store.js +153 -161
  120. package/dist/stored.d.ts +66 -55
  121. package/dist/stored.d.ts.map +1 -1
  122. package/dist/stored.js +341 -273
  123. package/dist/tables.d.ts +90 -63
  124. package/dist/tables.d.ts.map +1 -1
  125. package/dist/tables.js +174 -98
  126. package/dist/transfer.d.ts +0 -5
  127. package/dist/transfer.d.ts.map +1 -1
  128. package/dist/validation.d.ts +3 -4
  129. package/dist/validation.d.ts.map +1 -1
  130. package/dist/validation.js +42 -100
  131. package/dist/windows.d.ts +1 -12
  132. package/dist/windows.d.ts.map +1 -1
  133. package/dist/windows.js +2 -75
  134. package/dist/wire.d.ts +17 -8
  135. package/dist/wire.d.ts.map +1 -1
  136. package/dist/wire.js +5 -2
  137. package/package.json +2 -2
  138. package/dist/fence.d.ts +0 -6
  139. package/dist/fence.d.ts.map +0 -1
  140. package/dist/fence.js +0 -10
  141. package/dist/fulltext.d.ts +0 -27
  142. package/dist/fulltext.d.ts.map +0 -1
  143. package/dist/fulltext.js +0 -368
  144. package/dist/reads.d.ts +0 -11
  145. package/dist/reads.d.ts.map +0 -1
  146. package/dist/reads.js +0 -100
  147. package/dist/words.d.ts +0 -19
  148. package/dist/words.d.ts.map +0 -1
  149. package/dist/words.js +0 -150
package/dist/compile.js CHANGED
@@ -1,107 +1,36 @@
1
- import { ARRAY_INDEX, parse, pointerSegments } from '@agentshouse/mdmodel';
1
+ import { ARRAY_INDEX, bodied, byCodePoint, compared, cursorOf, heldKind, parse, pointerSegments, sql, } from '@agentshouse/mdmodel';
2
2
  import { binaryRecord } from './binary.js';
3
- import { documentKey, kindOf, pathOfKey, roomOfKey, settingsTextPath } from './rows.js';
4
- import { byCodePoint, compared, cursorOf, FROM_START, MAXIMUM_WINDOW, } from './windows.js';
3
+ import { documentKey, pathOfKey, roomOfKey } from './rows.js';
4
+ import { FROM_START, MAXIMUM_WINDOW } from './windows.js';
5
+ const { all, any, exists, fragment, grouped, joined, listed } = sql;
5
6
  const VISIBLE = (alias) => `${alias}.visible = 1 AND ${alias}.present = 1 AND ${alias}.queried = 1`;
6
- function fragment(sql, bind = []) {
7
- return { sql, bind };
7
+ function located(alias) {
8
+ return fragment(`(SELECT k.locator FROM replica_keys k WHERE k.key = ${alias}.key)`);
8
9
  }
9
- function joined(parts, separator) {
10
- return {
11
- sql: parts.map((part) => part.sql).join(separator),
12
- bind: parts.flatMap((part) => part.bind),
13
- };
14
- }
15
- function all(parts) {
16
- return parts.length === 0 ? fragment('1') : joined(parts.map(grouped), ' AND ');
17
- }
18
- function any(parts) {
19
- return parts.length === 0 ? fragment('0') : joined(parts.map(grouped), ' OR ');
20
- }
21
- function grouped(part) {
22
- return { sql: `(${part.sql})`, bind: part.bind };
23
- }
24
- function list(values) {
25
- return fragment(`(${values.map(() => '?').join(', ')})`, values);
26
- }
27
- function sqlValue(value) {
28
- if (value === null)
29
- return { kind: 'null', value: null };
30
- if (typeof value === 'boolean')
31
- return { kind: 'boolean', value: value ? 1 : 0 };
32
- if (typeof value === 'number')
33
- return { kind: 'number', value };
34
- return { kind: 'string', value };
35
- }
36
- function same(alias, value) {
37
- const held = sqlValue(value);
38
- return held.kind === 'null'
39
- ? fragment(`${alias}.kind = 'null'`)
40
- : fragment(`${alias}.kind = ? AND ${alias}.value = ?`, [held.kind, held.value]);
41
- }
42
- const BOUNDS = { gt: '>', gte: '>=', lt: '<', lte: '<=' };
43
- function heldValues(doc, field, alias, where) {
44
- return fragment(`SELECT 1 FROM replica_values ${alias} WHERE ${alias}.doc = ${doc}.id AND ${alias}.form = ? AND (${where.sql})`, [field, ...where.bind]);
45
- }
46
- function exists(part) {
47
- return { sql: `EXISTS (${part.sql})`, bind: part.bind };
48
- }
49
- function matching(held, doc, field, values) {
50
- return any(values.map((value) => any([
51
- same(held, value),
52
- all([
53
- fragment(`${held}.kind = 'array'`),
54
- exists(heldValues(doc, field, 'i', all([fragment(`i.held = ${held}.held AND i.item >= 0`), same('i', value)]))),
55
- ]),
56
- ])));
57
- }
58
- function tested(doc, field, test) {
59
- const reached = exists(heldValues(doc, field, 'r', fragment('r.item = -1')));
60
- if (test.op === 'exists')
61
- return test.exists ? reached : fragment(`NOT ${reached.sql}`, reached.bind);
62
- if (test.op === 'eq' || test.op === 'in') {
63
- const values = test.op === 'eq' ? [test.value] : test.values;
64
- return exists(heldValues(doc, field, 'v', any(values.map((value) => same('v', value)))));
65
- }
66
- if (test.op === 'ne' || test.op === 'nin') {
67
- const values = test.op === 'ne' ? [test.value] : test.values;
68
- const unmatched = matching('h', doc, field, values);
69
- return any([
70
- fragment(`NOT ${reached.sql}`, reached.bind),
71
- exists(heldValues(doc, field, 'h', all([fragment('h.item = -1'), fragment(`NOT (${unmatched.sql})`, unmatched.bind)]))),
72
- ]);
73
- }
74
- if (test.op === 'range') {
75
- return exists(heldValues(doc, field, 'v', all([
76
- fragment('v.item = -1'),
77
- ...test.bounds.map(([operator, bound]) => {
78
- const held = sqlValue(bound);
79
- return fragment(`v.kind = ? AND v.value ${BOUNDS[operator]} ?`, [
80
- held.kind,
81
- held.value,
82
- ]);
83
- }),
84
- ])));
85
- }
86
- if (test.op === 'all') {
87
- return exists(heldValues(doc, field, 'h', all([
88
- fragment("h.item = -1 AND h.kind = 'array'"),
89
- ...test.values.map((value) => exists(heldValues(doc, field, 'i', all([fragment('i.held = h.held AND i.item >= 0'), same('i', value)])))),
90
- ])));
91
- }
92
- return exists(heldValues(doc, field, 'v', fragment("v.item = -1 AND v.kind = 'array' AND v.size = ?", [test.size])));
10
+ function within(alias, path) {
11
+ return fragment(`(${alias}.path = ? OR (${alias}.path >= ? AND ${alias}.path < ?))`, [
12
+ path,
13
+ `${path}/`,
14
+ `${path}0`,
15
+ ]);
93
16
  }
94
- function conditioned(doc, condition) {
95
- if ('every' in condition)
96
- return all(condition.every.map((one) => conditioned(doc, one)));
97
- if ('some' in condition)
98
- return any(condition.some.map((one) => conditioned(doc, one)));
99
- return tested(doc, condition.field, condition.test);
17
+ function ancestors(area) {
18
+ const depth = area.path.split('/').length;
19
+ return area.open.flatMap((open) => {
20
+ const segments = open.split('/');
21
+ return segments.slice(depth - 1, -1).map((_, at) => segments.slice(0, depth + at).join('/'));
22
+ });
100
23
  }
101
- function located(bound, alias) {
102
- if (bound.rooms.length === 0)
103
- return fragment("''");
104
- return fragment(`CASE ${alias}.key ${bound.rooms.map(() => 'WHEN ? THEN ?').join(' ')} END`, bound.rooms.flatMap(({ key, locator }) => [key, locator]));
24
+ function fenced(alias, key, area, folders) {
25
+ const opened = any([
26
+ ...area.open.map((open) => within(alias, open)),
27
+ ...(folders ? ancestors(area).map((path) => fragment(`${alias}.path = ?`, [path])) : []),
28
+ ]);
29
+ return all([
30
+ fragment(`${alias}.key = ?`, [key]),
31
+ within(alias, area.path),
32
+ fragment(`NOT ${grouped(opened).sql}`, opened.bind),
33
+ ]);
105
34
  }
106
35
  function reached(bound, alias) {
107
36
  const reach = bound.reach;
@@ -115,44 +44,38 @@ function reached(bound, alias) {
115
44
  const key = bound.keys.get(roomOfKey(held));
116
45
  return key === undefined ? [] : [[key, pathOfKey(held)]];
117
46
  });
118
- return any([
119
- ...(rooms.length === 0 ? [] : [fragment(`${alias}.key IN ${list(rooms).sql}`, rooms)]),
120
- ...(documents.length === 0
121
- ? []
122
- : [
123
- fragment(`EXISTS (SELECT 1 FROM json_each(?) j WHERE j.value ->> 0 = ${alias}.key AND j.value ->> 1 = ${alias}.path)`, [JSON.stringify(documents)]),
124
- ]),
125
- ]);
126
- }
127
- function scoped(alias, scope) {
47
+ const outside = any(reach.areas.flatMap((area) => {
48
+ const key = bound.keys.get(area.room);
49
+ return key === undefined ? [] : [fenced(alias, key, area, false)];
50
+ }));
128
51
  return all([
129
- ...(scope.types === null
130
- ? []
131
- : [fragment(`${alias}.type IN ${list(scope.types).sql}`, scope.types)]),
132
- ...(scope.pathPrefix === null || scope.pathPrefix === ''
133
- ? []
134
- : [
135
- fragment(`${alias}.path >= ? AND ${alias}.path < ?`, [
136
- `${scope.pathPrefix}/`,
137
- `${scope.pathPrefix}0`,
52
+ any([
53
+ ...(rooms.length === 0 ? [] : [fragment(`${alias}.key IN ${listed(rooms).sql}`, rooms)]),
54
+ ...(documents.length === 0
55
+ ? []
56
+ : [
57
+ fragment(`EXISTS (SELECT 1 FROM json_each(?) j WHERE j.value ->> 0 = ${alias}.key AND j.value ->> 1 = ${alias}.path)`, [JSON.stringify(documents)]),
138
58
  ]),
139
- ]),
140
- ...(scope.where === null ? [] : [conditioned(alias, scope.where)]),
59
+ ]),
60
+ ...(reach.areas.length === 0 ? [] : [fragment(`NOT ${grouped(outside).sql}`, outside.bind)]),
141
61
  ]);
142
62
  }
143
63
  function related(bound, plan) {
144
64
  const parts = [];
145
65
  if (plan.hop !== null) {
146
66
  parts.push(exists(joined([
147
- fragment(`SELECT 1 FROM replica_links l JOIN replica_entries m ON m.key = d.key AND m.path = l.target AND ${VISIBLE('m')} WHERE l.doc = d.id AND l.kind = 'field' AND l.form = ? AND`, [plan.hop.field]),
148
- grouped(all([scoped('m', plan.hop), reached(bound, 'm')])),
67
+ fragment(`SELECT 1 FROM replica_links l JOIN replica_entries m ON m.key = d.key AND m.path = l.target AND ${VISIBLE('m')} WHERE l.doc = d.id AND l.kind = 'field' AND`),
68
+ grouped(sql.placed('l', plan.hop.field, 'link')),
69
+ fragment('AND'),
70
+ grouped(all([sql.scoped('m', plan.hop), reached(bound, 'm')])),
149
71
  ], ' ')));
150
72
  }
151
73
  if (plan.backlinks !== null) {
152
74
  const field = plan.backlinks.field;
153
75
  parts.push(exists(joined([
154
- fragment(`SELECT 1 FROM replica_links l JOIN replica_entries s ON s.id = l.doc AND ${VISIBLE('s')} WHERE l.key = d.key AND l.target = d.path AND l.kind = 'field'${field === null ? '' : ' AND l.form = ?'} AND`, field === null ? [] : [field]),
155
- grouped(all([scoped('s', plan.backlinks), reached(bound, 's')])),
76
+ fragment(`SELECT 1 FROM replica_links l JOIN replica_entries s ON s.id = l.doc AND ${VISIBLE('s')} WHERE l.key = d.key AND l.target = d.path AND l.kind = 'field' AND`),
77
+ ...(field === null ? [] : [grouped(sql.placed('l', field, 'link')), fragment('AND')]),
78
+ grouped(all([sql.scoped('s', plan.backlinks), reached(bound, 's')])),
156
79
  ], ' ')));
157
80
  }
158
81
  return parts;
@@ -160,11 +83,9 @@ function related(bound, plan) {
160
83
  function filtered(bound, plan) {
161
84
  const keys = bound.rooms.map(({ key }) => key);
162
85
  return all([
163
- fragment(`d.key IN ${list(keys).sql} AND ${VISIBLE('d')}`, keys),
164
- ...(plan.paths === null ? [] : [fragment(`d.path IN ${list(plan.paths).sql}`, plan.paths)]),
165
- ...(plan.folder === null ? [] : [fragment('d.folder = ?', [plan.folder])]),
86
+ fragment(`d.key IN ${listed(keys).sql} AND ${VISIBLE('d')}`, keys),
166
87
  reached(bound, 'd'),
167
- scoped('d', plan.root),
88
+ sql.tests('d', plan),
168
89
  ...related(bound, plan),
169
90
  ]);
170
91
  }
@@ -195,99 +116,25 @@ const GROUP_TIES = {
195
116
  locale: undefined,
196
117
  localeOptions: undefined,
197
118
  };
198
- function classOf(value, ordering) {
199
- if (value === null || value === undefined) {
200
- return { placement: ordering.nulls === 'first' ? 0 : 2, klass: 0, scalar: null };
201
- }
202
- if (typeof value === 'boolean')
203
- return { placement: 1, klass: 1, scalar: value ? 1 : 0 };
204
- if (typeof value === 'number')
205
- return { placement: 1, klass: 1, scalar: value };
206
- if (typeof value === 'string')
207
- return { placement: 1, klass: 2, scalar: value };
208
- return { placement: 1, klass: 3, scalar: JSON.stringify(value) };
209
- }
210
- function placement(term) {
211
- const nulls = term.ordering.nulls === 'first' ? 0 : 2;
212
- return fragment(`CASE WHEN ${term.kind.sql} IS NULL OR ${term.kind.sql} = 'null' THEN ${nulls} ELSE 1 END`, [...term.kind.bind, ...term.kind.bind]);
213
- }
214
- function klass(term) {
215
- return fragment(`CASE ${term.kind.sql} WHEN 'boolean' THEN 1 WHEN 'number' THEN 1 WHEN 'string' THEN 2 WHEN 'array' THEN 3 ELSE 4 END`, term.kind.bind);
216
- }
217
- function classed(term, which) {
218
- const kinds = which === 1 ? "('boolean', 'number')" : which === 2 ? "('string')" : "('array')";
219
- return fragment(`(CASE WHEN ${term.kind.sql} IN ${kinds} THEN ${term.value.sql} END)`, [
220
- ...term.kind.bind,
221
- ...term.value.bind,
222
- ]);
223
- }
224
- function collated(bound, term, which) {
225
- if (which === 1)
226
- return '';
227
- const name = bound.collate(term.ordering, which === 3, term.total === true);
228
- return name === null ? '' : ` COLLATE ${name}`;
229
- }
230
- function sorted(bound, term) {
231
- const direction = term.ordering.direction === 'desc' ? 'DESC' : 'ASC';
232
- return joined([
233
- placement(term),
234
- fragment(`${klass(term).sql} ${direction}`, klass(term).bind),
235
- ...[1, 2, 3].map((which) => {
236
- const value = classed(term, which);
237
- return fragment(`${value.sql}${collated(bound, term, which)} ${direction}`, value.bind);
238
- }),
239
- ], ', ');
240
- }
241
- function beyond(bound, term, value) {
242
- const held = classOf(value, term.ordering);
243
- const place = placement(term);
244
- if (held.placement !== 1) {
245
- return {
246
- beyond: fragment(`${place.sql} > ${held.placement}`, place.bind),
247
- equal: fragment(`${place.sql} = ${held.placement}`, place.bind),
248
- };
249
- }
250
- const after = term.ordering.direction === 'desc' ? '<' : '>';
251
- const kind = klass(term);
252
- const scalar = classed(term, held.klass);
253
- const collation = collated(bound, term, held.klass);
254
- return {
255
- beyond: any([
256
- fragment(`${place.sql} > 1`, place.bind),
257
- all([
258
- fragment(`${place.sql} = 1`, place.bind),
259
- any([
260
- fragment(`${kind.sql} ${after} ${held.klass}`, kind.bind),
261
- all([
262
- fragment(`${kind.sql} = ${held.klass}`, kind.bind),
263
- fragment(`${scalar.sql} ${after} ?${collation}`, [...scalar.bind, held.scalar]),
264
- ]),
265
- ]),
266
- ]),
267
- ]),
268
- equal: all([
269
- fragment(`${place.sql} = 1 AND ${kind.sql} = ${held.klass}`, [...place.bind, ...kind.bind]),
270
- fragment(`${scalar.sql} = ?${collation}`, [...scalar.bind, held.scalar]),
271
- ]),
272
- };
273
- }
274
- function following(parts, tie) {
275
- let condition = tie;
276
- for (const part of [...parts].reverse()) {
277
- condition = any([part.beyond, all([part.equal, condition])]);
278
- }
279
- return condition;
280
- }
281
- function keyTerm(bound, key, ordering, index) {
119
+ function keyTerm(key, ordering, index) {
282
120
  if (key.of === 'field') {
283
121
  const alias = `o${index}`;
284
122
  return {
285
- term: { ordering, kind: fragment(`${alias}.kind`), value: fragment(`${alias}.value`) },
286
- join: fragment(`LEFT JOIN replica_values ${alias} ON ${alias}.doc = d.id AND ${alias}.form = ? AND ${alias}.single = 1 AND ${alias}.item = -1`, [key.field]),
123
+ term: {
124
+ ordering,
125
+ kind: fragment(`${alias}.kind`),
126
+ value: fragment(`${alias}.value`),
127
+ instant: fragment(`${alias}.instant`),
128
+ },
129
+ join: joined([
130
+ fragment(`LEFT JOIN replica_values ${alias} ON ${alias}.doc = d.id AND `),
131
+ grouped(sql.placed(alias, key.field, 'exact')),
132
+ fragment(` AND ${alias}.item = -1 AND ${alias}.kind NOT IN ('object', 'undefined')`),
133
+ ], ''),
287
134
  };
288
135
  }
289
136
  const expression = key.of === 'room'
290
- ? located(bound, 'd')
137
+ ? located('d')
291
138
  : key.of === 'path'
292
139
  ? fragment('d.path')
293
140
  : key.of === 'type'
@@ -313,13 +160,20 @@ function decoded(kind, value) {
313
160
  return JSON.parse(value);
314
161
  return null;
315
162
  }
163
+ function sortedValue(row, index) {
164
+ const kind = row[`k${index}`];
165
+ return sql.sortValue(kind, decoded(kind, row[`v${index}`]), row[`i${index}`]);
166
+ }
316
167
  function decodedKey(key, row, index) {
317
168
  if (key.of === 'field') {
318
169
  const kind = row[`k${index}`];
319
- return kind === null || kind === undefined ? undefined : decoded(kind, row[`v${index}`]);
170
+ return kind === null || kind === undefined ? undefined : sortedValue(row, index);
320
171
  }
321
172
  return row[`v${index}`] ?? null;
322
173
  }
174
+ function instantOf(term) {
175
+ return term.instant ?? fragment('NULL');
176
+ }
323
177
  const registered = new WeakMap();
324
178
  function hashed(text) {
325
179
  let hash = 0x811c9dc5;
@@ -369,9 +223,6 @@ function collating(connection) {
369
223
  return name;
370
224
  };
371
225
  }
372
- function bodied(shape) {
373
- return shape.sections !== null || shape.preamble || shape.markdown;
374
- }
375
226
  function masked(pointers) {
376
227
  const root = { whole: false, members: new Map() };
377
228
  for (const pointer of pointers) {
@@ -439,9 +290,7 @@ function documentOf(shape, room, row, linked) {
439
290
  : {
440
291
  binaryFile: binaryRecord(path, binary, revision ?? undefined),
441
292
  }),
442
- ...(shape.markdown && content !== null && settingsTextPath(path)
443
- ? { markdown: content }
444
- : {}),
293
+ ...(shape.markdown && content !== null ? { markdown: content } : {}),
445
294
  ...(shape.fileId ? { fileId } : {}),
446
295
  ...(shape.revision ? { revision } : {}),
447
296
  };
@@ -464,7 +313,7 @@ function documentOf(shape, room, row, linked) {
464
313
  ? {}
465
314
  : { sections: sectionsOf(body.sections, shape.sections) }),
466
315
  ...(shape.preamble && body !== null ? { preamble: body.preamble } : {}),
467
- ...(shape.markdown && body !== null ? { markdown: body.content } : {}),
316
+ ...(shape.markdown ? { markdown: row.content } : {}),
468
317
  ...(shape.backlinks ? { backlinks: linked.get(documentKey(room, path)) ?? [] } : {}),
469
318
  ...(shape.change
470
319
  ? {
@@ -498,10 +347,10 @@ function backlinksOf(connection, bound, rows) {
498
347
  return linked;
499
348
  }
500
349
  function documents(connection, bound, plan, shape, budget) {
501
- const terms = shape.orderBy.map(({ key, options }, index) => keyTerm(bound, key, orderingOf(options), index));
350
+ const terms = shape.orderBy.map(({ key, options }, index) => keyTerm(key, orderingOf(options), index));
502
351
  const filter = filtered(bound, plan);
503
352
  const cursor = plan.after?.values ?? null;
504
- const room = located(bound, 'd');
353
+ const room = located('d');
505
354
  const tie = cursor === null
506
355
  ? null
507
356
  : (() => {
@@ -517,19 +366,20 @@ function documents(connection, bound, plan, shape, budget) {
517
366
  })();
518
367
  const after = cursor === null || tie === null
519
368
  ? null
520
- : following(terms.map(({ term }, index) => beyond(bound, term, cursor[index])), tie);
369
+ : sql.following(terms.map(({ term }, index) => sql.beyond(bound.collate, term, cursor[index])), tie);
521
370
  const selected = joined([
522
371
  fragment('SELECT d.id, d.key, d.path, d.type, d.binary, d.revision, d.fields, d.change, d.file_id'),
523
372
  ...terms.map(({ term }, index) => joined([
524
373
  fragment(`, ${term.kind.sql} AS k${index}`, term.kind.bind),
525
374
  fragment(`, ${term.value.sql} AS v${index}`, term.value.bind),
375
+ fragment(`, ${instantOf(term).sql} AS i${index}`, instantOf(term).bind),
526
376
  ], '')),
527
377
  fragment(' FROM replica_entries d'),
528
378
  ...terms.flatMap(({ join }) => (join === null ? [] : [fragment(' '), join])),
529
379
  fragment(' WHERE '),
530
380
  after === null ? filter : all([filter, after]),
531
381
  fragment(' ORDER BY '),
532
- joined([...terms.map(({ term }) => sorted(bound, term)), room, fragment('d.path')], ', '),
382
+ joined([...terms.map(({ term }) => sql.sorted(bound.collate, term)), room, fragment('d.path')], ', '),
533
383
  ...(plan.limit === null ? [] : [fragment(' LIMIT ?', [plan.limit + 1])]),
534
384
  ], '');
535
385
  const rows = connection.rows(selected.sql, selected.bind);
@@ -568,7 +418,7 @@ function withContent(connection, rows, budget) {
568
418
  return rows;
569
419
  const ids = rows.map((row) => row.id);
570
420
  const sizes = new Map(connection
571
- .rows(`SELECT id, octet_length(content) AS size FROM replica_entries WHERE id IN ${list(ids).sql}`, ids)
421
+ .rows(`SELECT id, octet_length(content) AS size FROM replica_entries WHERE id IN ${listed(ids).sql}`, ids)
572
422
  .map((row) => [row.id, Number(row.size ?? 0)]));
573
423
  let cut = rows.length;
574
424
  let spent = 0;
@@ -583,20 +433,30 @@ function withContent(connection, rows, budget) {
583
433
  const kept = rows.slice(0, cut);
584
434
  const read = kept.map((row) => row.id);
585
435
  const held = new Map(connection
586
- .rows(`SELECT id, content FROM replica_entries WHERE id IN ${list(read).sql}`, read)
436
+ .rows(`SELECT id, content FROM replica_entries WHERE id IN ${listed(read).sql}`, read)
587
437
  .map((row) => [row.id, row.content]));
588
438
  return kept.map((row) => ({ ...row, content: held.get(row.id) ?? null }));
589
439
  }
590
440
  function storedFolders(bound, plan) {
591
- if (plan.paths !== null || plan.hop !== null || plan.backlinks !== null)
441
+ if (plan.paths !== null || plan.fileIds !== null)
442
+ return null;
443
+ if (plan.hop !== null || plan.backlinks !== null)
592
444
  return null;
593
445
  const whole = bound.rooms.filter(({ locator }) => bound.reach === null || bound.reach.rooms.includes(locator));
594
446
  if (whole.length === 0)
595
447
  return null;
596
448
  const keys = whole.map(({ key }) => key);
597
449
  const prefix = plan.root.pathPrefix;
450
+ const outside = (bound.reach?.areas ?? []).flatMap((area) => {
451
+ const key = bound.keys.get(area.room);
452
+ if (key === undefined)
453
+ return [];
454
+ const fence = fenced('f', key, area, true);
455
+ return [fragment(`NOT ${grouped(fence).sql}`, fence.bind)];
456
+ });
598
457
  return all([
599
- fragment(`f.key IN ${list(keys).sql} AND f.layer = 0 AND f.present = 1`, keys),
458
+ fragment(`f.key IN ${listed(keys).sql} AND f.layer = 0 AND f.present = 1`, keys),
459
+ ...outside,
600
460
  ...(prefix === null || prefix === ''
601
461
  ? []
602
462
  : [
@@ -611,7 +471,7 @@ function storedFolders(bound, plan) {
611
471
  }
612
472
  function groups(connection, bound, plan, shape) {
613
473
  const keyed = shape.groupBy.map((key, index) => {
614
- const { term, join } = keyTerm(bound, key, GROUP_ORDER, index);
474
+ const { term, join } = keyTerm(key, GROUP_ORDER, index);
615
475
  const kind = fragment(`COALESCE(${term.kind.sql}, 'null')`, term.kind.bind);
616
476
  return { term: { ...term, kind }, join };
617
477
  });
@@ -621,6 +481,7 @@ function groups(connection, bound, plan, shape) {
621
481
  joined(keyed.map(({ term }, index) => joined([
622
482
  fragment(`${term.kind.sql} AS k${index}`, term.kind.bind),
623
483
  fragment(`, ${term.value.sql} AS v${index}`, term.value.bind),
484
+ fragment(`, ${instantOf(term).sql} AS i${index}`, instantOf(term).bind),
624
485
  ], '')), ', '),
625
486
  fragment(', 1 AS n FROM replica_entries d'),
626
487
  ...keyed.flatMap(({ join }) => (join === null ? [] : [fragment(' '), join])),
@@ -634,10 +495,15 @@ function groups(connection, bound, plan, shape) {
634
495
  ? inner
635
496
  : joined([
636
497
  inner,
637
- fragment(" UNION ALL SELECT 'string' AS k0, f.path AS v0, 0 AS n FROM replica_folders f WHERE "),
498
+ fragment(" UNION ALL SELECT 'string' AS k0, f.path AS v0, NULL AS i0, 0 AS n FROM replica_folders f WHERE "),
638
499
  folders,
639
500
  ], '');
640
- const columns = keyed.map((_, index) => `k${index}, v${index}`).join(', ');
501
+ const columns = keyed
502
+ .map((_, index) => `k${index}, min(v${index}) AS v${index}, i${index}`)
503
+ .join(', ');
504
+ const held = keyed
505
+ .map((_, index) => `k${index}, i${index}, CASE WHEN i${index} IS NULL THEN v${index} END`)
506
+ .join(', ');
641
507
  const counted = {
642
508
  ordering: { ...GROUP_ORDER },
643
509
  kind: fragment("'number'"),
@@ -650,7 +516,7 @@ function groups(connection, bound, plan, shape) {
650
516
  });
651
517
  const ordered = shape.orderBy.map(({ by, direction }) => by === 'count'
652
518
  ? { ...counted, ordering: { ...GROUP_ORDER, direction } }
653
- : outer(by, { ...GROUP_ORDER, direction }));
519
+ : { ...outer(by, { ...GROUP_ORDER, direction }), instant: fragment(`i${by}`) });
654
520
  const ties = keyed.flatMap((_, index) => [
655
521
  { ...outer(index, GROUP_TIES), total: true },
656
522
  { ordering: GROUP_TIES, kind: fragment("'string'"), value: fragment(`k${index}`) },
@@ -660,19 +526,21 @@ function groups(connection, bound, plan, shape) {
660
526
  ? null
661
527
  : cursor[shape.orderBy.length].flatMap((value) => [
662
528
  value,
663
- kindOf(value ?? null),
529
+ heldKind(value ?? null),
664
530
  ]);
665
531
  const after = cursor === null || tieValues === null
666
532
  ? null
667
- : following(ordered.map((term, index) => beyond(bound, term, cursor[index])), following(ties.slice(0, -1).map((term, index) => beyond(bound, term, tieValues[index])), beyond(bound, ties.at(-1), tieValues.at(-1)).beyond));
668
- const grouping = joined([fragment(`SELECT ${columns}, sum(n) AS n FROM (`), source, fragment(`) GROUP BY ${columns}`)], '');
533
+ : sql.following(ordered.map((term, index) => sql.beyond(bound.collate, term, cursor[index])), sql.following(ties
534
+ .slice(0, -1)
535
+ .map((term, index) => sql.beyond(bound.collate, term, tieValues[index])), sql.beyond(bound.collate, ties.at(-1), tieValues.at(-1)).beyond));
536
+ const grouping = joined([fragment(`SELECT ${columns}, sum(n) AS n FROM (`), source, fragment(`) GROUP BY ${held}`)], '');
669
537
  const windowed = joined([
670
538
  fragment('SELECT * FROM ('),
671
539
  grouping,
672
540
  fragment(')'),
673
541
  ...(after === null ? [] : [fragment(' WHERE '), after]),
674
542
  fragment(' ORDER BY '),
675
- joined([...ordered, ...ties].map((term) => sorted(bound, term)), ', '),
543
+ joined([...ordered, ...ties].map((term) => sql.sorted(bound.collate, term)), ', '),
676
544
  ...(plan.limit === null ? [] : [fragment(' LIMIT ?', [plan.limit + 1])]),
677
545
  ], '');
678
546
  const rows = connection.rows(windowed.sql, windowed.bind);
@@ -685,7 +553,7 @@ function groups(connection, bound, plan, shape) {
685
553
  : last === undefined
686
554
  ? (plan.after?.text ?? FROM_START)
687
555
  : cursorOf([
688
- ...shape.orderBy.map(({ by }) => by === 'count' ? Number(last.n) : decoded(last[`k${by}`], last[`v${by}`])),
556
+ ...shape.orderBy.map(({ by }) => by === 'count' ? Number(last.n) : sortedValue(last, by)),
689
557
  keyOf(last),
690
558
  ]);
691
559
  const total = plan.total
@@ -705,9 +573,6 @@ function boundOf(connection, plan, rooms) {
705
573
  collate: collating(connection),
706
574
  };
707
575
  }
708
- export function selected(connection, plan, rooms) {
709
- return filtered(boundOf(connection, plan, rooms), plan);
710
- }
711
576
  export function answerQuery(connection, plan, rooms, budget = null) {
712
577
  const bound = boundOf(connection, plan, rooms);
713
578
  if (rooms.length === 0) {
@@ -1,19 +1,20 @@
1
+ import { type DocumentQuery, type GroupQuery } from '@agentshouse/mdmodel';
1
2
  import { type TypesResult } from './model.js';
2
3
  import { type Observation } from './observation.js';
3
- import { type DocumentQuery, type DocumentResult, type GroupQuery, type GroupResult, type PendingResult } from './query.js';
4
+ import { type DocumentResult, type GroupResult, type PendingResult } from './query.js';
4
5
  import { type EntrySeed } from './seed.js';
5
6
  import { type SearchRequest, type SearchResult } from './search.js';
6
7
  import { type Adapter, type Authorization, type Member, type ProviderRefusal, type ServiceMode } from './adapter.js';
7
8
  import { type ChatDraftSurface, type ConversationAgent, type ConversationEntry, type ConversationOptions, type ConversationSurface, type ConversationView } from './conversation.js';
8
9
  import { type WrittenBy } from './change.js';
9
10
  import type { Open } from './open.js';
10
- import { type BinaryLinkAnswer, type CallResult, type HeldBinaryAnswer, type HistoryAnswer, type QueueEntry, type Resolution } from './outcome.js';
11
+ import { type AttachAnswer, type BinaryLinkAnswer, type CallResult, type HeldBinaryAnswer, type HeldShownAnswer, type HistoryAnswer, type QueueEntry, type Resolution } from './outcome.js';
11
12
  import { type Recovery } from './recovery.js';
12
13
  import { type SaveRefusal } from './save.js';
13
14
  import { type RoomDraft, type RoomDraftParts, type RoomDraftWrite } from './room-draft.js';
14
15
  import { type SyncStatus } from './status.js';
15
- import { type RuntimeShell } from './durability.js';
16
- import { type CodeAvailability, type Persistence, type Readiness, type SearchObligation, type Space } from './readiness.js';
16
+ import { type RuntimeShell, type UnsentWork } from './durability.js';
17
+ import { type CodeAvailability, type Persistence, type Readiness } from './readiness.js';
17
18
  import type { RuntimeDatabase } from './tables.js';
18
19
  import { type NarrowedView, type PortReach, type RuntimeView } from './reach.js';
19
20
  export type AppTypedSelection = {
@@ -54,6 +55,7 @@ export type MdRuntimeOptions = {
54
55
  authorization?: Authorization;
55
56
  signal?: AbortSignal;
56
57
  recovery?: Recovery;
58
+ usableWhenHeld?: readonly string[];
57
59
  };
58
60
  export type SaveResult = {
59
61
  outcome: 'saved';
@@ -83,6 +85,8 @@ export type PresentationSurface = {
83
85
  export type BinaryFileSurface = {
84
86
  read(room: string, path: string, revision?: string): Promise<HeldBinaryAnswer>;
85
87
  link(room: string, path: string, revision?: string): Promise<BinaryLinkAnswer>;
88
+ show(room: string, path: string, revision?: string): Promise<HeldShownAnswer>;
89
+ attach(room: string, file: File): Promise<AttachAnswer>;
86
90
  part(file: string, part: number): Promise<Uint8Array | null>;
87
91
  };
88
92
  export type HistoryQuery = {
@@ -91,13 +95,13 @@ export type HistoryQuery = {
91
95
  };
92
96
  export type MdRuntime = {
93
97
  loaded(): Promise<void>;
94
- rooms(): readonly AppRoom[];
98
+ rooms(): Promise<readonly AppRoom[]>;
95
99
  query: {
96
100
  (query: DocumentQuery): Promise<DocumentResult | PendingResult>;
97
101
  (query: GroupQuery): Promise<GroupResult | PendingResult>;
98
102
  };
99
103
  search(request: SearchRequest): Promise<SearchResult>;
100
- types(locator: string): RoomSchemas;
104
+ types(locator: string): Promise<RoomSchemas>;
101
105
  roomDraft: RoomDraftSurface;
102
106
  conversation: ConversationSurface;
103
107
  chatDraft: ChatDraftSurface;
@@ -113,6 +117,8 @@ export type MdRuntime = {
113
117
  persistence(): boolean;
114
118
  retry(): Promise<void>;
115
119
  cleanUp(): Promise<number>;
120
+ unsent(): Promise<readonly UnsentWork[]>;
121
+ reset(): Promise<void>;
116
122
  observe: {
117
123
  (topic: 'rooms'): Promise<Observation<readonly AppRoom[]>>;
118
124
  (topic: 'readiness'): Promise<Observation<Readiness>>;
@@ -121,6 +127,7 @@ export type MdRuntime = {
121
127
  (topic: 'sync-status'): Promise<Observation<SyncStatus>>;
122
128
  (topic: 'service-mode'): Promise<Observation<ServiceMode>>;
123
129
  (topic: 'member'): Promise<Observation<Member | null>>;
130
+ (topic: 'authority-changes'): Promise<Observation<number>>;
124
131
  (topic: 'query', query: DocumentQuery): Promise<Observation<DocumentResult | PendingResult>>;
125
132
  (topic: 'query', query: GroupQuery): Promise<Observation<GroupResult | PendingResult>>;
126
133
  (topic: 'queue', locator: string): Promise<Observation<readonly QueueEntry[]>>;
@@ -132,19 +139,15 @@ export type MdRuntime = {
132
139
  };
133
140
  reauthorize(): Promise<void>;
134
141
  };
135
- export type RuntimeTopic = 'rooms' | 'readiness' | 'connection-refusal' | 'storage-loss' | 'sync-status' | 'service-mode' | 'member' | 'query' | 'queue' | 'room-drafts' | 'presentation' | 'conversation' | 'conversations' | 'agents';
142
+ export type RuntimeTopic = 'rooms' | 'readiness' | 'connection-refusal' | 'storage-loss' | 'sync-status' | 'service-mode' | 'member' | 'authority-changes' | 'query' | 'queue' | 'room-drafts' | 'presentation' | 'conversation' | 'conversations' | 'agents';
136
143
  export type ReadinessPort = {
137
144
  readonly target: string;
138
145
  readonly code: CodeAvailability;
139
- readonly space: Space | null;
140
- readonly search: SearchObligation;
141
146
  };
142
147
  export type ComposedRuntime = RuntimeView & {
143
148
  seed(entry: EntrySeed): void;
144
149
  stop(): Promise<void>;
145
- lose(locators: readonly string[]): Promise<void>;
146
150
  persisted(answer: Persistence): void;
147
- shown(onScreen: boolean): void;
148
151
  within(reach: PortReach | null, port: ReadinessPort): NarrowedView;
149
152
  };
150
153
  export declare function composeRuntime(options: MdRuntimeOptions): ComposedRuntime;