@agentshouse/mdruntime 0.1.0-cba6f2439bb25 → 0.1.0-cbe358f60d1d3

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 (127) hide show
  1. package/README.md +1 -1
  2. package/dist/adapter.d.ts +66 -52
  3. package/dist/adapter.d.ts.map +1 -1
  4. package/dist/bootstrap.d.ts +6 -9
  5. package/dist/bootstrap.d.ts.map +1 -1
  6. package/dist/bootstrap.js +28 -22
  7. package/dist/chain.d.ts +2 -1
  8. package/dist/chain.d.ts.map +1 -1
  9. package/dist/client.d.ts +9 -5
  10. package/dist/client.d.ts.map +1 -1
  11. package/dist/client.js +2 -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 +91 -276
  18. package/dist/connection.d.ts +8 -8
  19. package/dist/connection.d.ts.map +1 -1
  20. package/dist/connection.js +738 -968
  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 -14
  25. package/dist/conversation.d.ts.map +1 -1
  26. package/dist/conversation.js +141 -167
  27. package/dist/durability.d.ts +6 -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 +13 -5
  32. package/dist/host.d.ts +3 -7
  33. package/dist/host.d.ts.map +1 -1
  34. package/dist/host.js +22 -27
  35. package/dist/identity.d.ts +0 -1
  36. package/dist/identity.d.ts.map +1 -1
  37. package/dist/identity.js +0 -3
  38. package/dist/index.d.ts +11 -7
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +4 -1
  41. package/dist/job.d.ts +14 -0
  42. package/dist/job.d.ts.map +1 -0
  43. package/dist/job.js +28 -0
  44. package/dist/live.d.ts +2 -1
  45. package/dist/live.d.ts.map +1 -1
  46. package/dist/live.js +10 -7
  47. package/dist/media.d.ts +1 -1
  48. package/dist/media.d.ts.map +1 -1
  49. package/dist/media.js +19 -9
  50. package/dist/memory.d.ts +9 -4
  51. package/dist/memory.d.ts.map +1 -1
  52. package/dist/memory.js +132 -82
  53. package/dist/messages.d.ts +2 -2
  54. package/dist/messages.d.ts.map +1 -1
  55. package/dist/messages.js +15 -16
  56. package/dist/model.d.ts +1 -3
  57. package/dist/model.d.ts.map +1 -1
  58. package/dist/model.js +0 -12
  59. package/dist/observation.d.ts +2 -2
  60. package/dist/observation.d.ts.map +1 -1
  61. package/dist/observation.js +7 -2
  62. package/dist/outcome.d.ts +0 -3
  63. package/dist/outcome.d.ts.map +1 -1
  64. package/dist/query.d.ts +4 -209
  65. package/dist/query.d.ts.map +1 -1
  66. package/dist/query.js +3 -505
  67. package/dist/reach.d.ts +7 -7
  68. package/dist/reach.d.ts.map +1 -1
  69. package/dist/reach.js +34 -45
  70. package/dist/react.d.ts +3 -1
  71. package/dist/react.d.ts.map +1 -1
  72. package/dist/readiness.d.ts +2 -15
  73. package/dist/readiness.d.ts.map +1 -1
  74. package/dist/readiness.js +4 -10
  75. package/dist/recovery.d.ts +1 -0
  76. package/dist/recovery.d.ts.map +1 -1
  77. package/dist/recovery.js +26 -0
  78. package/dist/refresh.d.ts +6 -4
  79. package/dist/refresh.d.ts.map +1 -1
  80. package/dist/refresh.js +26 -5
  81. package/dist/replica.d.ts +2 -2
  82. package/dist/replica.d.ts.map +1 -1
  83. package/dist/replica.js +2 -5
  84. package/dist/room-draft.d.ts +4 -3
  85. package/dist/room-draft.d.ts.map +1 -1
  86. package/dist/room-draft.js +65 -52
  87. package/dist/rows.d.ts +1 -17
  88. package/dist/rows.d.ts.map +1 -1
  89. package/dist/rows.js +4 -137
  90. package/dist/search.d.ts +12 -63
  91. package/dist/search.d.ts.map +1 -1
  92. package/dist/search.js +9 -52
  93. package/dist/shared.d.ts +0 -1
  94. package/dist/shared.d.ts.map +1 -1
  95. package/dist/shared.js +0 -10
  96. package/dist/status.d.ts +0 -4
  97. package/dist/status.d.ts.map +1 -1
  98. package/dist/store.d.ts +5 -20
  99. package/dist/store.d.ts.map +1 -1
  100. package/dist/store.js +10 -44
  101. package/dist/stored.d.ts +0 -6
  102. package/dist/stored.d.ts.map +1 -1
  103. package/dist/stored.js +42 -115
  104. package/dist/tables.d.ts +10 -34
  105. package/dist/tables.d.ts.map +1 -1
  106. package/dist/tables.js +23 -57
  107. package/dist/transfer.d.ts +0 -5
  108. package/dist/transfer.d.ts.map +1 -1
  109. package/dist/validation.d.ts +3 -4
  110. package/dist/validation.d.ts.map +1 -1
  111. package/dist/validation.js +39 -97
  112. package/dist/windows.d.ts +1 -12
  113. package/dist/windows.d.ts.map +1 -1
  114. package/dist/windows.js +2 -75
  115. package/dist/wire.d.ts +9 -4
  116. package/dist/wire.d.ts.map +1 -1
  117. package/dist/wire.js +3 -1
  118. package/package.json +2 -2
  119. package/dist/fulltext.d.ts +0 -27
  120. package/dist/fulltext.d.ts.map +0 -1
  121. package/dist/fulltext.js +0 -370
  122. package/dist/reads.d.ts +0 -11
  123. package/dist/reads.d.ts.map +0 -1
  124. package/dist/reads.js +0 -91
  125. package/dist/words.d.ts +0 -19
  126. package/dist/words.d.ts.map +0 -1
  127. package/dist/words.js +0 -156
@@ -1,8 +1,6 @@
1
- import { type Document } from '@agentshouse/mdmodel';
1
+ import { type Document, type TypedPath } from '@agentshouse/mdmodel';
2
2
  import type { ModelState } from './model.js';
3
3
  import type { MdModelIdentity } from './adapter.js';
4
- import { type Paths } from './shared.js';
5
- import type { Refused } from './transfer.js';
6
4
  export declare const MD_MODEL: MdModelIdentity;
7
5
  export type Collection = {
8
6
  text: Readonly<Record<string, string>>;
@@ -15,6 +13,7 @@ export type Composition = {
15
13
  roots: readonly string[];
16
14
  model: ModelState;
17
15
  } | null;
16
+ typedPaths: readonly TypedPath[];
18
17
  };
19
18
  export type Composed = {
20
19
  readonly composition: Composition;
@@ -25,7 +24,6 @@ export declare function selected(composition: Composition, path: string): boolea
25
24
  export declare function governing(composition: Composition, path: string): ModelState;
26
25
  export declare function collectionOf(text: Readonly<Record<string, string>>, composition: Composition): Collection;
27
26
  export declare function documentsOf(collection: Collection): Generator<number, Iterable<readonly [string, Document]>, void>;
28
- export declare function unreadable(text: Paths<string>, paths?: Iterable<string> | null): Generator<number, Refused | null, void>;
29
27
  export declare function validRoot(root: unknown): root is string;
30
28
  export declare function available(composition: Composition): boolean;
31
29
  //# sourceMappingURL=collection.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"collection.d.ts","sourceRoot":"","sources":["../src/collection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAErE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAEpD,OAAO,EAAsB,KAAK,KAAK,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAG7C,eAAO,MAAM,QAAQ,EAAE,eAAwE,CAAC;AAEhG,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACvC,WAAW,EAAE,WAAW,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAC;CAC9C,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,EAAE,UAAU,CAAC;IAClB,SAAS,EAAE;QAAE,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;QAAC,KAAK,EAAE,UAAU,CAAA;KAAE,GAAG,IAAI,CAAC;CACnE,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG;IAAE,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAAC;AAc3F,wBAAgB,UAAU,CAAC,WAAW,EAAE,WAAW,GAAG,QAAQ,CAS7D;AAED,wBAAgB,QAAQ,CAAC,WAAW,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAKxE;AAED,wBAAgB,SAAS,CAAC,WAAW,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG,UAAU,CAI5E;AAED,wBAAgB,YAAY,CAC1B,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EACtC,WAAW,EAAE,WAAW,GACvB,UAAU,CAcZ;AAED,wBAAiB,WAAW,CAC1B,UAAU,EAAE,UAAU,GACrB,SAAS,CAAC,MAAM,EAAE,QAAQ,CAAC,SAAS,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,EAAE,IAAI,CAAC,CAQhE;AAED,wBAAiB,UAAU,CACzB,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,EACnB,KAAK,GAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,IAAW,GACpC,SAAS,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,EAAE,IAAI,CAAC,CAazC;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI,MAAM,CAEvD;AAED,wBAAgB,SAAS,CAAC,WAAW,EAAE,WAAW,GAAG,OAAO,CAE3D"}
1
+ {"version":3,"file":"collection.d.ts","sourceRoot":"","sources":["../src/collection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,QAAQ,EAAE,KAAK,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAErF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAIpD,eAAO,MAAM,QAAQ,EAAE,eAAwE,CAAC;AAEhG,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACvC,WAAW,EAAE,WAAW,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAC;CAC9C,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,EAAE,UAAU,CAAC;IAClB,SAAS,EAAE;QAAE,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;QAAC,KAAK,EAAE,UAAU,CAAA;KAAE,GAAG,IAAI,CAAC;IAClE,UAAU,EAAE,SAAS,SAAS,EAAE,CAAC;CAClC,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG;IAAE,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAAC;AAc3F,wBAAgB,UAAU,CAAC,WAAW,EAAE,WAAW,GAAG,QAAQ,CAS7D;AAED,wBAAgB,QAAQ,CAAC,WAAW,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAKxE;AAED,wBAAgB,SAAS,CAAC,WAAW,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG,UAAU,CAI5E;AAED,wBAAgB,YAAY,CAC1B,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EACtC,WAAW,EAAE,WAAW,GACvB,UAAU,CAcZ;AAED,wBAAiB,WAAW,CAC1B,UAAU,EAAE,UAAU,GACrB,SAAS,CAAC,MAAM,EAAE,QAAQ,CAAC,SAAS,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,EAAE,IAAI,CAAC,CAQhE;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI,MAAM,CAEvD;AAED,wBAAgB,SAAS,CAAC,WAAW,EAAE,WAAW,GAAG,OAAO,CAE3D"}
@@ -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;AAmsBjF,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;AA+a3E,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,42 +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 scoped(alias, scope) {
159
- return all([
160
- ...(scope.types === null
161
- ? []
162
- : [fragment(`${alias}.type IN ${list(scope.types).sql}`, scope.types)]),
163
- ...(scope.pathPrefix === null || scope.pathPrefix === ''
164
- ? []
165
- : [
166
- fragment(`${alias}.path >= ? AND ${alias}.path < ?`, [
167
- `${scope.pathPrefix}/`,
168
- `${scope.pathPrefix}0`,
169
- ]),
170
- ]),
171
- ...(scope.where === null ? [] : [conditioned(alias, scope.where)]),
60
+ ...(reach.areas.length === 0 ? [] : [fragment(`NOT ${grouped(outside).sql}`, outside.bind)]),
172
61
  ]);
173
62
  }
174
63
  function related(bound, plan) {
@@ -176,17 +65,17 @@ function related(bound, plan) {
176
65
  if (plan.hop !== null) {
177
66
  parts.push(exists(joined([
178
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`),
179
- grouped(pointed('l', plan.hop.field, 'link')),
68
+ grouped(sql.placed('l', plan.hop.field, 'link')),
180
69
  fragment('AND'),
181
- grouped(all([scoped('m', plan.hop), reached(bound, 'm')])),
70
+ grouped(all([sql.scoped('m', plan.hop), reached(bound, 'm')])),
182
71
  ], ' ')));
183
72
  }
184
73
  if (plan.backlinks !== null) {
185
74
  const field = plan.backlinks.field;
186
75
  parts.push(exists(joined([
187
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`),
188
- ...(field === null ? [] : [grouped(pointed('l', field, 'link')), fragment('AND')]),
189
- 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')])),
190
79
  ], ' ')));
191
80
  }
192
81
  return parts;
@@ -194,11 +83,9 @@ function related(bound, plan) {
194
83
  function filtered(bound, plan) {
195
84
  const keys = bound.rooms.map(({ key }) => key);
196
85
  return all([
197
- fragment(`d.key IN ${list(keys).sql} AND ${VISIBLE('d')}`, keys),
198
- ...(plan.paths === null ? [] : [fragment(`d.path IN ${list(plan.paths).sql}`, plan.paths)]),
199
- ...(plan.folder === null ? [] : [fragment('d.folder = ?', [plan.folder])]),
86
+ fragment(`d.key IN ${listed(keys).sql} AND ${VISIBLE('d')}`, keys),
200
87
  reached(bound, 'd'),
201
- scoped('d', plan.root),
88
+ sql.tests('d', plan),
202
89
  ...related(bound, plan),
203
90
  ]);
204
91
  }
@@ -229,103 +116,25 @@ const GROUP_TIES = {
229
116
  locale: undefined,
230
117
  localeOptions: undefined,
231
118
  };
232
- function classOf(value, ordering) {
233
- if (value === null || value === undefined) {
234
- return { placement: ordering.nulls === 'first' ? 0 : 2, klass: 0, scalar: null };
235
- }
236
- if (typeof value === 'boolean')
237
- return { placement: 1, klass: 1, scalar: value ? 1 : 0 };
238
- if (typeof value === 'number')
239
- return { placement: 1, klass: 1, scalar: value };
240
- if (typeof value === 'string')
241
- return { placement: 1, klass: 2, scalar: value };
242
- return { placement: 1, klass: 3, scalar: JSON.stringify(value) };
243
- }
244
- function placement(term) {
245
- const nulls = term.ordering.nulls === 'first' ? 0 : 2;
246
- 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]);
247
- }
248
- function klass(term) {
249
- 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);
250
- }
251
- function classed(term, which) {
252
- const kinds = which === 1 ? "('boolean', 'number')" : which === 2 ? "('string')" : "('array')";
253
- return fragment(`(CASE WHEN ${term.kind.sql} IN ${kinds} THEN ${term.value.sql} END)`, [
254
- ...term.kind.bind,
255
- ...term.value.bind,
256
- ]);
257
- }
258
- function collated(bound, term, which) {
259
- if (which === 1)
260
- return '';
261
- const name = bound.collate(term.ordering, which === 3, term.total === true);
262
- return name === null ? '' : ` COLLATE ${name}`;
263
- }
264
- function sorted(bound, term) {
265
- const direction = term.ordering.direction === 'desc' ? 'DESC' : 'ASC';
266
- return joined([
267
- placement(term),
268
- fragment(`${klass(term).sql} ${direction}`, klass(term).bind),
269
- ...[1, 2, 3].map((which) => {
270
- const value = classed(term, which);
271
- return fragment(`${value.sql}${collated(bound, term, which)} ${direction}`, value.bind);
272
- }),
273
- ], ', ');
274
- }
275
- function beyond(bound, term, value) {
276
- const held = classOf(value, term.ordering);
277
- const place = placement(term);
278
- if (held.placement !== 1) {
279
- return {
280
- beyond: fragment(`${place.sql} > ${held.placement}`, place.bind),
281
- equal: fragment(`${place.sql} = ${held.placement}`, place.bind),
282
- };
283
- }
284
- const after = term.ordering.direction === 'desc' ? '<' : '>';
285
- const kind = klass(term);
286
- const scalar = classed(term, held.klass);
287
- const collation = collated(bound, term, held.klass);
288
- return {
289
- beyond: any([
290
- fragment(`${place.sql} > 1`, place.bind),
291
- all([
292
- fragment(`${place.sql} = 1`, place.bind),
293
- any([
294
- fragment(`${kind.sql} ${after} ${held.klass}`, kind.bind),
295
- all([
296
- fragment(`${kind.sql} = ${held.klass}`, kind.bind),
297
- fragment(`${scalar.sql} ${after} ?${collation}`, [...scalar.bind, held.scalar]),
298
- ]),
299
- ]),
300
- ]),
301
- ]),
302
- equal: all([
303
- fragment(`${place.sql} = 1 AND ${kind.sql} = ${held.klass}`, [...place.bind, ...kind.bind]),
304
- fragment(`${scalar.sql} = ?${collation}`, [...scalar.bind, held.scalar]),
305
- ]),
306
- };
307
- }
308
- function following(parts, tie) {
309
- let condition = tie;
310
- for (const part of [...parts].reverse()) {
311
- condition = any([part.beyond, all([part.equal, condition])]);
312
- }
313
- return condition;
314
- }
315
- function keyTerm(bound, key, ordering, index) {
119
+ function keyTerm(key, ordering, index) {
316
120
  if (key.of === 'field') {
317
121
  const alias = `o${index}`;
318
122
  return {
319
- 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
+ },
320
129
  join: joined([
321
130
  fragment(`LEFT JOIN replica_values ${alias} ON ${alias}.doc = d.id AND `),
322
- grouped(pointed(alias, key.field, 'exact')),
131
+ grouped(sql.placed(alias, key.field, 'exact')),
323
132
  fragment(` AND ${alias}.item = -1 AND ${alias}.kind NOT IN ('object', 'undefined')`),
324
133
  ], ''),
325
134
  };
326
135
  }
327
136
  const expression = key.of === 'room'
328
- ? located(bound, 'd')
137
+ ? located('d')
329
138
  : key.of === 'path'
330
139
  ? fragment('d.path')
331
140
  : key.of === 'type'
@@ -351,13 +160,20 @@ function decoded(kind, value) {
351
160
  return JSON.parse(value);
352
161
  return null;
353
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
+ }
354
167
  function decodedKey(key, row, index) {
355
168
  if (key.of === 'field') {
356
169
  const kind = row[`k${index}`];
357
- return kind === null || kind === undefined ? undefined : decoded(kind, row[`v${index}`]);
170
+ return kind === null || kind === undefined ? undefined : sortedValue(row, index);
358
171
  }
359
172
  return row[`v${index}`] ?? null;
360
173
  }
174
+ function instantOf(term) {
175
+ return term.instant ?? fragment('NULL');
176
+ }
361
177
  const registered = new WeakMap();
362
178
  function hashed(text) {
363
179
  let hash = 0x811c9dc5;
@@ -407,9 +223,6 @@ function collating(connection) {
407
223
  return name;
408
224
  };
409
225
  }
410
- function bodied(shape) {
411
- return shape.sections !== null || shape.preamble || shape.markdown;
412
- }
413
226
  function masked(pointers) {
414
227
  const root = { whole: false, members: new Map() };
415
228
  for (const pointer of pointers) {
@@ -500,7 +313,7 @@ function documentOf(shape, room, row, linked) {
500
313
  ? {}
501
314
  : { sections: sectionsOf(body.sections, shape.sections) }),
502
315
  ...(shape.preamble && body !== null ? { preamble: body.preamble } : {}),
503
- ...(shape.markdown && body !== null ? { markdown: body.content } : {}),
316
+ ...(shape.markdown ? { markdown: row.content } : {}),
504
317
  ...(shape.backlinks ? { backlinks: linked.get(documentKey(room, path)) ?? [] } : {}),
505
318
  ...(shape.change
506
319
  ? {
@@ -534,10 +347,10 @@ function backlinksOf(connection, bound, rows) {
534
347
  return linked;
535
348
  }
536
349
  function documents(connection, bound, plan, shape, budget) {
537
- 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));
538
351
  const filter = filtered(bound, plan);
539
352
  const cursor = plan.after?.values ?? null;
540
- const room = located(bound, 'd');
353
+ const room = located('d');
541
354
  const tie = cursor === null
542
355
  ? null
543
356
  : (() => {
@@ -553,19 +366,20 @@ function documents(connection, bound, plan, shape, budget) {
553
366
  })();
554
367
  const after = cursor === null || tie === null
555
368
  ? null
556
- : 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);
557
370
  const selected = joined([
558
371
  fragment('SELECT d.id, d.key, d.path, d.type, d.binary, d.revision, d.fields, d.change, d.file_id'),
559
372
  ...terms.map(({ term }, index) => joined([
560
373
  fragment(`, ${term.kind.sql} AS k${index}`, term.kind.bind),
561
374
  fragment(`, ${term.value.sql} AS v${index}`, term.value.bind),
375
+ fragment(`, ${instantOf(term).sql} AS i${index}`, instantOf(term).bind),
562
376
  ], '')),
563
377
  fragment(' FROM replica_entries d'),
564
378
  ...terms.flatMap(({ join }) => (join === null ? [] : [fragment(' '), join])),
565
379
  fragment(' WHERE '),
566
380
  after === null ? filter : all([filter, after]),
567
381
  fragment(' ORDER BY '),
568
- 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')], ', '),
569
383
  ...(plan.limit === null ? [] : [fragment(' LIMIT ?', [plan.limit + 1])]),
570
384
  ], '');
571
385
  const rows = connection.rows(selected.sql, selected.bind);
@@ -604,7 +418,7 @@ function withContent(connection, rows, budget) {
604
418
  return rows;
605
419
  const ids = rows.map((row) => row.id);
606
420
  const sizes = new Map(connection
607
- .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)
608
422
  .map((row) => [row.id, Number(row.size ?? 0)]));
609
423
  let cut = rows.length;
610
424
  let spent = 0;
@@ -619,34 +433,30 @@ function withContent(connection, rows, budget) {
619
433
  const kept = rows.slice(0, cut);
620
434
  const read = kept.map((row) => row.id);
621
435
  const held = new Map(connection
622
- .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)
623
437
  .map((row) => [row.id, row.content]));
624
438
  return kept.map((row) => ({ ...row, content: held.get(row.id) ?? null }));
625
439
  }
626
440
  function storedFolders(bound, plan) {
627
- 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)
628
444
  return null;
629
445
  const whole = bound.rooms.filter(({ locator }) => bound.reach === null || bound.reach.rooms.includes(locator));
630
446
  if (whole.length === 0)
631
447
  return null;
632
448
  const keys = whole.map(({ key }) => key);
633
449
  const prefix = plan.root.pathPrefix;
634
- const fenced = (bound.reach?.areas ?? []).flatMap((area) => {
450
+ const outside = (bound.reach?.areas ?? []).flatMap((area) => {
635
451
  const key = bound.keys.get(area.room);
636
- return key === undefined
637
- ? []
638
- : [
639
- fragment('NOT (f.key = ? AND (f.path = ? OR (f.path >= ? AND f.path < ?)))', [
640
- key,
641
- area.path,
642
- `${area.path}/`,
643
- `${area.path}0`,
644
- ]),
645
- ];
452
+ if (key === undefined)
453
+ return [];
454
+ const fence = fenced('f', key, area, true);
455
+ return [fragment(`NOT ${grouped(fence).sql}`, fence.bind)];
646
456
  });
647
457
  return all([
648
- fragment(`f.key IN ${list(keys).sql} AND f.layer = 0 AND f.present = 1`, keys),
649
- ...fenced,
458
+ fragment(`f.key IN ${listed(keys).sql} AND f.layer = 0 AND f.present = 1`, keys),
459
+ ...outside,
650
460
  ...(prefix === null || prefix === ''
651
461
  ? []
652
462
  : [
@@ -661,7 +471,7 @@ function storedFolders(bound, plan) {
661
471
  }
662
472
  function groups(connection, bound, plan, shape) {
663
473
  const keyed = shape.groupBy.map((key, index) => {
664
- const { term, join } = keyTerm(bound, key, GROUP_ORDER, index);
474
+ const { term, join } = keyTerm(key, GROUP_ORDER, index);
665
475
  const kind = fragment(`COALESCE(${term.kind.sql}, 'null')`, term.kind.bind);
666
476
  return { term: { ...term, kind }, join };
667
477
  });
@@ -671,6 +481,7 @@ function groups(connection, bound, plan, shape) {
671
481
  joined(keyed.map(({ term }, index) => joined([
672
482
  fragment(`${term.kind.sql} AS k${index}`, term.kind.bind),
673
483
  fragment(`, ${term.value.sql} AS v${index}`, term.value.bind),
484
+ fragment(`, ${instantOf(term).sql} AS i${index}`, instantOf(term).bind),
674
485
  ], '')), ', '),
675
486
  fragment(', 1 AS n FROM replica_entries d'),
676
487
  ...keyed.flatMap(({ join }) => (join === null ? [] : [fragment(' '), join])),
@@ -684,10 +495,15 @@ function groups(connection, bound, plan, shape) {
684
495
  ? inner
685
496
  : joined([
686
497
  inner,
687
- 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 "),
688
499
  folders,
689
500
  ], '');
690
- 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(', ');
691
507
  const counted = {
692
508
  ordering: { ...GROUP_ORDER },
693
509
  kind: fragment("'number'"),
@@ -700,7 +516,7 @@ function groups(connection, bound, plan, shape) {
700
516
  });
701
517
  const ordered = shape.orderBy.map(({ by, direction }) => by === 'count'
702
518
  ? { ...counted, ordering: { ...GROUP_ORDER, direction } }
703
- : outer(by, { ...GROUP_ORDER, direction }));
519
+ : { ...outer(by, { ...GROUP_ORDER, direction }), instant: fragment(`i${by}`) });
704
520
  const ties = keyed.flatMap((_, index) => [
705
521
  { ...outer(index, GROUP_TIES), total: true },
706
522
  { ordering: GROUP_TIES, kind: fragment("'string'"), value: fragment(`k${index}`) },
@@ -710,19 +526,21 @@ function groups(connection, bound, plan, shape) {
710
526
  ? null
711
527
  : cursor[shape.orderBy.length].flatMap((value) => [
712
528
  value,
713
- kindOf(value ?? null),
529
+ heldKind(value ?? null),
714
530
  ]);
715
531
  const after = cursor === null || tieValues === null
716
532
  ? null
717
- : 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));
718
- 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}`)], '');
719
537
  const windowed = joined([
720
538
  fragment('SELECT * FROM ('),
721
539
  grouping,
722
540
  fragment(')'),
723
541
  ...(after === null ? [] : [fragment(' WHERE '), after]),
724
542
  fragment(' ORDER BY '),
725
- joined([...ordered, ...ties].map((term) => sorted(bound, term)), ', '),
543
+ joined([...ordered, ...ties].map((term) => sql.sorted(bound.collate, term)), ', '),
726
544
  ...(plan.limit === null ? [] : [fragment(' LIMIT ?', [plan.limit + 1])]),
727
545
  ], '');
728
546
  const rows = connection.rows(windowed.sql, windowed.bind);
@@ -735,7 +553,7 @@ function groups(connection, bound, plan, shape) {
735
553
  : last === undefined
736
554
  ? (plan.after?.text ?? FROM_START)
737
555
  : cursorOf([
738
- ...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)),
739
557
  keyOf(last),
740
558
  ]);
741
559
  const total = plan.total
@@ -755,9 +573,6 @@ function boundOf(connection, plan, rooms) {
755
573
  collate: collating(connection),
756
574
  };
757
575
  }
758
- export function selected(connection, plan, rooms) {
759
- return filtered(boundOf(connection, plan, rooms), plan);
760
- }
761
576
  export function answerQuery(connection, plan, rooms, budget = null) {
762
577
  const bound = boundOf(connection, plan, rooms);
763
578
  if (rooms.length === 0) {