@modernrelay/orbit-omnigraph 0.2.0

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ModernRelay
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,167 @@
1
+ # @modernrelay/orbit-omnigraph
2
+
3
+ Optional Omnigraph data-source adapter for Orbit (spec Appendix B). Framework-agnostic:
4
+ depends only on the framework-free ingestion/error surface of `@modernrelay/orbit-core`
5
+ and the official TypeScript SDK `@modernrelay/omnigraph` (0.8.x) — never on React, and
6
+ core never depends on it.
7
+
8
+ ## The v1 rule (B.10)
9
+
10
+ > **v1 loads graphs via `og.export()`; queries only ever resolve ids** (search, overlay
11
+ > selection — search itself arrives with S11). The query path never introduces nodes or
12
+ > edges into the graph.
13
+
14
+ Export is the only complete-graph read: it streams NDJSON with full identity — physical
15
+ edge ids included — so every v1 edge carries an export id and no synthesized-id scheme ever
16
+ mixes in (B.4/§5). What that buys, and what it defers (server-backed expansion, query-path
17
+ loading, authed blob fetch), is tabulated in spec B.10.
18
+
19
+ ## Quick start
20
+
21
+ ```ts
22
+ // server.ts — the ONLY place authenticated construction lives (B.1)
23
+ import { createOmnigraphServerClient } from '@modernrelay/orbit-omnigraph/server';
24
+
25
+ const client = createOmnigraphServerClient({
26
+ baseUrl: 'http://127.0.0.1:8080',
27
+ token: process.env.OMNIGRAPH_TOKEN!, // secret material — server-side only
28
+ });
29
+ ```
30
+
31
+ ```ts
32
+ // anywhere (browser code passes a preconfigured safe client instead — see below)
33
+ import { createOmnigraphSource } from '@modernrelay/orbit-omnigraph';
34
+ import { createGraphInstance } from '@modernrelay/orbit-core';
35
+
36
+ const source = createOmnigraphSource({
37
+ client, // preconfigured SDK client — no token option here
38
+ graphId: 'demo',
39
+ branch: 'main', // default
40
+ // typeNames: ['Signal', 'Correlates'], // partial per-type load (wire: type_names)
41
+ // driftPolicy: 'reject', // default; see below
42
+ // maxPendingBytes: 64 * 1024 * 1024, // default whole-load atomic budget
43
+ onProgress: ({ lines, nodes, edges, bytes }) => console.log(lines, nodes, edges, bytes),
44
+ });
45
+
46
+ const instance = createGraphInstance({ engine });
47
+ const result = await source.load(instance /*, abortSignal */);
48
+ // result: { sourceRevision, dataRef, counts, serverVersion, warnings }
49
+ ```
50
+
51
+ `load()` drives one streamed export pass into a `purpose:'replace'` ingest session
52
+ (`datasetKey` = `og:<graphId>:<branch>`): edge lines arrive first (lexicographic table-key
53
+ order) and pass straight through — core's pending-endpoint index links them as nodes land
54
+ (§7.5). Every append is awaited, and the atomic load has a finite whole-export byte budget
55
+ (`maxPendingBytes`, 64 MiB by default); exceeding it aborts without publishing a partial
56
+ graph. Ids are namespaced through the B.3 codec, and B.6 wire encodings are normalized to
57
+ the query-path string forms. Byte progress and budgets use re-serialized UTF-8 NDJSON size,
58
+ including one line break per export row.
59
+
60
+ The `target` is structural (`IngestTarget`: `beginIngest` + `getRevisions` +
61
+ optional `getDiagnostics`) — deliberately decoupled from `GraphInstance`, which satisfies
62
+ it structurally; so does any recorder or headless pipeline.
63
+
64
+ ## `sourceRevision` and drift (B.2)
65
+
66
+ The canonical `sourceRevision` is a hash of
67
+ `{ graphId, branch, headBefore, headAfter, schemaFingerprint }` — but `/export` is
68
+ branch-only (no snapshot isolation), so `headAfter` is only knowable after the stream,
69
+ while core's `beginIngest` requires `sourceRevision` up front for a replace session.
70
+
71
+ Resolution: under `'reject'` and `'retry-once'`, a session begins under the **provisional**
72
+ revision (the canonical hash with `headAfter := headBefore`), the stream appends into it,
73
+ and the head is re-read **before** `commit()`. `'accept-warn'` buffers the bounded export
74
+ until that second head is known, then opens one session under the canonical final revision:
75
+
76
+ - **equal heads** — provisional === final; commit cleanly. The steady-state path.
77
+ - **drift** (`driftPolicy`):
78
+ - `'reject'` (default) — abort the session and throw `OmnigraphDriftError`; the graph is
79
+ untouched. Right for durable/shareable sessions.
80
+ - `'accept-warn'` — commit under the canonical final revision; `dataRef` records
81
+ **both** heads and a warning is added to the result.
82
+ - `'retry-once'` — abort and restart the whole load once; a second drift rejects.
83
+
84
+ This is sound because a replace session is atomic and invisible until commit (§7.5):
85
+ nothing publishes under a revision the policy did not explicitly accept, and the decision
86
+ is never hidden — every accepted drift or retry appears in `result.warnings`.
87
+
88
+ A second identical load of a quiescent branch produces the same
89
+ `{datasetKey, sourceRevision}` and replays idempotently — core publishes nothing (§7.5).
90
+
91
+ ## Search: stored-query `SearchService` (B.7 / §16.5)
92
+
93
+ `createOmnigraphSearchService` wires a stored server-side search query (BM25 / fuzzy /
94
+ vector / RRF with `order { score desc } limit K`) as a §16.5 `SearchService` — plug it into
95
+ core as `services.search` and the instance owns `RequestContext` creation, revision-keyed
96
+ caching, supersede cancellation, and stale-result admission. The service declares
97
+ `revisionDependencies: ['source']` and passes `ctx.signal` through SDK `CallOptions`, so a
98
+ superseded keystroke cancels the in-flight HTTP call.
99
+
100
+ ```ts
101
+ import { createOmnigraphSearchService } from '@modernrelay/orbit-omnigraph';
102
+
103
+ const search = createOmnigraphSearchService({
104
+ client, // preconfigured SDK client — same B.1 rule as the loader
105
+ graphId: 'demo',
106
+ branch: 'main', // default
107
+ queryName: 'search-intel', // registry name; invoked as POST /queries/search-intel
108
+ // params: (q, limit) => ({ q, limit }), // default — reshape for your query's params
109
+ typeOf: { $s: 'Signal' }, // REQUIRED (B.3): column → node type for the id encoding
110
+ // labelColumn: '$s.title', // default: the first string-valued column per row
111
+ });
112
+
113
+ const instance = createGraphInstance({ engine, services: { search } });
114
+ ```
115
+
116
+ Row mapping (override wholesale with `mapRow: (row) => SearchResult | null`, `null` skips):
117
+
118
+ - **id** — Omnigraph ids are unique per type only and query rows carry no type
119
+ discriminator, so the adapter **requires** a caller-supplied column→node-type mapping
120
+ (`typeOf`): a record keyed by the bare-variable projection columns holding node structs
121
+ (`return { $s }` → `{ $s: 'Signal' }`; the first listed column present wins), or a
122
+ per-row function returning the type name (the row's first node-struct column supplies the
123
+ physical id). Every id is qualified via `encodeSourceId(nodeType, physicalId)` — it
124
+ round-trips `decodeSourceId` and matches export-loaded node ids exactly.
125
+ - **score** — a finite number in the row's `score` column (the score lane never doubles as
126
+ a label).
127
+ - **label** — `labelColumn`'s value when configured, else the first string-valued column in
128
+ row key order.
129
+
130
+ Trivial calls (empty query, non-positive limit) resolve `[]` without a network call, and
131
+ results are defensively capped at the requested limit. Errors follow B.9: SDK typed errors
132
+ rethrow as plain `omnigraph:`-prefixed `Error`s; aborts surface as `AbortError`.
133
+
134
+ > **v1 caveat (B.7):** the stored query searches the WHOLE branch server-side, so against a
135
+ > *partial* export load (`typeNames`) it can return ids outside the loaded set.
136
+ > `activateSearchResult` classifies those as `'not-loaded'` (§16.5) — the host may remedy
137
+ > that only by re-running export for the relevant types (query-path single-node fetch is
138
+ > exactly what B.10 defers). Constrain the stored query to the loaded types, or load the
139
+ > full graph, to avoid the mismatch. Discovery via `og.queries.list()` returns only the
140
+ > `mcp.expose == true` registry subset — invoking a known `queryName` works regardless.
141
+
142
+ ## Server-only entry — do not import from the browser
143
+
144
+ `@modernrelay/orbit-omnigraph/server` (`createOmnigraphServerClient`) is the only
145
+ authenticated SDK construction and must stay server-side: `omnigraph-server` ships no CORS
146
+ configuration and uses static bearer tokens (secret material) (B.9). Browser deployments
147
+ pass a preconfigured safe same-origin/public client to `createOmnigraphSource` — the
148
+ browser entry deliberately has no `baseUrl`/`token` option — and typically route reads
149
+ through a proxy/BFF. A client-bundle exclusion gate (`scripts/pack-smoke.mjs`,
150
+ `treeshake:omnigraph-client`) asserts the server entry never reaches a client bundle.
151
+
152
+ ## Error surface (B.9)
153
+
154
+ SDK typed errors (`NetworkError`, `ConflictError`, …) never cross this package's public
155
+ surface — failures rethrow as plain `Error`s with a stable `omnigraph:` message prefix.
156
+ Cancellation (`AbortSignal`) surfaces as an `AbortError`; the session is aborted and the
157
+ graph left untouched. SDK/server `major.minor` version mismatches (`og.health()` vs the
158
+ SDK's pinned `SERVER_VERSION`) surface as a result warning, never a hard failure (B.1).
159
+
160
+ ## Also in the box
161
+
162
+ - `encodeSourceId`/`decodeSourceId`/`encodeSyntheticEdgeId` — the B.3/B.4 identity codec
163
+ every adapter path must use.
164
+ - `parsePgSchema`/`edgeEndpointTypes`/`schemaFingerprint`/`bigIntKeyWarnings` — the `.pg`
165
+ schema model (B.6): endpoint-type resolution, wire-type knowledge, revision fingerprint.
166
+ - `classifyExportLine`/`normalizeNode`/`normalizeEdge` — export-line normalization
167
+ (B.2/B.6).