mrplex 0.0.1
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/LICENSE +21 -0
- package/README.md +284 -0
- package/dist/cli/config.d.ts +28 -0
- package/dist/cli/config.js +48 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/exit-codes.d.ts +13 -0
- package/dist/cli/exit-codes.js +39 -0
- package/dist/cli/exit-codes.js.map +1 -0
- package/dist/cli/format.d.ts +22 -0
- package/dist/cli/format.js +148 -0
- package/dist/cli/format.js.map +1 -0
- package/dist/cli/main.d.ts +16 -0
- package/dist/cli/main.js +1518 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/client/kernel-client.d.ts +97 -0
- package/dist/client/kernel-client.js +14 -0
- package/dist/client/kernel-client.js.map +1 -0
- package/dist/client/local.d.ts +26 -0
- package/dist/client/local.js +103 -0
- package/dist/client/local.js.map +1 -0
- package/dist/client/remote-mcp.d.ts +35 -0
- package/dist/client/remote-mcp.js +174 -0
- package/dist/client/remote-mcp.js.map +1 -0
- package/dist/embed/backfill.d.ts +24 -0
- package/dist/embed/backfill.js +33 -0
- package/dist/embed/backfill.js.map +1 -0
- package/dist/embed/chunker.d.ts +31 -0
- package/dist/embed/chunker.js +121 -0
- package/dist/embed/chunker.js.map +1 -0
- package/dist/embed/cmd-hook.d.ts +21 -0
- package/dist/embed/cmd-hook.js +100 -0
- package/dist/embed/cmd-hook.js.map +1 -0
- package/dist/embed/config.d.ts +61 -0
- package/dist/embed/config.js +109 -0
- package/dist/embed/config.js.map +1 -0
- package/dist/embed/hook.d.ts +38 -0
- package/dist/embed/hook.js +56 -0
- package/dist/embed/hook.js.map +1 -0
- package/dist/embed/http-hook.d.ts +13 -0
- package/dist/embed/http-hook.js +39 -0
- package/dist/embed/http-hook.js.map +1 -0
- package/dist/embed/worker.d.ts +62 -0
- package/dist/embed/worker.js +228 -0
- package/dist/embed/worker.js.map +1 -0
- package/dist/fixtures/hwe/characters/albion-ever.md +12 -0
- package/dist/fixtures/hwe/characters/cent.md +11 -0
- package/dist/fixtures/hwe/concepts/cent-demiurge-resonance.md +10 -0
- package/dist/fixtures/hwe/concepts/phase-looper.md +10 -0
- package/dist/fixtures/hwe/entities/demiurge.md +11 -0
- package/dist/fixtures/hwe/entities/monad.md +11 -0
- package/dist/fixtures/hwe/moc/globocorp-network.md +19 -0
- package/dist/fixtures/hwe/organizations/globocorp.md +11 -0
- package/dist/fixtures/hwe/readme.md +24 -0
- package/dist/fixtures/starship/crew/aria-okonkwo.md +14 -0
- package/dist/fixtures/starship/crew/bexley-orr.md +14 -0
- package/dist/fixtures/starship/crew/dax-thorne.md +14 -0
- package/dist/fixtures/starship/crew/isolde-marsh.md +16 -0
- package/dist/fixtures/starship/crew/kestrel-vance.md +15 -0
- package/dist/fixtures/starship/crew/quill-vasquez.md +14 -0
- package/dist/fixtures/starship/crew/soren-halloway.md +16 -0
- package/dist/fixtures/starship/encounters/the-drift-choir.md +14 -0
- package/dist/fixtures/starship/encounters/the-hollow-signal.md +16 -0
- package/dist/fixtures/starship/encounters/the-wandering-buoy.md +14 -0
- package/dist/fixtures/starship/equipment/coolant-loop-b.md +13 -0
- package/dist/fixtures/starship/equipment/emergency-ration-bars.md +12 -0
- package/dist/fixtures/starship/equipment/long-range-sensors.md +13 -0
- package/dist/fixtures/starship/equipment/plasma-manifold-3.md +15 -0
- package/dist/fixtures/starship/equipment/shuttle-corvid.md +13 -0
- package/dist/fixtures/starship/logs/marsh-4419-1.md +13 -0
- package/dist/fixtures/starship/logs/okonkwo-4415-1.md +13 -0
- package/dist/fixtures/starship/logs/orr-4413-1.md +13 -0
- package/dist/fixtures/starship/logs/orr-4420-1.md +12 -0
- package/dist/fixtures/starship/logs/thorne-4413-2.md +13 -0
- package/dist/fixtures/starship/logs/vance-4419-2.md +13 -0
- package/dist/fixtures/starship/misc/the-galley-ficus.md +11 -0
- package/dist/fixtures/starship/missions/the-cinder-run.md +17 -0
- package/dist/fixtures/starship/missions/the-drift-choir.md +17 -0
- package/dist/fixtures/starship/missions/the-hollow-signal.md +17 -0
- package/dist/fixtures/starship/missions/the-silent-beacon.md +17 -0
- package/dist/fixtures/starship/moc/crew.md +19 -0
- package/dist/fixtures/starship/moc/encounters.md +14 -0
- package/dist/fixtures/starship/moc/missions.md +15 -0
- package/dist/fixtures/starship/readme.md +60 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/kernel/auth/glob.d.ts +46 -0
- package/dist/kernel/auth/glob.js +112 -0
- package/dist/kernel/auth/glob.js.map +1 -0
- package/dist/kernel/auth/scope.d.ts +53 -0
- package/dist/kernel/auth/scope.js +83 -0
- package/dist/kernel/auth/scope.js.map +1 -0
- package/dist/kernel/casefold.d.ts +37 -0
- package/dist/kernel/casefold.js +44 -0
- package/dist/kernel/casefold.js.map +1 -0
- package/dist/kernel/constants.d.ts +21 -0
- package/dist/kernel/constants.js +22 -0
- package/dist/kernel/constants.js.map +1 -0
- package/dist/kernel/context.d.ts +70 -0
- package/dist/kernel/context.js +114 -0
- package/dist/kernel/context.js.map +1 -0
- package/dist/kernel/deletion.d.ts +50 -0
- package/dist/kernel/deletion.js +83 -0
- package/dist/kernel/deletion.js.map +1 -0
- package/dist/kernel/diff.d.ts +36 -0
- package/dist/kernel/diff.js +61 -0
- package/dist/kernel/diff.js.map +1 -0
- package/dist/kernel/errors.d.ts +37 -0
- package/dist/kernel/errors.js +55 -0
- package/dist/kernel/errors.js.map +1 -0
- package/dist/kernel/frontmatter-input.d.ts +32 -0
- package/dist/kernel/frontmatter-input.js +85 -0
- package/dist/kernel/frontmatter-input.js.map +1 -0
- package/dist/kernel/graph.d.ts +34 -0
- package/dist/kernel/graph.js +559 -0
- package/dist/kernel/graph.js.map +1 -0
- package/dist/kernel/history.d.ts +74 -0
- package/dist/kernel/history.js +143 -0
- package/dist/kernel/history.js.map +1 -0
- package/dist/kernel/kernel.d.ts +162 -0
- package/dist/kernel/kernel.js +675 -0
- package/dist/kernel/kernel.js.map +1 -0
- package/dist/kernel/path-config.d.ts +101 -0
- package/dist/kernel/path-config.js +171 -0
- package/dist/kernel/path-config.js.map +1 -0
- package/dist/kernel/query/ast.d.ts +24 -0
- package/dist/kernel/query/ast.js +52 -0
- package/dist/kernel/query/ast.js.map +1 -0
- package/dist/kernel/query/cel-parse.d.ts +37 -0
- package/dist/kernel/query/cel-parse.js +88 -0
- package/dist/kernel/query/cel-parse.js.map +1 -0
- package/dist/kernel/query/degrees.d.ts +31 -0
- package/dist/kernel/query/degrees.js +135 -0
- package/dist/kernel/query/degrees.js.map +1 -0
- package/dist/kernel/query/graph-ast.d.ts +51 -0
- package/dist/kernel/query/graph-ast.js +139 -0
- package/dist/kernel/query/graph-ast.js.map +1 -0
- package/dist/kernel/query/query.d.ts +56 -0
- package/dist/kernel/query/query.js +276 -0
- package/dist/kernel/query/query.js.map +1 -0
- package/dist/kernel/validation.d.ts +64 -0
- package/dist/kernel/validation.js +183 -0
- package/dist/kernel/validation.js.map +1 -0
- package/dist/kernel/version-id.d.ts +10 -0
- package/dist/kernel/version-id.js +21 -0
- package/dist/kernel/version-id.js.map +1 -0
- package/dist/kernel/wire.d.ts +148 -0
- package/dist/kernel/wire.js +6 -0
- package/dist/kernel/wire.js.map +1 -0
- package/dist/links/backfill.d.ts +28 -0
- package/dist/links/backfill.js +33 -0
- package/dist/links/backfill.js.map +1 -0
- package/dist/links/extract.d.ts +29 -0
- package/dist/links/extract.js +233 -0
- package/dist/links/extract.js.map +1 -0
- package/dist/links/link-config.d.ts +70 -0
- package/dist/links/link-config.js +107 -0
- package/dist/links/link-config.js.map +1 -0
- package/dist/links/maintain.d.ts +37 -0
- package/dist/links/maintain.js +91 -0
- package/dist/links/maintain.js.map +1 -0
- package/dist/links/repair.d.ts +46 -0
- package/dist/links/repair.js +96 -0
- package/dist/links/repair.js.map +1 -0
- package/dist/links/resolve.d.ts +41 -0
- package/dist/links/resolve.js +121 -0
- package/dist/links/resolve.js.map +1 -0
- package/dist/links/stale.d.ts +32 -0
- package/dist/links/stale.js +73 -0
- package/dist/links/stale.js.map +1 -0
- package/dist/markdown/content-hash.d.ts +34 -0
- package/dist/markdown/content-hash.js +46 -0
- package/dist/markdown/content-hash.js.map +1 -0
- package/dist/markdown/frontmatter.d.ts +69 -0
- package/dist/markdown/frontmatter.js +147 -0
- package/dist/markdown/frontmatter.js.map +1 -0
- package/dist/markdown/hash-backfill.d.ts +22 -0
- package/dist/markdown/hash-backfill.js +43 -0
- package/dist/markdown/hash-backfill.js.map +1 -0
- package/dist/mcp/query-syntax.d.ts +20 -0
- package/dist/mcp/query-syntax.js +194 -0
- package/dist/mcp/query-syntax.js.map +1 -0
- package/dist/mcp/render.d.ts +31 -0
- package/dist/mcp/render.js +110 -0
- package/dist/mcp/render.js.map +1 -0
- package/dist/mcp/server.d.ts +60 -0
- package/dist/mcp/server.js +170 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools.d.ts +93 -0
- package/dist/mcp/tools.js +1218 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/rest/conditional.d.ts +37 -0
- package/dist/rest/conditional.js +58 -0
- package/dist/rest/conditional.js.map +1 -0
- package/dist/rest/negotiate.d.ts +33 -0
- package/dist/rest/negotiate.js +54 -0
- package/dist/rest/negotiate.js.map +1 -0
- package/dist/rest/routes.d.ts +37 -0
- package/dist/rest/routes.js +744 -0
- package/dist/rest/routes.js.map +1 -0
- package/dist/seed/fixture-seed.d.ts +35 -0
- package/dist/seed/fixture-seed.js +75 -0
- package/dist/seed/fixture-seed.js.map +1 -0
- package/dist/seed/starship-config.d.ts +3 -0
- package/dist/seed/starship-config.js +5 -0
- package/dist/seed/starship-config.js.map +1 -0
- package/dist/server/headers.d.ts +36 -0
- package/dist/server/headers.js +30 -0
- package/dist/server/headers.js.map +1 -0
- package/dist/server/http-error.d.ts +53 -0
- package/dist/server/http-error.js +104 -0
- package/dist/server/http-error.js.map +1 -0
- package/dist/server/serve.d.ts +56 -0
- package/dist/server/serve.js +135 -0
- package/dist/server/serve.js.map +1 -0
- package/dist/shell/audit.d.ts +32 -0
- package/dist/shell/audit.js +28 -0
- package/dist/shell/audit.js.map +1 -0
- package/dist/shell/guard.d.ts +50 -0
- package/dist/shell/guard.js +176 -0
- package/dist/shell/guard.js.map +1 -0
- package/dist/shell/keys.d.ts +36 -0
- package/dist/shell/keys.js +64 -0
- package/dist/shell/keys.js.map +1 -0
- package/dist/shell/login.d.ts +56 -0
- package/dist/shell/login.js +130 -0
- package/dist/shell/login.js.map +1 -0
- package/dist/shell/oidc.d.ts +62 -0
- package/dist/shell/oidc.js +102 -0
- package/dist/shell/oidc.js.map +1 -0
- package/dist/shell/policy.d.ts +100 -0
- package/dist/shell/policy.js +232 -0
- package/dist/shell/policy.js.map +1 -0
- package/dist/shell/proxy-policy.d.ts +34 -0
- package/dist/shell/proxy-policy.js +120 -0
- package/dist/shell/proxy-policy.js.map +1 -0
- package/dist/shell/proxy.d.ts +47 -0
- package/dist/shell/proxy.js +253 -0
- package/dist/shell/proxy.js.map +1 -0
- package/dist/shell/serve.d.ts +86 -0
- package/dist/shell/serve.js +0 -0
- package/dist/shell/serve.js.map +1 -0
- package/dist/shell/stdio.d.ts +55 -0
- package/dist/shell/stdio.js +63 -0
- package/dist/shell/stdio.js.map +1 -0
- package/dist/storage/registry.d.ts +20 -0
- package/dist/storage/registry.js +38 -0
- package/dist/storage/registry.js.map +1 -0
- package/dist/storage/search-plan.d.ts +93 -0
- package/dist/storage/search-plan.js +15 -0
- package/dist/storage/search-plan.js.map +1 -0
- package/dist/storage/types.d.ts +448 -0
- package/dist/storage/types.js +14 -0
- package/dist/storage/types.js.map +1 -0
- package/dist/storage/versions-since.d.ts +72 -0
- package/dist/storage/versions-since.js +92 -0
- package/dist/storage/versions-since.js.map +1 -0
- package/dist/storage-postgres/adapter.d.ts +17 -0
- package/dist/storage-postgres/adapter.js +782 -0
- package/dist/storage-postgres/adapter.js.map +1 -0
- package/dist/storage-postgres/compile-postgres.d.ts +22 -0
- package/dist/storage-postgres/compile-postgres.js +830 -0
- package/dist/storage-postgres/compile-postgres.js.map +1 -0
- package/dist/storage-postgres/errors.d.ts +34 -0
- package/dist/storage-postgres/errors.js +50 -0
- package/dist/storage-postgres/errors.js.map +1 -0
- package/dist/storage-postgres/migrations/0001_init.sql +113 -0
- package/dist/storage-postgres/migrations/0002_content_hash.sql +7 -0
- package/dist/storage-postgres/migrations/index.d.ts +22 -0
- package/dist/storage-postgres/migrations/index.js +70 -0
- package/dist/storage-postgres/migrations/index.js.map +1 -0
- package/dist/storage-sqlite/adapter.d.ts +2 -0
- package/dist/storage-sqlite/adapter.js +688 -0
- package/dist/storage-sqlite/adapter.js.map +1 -0
- package/dist/storage-sqlite/compile-filter.d.ts +41 -0
- package/dist/storage-sqlite/compile-filter.js +881 -0
- package/dist/storage-sqlite/compile-filter.js.map +1 -0
- package/dist/storage-sqlite/compile-sqlite.d.ts +19 -0
- package/dist/storage-sqlite/compile-sqlite.js +176 -0
- package/dist/storage-sqlite/compile-sqlite.js.map +1 -0
- package/dist/storage-sqlite/migrations/0001_init.sql +123 -0
- package/dist/storage-sqlite/migrations/0002_content_hash.sql +7 -0
- package/dist/storage-sqlite/migrations/index.d.ts +6 -0
- package/dist/storage-sqlite/migrations/index.js +41 -0
- package/dist/storage-sqlite/migrations/index.js.map +1 -0
- package/dist/storage-sqlite/vec.d.ts +40 -0
- package/dist/storage-sqlite/vec.js +62 -0
- package/dist/storage-sqlite/vec.js.map +1 -0
- package/dist/sync/cursor.d.ts +36 -0
- package/dist/sync/cursor.js +66 -0
- package/dist/sync/cursor.js.map +1 -0
- package/dist/sync/daemon.d.ts +40 -0
- package/dist/sync/daemon.js +202 -0
- package/dist/sync/daemon.js.map +1 -0
- package/dist/sync/feed.d.ts +38 -0
- package/dist/sync/feed.js +125 -0
- package/dist/sync/feed.js.map +1 -0
- package/dist/sync/fs-store.d.ts +8 -0
- package/dist/sync/fs-store.js +76 -0
- package/dist/sync/fs-store.js.map +1 -0
- package/dist/sync/intrinsics.d.ts +52 -0
- package/dist/sync/intrinsics.js +76 -0
- package/dist/sync/intrinsics.js.map +1 -0
- package/dist/sync/paths.d.ts +38 -0
- package/dist/sync/paths.js +56 -0
- package/dist/sync/paths.js.map +1 -0
- package/dist/sync/push.d.ts +48 -0
- package/dist/sync/push.js +328 -0
- package/dist/sync/push.js.map +1 -0
- package/dist/sync/reconcile.d.ts +54 -0
- package/dist/sync/reconcile.js +252 -0
- package/dist/sync/reconcile.js.map +1 -0
- package/dist/sync/run.d.ts +27 -0
- package/dist/sync/run.js +46 -0
- package/dist/sync/run.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +5 -0
- package/dist/version.js.map +1 -0
- package/package.json +80 -0
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storage layer types — adapter-agnostic. Kernel operations run on top of these.
|
|
3
|
+
*
|
|
4
|
+
* Integer ids are internal (design §3.3) and never cross the wire — the kernel
|
|
5
|
+
* translates rows into Version envelopes with opaque `version_id` strings and
|
|
6
|
+
* slug-based references.
|
|
7
|
+
*
|
|
8
|
+
* All Storage methods are async. SQLite adapter methods complete
|
|
9
|
+
* synchronously and resolve on the next microtask; Postgres adapter
|
|
10
|
+
* methods await real I/O. The uniform signature keeps kernel code
|
|
11
|
+
* dialect-agnostic (design §7.2, m5-plan WS1).
|
|
12
|
+
*/
|
|
13
|
+
import type { SearchPlan } from "./search-plan.js";
|
|
14
|
+
export type RepoRow = {
|
|
15
|
+
id: number;
|
|
16
|
+
slug: string;
|
|
17
|
+
path_config: string | null;
|
|
18
|
+
/** Per-repo link-extraction override JSON (§11.2). Null = inherit. */
|
|
19
|
+
link_config: string | null;
|
|
20
|
+
created_at: string;
|
|
21
|
+
};
|
|
22
|
+
export type DocumentRow = {
|
|
23
|
+
id: number;
|
|
24
|
+
repo_id: number;
|
|
25
|
+
};
|
|
26
|
+
export type FrontmatterJson = Record<string, unknown>;
|
|
27
|
+
export type VersionRow = {
|
|
28
|
+
id: number;
|
|
29
|
+
document_id: number;
|
|
30
|
+
repo_id: number;
|
|
31
|
+
prev_id: number | null;
|
|
32
|
+
next_id: number | null;
|
|
33
|
+
path: string;
|
|
34
|
+
frontmatter_raw: string;
|
|
35
|
+
frontmatter: FrontmatterJson;
|
|
36
|
+
body: string;
|
|
37
|
+
author: string;
|
|
38
|
+
created_at: string;
|
|
39
|
+
/**
|
|
40
|
+
* SHA-256 (bare hex) of canonical content (sync/history plan §2). Derived and
|
|
41
|
+
* server-owned — computed in-tx by `version_insert`, never part of the insert
|
|
42
|
+
* input. Null only on pre-backfill rows written before migration 0002.
|
|
43
|
+
*/
|
|
44
|
+
content_hash: string | null;
|
|
45
|
+
};
|
|
46
|
+
export type VersionInsertInput = {
|
|
47
|
+
document_id: number;
|
|
48
|
+
repo_id: number;
|
|
49
|
+
prev_id: number | null;
|
|
50
|
+
path: string;
|
|
51
|
+
frontmatter_raw: string;
|
|
52
|
+
frontmatter: FrontmatterJson;
|
|
53
|
+
body: string;
|
|
54
|
+
author: string;
|
|
55
|
+
created_at: string;
|
|
56
|
+
};
|
|
57
|
+
export type HistoryOptions = {
|
|
58
|
+
limit?: number;
|
|
59
|
+
before?: string;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Options for the scoped history walk (sync/history plan §3.5). A forward
|
|
63
|
+
* id-ordered walk over the versions of documents selected by a path glob,
|
|
64
|
+
* bounded by version-id cursors. `path_regexes` are anchored regex sources
|
|
65
|
+
* (the gitignore-glob compilation used everywhere); empty = every document in
|
|
66
|
+
* the repo. `ever: false` anchors on the *live* set (docs whose CURRENT path
|
|
67
|
+
* matches); `ever: true` includes any document that *ever* had a matching path
|
|
68
|
+
* (whole chains). `after_id`/`until_id` are version-id bounds (exclusive lower,
|
|
69
|
+
* inclusive upper). `order` picks id ascending (oldest-first) or descending.
|
|
70
|
+
*/
|
|
71
|
+
export type VersionsListOptions = {
|
|
72
|
+
repo_id: number;
|
|
73
|
+
path_regexes: readonly string[];
|
|
74
|
+
ever: boolean;
|
|
75
|
+
after_id?: number;
|
|
76
|
+
until_id?: number;
|
|
77
|
+
order: "asc" | "desc";
|
|
78
|
+
limit: number;
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* Options for the gap-aware forward feed walk (sync/history plan §3.2–3.3).
|
|
82
|
+
* `after_id` is the resume cursor (0 = from the beginning); `repo_id` filters
|
|
83
|
+
* the output without affecting gap detection (gaps are global). `now_ms` and
|
|
84
|
+
* `window_ms` drive the safety window — the adapter supplies wall-clock now;
|
|
85
|
+
* `window_ms` is the caller's tolerance for treating a gap's successor as
|
|
86
|
+
* "settled long enough that the hole is burned, not pending."
|
|
87
|
+
*/
|
|
88
|
+
export type VersionsSinceOptions = {
|
|
89
|
+
after_id: number;
|
|
90
|
+
repo_id?: number;
|
|
91
|
+
limit: number;
|
|
92
|
+
now_ms: number;
|
|
93
|
+
window_ms: number;
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* Result of `versions_since`: the settled rows in `(after_id, next_id]`
|
|
97
|
+
* (repo-filtered, ascending by id) and the resume cursor `next_id`. Every id
|
|
98
|
+
* ≤ `next_id` is final and gap-free; `next_id === after_id` means nothing is
|
|
99
|
+
* safe to deliver yet (caught up, or stalled at a hot gap — the client just
|
|
100
|
+
* polls again).
|
|
101
|
+
*/
|
|
102
|
+
export type VersionsSinceResult = {
|
|
103
|
+
rows: VersionRow[];
|
|
104
|
+
next_id: number;
|
|
105
|
+
};
|
|
106
|
+
/**
|
|
107
|
+
* Chunks (design §3.2, §5.3). One row per chunk of a version's body.
|
|
108
|
+
* `embedding` is null while the row is pending embedding (rare in
|
|
109
|
+
* practice — chunk rows are only inserted alongside their vectors).
|
|
110
|
+
* Content-hash dedup key is (model, text_hash). Embeddings are
|
|
111
|
+
* exchanged with the storage layer as Float32Array — the adapter owns
|
|
112
|
+
* the on-disk representation (byte layout is private to each engine).
|
|
113
|
+
*/
|
|
114
|
+
export type ChunkRow = {
|
|
115
|
+
version_id: number;
|
|
116
|
+
ix: number;
|
|
117
|
+
text: string;
|
|
118
|
+
text_hash: string;
|
|
119
|
+
model: string;
|
|
120
|
+
embedding: Float32Array | null;
|
|
121
|
+
};
|
|
122
|
+
export type ChunkUpsertInput = {
|
|
123
|
+
ix: number;
|
|
124
|
+
text: string;
|
|
125
|
+
text_hash: string;
|
|
126
|
+
model: string;
|
|
127
|
+
/**
|
|
128
|
+
* Query vector. Callers passing fresh vectors use `readonly number[]`
|
|
129
|
+
* (that's what the embed hook returns); callers reusing a dedup-hit
|
|
130
|
+
* vector pass the Float32Array they got back from chunks_by_hash so
|
|
131
|
+
* the adapter round-trips it without re-encoding.
|
|
132
|
+
*/
|
|
133
|
+
embedding: readonly number[] | Float32Array;
|
|
134
|
+
};
|
|
135
|
+
export type VectorSearchHit = {
|
|
136
|
+
version_id: number;
|
|
137
|
+
chunk_ix: number;
|
|
138
|
+
score: number;
|
|
139
|
+
};
|
|
140
|
+
/**
|
|
141
|
+
* Links derived index (design §11.2). One row per outbound STATIC edge
|
|
142
|
+
* from a source document's CURRENT version, doc-keyed. The kernel extracts
|
|
143
|
+
* + resolves edges and hands the adapter the resolved shape below;
|
|
144
|
+
* extraction/resolution logic (markdown parsing, path normalization) lives
|
|
145
|
+
* in src/links, never in the storage layer.
|
|
146
|
+
*/
|
|
147
|
+
/**
|
|
148
|
+
* A resolved outbound edge, ready to persist. `target_id` is the bound
|
|
149
|
+
* document identity or null when dangling (the named path has no live
|
|
150
|
+
* document yet). `target_norm` is the folded, anchor-stripped resolution
|
|
151
|
+
* key used to rebind danglers case-insensitively (§3.5.1).
|
|
152
|
+
*/
|
|
153
|
+
export type LinkEdgeInput = {
|
|
154
|
+
ord: number;
|
|
155
|
+
field: string;
|
|
156
|
+
target_raw: string;
|
|
157
|
+
target_norm: string;
|
|
158
|
+
target_id: number | null;
|
|
159
|
+
};
|
|
160
|
+
/** A stored link edge row (read shape — backfill, links.stale, tests). */
|
|
161
|
+
export type LinkRow = {
|
|
162
|
+
repo_id: number;
|
|
163
|
+
source_id: number;
|
|
164
|
+
ord: number;
|
|
165
|
+
field: string;
|
|
166
|
+
target_raw: string;
|
|
167
|
+
target_norm: string;
|
|
168
|
+
target_id: number | null;
|
|
169
|
+
};
|
|
170
|
+
/**
|
|
171
|
+
* A distinct resolved adjacency triple for the graph read surface
|
|
172
|
+
* (docs/graph-plan.md WS1). `ord`, `target_raw`, and `target_norm` never
|
|
173
|
+
* leave storage on this path: adjacency reads collapse multiple occurrences
|
|
174
|
+
* of the same `(source, target, field)` to one row (`SELECT DISTINCT`) and
|
|
175
|
+
* exclude dangling edges (`target_id IS NULL`). Both endpoints are document
|
|
176
|
+
* ids; the kernel maps them to paths and applies scope.
|
|
177
|
+
*/
|
|
178
|
+
export type AdjacentLink = {
|
|
179
|
+
source_id: number;
|
|
180
|
+
target_id: number;
|
|
181
|
+
field: string;
|
|
182
|
+
};
|
|
183
|
+
/**
|
|
184
|
+
* Embedding backlog (design §3.2, §5.3). One row per version awaiting
|
|
185
|
+
* (or having failed) embedding. `attempts` counts failed tries; a fresh
|
|
186
|
+
* enqueue resets `attempts` and `next_retry_at` so a superseding write
|
|
187
|
+
* doesn't inherit an old backoff.
|
|
188
|
+
*/
|
|
189
|
+
export type BacklogRow = {
|
|
190
|
+
version_id: number;
|
|
191
|
+
attempts: number;
|
|
192
|
+
last_error: string | null;
|
|
193
|
+
next_retry_at: string | null;
|
|
194
|
+
};
|
|
195
|
+
export type BacklogStatus = {
|
|
196
|
+
pending: number;
|
|
197
|
+
due: number;
|
|
198
|
+
failing: number;
|
|
199
|
+
oldest_next_retry_at: string | null;
|
|
200
|
+
recent_errors: readonly {
|
|
201
|
+
version_id: number;
|
|
202
|
+
last_error: string;
|
|
203
|
+
}[];
|
|
204
|
+
models: readonly {
|
|
205
|
+
model: string;
|
|
206
|
+
chunk_count: number;
|
|
207
|
+
}[];
|
|
208
|
+
};
|
|
209
|
+
export type Storage = {
|
|
210
|
+
close(): Promise<void>;
|
|
211
|
+
migrate(): Promise<void>;
|
|
212
|
+
/**
|
|
213
|
+
* Serializable transaction. Nested tx flattens into the outer via
|
|
214
|
+
* savepoints. Contract: never await foreign I/O inside `fn` — the
|
|
215
|
+
* tx holds locks and the adapter's REPEATABLE READ retry loop (PG)
|
|
216
|
+
* must be able to replay the whole body. Kernel tx bodies only call
|
|
217
|
+
* storage.
|
|
218
|
+
*/
|
|
219
|
+
tx<T>(fn: () => Promise<T>): Promise<T>;
|
|
220
|
+
repos_list(): Promise<RepoRow[]>;
|
|
221
|
+
repos_create(input: {
|
|
222
|
+
slug: string;
|
|
223
|
+
created_at: string;
|
|
224
|
+
}): Promise<RepoRow>;
|
|
225
|
+
repos_rename(id: number, new_slug: string): Promise<RepoRow>;
|
|
226
|
+
repos_set_path_config(id: number, path_config: string | null): Promise<RepoRow>;
|
|
227
|
+
repos_set_link_config(id: number, link_config: string | null): Promise<RepoRow>;
|
|
228
|
+
repos_by_slug(slug: string): Promise<RepoRow | null>;
|
|
229
|
+
repos_by_id(id: number): Promise<RepoRow | null>;
|
|
230
|
+
documents_create(repo_id: number): Promise<DocumentRow>;
|
|
231
|
+
/**
|
|
232
|
+
* Insert a new version and advance the chain atomically (design §7.2.2 #1):
|
|
233
|
+
* new row goes in; `prev.next_id` is updated to the new row's id; both happen
|
|
234
|
+
* in one tx. The two partial unique indexes on `versions` enforce the
|
|
235
|
+
* "one current per document" and "one live per (repo, path)" invariants
|
|
236
|
+
* at the storage layer (design §7.2.2 #2).
|
|
237
|
+
*/
|
|
238
|
+
version_insert(input: VersionInsertInput): Promise<VersionRow>;
|
|
239
|
+
version_by_id(id: number): Promise<VersionRow | null>;
|
|
240
|
+
version_current(repo_id: number, path: string): Promise<VersionRow | null>;
|
|
241
|
+
/**
|
|
242
|
+
* Current-version rows for a batch of live paths in `repo_id`. Lookup is by
|
|
243
|
+
* `path_norm` (same folding as `version_current`); paths with no live row are
|
|
244
|
+
* simply absent.
|
|
245
|
+
*/
|
|
246
|
+
versions_current_by_paths(repo_id: number, paths: readonly string[]): Promise<VersionRow[]>;
|
|
247
|
+
version_history(document_id: number, opts?: HistoryOptions): Promise<VersionRow[]>;
|
|
248
|
+
/**
|
|
249
|
+
* Scoped, document-spanning history walk (sync/history plan §3.5). Selects
|
|
250
|
+
* documents by path glob (via `path_regexes`), then returns their version
|
|
251
|
+
* rows interleaved by id, bounded by `after_id`/`until_id` cursors. See
|
|
252
|
+
* `VersionsListOptions` for the `ever` (live vs. whole-corpus) distinction.
|
|
253
|
+
*/
|
|
254
|
+
versions_list(opts: VersionsListOptions): Promise<VersionRow[]>;
|
|
255
|
+
/**
|
|
256
|
+
* The gap-aware forward feed walk (sync/history plan §3.2–3.3): the longest
|
|
257
|
+
* safe contiguous run of version rows after `opts.after_id`, filtered to
|
|
258
|
+
* `opts.repo_id` when given. Returns settled rows only (never crosses a hot
|
|
259
|
+
* gap) plus the resume cursor `next_id`. See `versions-since.ts` for the
|
|
260
|
+
* safety-window logic both adapters share.
|
|
261
|
+
*/
|
|
262
|
+
versions_since(opts: VersionsSinceOptions): Promise<VersionsSinceResult>;
|
|
263
|
+
/**
|
|
264
|
+
* Batch id → path lookup (sync/history plan §3.3): the feed derives each
|
|
265
|
+
* ref's `prev_path` (both ends of a move/delete) from its `prev_id` without
|
|
266
|
+
* hydrating whole prev rows. Ids not present are simply absent from the map.
|
|
267
|
+
*/
|
|
268
|
+
versions_paths_by_ids(ids: readonly number[]): Promise<Map<number, string>>;
|
|
269
|
+
/**
|
|
270
|
+
* The safe head `R` (sync/history plan §3.4): the raw version id the feed
|
|
271
|
+
* would hand out as `next_since` at the live tip — everything ≤ R is visible
|
|
272
|
+
* and final. Computed with the same safety-window machinery as the feed, but
|
|
273
|
+
* anchored at the tip rather than a cursor, so `history.index` scans through
|
|
274
|
+
* a settled boundary while `history.since(R)` covers the rest with no gaps.
|
|
275
|
+
* Returns 0 when the log is empty.
|
|
276
|
+
*/
|
|
277
|
+
versions_safe_head(now_ms: number, window_ms: number): Promise<number>;
|
|
278
|
+
/**
|
|
279
|
+
* One keyset page of the live set for `history.index` (§3.4): live rows
|
|
280
|
+
* (`next_id IS NULL`) in `repo_id` whose current version id is in
|
|
281
|
+
* `(after_id, through_id]`, ascending by id, capped at `limit`. Lightweight
|
|
282
|
+
* tuples only — the kernel applies system/hidden exclusion and scope.
|
|
283
|
+
*/
|
|
284
|
+
versions_live_index(opts: {
|
|
285
|
+
repo_id: number;
|
|
286
|
+
through_id: number;
|
|
287
|
+
after_id: number;
|
|
288
|
+
limit: number;
|
|
289
|
+
}): Promise<{
|
|
290
|
+
id: number;
|
|
291
|
+
path: string;
|
|
292
|
+
content_hash: string | null;
|
|
293
|
+
}[]>;
|
|
294
|
+
/**
|
|
295
|
+
* Content-hash backfill (sync/history plan §2.6). Fetch one batch of rows
|
|
296
|
+
* with `content_hash IS NULL` (id-ascending, id > `after_id`, capped at
|
|
297
|
+
* `limit`), optionally scoped to `repo_id`. Returns the raw fields the shared
|
|
298
|
+
* hash function needs; the kernel computes hashes and writes them via
|
|
299
|
+
* `versions_set_content_hash`. Keyset by id so batches don't re-scan.
|
|
300
|
+
*/
|
|
301
|
+
versions_missing_content_hash(opts: {
|
|
302
|
+
repo_id?: number;
|
|
303
|
+
after_id: number;
|
|
304
|
+
limit: number;
|
|
305
|
+
}): Promise<{
|
|
306
|
+
id: number;
|
|
307
|
+
frontmatter_raw: string;
|
|
308
|
+
body: string;
|
|
309
|
+
}[]>;
|
|
310
|
+
/** Set `content_hash` for a batch of version ids (backfill writer, §2.6). */
|
|
311
|
+
versions_set_content_hash(updates: readonly {
|
|
312
|
+
id: number;
|
|
313
|
+
content_hash: string;
|
|
314
|
+
}[]): Promise<void>;
|
|
315
|
+
/**
|
|
316
|
+
* All currently-live versions in a repo (i.e. rows where next_id IS NULL).
|
|
317
|
+
* Used by `repos.set_path_config` to produce the advisory PathWarning[]
|
|
318
|
+
* scan (§3.5.3). Riding the partial-index on (repo_id, path) where
|
|
319
|
+
* next_id is null, so O(live-set) per repo.
|
|
320
|
+
*/
|
|
321
|
+
versions_live_by_repo(repo_id: number): Promise<VersionRow[]>;
|
|
322
|
+
fts_index(version_id: number, body: string): Promise<void>;
|
|
323
|
+
/**
|
|
324
|
+
* Replace document `source_id`'s outbound edges wholesale (delete +
|
|
325
|
+
* insert) in one shot. `repo_id` scopes the rows (links are repo-local).
|
|
326
|
+
* Called on every create/put that advances the doc's current version.
|
|
327
|
+
* Passing an empty `edges` clears the doc's outbound rows.
|
|
328
|
+
*/
|
|
329
|
+
links_replace(repo_id: number, source_id: number, edges: readonly LinkEdgeInput[]): Promise<void>;
|
|
330
|
+
/** Clear a document's outbound edges (on delete). Inbound rows stay put. */
|
|
331
|
+
links_clear(source_id: number): Promise<void>;
|
|
332
|
+
/**
|
|
333
|
+
* Bind dangling edges in `repo_id` whose folded target matches
|
|
334
|
+
* `target_norm` to `document_id`. Called when a document appears at a
|
|
335
|
+
* path (create / move-in / restore) so waiting danglers resolve — the
|
|
336
|
+
* identity-bound counterpart to "backlinks survive renames" (§11.2).
|
|
337
|
+
* Bind-only: already-bound edges are never touched, and edges are never
|
|
338
|
+
* unbound (a move produces zero inbound churn; a delete leaves inbound
|
|
339
|
+
* rows bound and lets visibility filtering hide them — §11.2). A source's
|
|
340
|
+
* edge is never bound to the source itself (self-links are noise and are
|
|
341
|
+
* excluded, `source_id <> document_id`). Returns the number of edges
|
|
342
|
+
* newly bound.
|
|
343
|
+
*/
|
|
344
|
+
links_resolve_dangling(repo_id: number, target_norm: string, document_id: number): Promise<number>;
|
|
345
|
+
/** A document's outbound edges (backfill, links.stale, tests). */
|
|
346
|
+
links_by_source(source_id: number): Promise<LinkRow[]>;
|
|
347
|
+
/** Every link row in a repo, ordered by (source_id, ord) (tests, verify). */
|
|
348
|
+
links_by_repo(repo_id: number): Promise<LinkRow[]>;
|
|
349
|
+
/**
|
|
350
|
+
* Outbound adjacency for a batch of source documents: distinct
|
|
351
|
+
* `(source_id, target_id, field)` triples where `source_id` is in the
|
|
352
|
+
* batch. Dangling edges (`target_id IS NULL`) are excluded in SQL. Used by
|
|
353
|
+
* the graph read surface (docs/graph-plan.md WS1); the kernel applies
|
|
354
|
+
* scope/filter visibility to the endpoints. `ord` never leaves storage.
|
|
355
|
+
*/
|
|
356
|
+
links_adjacent_out(repo_id: number, source_ids: readonly number[]): Promise<AdjacentLink[]>;
|
|
357
|
+
/**
|
|
358
|
+
* Inbound adjacency for a batch of target documents: distinct
|
|
359
|
+
* `(source_id, target_id, field)` triples where `target_id` is in the
|
|
360
|
+
* batch (resolved edges only). The counterpart to links_adjacent_out.
|
|
361
|
+
*/
|
|
362
|
+
links_adjacent_in(repo_id: number, target_ids: readonly number[]): Promise<AdjacentLink[]>;
|
|
363
|
+
/**
|
|
364
|
+
* Current-version rows for a batch of document ids (graph read surface).
|
|
365
|
+
* Only live rows (`next_id IS NULL`) in `repo_id`; ids not currently live
|
|
366
|
+
* are simply absent. The kernel needs the paths + frontmatter to project
|
|
367
|
+
* documents and to apply scope to graph endpoints.
|
|
368
|
+
*/
|
|
369
|
+
versions_current_by_documents(repo_id: number, document_ids: readonly number[]): Promise<VersionRow[]>;
|
|
370
|
+
/**
|
|
371
|
+
* Document ids of live versions in `repo_id` whose path matches ANY of the
|
|
372
|
+
* given anchored regex sources (`^…$`, the gitignore-glob compilation used
|
|
373
|
+
* everywhere). Path matching happens in SQL (SQLite `regexp()` UDF, Postgres
|
|
374
|
+
* `~`) so the graph root-resolution path never materializes the whole repo.
|
|
375
|
+
* An empty `path_regexes` returns [].
|
|
376
|
+
*/
|
|
377
|
+
versions_live_document_ids_matching(repo_id: number, path_regexes: readonly string[]): Promise<number[]>;
|
|
378
|
+
/**
|
|
379
|
+
* Composed query — the kernel orchestrator (§5) hands over a structured
|
|
380
|
+
* `SearchPlan` (repo ids + parsed CEL AST + scope regex sources + sigil
|
|
381
|
+
* exclusions + optional text + candidate whitelist + limit); the adapter
|
|
382
|
+
* compiles the plan into engine-specific SQL. Ordering per §5.1:
|
|
383
|
+
* text-score if `text` is present, else `$created_at DESC`.
|
|
384
|
+
*
|
|
385
|
+
* m5-plan WS2 pushed compilation behind the adapter so the kernel emits
|
|
386
|
+
* no SQL strings.
|
|
387
|
+
*/
|
|
388
|
+
versions_search(plan: SearchPlan): Promise<VersionRow[]>;
|
|
389
|
+
/**
|
|
390
|
+
* Chunks + vectors (design §3.2, §5.3, §7.2.2). Written by the
|
|
391
|
+
* backlog worker; read by kernel.query's `semantic` branch.
|
|
392
|
+
*
|
|
393
|
+
* `chunks_upsert` replaces all chunks for `version_id` in one tx.
|
|
394
|
+
* All vectors in the input must share `model` and the same
|
|
395
|
+
* dimensionality; the adapter refuses mixed-dim writes (m4-plan §1,
|
|
396
|
+
* §5.3 "refuse mixed-dim writes to the chunks table").
|
|
397
|
+
*/
|
|
398
|
+
chunks_upsert(version_id: number, model: string, chunks: readonly ChunkUpsertInput[]): Promise<void>;
|
|
399
|
+
/**
|
|
400
|
+
* Content-hash dedup lookup (§5.3). Returns one row per hash present
|
|
401
|
+
* in the input list for the given model, with its stored vector so
|
|
402
|
+
* the worker can reuse it without calling the hook.
|
|
403
|
+
*/
|
|
404
|
+
chunks_by_hash(model: string, text_hashes: readonly string[]): Promise<{
|
|
405
|
+
text_hash: string;
|
|
406
|
+
embedding: Float32Array;
|
|
407
|
+
}[]>;
|
|
408
|
+
chunks_by_version(version_id: number): Promise<ChunkRow[]>;
|
|
409
|
+
/**
|
|
410
|
+
* Distinct (model, chunk_count) pairs across the chunks table — for
|
|
411
|
+
* `embed status`. `chunk_count` counts rows, not distinct hashes.
|
|
412
|
+
*/
|
|
413
|
+
chunks_model_summary(): Promise<{
|
|
414
|
+
model: string;
|
|
415
|
+
chunk_count: number;
|
|
416
|
+
}[]>;
|
|
417
|
+
/**
|
|
418
|
+
* Brute-force k-NN over current-version chunks with vectors matching
|
|
419
|
+
* `model`. §7.2.1 pins v1 at brute-force — indexed ANN (HNSW/IVFFlat
|
|
420
|
+
* for pgvector) is a fast-follow.
|
|
421
|
+
*
|
|
422
|
+
* `k` limits distinct-version results, not chunk hits. The adapter
|
|
423
|
+
* owns the version-collapse (best chunk per version) AND the vector
|
|
424
|
+
* serialization (float32 vs float64, LE vs BE, etc.). Kernel callers
|
|
425
|
+
* pass a plain JS number array — no dialect-specific encoding leaks
|
|
426
|
+
* into the query layer.
|
|
427
|
+
*/
|
|
428
|
+
vector_search(repo_ids: readonly number[], model: string, embedding: readonly number[], k: number): Promise<VectorSearchHit[]>;
|
|
429
|
+
backlog_enqueue(version_id: number): Promise<void>;
|
|
430
|
+
/** Rows due now (next_retry_at IS NULL or <= now), oldest first. */
|
|
431
|
+
backlog_dequeue(now: string, limit: number): Promise<BacklogRow[]>;
|
|
432
|
+
backlog_retain(input: {
|
|
433
|
+
version_id: number;
|
|
434
|
+
attempts: number;
|
|
435
|
+
last_error: string;
|
|
436
|
+
next_retry_at: string;
|
|
437
|
+
}): Promise<void>;
|
|
438
|
+
backlog_delete(version_id: number): Promise<void>;
|
|
439
|
+
backlog_status(now: string): Promise<BacklogStatus>;
|
|
440
|
+
};
|
|
441
|
+
export type OpenConfig = {
|
|
442
|
+
/** Database url — sqlite:./path.db or postgres://… */
|
|
443
|
+
database: string;
|
|
444
|
+
};
|
|
445
|
+
export type StorageAdapter = {
|
|
446
|
+
scheme: string;
|
|
447
|
+
open(config: OpenConfig): Promise<Storage>;
|
|
448
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storage layer types — adapter-agnostic. Kernel operations run on top of these.
|
|
3
|
+
*
|
|
4
|
+
* Integer ids are internal (design §3.3) and never cross the wire — the kernel
|
|
5
|
+
* translates rows into Version envelopes with opaque `version_id` strings and
|
|
6
|
+
* slug-based references.
|
|
7
|
+
*
|
|
8
|
+
* All Storage methods are async. SQLite adapter methods complete
|
|
9
|
+
* synchronously and resolve on the next microtask; Postgres adapter
|
|
10
|
+
* methods await real I/O. The uniform signature keeps kernel code
|
|
11
|
+
* dialect-agnostic (design §7.2, m5-plan WS1).
|
|
12
|
+
*/
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/storage/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The gap-aware forward walk — the brain of the change feed (sync/history
|
|
3
|
+
* plan §3.2). Pure logic shared by both storage adapters so SQLite and
|
|
4
|
+
* Postgres compute an identical safe frontier.
|
|
5
|
+
*
|
|
6
|
+
* `versions.id` is an exact cursor iff writes are serialized. Always true on
|
|
7
|
+
* SQLite (single writer). On Postgres, concurrent writers create
|
|
8
|
+
* commit-visibility skew and `nextval` burns ids on rollback, so gaps are
|
|
9
|
+
* routine — each is either *pending* (in-flight, will appear) or *burned*
|
|
10
|
+
* (never will), indistinguishable except by age.
|
|
11
|
+
*
|
|
12
|
+
* The feed therefore returns the longest safe contiguous run after the cursor,
|
|
13
|
+
* not "everything since." Gap rule, keyed on the **successor's** age: if id 100
|
|
14
|
+
* is missing and its successor 101 has been visible longer than the window,
|
|
15
|
+
* 100 is burned → cross it; if 101 is younger than the window, 100 may be a
|
|
16
|
+
* sibling still committing → truncate the page before 101. The page ends at
|
|
17
|
+
* the first hot gap, the scan boundary (treated as a safe tip), or the output
|
|
18
|
+
* limit — whichever comes first.
|
|
19
|
+
*
|
|
20
|
+
* Gaps are only meaningful on the **global** id sequence: a repo filter
|
|
21
|
+
* naturally skips ids taken by other repos, so the walk runs over global rows
|
|
22
|
+
* (id + repo + age) and the repo filter is applied to the output only.
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* Upper bound on the lightweight global scan per poll. Large enough that a
|
|
26
|
+
* repo-filtered `limit` page is almost always satisfied in one scan, bounded
|
|
27
|
+
* so a cold `after_id=0` poll over a huge log stays cheap. Truncating the scan
|
|
28
|
+
* never crosses a gap — it only under-delivers, and the next poll continues
|
|
29
|
+
* from the returned cursor.
|
|
30
|
+
*/
|
|
31
|
+
export declare const GLOBAL_SCAN_CAP = 10000;
|
|
32
|
+
/**
|
|
33
|
+
* How many recent rows to inspect when computing the safe head `R`. Only the
|
|
34
|
+
* newest rows can hold an unsettled (hot) gap — anything older than the safety
|
|
35
|
+
* window is burned-and-settled — so a small tail suffices regardless of log
|
|
36
|
+
* size. Comfortably exceeds any plausible in-flight concurrent-write count.
|
|
37
|
+
*/
|
|
38
|
+
export declare const SAFE_HEAD_TAIL = 1000;
|
|
39
|
+
/** A lightweight row for the frontier walk — no body/frontmatter needed. */
|
|
40
|
+
export type FrontierRow = {
|
|
41
|
+
id: number;
|
|
42
|
+
repo_id: number;
|
|
43
|
+
created_at_ms: number;
|
|
44
|
+
};
|
|
45
|
+
export type FrontierResult = {
|
|
46
|
+
/**
|
|
47
|
+
* Inclusive upper-bound id for the page: every id in `(after_id, upper_id]`
|
|
48
|
+
* is settled and final. Also the resume cursor (`next_since`). Equals
|
|
49
|
+
* `after_id` when nothing is safe to deliver yet, so a caught-up or
|
|
50
|
+
* hot-gap-stalled poll simply doesn't advance.
|
|
51
|
+
*/
|
|
52
|
+
upper_id: number;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Walk `light` (global rows with id > after_id, ascending) and return the
|
|
56
|
+
* inclusive `upper_id` through which the feed is gap-free. Rows matching
|
|
57
|
+
* `repo_id` (or all rows when it is undefined) are counted toward `limit`; the
|
|
58
|
+
* page caps at the id of the `limit`-th match when a further match exists
|
|
59
|
+
* within the safe region, otherwise at the safe frontier so an empty tail is
|
|
60
|
+
* skipped and the cursor keeps advancing.
|
|
61
|
+
*/
|
|
62
|
+
export declare function safeFrontier(light: readonly FrontierRow[], after_id: number, repo_id: number | undefined, limit: number, now_ms: number, window_ms: number): FrontierResult;
|
|
63
|
+
/**
|
|
64
|
+
* The safe head `R` (§3.4) from a bounded tail of recent rows. `tail` is the
|
|
65
|
+
* last rows by id, ascending (id + age; repo irrelevant here). We anchor the
|
|
66
|
+
* walk at `tail[0].id - 1` so the tail's first row isn't mistaken for a leading
|
|
67
|
+
* gap: everything below the tail is older than any realistic safety window, so
|
|
68
|
+
* its gaps are burned and already settled. Within the tail, the walk crosses
|
|
69
|
+
* burned gaps and stops before the first hot one — exactly the id the feed
|
|
70
|
+
* would hand out as `next_since` at the tip. Empty tail → 0.
|
|
71
|
+
*/
|
|
72
|
+
export declare function safeHeadFromTail(tail: readonly FrontierRow[], now_ms: number, window_ms: number): number;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The gap-aware forward walk — the brain of the change feed (sync/history
|
|
3
|
+
* plan §3.2). Pure logic shared by both storage adapters so SQLite and
|
|
4
|
+
* Postgres compute an identical safe frontier.
|
|
5
|
+
*
|
|
6
|
+
* `versions.id` is an exact cursor iff writes are serialized. Always true on
|
|
7
|
+
* SQLite (single writer). On Postgres, concurrent writers create
|
|
8
|
+
* commit-visibility skew and `nextval` burns ids on rollback, so gaps are
|
|
9
|
+
* routine — each is either *pending* (in-flight, will appear) or *burned*
|
|
10
|
+
* (never will), indistinguishable except by age.
|
|
11
|
+
*
|
|
12
|
+
* The feed therefore returns the longest safe contiguous run after the cursor,
|
|
13
|
+
* not "everything since." Gap rule, keyed on the **successor's** age: if id 100
|
|
14
|
+
* is missing and its successor 101 has been visible longer than the window,
|
|
15
|
+
* 100 is burned → cross it; if 101 is younger than the window, 100 may be a
|
|
16
|
+
* sibling still committing → truncate the page before 101. The page ends at
|
|
17
|
+
* the first hot gap, the scan boundary (treated as a safe tip), or the output
|
|
18
|
+
* limit — whichever comes first.
|
|
19
|
+
*
|
|
20
|
+
* Gaps are only meaningful on the **global** id sequence: a repo filter
|
|
21
|
+
* naturally skips ids taken by other repos, so the walk runs over global rows
|
|
22
|
+
* (id + repo + age) and the repo filter is applied to the output only.
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* Upper bound on the lightweight global scan per poll. Large enough that a
|
|
26
|
+
* repo-filtered `limit` page is almost always satisfied in one scan, bounded
|
|
27
|
+
* so a cold `after_id=0` poll over a huge log stays cheap. Truncating the scan
|
|
28
|
+
* never crosses a gap — it only under-delivers, and the next poll continues
|
|
29
|
+
* from the returned cursor.
|
|
30
|
+
*/
|
|
31
|
+
export const GLOBAL_SCAN_CAP = 10000;
|
|
32
|
+
/**
|
|
33
|
+
* How many recent rows to inspect when computing the safe head `R`. Only the
|
|
34
|
+
* newest rows can hold an unsettled (hot) gap — anything older than the safety
|
|
35
|
+
* window is burned-and-settled — so a small tail suffices regardless of log
|
|
36
|
+
* size. Comfortably exceeds any plausible in-flight concurrent-write count.
|
|
37
|
+
*/
|
|
38
|
+
export const SAFE_HEAD_TAIL = 1000;
|
|
39
|
+
/**
|
|
40
|
+
* Walk `light` (global rows with id > after_id, ascending) and return the
|
|
41
|
+
* inclusive `upper_id` through which the feed is gap-free. Rows matching
|
|
42
|
+
* `repo_id` (or all rows when it is undefined) are counted toward `limit`; the
|
|
43
|
+
* page caps at the id of the `limit`-th match when a further match exists
|
|
44
|
+
* within the safe region, otherwise at the safe frontier so an empty tail is
|
|
45
|
+
* skipped and the cursor keeps advancing.
|
|
46
|
+
*/
|
|
47
|
+
export function safeFrontier(light, after_id, repo_id, limit, now_ms, window_ms) {
|
|
48
|
+
let expected = after_id + 1;
|
|
49
|
+
let frontier = after_id; // last globally-contiguous, settled id
|
|
50
|
+
let cutoff = after_id; // id of the last matching row we admit
|
|
51
|
+
let matched = 0;
|
|
52
|
+
let capped = false;
|
|
53
|
+
for (const r of light) {
|
|
54
|
+
if (r.id > expected) {
|
|
55
|
+
// A gap precedes r; r is the successor. Young successor → the missing
|
|
56
|
+
// ids may still be committing → stop before r (hot gap). Old successor
|
|
57
|
+
// → the gap is burned → cross it.
|
|
58
|
+
if (now_ms - r.created_at_ms < window_ms)
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
const matches = repo_id === undefined || r.repo_id === repo_id;
|
|
62
|
+
if (matches && matched === limit) {
|
|
63
|
+
// A (limit+1)-th deliverable row sits within the safe region: cap the
|
|
64
|
+
// page at the limit-th match so the client fetches the rest next poll.
|
|
65
|
+
capped = true;
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
frontier = r.id;
|
|
69
|
+
expected = r.id + 1;
|
|
70
|
+
if (matches) {
|
|
71
|
+
matched++;
|
|
72
|
+
cutoff = r.id;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return { upper_id: capped ? cutoff : frontier };
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The safe head `R` (§3.4) from a bounded tail of recent rows. `tail` is the
|
|
79
|
+
* last rows by id, ascending (id + age; repo irrelevant here). We anchor the
|
|
80
|
+
* walk at `tail[0].id - 1` so the tail's first row isn't mistaken for a leading
|
|
81
|
+
* gap: everything below the tail is older than any realistic safety window, so
|
|
82
|
+
* its gaps are burned and already settled. Within the tail, the walk crosses
|
|
83
|
+
* burned gaps and stops before the first hot one — exactly the id the feed
|
|
84
|
+
* would hand out as `next_since` at the tip. Empty tail → 0.
|
|
85
|
+
*/
|
|
86
|
+
export function safeHeadFromTail(tail, now_ms, window_ms) {
|
|
87
|
+
if (tail.length === 0)
|
|
88
|
+
return 0;
|
|
89
|
+
const anchor = tail[0].id - 1;
|
|
90
|
+
return safeFrontier(tail, anchor, undefined, tail.length, now_ms, window_ms).upper_id;
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=versions-since.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"versions-since.js","sourceRoot":"","sources":["../../src/storage/versions-since.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,KAAK,CAAC;AAErC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,IAAI,CAAC;AAmBnC;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAC1B,KAA6B,EAC7B,QAAgB,EAChB,OAA2B,EAC3B,KAAa,EACb,MAAc,EACd,SAAiB;IAEjB,IAAI,QAAQ,GAAG,QAAQ,GAAG,CAAC,CAAC;IAC5B,IAAI,QAAQ,GAAG,QAAQ,CAAC,CAAC,uCAAuC;IAChE,IAAI,MAAM,GAAG,QAAQ,CAAC,CAAC,uCAAuC;IAC9D,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,IAAI,MAAM,GAAG,KAAK,CAAC;IAEnB,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,IAAI,CAAC,CAAC,EAAE,GAAG,QAAQ,EAAE,CAAC;YACpB,sEAAsE;YACtE,uEAAuE;YACvE,kCAAkC;YAClC,IAAI,MAAM,GAAG,CAAC,CAAC,aAAa,GAAG,SAAS;gBAAE,MAAM;QAClD,CAAC;QACD,MAAM,OAAO,GAAG,OAAO,KAAK,SAAS,IAAI,CAAC,CAAC,OAAO,KAAK,OAAO,CAAC;QAC/D,IAAI,OAAO,IAAI,OAAO,KAAK,KAAK,EAAE,CAAC;YACjC,sEAAsE;YACtE,uEAAuE;YACvE,MAAM,GAAG,IAAI,CAAC;YACd,MAAM;QACR,CAAC;QACD,QAAQ,GAAG,CAAC,CAAC,EAAE,CAAC;QAChB,QAAQ,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;QACpB,IAAI,OAAO,EAAE,CAAC;YACZ,OAAO,EAAE,CAAC;YACV,MAAM,GAAG,CAAC,CAAC,EAAE,CAAC;QAChB,CAAC;IACH,CAAC;IAED,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;AAClD,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,gBAAgB,CAC9B,IAA4B,EAC5B,MAAc,EACd,SAAiB;IAEjB,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IAChC,MAAM,MAAM,GAAI,IAAI,CAAC,CAAC,CAAiB,CAAC,EAAE,GAAG,CAAC,CAAC;IAC/C,OAAO,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC,QAAQ,CAAC;AACxF,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Postgres adapter (m5-plan WS4).
|
|
3
|
+
*
|
|
4
|
+
* Contract:
|
|
5
|
+
* - Kernel calls are async; storage owns SQL emission.
|
|
6
|
+
* - `tx()` wraps `begin isolation level repeatable read` and reuses the
|
|
7
|
+
* same PoolClient inside via AsyncLocalStorage. Nested tx uses
|
|
8
|
+
* savepoints. On 40001/40P01 the whole tx body retries with jittered
|
|
9
|
+
* backoff up to 3 times.
|
|
10
|
+
* - int8 parser: PG returns bigints as strings by default; a custom
|
|
11
|
+
* parser converts them to JS numbers with a SafeInteger guard so id
|
|
12
|
+
* drift is loud, not silent.
|
|
13
|
+
* - Vectors serialize as pgvector's `'[…]'` literal; on read they
|
|
14
|
+
* arrive as a string and parse back to Float32Array.
|
|
15
|
+
*/
|
|
16
|
+
import type { StorageAdapter } from "../storage/types.js";
|
|
17
|
+
export declare const postgresAdapter: StorageAdapter;
|