@hraness/oh 0.2.6 → 0.3.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/README.md +116 -12
- package/dist/canonical.d.ts.map +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +91 -19
- package/dist/cloudflare-embedding.d.ts +104 -0
- package/dist/cloudflare-embedding.d.ts.map +1 -0
- package/dist/graph.d.ts.map +1 -1
- package/dist/index.js +90 -18
- package/dist/libsql-semantic.d.ts +111 -0
- package/dist/libsql-semantic.d.ts.map +1 -0
- package/dist/libsql.js +105 -18
- package/dist/memory-page.d.ts +2 -0
- package/dist/memory-page.d.ts.map +1 -0
- package/dist/memory-page.js +725 -0
- package/dist/memory-pages.d.ts +76 -0
- package/dist/memory-pages.d.ts.map +1 -0
- package/dist/memory.d.ts +1 -0
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +478 -18
- package/dist/projection-public.js +105 -18
- package/dist/projection-suss.js +105 -18
- package/dist/sdk.js +90 -18
- package/dist/semantic-cloud.d.ts +3 -0
- package/dist/semantic-cloud.d.ts.map +1 -0
- package/dist/semantic-cloud.js +1843 -0
- package/dist/semantic.d.ts.map +1 -1
- package/dist/semantic.js +104 -21
- package/dist/sqlite/index.js +90 -18
- package/dist/store.js +105 -18
- package/dist/sync.js +90 -18
- package/package.json +10 -2
- package/skills/oh/SKILL.md +28 -2
- package/spec/README.md +11 -3
- package/spec/manifest.json +9 -1
- package/spec/v1/cloudflare-embedding-profile.json +13 -0
- package/spec/v1/cloudflare-embedding-renderer.json +8 -0
- package/spec/v1/memory-page.md +153 -0
- package/spec/v1/memory-page.schema.json +154 -0
- package/spec/v1/memory.md +18 -0
- package/spec/v1/migration.md +13 -0
- package/spec/v1/semantic-cloud.md +87 -0
- package/src/canonical.ts +28 -13
- package/src/cli.ts +1 -1
- package/src/cloudflare-embedding.test.ts +306 -0
- package/src/cloudflare-embedding.ts +385 -0
- package/src/contracts.test.ts +20 -0
- package/src/graph.ts +63 -6
- package/src/libsql-semantic.test.ts +478 -0
- package/src/libsql-semantic.ts +1117 -0
- package/src/memory-page.ts +1 -0
- package/src/memory-pages.test.ts +277 -0
- package/src/memory-pages.ts +440 -0
- package/src/memory.ts +2 -0
- package/src/semantic-cloud.ts +2 -0
- package/src/semantic.ts +14 -3
package/README.md
CHANGED
|
@@ -22,9 +22,9 @@ indexes derived and replaceable.
|
|
|
22
22
|
- **Keep local state authoritative.** Records and operations live in one SQLite
|
|
23
23
|
file you control. Sync is an explicit transport seam and accepts only
|
|
24
24
|
fast-forward histories after an exact contract handshake.
|
|
25
|
-
- **Treat search as a view.** FTS5 documents
|
|
26
|
-
derived from current record digests, so
|
|
27
|
-
becoming graph authority.
|
|
25
|
+
- **Treat search as a view.** FTS5 documents, optional local embeddings, and a
|
|
26
|
+
separately scoped hosted cache are derived from current record digests, so
|
|
27
|
+
an index can be rebuilt without becoming graph authority.
|
|
28
28
|
- **Derive without silently asserting.** Positive recursive rules run against
|
|
29
29
|
one exact graph head and fact-pack digest. Their tuples and bounded proofs
|
|
30
30
|
are deterministic, disposable output rather than accepted graph records.
|
|
@@ -40,16 +40,16 @@ direct libSQL authority also support Node 24 serverless runtimes. Install the
|
|
|
40
40
|
exact current release from npm:
|
|
41
41
|
|
|
42
42
|
```sh
|
|
43
|
-
bun add --global @hraness/oh@0.
|
|
43
|
+
bun add --global @hraness/oh@0.3.0
|
|
44
44
|
oh --help
|
|
45
45
|
```
|
|
46
46
|
|
|
47
47
|
The identical package bytes and their checksum are available from the
|
|
48
|
-
[immutable GitHub Release](https://github.com/hraness/oh/releases/tag/v0.
|
|
48
|
+
[immutable GitHub Release](https://github.com/hraness/oh/releases/tag/v0.3.0),
|
|
49
49
|
including the mirrored
|
|
50
|
-
[`hraness-oh-0.
|
|
50
|
+
[`hraness-oh-0.3.0.tgz`](https://github.com/hraness/oh/releases/download/v0.3.0/hraness-oh-0.3.0.tgz)
|
|
51
51
|
and
|
|
52
|
-
[`SHA256SUMS`](https://github.com/hraness/oh/releases/download/v0.
|
|
52
|
+
[`SHA256SUMS`](https://github.com/hraness/oh/releases/download/v0.3.0/SHA256SUMS).
|
|
53
53
|
|
|
54
54
|
Oh writes to `.oh/oh.sqlite` and the `default` space unless you select another
|
|
55
55
|
path or space. Keep `.oh/` out of source control.
|
|
@@ -107,7 +107,7 @@ For a project dependency, pin the same immutable release in `package.json`:
|
|
|
107
107
|
```json
|
|
108
108
|
{
|
|
109
109
|
"dependencies": {
|
|
110
|
-
"@hraness/oh": "0.
|
|
110
|
+
"@hraness/oh": "0.3.0"
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
113
|
```
|
|
@@ -151,7 +151,10 @@ promise interface, `@hraness/oh/libsql` for a direct Node 24 or serverless
|
|
|
151
151
|
authority, `@hraness/oh/sqlite` for the local Bun store, `@hraness/oh/sdk` for
|
|
152
152
|
the local `Oh` facade, `@hraness/oh/sync` for transport seams,
|
|
153
153
|
`@hraness/oh/projection` for recursive derived views, and
|
|
154
|
-
`@hraness/oh/semantic` for the optional local embedding backend
|
|
154
|
+
`@hraness/oh/semantic` for the optional local embedding backend, and
|
|
155
|
+
`@hraness/oh/semantic-cloud` for the Cloudflare EmbeddingGemma plus direct
|
|
156
|
+
libSQL derived-cache adapter. Use the narrow stable `@hraness/oh/memory-page`
|
|
157
|
+
entrypoint for model-neutral page records and `.oh.md` interchange. The
|
|
155
158
|
`@hraness/oh/experimental/memory` subpath composes host-bound working and
|
|
156
159
|
canonical stores behind a smaller agent-facing surface.
|
|
157
160
|
|
|
@@ -345,6 +348,51 @@ proposal; it never promotes it. Read the
|
|
|
345
348
|
[experimental memory specification](spec/v1/memory.md) for the complete
|
|
346
349
|
authority and lifecycle boundary.
|
|
347
350
|
|
|
351
|
+
## Use a memory page or `.oh.md` file
|
|
352
|
+
|
|
353
|
+
A memory page is an ordinary `edition` record with bounded Markdown, explicit
|
|
354
|
+
source observations, and a host-attestation receipt reference. It is useful
|
|
355
|
+
for readable summaries and long-running-agent notes without introducing a
|
|
356
|
+
second memory ontology. Its canonical `.oh.md` form contains the complete
|
|
357
|
+
record key, dependencies, digest, metadata, and body:
|
|
358
|
+
|
|
359
|
+
```ts
|
|
360
|
+
import {
|
|
361
|
+
createOhMemoryPageRecordV1,
|
|
362
|
+
renderOhMemoryPageMarkdownV1,
|
|
363
|
+
} from "@hraness/oh/memory-page";
|
|
364
|
+
|
|
365
|
+
const page = createOhMemoryPageRecordV1({
|
|
366
|
+
dependencies: ["activity:memory-attestation"],
|
|
367
|
+
key: "edition:session-summary",
|
|
368
|
+
value: {
|
|
369
|
+
body: "## Current state\n\nThe provider rollout is paused before activation.",
|
|
370
|
+
createdAt: "2026-08-31T12:00:00.000Z",
|
|
371
|
+
format: "oh.memory-page.v1",
|
|
372
|
+
language: "en",
|
|
373
|
+
provenance: {
|
|
374
|
+
actorId: "host.memory",
|
|
375
|
+
attestationSha256: receiptSha256,
|
|
376
|
+
attestedAt: "2026-08-31T12:00:00.000Z",
|
|
377
|
+
kind: "host-attested",
|
|
378
|
+
v: 1,
|
|
379
|
+
},
|
|
380
|
+
sources: [],
|
|
381
|
+
summary: "The exact resumable session frontier.",
|
|
382
|
+
title: "Session frontier",
|
|
383
|
+
updatedAt: "2026-08-31T12:00:00.000Z",
|
|
384
|
+
v: 1,
|
|
385
|
+
},
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
const portable = renderOhMemoryPageMarkdownV1(page);
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
The page never stores vectors, model IDs, scores, or provider configuration.
|
|
392
|
+
Treat its Markdown and source titles as untrusted data. See the
|
|
393
|
+
[memory-page specification](spec/v1/memory-page.md) for the exact format and
|
|
394
|
+
round-trip rules.
|
|
395
|
+
|
|
348
396
|
## Derive an exact projection
|
|
349
397
|
|
|
350
398
|
The projection subpath is pure TypeScript and runs in Node 24 serverless
|
|
@@ -478,6 +526,62 @@ The exact V1 profile is documented in
|
|
|
478
526
|
all inference stay local. Keyword mode remains available when QMD or the model
|
|
479
527
|
is absent.
|
|
480
528
|
|
|
529
|
+
## Add a hosted semantic cache
|
|
530
|
+
|
|
531
|
+
The hosted adapter uses the same source-record principle with a distinct,
|
|
532
|
+
profile-bound cache. It sends bounded inputs to Cloudflare Workers AI's
|
|
533
|
+
EmbeddingGemma model and stores only float32 vectors, input digests, record
|
|
534
|
+
digests, immutable generation membership, and a published pointer in direct
|
|
535
|
+
libSQL. It stores no title, body, record JSON, or query text.
|
|
536
|
+
|
|
537
|
+
```ts
|
|
538
|
+
import { createClient } from "@libsql/client";
|
|
539
|
+
import {
|
|
540
|
+
OhCloudflareEmbeddingClientV1,
|
|
541
|
+
bootstrapOhLibSqlSemanticCacheV1,
|
|
542
|
+
openOhLibSqlSemanticCacheV1,
|
|
543
|
+
} from "@hraness/oh/semantic-cloud";
|
|
544
|
+
|
|
545
|
+
// Deploy once with a short-lived schema credential.
|
|
546
|
+
const schemaClient = createClient({
|
|
547
|
+
authToken: process.env.OH_SEMANTIC_SCHEMA_TOKEN!,
|
|
548
|
+
url: process.env.OH_SEMANTIC_DATABASE_URL!,
|
|
549
|
+
});
|
|
550
|
+
await bootstrapOhLibSqlSemanticCacheV1(schemaClient);
|
|
551
|
+
schemaClient.close();
|
|
552
|
+
|
|
553
|
+
const client = createClient({
|
|
554
|
+
authToken: process.env.OH_SEMANTIC_RUNTIME_TOKEN!,
|
|
555
|
+
url: process.env.OH_SEMANTIC_DATABASE_URL!,
|
|
556
|
+
});
|
|
557
|
+
const cache = await openOhLibSqlSemanticCacheV1(client, { closeClient: true });
|
|
558
|
+
const embedder = new OhCloudflareEmbeddingClientV1({
|
|
559
|
+
accountId: process.env.CLOUDFLARE_ACCOUNT_ID!,
|
|
560
|
+
apiToken: process.env.CLOUDFLARE_WORKERS_AI_TOKEN!,
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
const staged = await cache.stage({
|
|
564
|
+
authorityId: "thread:research/epoch:1",
|
|
565
|
+
authoritySha256: snapshot.head.recordsSha256,
|
|
566
|
+
documents,
|
|
567
|
+
embeddingClient: embedder,
|
|
568
|
+
generation: snapshot.head.generation,
|
|
569
|
+
});
|
|
570
|
+
await cache.publish({
|
|
571
|
+
authorityId: "thread:research/epoch:1",
|
|
572
|
+
expectedPublishedGeneration: null,
|
|
573
|
+
generation: staged.generation,
|
|
574
|
+
});
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
Search requires the exact current authority generation and record digests, and
|
|
578
|
+
returns nothing from a stale or concurrently replaced head. Purge writes a
|
|
579
|
+
permanent authority tombstone before deleting memberships and unused vectors.
|
|
580
|
+
The same authority ID cannot be resurrected; allocate a new epoch for a new
|
|
581
|
+
lifetime. Hosted failure is a missing convenience lane, never permission to
|
|
582
|
+
weaken exact graph or Datalog operations. Read the
|
|
583
|
+
[hosted semantic-cache specification](spec/v1/semantic-cloud.md).
|
|
584
|
+
|
|
481
585
|
## Sync through libSQL or Turso
|
|
482
586
|
|
|
483
587
|
`createLibSqlOperationSyncTransportV1` accepts the `execute` and `batch` shape
|
|
@@ -545,9 +649,9 @@ keep remote sync explicit.
|
|
|
545
649
|
You can also give an agent this prompt:
|
|
546
650
|
|
|
547
651
|
```text
|
|
548
|
-
Install @hraness/oh@0.
|
|
549
|
-
exact npm tarball and SHA256SUMS are mirrored by the immutable v0.
|
|
550
|
-
https://github.com/hraness/oh/releases/tag/v0.
|
|
652
|
+
Install @hraness/oh@0.3.0 from npm and use its packaged Oh Agent Skill. The
|
|
653
|
+
exact npm tarball and SHA256SUMS are mirrored by the immutable v0.3.0 Release at
|
|
654
|
+
https://github.com/hraness/oh/releases/tag/v0.3.0. Verify the CLI with
|
|
551
655
|
`oh --help` and `oh version`.
|
|
552
656
|
Do not create or modify an Oh database until I name its path and ask you to.
|
|
553
657
|
```
|
package/dist/canonical.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"canonical.d.ts","sourceRoot":"","sources":["../src/canonical.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,IAAI,GAAG,MAAM,GAAG,MAAM,CAAC;AAC7D,MAAM,MAAM,SAAS,GAAG,aAAa,GAAG,SAAS,SAAS,EAAE,GAAG,UAAU,CAAC;AAC1E,MAAM,MAAM,UAAU,GAAG,QAAQ,CAAC;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAAC,CAAC;AAEhE,OAAO,CAAC,MAAM,cAAc,EAAE,OAAO,MAAM,CAAC;AAC5C,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,CAAC,cAAc,CAAC,EAAE,WAAW,CAAA;CAAE,CAAC;AAE5E,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBAEV,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAMxD;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAI9E;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAG7F;
|
|
1
|
+
{"version":3,"file":"canonical.d.ts","sourceRoot":"","sources":["../src/canonical.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,IAAI,GAAG,MAAM,GAAG,MAAM,CAAC;AAC7D,MAAM,MAAM,SAAS,GAAG,aAAa,GAAG,SAAS,SAAS,EAAE,GAAG,UAAU,CAAC;AAC1E,MAAM,MAAM,UAAU,GAAG,QAAQ,CAAC;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAAC,CAAC;AAEhE,OAAO,CAAC,MAAM,cAAc,EAAE,OAAO,MAAM,CAAC;AAC5C,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,CAAC,cAAc,CAAC,EAAE,WAAW,CAAA;CAAE,CAAC;AAE5E,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBAEV,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAMxD;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAI9E;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAG7F;AA+FD,wEAAwE;AACxE,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAEpD;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,SAAmB,GAAG,SAAS,CAc3F;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAE/D;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,SAAS,CAEzD;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,SAAS,GAAG,IAAI,CAI/D;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAOrE;AAED,wBAAgB,YAAY,IAAI,MAAM,CAErC;AAED,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAK/C;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,aAAa,SAAM,GAAG,MAAM,GAAG,IAAI,CAK3E;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,YAAY,SAAY,GAAG,MAAM,GAAG,IAAI,CAUnF;AAED,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,EAAE,GAAG,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,MAAM,GAAG,OAAO,CAEzF;AAED,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,EAAE,GAAG,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,MAAM,GAAG,SAAS,CAAC,EAAE,CAU3F"}
|
package/dist/cli.d.ts
CHANGED
package/dist/cli.js
CHANGED
|
@@ -63,17 +63,27 @@ function encodeCanonical(value, path, ancestors) {
|
|
|
63
63
|
ancestors.add(value);
|
|
64
64
|
try {
|
|
65
65
|
if (Array.isArray(value)) {
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
encoded.push(encodeCanonical(value[index], `${path}[${index}]`, ancestors));
|
|
66
|
+
const lengthDescriptor = Object.getOwnPropertyDescriptor(value, "length");
|
|
67
|
+
const length = lengthDescriptor?.value;
|
|
68
|
+
if (typeof length !== "number" || !Number.isSafeInteger(length) || length < 0) {
|
|
69
|
+
throw new OhValidationError("non-json-property", path, "array has an invalid length descriptor");
|
|
72
70
|
}
|
|
73
|
-
const
|
|
74
|
-
if (
|
|
71
|
+
const ownKeys2 = Reflect.ownKeys(value);
|
|
72
|
+
if (!ownKeys2.includes("length") || ownKeys2.some((key) => key !== "length" && (typeof key !== "string" || !/^(?:0|[1-9][0-9]*)$/u.test(key) || Number(key) >= length))) {
|
|
75
73
|
throw new OhValidationError("non-json-property", path, "array has non-index properties");
|
|
76
74
|
}
|
|
75
|
+
const elements = [];
|
|
76
|
+
for (let index = 0;index < length; index += 1) {
|
|
77
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
|
|
78
|
+
if (descriptor === undefined) {
|
|
79
|
+
throw new OhValidationError("sparse-array", `${path}[${index}]`, "must not contain holes");
|
|
80
|
+
}
|
|
81
|
+
if (!descriptor.enumerable || descriptor.get !== undefined || descriptor.set !== undefined) {
|
|
82
|
+
throw new OhValidationError("non-json-property", `${path}[${index}]`, "must be an enumerable data property");
|
|
83
|
+
}
|
|
84
|
+
elements.push(descriptor.value);
|
|
85
|
+
}
|
|
86
|
+
const encoded = elements.map((element, index) => encodeCanonical(element, `${path}[${index}]`, ancestors));
|
|
77
87
|
return `[${encoded.join(",")}]`;
|
|
78
88
|
}
|
|
79
89
|
if (!isPlainRecord(value)) {
|
|
@@ -83,19 +93,21 @@ function encodeCanonical(value, path, ancestors) {
|
|
|
83
93
|
if (ownKeys.some((key) => typeof key !== "string")) {
|
|
84
94
|
throw new OhValidationError("non-json-property", path, "object has a symbol property");
|
|
85
95
|
}
|
|
96
|
+
const entries = [];
|
|
86
97
|
const keys = ownKeys;
|
|
87
98
|
for (const key of keys) {
|
|
88
99
|
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
89
100
|
if (descriptor === undefined || !descriptor.enumerable || descriptor.get !== undefined || descriptor.set !== undefined) {
|
|
90
101
|
throw new OhValidationError("non-json-property", `${path}.${key}`, "must be an enumerable data property");
|
|
91
102
|
}
|
|
103
|
+
entries.push([key, descriptor.value]);
|
|
92
104
|
}
|
|
93
|
-
|
|
94
|
-
const
|
|
105
|
+
entries.sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0);
|
|
106
|
+
const encodedEntries = entries.map(([key, entryValue]) => {
|
|
95
107
|
assertUnicodeScalarString(key, `${path}.<key>`);
|
|
96
|
-
return `${JSON.stringify(key)}:${encodeCanonical(
|
|
108
|
+
return `${JSON.stringify(key)}:${encodeCanonical(entryValue, `${path}.${key}`, ancestors)}`;
|
|
97
109
|
});
|
|
98
|
-
return `{${
|
|
110
|
+
return `{${encodedEntries.join(",")}}`;
|
|
99
111
|
} finally {
|
|
100
112
|
ancestors.delete(value);
|
|
101
113
|
}
|
|
@@ -207,17 +219,70 @@ var OH_KNOWLEDGE_GRAPH_RECORD_KINDS_V1 = [
|
|
|
207
219
|
"view",
|
|
208
220
|
"vocabulary"
|
|
209
221
|
];
|
|
222
|
+
var KNOWLEDGE_GRAPH_RECORD_KEYS_V1 = [
|
|
223
|
+
"dependencies",
|
|
224
|
+
"key",
|
|
225
|
+
"kind",
|
|
226
|
+
"recordSha256",
|
|
227
|
+
"v",
|
|
228
|
+
"value"
|
|
229
|
+
];
|
|
230
|
+
function exactKnowledgeGraphRecordEnvelopeV1(value) {
|
|
231
|
+
try {
|
|
232
|
+
if (!isPlainRecord(value))
|
|
233
|
+
return null;
|
|
234
|
+
const ownKeys = Reflect.ownKeys(value);
|
|
235
|
+
if (ownKeys.length !== KNOWLEDGE_GRAPH_RECORD_KEYS_V1.length || ownKeys.some((key) => typeof key !== "string") || KNOWLEDGE_GRAPH_RECORD_KEYS_V1.some((key) => !ownKeys.includes(key)))
|
|
236
|
+
return null;
|
|
237
|
+
const detached = {};
|
|
238
|
+
for (const key of KNOWLEDGE_GRAPH_RECORD_KEYS_V1) {
|
|
239
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
240
|
+
if (descriptor === undefined || !descriptor.enumerable || descriptor.get !== undefined || descriptor.set !== undefined)
|
|
241
|
+
return null;
|
|
242
|
+
detached[key] = descriptor.value;
|
|
243
|
+
}
|
|
244
|
+
return detached;
|
|
245
|
+
} catch {
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
function exactGraphDependenciesV1(value) {
|
|
250
|
+
try {
|
|
251
|
+
if (!Array.isArray(value))
|
|
252
|
+
return null;
|
|
253
|
+
const lengthDescriptor = Object.getOwnPropertyDescriptor(value, "length");
|
|
254
|
+
const length = lengthDescriptor?.value;
|
|
255
|
+
if (typeof length !== "number" || !Number.isSafeInteger(length) || length < 0 || length > OH_GRAPH_LIMITS_V1.dependenciesPerRecord)
|
|
256
|
+
return null;
|
|
257
|
+
const ownKeys = Reflect.ownKeys(value);
|
|
258
|
+
if (ownKeys.length !== length + 1 || !ownKeys.includes("length") || ownKeys.some((key) => key !== "length" && (typeof key !== "string" || !/^(?:0|[1-9][0-9]*)$/u.test(key) || Number(key) >= length)))
|
|
259
|
+
return null;
|
|
260
|
+
const detached = [];
|
|
261
|
+
for (let index = 0;index < length; index += 1) {
|
|
262
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
|
|
263
|
+
if (descriptor === undefined || !descriptor.enumerable || descriptor.get !== undefined || descriptor.set !== undefined)
|
|
264
|
+
return null;
|
|
265
|
+
detached.push(descriptor.value);
|
|
266
|
+
}
|
|
267
|
+
return detached;
|
|
268
|
+
} catch {
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
210
272
|
function recordKey(value) {
|
|
211
273
|
return typeof value === "string" && value.length <= 512 && /^[a-z][a-z0-9]*(?:[._:/-][a-z0-9]+)*$/u.test(value) ? value : null;
|
|
212
274
|
}
|
|
213
275
|
function createKnowledgeGraphRecordV1(input) {
|
|
214
|
-
if (!isPlainRecord(input) || !hasExactKeys(input, ["dependencies", "key", "kind", "v", "value"]) || input.v !== 1
|
|
276
|
+
if (!isPlainRecord(input) || !hasExactKeys(input, ["dependencies", "key", "kind", "v", "value"]) || input.v !== 1)
|
|
215
277
|
throw new TypeError("Invalid graph record input.");
|
|
278
|
+
const dependencyInput = exactGraphDependenciesV1(input.dependencies);
|
|
279
|
+
if (dependencyInput === null)
|
|
280
|
+
throw new TypeError("Invalid graph record dependencies.");
|
|
216
281
|
const key = recordKey(input.key);
|
|
217
282
|
const kind = OH_KNOWLEDGE_GRAPH_RECORD_KINDS_V1.find((candidate) => candidate === input.kind);
|
|
218
|
-
if (key === null || kind === undefined
|
|
283
|
+
if (key === null || kind === undefined)
|
|
219
284
|
throw new TypeError("Invalid graph record identity.");
|
|
220
|
-
const dependencies =
|
|
285
|
+
const dependencies = dependencyInput.map(recordKey);
|
|
221
286
|
if (dependencies.some((dependency) => dependency === null) || !orderedUnique(dependencies, String) || dependencies.includes(key)) {
|
|
222
287
|
throw new TypeError("Graph dependencies must be ordered, unique, and non-reflexive.");
|
|
223
288
|
}
|
|
@@ -229,10 +294,17 @@ function createKnowledgeGraphRecordV1(input) {
|
|
|
229
294
|
return { ...payload, recordSha256: canonicalSha256(payload) };
|
|
230
295
|
}
|
|
231
296
|
function parseKnowledgeGraphRecordV1(value) {
|
|
232
|
-
|
|
297
|
+
const envelope = exactKnowledgeGraphRecordEnvelopeV1(value);
|
|
298
|
+
if (envelope === null)
|
|
233
299
|
return null;
|
|
234
|
-
const recordSha256 = parseSha256Hex(
|
|
235
|
-
const
|
|
300
|
+
const recordSha256 = parseSha256Hex(envelope.recordSha256);
|
|
301
|
+
const input = {
|
|
302
|
+
dependencies: envelope.dependencies,
|
|
303
|
+
key: envelope.key,
|
|
304
|
+
kind: envelope.kind,
|
|
305
|
+
v: envelope.v,
|
|
306
|
+
value: envelope.value
|
|
307
|
+
};
|
|
236
308
|
try {
|
|
237
309
|
const created = createKnowledgeGraphRecordV1(input);
|
|
238
310
|
return recordSha256 !== null && created.recordSha256 === recordSha256 ? { ...created, recordSha256 } : null;
|
|
@@ -3072,7 +3144,7 @@ class Oh {
|
|
|
3072
3144
|
|
|
3073
3145
|
// src/cli.ts
|
|
3074
3146
|
import { readFile } from "fs/promises";
|
|
3075
|
-
var OH_PACKAGE_VERSION = "0.
|
|
3147
|
+
var OH_PACKAGE_VERSION = "0.3.0";
|
|
3076
3148
|
var KNOWN_OPTIONS = new Set([
|
|
3077
3149
|
"actor",
|
|
3078
3150
|
"after",
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { type Sha256Hex } from "./canonical";
|
|
2
|
+
/** A hosted embedding space. It is intentionally distinct from the local QMD profile. */
|
|
3
|
+
export declare const OH_CLOUDFLARE_EMBEDDING_PROFILE_V1: Readonly<{
|
|
4
|
+
profileSha256: Sha256Hex;
|
|
5
|
+
dimensions: 768;
|
|
6
|
+
distance: "cosine";
|
|
7
|
+
documentFormat: "title: {title} | text: {content}";
|
|
8
|
+
inputUtf8Bytes: 448;
|
|
9
|
+
model: "@cf/google/embeddinggemma-300m";
|
|
10
|
+
normalization: "l2";
|
|
11
|
+
profileId: "oh.cloudflare.embeddinggemma.v1";
|
|
12
|
+
provider: "cloudflare.workers-ai";
|
|
13
|
+
queryFormat: "task: search result | query: {query}";
|
|
14
|
+
v: 1;
|
|
15
|
+
}>;
|
|
16
|
+
export declare const OH_SEMANTIC_RENDERER_V1: Readonly<{
|
|
17
|
+
rendererSha256: Sha256Hex;
|
|
18
|
+
documentFormat: "title: {title} | text: {content}";
|
|
19
|
+
inputUtf8Bytes: 448;
|
|
20
|
+
rendererId: "oh.embedding-input.utf8-chunks.v1";
|
|
21
|
+
split: "unicode-scalar-greedy";
|
|
22
|
+
v: 1;
|
|
23
|
+
}>;
|
|
24
|
+
export declare const OH_CLOUDFLARE_EMBEDDING_LIMITS_V1: Readonly<{
|
|
25
|
+
batchInputs: 32;
|
|
26
|
+
deadlineMs: 30000;
|
|
27
|
+
documentBytes: number;
|
|
28
|
+
inputUtf8Bytes: 448;
|
|
29
|
+
responseBytes: number;
|
|
30
|
+
renderedChunks: 256;
|
|
31
|
+
titleBytes: number;
|
|
32
|
+
}>;
|
|
33
|
+
export type OhRenderedEmbeddingInputV1 = Readonly<{
|
|
34
|
+
input: string;
|
|
35
|
+
inputSha256: Sha256Hex;
|
|
36
|
+
kind: "document" | "query";
|
|
37
|
+
utf8Bytes: number;
|
|
38
|
+
v: 1;
|
|
39
|
+
}>;
|
|
40
|
+
export type OhRenderedDocumentChunkV1 = Readonly<{
|
|
41
|
+
content: string;
|
|
42
|
+
input: OhRenderedEmbeddingInputV1;
|
|
43
|
+
ordinal: number;
|
|
44
|
+
title: string;
|
|
45
|
+
v: 1;
|
|
46
|
+
}>;
|
|
47
|
+
export type OhRenderedDocumentV1 = Readonly<{
|
|
48
|
+
chunks: readonly OhRenderedDocumentChunkV1[];
|
|
49
|
+
diagnostic: null | Readonly<{
|
|
50
|
+
code: "oversize-prefix" | "partial";
|
|
51
|
+
maximumChunks: number;
|
|
52
|
+
omittedUtf8Bytes: number;
|
|
53
|
+
v: 1;
|
|
54
|
+
}>;
|
|
55
|
+
sourceUtf8Bytes: number;
|
|
56
|
+
status: "complete" | "oversize" | "partial";
|
|
57
|
+
v: 1;
|
|
58
|
+
}>;
|
|
59
|
+
export declare class OhCloudflareEmbeddingError extends Error {
|
|
60
|
+
readonly code: "aborted" | "invalid-input" | "invalid-response" | "provider-unavailable";
|
|
61
|
+
readonly status: number | null;
|
|
62
|
+
constructor(code: OhCloudflareEmbeddingError["code"], message: string, status?: number | null);
|
|
63
|
+
}
|
|
64
|
+
export declare function renderOhCloudflareEmbeddingQueryV1(query: string): OhRenderedEmbeddingInputV1;
|
|
65
|
+
/**
|
|
66
|
+
* Renders every source scalar in order. A caller-selected chunk limit is made
|
|
67
|
+
* visible as `partial`; a title that leaves no content capacity is `oversize`.
|
|
68
|
+
*/
|
|
69
|
+
export declare function renderOhCloudflareEmbeddingDocumentV1(input: Readonly<{
|
|
70
|
+
content: string;
|
|
71
|
+
maximumChunks?: number;
|
|
72
|
+
title: string;
|
|
73
|
+
}>): OhRenderedDocumentV1;
|
|
74
|
+
export type OhEmbeddingFetchV1 = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
|
|
75
|
+
export type OhCloudflareEmbeddingClientOptionsV1 = Readonly<{
|
|
76
|
+
accountId: string;
|
|
77
|
+
apiToken: string;
|
|
78
|
+
deadlineMs?: number;
|
|
79
|
+
fetch?: OhEmbeddingFetchV1;
|
|
80
|
+
maximumBatchInputs?: number;
|
|
81
|
+
maximumResponseBytes?: number;
|
|
82
|
+
}>;
|
|
83
|
+
/** Fixed Workers AI adapter; credentials are never included in surfaced errors. */
|
|
84
|
+
export declare class OhCloudflareEmbeddingClientV1 {
|
|
85
|
+
#private;
|
|
86
|
+
readonly profile: Readonly<{
|
|
87
|
+
profileSha256: Sha256Hex;
|
|
88
|
+
dimensions: 768;
|
|
89
|
+
distance: "cosine";
|
|
90
|
+
documentFormat: "title: {title} | text: {content}";
|
|
91
|
+
inputUtf8Bytes: 448;
|
|
92
|
+
model: "@cf/google/embeddinggemma-300m";
|
|
93
|
+
normalization: "l2";
|
|
94
|
+
profileId: "oh.cloudflare.embeddinggemma.v1";
|
|
95
|
+
provider: "cloudflare.workers-ai";
|
|
96
|
+
queryFormat: "task: search result | query: {query}";
|
|
97
|
+
v: 1;
|
|
98
|
+
}>;
|
|
99
|
+
constructor(options: OhCloudflareEmbeddingClientOptionsV1);
|
|
100
|
+
embed(inputs: readonly OhRenderedEmbeddingInputV1[], options?: Readonly<{
|
|
101
|
+
signal?: AbortSignal;
|
|
102
|
+
}>): Promise<readonly (readonly number[])[]>;
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=cloudflare-embedding.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloudflare-embedding.d.ts","sourceRoot":"","sources":["../src/cloudflare-embedding.ts"],"names":[],"mappings":"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"}
|
package/dist/graph.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph.d.ts","sourceRoot":"","sources":["../src/graph.ts"],"names":[],"mappings":"AAAA,OAAO,EASL,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,aAAa,CAAC;AAErB,eAAO,MAAM,0BAA0B,EAAG,CAAU,CAAC;AACrD,eAAO,MAAM,kBAAkB;;;;;EAK7B,CAAC;AAEH,eAAO,MAAM,kCAAkC,wQAKrC,CAAC;AACX,MAAM,MAAM,0BAA0B,GAAG,CAAC,OAAO,kCAAkC,CAAC,CAAC,MAAM,CAAC,CAAC;AAE7F,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAC5C,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,0BAA0B,CAAC;IACjC,YAAY,EAAE,SAAS,CAAC;IACxB,CAAC,EAAE,CAAC,CAAC;IACL,KAAK,EAAE,SAAS,CAAC;CAClB,CAAC,CAAC;AACH,MAAM,MAAM,2BAA2B,GAAG,IAAI,CAAC,sBAAsB,EAAE,cAAc,CAAC,CAAC;AAEvF,MAAM,MAAM,yBAAyB,GAAG,QAAQ,CAAC;IAC/C,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,0BAA0B,CAAC;IACjC,MAAM,EAAE,SAAS,CAAC;IAClB,CAAC,EAAE,CAAC,CAAC;CACN,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"graph.d.ts","sourceRoot":"","sources":["../src/graph.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -61,17 +61,27 @@ function encodeCanonical(value, path, ancestors) {
|
|
|
61
61
|
ancestors.add(value);
|
|
62
62
|
try {
|
|
63
63
|
if (Array.isArray(value)) {
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
69
|
-
encoded.push(encodeCanonical(value[index], `${path}[${index}]`, ancestors));
|
|
64
|
+
const lengthDescriptor = Object.getOwnPropertyDescriptor(value, "length");
|
|
65
|
+
const length = lengthDescriptor?.value;
|
|
66
|
+
if (typeof length !== "number" || !Number.isSafeInteger(length) || length < 0) {
|
|
67
|
+
throw new OhValidationError("non-json-property", path, "array has an invalid length descriptor");
|
|
70
68
|
}
|
|
71
|
-
const
|
|
72
|
-
if (
|
|
69
|
+
const ownKeys2 = Reflect.ownKeys(value);
|
|
70
|
+
if (!ownKeys2.includes("length") || ownKeys2.some((key) => key !== "length" && (typeof key !== "string" || !/^(?:0|[1-9][0-9]*)$/u.test(key) || Number(key) >= length))) {
|
|
73
71
|
throw new OhValidationError("non-json-property", path, "array has non-index properties");
|
|
74
72
|
}
|
|
73
|
+
const elements = [];
|
|
74
|
+
for (let index = 0;index < length; index += 1) {
|
|
75
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
|
|
76
|
+
if (descriptor === undefined) {
|
|
77
|
+
throw new OhValidationError("sparse-array", `${path}[${index}]`, "must not contain holes");
|
|
78
|
+
}
|
|
79
|
+
if (!descriptor.enumerable || descriptor.get !== undefined || descriptor.set !== undefined) {
|
|
80
|
+
throw new OhValidationError("non-json-property", `${path}[${index}]`, "must be an enumerable data property");
|
|
81
|
+
}
|
|
82
|
+
elements.push(descriptor.value);
|
|
83
|
+
}
|
|
84
|
+
const encoded = elements.map((element, index) => encodeCanonical(element, `${path}[${index}]`, ancestors));
|
|
75
85
|
return `[${encoded.join(",")}]`;
|
|
76
86
|
}
|
|
77
87
|
if (!isPlainRecord(value)) {
|
|
@@ -81,19 +91,21 @@ function encodeCanonical(value, path, ancestors) {
|
|
|
81
91
|
if (ownKeys.some((key) => typeof key !== "string")) {
|
|
82
92
|
throw new OhValidationError("non-json-property", path, "object has a symbol property");
|
|
83
93
|
}
|
|
94
|
+
const entries = [];
|
|
84
95
|
const keys = ownKeys;
|
|
85
96
|
for (const key of keys) {
|
|
86
97
|
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
87
98
|
if (descriptor === undefined || !descriptor.enumerable || descriptor.get !== undefined || descriptor.set !== undefined) {
|
|
88
99
|
throw new OhValidationError("non-json-property", `${path}.${key}`, "must be an enumerable data property");
|
|
89
100
|
}
|
|
101
|
+
entries.push([key, descriptor.value]);
|
|
90
102
|
}
|
|
91
|
-
|
|
92
|
-
const
|
|
103
|
+
entries.sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0);
|
|
104
|
+
const encodedEntries = entries.map(([key, entryValue]) => {
|
|
93
105
|
assertUnicodeScalarString(key, `${path}.<key>`);
|
|
94
|
-
return `${JSON.stringify(key)}:${encodeCanonical(
|
|
106
|
+
return `${JSON.stringify(key)}:${encodeCanonical(entryValue, `${path}.${key}`, ancestors)}`;
|
|
95
107
|
});
|
|
96
|
-
return `{${
|
|
108
|
+
return `{${encodedEntries.join(",")}}`;
|
|
97
109
|
} finally {
|
|
98
110
|
ancestors.delete(value);
|
|
99
111
|
}
|
|
@@ -205,17 +217,70 @@ var OH_KNOWLEDGE_GRAPH_RECORD_KINDS_V1 = [
|
|
|
205
217
|
"view",
|
|
206
218
|
"vocabulary"
|
|
207
219
|
];
|
|
220
|
+
var KNOWLEDGE_GRAPH_RECORD_KEYS_V1 = [
|
|
221
|
+
"dependencies",
|
|
222
|
+
"key",
|
|
223
|
+
"kind",
|
|
224
|
+
"recordSha256",
|
|
225
|
+
"v",
|
|
226
|
+
"value"
|
|
227
|
+
];
|
|
228
|
+
function exactKnowledgeGraphRecordEnvelopeV1(value) {
|
|
229
|
+
try {
|
|
230
|
+
if (!isPlainRecord(value))
|
|
231
|
+
return null;
|
|
232
|
+
const ownKeys = Reflect.ownKeys(value);
|
|
233
|
+
if (ownKeys.length !== KNOWLEDGE_GRAPH_RECORD_KEYS_V1.length || ownKeys.some((key) => typeof key !== "string") || KNOWLEDGE_GRAPH_RECORD_KEYS_V1.some((key) => !ownKeys.includes(key)))
|
|
234
|
+
return null;
|
|
235
|
+
const detached = {};
|
|
236
|
+
for (const key of KNOWLEDGE_GRAPH_RECORD_KEYS_V1) {
|
|
237
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
238
|
+
if (descriptor === undefined || !descriptor.enumerable || descriptor.get !== undefined || descriptor.set !== undefined)
|
|
239
|
+
return null;
|
|
240
|
+
detached[key] = descriptor.value;
|
|
241
|
+
}
|
|
242
|
+
return detached;
|
|
243
|
+
} catch {
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
function exactGraphDependenciesV1(value) {
|
|
248
|
+
try {
|
|
249
|
+
if (!Array.isArray(value))
|
|
250
|
+
return null;
|
|
251
|
+
const lengthDescriptor = Object.getOwnPropertyDescriptor(value, "length");
|
|
252
|
+
const length = lengthDescriptor?.value;
|
|
253
|
+
if (typeof length !== "number" || !Number.isSafeInteger(length) || length < 0 || length > OH_GRAPH_LIMITS_V1.dependenciesPerRecord)
|
|
254
|
+
return null;
|
|
255
|
+
const ownKeys = Reflect.ownKeys(value);
|
|
256
|
+
if (ownKeys.length !== length + 1 || !ownKeys.includes("length") || ownKeys.some((key) => key !== "length" && (typeof key !== "string" || !/^(?:0|[1-9][0-9]*)$/u.test(key) || Number(key) >= length)))
|
|
257
|
+
return null;
|
|
258
|
+
const detached = [];
|
|
259
|
+
for (let index = 0;index < length; index += 1) {
|
|
260
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
|
|
261
|
+
if (descriptor === undefined || !descriptor.enumerable || descriptor.get !== undefined || descriptor.set !== undefined)
|
|
262
|
+
return null;
|
|
263
|
+
detached.push(descriptor.value);
|
|
264
|
+
}
|
|
265
|
+
return detached;
|
|
266
|
+
} catch {
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
208
270
|
function recordKey(value) {
|
|
209
271
|
return typeof value === "string" && value.length <= 512 && /^[a-z][a-z0-9]*(?:[._:/-][a-z0-9]+)*$/u.test(value) ? value : null;
|
|
210
272
|
}
|
|
211
273
|
function createKnowledgeGraphRecordV1(input) {
|
|
212
|
-
if (!isPlainRecord(input) || !hasExactKeys(input, ["dependencies", "key", "kind", "v", "value"]) || input.v !== 1
|
|
274
|
+
if (!isPlainRecord(input) || !hasExactKeys(input, ["dependencies", "key", "kind", "v", "value"]) || input.v !== 1)
|
|
213
275
|
throw new TypeError("Invalid graph record input.");
|
|
276
|
+
const dependencyInput = exactGraphDependenciesV1(input.dependencies);
|
|
277
|
+
if (dependencyInput === null)
|
|
278
|
+
throw new TypeError("Invalid graph record dependencies.");
|
|
214
279
|
const key = recordKey(input.key);
|
|
215
280
|
const kind = OH_KNOWLEDGE_GRAPH_RECORD_KINDS_V1.find((candidate) => candidate === input.kind);
|
|
216
|
-
if (key === null || kind === undefined
|
|
281
|
+
if (key === null || kind === undefined)
|
|
217
282
|
throw new TypeError("Invalid graph record identity.");
|
|
218
|
-
const dependencies =
|
|
283
|
+
const dependencies = dependencyInput.map(recordKey);
|
|
219
284
|
if (dependencies.some((dependency) => dependency === null) || !orderedUnique(dependencies, String) || dependencies.includes(key)) {
|
|
220
285
|
throw new TypeError("Graph dependencies must be ordered, unique, and non-reflexive.");
|
|
221
286
|
}
|
|
@@ -227,10 +292,17 @@ function createKnowledgeGraphRecordV1(input) {
|
|
|
227
292
|
return { ...payload, recordSha256: canonicalSha256(payload) };
|
|
228
293
|
}
|
|
229
294
|
function parseKnowledgeGraphRecordV1(value) {
|
|
230
|
-
|
|
295
|
+
const envelope = exactKnowledgeGraphRecordEnvelopeV1(value);
|
|
296
|
+
if (envelope === null)
|
|
231
297
|
return null;
|
|
232
|
-
const recordSha256 = parseSha256Hex(
|
|
233
|
-
const
|
|
298
|
+
const recordSha256 = parseSha256Hex(envelope.recordSha256);
|
|
299
|
+
const input = {
|
|
300
|
+
dependencies: envelope.dependencies,
|
|
301
|
+
key: envelope.key,
|
|
302
|
+
kind: envelope.kind,
|
|
303
|
+
v: envelope.v,
|
|
304
|
+
value: envelope.value
|
|
305
|
+
};
|
|
234
306
|
try {
|
|
235
307
|
const created = createKnowledgeGraphRecordV1(input);
|
|
236
308
|
return recordSha256 !== null && created.recordSha256 === recordSha256 ? { ...created, recordSha256 } : null;
|