@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/dist/index.js ADDED
@@ -0,0 +1,428 @@
1
+ import { encodeSourceId, parsePgSchema, schemaFingerprint, bigIntKeyWarnings, classifyExportLine, normalizeNode, normalizeEdge } from './chunk-32TESFMK.js';
2
+ export { InvalidExportLineError, ORBIT_TYPE_KEY, UnknownEdgeTypeError, bigIntKeyWarnings, classifyExportLine, decodeSourceId, edgeEndpointTypes, encodeSourceId, encodeSyntheticEdgeId, normalizeEdge, normalizeNode, parsePgSchema, schemaFingerprint } from './chunk-32TESFMK.js';
3
+ import { SERVER_VERSION, OmnigraphError } from '@modernrelay/omnigraph';
4
+ import { OrbitOperationError } from '@modernrelay/orbit-core';
5
+
6
+ var DEFAULT_BATCH_SIZE = 2e3;
7
+ var DEFAULT_MAX_PENDING_BYTES = 512 * 1024 * 1024;
8
+ var UTF8_ENCODER = new TextEncoder();
9
+ var OmnigraphDriftError = class extends Error {
10
+ name = "OmnigraphDriftError";
11
+ graphId;
12
+ branch;
13
+ headBefore;
14
+ headAfter;
15
+ constructor(graphId, branch, headBefore, headAfter) {
16
+ super(
17
+ `omnigraph: branch '${branch}' of graph '${graphId}' changed during export (head ${headBefore} \u2192 ${headAfter}); session aborted per driftPolicy (spec B.2: service:omnigraph-source-changed-during-export)`
18
+ );
19
+ this.graphId = graphId;
20
+ this.branch = branch;
21
+ this.headBefore = headBefore;
22
+ this.headAfter = headAfter;
23
+ }
24
+ };
25
+ function hash64(s) {
26
+ const MASK64 = 0xffffffffffffffffn;
27
+ let h = 0xcbf29ce484222325n;
28
+ for (const b of new TextEncoder().encode(s)) {
29
+ h ^= BigInt(b);
30
+ h = h * 0x100000001b3n & MASK64;
31
+ }
32
+ return h.toString(16).padStart(16, "0");
33
+ }
34
+ function canonicalSourceRevision(ref) {
35
+ return `og:${hash64(
36
+ JSON.stringify([ref.graphId, ref.branch, ref.headBefore, ref.headAfter, ref.schemaFingerprint])
37
+ )}`;
38
+ }
39
+ function majorMinor(version) {
40
+ const m = /^(\d+)\.(\d+)/.exec(version);
41
+ return m ? `${m[1]}.${m[2]}` : null;
42
+ }
43
+ function callOpts(signal) {
44
+ return signal ? { signal } : {};
45
+ }
46
+ function throwIfAborted(signal) {
47
+ if (signal?.aborted) {
48
+ throw new DOMException("omnigraph: load aborted by caller signal", "AbortError");
49
+ }
50
+ }
51
+ function isAbortError(err) {
52
+ return err instanceof Error && err.name === "AbortError";
53
+ }
54
+ function mapError(err) {
55
+ if (err instanceof OmnigraphError && !isAbortError(err)) {
56
+ return new Error(`omnigraph: ${err.name} (status ${err.status}): ${err.message}`);
57
+ }
58
+ return err;
59
+ }
60
+ function pushUnique(warnings, warning) {
61
+ if (!warnings.includes(warning)) warnings.push(warning);
62
+ }
63
+ function createOmnigraphSource(options) {
64
+ const graphId = options.graphId;
65
+ const branch = options.branch ?? "main";
66
+ const batchSize = Math.max(1, Math.floor(options.batchSize ?? DEFAULT_BATCH_SIZE));
67
+ const maxPendingBytes = options.maxPendingBytes ?? DEFAULT_MAX_PENDING_BYTES;
68
+ if (!Number.isSafeInteger(maxPendingBytes) || maxPendingBytes <= 0) {
69
+ throw new TypeError(
70
+ "createOmnigraphSource: maxPendingBytes must be a positive safe integer"
71
+ );
72
+ }
73
+ const driftPolicy = options.driftPolicy ?? "reject";
74
+ const onProgress = options.onProgress;
75
+ const typeNames = options.typeNames === void 0 ? void 0 : [...options.typeNames];
76
+ const client = options.client.graph(graphId);
77
+ const datasetKey = `og:${graphId}:${branch}`;
78
+ async function newestHead(signal) {
79
+ const commits = await client.commits.list({ branch }, callOpts(signal));
80
+ const newest = commits[0];
81
+ if (newest === void 0) {
82
+ throw new Error(
83
+ `omnigraph: branch '${branch}' of graph '${graphId}' has no commits \u2014 nothing to export`
84
+ );
85
+ }
86
+ return newest.graphCommitId;
87
+ }
88
+ async function runAttempt(target, signal, warnings, attempt) {
89
+ throwIfAborted(signal);
90
+ const baseSource = target.getRevisions().source;
91
+ const headBefore = await newestHead(signal);
92
+ const schemaSource = (await client.schema.get(callOpts(signal))).schemaSource;
93
+ const schema = parsePgSchema(schemaSource);
94
+ const fingerprint = schemaFingerprint(schemaSource);
95
+ for (const hazard of bigIntKeyWarnings(schema)) {
96
+ pushUnique(
97
+ warnings,
98
+ `omnigraph: ${hazard.type}.${hazard.property} is a 64-bit integer used as identity \u2014 JSON parsing silently rounds values past \xB12^53, which can collapse distinct ids (B.6)`
99
+ );
100
+ }
101
+ const provisionalRef = {
102
+ graphId,
103
+ branch,
104
+ headBefore,
105
+ headAfter: headBefore,
106
+ // provisional: assume quiescence, verify after the stream
107
+ schemaFingerprint: fingerprint
108
+ };
109
+ const provisionalRevision = canonicalSourceRevision(provisionalRef);
110
+ function beginSession(sourceRevision) {
111
+ const now = target.getRevisions();
112
+ if (now.source !== baseSource) {
113
+ throw new Error(
114
+ `omnigraph: the target's source lineage changed while the load was streaming (a competing replace or snapshot landed); aborting instead of overwriting it`
115
+ );
116
+ }
117
+ return target.beginIngest({
118
+ purpose: "replace",
119
+ datasetKey,
120
+ sourceRevision,
121
+ baseModelRevision: now.model,
122
+ maxPendingBytes
123
+ });
124
+ }
125
+ let session = driftPolicy === "accept-warn" ? void 0 : beginSession(provisionalRevision);
126
+ const requestId = hash64(`${datasetKey}|${provisionalRevision}|${attempt}`);
127
+ const bufferedBatches = driftPolicy === "accept-warn" ? [] : void 0;
128
+ let lines = 0;
129
+ let nodeCount = 0;
130
+ let edgeCount = 0;
131
+ let bytes = 0;
132
+ let unknown = 0;
133
+ let sequence = 0;
134
+ let pendingNodes = [];
135
+ let pendingEdges = [];
136
+ let pendingBytes = 0;
137
+ let acceptedBytes = 0;
138
+ function accountAcceptedBytes(lineBytes) {
139
+ const next = acceptedBytes + lineBytes;
140
+ if (next > maxPendingBytes) {
141
+ throw new OrbitOperationError(
142
+ { code: "queue-overflow", queuedBytes: next, limit: maxPendingBytes },
143
+ `omnigraph: export rows require at least ${next} bytes, exceeding maxPendingBytes ${maxPendingBytes}; load aborted before commit`
144
+ );
145
+ }
146
+ acceptedBytes = next;
147
+ pendingBytes += lineBytes;
148
+ }
149
+ async function flush() {
150
+ if (pendingNodes.length === 0 && pendingEdges.length === 0) return;
151
+ const nodes = pendingNodes;
152
+ const edges = pendingEdges;
153
+ const batchBytes = pendingBytes;
154
+ const progress = { lines, nodes: nodeCount, edges: edgeCount, bytes };
155
+ pendingNodes = [];
156
+ pendingEdges = [];
157
+ pendingBytes = 0;
158
+ if (bufferedBatches !== void 0) {
159
+ bufferedBatches.push({ nodes, edges, bytes: batchBytes, progress });
160
+ return;
161
+ }
162
+ const activeSession = session;
163
+ if (activeSession === void 0) throw new Error("omnigraph: internal missing ingest session");
164
+ const batch = {
165
+ sequence,
166
+ batchId: `og:${requestId}:${sequence}`,
167
+ bytes: batchBytes
168
+ };
169
+ if (edges.length > 0) batch.edges = edges;
170
+ if (nodes.length > 0) batch.nodes = nodes;
171
+ sequence += 1;
172
+ await activeSession.append(batch);
173
+ onProgress?.(progress);
174
+ }
175
+ async function commitBuffered(sourceRevision) {
176
+ if (bufferedBatches === void 0) {
177
+ throw new Error("omnigraph: internal missing accept-warn batch buffer");
178
+ }
179
+ const finalSession = beginSession(sourceRevision);
180
+ session = finalSession;
181
+ const finalRequestId = hash64(`${datasetKey}|${sourceRevision}|${attempt}`);
182
+ for (let i = 0; i < bufferedBatches.length; i++) {
183
+ throwIfAborted(signal);
184
+ const buffered = bufferedBatches[i];
185
+ if (buffered === void 0) continue;
186
+ bufferedBatches[i] = void 0;
187
+ const batch = {
188
+ sequence: i,
189
+ batchId: `og:${finalRequestId}:${i}`,
190
+ bytes: buffered.bytes
191
+ };
192
+ if (buffered.edges.length > 0) batch.edges = buffered.edges;
193
+ if (buffered.nodes.length > 0) batch.nodes = buffered.nodes;
194
+ await finalSession.append(batch);
195
+ onProgress?.(buffered.progress);
196
+ }
197
+ bufferedBatches.length = 0;
198
+ await finalSession.commit();
199
+ }
200
+ const exportInput = { branch, ...typeNames !== void 0 ? { typeNames } : {} };
201
+ const iterator = client.export(exportInput, callOpts(signal))[Symbol.asyncIterator]();
202
+ try {
203
+ for (; ; ) {
204
+ throwIfAborted(signal);
205
+ const step = await iterator.next();
206
+ if (step.done === true) break;
207
+ const line = step.value;
208
+ lines += 1;
209
+ const lineBytes = UTF8_ENCODER.encode(JSON.stringify(line)).byteLength + 1;
210
+ bytes += lineBytes;
211
+ const classified = classifyExportLine(line);
212
+ if (classified.kind === "node") {
213
+ const node = normalizeNode(classified, schema);
214
+ accountAcceptedBytes(lineBytes);
215
+ pendingNodes.push(node);
216
+ nodeCount += 1;
217
+ } else if (classified.kind === "edge") {
218
+ const edge = normalizeEdge(classified, schema);
219
+ accountAcceptedBytes(lineBytes);
220
+ pendingEdges.push(edge);
221
+ edgeCount += 1;
222
+ } else {
223
+ unknown += 1;
224
+ }
225
+ if (pendingNodes.length + pendingEdges.length >= batchSize) await flush();
226
+ }
227
+ await flush();
228
+ if (unknown > 0) {
229
+ pushUnique(warnings, `omnigraph: skipped ${unknown} unrecognized export line(s) (B.2)`);
230
+ }
231
+ throwIfAborted(signal);
232
+ const headAfter = await newestHead(signal);
233
+ const counts = { lines, nodes: nodeCount, edges: edgeCount, bytes };
234
+ if (driftPolicy === "accept-warn") {
235
+ const finalRef = { ...provisionalRef, headAfter };
236
+ const finalRevision = canonicalSourceRevision(finalRef);
237
+ if (headAfter !== headBefore) {
238
+ pushUnique(
239
+ warnings,
240
+ `omnigraph: branch '${branch}' advanced during export (head ${headBefore} \u2192 ${headAfter}); committed under the canonical final revision per driftPolicy:'accept-warn' \u2014 dataRef records both heads (B.2)`
241
+ );
242
+ }
243
+ await commitBuffered(finalRevision);
244
+ return { kind: "done", sourceRevision: finalRevision, dataRef: finalRef, counts };
245
+ }
246
+ if (headAfter === headBefore) {
247
+ const activeSession2 = session;
248
+ if (activeSession2 === void 0) throw new Error("omnigraph: internal missing ingest session");
249
+ await activeSession2.commit();
250
+ return { kind: "done", sourceRevision: provisionalRevision, dataRef: provisionalRef, counts };
251
+ }
252
+ const activeSession = session;
253
+ if (activeSession === void 0) throw new Error("omnigraph: internal missing ingest session");
254
+ await activeSession.abort("omnigraph: source changed during export (B.2)");
255
+ if (driftPolicy === "retry-once" && attempt === 1) {
256
+ pushUnique(
257
+ warnings,
258
+ `omnigraph: branch '${branch}' advanced during export (head ${headBefore} \u2192 ${headAfter}); load restarted once per driftPolicy:'retry-once' (B.2)`
259
+ );
260
+ return { kind: "retry" };
261
+ }
262
+ throw new OmnigraphDriftError(graphId, branch, headBefore, headAfter);
263
+ } catch (err) {
264
+ if (session?.state === "open") {
265
+ try {
266
+ await session.abort(err);
267
+ } catch {
268
+ }
269
+ }
270
+ throw mapError(err);
271
+ } finally {
272
+ try {
273
+ await iterator.return?.();
274
+ } catch {
275
+ }
276
+ }
277
+ }
278
+ async function load(target, signal) {
279
+ const warnings = [];
280
+ let serverVersion;
281
+ try {
282
+ serverVersion = (await client.health(callOpts(signal))).version;
283
+ } catch (err) {
284
+ throw mapError(err);
285
+ }
286
+ const server = majorMinor(serverVersion);
287
+ const sdk = majorMinor(SERVER_VERSION);
288
+ if (server === null || server !== sdk) {
289
+ warnings.push(
290
+ `omnigraph: server version ${serverVersion} does not match the SDK-pinned server version ${SERVER_VERSION} (major.minor differ) \u2014 SDK behavior is undefined against this server (B.1)`
291
+ );
292
+ }
293
+ for (let attempt = 1; ; attempt += 1) {
294
+ let outcome;
295
+ try {
296
+ outcome = await runAttempt(target, signal, warnings, attempt);
297
+ } catch (err) {
298
+ throw mapError(err);
299
+ }
300
+ if (outcome.kind === "retry") continue;
301
+ return {
302
+ sourceRevision: outcome.sourceRevision,
303
+ dataRef: outcome.dataRef,
304
+ counts: outcome.counts,
305
+ serverVersion,
306
+ warnings
307
+ };
308
+ }
309
+ }
310
+ return { load };
311
+ }
312
+ function defaultParams(q, limit) {
313
+ return { q, limit };
314
+ }
315
+ function throwIfAborted2(signal, queryName) {
316
+ if (signal.aborted) {
317
+ throw new DOMException(
318
+ `omnigraph: search query '${queryName}' aborted by request signal`,
319
+ "AbortError"
320
+ );
321
+ }
322
+ }
323
+ function isAbortError2(err) {
324
+ return err instanceof Error && err.name === "AbortError";
325
+ }
326
+ function mapError2(err) {
327
+ if (err instanceof OmnigraphError && !isAbortError2(err)) {
328
+ return new Error(`omnigraph: ${err.name} (status ${err.status}): ${err.message}`);
329
+ }
330
+ return err;
331
+ }
332
+ function isNodeStruct(value) {
333
+ return typeof value === "object" && value !== null && !Array.isArray(value) && typeof value.id === "string";
334
+ }
335
+ function extractRows(response, queryName) {
336
+ const rows = response.rows;
337
+ if (!Array.isArray(rows)) {
338
+ throw new Error(
339
+ `omnigraph: stored query '${queryName}' did not return a read envelope with rows \u2014 the B.7 search query must be a stored READ query (not a mutation)`
340
+ );
341
+ }
342
+ for (const row of rows) {
343
+ if (typeof row !== "object" || row === null || Array.isArray(row)) {
344
+ throw new Error(
345
+ `omnigraph: stored query '${queryName}' returned a non-object row \u2014 expected column\u2192value row objects`
346
+ );
347
+ }
348
+ }
349
+ return rows;
350
+ }
351
+ function resolveIdentity(row, typeOf, queryName) {
352
+ if (typeof typeOf === "function") {
353
+ const kind = typeOf(row);
354
+ if (typeof kind !== "string" || kind.length === 0) {
355
+ throw new Error(
356
+ `omnigraph: typeOf returned ${JSON.stringify(kind)} for a '${queryName}' search row \u2014 it must return a non-empty node type name (B.3)`
357
+ );
358
+ }
359
+ for (const key of Object.keys(row)) {
360
+ const value = row[key];
361
+ if (isNodeStruct(value)) return { kind, sourceId: value.id };
362
+ }
363
+ throw new Error(
364
+ `omnigraph: '${queryName}' search row has no node-struct column (an object with a string 'id') \u2014 project the matched entity bare (return { $s }) so its physical id is available (B.7)`
365
+ );
366
+ }
367
+ for (const [column, kind] of Object.entries(typeOf)) {
368
+ const value = row[column];
369
+ if (isNodeStruct(value)) return { kind, sourceId: value.id };
370
+ }
371
+ throw new Error(
372
+ `omnigraph: '${queryName}' search row has no node struct under the mapped column(s) ${JSON.stringify(Object.keys(typeOf))} \u2014 the typeOf record must key the bare-variable projection column(s) (B.3)`
373
+ );
374
+ }
375
+ function createOmnigraphSearchService(options) {
376
+ const { graphId, queryName, typeOf, labelColumn, mapRow } = options;
377
+ const branch = options.branch ?? "main";
378
+ const buildParams = options.params ?? defaultParams;
379
+ const client = options.client.graph(graphId);
380
+ function mapRowDefault(row) {
381
+ const { kind, sourceId } = resolveIdentity(row, typeOf, queryName);
382
+ const result = { id: encodeSourceId(kind, sourceId) };
383
+ const score = row["score"];
384
+ if (typeof score === "number" && Number.isFinite(score)) result.score = score;
385
+ if (labelColumn !== void 0) {
386
+ const value = row[labelColumn];
387
+ if (typeof value === "string" || typeof value === "number") result.label = String(value);
388
+ } else {
389
+ for (const key of Object.keys(row)) {
390
+ if (key === "score") continue;
391
+ const value = row[key];
392
+ if (typeof value === "string") {
393
+ result.label = value;
394
+ break;
395
+ }
396
+ }
397
+ }
398
+ return result;
399
+ }
400
+ return {
401
+ revisionDependencies: ["source"],
402
+ async search(q, searchOptions, ctx) {
403
+ throwIfAborted2(ctx.signal, queryName);
404
+ const limit = Number.isFinite(searchOptions.limit) ? Math.floor(searchOptions.limit) : 0;
405
+ if (q.length === 0 || limit <= 0) return [];
406
+ const input = { branch, params: buildParams(q, limit) };
407
+ let response;
408
+ try {
409
+ response = await client.queries.invoke(queryName, input, { signal: ctx.signal });
410
+ } catch (err) {
411
+ throw mapError2(err);
412
+ }
413
+ throwIfAborted2(ctx.signal, queryName);
414
+ const rows = extractRows(response, queryName);
415
+ const results = [];
416
+ for (const row of rows) {
417
+ if (results.length >= limit) break;
418
+ const mapped = mapRow !== void 0 ? mapRow(row) : mapRowDefault(row);
419
+ if (mapped !== null) results.push(mapped);
420
+ }
421
+ return results;
422
+ }
423
+ };
424
+ }
425
+
426
+ export { OmnigraphDriftError, createOmnigraphSearchService, createOmnigraphSource };
427
+ //# sourceMappingURL=index.js.map
428
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/loader.ts","../src/searchService.ts"],"names":["activeSession","throwIfAborted","isAbortError","mapError","OmnigraphError"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * v1 export loader (spec Appendix B.2 / B.8 / B.10).\n *\n * The organizing rule (B.10): graphs load via `og.export()` — one streamed\n * NDJSON pass, edge lines first (lexicographic table-key order), driven\n * straight into a `purpose:'replace'` ingest session on the target. Queries\n * never introduce nodes or edges.\n *\n * Revision stamp (B.2): the canonical `sourceRevision` hashes\n * `{ graphId, branch, headBefore, headAfter, schemaFingerprint }`, but\n * `headAfter` is only knowable after the stream — while core's\n * `BeginIngestOptions` requires `sourceRevision` up front for a replace\n * session. Resolution: under the rejecting/retrying policies the session\n * begins under the PROVISIONAL revision (the canonical hash with\n * `headAfter := headBefore`), the stream appends into it, and `headAfter` is\n * captured BEFORE `commit()`. `accept-warn` is the exception: normalized\n * batches are buffered until `headAfter` is known, then appended once into a\n * session opened with the canonical FINAL revision:\n *\n * - equal heads → provisional === final; commit cleanly. The session only\n * ever commits a revision whose hash is truthful.\n * - drifted heads → `driftPolicy` decides: `'reject'` aborts the session\n * (graph untouched) and throws {@link OmnigraphDriftError};\n * `'accept-warn'` commits the buffered export under the\n * final revision, records BOTH heads in `dataRef`, and\n * adds a warning;\n * `'retry-once'` aborts and restarts the whole load once\n * (a second drift rejects).\n *\n * This is sound because a replace session is atomic and invisible until\n * commit (§7.5): nothing is published under a revision the policy did not\n * explicitly accept.\n *\n * Error surface (B.9): SDK typed errors never cross this package's public\n * surface — they are mapped to plain `Error`s with a stable `omnigraph:`\n * message prefix.\n */\n\nimport { OmnigraphError, SERVER_VERSION } from '@modernrelay/omnigraph';\nimport type { CallOptions, ExportInput, Omnigraph } from '@modernrelay/omnigraph';\nimport { OrbitOperationError } from '@modernrelay/orbit-core';\nimport type { GraphEdge, GraphNode, IngestBatch, IngestSession } from '@modernrelay/orbit-core';\n\nimport { classifyExportLine, normalizeEdge, normalizeNode } from './normalize';\nimport { bigIntKeyWarnings, parsePgSchema, schemaFingerprint } from './pgSchema';\nimport type { PgSchema } from './pgSchema';\nimport type {\n IngestTarget,\n OmnigraphDataRef,\n OmnigraphLoadResult,\n OmnigraphSourceOptions,\n} from './types';\n\nconst DEFAULT_BATCH_SIZE = 2000;\n\n/**\n * Finite whole-export cap for the necessarily atomic replace session. A\n * whole-graph load stages EVERY row before commit, so this is a total-load\n * memory bound, not a backpressure window — real graphs routinely exceed\n * 100 MB of serialized rows (the intel fixture streams ~128 MB), so the\n * default is deliberately generous; callers with tighter memory budgets pass\n * `maxPendingBytes` explicitly. PR #8's 64 MiB default regressed >64 MiB\n * loads that v0.6 handled.\n */\nconst DEFAULT_MAX_PENDING_BYTES = 512 * 1024 * 1024;\n\n/** Stateless and safe to reuse; counts serialized UTF-8 rather than UTF-16 code units. */\nconst UTF8_ENCODER = new TextEncoder();\n\n/**\n * B.2 drift under `driftPolicy: 'reject'` (or a second drift under\n * `'retry-once'`): the branch head moved while the export streamed, the\n * session was aborted, and the target graph is untouched.\n */\nexport class OmnigraphDriftError extends Error {\n override readonly name = 'OmnigraphDriftError';\n readonly graphId: string;\n readonly branch: string;\n readonly headBefore: string;\n readonly headAfter: string;\n constructor(graphId: string, branch: string, headBefore: string, headAfter: string) {\n super(\n `omnigraph: branch '${branch}' of graph '${graphId}' changed during export ` +\n `(head ${headBefore} → ${headAfter}); session aborted per driftPolicy (spec B.2: ` +\n `service:omnigraph-source-changed-during-export)`,\n );\n this.graphId = graphId;\n this.branch = branch;\n this.headBefore = headBefore;\n this.headAfter = headAfter;\n }\n}\n\n/** The one load path a v1 source exposes (B.10). */\nexport interface OmnigraphSource {\n /**\n * Stream one export of the configured graph/branch into `target` via a\n * `purpose:'replace'` ingest session. Resolves with the revision-stamped\n * result; rejects with the session aborted and the target untouched.\n * `signal` aborts both the HTTP stream and the session.\n */\n load(target: IngestTarget, signal?: AbortSignal): Promise<OmnigraphLoadResult>;\n}\n\n// ---------------------------------------------------------------------------\n// Small pure helpers\n// ---------------------------------------------------------------------------\n\n/** FNV-1a 64-bit over the UTF-8 bytes of `s`, as 16 lowercase hex chars. */\nfunction hash64(s: string): string {\n const MASK64 = 0xffffffffffffffffn;\n let h = 0xcbf29ce484222325n;\n for (const b of new TextEncoder().encode(s)) {\n h ^= BigInt(b);\n h = (h * 0x100000001b3n) & MASK64;\n }\n return h.toString(16).padStart(16, '0');\n}\n\n/**\n * The canonical B.2 source revision: a hash of the readable `dataRef` object\n * `{ graphId, branch, headBefore, headAfter, schemaFingerprint }` (`dataRef`\n * itself retains the readable form).\n */\nfunction canonicalSourceRevision(ref: OmnigraphDataRef): string {\n return `og:${hash64(\n JSON.stringify([ref.graphId, ref.branch, ref.headBefore, ref.headAfter, ref.schemaFingerprint]),\n )}`;\n}\n\n/** `'0.8.1'` → `'0.8'`; null when unparseable. */\nfunction majorMinor(version: string): string | null {\n const m = /^(\\d+)\\.(\\d+)/.exec(version);\n return m ? `${m[1]}.${m[2]}` : null;\n}\n\n/** CallOptions without an explicit-undefined signal (exactOptionalPropertyTypes). */\nfunction callOpts(signal: AbortSignal | undefined): CallOptions {\n return signal ? { signal } : {};\n}\n\nfunction throwIfAborted(signal: AbortSignal | undefined): void {\n if (signal?.aborted) {\n throw new DOMException('omnigraph: load aborted by caller signal', 'AbortError');\n }\n}\n\nfunction isAbortError(err: unknown): boolean {\n return err instanceof Error && err.name === 'AbortError';\n}\n\n/**\n * B.9: SDK typed errors (NetworkError, ConflictError, …) never leak across\n * the public surface — rethrow as plain `Error` with a stable prefix.\n * Abort rejections and this package's own errors pass through unchanged.\n */\nfunction mapError(err: unknown): unknown {\n if (err instanceof OmnigraphError && !isAbortError(err)) {\n return new Error(`omnigraph: ${err.name} (status ${err.status}): ${err.message}`);\n }\n return err;\n}\n\nfunction pushUnique(warnings: string[], warning: string): void {\n if (!warnings.includes(warning)) warnings.push(warning);\n}\n\n// ---------------------------------------------------------------------------\n// createOmnigraphSource\n// ---------------------------------------------------------------------------\n\ninterface AttemptSuccess {\n kind: 'done';\n sourceRevision: string;\n dataRef: OmnigraphDataRef;\n counts: { lines: number; nodes: number; edges: number; bytes: number };\n}\n\ninterface AttemptRetry {\n kind: 'retry';\n}\n\n/**\n * Create a v1 export-backed data source (B.2/B.10). The client must be\n * preconfigured (B.1): in the browser a safe same-origin or public client —\n * there is deliberately no `baseUrl`/`token` option here. Authenticated\n * construction lives only in `@modernrelay/orbit-omnigraph/server`.\n */\nexport function createOmnigraphSource(options: OmnigraphSourceOptions): OmnigraphSource {\n const graphId = options.graphId;\n const branch = options.branch ?? 'main';\n const batchSize = Math.max(1, Math.floor(options.batchSize ?? DEFAULT_BATCH_SIZE));\n const maxPendingBytes = options.maxPendingBytes ?? DEFAULT_MAX_PENDING_BYTES;\n if (!Number.isSafeInteger(maxPendingBytes) || maxPendingBytes <= 0) {\n throw new TypeError(\n 'createOmnigraphSource: maxPendingBytes must be a positive safe integer',\n );\n }\n const driftPolicy = options.driftPolicy ?? 'reject';\n const onProgress = options.onProgress;\n const typeNames = options.typeNames === undefined ? undefined : [...options.typeNames];\n /** Every call is scoped to the graph (SDK cluster routing). */\n const client: Omnigraph = options.client.graph(graphId);\n const datasetKey = `og:${graphId}:${branch}`;\n\n async function newestHead(signal: AbortSignal | undefined): Promise<string> {\n const commits = await client.commits.list({ branch }, callOpts(signal));\n const newest = commits[0]; // SDK: most recent first\n if (newest === undefined) {\n throw new Error(\n `omnigraph: branch '${branch}' of graph '${graphId}' has no commits — nothing to export`,\n );\n }\n return newest.graphCommitId;\n }\n\n async function runAttempt(\n target: IngestTarget,\n signal: AbortSignal | undefined,\n warnings: string[],\n attempt: number,\n ): Promise<AttemptSuccess | AttemptRetry> {\n throwIfAborted(signal);\n // Pin the target's SOURCE lineage before any remote work: a competing\n // replace/snapshot landing mid-stream must fail the load. Overlay-only\n // model advances are benign for a replace commit (it supersedes overlays\n // anyway), so the CAS base itself is read fresh at each beginIngest —\n // pinning it here made long accept-warn streams fail late (and livelock)\n // under unrelated concurrent overlay traffic (PR #8 follow-up).\n const baseSource = target.getRevisions().source;\n\n // The before/after head bracket must enclose BOTH the schema read and the\n // export. Otherwise a schema migration between schema.get() and the first\n // head sample could look quiescent while we normalize with stale types.\n const headBefore = await newestHead(signal);\n\n // Endpoint-type resolution + wire-type normalization + the fingerprint\n // half of the revision stamp (B.2/B.3/B.6).\n const schemaSource = (await client.schema.get(callOpts(signal))).schemaSource;\n const schema: PgSchema = parsePgSchema(schemaSource);\n const fingerprint = schemaFingerprint(schemaSource);\n for (const hazard of bigIntKeyWarnings(schema)) {\n pushUnique(\n warnings,\n `omnigraph: ${hazard.type}.${hazard.property} is a 64-bit integer used as identity — ` +\n `JSON parsing silently rounds values past ±2^53, which can collapse distinct ids (B.6)`,\n );\n }\n // NOTE (B.6): a schema-declared `type` property needs no warning — the\n // adapter's discriminator lives at the namespaced ORBIT_TYPE_KEY, so the\n // source's own `type` loads as ordinary data with nothing to report.\n\n const provisionalRef: OmnigraphDataRef = {\n graphId,\n branch,\n headBefore,\n headAfter: headBefore, // provisional: assume quiescence, verify after the stream\n schemaFingerprint: fingerprint,\n };\n const provisionalRevision = canonicalSourceRevision(provisionalRef);\n\n function beginSession(sourceRevision: string): IngestSession {\n const now = target.getRevisions();\n if (now.source !== baseSource) {\n throw new Error(\n `omnigraph: the target's source lineage changed while the load was streaming ` +\n `(a competing replace or snapshot landed); aborting instead of overwriting it`,\n );\n }\n return target.beginIngest({\n purpose: 'replace',\n datasetKey,\n sourceRevision,\n baseModelRevision: now.model,\n maxPendingBytes,\n });\n }\n\n // `accept-warn` cannot open a truthful session until headAfter is known.\n // Buffer only that opt-in policy; rejecting/retrying loads keep streaming\n // directly into the atomic target under the provisional revision.\n let session: IngestSession | undefined =\n driftPolicy === 'accept-warn' ? undefined : beginSession(provisionalRevision);\n const requestId = hash64(`${datasetKey}|${provisionalRevision}|${attempt}`);\n\n interface BufferedBatch {\n nodes: GraphNode[];\n edges: GraphEdge[];\n bytes: number;\n progress: { lines: number; nodes: number; edges: number; bytes: number };\n }\n const bufferedBatches: Array<BufferedBatch | undefined> | undefined =\n driftPolicy === 'accept-warn' ? [] : undefined;\n\n let lines = 0;\n let nodeCount = 0;\n let edgeCount = 0;\n let bytes = 0;\n let unknown = 0;\n let sequence = 0;\n let pendingNodes: GraphNode[] = [];\n let pendingEdges: GraphEdge[] = [];\n let pendingBytes = 0;\n let acceptedBytes = 0;\n\n /**\n * Replace ingestion is atomic, so neither core staging nor accept-warn's\n * adapter buffer can drain before commit. Enforce the same finite\n * whole-load budget before retaining the next normalized row.\n */\n function accountAcceptedBytes(lineBytes: number): void {\n const next = acceptedBytes + lineBytes;\n if (next > maxPendingBytes) {\n throw new OrbitOperationError(\n { code: 'queue-overflow', queuedBytes: next, limit: maxPendingBytes },\n `omnigraph: export rows require at least ${next} bytes, exceeding ` +\n `maxPendingBytes ${maxPendingBytes}; load aborted before commit`,\n );\n }\n acceptedBytes = next;\n pendingBytes += lineBytes;\n }\n\n /** Await every append; retain bounded adapter-side pending batches (B.2). */\n async function flush(): Promise<void> {\n if (pendingNodes.length === 0 && pendingEdges.length === 0) return;\n const nodes = pendingNodes;\n const edges = pendingEdges;\n const batchBytes = pendingBytes;\n const progress = { lines, nodes: nodeCount, edges: edgeCount, bytes };\n pendingNodes = [];\n pendingEdges = [];\n pendingBytes = 0;\n\n if (bufferedBatches !== undefined) {\n bufferedBatches.push({ nodes, edges, bytes: batchBytes, progress });\n return;\n }\n\n const activeSession = session;\n if (activeSession === undefined) throw new Error('omnigraph: internal missing ingest session');\n const batch: IngestBatch = {\n sequence,\n batchId: `og:${requestId}:${sequence}`,\n bytes: batchBytes,\n };\n if (edges.length > 0) batch.edges = edges;\n if (nodes.length > 0) batch.nodes = nodes;\n sequence += 1;\n await activeSession.append(batch);\n onProgress?.(progress);\n }\n\n /** Commit buffered accept-warn rows once, under the now-known final revision. */\n async function commitBuffered(sourceRevision: string): Promise<void> {\n if (bufferedBatches === undefined) {\n throw new Error('omnigraph: internal missing accept-warn batch buffer');\n }\n const finalSession = beginSession(sourceRevision);\n session = finalSession;\n const finalRequestId = hash64(`${datasetKey}|${sourceRevision}|${attempt}`);\n for (let i = 0; i < bufferedBatches.length; i++) {\n throwIfAborted(signal);\n const buffered = bufferedBatches[i];\n if (buffered === undefined) continue;\n // Release the buffer's array slot as soon as ownership passes to the\n // target session; the local `buffered` reference dies this iteration.\n bufferedBatches[i] = undefined;\n const batch: IngestBatch = {\n sequence: i,\n batchId: `og:${finalRequestId}:${i}`,\n bytes: buffered.bytes,\n };\n if (buffered.edges.length > 0) batch.edges = buffered.edges;\n if (buffered.nodes.length > 0) batch.nodes = buffered.nodes;\n await finalSession.append(batch);\n onProgress?.(buffered.progress);\n }\n bufferedBatches.length = 0;\n await finalSession.commit();\n }\n\n // Bind the iterator ONCE (B.2: each iteration re-issues the request) and\n // make sure early exits cancel the underlying stream via `return()`.\n const exportInput: ExportInput = { branch, ...(typeNames !== undefined ? { typeNames } : {}) };\n const iterator = client\n .export<Record<string, unknown>>(exportInput, callOpts(signal))\n [Symbol.asyncIterator]();\n\n try {\n for (;;) {\n throwIfAborted(signal);\n const step = await iterator.next();\n if (step.done === true) break;\n const line = step.value;\n lines += 1;\n // The SDK exposes parsed rows rather than raw chunks. Re-serialize to\n // UTF-8 and include one NDJSON newline: a conservative, consistent\n // accounting unit for progress, batches, and the whole-load cap.\n const lineBytes = UTF8_ENCODER.encode(JSON.stringify(line)).byteLength + 1;\n bytes += lineBytes;\n const classified = classifyExportLine(line);\n if (classified.kind === 'node') {\n const node = normalizeNode(classified, schema);\n accountAcceptedBytes(lineBytes);\n pendingNodes.push(node);\n nodeCount += 1;\n } else if (classified.kind === 'edge') {\n const edge = normalizeEdge(classified, schema);\n accountAcceptedBytes(lineBytes);\n pendingEdges.push(edge);\n edgeCount += 1;\n } else {\n unknown += 1;\n }\n if (pendingNodes.length + pendingEdges.length >= batchSize) await flush();\n }\n await flush();\n\n if (unknown > 0) {\n pushUnique(warnings, `omnigraph: skipped ${unknown} unrecognized export line(s) (B.2)`);\n }\n\n // Revision stamp, second half: the head AFTER the stream, captured\n // BEFORE commit (B.2).\n throwIfAborted(signal);\n const headAfter = await newestHead(signal);\n const counts = { lines, nodes: nodeCount, edges: edgeCount, bytes };\n\n if (driftPolicy === 'accept-warn') {\n const finalRef: OmnigraphDataRef = { ...provisionalRef, headAfter };\n const finalRevision = canonicalSourceRevision(finalRef);\n if (headAfter !== headBefore) {\n pushUnique(\n warnings,\n `omnigraph: branch '${branch}' advanced during export (head ${headBefore} → ` +\n `${headAfter}); committed under the canonical final revision per ` +\n `driftPolicy:'accept-warn' — dataRef records both heads (B.2)`,\n );\n }\n await commitBuffered(finalRevision);\n return { kind: 'done', sourceRevision: finalRevision, dataRef: finalRef, counts };\n }\n\n if (headAfter === headBefore) {\n const activeSession = session;\n if (activeSession === undefined) throw new Error('omnigraph: internal missing ingest session');\n await activeSession.commit();\n return { kind: 'done', sourceRevision: provisionalRevision, dataRef: provisionalRef, counts };\n }\n\n // Drift (B.2: service:omnigraph-source-changed-during-export).\n const activeSession = session;\n if (activeSession === undefined) throw new Error('omnigraph: internal missing ingest session');\n await activeSession.abort('omnigraph: source changed during export (B.2)');\n if (driftPolicy === 'retry-once' && attempt === 1) {\n pushUnique(\n warnings,\n `omnigraph: branch '${branch}' advanced during export (head ${headBefore} → ` +\n `${headAfter}); load restarted once per driftPolicy:'retry-once' (B.2)`,\n );\n return { kind: 'retry' };\n }\n throw new OmnigraphDriftError(graphId, branch, headBefore, headAfter);\n } catch (err) {\n if (session?.state === 'open') {\n try {\n await session.abort(err);\n } catch {\n // the original failure wins\n }\n }\n throw mapError(err);\n } finally {\n // Cancel the underlying NDJSON stream on any non-exhausted exit.\n try {\n await iterator.return?.();\n } catch {\n // stream teardown must never mask the outcome\n }\n }\n }\n\n async function load(target: IngestTarget, signal?: AbortSignal): Promise<OmnigraphLoadResult> {\n const warnings: string[] = [];\n\n // B.1: surface an SDK/server major.minor mismatch — a warning, never a\n // hard failure.\n let serverVersion: string;\n try {\n serverVersion = (await client.health(callOpts(signal))).version;\n } catch (err) {\n throw mapError(err);\n }\n const server = majorMinor(serverVersion);\n const sdk = majorMinor(SERVER_VERSION);\n if (server === null || server !== sdk) {\n warnings.push(\n `omnigraph: server version ${serverVersion} does not match the SDK-pinned server ` +\n `version ${SERVER_VERSION} (major.minor differ) — SDK behavior is undefined ` +\n `against this server (B.1)`,\n );\n }\n\n for (let attempt = 1; ; attempt += 1) {\n let outcome: AttemptSuccess | AttemptRetry;\n try {\n outcome = await runAttempt(target, signal, warnings, attempt);\n } catch (err) {\n // Covers SDK errors thrown before the session/stream phase\n // (schema.get, commits.list); mapError is a no-op on already-mapped\n // or package-owned errors (B.9).\n throw mapError(err);\n }\n if (outcome.kind === 'retry') continue;\n return {\n sourceRevision: outcome.sourceRevision,\n dataRef: outcome.dataRef,\n counts: outcome.counts,\n serverVersion,\n warnings,\n };\n }\n }\n\n return { load };\n}\n","/**\n * B.7 stored-query `SearchService` (§16.5).\n *\n * A stored query using `bm25`/`fuzzy`/`nearest`/`rrf` with\n * `order { score desc } limit K` returns entity rows plus a score column; this\n * module wires it as a §16.5 `SearchService` via\n * `og.queries.invoke(name, { params, branch })`, passing\n * `RequestContext.signal` through SDK `CallOptions`. Core performs revision\n * admission — the service only declares `revisionDependencies: ['source']`\n * (results come from the server-side branch, so they are invalidated by a\n * source change, never by client-side model/scope drift).\n *\n * Identity (B.3): Omnigraph ids are unique per type only, and a query row\n * carries no type discriminator of its own — so the adapter REQUIRES a\n * caller-supplied column→node-type mapping (`typeOf`), either a\n * `{ column: NodeType }` record keyed by the projection columns that hold\n * bare-variable node structs, or a per-row function. Every returned id is\n * qualified through `encodeSourceId(nodeType, physicalId)` — the same codec\n * every other adapter path uses — so results round-trip `decodeSourceId` and\n * match export-loaded node ids exactly.\n *\n * v1 caveat (B.7): search runs server-side over the WHOLE branch, so against\n * a partial export load it can return ids outside the loaded set. §16.5\n * classifies those as `'not-loaded'` at activation; constrain the stored\n * query to the loaded types, or load the full graph, to avoid the mismatch.\n *\n * Error surface (B.9): SDK typed errors never cross this package's public\n * surface — they rethrow as plain `Error`s with the stable `omnigraph:`\n * prefix. Abort rejections pass through unchanged.\n */\n\nimport { OmnigraphError } from '@modernrelay/omnigraph';\nimport type { InvokeQueryInput, Omnigraph } from '@modernrelay/omnigraph';\nimport type { RequestContext, SearchResult, SearchService } from '@modernrelay/orbit-core';\n\nimport { encodeSourceId } from './idCodec';\n\n/** One stored-query result row: projection column → value. Bare-variable\n * projections (`return { $s }`) hold whole-node structs including `id`. */\nexport type OmnigraphSearchRow = Record<string, unknown>;\n\n/**\n * The required B.3 column→node-type mapping:\n *\n * - a record `{ '$s': 'Signal' }` — the FIRST listed column present in a row\n * with a node struct supplies the physical id, encoded under the mapped\n * type name;\n * - or a per-row function returning the node type name — the row's first\n * node-struct column (row key order) supplies the physical id.\n */\nexport type OmnigraphSearchTypeOf =\n | ((row: OmnigraphSearchRow) => string)\n | Readonly<Record<string, string>>;\n\nexport interface OmnigraphSearchServiceOptions<N = Record<string, unknown>> {\n /** A **preconfigured** SDK client (B.1) — no `baseUrl`/`token` here. */\n client: Omnigraph;\n /** Cluster graph id; the invoke is scoped via `client.graph(graphId)`. */\n graphId: string;\n /** Branch the stored query reads (B.7). Default `'main'`. */\n branch?: string;\n /** Registry name of the stored search query (`POST /queries/{name}`).\n * Invoking a known name works whether or not it is `mcp.expose`d. */\n queryName: string;\n /** Builds the stored query's `params` object from the §16.5 call.\n * Default: `(q, limit) => ({ q, limit })`. */\n params?: (q: string, limit: number) => Record<string, unknown>;\n /** REQUIRED B.3 mapping from row to node type — see\n * {@link OmnigraphSearchTypeOf}. */\n typeOf: OmnigraphSearchTypeOf;\n /** Column whose value becomes `label` (String()-coerced when present).\n * Default: the first string-valued column in row key order. */\n labelColumn?: string;\n /** Full custom row→result escape hatch: overrides the default mapping\n * (including `typeOf`/`labelColumn`); return `null` to skip a row. The\n * returned `id` MUST already be B.3-encoded via `encodeSourceId`. */\n mapRow?: (row: OmnigraphSearchRow) => SearchResult<N> | null;\n}\n\n// ---------------------------------------------------------------------------\n// Small pure helpers\n// ---------------------------------------------------------------------------\n\nfunction defaultParams(q: string, limit: number): Record<string, unknown> {\n return { q, limit };\n}\n\nfunction throwIfAborted(signal: AbortSignal, queryName: string): void {\n if (signal.aborted) {\n throw new DOMException(\n `omnigraph: search query '${queryName}' aborted by request signal`,\n 'AbortError',\n );\n }\n}\n\nfunction isAbortError(err: unknown): boolean {\n return err instanceof Error && err.name === 'AbortError';\n}\n\n/** B.9: SDK typed errors rethrow as plain prefixed `Error`s; abort rejections\n * and everything else pass through unchanged. */\nfunction mapError(err: unknown): unknown {\n if (err instanceof OmnigraphError && !isAbortError(err)) {\n return new Error(`omnigraph: ${err.name} (status ${err.status}): ${err.message}`);\n }\n return err;\n}\n\n/** A bare-variable projection value: a whole-node struct carrying its\n * physical id (B.5 note — `return { $s }` yields `{ id, ...props }`). */\nfunction isNodeStruct(value: unknown): value is { id: string } {\n return (\n typeof value === 'object' &&\n value !== null &&\n !Array.isArray(value) &&\n typeof (value as { id?: unknown }).id === 'string'\n );\n}\n\n/** The invoke response must be a READ envelope with tabular rows — a stored\n * mutation (Change envelope) or malformed body is a hard error. */\nfunction extractRows(response: unknown, queryName: string): readonly OmnigraphSearchRow[] {\n const rows = (response as { rows?: unknown }).rows;\n if (!Array.isArray(rows)) {\n throw new Error(\n `omnigraph: stored query '${queryName}' did not return a read envelope with rows — ` +\n `the B.7 search query must be a stored READ query (not a mutation)`,\n );\n }\n for (const row of rows) {\n if (typeof row !== 'object' || row === null || Array.isArray(row)) {\n throw new Error(\n `omnigraph: stored query '${queryName}' returned a non-object row — ` +\n `expected column→value row objects`,\n );\n }\n }\n return rows as OmnigraphSearchRow[];\n}\n\ninterface ResolvedIdentity {\n kind: string;\n sourceId: string;\n}\n\n/** Resolve the B.3 `(nodeType, physicalId)` pair for one row via `typeOf`. */\nfunction resolveIdentity(\n row: OmnigraphSearchRow,\n typeOf: OmnigraphSearchTypeOf,\n queryName: string,\n): ResolvedIdentity {\n if (typeof typeOf === 'function') {\n const kind = typeOf(row);\n if (typeof kind !== 'string' || kind.length === 0) {\n throw new Error(\n `omnigraph: typeOf returned ${JSON.stringify(kind)} for a '${queryName}' search row — ` +\n `it must return a non-empty node type name (B.3)`,\n );\n }\n for (const key of Object.keys(row)) {\n const value = row[key];\n if (isNodeStruct(value)) return { kind, sourceId: value.id };\n }\n throw new Error(\n `omnigraph: '${queryName}' search row has no node-struct column (an object with a ` +\n `string 'id') — project the matched entity bare (return { $s }) so its physical ` +\n `id is available (B.7)`,\n );\n }\n for (const [column, kind] of Object.entries(typeOf)) {\n const value = row[column];\n if (isNodeStruct(value)) return { kind, sourceId: value.id };\n }\n throw new Error(\n `omnigraph: '${queryName}' search row has no node struct under the mapped column(s) ` +\n `${JSON.stringify(Object.keys(typeOf))} — the typeOf record must key the ` +\n `bare-variable projection column(s) (B.3)`,\n );\n}\n\n// ---------------------------------------------------------------------------\n// createOmnigraphSearchService\n// ---------------------------------------------------------------------------\n\n/**\n * Create the B.7 stored-query search service. Plug it into core as\n * `services.search`; the instance owns `RequestContext` creation,\n * revision-keyed caching, supersede cancellation, and stale-result rejection\n * at admission (§16.5).\n */\nexport function createOmnigraphSearchService<N = Record<string, unknown>>(\n options: OmnigraphSearchServiceOptions<N>,\n): SearchService<N> {\n const { graphId, queryName, typeOf, labelColumn, mapRow } = options;\n const branch = options.branch ?? 'main';\n const buildParams = options.params ?? defaultParams;\n /** Every call is scoped to the graph (SDK cluster routing). */\n const client: Omnigraph = options.client.graph(graphId);\n\n function mapRowDefault(row: OmnigraphSearchRow): SearchResult<N> {\n const { kind, sourceId } = resolveIdentity(row, typeOf, queryName);\n const result: SearchResult<N> = { id: encodeSourceId(kind, sourceId) };\n\n const score = row['score'];\n if (typeof score === 'number' && Number.isFinite(score)) result.score = score;\n\n if (labelColumn !== undefined) {\n const value = row[labelColumn];\n if (typeof value === 'string' || typeof value === 'number') result.label = String(value);\n } else {\n for (const key of Object.keys(row)) {\n if (key === 'score') continue; // the score lane never doubles as a label\n const value = row[key];\n if (typeof value === 'string') {\n result.label = value;\n break;\n }\n }\n }\n return result;\n }\n\n return {\n revisionDependencies: ['source'],\n async search(\n q: string,\n searchOptions: { limit: number },\n ctx: RequestContext,\n ): Promise<readonly SearchResult<N>[]> {\n throwIfAborted(ctx.signal, queryName);\n const limit = Number.isFinite(searchOptions.limit) ? Math.floor(searchOptions.limit) : 0;\n if (q.length === 0 || limit <= 0) return [];\n\n const input: InvokeQueryInput = { branch, params: buildParams(q, limit) };\n let response: unknown;\n try {\n response = await client.queries.invoke(queryName, input, { signal: ctx.signal });\n } catch (err) {\n throw mapError(err);\n }\n throwIfAborted(ctx.signal, queryName);\n\n const rows = extractRows(response, queryName);\n const results: SearchResult<N>[] = [];\n for (const row of rows) {\n if (results.length >= limit) break; // defensive cap — the query owns K\n const mapped = mapRow !== undefined ? mapRow(row) : mapRowDefault(row);\n if (mapped !== null) results.push(mapped);\n }\n return results;\n },\n };\n}\n"]}
@@ -0,0 +1,32 @@
1
+ import { FetchLike, Omnigraph } from '@modernrelay/omnigraph';
2
+
3
+ /**
4
+ * @modernrelay/orbit-omnigraph/server — **SERVER-ONLY** entry (spec B.1/B.9).
5
+ *
6
+ * This module is the ONLY place in the adapter where an authenticated
7
+ * Omnigraph client is constructed. It must never be imported from browser
8
+ * code: `omnigraph-server` ships no CORS configuration and uses static
9
+ * bearer tokens (secret material), so browser deployments pass a
10
+ * preconfigured safe same-origin/public client to `createOmnigraphSource()`
11
+ * instead — typically routing reads through a proxy/BFF (B.9). The
12
+ * `client-bundle exclusion gate (scripts/pack-smoke.mjs, sentinel
13
+ * `createOmnigraphServerClient`) enforces that the browser entry never pulls
14
+ * this module in.
15
+ */
16
+
17
+ interface OmnigraphServerClientOptions {
18
+ /** Base URL of the omnigraph-server, e.g. `http://127.0.0.1:8080`. */
19
+ baseUrl: string;
20
+ /** Static bearer token — secret material; must stay server-side (B.9). */
21
+ token: string;
22
+ /** Inject a custom fetch (tracing, agents, testing). */
23
+ fetch?: FetchLike;
24
+ }
25
+ /**
26
+ * Construct an authenticated Omnigraph SDK client (server-only, B.1). Pass
27
+ * the result to `createOmnigraphSource({ client, ... })` in server code, or
28
+ * hand graph-scoped clones out via `client.graph(id)`.
29
+ */
30
+ declare function createOmnigraphServerClient(options: OmnigraphServerClientOptions): Omnigraph;
31
+
32
+ export { type OmnigraphServerClientOptions, createOmnigraphServerClient };
package/dist/server.js ADDED
@@ -0,0 +1,12 @@
1
+ import { Omnigraph } from '@modernrelay/omnigraph';
2
+
3
+ // src/server.ts
4
+ function createOmnigraphServerClient(options) {
5
+ const opts = { baseUrl: options.baseUrl, token: options.token };
6
+ if (options.fetch !== void 0) opts.fetch = options.fetch;
7
+ return new Omnigraph(opts);
8
+ }
9
+
10
+ export { createOmnigraphServerClient };
11
+ //# sourceMappingURL=server.js.map
12
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server.ts"],"names":[],"mappings":";;;AA+BO,SAAS,4BAA4B,OAAA,EAAkD;AAC5F,EAAA,MAAM,OAAyB,EAAE,OAAA,EAAS,QAAQ,OAAA,EAAS,KAAA,EAAO,QAAQ,KAAA,EAAM;AAChF,EAAA,IAAI,OAAA,CAAQ,KAAA,KAAU,MAAA,EAAW,IAAA,CAAK,QAAQ,OAAA,CAAQ,KAAA;AACtD,EAAA,OAAO,IAAI,UAAU,IAAI,CAAA;AAC3B","file":"server.js","sourcesContent":["/**\n * @modernrelay/orbit-omnigraph/server — **SERVER-ONLY** entry (spec B.1/B.9).\n *\n * This module is the ONLY place in the adapter where an authenticated\n * Omnigraph client is constructed. It must never be imported from browser\n * code: `omnigraph-server` ships no CORS configuration and uses static\n * bearer tokens (secret material), so browser deployments pass a\n * preconfigured safe same-origin/public client to `createOmnigraphSource()`\n * instead — typically routing reads through a proxy/BFF (B.9). The\n * `client-bundle exclusion gate (scripts/pack-smoke.mjs, sentinel\n * `createOmnigraphServerClient`) enforces that the browser entry never pulls\n * this module in.\n */\n\nimport { Omnigraph } from '@modernrelay/omnigraph';\nimport type { FetchLike, OmnigraphOptions } from '@modernrelay/omnigraph';\n\nexport interface OmnigraphServerClientOptions {\n /** Base URL of the omnigraph-server, e.g. `http://127.0.0.1:8080`. */\n baseUrl: string;\n /** Static bearer token — secret material; must stay server-side (B.9). */\n token: string;\n /** Inject a custom fetch (tracing, agents, testing). */\n fetch?: FetchLike;\n}\n\n/**\n * Construct an authenticated Omnigraph SDK client (server-only, B.1). Pass\n * the result to `createOmnigraphSource({ client, ... })` in server code, or\n * hand graph-scoped clones out via `client.graph(id)`.\n */\nexport function createOmnigraphServerClient(options: OmnigraphServerClientOptions): Omnigraph {\n const opts: OmnigraphOptions = { baseUrl: options.baseUrl, token: options.token };\n if (options.fetch !== undefined) opts.fetch = options.fetch;\n return new Omnigraph(opts);\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@modernrelay/orbit-omnigraph",
3
+ "version": "0.2.0",
4
+ "description": "Omnigraph adapter for Orbit: og.export() loader with drift policies, .pg schema parser, and typed codegen.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/ModernRelay/orbit.git",
9
+ "directory": "packages/omnigraph"
10
+ },
11
+ "type": "module",
12
+ "sideEffects": false,
13
+ "files": [
14
+ "dist"
15
+ ],
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/index.d.ts",
19
+ "default": "./dist/index.js"
20
+ },
21
+ "./server": {
22
+ "types": "./dist/server.d.ts",
23
+ "default": "./dist/server.js"
24
+ }
25
+ },
26
+ "publishConfig": {
27
+ "access": "public"
28
+ },
29
+ "bin": {
30
+ "orbit-omnigraph-codegen": "./dist/codegen-cli.js"
31
+ },
32
+ "dependencies": {
33
+ "@modernrelay/omnigraph": "0.8.0",
34
+ "@modernrelay/orbit-core": "0.2.0"
35
+ },
36
+ "devDependencies": {
37
+ "typescript": "^5.6.0",
38
+ "vitest": "^3.0.0"
39
+ },
40
+ "scripts": {
41
+ "build": "tsup",
42
+ "test": "vitest run",
43
+ "typecheck": "tsc --noEmit"
44
+ }
45
+ }