@agentshouse/mdruntime 0.1.0-cefbc8bb8a1c0 → 0.1.0-cf1e7fdb9c600

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 (118) hide show
  1. package/README.md +1 -1
  2. package/dist/adapter.d.ts +69 -49
  3. package/dist/adapter.d.ts.map +1 -1
  4. package/dist/bootstrap.d.ts +5 -8
  5. package/dist/bootstrap.d.ts.map +1 -1
  6. package/dist/bootstrap.js +20 -21
  7. package/dist/chain.d.ts +2 -1
  8. package/dist/chain.d.ts.map +1 -1
  9. package/dist/client.d.ts +6 -5
  10. package/dist/client.d.ts.map +1 -1
  11. package/dist/client.js +0 -1
  12. package/dist/collection.d.ts +2 -4
  13. package/dist/collection.d.ts.map +1 -1
  14. package/dist/collection.js +1 -17
  15. package/dist/compile.d.ts +1 -6
  16. package/dist/compile.d.ts.map +1 -1
  17. package/dist/compile.js +97 -281
  18. package/dist/connection.d.ts +5 -7
  19. package/dist/connection.d.ts.map +1 -1
  20. package/dist/connection.js +816 -846
  21. package/dist/context.d.ts +2 -0
  22. package/dist/context.d.ts.map +1 -0
  23. package/dist/context.js +7 -0
  24. package/dist/conversation.d.ts +15 -13
  25. package/dist/conversation.d.ts.map +1 -1
  26. package/dist/conversation.js +133 -142
  27. package/dist/durability.d.ts +0 -1
  28. package/dist/durability.d.ts.map +1 -1
  29. package/dist/entry.d.ts +2 -2
  30. package/dist/entry.d.ts.map +1 -1
  31. package/dist/entry.js +14 -5
  32. package/dist/host.d.ts +3 -7
  33. package/dist/host.d.ts.map +1 -1
  34. package/dist/host.js +12 -26
  35. package/dist/index.d.ts +9 -6
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +3 -1
  38. package/dist/job.d.ts +14 -0
  39. package/dist/job.d.ts.map +1 -0
  40. package/dist/job.js +28 -0
  41. package/dist/live.d.ts +2 -1
  42. package/dist/live.d.ts.map +1 -1
  43. package/dist/live.js +10 -7
  44. package/dist/memory.d.ts +9 -4
  45. package/dist/memory.d.ts.map +1 -1
  46. package/dist/memory.js +132 -81
  47. package/dist/messages.d.ts +2 -2
  48. package/dist/messages.d.ts.map +1 -1
  49. package/dist/messages.js +15 -16
  50. package/dist/model.d.ts +1 -3
  51. package/dist/model.d.ts.map +1 -1
  52. package/dist/model.js +0 -12
  53. package/dist/observation.d.ts +2 -2
  54. package/dist/observation.d.ts.map +1 -1
  55. package/dist/observation.js +7 -2
  56. package/dist/outcome.d.ts +0 -3
  57. package/dist/outcome.d.ts.map +1 -1
  58. package/dist/query.d.ts +5 -209
  59. package/dist/query.d.ts.map +1 -1
  60. package/dist/query.js +3 -505
  61. package/dist/reach.d.ts +7 -7
  62. package/dist/reach.d.ts.map +1 -1
  63. package/dist/reach.js +31 -44
  64. package/dist/react.d.ts +3 -1
  65. package/dist/react.d.ts.map +1 -1
  66. package/dist/readiness.d.ts +2 -15
  67. package/dist/readiness.d.ts.map +1 -1
  68. package/dist/readiness.js +4 -10
  69. package/dist/refresh.d.ts +7 -5
  70. package/dist/refresh.d.ts.map +1 -1
  71. package/dist/refresh.js +46 -6
  72. package/dist/replica.d.ts +2 -2
  73. package/dist/replica.d.ts.map +1 -1
  74. package/dist/replica.js +5 -5
  75. package/dist/room-draft.d.ts +2 -1
  76. package/dist/room-draft.d.ts.map +1 -1
  77. package/dist/room-draft.js +65 -52
  78. package/dist/rows.d.ts +3 -19
  79. package/dist/rows.d.ts.map +1 -1
  80. package/dist/rows.js +36 -164
  81. package/dist/search.d.ts +12 -63
  82. package/dist/search.d.ts.map +1 -1
  83. package/dist/search.js +9 -52
  84. package/dist/shared.d.ts +0 -1
  85. package/dist/shared.d.ts.map +1 -1
  86. package/dist/shared.js +0 -10
  87. package/dist/status.d.ts +0 -4
  88. package/dist/status.d.ts.map +1 -1
  89. package/dist/store.d.ts +15 -19
  90. package/dist/store.d.ts.map +1 -1
  91. package/dist/store.js +40 -39
  92. package/dist/stored.d.ts +33 -6
  93. package/dist/stored.d.ts.map +1 -1
  94. package/dist/stored.js +87 -124
  95. package/dist/tables.d.ts +34 -33
  96. package/dist/tables.d.ts.map +1 -1
  97. package/dist/tables.js +21 -54
  98. package/dist/transfer.d.ts +0 -5
  99. package/dist/transfer.d.ts.map +1 -1
  100. package/dist/validation.d.ts +3 -4
  101. package/dist/validation.d.ts.map +1 -1
  102. package/dist/validation.js +39 -97
  103. package/dist/windows.d.ts +1 -12
  104. package/dist/windows.d.ts.map +1 -1
  105. package/dist/windows.js +2 -75
  106. package/dist/wire.d.ts +1 -4
  107. package/dist/wire.d.ts.map +1 -1
  108. package/dist/wire.js +1 -1
  109. package/package.json +2 -2
  110. package/dist/fulltext.d.ts +0 -27
  111. package/dist/fulltext.d.ts.map +0 -1
  112. package/dist/fulltext.js +0 -370
  113. package/dist/reads.d.ts +0 -11
  114. package/dist/reads.d.ts.map +0 -1
  115. package/dist/reads.js +0 -91
  116. package/dist/words.d.ts +0 -19
  117. package/dist/words.d.ts.map +0 -1
  118. package/dist/words.js +0 -156
@@ -1,8 +1,7 @@
1
1
  import { parse, VERSION } from '@agentshouse/mdmodel';
2
2
  import { beneath, normalize } from './paths.js';
3
3
  import { hashed } from './observation.js';
4
- import { keysIn, sortedKeys } from './shared.js';
5
- import { ROW_WEIGHT } from './turn.js';
4
+ import { sortedKeys } from './shared.js';
6
5
  export const MD_MODEL = { identity: '@agentshouse/mdmodel', version: VERSION };
7
6
  const typesPrinted = new WeakMap();
8
7
  function typesPrint(model) {
@@ -61,21 +60,6 @@ export function* documentsOf(collection) {
61
60
  }
62
61
  })();
63
62
  }
64
- export function* unreadable(text, paths = null) {
65
- const named = paths ?? (yield* keysIn(text));
66
- let least = null;
67
- for (const path of named) {
68
- const content = Object.hasOwn(text, path) ? text[path] : undefined;
69
- if (content === undefined)
70
- continue;
71
- const read = parse(content, path);
72
- if (!read.ok && (least === null || path < least.path)) {
73
- least = { path, refused: { kind: 'refused', refusal: read.refusal } };
74
- }
75
- yield ROW_WEIGHT + content.length;
76
- }
77
- return least?.refused ?? null;
78
- }
79
63
  export function validRoot(root) {
80
64
  return typeof root === 'string' && normalize(root) === root;
81
65
  }
package/dist/compile.d.ts CHANGED
@@ -1,16 +1,11 @@
1
1
  import type { Plan, QueryRows } from './query.js';
2
- import type { Connection, Value } from './tables.js';
2
+ import type { Connection } from './tables.js';
3
3
  export type QueryRoom = {
4
4
  readonly locator: string;
5
5
  readonly key: number;
6
6
  };
7
- export type Fragment = {
8
- readonly sql: string;
9
- readonly bind: readonly Value[];
10
- };
11
7
  export type QueryAnswer = QueryRows & {
12
8
  readonly more?: string;
13
9
  };
14
- export declare function selected(connection: Connection, plan: Plan, rooms: readonly QueryRoom[]): Fragment;
15
10
  export declare function answerQuery(connection: Connection, plan: Plan, rooms: readonly QueryRoom[], budget?: number | null): QueryAnswer;
16
11
  //# sourceMappingURL=compile.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../src/compile.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAOV,IAAI,EAGJ,SAAS,EAIV,MAAM,YAAY,CAAC;AAEpB,OAAO,KAAK,EAAE,UAAU,EAAO,KAAK,EAAE,MAAM,aAAa,CAAC;AAU1D,MAAM,MAAM,SAAS,GAAG;IAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAE3E,MAAM,MAAM,QAAQ,GAAG;IAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,SAAS,KAAK,EAAE,CAAA;CAAE,CAAC;AAssBjF,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG;IAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AA4TjE,wBAAgB,QAAQ,CACtB,UAAU,EAAE,UAAU,EACtB,IAAI,EAAE,IAAI,EACV,KAAK,EAAE,SAAS,SAAS,EAAE,GAC1B,QAAQ,CAEV;AAED,wBAAgB,WAAW,CACzB,UAAU,EAAE,UAAU,EACtB,IAAI,EAAE,IAAI,EACV,KAAK,EAAE,SAAS,SAAS,EAAE,EAC3B,MAAM,GAAE,MAAM,GAAG,IAAW,GAC3B,WAAW,CAUb"}
1
+ {"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../src/compile.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,IAAI,EAAuC,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvF,OAAO,KAAK,EAAE,UAAU,EAAc,MAAM,aAAa,CAAC;AAG1D,MAAM,MAAM,SAAS,GAAG;IAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAkb3E,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG;IAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAkUjE,wBAAgB,WAAW,CACzB,UAAU,EAAE,UAAU,EACtB,IAAI,EAAE,IAAI,EACV,KAAK,EAAE,SAAS,SAAS,EAAE,EAC3B,MAAM,GAAE,MAAM,GAAG,IAAW,GAC3B,WAAW,CAUb"}
package/dist/compile.js CHANGED
@@ -1,125 +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, placedOf, roomOfKey } 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 balanced(parts, separator) {
16
- if (parts.length === 1)
17
- return grouped(parts[0]);
18
- const half = Math.ceil(parts.length / 2);
19
- return grouped(joined([balanced(parts.slice(0, half), separator), balanced(parts.slice(half), separator)], separator));
20
- }
21
- function all(parts) {
22
- return parts.length === 0 ? fragment('1') : balanced(parts, ' AND ');
23
- }
24
- function any(parts) {
25
- return parts.length === 0 ? fragment('0') : balanced(parts, ' OR ');
26
- }
27
- function grouped(part) {
28
- return { sql: `(${part.sql})`, bind: part.bind };
29
- }
30
- function list(values) {
31
- return fragment(`(${values.map(() => '?').join(', ')})`, values);
32
- }
33
- function sqlValue(value) {
34
- if (value === null)
35
- return { kind: 'null', value: null };
36
- if (typeof value === 'boolean')
37
- return { kind: 'boolean', value: value ? 1 : 0 };
38
- if (typeof value === 'number')
39
- return { kind: 'number', value };
40
- return { kind: 'string', value };
41
- }
42
- function same(alias, value) {
43
- const held = sqlValue(value);
44
- return held.kind === 'null'
45
- ? fragment(`${alias}.kind = 'null'`)
46
- : fragment(`${alias}.kind = ? AND ${alias}.value = ?`, [held.kind, held.value]);
47
- }
48
- const BOUNDS = { gt: '>', gte: '>=', lt: '<', lte: '<=' };
49
- function pointed(alias, field, reach) {
50
- const { keys, places, glob } = placedOf(field, reach);
51
- return fragment(`${alias}.keys = ? AND ${alias}.places ${glob ? 'GLOB' : '='} ?`, [keys, places]);
52
- }
53
- function heldValues(doc, field, alias, where) {
54
- return joined([
55
- fragment(`SELECT 1 FROM replica_values ${alias} WHERE ${alias}.doc = ${doc}.id AND `),
56
- grouped(pointed(alias, field, 'value')),
57
- fragment(' AND '),
58
- grouped(where),
59
- ], '');
60
- }
61
- function exists(part) {
62
- return { sql: `EXISTS (${part.sql})`, bind: part.bind };
63
- }
64
- function itemOf(held, where) {
65
- return exists(joined([
66
- fragment(`SELECT 1 FROM replica_values i WHERE i.doc = ${held}.doc AND i.keys = ${held}.keys AND i.places = ${held}.places AND i.item >= 0 AND `),
67
- grouped(where),
68
- ], ''));
69
- }
70
- function matching(held, values) {
71
- return any(values.map((value) => any([
72
- same(held, value),
73
- all([fragment(`${held}.kind = 'array'`), itemOf(held, same('i', value))]),
74
- ])));
75
- }
76
- function tested(doc, field, test) {
77
- const reached = exists(heldValues(doc, field, 'r', fragment('r.item = -1')));
78
- if (test.op === 'exists')
79
- return test.exists ? reached : fragment(`NOT ${reached.sql}`, reached.bind);
80
- if (test.op === 'eq' || test.op === 'in') {
81
- const values = test.op === 'eq' ? [test.value] : test.values;
82
- return exists(heldValues(doc, field, 'v', any(values.map((value) => same('v', value)))));
83
- }
84
- if (test.op === 'ne' || test.op === 'nin') {
85
- const values = test.op === 'ne' ? [test.value] : test.values;
86
- const unmatched = matching('h', values);
87
- return any([
88
- fragment(`NOT ${reached.sql}`, reached.bind),
89
- exists(heldValues(doc, field, 'h', all([fragment('h.item = -1'), fragment(`NOT (${unmatched.sql})`, unmatched.bind)]))),
90
- ]);
91
- }
92
- if (test.op === 'range') {
93
- return exists(heldValues(doc, field, 'v', all([
94
- fragment('v.item = -1'),
95
- ...test.bounds.map(([operator, bound]) => {
96
- const held = sqlValue(bound);
97
- return fragment(`v.kind = ? AND v.value ${BOUNDS[operator]} ?`, [
98
- held.kind,
99
- held.value,
100
- ]);
101
- }),
102
- ])));
103
- }
104
- if (test.op === 'all') {
105
- return exists(heldValues(doc, field, 'h', all([
106
- fragment("h.item = -1 AND h.kind = 'array'"),
107
- ...test.values.map((value) => itemOf('h', same('i', value))),
108
- ])));
109
- }
110
- 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
+ ]);
111
16
  }
112
- function conditioned(doc, condition) {
113
- if ('every' in condition)
114
- return all(condition.every.map((one) => conditioned(doc, one)));
115
- if ('some' in condition)
116
- return any(condition.some.map((one) => conditioned(doc, one)));
117
- 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
+ });
118
23
  }
119
- function located(bound, alias) {
120
- if (bound.rooms.length === 0)
121
- return fragment("''");
122
- 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
+ ]);
123
34
  }
124
35
  function reached(bound, alias) {
125
36
  const reach = bound.reach;
@@ -133,44 +44,20 @@ function reached(bound, alias) {
133
44
  const key = bound.keys.get(roomOfKey(held));
134
45
  return key === undefined ? [] : [[key, pathOfKey(held)]];
135
46
  });
136
- const areas = reach.areas.flatMap((area) => {
47
+ const outside = any(reach.areas.flatMap((area) => {
137
48
  const key = bound.keys.get(area.room);
138
- return key === undefined ? [] : [{ key, path: area.path, types: area.types }];
139
- });
140
- const beneath = (area) => fragment(`${alias}.key = ? AND (${alias}.path = ? OR (${alias}.path >= ? AND ${alias}.path < ?))`, [area.key, area.path, `${area.path}/`, `${area.path}0`]);
141
- const typed = (types) => fragment(`${alias}.type IN ${list(types).sql}`, types);
142
- const outside = any(areas.map((area) => area.types.length === 0
143
- ? beneath(area)
144
- : all([beneath(area), fragment(`NOT ${grouped(typed(area.types)).sql}`, area.types)])));
49
+ return key === undefined ? [] : [fenced(alias, key, area, false)];
50
+ }));
145
51
  return all([
146
52
  any([
147
- ...(rooms.length === 0 ? [] : [fragment(`${alias}.key IN ${list(rooms).sql}`, rooms)]),
53
+ ...(rooms.length === 0 ? [] : [fragment(`${alias}.key IN ${listed(rooms).sql}`, rooms)]),
148
54
  ...(documents.length === 0
149
55
  ? []
150
56
  : [
151
57
  fragment(`EXISTS (SELECT 1 FROM json_each(?) j WHERE j.value ->> 0 = ${alias}.key AND j.value ->> 1 = ${alias}.path)`, [JSON.stringify(documents)]),
152
58
  ]),
153
- ...areas.flatMap((area) => area.types.length === 0 ? [] : [all([beneath(area), typed(area.types)])]),
154
59
  ]),
155
- ...(areas.length === 0 ? [] : [fragment(`NOT ${grouped(outside).sql}`, outside.bind)]),
156
- ]);
157
- }
158
- function unindexed(column) {
159
- return `+${column}`;
160
- }
161
- function scoped(alias, scope, byPaths = false) {
162
- const type = byPaths ? unindexed(`${alias}.type`) : `${alias}.type`;
163
- return all([
164
- ...(scope.types === null ? [] : [fragment(`${type} IN ${list(scope.types).sql}`, scope.types)]),
165
- ...(scope.pathPrefix === null || scope.pathPrefix === ''
166
- ? []
167
- : [
168
- fragment(`${alias}.path >= ? AND ${alias}.path < ?`, [
169
- `${scope.pathPrefix}/`,
170
- `${scope.pathPrefix}0`,
171
- ]),
172
- ]),
173
- ...(scope.where === null ? [] : [conditioned(alias, scope.where)]),
60
+ ...(reach.areas.length === 0 ? [] : [fragment(`NOT ${grouped(outside).sql}`, outside.bind)]),
174
61
  ]);
175
62
  }
176
63
  function related(bound, plan) {
@@ -178,17 +65,17 @@ function related(bound, plan) {
178
65
  if (plan.hop !== null) {
179
66
  parts.push(exists(joined([
180
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`),
181
- grouped(pointed('l', plan.hop.field, 'link')),
68
+ grouped(sql.placed('l', plan.hop.field, 'link')),
182
69
  fragment('AND'),
183
- grouped(all([scoped('m', plan.hop), reached(bound, 'm')])),
70
+ grouped(all([sql.scoped('m', plan.hop), reached(bound, 'm')])),
184
71
  ], ' ')));
185
72
  }
186
73
  if (plan.backlinks !== null) {
187
74
  const field = plan.backlinks.field;
188
75
  parts.push(exists(joined([
189
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`),
190
- ...(field === null ? [] : [grouped(pointed('l', field, 'link')), fragment('AND')]),
191
- grouped(all([scoped('s', plan.backlinks), reached(bound, 's')])),
77
+ ...(field === null ? [] : [grouped(sql.placed('l', field, 'link')), fragment('AND')]),
78
+ grouped(all([sql.scoped('s', plan.backlinks), reached(bound, 's')])),
192
79
  ], ' ')));
193
80
  }
194
81
  return parts;
@@ -196,11 +83,9 @@ function related(bound, plan) {
196
83
  function filtered(bound, plan) {
197
84
  const keys = bound.rooms.map(({ key }) => key);
198
85
  return all([
199
- fragment(`d.key IN ${list(keys).sql} AND ${VISIBLE('d')}`, keys),
200
- ...(plan.paths === null ? [] : [fragment(`d.path IN ${list(plan.paths).sql}`, plan.paths)]),
201
- ...(plan.folder === null ? [] : [fragment('d.folder = ?', [plan.folder])]),
86
+ fragment(`d.key IN ${listed(keys).sql} AND ${VISIBLE('d')}`, keys),
202
87
  reached(bound, 'd'),
203
- scoped('d', plan.root, plan.paths !== null),
88
+ sql.tests('d', plan),
204
89
  ...related(bound, plan),
205
90
  ]);
206
91
  }
@@ -231,103 +116,25 @@ const GROUP_TIES = {
231
116
  locale: undefined,
232
117
  localeOptions: undefined,
233
118
  };
234
- function classOf(value, ordering) {
235
- if (value === null || value === undefined) {
236
- return { placement: ordering.nulls === 'first' ? 0 : 2, klass: 0, scalar: null };
237
- }
238
- if (typeof value === 'boolean')
239
- return { placement: 1, klass: 1, scalar: value ? 1 : 0 };
240
- if (typeof value === 'number')
241
- return { placement: 1, klass: 1, scalar: value };
242
- if (typeof value === 'string')
243
- return { placement: 1, klass: 2, scalar: value };
244
- return { placement: 1, klass: 3, scalar: JSON.stringify(value) };
245
- }
246
- function placement(term) {
247
- const nulls = term.ordering.nulls === 'first' ? 0 : 2;
248
- 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]);
249
- }
250
- function klass(term) {
251
- 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);
252
- }
253
- function classed(term, which) {
254
- const kinds = which === 1 ? "('boolean', 'number')" : which === 2 ? "('string')" : "('array')";
255
- return fragment(`(CASE WHEN ${term.kind.sql} IN ${kinds} THEN ${term.value.sql} END)`, [
256
- ...term.kind.bind,
257
- ...term.value.bind,
258
- ]);
259
- }
260
- function collated(bound, term, which) {
261
- if (which === 1)
262
- return '';
263
- const name = bound.collate(term.ordering, which === 3, term.total === true);
264
- return name === null ? '' : ` COLLATE ${name}`;
265
- }
266
- function sorted(bound, term) {
267
- const direction = term.ordering.direction === 'desc' ? 'DESC' : 'ASC';
268
- return joined([
269
- placement(term),
270
- fragment(`${klass(term).sql} ${direction}`, klass(term).bind),
271
- ...[1, 2, 3].map((which) => {
272
- const value = classed(term, which);
273
- return fragment(`${value.sql}${collated(bound, term, which)} ${direction}`, value.bind);
274
- }),
275
- ], ', ');
276
- }
277
- function beyond(bound, term, value) {
278
- const held = classOf(value, term.ordering);
279
- const place = placement(term);
280
- if (held.placement !== 1) {
281
- return {
282
- beyond: fragment(`${place.sql} > ${held.placement}`, place.bind),
283
- equal: fragment(`${place.sql} = ${held.placement}`, place.bind),
284
- };
285
- }
286
- const after = term.ordering.direction === 'desc' ? '<' : '>';
287
- const kind = klass(term);
288
- const scalar = classed(term, held.klass);
289
- const collation = collated(bound, term, held.klass);
290
- return {
291
- beyond: any([
292
- fragment(`${place.sql} > 1`, place.bind),
293
- all([
294
- fragment(`${place.sql} = 1`, place.bind),
295
- any([
296
- fragment(`${kind.sql} ${after} ${held.klass}`, kind.bind),
297
- all([
298
- fragment(`${kind.sql} = ${held.klass}`, kind.bind),
299
- fragment(`${scalar.sql} ${after} ?${collation}`, [...scalar.bind, held.scalar]),
300
- ]),
301
- ]),
302
- ]),
303
- ]),
304
- equal: all([
305
- fragment(`${place.sql} = 1 AND ${kind.sql} = ${held.klass}`, [...place.bind, ...kind.bind]),
306
- fragment(`${scalar.sql} = ?${collation}`, [...scalar.bind, held.scalar]),
307
- ]),
308
- };
309
- }
310
- function following(parts, tie) {
311
- let condition = tie;
312
- for (const part of [...parts].reverse()) {
313
- condition = any([part.beyond, all([part.equal, condition])]);
314
- }
315
- return condition;
316
- }
317
- function keyTerm(bound, key, ordering, index) {
119
+ function keyTerm(key, ordering, index) {
318
120
  if (key.of === 'field') {
319
121
  const alias = `o${index}`;
320
122
  return {
321
- term: { ordering, kind: fragment(`${alias}.kind`), value: fragment(`${alias}.value`) },
123
+ term: {
124
+ ordering,
125
+ kind: fragment(`${alias}.kind`),
126
+ value: fragment(`${alias}.value`),
127
+ instant: fragment(`${alias}.instant`),
128
+ },
322
129
  join: joined([
323
130
  fragment(`LEFT JOIN replica_values ${alias} ON ${alias}.doc = d.id AND `),
324
- grouped(pointed(alias, key.field, 'exact')),
131
+ grouped(sql.placed(alias, key.field, 'exact')),
325
132
  fragment(` AND ${alias}.item = -1 AND ${alias}.kind NOT IN ('object', 'undefined')`),
326
133
  ], ''),
327
134
  };
328
135
  }
329
136
  const expression = key.of === 'room'
330
- ? located(bound, 'd')
137
+ ? located('d')
331
138
  : key.of === 'path'
332
139
  ? fragment('d.path')
333
140
  : key.of === 'type'
@@ -353,13 +160,20 @@ function decoded(kind, value) {
353
160
  return JSON.parse(value);
354
161
  return null;
355
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
+ }
356
167
  function decodedKey(key, row, index) {
357
168
  if (key.of === 'field') {
358
169
  const kind = row[`k${index}`];
359
- return kind === null || kind === undefined ? undefined : decoded(kind, row[`v${index}`]);
170
+ return kind === null || kind === undefined ? undefined : sortedValue(row, index);
360
171
  }
361
172
  return row[`v${index}`] ?? null;
362
173
  }
174
+ function instantOf(term) {
175
+ return term.instant ?? fragment('NULL');
176
+ }
363
177
  const registered = new WeakMap();
364
178
  function hashed(text) {
365
179
  let hash = 0x811c9dc5;
@@ -409,9 +223,6 @@ function collating(connection) {
409
223
  return name;
410
224
  };
411
225
  }
412
- function bodied(shape) {
413
- return shape.sections !== null || shape.preamble || shape.markdown;
414
- }
415
226
  function masked(pointers) {
416
227
  const root = { whole: false, members: new Map() };
417
228
  for (const pointer of pointers) {
@@ -468,9 +279,10 @@ function documentOf(shape, room, row, linked) {
468
279
  const type = row.type;
469
280
  const fileId = row.file_id ?? null;
470
281
  const revision = row.revision ?? null;
282
+ const content = row.content ?? null;
283
+ const unheld = Number(row.unheld) === 1 ? { textHeld: false } : {};
471
284
  if (type === null) {
472
285
  const binary = row.binary === null ? null : JSON.parse(row.binary);
473
- const content = row.content ?? null;
474
286
  return {
475
287
  room,
476
288
  path,
@@ -480,11 +292,12 @@ function documentOf(shape, room, row, linked) {
480
292
  binaryFile: binaryRecord(path, binary, revision ?? undefined),
481
293
  }),
482
294
  ...(shape.markdown && content !== null ? { markdown: content } : {}),
295
+ ...unheld,
483
296
  ...(shape.fileId ? { fileId } : {}),
484
297
  ...(shape.revision ? { revision } : {}),
485
298
  };
486
299
  }
487
- const read = bodied(shape) ? parse(row.content, path) : null;
300
+ const read = bodied(shape) && content !== null ? parse(content, path) : null;
488
301
  const body = read?.ok === true ? read.document : null;
489
302
  const fields = JSON.parse(row.fields ?? '{}');
490
303
  return {
@@ -502,7 +315,8 @@ function documentOf(shape, room, row, linked) {
502
315
  ? {}
503
316
  : { sections: sectionsOf(body.sections, shape.sections) }),
504
317
  ...(shape.preamble && body !== null ? { preamble: body.preamble } : {}),
505
- ...(shape.markdown && body !== null ? { markdown: body.content } : {}),
318
+ ...(shape.markdown && content !== null ? { markdown: content } : {}),
319
+ ...unheld,
506
320
  ...(shape.backlinks ? { backlinks: linked.get(documentKey(room, path)) ?? [] } : {}),
507
321
  ...(shape.change
508
322
  ? {
@@ -536,10 +350,10 @@ function backlinksOf(connection, bound, rows) {
536
350
  return linked;
537
351
  }
538
352
  function documents(connection, bound, plan, shape, budget) {
539
- const terms = shape.orderBy.map(({ key, options }, index) => keyTerm(bound, key, orderingOf(options), index));
353
+ const terms = shape.orderBy.map(({ key, options }, index) => keyTerm(key, orderingOf(options), index));
540
354
  const filter = filtered(bound, plan);
541
355
  const cursor = plan.after?.values ?? null;
542
- const room = located(bound, 'd');
356
+ const room = located('d');
543
357
  const tie = cursor === null
544
358
  ? null
545
359
  : (() => {
@@ -555,19 +369,20 @@ function documents(connection, bound, plan, shape, budget) {
555
369
  })();
556
370
  const after = cursor === null || tie === null
557
371
  ? null
558
- : following(terms.map(({ term }, index) => beyond(bound, term, cursor[index])), tie);
372
+ : sql.following(terms.map(({ term }, index) => sql.beyond(bound.collate, term, cursor[index])), tie);
559
373
  const selected = joined([
560
- fragment('SELECT d.id, d.key, d.path, d.type, d.binary, d.revision, d.fields, d.change, d.file_id'),
374
+ fragment('SELECT d.id, d.key, d.path, d.type, d.binary, d.revision, d.fields, d.change, d.file_id, d.content IS NULL AND d.outline IS NOT NULL AS unheld'),
561
375
  ...terms.map(({ term }, index) => joined([
562
376
  fragment(`, ${term.kind.sql} AS k${index}`, term.kind.bind),
563
377
  fragment(`, ${term.value.sql} AS v${index}`, term.value.bind),
378
+ fragment(`, ${instantOf(term).sql} AS i${index}`, instantOf(term).bind),
564
379
  ], '')),
565
380
  fragment(' FROM replica_entries d'),
566
381
  ...terms.flatMap(({ join }) => (join === null ? [] : [fragment(' '), join])),
567
382
  fragment(' WHERE '),
568
383
  after === null ? filter : all([filter, after]),
569
384
  fragment(' ORDER BY '),
570
- joined([...terms.map(({ term }) => sorted(bound, term)), room, fragment('d.path')], ', '),
385
+ joined([...terms.map(({ term }) => sql.sorted(bound.collate, term)), room, fragment('d.path')], ', '),
571
386
  ...(plan.limit === null ? [] : [fragment(' LIMIT ?', [plan.limit + 1])]),
572
387
  ], '');
573
388
  const rows = connection.rows(selected.sql, selected.bind);
@@ -606,7 +421,7 @@ function withContent(connection, rows, budget) {
606
421
  return rows;
607
422
  const ids = rows.map((row) => row.id);
608
423
  const sizes = new Map(connection
609
- .rows(`SELECT id, octet_length(content) AS size FROM replica_entries WHERE id IN ${list(ids).sql}`, ids)
424
+ .rows(`SELECT id, octet_length(content) AS size FROM replica_entries WHERE id IN ${listed(ids).sql}`, ids)
610
425
  .map((row) => [row.id, Number(row.size ?? 0)]));
611
426
  let cut = rows.length;
612
427
  let spent = 0;
@@ -621,34 +436,30 @@ function withContent(connection, rows, budget) {
621
436
  const kept = rows.slice(0, cut);
622
437
  const read = kept.map((row) => row.id);
623
438
  const held = new Map(connection
624
- .rows(`SELECT id, content FROM replica_entries WHERE id IN ${list(read).sql}`, read)
439
+ .rows(`SELECT id, content FROM replica_entries WHERE id IN ${listed(read).sql}`, read)
625
440
  .map((row) => [row.id, row.content]));
626
441
  return kept.map((row) => ({ ...row, content: held.get(row.id) ?? null }));
627
442
  }
628
443
  function storedFolders(bound, plan) {
629
- if (plan.paths !== null || plan.hop !== null || plan.backlinks !== null)
444
+ if (plan.paths !== null || plan.fileIds !== null)
445
+ return null;
446
+ if (plan.hop !== null || plan.backlinks !== null)
630
447
  return null;
631
448
  const whole = bound.rooms.filter(({ locator }) => bound.reach === null || bound.reach.rooms.includes(locator));
632
449
  if (whole.length === 0)
633
450
  return null;
634
451
  const keys = whole.map(({ key }) => key);
635
452
  const prefix = plan.root.pathPrefix;
636
- const fenced = (bound.reach?.areas ?? []).flatMap((area) => {
453
+ const outside = (bound.reach?.areas ?? []).flatMap((area) => {
637
454
  const key = bound.keys.get(area.room);
638
- return key === undefined
639
- ? []
640
- : [
641
- fragment('NOT (f.key = ? AND (f.path = ? OR (f.path >= ? AND f.path < ?)))', [
642
- key,
643
- area.path,
644
- `${area.path}/`,
645
- `${area.path}0`,
646
- ]),
647
- ];
455
+ if (key === undefined)
456
+ return [];
457
+ const fence = fenced('f', key, area, true);
458
+ return [fragment(`NOT ${grouped(fence).sql}`, fence.bind)];
648
459
  });
649
460
  return all([
650
- fragment(`f.key IN ${list(keys).sql} AND f.layer = 0 AND f.present = 1`, keys),
651
- ...fenced,
461
+ fragment(`f.key IN ${listed(keys).sql} AND f.layer = 0 AND f.present = 1`, keys),
462
+ ...outside,
652
463
  ...(prefix === null || prefix === ''
653
464
  ? []
654
465
  : [
@@ -663,7 +474,7 @@ function storedFolders(bound, plan) {
663
474
  }
664
475
  function groups(connection, bound, plan, shape) {
665
476
  const keyed = shape.groupBy.map((key, index) => {
666
- const { term, join } = keyTerm(bound, key, GROUP_ORDER, index);
477
+ const { term, join } = keyTerm(key, GROUP_ORDER, index);
667
478
  const kind = fragment(`COALESCE(${term.kind.sql}, 'null')`, term.kind.bind);
668
479
  return { term: { ...term, kind }, join };
669
480
  });
@@ -673,6 +484,7 @@ function groups(connection, bound, plan, shape) {
673
484
  joined(keyed.map(({ term }, index) => joined([
674
485
  fragment(`${term.kind.sql} AS k${index}`, term.kind.bind),
675
486
  fragment(`, ${term.value.sql} AS v${index}`, term.value.bind),
487
+ fragment(`, ${instantOf(term).sql} AS i${index}`, instantOf(term).bind),
676
488
  ], '')), ', '),
677
489
  fragment(', 1 AS n FROM replica_entries d'),
678
490
  ...keyed.flatMap(({ join }) => (join === null ? [] : [fragment(' '), join])),
@@ -686,10 +498,15 @@ function groups(connection, bound, plan, shape) {
686
498
  ? inner
687
499
  : joined([
688
500
  inner,
689
- fragment(" UNION ALL SELECT 'string' AS k0, f.path AS v0, 0 AS n FROM replica_folders f WHERE "),
501
+ fragment(" UNION ALL SELECT 'string' AS k0, f.path AS v0, NULL AS i0, 0 AS n FROM replica_folders f WHERE "),
690
502
  folders,
691
503
  ], '');
692
- const columns = keyed.map((_, index) => `k${index}, v${index}`).join(', ');
504
+ const columns = keyed
505
+ .map((_, index) => `k${index}, min(v${index}) AS v${index}, i${index}`)
506
+ .join(', ');
507
+ const held = keyed
508
+ .map((_, index) => `k${index}, i${index}, CASE WHEN i${index} IS NULL THEN v${index} END`)
509
+ .join(', ');
693
510
  const counted = {
694
511
  ordering: { ...GROUP_ORDER },
695
512
  kind: fragment("'number'"),
@@ -702,7 +519,7 @@ function groups(connection, bound, plan, shape) {
702
519
  });
703
520
  const ordered = shape.orderBy.map(({ by, direction }) => by === 'count'
704
521
  ? { ...counted, ordering: { ...GROUP_ORDER, direction } }
705
- : outer(by, { ...GROUP_ORDER, direction }));
522
+ : { ...outer(by, { ...GROUP_ORDER, direction }), instant: fragment(`i${by}`) });
706
523
  const ties = keyed.flatMap((_, index) => [
707
524
  { ...outer(index, GROUP_TIES), total: true },
708
525
  { ordering: GROUP_TIES, kind: fragment("'string'"), value: fragment(`k${index}`) },
@@ -712,19 +529,21 @@ function groups(connection, bound, plan, shape) {
712
529
  ? null
713
530
  : cursor[shape.orderBy.length].flatMap((value) => [
714
531
  value,
715
- kindOf(value ?? null),
532
+ heldKind(value ?? null),
716
533
  ]);
717
534
  const after = cursor === null || tieValues === null
718
535
  ? null
719
- : 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));
720
- const grouping = joined([fragment(`SELECT ${columns}, sum(n) AS n FROM (`), source, fragment(`) GROUP BY ${columns}`)], '');
536
+ : sql.following(ordered.map((term, index) => sql.beyond(bound.collate, term, cursor[index])), sql.following(ties
537
+ .slice(0, -1)
538
+ .map((term, index) => sql.beyond(bound.collate, term, tieValues[index])), sql.beyond(bound.collate, ties.at(-1), tieValues.at(-1)).beyond));
539
+ const grouping = joined([fragment(`SELECT ${columns}, sum(n) AS n FROM (`), source, fragment(`) GROUP BY ${held}`)], '');
721
540
  const windowed = joined([
722
541
  fragment('SELECT * FROM ('),
723
542
  grouping,
724
543
  fragment(')'),
725
544
  ...(after === null ? [] : [fragment(' WHERE '), after]),
726
545
  fragment(' ORDER BY '),
727
- joined([...ordered, ...ties].map((term) => sorted(bound, term)), ', '),
546
+ joined([...ordered, ...ties].map((term) => sql.sorted(bound.collate, term)), ', '),
728
547
  ...(plan.limit === null ? [] : [fragment(' LIMIT ?', [plan.limit + 1])]),
729
548
  ], '');
730
549
  const rows = connection.rows(windowed.sql, windowed.bind);
@@ -737,7 +556,7 @@ function groups(connection, bound, plan, shape) {
737
556
  : last === undefined
738
557
  ? (plan.after?.text ?? FROM_START)
739
558
  : cursorOf([
740
- ...shape.orderBy.map(({ by }) => by === 'count' ? Number(last.n) : decoded(last[`k${by}`], last[`v${by}`])),
559
+ ...shape.orderBy.map(({ by }) => by === 'count' ? Number(last.n) : sortedValue(last, by)),
741
560
  keyOf(last),
742
561
  ]);
743
562
  const total = plan.total
@@ -757,9 +576,6 @@ function boundOf(connection, plan, rooms) {
757
576
  collate: collating(connection),
758
577
  };
759
578
  }
760
- export function selected(connection, plan, rooms) {
761
- return filtered(boundOf(connection, plan, rooms), plan);
762
- }
763
579
  export function answerQuery(connection, plan, rooms, budget = null) {
764
580
  const bound = boundOf(connection, plan, rooms);
765
581
  if (rooms.length === 0) {