@hraness/wordcell 0.21.3 → 0.22.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 +170 -64
- package/dist/{authority-4sp9d552.js → authority-1pgftmwq.js} +55 -10
- package/dist/benchmark.js +3 -3
- package/dist/capture.js +6 -5
- package/dist/cli.js +37 -31
- package/dist/clip/acquire.js +3 -2
- package/dist/clip/cli.js +6 -5
- package/dist/clip/doctor.js +4 -3
- package/dist/clip/jobs.js +3 -3
- package/dist/clip/refresh.js +1 -1
- package/dist/evaluation-builder.js +18 -17
- package/dist/evaluation-kb.js +14 -13
- package/dist/graph-authority.js +3 -3
- package/dist/graph-percolation.js +5 -5
- package/dist/index-0dqfnrz9.js +1985 -0
- package/dist/{index-0k2x4nn9.js → index-1tm7bgx7.js} +16 -4
- package/dist/{index-vdw5yjfa.js → index-2hf27mws.js} +108 -18
- package/dist/index-3agn8scn.js +306 -0
- package/dist/{index-0rfcqab6.js → index-4ag64rc1.js} +555 -59
- package/dist/index-4knsp9qj.js +34 -0
- package/dist/index-66pshdtx.js +341 -0
- package/dist/index-9rf81m0p.js +368 -0
- package/dist/{index-59ssmqqm.js → index-a6kbv718.js} +1 -1
- package/dist/{index-51bap0mq.js → index-avbce90h.js} +9 -9
- package/dist/{index-9zcpdbjg.js → index-de2w8crk.js} +13 -13
- package/dist/{index-pbr6rv47.js → index-e6p9e926.js} +6 -6
- package/dist/{index-bcfn9xah.js → index-famy7fhs.js} +5 -5
- package/dist/{index-van48n3g.js → index-fc4dr114.js} +4 -2
- package/dist/{index-jzcws0zn.js → index-g5vsqmdy.js} +27 -57
- package/dist/{index-115b07ap.js → index-hcw140eb.js} +5 -5
- package/dist/index-j70m75wd.js +217 -0
- package/dist/{index-3ehtt59c.js → index-qwgsmtsz.js} +1 -1
- package/dist/{index-bgfzwt4h.js → index-t2bs9xdr.js} +4 -4
- package/dist/index.js +285 -93
- package/dist/portfolio.js +14 -13
- package/dist/publish-model.js +36 -0
- package/dist/publish-reader/reader.css +1042 -0
- package/dist/publish-reader/reader.js +1534 -0
- package/dist/publish-reader/theme.js +92 -0
- package/dist/publish-search.js +52 -0
- package/dist/publish.js +36 -0
- package/dist/rerank-typesafe.js +13 -0
- package/dist/rerank.js +14 -0
- package/dist/sdk.js +14 -11
- package/dist/search.js +2 -2
- package/dist/semantic.js +4 -4
- package/dist/{support-vm4mkw0k.js → support-mywv1em0.js} +38 -32
- package/dist/workflows/decision-context.js +13 -12
- package/dist/workflows/index.js +13 -12
- package/package.json +63 -5
- package/skills/wordcell/AGENTS.md +1 -0
- package/skills/wordcell/SKILL.md +2 -1
- package/skills/wordcell/agents/openai.yaml +1 -1
- package/skills/wordcell/references/publish.md +93 -0
- package/skills/wordcell/references/query.md +22 -0
- package/src/cli-program.ts +347 -9
- package/src/index.ts +4 -0
- package/src/oh/authority.ts +14 -5
- package/src/oh/canonical-rust.ts +127 -0
- package/src/oh/projection-rust.ts +63 -0
- package/src/oh-adoption.ts +7 -1
- package/src/publish-graph.ts +0 -0
- package/src/publish-index.ts +195 -0
- package/src/publish-markdown.ts +617 -0
- package/src/publish-model.ts +599 -0
- package/src/publish-nav.ts +204 -0
- package/src/publish-pages.ts +382 -0
- package/src/publish-reader/reader.css +1042 -0
- package/src/publish-reader/reader.ts +1017 -0
- package/src/publish-reader/theme.ts +97 -0
- package/src/publish-search.ts +418 -0
- package/src/publish-select.ts +419 -0
- package/src/publish-theme.ts +106 -0
- package/src/publish.ts +904 -0
- package/src/rerank-credentials.ts +56 -0
- package/src/rerank-typesafe.ts +472 -0
- package/src/rerank.ts +389 -0
- package/src/sdk.ts +157 -6
- package/src/serve.ts +180 -0
- package/src/vault.ts +23 -9
- package/dist/{index-why54q5p.js → index-gm9t95d9.js} +3 -3
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/clip/package-root.ts
|
|
3
|
+
import { existsSync, readFileSync } from "fs";
|
|
4
|
+
import { createRequire } from "module";
|
|
5
|
+
import { dirname, join, resolve } from "path";
|
|
6
|
+
function isPackageManifest(value) {
|
|
7
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8
|
+
}
|
|
9
|
+
function findKbPackageRoot(startDirectory = import.meta.dir, dependencies = {}) {
|
|
10
|
+
const exists = dependencies.exists ?? existsSync;
|
|
11
|
+
const readText = dependencies.readText ?? ((path) => readFileSync(path, "utf8"));
|
|
12
|
+
let directory = resolve(startDirectory);
|
|
13
|
+
for (let depth = 0;depth < 8; depth += 1) {
|
|
14
|
+
const manifestPath = join(directory, "package.json");
|
|
15
|
+
if (exists(manifestPath)) {
|
|
16
|
+
try {
|
|
17
|
+
const parsed = JSON.parse(readText(manifestPath));
|
|
18
|
+
if (isPackageManifest(parsed) && typeof parsed.name === "string" && parsed.name === "@hraness/wordcell" && typeof parsed.version === "string")
|
|
19
|
+
return directory;
|
|
20
|
+
} catch {}
|
|
21
|
+
}
|
|
22
|
+
const parent = dirname(directory);
|
|
23
|
+
if (parent === directory)
|
|
24
|
+
break;
|
|
25
|
+
directory = parent;
|
|
26
|
+
}
|
|
27
|
+
throw new Error("Could not locate the wordcell package root.");
|
|
28
|
+
}
|
|
29
|
+
function resolvePackageDirectory(packageName, parentUrl = import.meta.url) {
|
|
30
|
+
const manifest = createRequire(parentUrl).resolve(`${packageName}/package.json`);
|
|
31
|
+
return dirname(manifest);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export { findKbPackageRoot, resolvePackageDirectory };
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/publish-model.ts
|
|
3
|
+
var WORDCELL_SITE_FORMAT_V1 = "hraness.wordcell.site.v1";
|
|
4
|
+
var WORDCELL_SITE_CATALOG_FORMAT_V1 = "hraness.wordcell.site-catalog.v1";
|
|
5
|
+
var WORDCELL_SITE_DOCS_FORMAT_V1 = "hraness.wordcell.site-docs.v1";
|
|
6
|
+
var WORDCELL_SITE_TERMS_FORMAT_V1 = "hraness.wordcell.site-terms.v1";
|
|
7
|
+
var WORDCELL_SITE_POSTINGS_FORMAT_V1 = "hraness.wordcell.site-postings.v1";
|
|
8
|
+
var WORDCELL_SITE_NOTE_FORMAT_V1 = "hraness.wordcell.site-note.v1";
|
|
9
|
+
var WORDCELL_SITE_GRAPH_FORMAT_V1 = "hraness.wordcell.site-graph.v1";
|
|
10
|
+
var WORDCELL_SITE_LIMITS_V1 = Object.freeze({
|
|
11
|
+
notes: 1e4,
|
|
12
|
+
assets: 1e4,
|
|
13
|
+
assetBytes: 16 * 1024 * 1024,
|
|
14
|
+
totalAssetBytes: 512 * 1024 * 1024,
|
|
15
|
+
indexTerms: 250000,
|
|
16
|
+
contentShards: 256,
|
|
17
|
+
shardPairs: 250000,
|
|
18
|
+
docPreviewBytes: 240,
|
|
19
|
+
fieldTextBytes: 4 * 1024,
|
|
20
|
+
inlineTextBytes: 16 * 1024,
|
|
21
|
+
inlineTotalBytes: 2 * 1024 * 1024,
|
|
22
|
+
noteTextBytes: 32 * 1024,
|
|
23
|
+
titleBytes: 512,
|
|
24
|
+
descriptionBytes: 4 * 1024,
|
|
25
|
+
searchHydration: 50,
|
|
26
|
+
searchResults: 100
|
|
27
|
+
});
|
|
28
|
+
var textEncoder = new TextEncoder;
|
|
29
|
+
function isRecord(value) {
|
|
30
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
31
|
+
}
|
|
32
|
+
function boundedText(value, maximumBytes, field) {
|
|
33
|
+
if (typeof value !== "string" || textEncoder.encode(value).byteLength > maximumBytes) {
|
|
34
|
+
throw new TypeError(`${field} must be a string of at most ${maximumBytes} bytes`);
|
|
35
|
+
}
|
|
36
|
+
return value;
|
|
37
|
+
}
|
|
38
|
+
function boundedCount(value, maximum, field) {
|
|
39
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0 || value > maximum) {
|
|
40
|
+
throw new TypeError(`${field} must be a safe integer from 0 through ${maximum}`);
|
|
41
|
+
}
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
function exactKeys(value, required, optional, field) {
|
|
45
|
+
const allowed = new Set([...required, ...optional]);
|
|
46
|
+
for (const key of Object.keys(value)) {
|
|
47
|
+
if (!allowed.has(key))
|
|
48
|
+
throw new TypeError(`${field} has unexpected key ${JSON.stringify(key)}`);
|
|
49
|
+
}
|
|
50
|
+
for (const key of required) {
|
|
51
|
+
if (!(key in value))
|
|
52
|
+
throw new TypeError(`${field} is missing ${JSON.stringify(key)}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function stringArray(value, maximum, field) {
|
|
56
|
+
if (!Array.isArray(value) || value.length > maximum) {
|
|
57
|
+
throw new TypeError(`${field} must be an array of at most ${maximum} strings`);
|
|
58
|
+
}
|
|
59
|
+
for (const item of value) {
|
|
60
|
+
if (typeof item !== "string")
|
|
61
|
+
throw new TypeError(`${field} entries must be strings`);
|
|
62
|
+
}
|
|
63
|
+
return value;
|
|
64
|
+
}
|
|
65
|
+
function contentSearch(value) {
|
|
66
|
+
if (value === "inline" || value === "shards" || value === "none")
|
|
67
|
+
return value;
|
|
68
|
+
throw new TypeError("search.content must be inline, shards, or none");
|
|
69
|
+
}
|
|
70
|
+
function parseSiteManifestV1(value) {
|
|
71
|
+
if (!isRecord(value))
|
|
72
|
+
throw new TypeError("manifest must be an object");
|
|
73
|
+
exactKeys(value, ["format", "site", "generated", "source", "paths", "search", "counts", "truncated"], [], "manifest");
|
|
74
|
+
if (value.format !== WORDCELL_SITE_FORMAT_V1) {
|
|
75
|
+
throw new TypeError(`manifest format must be ${WORDCELL_SITE_FORMAT_V1}`);
|
|
76
|
+
}
|
|
77
|
+
const { site, generated, source, paths, search, counts, truncated } = value;
|
|
78
|
+
if (!isRecord(site))
|
|
79
|
+
throw new TypeError("manifest.site must be an object");
|
|
80
|
+
exactKeys(site, ["title", "basePath"], ["description"], "manifest.site");
|
|
81
|
+
if (!isRecord(generated))
|
|
82
|
+
throw new TypeError("manifest.generated must be an object");
|
|
83
|
+
exactKeys(generated, ["by", "version"], ["at"], "manifest.generated");
|
|
84
|
+
if (generated.by !== "@hraness/wordcell") {
|
|
85
|
+
throw new TypeError("manifest.generated.by must be @hraness/wordcell");
|
|
86
|
+
}
|
|
87
|
+
if (!isRecord(source))
|
|
88
|
+
throw new TypeError("manifest.source must be an object");
|
|
89
|
+
exactKeys(source, ["selection", "notes", "digest"], [], "manifest.source");
|
|
90
|
+
if (!isRecord(paths))
|
|
91
|
+
throw new TypeError("manifest.paths must be an object");
|
|
92
|
+
exactKeys(paths, ["catalog", "graph", "docs", "terms", "postingsPrefix", "notePrefix", "assetPrefix", "readerPrefix"], [], "manifest.paths");
|
|
93
|
+
if (!isRecord(search))
|
|
94
|
+
throw new TypeError("manifest.search must be an object");
|
|
95
|
+
exactKeys(search, ["mode", "content", "shards", "hash"], [], "manifest.search");
|
|
96
|
+
if (search.mode !== "exact")
|
|
97
|
+
throw new TypeError("manifest.search.mode must be exact");
|
|
98
|
+
if (search.hash !== "fnv1a32-8bit") {
|
|
99
|
+
throw new TypeError("manifest.search.hash must be fnv1a32-8bit");
|
|
100
|
+
}
|
|
101
|
+
if (!isRecord(counts))
|
|
102
|
+
throw new TypeError("manifest.counts must be an object");
|
|
103
|
+
exactKeys(counts, ["notes", "assets", "bytes"], [], "manifest.counts");
|
|
104
|
+
if (!isRecord(truncated))
|
|
105
|
+
throw new TypeError("manifest.truncated must be an object");
|
|
106
|
+
exactKeys(truncated, [], ["terms", "text", "assets"], "manifest.truncated");
|
|
107
|
+
for (const flag of Object.values(truncated)) {
|
|
108
|
+
if (flag !== true)
|
|
109
|
+
throw new TypeError("manifest.truncated flags must be true");
|
|
110
|
+
}
|
|
111
|
+
const digest = source.digest;
|
|
112
|
+
if (typeof digest !== "string" || !/^sha256:[0-9a-f]{64}$/u.test(digest)) {
|
|
113
|
+
throw new TypeError("manifest.source.digest must be a sha256 hex digest");
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
format: WORDCELL_SITE_FORMAT_V1,
|
|
117
|
+
site: Object.freeze({
|
|
118
|
+
title: boundedText(site.title, WORDCELL_SITE_LIMITS_V1.titleBytes, "site.title"),
|
|
119
|
+
basePath: boundedText(site.basePath, 1024, "site.basePath"),
|
|
120
|
+
...site.description === undefined ? {} : { description: boundedText(site.description, WORDCELL_SITE_LIMITS_V1.descriptionBytes, "site.description") }
|
|
121
|
+
}),
|
|
122
|
+
generated: Object.freeze({
|
|
123
|
+
by: "@hraness/wordcell",
|
|
124
|
+
version: boundedText(generated.version, 64, "generated.version"),
|
|
125
|
+
...generated.at === undefined ? {} : { at: boundedText(generated.at, 64, "generated.at") }
|
|
126
|
+
}),
|
|
127
|
+
source: Object.freeze({
|
|
128
|
+
selection: source.selection,
|
|
129
|
+
notes: boundedCount(source.notes, WORDCELL_SITE_LIMITS_V1.notes, "source.notes"),
|
|
130
|
+
digest
|
|
131
|
+
}),
|
|
132
|
+
paths: Object.freeze({
|
|
133
|
+
catalog: boundedText(paths.catalog, 256, "paths.catalog"),
|
|
134
|
+
graph: boundedText(paths.graph, 256, "paths.graph"),
|
|
135
|
+
docs: boundedText(paths.docs, 256, "paths.docs"),
|
|
136
|
+
terms: boundedText(paths.terms, 256, "paths.terms"),
|
|
137
|
+
postingsPrefix: boundedText(paths.postingsPrefix, 256, "paths.postingsPrefix"),
|
|
138
|
+
notePrefix: boundedText(paths.notePrefix, 256, "paths.notePrefix"),
|
|
139
|
+
assetPrefix: boundedText(paths.assetPrefix, 256, "paths.assetPrefix"),
|
|
140
|
+
readerPrefix: boundedText(paths.readerPrefix, 256, "paths.readerPrefix")
|
|
141
|
+
}),
|
|
142
|
+
search: Object.freeze({
|
|
143
|
+
mode: "exact",
|
|
144
|
+
content: contentSearch(search.content),
|
|
145
|
+
shards: boundedCount(search.shards, WORDCELL_SITE_LIMITS_V1.contentShards, "search.shards"),
|
|
146
|
+
hash: "fnv1a32-8bit"
|
|
147
|
+
}),
|
|
148
|
+
counts: Object.freeze({
|
|
149
|
+
notes: boundedCount(counts.notes, WORDCELL_SITE_LIMITS_V1.notes, "counts.notes"),
|
|
150
|
+
assets: boundedCount(counts.assets, WORDCELL_SITE_LIMITS_V1.assets, "counts.assets"),
|
|
151
|
+
bytes: boundedCount(counts.bytes, Number.MAX_SAFE_INTEGER, "counts.bytes")
|
|
152
|
+
}),
|
|
153
|
+
truncated: Object.freeze({ ...truncated })
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
function parseSiteCatalogV1(value) {
|
|
157
|
+
if (!isRecord(value))
|
|
158
|
+
throw new TypeError("catalog must be an object");
|
|
159
|
+
exactKeys(value, ["format", "entries"], [], "catalog");
|
|
160
|
+
if (value.format !== WORDCELL_SITE_CATALOG_FORMAT_V1) {
|
|
161
|
+
throw new TypeError(`catalog format must be ${WORDCELL_SITE_CATALOG_FORMAT_V1}`);
|
|
162
|
+
}
|
|
163
|
+
if (!Array.isArray(value.entries) || value.entries.length > WORDCELL_SITE_LIMITS_V1.notes) {
|
|
164
|
+
throw new TypeError("catalog.entries must be a bounded array");
|
|
165
|
+
}
|
|
166
|
+
const entries = value.entries.map((entry, index) => {
|
|
167
|
+
if (!isRecord(entry))
|
|
168
|
+
throw new TypeError(`catalog entry ${index} must be an object`);
|
|
169
|
+
exactKeys(entry, ["i", "s", "t"], ["type", "g"], `catalog entry ${index}`);
|
|
170
|
+
return {
|
|
171
|
+
i: boundedCount(entry.i, WORDCELL_SITE_LIMITS_V1.notes, `catalog entry ${index}.i`),
|
|
172
|
+
s: boundedText(entry.s, 1024, `catalog entry ${index}.s`),
|
|
173
|
+
t: boundedText(entry.t, WORDCELL_SITE_LIMITS_V1.titleBytes, `catalog entry ${index}.t`),
|
|
174
|
+
...entry.type === undefined ? {} : { type: boundedText(entry.type, 256, `catalog entry ${index}.type`) },
|
|
175
|
+
...entry.g === undefined ? {} : { g: stringArray(entry.g, 128, `catalog entry ${index}.g`) }
|
|
176
|
+
};
|
|
177
|
+
});
|
|
178
|
+
return { format: WORDCELL_SITE_CATALOG_FORMAT_V1, entries };
|
|
179
|
+
}
|
|
180
|
+
function docFields(value, field) {
|
|
181
|
+
if (!isRecord(value))
|
|
182
|
+
throw new TypeError(`${field} must be an object`);
|
|
183
|
+
exactKeys(value, ["t", "a", "p", "g", "m"], [], field);
|
|
184
|
+
return {
|
|
185
|
+
t: boundedText(value.t, WORDCELL_SITE_LIMITS_V1.fieldTextBytes, `${field}.t`),
|
|
186
|
+
a: boundedText(value.a, WORDCELL_SITE_LIMITS_V1.fieldTextBytes, `${field}.a`),
|
|
187
|
+
p: boundedText(value.p, WORDCELL_SITE_LIMITS_V1.fieldTextBytes, `${field}.p`),
|
|
188
|
+
g: boundedText(value.g, WORDCELL_SITE_LIMITS_V1.fieldTextBytes, `${field}.g`),
|
|
189
|
+
m: boundedText(value.m, WORDCELL_SITE_LIMITS_V1.fieldTextBytes, `${field}.m`)
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
function parseSiteDocsV1(value) {
|
|
193
|
+
if (!isRecord(value))
|
|
194
|
+
throw new TypeError("docs must be an object");
|
|
195
|
+
exactKeys(value, ["format", "content", "docs"], [], "docs");
|
|
196
|
+
if (value.format !== WORDCELL_SITE_DOCS_FORMAT_V1) {
|
|
197
|
+
throw new TypeError(`docs format must be ${WORDCELL_SITE_DOCS_FORMAT_V1}`);
|
|
198
|
+
}
|
|
199
|
+
const content = contentSearch(value.content);
|
|
200
|
+
if (!Array.isArray(value.docs) || value.docs.length > WORDCELL_SITE_LIMITS_V1.notes) {
|
|
201
|
+
throw new TypeError("docs.docs must be a bounded array");
|
|
202
|
+
}
|
|
203
|
+
const docs = value.docs.map((entry, index) => {
|
|
204
|
+
const field = `docs entry ${index}`;
|
|
205
|
+
if (!isRecord(entry))
|
|
206
|
+
throw new TypeError(`${field} must be an object`);
|
|
207
|
+
exactKeys(entry, ["i", "s", "t", "p", "f"], ["x"], field);
|
|
208
|
+
const inline = entry.x === undefined ? {} : { x: boundedText(entry.x, WORDCELL_SITE_LIMITS_V1.inlineTextBytes, `${field}.x`) };
|
|
209
|
+
return {
|
|
210
|
+
i: boundedCount(entry.i, WORDCELL_SITE_LIMITS_V1.notes, `${field}.i`),
|
|
211
|
+
s: boundedText(entry.s, 1024, `${field}.s`),
|
|
212
|
+
t: boundedText(entry.t, WORDCELL_SITE_LIMITS_V1.titleBytes, `${field}.t`),
|
|
213
|
+
p: boundedText(entry.p, WORDCELL_SITE_LIMITS_V1.docPreviewBytes + 16, `${field}.p`),
|
|
214
|
+
f: docFields(entry.f, `${field}.f`),
|
|
215
|
+
...inline
|
|
216
|
+
};
|
|
217
|
+
});
|
|
218
|
+
return { format: WORDCELL_SITE_DOCS_FORMAT_V1, content, docs };
|
|
219
|
+
}
|
|
220
|
+
function parseSiteTermsV1(value) {
|
|
221
|
+
if (!isRecord(value))
|
|
222
|
+
throw new TypeError("terms must be an object");
|
|
223
|
+
exactKeys(value, ["format", "terms"], [], "terms");
|
|
224
|
+
if (value.format !== WORDCELL_SITE_TERMS_FORMAT_V1) {
|
|
225
|
+
throw new TypeError(`terms format must be ${WORDCELL_SITE_TERMS_FORMAT_V1}`);
|
|
226
|
+
}
|
|
227
|
+
return {
|
|
228
|
+
format: WORDCELL_SITE_TERMS_FORMAT_V1,
|
|
229
|
+
terms: stringArray(value.terms, WORDCELL_SITE_LIMITS_V1.indexTerms, "terms.terms")
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
function parseSitePostingsV1(value) {
|
|
233
|
+
if (!isRecord(value))
|
|
234
|
+
throw new TypeError("postings must be an object");
|
|
235
|
+
exactKeys(value, ["format", "shard", "postings"], [], "postings");
|
|
236
|
+
if (value.format !== WORDCELL_SITE_POSTINGS_FORMAT_V1) {
|
|
237
|
+
throw new TypeError(`postings format must be ${WORDCELL_SITE_POSTINGS_FORMAT_V1}`);
|
|
238
|
+
}
|
|
239
|
+
const shard = value.shard;
|
|
240
|
+
if (typeof shard !== "string" || !/^[0-9a-f]{2}$/u.test(shard)) {
|
|
241
|
+
throw new TypeError("postings.shard must be a two-digit lowercase hex string");
|
|
242
|
+
}
|
|
243
|
+
if (!isRecord(value.postings))
|
|
244
|
+
throw new TypeError("postings.postings must be an object");
|
|
245
|
+
const entries = Object.entries(value.postings);
|
|
246
|
+
if (entries.length > WORDCELL_SITE_LIMITS_V1.shardPairs) {
|
|
247
|
+
throw new TypeError("postings.postings exceeds the shard pair limit");
|
|
248
|
+
}
|
|
249
|
+
const postings = Object.create(null);
|
|
250
|
+
for (const [term, docs] of entries) {
|
|
251
|
+
if (!Array.isArray(docs) || docs.length > WORDCELL_SITE_LIMITS_V1.notes) {
|
|
252
|
+
throw new TypeError(`postings for ${JSON.stringify(term)} must be a bounded array`);
|
|
253
|
+
}
|
|
254
|
+
postings[term] = docs.map((doc) => boundedCount(doc, WORDCELL_SITE_LIMITS_V1.notes, "posting document id"));
|
|
255
|
+
}
|
|
256
|
+
return { format: WORDCELL_SITE_POSTINGS_FORMAT_V1, shard, postings: Object.freeze(postings) };
|
|
257
|
+
}
|
|
258
|
+
function noteLinks(value, field) {
|
|
259
|
+
if (!Array.isArray(value) || value.length > WORDCELL_SITE_LIMITS_V1.notes) {
|
|
260
|
+
throw new TypeError(`${field} must be a bounded array`);
|
|
261
|
+
}
|
|
262
|
+
return value.map((entry, index) => {
|
|
263
|
+
if (!isRecord(entry))
|
|
264
|
+
throw new TypeError(`${field} ${index} must be an object`);
|
|
265
|
+
exactKeys(entry, ["s", "t"], [], `${field} ${index}`);
|
|
266
|
+
return {
|
|
267
|
+
s: boundedText(entry.s, 1024, `${field} ${index}.s`),
|
|
268
|
+
t: boundedText(entry.t, WORDCELL_SITE_LIMITS_V1.titleBytes, `${field} ${index}.t`)
|
|
269
|
+
};
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
function parseSiteNoteV1(value) {
|
|
273
|
+
if (!isRecord(value))
|
|
274
|
+
throw new TypeError("note payload must be an object");
|
|
275
|
+
exactKeys(value, ["format", "id", "slug", "title", "aliases", "tags", "summary", "text", "textTruncated", "links", "backlinks", "relations", "relationBacklinks"], ["type"], "note payload");
|
|
276
|
+
if (value.format !== WORDCELL_SITE_NOTE_FORMAT_V1) {
|
|
277
|
+
throw new TypeError(`note payload format must be ${WORDCELL_SITE_NOTE_FORMAT_V1}`);
|
|
278
|
+
}
|
|
279
|
+
if (value.textTruncated !== true && value.textTruncated !== false) {
|
|
280
|
+
throw new TypeError("note payload textTruncated must be boolean");
|
|
281
|
+
}
|
|
282
|
+
return {
|
|
283
|
+
format: WORDCELL_SITE_NOTE_FORMAT_V1,
|
|
284
|
+
id: boundedText(value.id, 1024, "note.id"),
|
|
285
|
+
slug: boundedText(value.slug, 1024, "note.slug"),
|
|
286
|
+
title: boundedText(value.title, WORDCELL_SITE_LIMITS_V1.titleBytes, "note.title"),
|
|
287
|
+
aliases: stringArray(value.aliases, 128, "note.aliases"),
|
|
288
|
+
...value.type === undefined ? {} : { type: boundedText(value.type, 256, "note.type") },
|
|
289
|
+
tags: stringArray(value.tags, 128, "note.tags"),
|
|
290
|
+
summary: boundedText(value.summary, WORDCELL_SITE_LIMITS_V1.descriptionBytes, "note.summary"),
|
|
291
|
+
text: boundedText(value.text, WORDCELL_SITE_LIMITS_V1.noteTextBytes, "note.text"),
|
|
292
|
+
textTruncated: value.textTruncated,
|
|
293
|
+
links: noteLinks(value.links, "note.links"),
|
|
294
|
+
backlinks: noteLinks(value.backlinks, "note.backlinks"),
|
|
295
|
+
relations: noteRelations(value.relations, "note.relations"),
|
|
296
|
+
relationBacklinks: noteRelations(value.relationBacklinks, "note.relationBacklinks")
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
function noteRelations(value, field) {
|
|
300
|
+
if (!Array.isArray(value) || value.length > WORDCELL_SITE_LIMITS_V1.notes) {
|
|
301
|
+
throw new TypeError(`${field} must be a bounded array`);
|
|
302
|
+
}
|
|
303
|
+
return value.map((entry, index) => {
|
|
304
|
+
if (!isRecord(entry))
|
|
305
|
+
throw new TypeError(`${field} ${index} must be an object`);
|
|
306
|
+
exactKeys(entry, ["p", "s", "t"], [], `${field} ${index}`);
|
|
307
|
+
return {
|
|
308
|
+
p: boundedText(entry.p, 256, `${field} ${index}.p`),
|
|
309
|
+
s: boundedText(entry.s, 1024, `${field} ${index}.s`),
|
|
310
|
+
t: boundedText(entry.t, WORDCELL_SITE_LIMITS_V1.titleBytes, `${field} ${index}.t`)
|
|
311
|
+
};
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
function parseSiteGraphV1(value) {
|
|
315
|
+
if (!isRecord(value))
|
|
316
|
+
throw new TypeError("graph must be an object");
|
|
317
|
+
exactKeys(value, ["format", "edges"], [], "graph");
|
|
318
|
+
if (value.format !== WORDCELL_SITE_GRAPH_FORMAT_V1) {
|
|
319
|
+
throw new TypeError(`graph format must be ${WORDCELL_SITE_GRAPH_FORMAT_V1}`);
|
|
320
|
+
}
|
|
321
|
+
if (!Array.isArray(value.edges) || value.edges.length > 1e6) {
|
|
322
|
+
throw new TypeError("graph.edges must be a bounded array");
|
|
323
|
+
}
|
|
324
|
+
const edges = value.edges.map((entry, index) => {
|
|
325
|
+
if (!isRecord(entry))
|
|
326
|
+
throw new TypeError(`graph edge ${index} must be an object`);
|
|
327
|
+
exactKeys(entry, ["s", "t", "k"], ["p"], `graph edge ${index}`);
|
|
328
|
+
if (entry.k !== "link" && entry.k !== "relation") {
|
|
329
|
+
throw new TypeError(`graph edge ${index}.k must be link or relation`);
|
|
330
|
+
}
|
|
331
|
+
return {
|
|
332
|
+
s: boundedCount(entry.s, WORDCELL_SITE_LIMITS_V1.notes, `graph edge ${index}.s`),
|
|
333
|
+
t: boundedCount(entry.t, WORDCELL_SITE_LIMITS_V1.notes, `graph edge ${index}.t`),
|
|
334
|
+
k: entry.k,
|
|
335
|
+
...entry.p === undefined ? {} : { p: boundedText(entry.p, 256, `graph edge ${index}.p`) }
|
|
336
|
+
};
|
|
337
|
+
});
|
|
338
|
+
return { format: WORDCELL_SITE_GRAPH_FORMAT_V1, edges };
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export { WORDCELL_SITE_FORMAT_V1, WORDCELL_SITE_CATALOG_FORMAT_V1, WORDCELL_SITE_DOCS_FORMAT_V1, WORDCELL_SITE_TERMS_FORMAT_V1, WORDCELL_SITE_POSTINGS_FORMAT_V1, WORDCELL_SITE_NOTE_FORMAT_V1, WORDCELL_SITE_GRAPH_FORMAT_V1, WORDCELL_SITE_LIMITS_V1, parseSiteManifestV1, parseSiteCatalogV1, parseSiteDocsV1, parseSiteTermsV1, parseSitePostingsV1, parseSiteNoteV1, parseSiteGraphV1 };
|