@agentshouse/mdruntime 0.1.0-cd50a4607cb7d → 0.1.0-cd574a610e9c8
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.
- package/README.md +24 -19
- package/dist/acceptance.d.ts +3 -0
- package/dist/acceptance.d.ts.map +1 -0
- package/dist/acceptance.js +299 -0
- package/dist/act-lanes.d.ts +30 -0
- package/dist/act-lanes.d.ts.map +1 -0
- package/dist/act-lanes.js +225 -0
- package/dist/actions.d.ts +26 -0
- package/dist/actions.d.ts.map +1 -0
- package/dist/actions.js +302 -0
- package/dist/adapter.d.ts +226 -117
- package/dist/adapter.d.ts.map +1 -1
- package/dist/attached-model.d.ts +6 -10
- package/dist/attached-model.d.ts.map +1 -1
- package/dist/attached-model.js +35 -41
- package/dist/authored.d.ts +88 -0
- package/dist/authored.d.ts.map +1 -0
- package/dist/authored.js +345 -0
- package/dist/binary.d.ts +0 -5
- package/dist/binary.d.ts.map +1 -1
- package/dist/binary.js +1 -1
- package/dist/bootstrap.d.ts +31 -11
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +309 -109
- package/dist/cell.d.ts +6 -0
- package/dist/cell.d.ts.map +1 -0
- package/dist/cell.js +9 -0
- package/dist/chain.d.ts +12 -0
- package/dist/chain.d.ts.map +1 -0
- package/dist/chain.js +64 -0
- package/dist/change-set.d.ts +13 -16
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +17 -88
- package/dist/change.d.ts +36 -1
- package/dist/change.d.ts.map +1 -1
- package/dist/change.js +17 -3
- package/dist/chat-types.d.ts +3 -0
- package/dist/chat-types.d.ts.map +1 -0
- package/dist/chat-types.js +11 -0
- package/dist/client.d.ts +45 -25
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +132 -27
- package/dist/collection.d.ts +32 -21
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +53 -288
- package/dist/column.d.ts +5 -0
- package/dist/column.d.ts.map +1 -0
- package/dist/column.js +9 -0
- package/dist/compile.d.ts +18 -0
- package/dist/compile.d.ts.map +1 -0
- package/dist/compile.js +723 -0
- package/dist/connection.d.ts +7 -118
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +111 -2975
- package/dist/context.d.ts +2 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/{chat/context.js → context.js} +2 -5
- package/dist/conversation-live.d.ts +150 -0
- package/dist/conversation-live.d.ts.map +1 -0
- package/dist/conversation-live.js +259 -0
- package/dist/conversation-row.d.ts +40 -0
- package/dist/conversation-row.d.ts.map +1 -0
- package/dist/conversation-row.js +66 -0
- package/dist/conversation.d.ts +85 -48
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +1095 -688
- package/dist/declaration.d.ts +10 -0
- package/dist/declaration.d.ts.map +1 -0
- package/dist/declaration.js +24 -0
- package/dist/digest.d.ts +7 -0
- package/dist/digest.d.ts.map +1 -0
- package/dist/digest.js +113 -0
- package/dist/discovery.d.ts +10 -0
- package/dist/discovery.d.ts.map +1 -0
- package/dist/discovery.js +150 -0
- package/dist/durability.d.ts +23 -1
- package/dist/durability.d.ts.map +1 -1
- package/dist/durability.js +9 -0
- package/dist/entry.d.ts +5 -2
- package/dist/entry.d.ts.map +1 -1
- package/dist/entry.js +33 -28
- package/dist/expectation.d.ts +26 -13
- package/dist/expectation.d.ts.map +1 -1
- package/dist/expectation.js +72 -19
- package/dist/failure.d.ts +3 -0
- package/dist/failure.d.ts.map +1 -1
- package/dist/failure.js +6 -0
- package/dist/figures.d.ts +49 -0
- package/dist/figures.d.ts.map +1 -0
- package/dist/figures.js +199 -0
- package/dist/file.d.ts +2 -19
- package/dist/file.d.ts.map +1 -1
- package/dist/file.js +8 -122
- package/dist/holdings.d.ts +29 -0
- package/dist/holdings.d.ts.map +1 -0
- package/dist/holdings.js +95 -0
- package/dist/host.d.ts +27 -4
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +177 -27
- package/dist/identity.d.ts +0 -2
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +0 -6
- package/dist/index.d.ts +41 -14
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -3
- package/dist/install.d.ts +60 -0
- package/dist/install.d.ts.map +1 -0
- package/dist/install.js +537 -0
- package/dist/lifecycle.d.ts +7 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/lifecycle.js +282 -0
- package/dist/listing.d.ts +52 -0
- package/dist/listing.d.ts.map +1 -0
- package/dist/listing.js +152 -0
- package/dist/live.d.ts +25 -0
- package/dist/live.d.ts.map +1 -0
- package/dist/live.js +206 -0
- package/dist/loaded.d.ts +5 -0
- package/dist/loaded.d.ts.map +1 -0
- package/dist/loaded.js +40 -0
- package/dist/locks.d.ts +7 -0
- package/dist/locks.d.ts.map +1 -0
- package/dist/locks.js +32 -0
- package/dist/lost.d.ts +5 -0
- package/dist/lost.d.ts.map +1 -0
- package/dist/lost.js +49 -0
- package/dist/media.d.ts +13 -0
- package/dist/media.d.ts.map +1 -0
- package/dist/media.js +269 -0
- package/dist/memory.d.ts +25 -7
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +320 -110
- package/dist/messages.d.ts +58 -0
- package/dist/messages.d.ts.map +1 -0
- package/dist/messages.js +160 -0
- package/dist/model.d.ts +11 -13
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +7 -24
- package/dist/models.d.ts +10 -0
- package/dist/models.d.ts.map +1 -0
- package/dist/models.js +153 -0
- package/dist/name.d.ts +4 -0
- package/dist/name.d.ts.map +1 -0
- package/dist/name.js +122 -0
- package/dist/observation.d.ts +40 -10
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +233 -39
- package/dist/opened.d.ts +22 -0
- package/dist/opened.d.ts.map +1 -0
- package/dist/opened.js +107 -0
- package/dist/opening.d.ts +4 -0
- package/dist/opening.d.ts.map +1 -0
- package/dist/opening.js +53 -0
- package/dist/outcome.d.ts +62 -9
- package/dist/outcome.d.ts.map +1 -1
- package/dist/outcome.js +72 -0
- package/dist/overlay.d.ts +9 -6
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +129 -166
- package/dist/page-metadata.d.ts +8 -0
- package/dist/page-metadata.d.ts.map +1 -0
- package/dist/page-metadata.js +19 -0
- package/dist/parts.d.ts +4 -0
- package/dist/parts.d.ts.map +1 -0
- package/dist/parts.js +13 -0
- package/dist/paths.d.ts +2 -0
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +26 -0
- package/dist/preparation.d.ts +41 -0
- package/dist/preparation.d.ts.map +1 -0
- package/dist/preparation.js +314 -0
- package/dist/presence.d.ts +58 -0
- package/dist/presence.d.ts.map +1 -0
- package/dist/presence.js +231 -0
- package/dist/queries.d.ts +16 -0
- package/dist/queries.d.ts.map +1 -0
- package/dist/queries.js +308 -0
- package/dist/query.d.ts +27 -186
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +3 -464
- package/dist/{chat/questions.d.ts → questions.d.ts} +6 -7
- package/dist/questions.d.ts.map +1 -0
- package/dist/{chat/questions.js → questions.js} +12 -7
- package/dist/queue.d.ts +40 -0
- package/dist/queue.d.ts.map +1 -0
- package/dist/queue.js +480 -0
- package/dist/reach.d.ts +20 -7
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +360 -98
- package/dist/react.d.ts +22 -5
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +40 -18
- package/dist/{chat/read.d.ts → read.d.ts} +2 -0
- package/dist/read.d.ts.map +1 -0
- package/dist/{chat/read.js → read.js} +4 -0
- package/dist/readiness.d.ts +55 -0
- package/dist/readiness.d.ts.map +1 -0
- package/dist/readiness.js +25 -0
- package/dist/reads.d.ts +39 -0
- package/dist/reads.d.ts.map +1 -0
- package/dist/reads.js +178 -0
- package/dist/recovery.d.ts +5 -2
- package/dist/recovery.d.ts.map +1 -1
- package/dist/recovery.js +65 -8
- package/dist/refresh.d.ts +17 -14
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +145 -54
- package/dist/refusal.d.ts +5 -0
- package/dist/refusal.d.ts.map +1 -0
- package/dist/refusal.js +55 -0
- package/dist/replica.d.ts +50 -33
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +340 -191
- package/dist/retried.d.ts +30 -0
- package/dist/retried.d.ts.map +1 -0
- package/dist/retried.js +72 -0
- package/dist/room-draft.d.ts +66 -12
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +416 -122
- package/dist/room-drafts.d.ts +24 -0
- package/dist/room-drafts.d.ts.map +1 -0
- package/dist/room-drafts.js +220 -0
- package/dist/room-list.d.ts +10 -0
- package/dist/room-list.d.ts.map +1 -0
- package/dist/room-list.js +45 -0
- package/dist/rows.d.ts +32 -29
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +60 -118
- package/dist/runtime.d.ts +34 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +1 -0
- package/dist/save.d.ts +43 -48
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +751 -303
- package/dist/saves.d.ts +34 -0
- package/dist/saves.d.ts.map +1 -0
- package/dist/saves.js +437 -0
- package/dist/search.d.ts +15 -77
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +10 -186
- package/dist/seed.d.ts +25 -0
- package/dist/seed.d.ts.map +1 -0
- package/dist/seed.js +145 -0
- package/dist/shared.d.ts +6 -7
- package/dist/shared.d.ts.map +1 -1
- package/dist/shared.js +75 -104
- package/dist/shown.d.ts +33 -0
- package/dist/shown.d.ts.map +1 -0
- package/dist/shown.js +309 -0
- package/dist/socket/admission.d.ts +7 -0
- package/dist/socket/admission.d.ts.map +1 -0
- package/dist/socket/admission.js +39 -0
- package/dist/socket/answers.d.ts +6 -0
- package/dist/socket/answers.d.ts.map +1 -0
- package/dist/socket/answers.js +20 -0
- package/dist/socket/lanes.d.ts +28 -0
- package/dist/socket/lanes.d.ts.map +1 -0
- package/dist/socket/lanes.js +103 -0
- package/dist/socket/reads.d.ts +10 -0
- package/dist/socket/reads.d.ts.map +1 -0
- package/dist/socket/reads.js +31 -0
- package/dist/socket/session.d.ts +38 -0
- package/dist/socket/session.d.ts.map +1 -0
- package/dist/socket/session.js +23 -0
- package/dist/socket.d.ts +67 -0
- package/dist/socket.d.ts.map +1 -0
- package/dist/socket.js +164 -0
- package/dist/source.d.ts +59 -0
- package/dist/source.d.ts.map +1 -0
- package/dist/source.js +93 -0
- package/dist/status.d.ts +4 -5
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +3 -7
- package/dist/store/installs.d.ts +30 -0
- package/dist/store/installs.d.ts.map +1 -0
- package/dist/store/installs.js +40 -0
- package/dist/store/readiness.d.ts +11 -0
- package/dist/store/readiness.d.ts.map +1 -0
- package/dist/store/readiness.js +68 -0
- package/dist/store/records.d.ts +223 -0
- package/dist/store/records.d.ts.map +1 -0
- package/dist/store/records.js +465 -0
- package/dist/store/room-held.d.ts +8 -0
- package/dist/store/room-held.d.ts.map +1 -0
- package/dist/store/room-held.js +17 -0
- package/dist/store/rooms-shown.d.ts +8 -0
- package/dist/store/rooms-shown.d.ts.map +1 -0
- package/dist/store/rooms-shown.js +13 -0
- package/dist/store/space.d.ts +11 -0
- package/dist/store/space.d.ts.map +1 -0
- package/dist/store/space.js +41 -0
- package/dist/store/watchers.d.ts +17 -0
- package/dist/store/watchers.d.ts.map +1 -0
- package/dist/store/watchers.js +56 -0
- package/dist/store/writes.d.ts +24 -0
- package/dist/store/writes.d.ts.map +1 -0
- package/dist/store/writes.js +125 -0
- package/dist/store.d.ts +114 -141
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +163 -687
- package/dist/stored.d.ts +167 -0
- package/dist/stored.d.ts.map +1 -0
- package/dist/stored.js +700 -0
- package/dist/surface.d.ts +90 -0
- package/dist/surface.d.ts.map +1 -0
- package/dist/surface.js +225 -0
- package/dist/sync.d.ts +19 -0
- package/dist/sync.d.ts.map +1 -0
- package/dist/sync.js +536 -0
- package/dist/tables.d.ts +376 -0
- package/dist/tables.d.ts.map +1 -0
- package/dist/tables.js +499 -0
- package/dist/tallies.d.ts +18 -0
- package/dist/tallies.d.ts.map +1 -0
- package/dist/tallies.js +128 -0
- package/dist/transfer.d.ts +0 -5
- package/dist/transfer.d.ts.map +1 -1
- package/dist/turn.d.ts +5 -3
- package/dist/turn.d.ts.map +1 -1
- package/dist/turn.js +51 -21
- package/dist/validation.d.ts +29 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +98 -0
- package/dist/windows.d.ts +11 -0
- package/dist/windows.d.ts.map +1 -0
- package/dist/windows.js +16 -0
- package/dist/wire.d.ts +230 -36
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +77 -33
- package/package.json +19 -19
- package/AGENTS.md +0 -296
- package/dist/attempt.d.ts +0 -16
- package/dist/attempt.d.ts.map +0 -1
- package/dist/attempt.js +0 -70
- package/dist/chat/acts.d.ts +0 -33
- package/dist/chat/acts.d.ts.map +0 -1
- package/dist/chat/acts.js +0 -36
- package/dist/chat/context.d.ts +0 -3
- package/dist/chat/context.d.ts.map +0 -1
- package/dist/chat/input.d.ts +0 -9
- package/dist/chat/input.d.ts.map +0 -1
- package/dist/chat/input.js +0 -99
- package/dist/chat/kit.d.ts +0 -60
- package/dist/chat/kit.d.ts.map +0 -1
- package/dist/chat/kit.js +0 -123
- package/dist/chat/lasting.d.ts +0 -6
- package/dist/chat/lasting.d.ts.map +0 -1
- package/dist/chat/lasting.js +0 -19
- package/dist/chat/list.d.ts +0 -26
- package/dist/chat/list.d.ts.map +0 -1
- package/dist/chat/list.js +0 -62
- package/dist/chat/markdown.d.ts +0 -20
- package/dist/chat/markdown.d.ts.map +0 -1
- package/dist/chat/markdown.js +0 -188
- package/dist/chat/message.d.ts +0 -6
- package/dist/chat/message.d.ts.map +0 -1
- package/dist/chat/message.js +0 -192
- package/dist/chat/messages.d.ts +0 -21
- package/dist/chat/messages.d.ts.map +0 -1
- package/dist/chat/messages.js +0 -151
- package/dist/chat/name.d.ts +0 -6
- package/dist/chat/name.d.ts.map +0 -1
- package/dist/chat/name.js +0 -37
- package/dist/chat/opened.d.ts +0 -11
- package/dist/chat/opened.d.ts.map +0 -1
- package/dist/chat/opened.js +0 -17
- package/dist/chat/overlay.d.ts +0 -31
- package/dist/chat/overlay.d.ts.map +0 -1
- package/dist/chat/overlay.js +0 -97
- package/dist/chat/parts.d.ts +0 -57
- package/dist/chat/parts.d.ts.map +0 -1
- package/dist/chat/parts.js +0 -119
- package/dist/chat/process.d.ts +0 -20
- package/dist/chat/process.d.ts.map +0 -1
- package/dist/chat/process.js +0 -32
- package/dist/chat/provider.d.ts +0 -63
- package/dist/chat/provider.d.ts.map +0 -1
- package/dist/chat/provider.js +0 -620
- package/dist/chat/question.d.ts +0 -9
- package/dist/chat/question.d.ts.map +0 -1
- package/dist/chat/question.js +0 -80
- package/dist/chat/questions.d.ts.map +0 -1
- package/dist/chat/read.d.ts.map +0 -1
- package/dist/chat/ready.d.ts +0 -33
- package/dist/chat/ready.d.ts.map +0 -1
- package/dist/chat/ready.js +0 -91
- package/dist/chat/style.d.ts +0 -4
- package/dist/chat/style.d.ts.map +0 -1
- package/dist/chat/style.js +0 -302
- package/dist/chat.d.ts +0 -14
- package/dist/chat.d.ts.map +0 -1
- package/dist/chat.js +0 -8
- package/dist/fence.d.ts +0 -6
- package/dist/fence.d.ts.map +0 -1
- package/dist/fence.js +0 -10
- package/dist/log.d.ts +0 -5
- package/dist/log.d.ts.map +0 -1
- package/dist/log.js +0 -14
- package/dist/read-side.d.ts +0 -25
- package/dist/read-side.d.ts.map +0 -1
- package/dist/read-side.js +0 -639
package/dist/compile.js
ADDED
|
@@ -0,0 +1,723 @@
|
|
|
1
|
+
import { ARRAY_INDEX, bodied, byCodePoint, compared, cursorOf, heldKind, parse, pointerSegments, sql, } from '@agentshouse/mdmodel';
|
|
2
|
+
import { binaryRecord } from './binary.js';
|
|
3
|
+
import { definitionsAt, UNTYPED } from './collection.js';
|
|
4
|
+
import { documentKey, pathOfKey, roomOfKey } from './rows.js';
|
|
5
|
+
import { FROM_START, MAXIMUM_WINDOW } from './windows.js';
|
|
6
|
+
const { all, any, exists, fragment, grouped, joined, listed } = sql;
|
|
7
|
+
export const VISIBLE = (alias) => `${alias}.visible = 1 AND ${alias}.present = 1`;
|
|
8
|
+
function located(alias) {
|
|
9
|
+
return fragment(`(SELECT k.locator FROM replica_keys k WHERE k.key = ${alias}.key)`);
|
|
10
|
+
}
|
|
11
|
+
export function within(alias, path) {
|
|
12
|
+
return fragment(`(${alias}.path = ? OR (${alias}.path >= ? AND ${alias}.path < ?))`, [
|
|
13
|
+
path,
|
|
14
|
+
`${path}/`,
|
|
15
|
+
`${path}0`,
|
|
16
|
+
]);
|
|
17
|
+
}
|
|
18
|
+
function ancestors(area) {
|
|
19
|
+
const depth = area.path.split('/').length;
|
|
20
|
+
return area.open.flatMap((open) => {
|
|
21
|
+
const segments = open.split('/');
|
|
22
|
+
return segments.slice(depth - 1, -1).map((_, at) => segments.slice(0, depth + at).join('/'));
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
function fenced(alias, key, area, folders) {
|
|
26
|
+
const opened = any([
|
|
27
|
+
...area.open.map((open) => within(alias, open)),
|
|
28
|
+
...(folders ? ancestors(area).map((path) => fragment(`${alias}.path = ?`, [path])) : []),
|
|
29
|
+
]);
|
|
30
|
+
return all([
|
|
31
|
+
fragment(`${alias}.key = ?`, [key]),
|
|
32
|
+
within(alias, area.path),
|
|
33
|
+
fragment(`NOT ${grouped(opened).sql}`, opened.bind),
|
|
34
|
+
]);
|
|
35
|
+
}
|
|
36
|
+
function reached(bound, alias) {
|
|
37
|
+
const reach = bound.reach;
|
|
38
|
+
if (reach === null)
|
|
39
|
+
return fragment('1');
|
|
40
|
+
const rooms = reach.rooms.flatMap((locator) => {
|
|
41
|
+
const key = bound.keys.get(locator);
|
|
42
|
+
return key === undefined ? [] : [key];
|
|
43
|
+
});
|
|
44
|
+
const documents = reach.documents.flatMap((held) => {
|
|
45
|
+
const key = bound.keys.get(roomOfKey(held));
|
|
46
|
+
return key === undefined ? [] : [[key, pathOfKey(held)]];
|
|
47
|
+
});
|
|
48
|
+
const outside = any(reach.areas.flatMap((area) => {
|
|
49
|
+
const key = bound.keys.get(area.room);
|
|
50
|
+
return key === undefined ? [] : [fenced(alias, key, area, false)];
|
|
51
|
+
}));
|
|
52
|
+
return all([
|
|
53
|
+
any([
|
|
54
|
+
...(rooms.length === 0 ? [] : [fragment(`${alias}.key IN ${listed(rooms).sql}`, rooms)]),
|
|
55
|
+
...(documents.length === 0
|
|
56
|
+
? []
|
|
57
|
+
: [
|
|
58
|
+
fragment(`EXISTS (SELECT 1 FROM json_each(?) j WHERE j.value ->> 0 = ${alias}.key AND j.value ->> 1 = ${alias}.path)`, [JSON.stringify(documents)]),
|
|
59
|
+
]),
|
|
60
|
+
]),
|
|
61
|
+
...(reach.areas.length === 0 ? [] : [fragment(`NOT ${grouped(outside).sql}`, outside.bind)]),
|
|
62
|
+
]);
|
|
63
|
+
}
|
|
64
|
+
function related(bound, plan) {
|
|
65
|
+
const parts = [];
|
|
66
|
+
if (plan.hop !== null) {
|
|
67
|
+
parts.push(exists(joined([
|
|
68
|
+
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`),
|
|
69
|
+
grouped(sql.placed('l', plan.hop.field, 'link')),
|
|
70
|
+
fragment('AND'),
|
|
71
|
+
grouped(all([sql.scoped('m', plan.hop), reached(bound, 'm')])),
|
|
72
|
+
], ' ')));
|
|
73
|
+
}
|
|
74
|
+
if (plan.backlinks !== null) {
|
|
75
|
+
const field = plan.backlinks.field;
|
|
76
|
+
parts.push(exists(joined([
|
|
77
|
+
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`),
|
|
78
|
+
...(field === null ? [] : [grouped(sql.placed('l', field, 'link')), fragment('AND')]),
|
|
79
|
+
grouped(all([sql.scoped('s', plan.backlinks), reached(bound, 's')])),
|
|
80
|
+
], ' ')));
|
|
81
|
+
}
|
|
82
|
+
return parts;
|
|
83
|
+
}
|
|
84
|
+
function filtered(bound, plan) {
|
|
85
|
+
const keys = bound.rooms.map(({ key }) => key);
|
|
86
|
+
return all([
|
|
87
|
+
fragment(`d.key IN ${listed(keys).sql} AND ${VISIBLE('d')}`, keys),
|
|
88
|
+
reached(bound, 'd'),
|
|
89
|
+
sql.tests('d', plan),
|
|
90
|
+
...related(bound, plan),
|
|
91
|
+
]);
|
|
92
|
+
}
|
|
93
|
+
function orderingOf(options) {
|
|
94
|
+
const stringSort = options.stringSort ?? 'locale';
|
|
95
|
+
const collated = stringSort === 'locale';
|
|
96
|
+
return {
|
|
97
|
+
direction: options.direction,
|
|
98
|
+
nulls: options.nulls ?? 'first',
|
|
99
|
+
stringSort,
|
|
100
|
+
locale: collated ? options.locale : undefined,
|
|
101
|
+
localeOptions: collated
|
|
102
|
+
? options.localeOptions
|
|
103
|
+
: undefined,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
const GROUP_ORDER = {
|
|
107
|
+
direction: 'asc',
|
|
108
|
+
nulls: 'first',
|
|
109
|
+
stringSort: 'locale',
|
|
110
|
+
locale: undefined,
|
|
111
|
+
localeOptions: undefined,
|
|
112
|
+
};
|
|
113
|
+
const GROUP_TIES = {
|
|
114
|
+
direction: 'asc',
|
|
115
|
+
nulls: 'last',
|
|
116
|
+
stringSort: 'lexical',
|
|
117
|
+
locale: undefined,
|
|
118
|
+
localeOptions: undefined,
|
|
119
|
+
};
|
|
120
|
+
const GREATEST = { ...GROUP_ORDER, direction: 'desc', nulls: 'last' };
|
|
121
|
+
const TIMES = {
|
|
122
|
+
created: { value: 'd.created', instant: 'd.created_instant' },
|
|
123
|
+
updated: { value: "(d.change ->> '$.time')", instant: 'd.changed_instant' },
|
|
124
|
+
};
|
|
125
|
+
function subfolderOf(column, prefix) {
|
|
126
|
+
const root = prefix ?? '';
|
|
127
|
+
const base = root === '' ? '' : `${root}/`;
|
|
128
|
+
const rest = `substr(${column}, length(?) + 1)`;
|
|
129
|
+
return fragment(`(CASE WHEN ${column} = ? THEN NULL WHEN instr(${rest}, '/') = 0 THEN ${column} ELSE ? || substr(${rest}, 1, instr(${rest}, '/') - 1) END)`, [root, base, base, base, base]);
|
|
130
|
+
}
|
|
131
|
+
function keyTerm(key, ordering, index, prefix) {
|
|
132
|
+
if (key.of === 'field') {
|
|
133
|
+
const alias = `o${index}`;
|
|
134
|
+
return {
|
|
135
|
+
term: {
|
|
136
|
+
ordering,
|
|
137
|
+
kind: fragment(`${alias}.kind`),
|
|
138
|
+
value: fragment(`${alias}.value`),
|
|
139
|
+
instant: fragment(`${alias}.instant`),
|
|
140
|
+
},
|
|
141
|
+
join: joined([
|
|
142
|
+
fragment(`LEFT JOIN replica_values ${alias} ON ${alias}.doc = d.id AND `),
|
|
143
|
+
grouped(sql.placed(alias, key.field, 'exact')),
|
|
144
|
+
fragment(` AND ${alias}.item = -1 AND ${alias}.kind NOT IN ('object', 'undefined')`),
|
|
145
|
+
], ''),
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
if (key.of === 'size') {
|
|
149
|
+
return {
|
|
150
|
+
term: {
|
|
151
|
+
ordering,
|
|
152
|
+
kind: fragment("(CASE WHEN d.bytes IS NULL THEN 'null' ELSE 'number' END)"),
|
|
153
|
+
value: fragment('d.bytes'),
|
|
154
|
+
},
|
|
155
|
+
join: null,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
if (key.of === 'created' || key.of === 'updated') {
|
|
159
|
+
const { value, instant } = TIMES[key.of];
|
|
160
|
+
return {
|
|
161
|
+
term: {
|
|
162
|
+
ordering,
|
|
163
|
+
kind: fragment(`(CASE WHEN ${value} IS NULL THEN 'null' ELSE 'string' END)`),
|
|
164
|
+
value: fragment(value),
|
|
165
|
+
instant: fragment(instant),
|
|
166
|
+
},
|
|
167
|
+
join: null,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
const expression = key.of === 'room'
|
|
171
|
+
? located('d')
|
|
172
|
+
: key.of === 'path'
|
|
173
|
+
? fragment('d.path')
|
|
174
|
+
: key.of === 'type'
|
|
175
|
+
? fragment('d.type')
|
|
176
|
+
: key.of === 'subfolder'
|
|
177
|
+
? subfolderOf('d.folder', prefix)
|
|
178
|
+
: fragment('d.folder');
|
|
179
|
+
return {
|
|
180
|
+
term: {
|
|
181
|
+
ordering,
|
|
182
|
+
kind: fragment(`(CASE WHEN ${expression.sql} IS NULL THEN 'null' ELSE 'string' END)`, expression.bind),
|
|
183
|
+
value: expression,
|
|
184
|
+
},
|
|
185
|
+
join: null,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function decoded(kind, value) {
|
|
189
|
+
if (kind === 'string')
|
|
190
|
+
return value;
|
|
191
|
+
if (kind === 'number')
|
|
192
|
+
return typeof value === 'bigint' ? Number(value) : value;
|
|
193
|
+
if (kind === 'boolean')
|
|
194
|
+
return Number(value) === 1;
|
|
195
|
+
if (kind === 'array')
|
|
196
|
+
return JSON.parse(value);
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
function sortedOf(kind, value, instant) {
|
|
200
|
+
return sql.sortValue(kind, decoded(kind, value), instant);
|
|
201
|
+
}
|
|
202
|
+
function sortedValue(row, index) {
|
|
203
|
+
return sortedOf(row[`k${index}`], row[`v${index}`], row[`i${index}`]);
|
|
204
|
+
}
|
|
205
|
+
function decodedKey(key, row, index) {
|
|
206
|
+
const kind = row[`k${index}`];
|
|
207
|
+
if (key.of === 'field' && (kind === null || kind === undefined))
|
|
208
|
+
return undefined;
|
|
209
|
+
return sortedValue(row, index);
|
|
210
|
+
}
|
|
211
|
+
function instantOf(term) {
|
|
212
|
+
return term.instant ?? fragment('NULL');
|
|
213
|
+
}
|
|
214
|
+
const registered = new WeakMap();
|
|
215
|
+
function hashed(text) {
|
|
216
|
+
let hash = 0x811c9dc5;
|
|
217
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
218
|
+
hash ^= text.charCodeAt(index);
|
|
219
|
+
hash = Math.imul(hash, 0x01000193);
|
|
220
|
+
}
|
|
221
|
+
return (hash >>> 0).toString(36);
|
|
222
|
+
}
|
|
223
|
+
function collating(connection) {
|
|
224
|
+
let held = registered.get(connection);
|
|
225
|
+
if (held === undefined) {
|
|
226
|
+
held = new Set();
|
|
227
|
+
registered.set(connection, held);
|
|
228
|
+
}
|
|
229
|
+
const names = held;
|
|
230
|
+
return (ordering, lists, total) => {
|
|
231
|
+
if (!lists && ordering.stringSort !== 'locale')
|
|
232
|
+
return null;
|
|
233
|
+
const inner = lists
|
|
234
|
+
? {
|
|
235
|
+
...ordering,
|
|
236
|
+
direction: 'asc',
|
|
237
|
+
nulls: ordering.direction === 'desc'
|
|
238
|
+
? ordering.nulls === 'first'
|
|
239
|
+
? 'last'
|
|
240
|
+
: 'first'
|
|
241
|
+
: ordering.nulls,
|
|
242
|
+
}
|
|
243
|
+
: ordering;
|
|
244
|
+
const spec = JSON.stringify([
|
|
245
|
+
lists,
|
|
246
|
+
total,
|
|
247
|
+
inner.nulls,
|
|
248
|
+
inner.stringSort,
|
|
249
|
+
inner.locale ?? null,
|
|
250
|
+
inner.localeOptions ?? null,
|
|
251
|
+
]);
|
|
252
|
+
const name = `mdr_${lists ? 'l' : 's'}${hashed(spec)}`;
|
|
253
|
+
if (!names.has(name)) {
|
|
254
|
+
const collator = new Intl.Collator(inner.locale, inner.localeOptions);
|
|
255
|
+
connection.collate(name, lists
|
|
256
|
+
? (left, right) => Math.sign(compared(JSON.parse(left), JSON.parse(right), inner)) || (total ? Math.sign(byCodePoint(left, right)) : 0)
|
|
257
|
+
: (left, right) => Math.sign(collator.compare(left, right)));
|
|
258
|
+
names.add(name);
|
|
259
|
+
}
|
|
260
|
+
return name;
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
function masked(pointers) {
|
|
264
|
+
const root = { whole: false, members: new Map() };
|
|
265
|
+
for (const pointer of pointers) {
|
|
266
|
+
let mask = root;
|
|
267
|
+
for (const token of pointerSegments(pointer)) {
|
|
268
|
+
const next = mask.members.get(token) ?? { whole: false, members: new Map() };
|
|
269
|
+
mask.members.set(token, next);
|
|
270
|
+
mask = next;
|
|
271
|
+
}
|
|
272
|
+
mask.whole = true;
|
|
273
|
+
}
|
|
274
|
+
return root;
|
|
275
|
+
}
|
|
276
|
+
function merged(left, right) {
|
|
277
|
+
if (left === undefined)
|
|
278
|
+
return right;
|
|
279
|
+
const members = new Map(left.members);
|
|
280
|
+
for (const [token, mask] of right.members)
|
|
281
|
+
members.set(token, merged(members.get(token), mask));
|
|
282
|
+
return { whole: left.whole || right.whole, members };
|
|
283
|
+
}
|
|
284
|
+
function cut(value, mask) {
|
|
285
|
+
if (mask.whole)
|
|
286
|
+
return value;
|
|
287
|
+
if (Array.isArray(value)) {
|
|
288
|
+
const each = {
|
|
289
|
+
whole: false,
|
|
290
|
+
members: new Map([...mask.members].filter(([token]) => !ARRAY_INDEX.test(token))),
|
|
291
|
+
};
|
|
292
|
+
const kept = [];
|
|
293
|
+
value.forEach((item, index) => {
|
|
294
|
+
const part = cut(item, merged(mask.members.get(String(index)), each));
|
|
295
|
+
if (part !== undefined)
|
|
296
|
+
kept[index] = part;
|
|
297
|
+
});
|
|
298
|
+
return kept.length === 0 ? undefined : kept;
|
|
299
|
+
}
|
|
300
|
+
if (typeof value !== 'object' || value === null)
|
|
301
|
+
return undefined;
|
|
302
|
+
const kept = Object.entries(value).flatMap(([key, held]) => {
|
|
303
|
+
const member = mask.members.get(key);
|
|
304
|
+
const part = member === undefined ? undefined : cut(held, member);
|
|
305
|
+
return part === undefined ? [] : [[key, part]];
|
|
306
|
+
});
|
|
307
|
+
return kept.length === 0 ? undefined : Object.fromEntries(kept);
|
|
308
|
+
}
|
|
309
|
+
function sectionsOf(sections, names) {
|
|
310
|
+
if (names === 'all')
|
|
311
|
+
return sections;
|
|
312
|
+
return Object.fromEntries(names.flatMap((name) => (Object.hasOwn(sections, name) ? [[name, sections[name]]] : [])));
|
|
313
|
+
}
|
|
314
|
+
function freeParts(room, path, type, content) {
|
|
315
|
+
const definitions = room.composition === undefined ? undefined : definitionsAt(room.composition, path);
|
|
316
|
+
const definition = definitions?.(type === UNTYPED ? undefined : type);
|
|
317
|
+
if (definition === undefined && type !== UNTYPED)
|
|
318
|
+
return null;
|
|
319
|
+
const read = parse(content, path, definition);
|
|
320
|
+
return read.ok ? read.document : null;
|
|
321
|
+
}
|
|
322
|
+
function documentOf(shape, queried, row, linked) {
|
|
323
|
+
const room = queried.locator;
|
|
324
|
+
const path = row.path;
|
|
325
|
+
const type = row.type;
|
|
326
|
+
const fileId = row.file_id;
|
|
327
|
+
const revision = row.revision;
|
|
328
|
+
if (type === null) {
|
|
329
|
+
const binary = row.binary === null ? null : JSON.parse(row.binary);
|
|
330
|
+
const content = row.content ?? null;
|
|
331
|
+
return {
|
|
332
|
+
room,
|
|
333
|
+
path,
|
|
334
|
+
...(binary === null
|
|
335
|
+
? {}
|
|
336
|
+
: {
|
|
337
|
+
binaryFile: binaryRecord(path, binary, revision ?? undefined),
|
|
338
|
+
}),
|
|
339
|
+
...(shape.markdown && content !== null ? { markdown: content } : {}),
|
|
340
|
+
...(shape.change ? { change: changeOf(row) } : {}),
|
|
341
|
+
...(shape.fileId ? { fileId } : {}),
|
|
342
|
+
...(shape.revision ? { revision } : {}),
|
|
343
|
+
...heldFacts(shape, row),
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
const body = shape.sections !== null || shape.preamble
|
|
347
|
+
? freeParts(queried, path, type, row.content)
|
|
348
|
+
: null;
|
|
349
|
+
const fields = JSON.parse(row.fields ?? '{}');
|
|
350
|
+
return {
|
|
351
|
+
room,
|
|
352
|
+
path,
|
|
353
|
+
type,
|
|
354
|
+
...(shape.fields === null
|
|
355
|
+
? {}
|
|
356
|
+
: {
|
|
357
|
+
fields: shape.fields === 'all'
|
|
358
|
+
? fields
|
|
359
|
+
: (cut(fields, masked(shape.fields)) ?? {}),
|
|
360
|
+
}),
|
|
361
|
+
...(shape.sections === null || body === null
|
|
362
|
+
? {}
|
|
363
|
+
: { sections: sectionsOf(body.sections, shape.sections) }),
|
|
364
|
+
...(shape.preamble && body !== null ? { preamble: body.preamble } : {}),
|
|
365
|
+
...(shape.markdown ? { markdown: row.content } : {}),
|
|
366
|
+
...(shape.backlinks ? { backlinks: linked.get(documentKey(room, path)) ?? [] } : {}),
|
|
367
|
+
...(shape.change ? { change: changeOf(row) } : {}),
|
|
368
|
+
...(shape.fileId ? { fileId } : {}),
|
|
369
|
+
...(shape.revision ? { revision } : {}),
|
|
370
|
+
...heldFacts(shape, row),
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
function changeOf(row) {
|
|
374
|
+
return row.change === null ? null : JSON.parse(row.change);
|
|
375
|
+
}
|
|
376
|
+
function heldFacts(shape, row) {
|
|
377
|
+
return {
|
|
378
|
+
...(shape.size ? { size: row.bytes === null ? null : Number(row.bytes) } : {}),
|
|
379
|
+
...(shape.created ? { created: row.created } : {}),
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
function backlinksOf(connection, bound, rows) {
|
|
383
|
+
const linked = new Map();
|
|
384
|
+
const byKey = new Map();
|
|
385
|
+
for (const row of rows) {
|
|
386
|
+
const key = Number(row.key);
|
|
387
|
+
const held = byKey.get(key) ?? [];
|
|
388
|
+
held.push(row.path);
|
|
389
|
+
byKey.set(key, held);
|
|
390
|
+
}
|
|
391
|
+
const reach = reached(bound, 's');
|
|
392
|
+
for (const [key, targets] of byKey) {
|
|
393
|
+
const room = bound.rooms.find((one) => one.key === key).locator;
|
|
394
|
+
const found = connection.rows(`SELECT target, path, type, field FROM (SELECT l.target, s.path, s.type, l.field, ROW_NUMBER() OVER (PARTITION BY l.target ORDER BY s.path, l.field) AS rank FROM (SELECT DISTINCT doc, target, field FROM replica_links WHERE key = ? AND kind = 'field' AND target IN (SELECT value FROM json_each(?))) l JOIN replica_entries s ON s.id = l.doc AND ${VISIBLE('s')} WHERE ${reach.sql}) WHERE rank <= ? ORDER BY target, path, field`, [key, JSON.stringify(targets), ...reach.bind, MAXIMUM_WINDOW]);
|
|
395
|
+
for (const row of found) {
|
|
396
|
+
const at = documentKey(room, row.target);
|
|
397
|
+
const held = linked.get(at) ?? [];
|
|
398
|
+
held.push({ path: row.path, type: row.type, field: row.field });
|
|
399
|
+
linked.set(at, held);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
return linked;
|
|
403
|
+
}
|
|
404
|
+
function documents(connection, bound, plan, shape, budget) {
|
|
405
|
+
const terms = shape.orderBy.map(({ key, options }, index) => keyTerm(key, orderingOf(options), index, plan.root.pathPrefix));
|
|
406
|
+
const filter = filtered(bound, plan);
|
|
407
|
+
const cursor = plan.after?.values ?? null;
|
|
408
|
+
const room = located('d');
|
|
409
|
+
const after = cursor === null
|
|
410
|
+
? null
|
|
411
|
+
: (() => {
|
|
412
|
+
const key = cursor[shape.orderBy.length];
|
|
413
|
+
return sql.following(terms.map(({ term }, index) => sql.beyond(bound.collate, term, cursor[index])), any([
|
|
414
|
+
fragment(`${room.sql} > ?`, [...room.bind, roomOfKey(key)]),
|
|
415
|
+
fragment(`${room.sql} = ? AND d.path > ?`, [
|
|
416
|
+
...room.bind,
|
|
417
|
+
roomOfKey(key),
|
|
418
|
+
pathOfKey(key),
|
|
419
|
+
]),
|
|
420
|
+
]));
|
|
421
|
+
})();
|
|
422
|
+
const selected = joined([
|
|
423
|
+
fragment('SELECT d.id, d.key, d.path, d.type, d.binary, d.revision, d.fields, d.change, d.file_id, d.bytes, d.created'),
|
|
424
|
+
...terms.map(({ term }, index) => joined([
|
|
425
|
+
fragment(`, ${term.kind.sql} AS k${index}`, term.kind.bind),
|
|
426
|
+
fragment(`, ${term.value.sql} AS v${index}`, term.value.bind),
|
|
427
|
+
fragment(`, ${instantOf(term).sql} AS i${index}`, instantOf(term).bind),
|
|
428
|
+
], '')),
|
|
429
|
+
fragment(' FROM replica_entries d'),
|
|
430
|
+
...terms.flatMap(({ join }) => (join === null ? [] : [fragment(' '), join])),
|
|
431
|
+
fragment(' WHERE '),
|
|
432
|
+
after === null ? filter : all([filter, after]),
|
|
433
|
+
fragment(' ORDER BY '),
|
|
434
|
+
joined([...terms.map(({ term }) => sql.sorted(bound.collate, term)), room, fragment('d.path')], ', '),
|
|
435
|
+
...(plan.limit === null ? [] : [fragment(' LIMIT ?', [plan.limit + 1])]),
|
|
436
|
+
], '');
|
|
437
|
+
const rows = connection.rows(selected.sql, selected.bind);
|
|
438
|
+
const windowed = plan.limit === null ? rows : rows.slice(0, plan.limit);
|
|
439
|
+
const kept = bodied(shape) ? withContent(connection, windowed, budget) : windowed;
|
|
440
|
+
const cut = kept.length;
|
|
441
|
+
const roomOf = new Map(bound.rooms.map(({ key, locator }) => [key, locator]));
|
|
442
|
+
const queried = new Map(bound.rooms.map((one) => [one.key, one]));
|
|
443
|
+
const linked = shape.backlinks ? backlinksOf(connection, bound, kept) : new Map();
|
|
444
|
+
const found = kept.map((row) => documentOf(shape, queried.get(Number(row.key)), row, linked));
|
|
445
|
+
const last = kept.at(-1);
|
|
446
|
+
const cursorAt = (row) => cursorOf([
|
|
447
|
+
...shape.orderBy.map(({ key }, index) => decodedKey(key, row, index)),
|
|
448
|
+
documentKey(roomOf.get(Number(row.key)), row.path),
|
|
449
|
+
]);
|
|
450
|
+
const more = cut < windowed.length && last !== undefined ? cursorAt(last) : undefined;
|
|
451
|
+
const next = more !== undefined || plan.limit === null || rows.length <= plan.limit
|
|
452
|
+
? undefined
|
|
453
|
+
: last === undefined
|
|
454
|
+
? (plan.after?.text ?? FROM_START)
|
|
455
|
+
: cursorAt(last);
|
|
456
|
+
const total = plan.total
|
|
457
|
+
? (() => {
|
|
458
|
+
const counted = joined([fragment('SELECT count(*) AS held FROM replica_entries d WHERE '), filter], '');
|
|
459
|
+
return Number(connection.rows(counted.sql, counted.bind)[0].held);
|
|
460
|
+
})()
|
|
461
|
+
: undefined;
|
|
462
|
+
return {
|
|
463
|
+
documents: found,
|
|
464
|
+
...(next === undefined ? {} : { next }),
|
|
465
|
+
...(more === undefined ? {} : { more }),
|
|
466
|
+
...(total === undefined ? {} : { total }),
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
function withContent(connection, rows, budget) {
|
|
470
|
+
if (rows.length === 0)
|
|
471
|
+
return rows;
|
|
472
|
+
const ids = rows.map((row) => row.id);
|
|
473
|
+
const sizes = new Map(connection
|
|
474
|
+
.rows(`SELECT id, octet_length(content) AS size FROM replica_entries WHERE id IN ${listed(ids).sql}`, ids)
|
|
475
|
+
.map((row) => [row.id, Number(row.size ?? 0)]));
|
|
476
|
+
let cut = rows.length;
|
|
477
|
+
let spent = 0;
|
|
478
|
+
for (let at = 0; budget !== null && at < rows.length; at += 1) {
|
|
479
|
+
const size = sizes.get(rows[at].id) ?? 0;
|
|
480
|
+
if (at > 0 && spent + size > budget) {
|
|
481
|
+
cut = at;
|
|
482
|
+
break;
|
|
483
|
+
}
|
|
484
|
+
spent += size;
|
|
485
|
+
}
|
|
486
|
+
const kept = rows.slice(0, cut);
|
|
487
|
+
const read = kept.map((row) => row.id);
|
|
488
|
+
const held = new Map(connection
|
|
489
|
+
.rows(`SELECT id, content FROM replica_entries WHERE id IN ${listed(read).sql}`, read)
|
|
490
|
+
.map((row) => [row.id, row.content]));
|
|
491
|
+
return kept.map((row) => ({ ...row, content: held.get(row.id) ?? null }));
|
|
492
|
+
}
|
|
493
|
+
function storedFolders(bound, plan, subfolder) {
|
|
494
|
+
if (plan.paths !== null || plan.fileIds !== null)
|
|
495
|
+
return null;
|
|
496
|
+
if (plan.hop !== null || plan.backlinks !== null)
|
|
497
|
+
return null;
|
|
498
|
+
if (subfolder && plan.folder !== null)
|
|
499
|
+
return null;
|
|
500
|
+
const whole = bound.rooms.filter(({ locator }) => bound.reach === null || bound.reach.rooms.includes(locator));
|
|
501
|
+
if (whole.length === 0)
|
|
502
|
+
return null;
|
|
503
|
+
const keys = whole.map(({ key }) => key);
|
|
504
|
+
const prefix = plan.root.pathPrefix ?? '';
|
|
505
|
+
const outside = (bound.reach?.areas ?? []).flatMap((area) => {
|
|
506
|
+
const key = bound.keys.get(area.room);
|
|
507
|
+
if (key === undefined)
|
|
508
|
+
return [];
|
|
509
|
+
const fence = fenced('f', key, area, true);
|
|
510
|
+
return [fragment(`NOT ${grouped(fence).sql}`, fence.bind)];
|
|
511
|
+
});
|
|
512
|
+
const under = prefix === ''
|
|
513
|
+
? subfolder
|
|
514
|
+
? [fragment("f.path <> ''")]
|
|
515
|
+
: []
|
|
516
|
+
: [
|
|
517
|
+
subfolder
|
|
518
|
+
? fragment('(f.path >= ? AND f.path < ?)', [`${prefix}/`, `${prefix}0`])
|
|
519
|
+
: within('f', prefix),
|
|
520
|
+
];
|
|
521
|
+
return all([
|
|
522
|
+
fragment(`f.key IN ${listed(keys).sql} AND f.layer = 0 AND f.present = 1`, keys),
|
|
523
|
+
...outside,
|
|
524
|
+
...under,
|
|
525
|
+
...(plan.outside ?? []).map((folder) => fragment(`NOT ${within('f', folder).sql}`, within('f', folder).bind)),
|
|
526
|
+
...(plan.folder === null ? [] : [fragment('f.path = ?', [plan.folder])]),
|
|
527
|
+
]);
|
|
528
|
+
}
|
|
529
|
+
const SIZES = [
|
|
530
|
+
'CASE WHEN d.binary IS NULL THEN 1 ELSE 0 END AS dn',
|
|
531
|
+
'CASE WHEN d.binary IS NULL THEN d.bytes ELSE 0 END AS ds',
|
|
532
|
+
'CASE WHEN d.binary IS NULL THEN 0 ELSE 1 END AS fn',
|
|
533
|
+
'CASE WHEN d.binary IS NULL THEN 0 ELSE d.bytes END AS fs',
|
|
534
|
+
];
|
|
535
|
+
const LATEST = "d.changed_instant || ' ' || (d.change ->> '$.time') AS u";
|
|
536
|
+
function greatestTerm(ordering) {
|
|
537
|
+
return { ordering, kind: fragment('gk'), value: fragment('gv'), instant: fragment('gi') };
|
|
538
|
+
}
|
|
539
|
+
function latestOf(held) {
|
|
540
|
+
if (typeof held !== 'string')
|
|
541
|
+
return null;
|
|
542
|
+
return held.slice(held.indexOf(' ') + 1);
|
|
543
|
+
}
|
|
544
|
+
function groups(connection, bound, plan, shape) {
|
|
545
|
+
const prefix = plan.root.pathPrefix;
|
|
546
|
+
const keyed = shape.groupBy.map((key, index) => {
|
|
547
|
+
const { term, join } = keyTerm(key, GROUP_ORDER, index, prefix);
|
|
548
|
+
const kind = fragment(`COALESCE(${term.kind.sql}, 'null')`, term.kind.bind);
|
|
549
|
+
return { term: { ...term, kind }, join };
|
|
550
|
+
});
|
|
551
|
+
const ordering = shape.orderBy.map(({ by }) => by);
|
|
552
|
+
const sized = shape.size || ordering.includes('size');
|
|
553
|
+
const dated = shape.updated || ordering.includes('updated');
|
|
554
|
+
const greatest = shape.greatest === null
|
|
555
|
+
? null
|
|
556
|
+
: keyTerm({ of: 'field', field: shape.greatest }, GREATEST, keyed.length, prefix);
|
|
557
|
+
const filter = filtered(bound, plan);
|
|
558
|
+
const inner = joined([
|
|
559
|
+
fragment('SELECT '),
|
|
560
|
+
joined(keyed.map(({ term }, index) => joined([
|
|
561
|
+
fragment(`${term.kind.sql} AS k${index}`, term.kind.bind),
|
|
562
|
+
fragment(`, ${term.value.sql} AS v${index}`, term.value.bind),
|
|
563
|
+
fragment(`, ${instantOf(term).sql} AS i${index}`, instantOf(term).bind),
|
|
564
|
+
], '')), ', '),
|
|
565
|
+
fragment(['', '1 AS n', ...(sized ? SIZES : []), ...(dated ? [LATEST] : [])].join(', ')),
|
|
566
|
+
...(greatest === null
|
|
567
|
+
? []
|
|
568
|
+
: [
|
|
569
|
+
fragment(`, ${greatest.term.kind.sql} AS gk`, greatest.term.kind.bind),
|
|
570
|
+
fragment(`, ${greatest.term.value.sql} AS gv`, greatest.term.value.bind),
|
|
571
|
+
fragment(`, ${instantOf(greatest.term).sql} AS gi`, instantOf(greatest.term).bind),
|
|
572
|
+
]),
|
|
573
|
+
fragment(' FROM replica_entries d'),
|
|
574
|
+
...[...keyed, ...(greatest === null ? [] : [greatest])].flatMap(({ join }) => join === null ? [] : [fragment(' '), join]),
|
|
575
|
+
fragment(' WHERE '),
|
|
576
|
+
filter,
|
|
577
|
+
], '');
|
|
578
|
+
const only = shape.groupBy.length === 1 ? shape.groupBy[0].of : null;
|
|
579
|
+
const subfolder = only === 'subfolder';
|
|
580
|
+
const folders = only === 'folder' || subfolder ? storedFolders(bound, plan, subfolder) : null;
|
|
581
|
+
const folderKey = subfolder ? subfolderOf('f.path', prefix) : fragment('f.path');
|
|
582
|
+
const zeros = [
|
|
583
|
+
...(sized ? ['0', '0', '0', '0'] : []),
|
|
584
|
+
...(dated ? ['NULL'] : []),
|
|
585
|
+
...(greatest === null ? [] : ['NULL', 'NULL', 'NULL']),
|
|
586
|
+
];
|
|
587
|
+
const source = folders === null
|
|
588
|
+
? inner
|
|
589
|
+
: joined([
|
|
590
|
+
inner,
|
|
591
|
+
fragment(` UNION ALL SELECT 'string' AS k0, ${folderKey.sql} AS v0, NULL AS i0, 0 AS n${zeros.map((one) => `, ${one}`).join('')} FROM replica_folders f WHERE `, folderKey.bind),
|
|
592
|
+
folders,
|
|
593
|
+
], '');
|
|
594
|
+
const held = keyed
|
|
595
|
+
.map((_, index) => `k${index}, i${index}, CASE WHEN i${index} IS NULL THEN v${index} END`)
|
|
596
|
+
.join(', ');
|
|
597
|
+
const ranked = greatest === null
|
|
598
|
+
? source
|
|
599
|
+
: joined([
|
|
600
|
+
fragment(`SELECT *, ROW_NUMBER() OVER (PARTITION BY ${held} ORDER BY `),
|
|
601
|
+
sql.sorted(bound.collate, greatestTerm(GREATEST)),
|
|
602
|
+
fragment(', gv) AS gr FROM ('),
|
|
603
|
+
source,
|
|
604
|
+
fragment(')'),
|
|
605
|
+
], '');
|
|
606
|
+
const columns = [
|
|
607
|
+
...keyed.map((_, index) => `k${index}, min(v${index}) AS v${index}, i${index}`),
|
|
608
|
+
'sum(n) AS n',
|
|
609
|
+
...(sized ? ['sum(dn) AS dn', 'sum(ds) AS ds', 'sum(fn) AS fn', 'sum(fs) AS fs'] : []),
|
|
610
|
+
...(dated ? ['max(u) AS u'] : []),
|
|
611
|
+
...(greatest === null
|
|
612
|
+
? []
|
|
613
|
+
: ['gk', 'gv', 'gi'].map((column) => `min(CASE WHEN gr = 1 THEN ${column} END) AS ${column}`)),
|
|
614
|
+
].join(', ');
|
|
615
|
+
const outer = (index, order) => ({
|
|
616
|
+
ordering: order,
|
|
617
|
+
kind: fragment(`k${index}`),
|
|
618
|
+
value: fragment(`v${index}`),
|
|
619
|
+
});
|
|
620
|
+
const termOf = (by, order) => {
|
|
621
|
+
if (by === 'count')
|
|
622
|
+
return { ordering: order, kind: fragment("'number'"), value: fragment('n') };
|
|
623
|
+
if (by === 'size') {
|
|
624
|
+
return { ordering: order, kind: fragment("'number'"), value: fragment('(ds + fs)') };
|
|
625
|
+
}
|
|
626
|
+
if (by === 'updated') {
|
|
627
|
+
return {
|
|
628
|
+
ordering: order,
|
|
629
|
+
kind: fragment("(CASE WHEN u IS NULL THEN 'null' ELSE 'string' END)"),
|
|
630
|
+
value: fragment("substr(u, instr(u, ' ') + 1)"),
|
|
631
|
+
instant: fragment("substr(u, 1, instr(u, ' ') - 1)"),
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
if (by === 'greatest')
|
|
635
|
+
return greatestTerm(order);
|
|
636
|
+
return { ...outer(by, order), instant: fragment(`i${by}`) };
|
|
637
|
+
};
|
|
638
|
+
const valueOf = (row, by) => {
|
|
639
|
+
if (by === 'count')
|
|
640
|
+
return Number(row.n);
|
|
641
|
+
if (by === 'size')
|
|
642
|
+
return Number(row.ds) + Number(row.fs);
|
|
643
|
+
if (by === 'updated') {
|
|
644
|
+
const text = latestOf(row.u);
|
|
645
|
+
return text === null ? null : sql.sortValue('string', text, row.u.split(' ')[0]);
|
|
646
|
+
}
|
|
647
|
+
if (by === 'greatest')
|
|
648
|
+
return sortedOf(row.gk, row.gv, row.gi);
|
|
649
|
+
return sortedValue(row, by);
|
|
650
|
+
};
|
|
651
|
+
const ordered = shape.orderBy.map(({ by, direction, nulls }) => termOf(by, { ...GROUP_ORDER, direction, nulls: nulls ?? GROUP_ORDER.nulls }));
|
|
652
|
+
const ties = keyed.flatMap((_, index) => [
|
|
653
|
+
{ ...outer(index, GROUP_TIES), total: true },
|
|
654
|
+
{ ordering: GROUP_TIES, kind: fragment("'string'"), value: fragment(`k${index}`) },
|
|
655
|
+
]);
|
|
656
|
+
const cursor = plan.after?.values ?? null;
|
|
657
|
+
const after = cursor === null
|
|
658
|
+
? null
|
|
659
|
+
: (() => {
|
|
660
|
+
const tieValues = cursor[shape.orderBy.length].flatMap((value) => [value, heldKind(value ?? null)]);
|
|
661
|
+
return sql.following(ordered.map((term, index) => sql.beyond(bound.collate, term, cursor[index])), sql.following(ties
|
|
662
|
+
.slice(0, -1)
|
|
663
|
+
.map((term, index) => sql.beyond(bound.collate, term, tieValues[index])), sql.beyond(bound.collate, ties.at(-1), tieValues.at(-1)).beyond));
|
|
664
|
+
})();
|
|
665
|
+
const grouping = joined([fragment(`SELECT ${columns} FROM (`), ranked, fragment(`) GROUP BY ${held}`)], '');
|
|
666
|
+
const windowed = joined([
|
|
667
|
+
fragment('SELECT * FROM ('),
|
|
668
|
+
grouping,
|
|
669
|
+
fragment(')'),
|
|
670
|
+
...(after === null ? [] : [fragment(' WHERE '), after]),
|
|
671
|
+
fragment(' ORDER BY '),
|
|
672
|
+
joined([...ordered, ...ties].map((term) => sql.sorted(bound.collate, term)), ', '),
|
|
673
|
+
...(plan.limit === null ? [] : [fragment(' LIMIT ?', [plan.limit + 1])]),
|
|
674
|
+
], '');
|
|
675
|
+
const rows = connection.rows(windowed.sql, windowed.bind);
|
|
676
|
+
const kept = plan.limit === null ? rows : rows.slice(0, plan.limit);
|
|
677
|
+
const keyOf = (row) => keyed.map((_, index) => decoded(row[`k${index}`], row[`v${index}`]));
|
|
678
|
+
const found = kept.map((row) => ({
|
|
679
|
+
key: keyOf(row),
|
|
680
|
+
count: Number(row.n),
|
|
681
|
+
...(shape.size
|
|
682
|
+
? {
|
|
683
|
+
documents: { count: Number(row.dn), size: Number(row.ds) },
|
|
684
|
+
files: { count: Number(row.fn), size: Number(row.fs) },
|
|
685
|
+
}
|
|
686
|
+
: {}),
|
|
687
|
+
...(shape.updated ? { updated: latestOf(row.u) } : {}),
|
|
688
|
+
...(shape.greatest === null ? {} : { greatest: decoded(row.gk, row.gv) }),
|
|
689
|
+
}));
|
|
690
|
+
const last = kept.at(-1);
|
|
691
|
+
const next = plan.limit === null || rows.length <= plan.limit
|
|
692
|
+
? undefined
|
|
693
|
+
: last === undefined
|
|
694
|
+
? (plan.after?.text ?? FROM_START)
|
|
695
|
+
: cursorOf([...shape.orderBy.map(({ by }) => valueOf(last, by)), keyOf(last)]);
|
|
696
|
+
const total = plan.total
|
|
697
|
+
? Number(connection.rows(joined([fragment('SELECT count(*) AS held FROM ('), grouping, fragment(')')], '').sql, grouping.bind)[0].held)
|
|
698
|
+
: undefined;
|
|
699
|
+
return {
|
|
700
|
+
groups: found,
|
|
701
|
+
...(next === undefined ? {} : { next }),
|
|
702
|
+
...(total === undefined ? {} : { total }),
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
function boundOf(connection, plan, rooms) {
|
|
706
|
+
return {
|
|
707
|
+
rooms: [...rooms].sort((left, right) => left.locator < right.locator ? -1 : left.locator > right.locator ? 1 : 0),
|
|
708
|
+
keys: new Map(rooms.map(({ locator, key }) => [locator, key])),
|
|
709
|
+
reach: plan.reach,
|
|
710
|
+
collate: collating(connection),
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
export function answerQuery(connection, plan, rooms, budget = null) {
|
|
714
|
+
const bound = boundOf(connection, plan, rooms);
|
|
715
|
+
if (rooms.length === 0) {
|
|
716
|
+
return plan.shape.kind === 'groups'
|
|
717
|
+
? { groups: [], ...(plan.total ? { total: 0 } : {}) }
|
|
718
|
+
: { documents: [], ...(plan.total ? { total: 0 } : {}) };
|
|
719
|
+
}
|
|
720
|
+
return plan.shape.kind === 'groups'
|
|
721
|
+
? groups(connection, bound, plan, plan.shape)
|
|
722
|
+
: documents(connection, bound, plan, plan.shape, budget);
|
|
723
|
+
}
|