@nubbin/core 0.1.0-rc.4 → 0.1.0-rc.6
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.d.ts +144 -20
- package/dist/index.js +435 -149
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,25 @@
|
|
|
1
|
-
import { StandardSchemaV1 } from '@standard-schema/spec';
|
|
1
|
+
import { StandardSchemaV1, StandardJSONSchemaV1 } from '@standard-schema/spec';
|
|
2
|
+
|
|
3
|
+
type FieldKind = "string" | "number" | "boolean" | "enum" | "array" | "object" | "union" | "unknown";
|
|
4
|
+
interface FieldNode {
|
|
5
|
+
/** Dotted path from the schema root, with `[]` for array members: `cta.label`, `items[].title`. */
|
|
6
|
+
path: string;
|
|
7
|
+
kind: FieldKind;
|
|
8
|
+
optional: boolean;
|
|
9
|
+
/** Present only for `enum`. */
|
|
10
|
+
members?: readonly string[];
|
|
11
|
+
}
|
|
12
|
+
interface SchemaAdapter {
|
|
13
|
+
describe(schema: unknown): FieldNode[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Reads a zod schema for the studio and hint resolution, entirely through the Standard JSON
|
|
18
|
+
* Schema converter the schema itself carries — `core` imports nothing from zod to do it.
|
|
19
|
+
* Validation never runs against this projection; it always runs `schema["~standard"].validate()`
|
|
20
|
+
* on the real schema.
|
|
21
|
+
*/
|
|
22
|
+
declare const zodAdapter: SchemaAdapter;
|
|
2
23
|
|
|
3
24
|
type UnknownProps = Record<string, unknown>;
|
|
4
25
|
/**
|
|
@@ -27,8 +48,6 @@ interface Block<Schema extends StandardSchemaV1 = StandardSchemaV1, Component =
|
|
|
27
48
|
/** A deprecated block still resolves; the studio hides it from the palette. */
|
|
28
49
|
status?: "active" | "deprecated";
|
|
29
50
|
slots: Record<string, SlotConstraint>;
|
|
30
|
-
/** Same-node prop reshaping only. It cannot touch slots, or split or delete a block. */
|
|
31
|
-
migrate?: Record<number, (props: UnknownProps) => UnknownProps>;
|
|
32
51
|
}
|
|
33
52
|
|
|
34
53
|
/** How a field's value resolves at render. Absent means static — the value freezes into props. */
|
|
@@ -78,7 +97,11 @@ interface Node {
|
|
|
78
97
|
interface DocumentVersion {
|
|
79
98
|
documentId: string;
|
|
80
99
|
version: number;
|
|
81
|
-
|
|
100
|
+
/**
|
|
101
|
+
* Ordered entry elements — the artifact's tree is these, denormalized, in this order. See
|
|
102
|
+
* [A document has many roots](../../../docs/decisions/a-document-has-many-roots.md).
|
|
103
|
+
*/
|
|
104
|
+
roots: readonly string[];
|
|
82
105
|
elements: Record<string, Node>;
|
|
83
106
|
meta: DocumentMeta;
|
|
84
107
|
createdAt: string;
|
|
@@ -133,7 +156,7 @@ interface ArtifactStore {
|
|
|
133
156
|
unpublish(route: string): Promise<void>;
|
|
134
157
|
}
|
|
135
158
|
|
|
136
|
-
type CompileIssueCode = "unknown-block" | "dangling-child" | "cycle" | "unreachable" | "slot-not-allowed" | "slot-min" | "slot-max" | "invalid-props";
|
|
159
|
+
type CompileIssueCode = "no-roots" | "unknown-block" | "dangling-child" | "cycle" | "unreachable" | "slot-not-allowed" | "slot-min" | "slot-max" | "invalid-props";
|
|
137
160
|
interface CompileIssue {
|
|
138
161
|
nodeId: string;
|
|
139
162
|
/** Where in the node the problem sits: `block`, `slots.items`, or a dotted prop path. */
|
|
@@ -148,6 +171,41 @@ declare class CompileError extends Error {
|
|
|
148
171
|
constructor(issues: readonly CompileIssue[]);
|
|
149
172
|
}
|
|
150
173
|
|
|
174
|
+
/** One route pointer and the artifact it names, as an adapter read them. */
|
|
175
|
+
interface LiveRoute {
|
|
176
|
+
pointer: RoutePointer;
|
|
177
|
+
/** `null` when the store holds no artifact at the pointer's hash — a pointer into nothing. */
|
|
178
|
+
artifact: Artifact | null;
|
|
179
|
+
}
|
|
180
|
+
/** One block's version delta between what a live artifact needs and what is registered now. */
|
|
181
|
+
interface BlockDrift {
|
|
182
|
+
block: string;
|
|
183
|
+
/** The version the artifact was compiled against. */
|
|
184
|
+
live: number;
|
|
185
|
+
/** The version registered now — `null` when the registry no longer holds the block at all. */
|
|
186
|
+
registered: number | null;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Why one live route would not render. `unreadable-artifact` is a pointer whose hash the store
|
|
190
|
+
* cannot resolve, which breaks the route without any registry change at all.
|
|
191
|
+
*/
|
|
192
|
+
type RouteIncompatibility = {
|
|
193
|
+
route: string;
|
|
194
|
+
hash: string;
|
|
195
|
+
reason: "unreadable-artifact";
|
|
196
|
+
} | {
|
|
197
|
+
route: string;
|
|
198
|
+
hash: string;
|
|
199
|
+
reason: "block-drift";
|
|
200
|
+
drifted: BlockDrift[];
|
|
201
|
+
};
|
|
202
|
+
/** The verdict over every live pointer. `checked` is reported so a run over nothing cannot read as a pass. */
|
|
203
|
+
interface CompatibilityReport {
|
|
204
|
+
checked: number;
|
|
205
|
+
compatible: boolean;
|
|
206
|
+
incompatible: RouteIncompatibility[];
|
|
207
|
+
}
|
|
208
|
+
|
|
151
209
|
interface Registry {
|
|
152
210
|
get(name: string): Block | undefined;
|
|
153
211
|
names(): string[];
|
|
@@ -155,6 +213,17 @@ interface Registry {
|
|
|
155
213
|
fingerprint(): string;
|
|
156
214
|
}
|
|
157
215
|
|
|
216
|
+
/**
|
|
217
|
+
* The guardrail's whole question: would this registry fail to render an artifact a live route
|
|
218
|
+
* pointer currently references? `checkRollback` decides each route — one artifact against the
|
|
219
|
+
* registry is the same comparison whether a pointer is moving or a registry is — and this walks
|
|
220
|
+
* every pointer the caller read, so publishing and merging are held to one rule.
|
|
221
|
+
*
|
|
222
|
+
* Pure, and synchronous: the caller reads the store and hands over what it found, so this runs
|
|
223
|
+
* in CI, in a worker, or in a browser studio unchanged.
|
|
224
|
+
*/
|
|
225
|
+
declare function checkCompatibility(live: readonly LiveRoute[], registry: Registry): CompatibilityReport;
|
|
226
|
+
|
|
158
227
|
type RollbackCheck = {
|
|
159
228
|
compatible: true;
|
|
160
229
|
} | {
|
|
@@ -188,8 +257,7 @@ declare function createRegistry(blocks: readonly Block[]): Registry;
|
|
|
188
257
|
/**
|
|
189
258
|
* Identity at runtime; its job is to fix the generic parameters at the call site so props are
|
|
190
259
|
* inferred from the schema rather than declared beside it. The checks here are the ones the
|
|
191
|
-
* type system cannot make — a slot that no composition could satisfy
|
|
192
|
-
* a version this block never reaches.
|
|
260
|
+
* type system cannot make — a version below 1, or a slot that no composition could satisfy.
|
|
193
261
|
*/
|
|
194
262
|
declare function defineBlock<Schema extends StandardSchemaV1, Component>(block: Block<Schema, Component>): Block<Schema, Component>;
|
|
195
263
|
|
|
@@ -200,18 +268,12 @@ declare function defineBlock<Schema extends StandardSchemaV1, Component>(block:
|
|
|
200
268
|
*/
|
|
201
269
|
declare function defineCatalog(entries: Record<string, CatalogEntry>): Catalog;
|
|
202
270
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
/** Present only for `enum`. */
|
|
210
|
-
members?: readonly string[];
|
|
211
|
-
}
|
|
212
|
-
interface SchemaAdapter {
|
|
213
|
-
describe(schema: unknown): FieldNode[];
|
|
214
|
-
}
|
|
271
|
+
/**
|
|
272
|
+
* The report as a CI log reads it. `checked` leads every line, including the passing one: a run
|
|
273
|
+
* that found no pointers and a run that cleared eight are the same word otherwise, and the first
|
|
274
|
+
* of those is a gate certifying nothing.
|
|
275
|
+
*/
|
|
276
|
+
declare function formatCompatibilityReport(report: CompatibilityReport): string;
|
|
215
277
|
|
|
216
278
|
/**
|
|
217
279
|
* matchKind is parsed from the route at publish, never caller-supplied. It lives in core so
|
|
@@ -219,4 +281,66 @@ interface SchemaAdapter {
|
|
|
219
281
|
*/
|
|
220
282
|
declare function parseMatchKind(route: string): RoutePointer["matchKind"];
|
|
221
283
|
|
|
222
|
-
|
|
284
|
+
/**
|
|
285
|
+
* The inline emphasis a span may carry. Semantic, never stylistic, and closed: a construct
|
|
286
|
+
* outside this set is added as a member deliberately, not smuggled in as markup.
|
|
287
|
+
*/
|
|
288
|
+
type RichTextMark = "strong" | "em" | "code";
|
|
289
|
+
/** The block kinds rich text is built from. Closed for the same reason the marks are. */
|
|
290
|
+
type RichTextBlockKind = "paragraph" | "listItem";
|
|
291
|
+
/** A run of text and what is true of it. Inert: nothing here is parsed or evaluated at render. */
|
|
292
|
+
interface RichTextSpan {
|
|
293
|
+
text: string;
|
|
294
|
+
marks?: readonly RichTextMark[];
|
|
295
|
+
href?: string;
|
|
296
|
+
}
|
|
297
|
+
/** One block of a rich-text value: its kind, and the ordered spans it reads as. */
|
|
298
|
+
interface RichTextBlock {
|
|
299
|
+
kind: RichTextBlockKind;
|
|
300
|
+
spans: readonly RichTextSpan[];
|
|
301
|
+
}
|
|
302
|
+
/** An ordered array of blocks — the whole value a `richText()` field holds. */
|
|
303
|
+
type RichText = readonly RichTextBlock[];
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* A schema `core` hand-writes rather than one a validator brings. Narrower than
|
|
307
|
+
* `StandardSchemaV1` in the two ways that matter to a consumer hosting it: `validate` is
|
|
308
|
+
* synchronous, which compile requires anyway, and the JSON Schema converter is always there,
|
|
309
|
+
* so the studio can read the field tree without testing for it.
|
|
310
|
+
*/
|
|
311
|
+
interface StandardDataSchema<Value> {
|
|
312
|
+
readonly "~standard": {
|
|
313
|
+
readonly version: 1;
|
|
314
|
+
readonly vendor: string;
|
|
315
|
+
readonly validate: (value: unknown) => StandardSchemaV1.Result<Value>;
|
|
316
|
+
readonly jsonSchema: StandardJSONSchemaV1.Converter;
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* The rich-text field type: an ordered array of blocks, each an ordered array of inert spans,
|
|
322
|
+
* over closed mark and kind sets. Nothing in the value is parsed or evaluated at render, so an
|
|
323
|
+
* artifact carrying it is as inert as one carrying a string.
|
|
324
|
+
*
|
|
325
|
+
* A call rather than a bare value, so a field declaration reads the way the rest of a block's
|
|
326
|
+
* schema does and so options can arrive without changing call sites that pass none. A scan for
|
|
327
|
+
* this call finds every rich-text field in a registry.
|
|
328
|
+
*/
|
|
329
|
+
declare function richText(): StandardDataSchema<RichText>;
|
|
330
|
+
|
|
331
|
+
/** Copy-on-write down one dotted path. Paths address object fields only; `[]` has no single target. */
|
|
332
|
+
declare function setAtPath(target: Record<string, unknown>, path: string, value: unknown): Record<string, unknown>;
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* The first document operation: a new `DocumentVersion` with one prop set on one node,
|
|
336
|
+
* copy-on-write, every other node untouched by reference. It lives beside `compile` so any
|
|
337
|
+
* caller — an editor, a script, an agent — writes a document the same way
|
|
338
|
+
* ([#134](https://github.com/effekt/nubbin/issues/134)).
|
|
339
|
+
*
|
|
340
|
+
* It does not validate the value (that is `compile`'s job at the next compile) and does not
|
|
341
|
+
* bump `version` — appending a version is the authoring store's concern
|
|
342
|
+
* ([#11](https://github.com/effekt/nubbin/issues/11)), not a property of one edit.
|
|
343
|
+
*/
|
|
344
|
+
declare function setNodeProp(version: DocumentVersion, nodeId: string, path: string, value: unknown): DocumentVersion;
|
|
345
|
+
|
|
346
|
+
export { type Artifact, type ArtifactNode, type ArtifactStore, type Block, type BlockDocs, type BlockDrift, type BlockUi, type Catalog, type CatalogEntry, type CompatibilityReport, CompileError, type CompileIssue, type CompileIssueCode, type DocumentMeta, type DocumentVersion, type FieldHint, type FieldHintData, type FieldKind, type FieldNode, type Holes, type InferProps, type LiveRoute, type Manifest, type Node, type Registry, type RichText, type RichTextBlock, type RichTextBlockKind, type RichTextMark, type RichTextSpan, type RollbackCheck, type RouteIncompatibility, type RoutePointer, type SchemaAdapter, type SlotConstraint, type StandardDataSchema, type UnknownProps, checkCompatibility, checkRollback, compile, createRegistry, defineBlock, defineCatalog, formatCompatibilityReport, parseMatchKind, richText, setAtPath, setNodeProp, zodAdapter };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,111 @@
|
|
|
1
|
+
// src/adapters/isStandardJsonSchemaCapable.ts
|
|
2
|
+
function isStandardJsonSchemaCapable(value) {
|
|
3
|
+
if (typeof value !== "object" || value === null || !("~standard" in value)) return false;
|
|
4
|
+
const props = value["~standard"];
|
|
5
|
+
if (typeof props !== "object" || props === null || !("jsonSchema" in props)) return false;
|
|
6
|
+
const converter = props.jsonSchema;
|
|
7
|
+
if (typeof converter !== "object" || converter === null || !("input" in converter)) return false;
|
|
8
|
+
return typeof converter.input === "function";
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// src/adapters/projectJsonSchema.ts
|
|
12
|
+
var OPTIONS = {
|
|
13
|
+
target: "draft-2020-12",
|
|
14
|
+
/** zod's option name: a type JSON Schema cannot express throws here, at registration. */
|
|
15
|
+
libraryOptions: { unrepresentable: "throw" }
|
|
16
|
+
};
|
|
17
|
+
function projectJsonSchema(schema) {
|
|
18
|
+
if (!isStandardJsonSchemaCapable(schema)) {
|
|
19
|
+
throw new Error("Schema does not expose the Standard JSON Schema converter (spec >= 1.1)");
|
|
20
|
+
}
|
|
21
|
+
return schema["~standard"].jsonSchema.input(OPTIONS);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// src/adapters/kindOfJsonSchema.ts
|
|
25
|
+
function kindOfJsonSchema(node) {
|
|
26
|
+
if (Array.isArray(node.enum)) return "enum";
|
|
27
|
+
if (Array.isArray(node.oneOf) || Array.isArray(node.anyOf)) return "union";
|
|
28
|
+
switch (node.type) {
|
|
29
|
+
case "string":
|
|
30
|
+
return "string";
|
|
31
|
+
case "number":
|
|
32
|
+
case "integer":
|
|
33
|
+
return "number";
|
|
34
|
+
case "boolean":
|
|
35
|
+
return "boolean";
|
|
36
|
+
case "array":
|
|
37
|
+
return "array";
|
|
38
|
+
case "object":
|
|
39
|
+
return "object";
|
|
40
|
+
default:
|
|
41
|
+
return "unknown";
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// src/adapters/fieldNodeAt.ts
|
|
46
|
+
function fieldNodeAt(path, node, optional) {
|
|
47
|
+
const kind = kindOfJsonSchema(node);
|
|
48
|
+
if (kind === "enum" && Array.isArray(node.enum)) {
|
|
49
|
+
return { path, kind, optional, members: node.enum.map(String) };
|
|
50
|
+
}
|
|
51
|
+
return { path, kind, optional };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// src/adapters/isJsonSchemaNode.ts
|
|
55
|
+
function isJsonSchemaNode(value) {
|
|
56
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// src/adapters/walkArrayItems.ts
|
|
60
|
+
function walkArrayItems(arrayNode, path, descend) {
|
|
61
|
+
if (!isJsonSchemaNode(arrayNode.items)) return [];
|
|
62
|
+
const itemPath = `${path}[]`;
|
|
63
|
+
return [fieldNodeAt(itemPath, arrayNode.items, false), ...descend(arrayNode.items, itemPath)];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// src/adapters/walkObjectProperties.ts
|
|
67
|
+
function walkObjectProperties(objectNode, basePath, descend) {
|
|
68
|
+
const { properties, required } = objectNode;
|
|
69
|
+
if (!isJsonSchemaNode(properties)) return [];
|
|
70
|
+
const requiredNames = Array.isArray(required) ? required : [];
|
|
71
|
+
const fields = [];
|
|
72
|
+
for (const [name, child] of Object.entries(properties)) {
|
|
73
|
+
if (!isJsonSchemaNode(child)) continue;
|
|
74
|
+
const path = basePath === "" ? name : `${basePath}.${name}`;
|
|
75
|
+
fields.push(fieldNodeAt(path, child, !requiredNames.includes(name)));
|
|
76
|
+
fields.push(...descend(child, path));
|
|
77
|
+
}
|
|
78
|
+
return fields;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// src/adapters/walkUnionBranches.ts
|
|
82
|
+
function walkUnionBranches(unionNode, path, descend) {
|
|
83
|
+
const branches = [unionNode.oneOf, unionNode.anyOf].filter(Array.isArray).flat();
|
|
84
|
+
return branches.flatMap((branch) => isJsonSchemaNode(branch) ? descend(branch, path) : []);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// src/adapters/walkJsonSchema.ts
|
|
88
|
+
function walkJsonSchema(node, basePath) {
|
|
89
|
+
const kind = kindOfJsonSchema(node);
|
|
90
|
+
if (kind === "object") return walkObjectProperties(node, basePath, walkJsonSchema);
|
|
91
|
+
if (kind === "array") return walkArrayItems(node, basePath, walkJsonSchema);
|
|
92
|
+
if (kind === "union") return walkUnionBranches(node, basePath, walkJsonSchema);
|
|
93
|
+
return [];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// src/adapters/zodAdapter.ts
|
|
97
|
+
var zodAdapter = {
|
|
98
|
+
describe(schema) {
|
|
99
|
+
const fields = walkJsonSchema(projectJsonSchema(schema), "");
|
|
100
|
+
const seen = /* @__PURE__ */ new Set();
|
|
101
|
+
return fields.filter((field) => {
|
|
102
|
+
if (seen.has(field.path)) return false;
|
|
103
|
+
seen.add(field.path);
|
|
104
|
+
return true;
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
1
109
|
// src/CompileError.ts
|
|
2
110
|
var CompileError = class extends Error {
|
|
3
111
|
issues;
|
|
@@ -16,6 +124,34 @@ function checkRollback(artifact, registry) {
|
|
|
16
124
|
return drifted.length === 0 ? { compatible: true } : { compatible: false, drifted };
|
|
17
125
|
}
|
|
18
126
|
|
|
127
|
+
// src/describeDrift.ts
|
|
128
|
+
function describeDrift(artifact, registry, drifted) {
|
|
129
|
+
return drifted.flatMap((block) => {
|
|
130
|
+
const live = artifact.blockVersions[block];
|
|
131
|
+
return live === void 0 ? [] : [{ block, live, registered: registry.get(block)?.version ?? null }];
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// src/checkCompatibility.ts
|
|
136
|
+
function checkCompatibility(live, registry) {
|
|
137
|
+
const incompatible = live.flatMap(({ pointer, artifact }) => {
|
|
138
|
+
const { route, hash } = pointer;
|
|
139
|
+
if (artifact === null) {
|
|
140
|
+
return [{ route, hash, reason: "unreadable-artifact" }];
|
|
141
|
+
}
|
|
142
|
+
const check = checkRollback(artifact, registry);
|
|
143
|
+
return check.compatible ? [] : [
|
|
144
|
+
{
|
|
145
|
+
route,
|
|
146
|
+
hash,
|
|
147
|
+
reason: "block-drift",
|
|
148
|
+
drifted: describeDrift(artifact, registry, check.drifted)
|
|
149
|
+
}
|
|
150
|
+
];
|
|
151
|
+
});
|
|
152
|
+
return { checked: live.length, compatible: incompatible.length === 0, incompatible };
|
|
153
|
+
}
|
|
154
|
+
|
|
19
155
|
// src/artifactNodeOf.ts
|
|
20
156
|
function artifactNodeOf(node, resolve) {
|
|
21
157
|
const { props, holes } = resolve(node);
|
|
@@ -55,7 +191,7 @@ function wireSlots(version, built) {
|
|
|
55
191
|
|
|
56
192
|
// src/denormalize.ts
|
|
57
193
|
function denormalize(version, resolve) {
|
|
58
|
-
const pending = [version.
|
|
194
|
+
const pending = [...version.roots];
|
|
59
195
|
const built = /* @__PURE__ */ new Map();
|
|
60
196
|
while (pending.length > 0) {
|
|
61
197
|
const id = pending.pop();
|
|
@@ -68,8 +204,10 @@ function denormalize(version, resolve) {
|
|
|
68
204
|
}
|
|
69
205
|
}
|
|
70
206
|
wireSlots(version, built);
|
|
71
|
-
|
|
72
|
-
|
|
207
|
+
return version.roots.flatMap((id) => {
|
|
208
|
+
const root = built.get(id);
|
|
209
|
+
return root === void 0 ? [] : [root];
|
|
210
|
+
});
|
|
73
211
|
}
|
|
74
212
|
|
|
75
213
|
// src/fnv1a.ts
|
|
@@ -96,18 +234,43 @@ function hashArtifact(artifact) {
|
|
|
96
234
|
return fnv1a(JSON.stringify(artifact, sortKeys));
|
|
97
235
|
}
|
|
98
236
|
|
|
237
|
+
// src/splitPath.ts
|
|
238
|
+
function splitPath(path) {
|
|
239
|
+
const [head, ...tail] = path.split(".");
|
|
240
|
+
if (head === void 0 || head === "" || head.includes("[]")) {
|
|
241
|
+
throw new Error(`path "${path}" is not addressable`);
|
|
242
|
+
}
|
|
243
|
+
return { head, tail };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// src/takeAtPath.ts
|
|
247
|
+
function takeAtPath(target, path) {
|
|
248
|
+
const { head, tail } = splitPath(path);
|
|
249
|
+
if (!Object.hasOwn(target, head)) return { rest: target, taken: false };
|
|
250
|
+
if (tail.length === 0) {
|
|
251
|
+
const remaining = { ...target };
|
|
252
|
+
delete remaining[head];
|
|
253
|
+
return { rest: remaining, taken: true };
|
|
254
|
+
}
|
|
255
|
+
const child = target[head];
|
|
256
|
+
if (typeof child !== "object" || child === null || Array.isArray(child)) {
|
|
257
|
+
return { rest: target, taken: false };
|
|
258
|
+
}
|
|
259
|
+
const inner = takeAtPath(child, tail.join("."));
|
|
260
|
+
if (!inner.taken) return { rest: target, taken: false };
|
|
261
|
+
return { rest: { ...target, [head]: inner.rest }, taken: true };
|
|
262
|
+
}
|
|
263
|
+
|
|
99
264
|
// src/partitionProps.ts
|
|
100
265
|
function partitionProps(validated, hints) {
|
|
101
|
-
|
|
266
|
+
let props = { ...validated };
|
|
102
267
|
const holes = {};
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
const
|
|
106
|
-
if (
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
holes[key] = data;
|
|
110
|
-
}
|
|
268
|
+
for (const [path, hint] of Object.entries(hints?.fields ?? {})) {
|
|
269
|
+
if (hint.data === void 0) continue;
|
|
270
|
+
const { rest, taken } = takeAtPath(props, path);
|
|
271
|
+
if (!taken) continue;
|
|
272
|
+
props = rest;
|
|
273
|
+
holes[path] = hint.data;
|
|
111
274
|
}
|
|
112
275
|
return { props, holes };
|
|
113
276
|
}
|
|
@@ -216,12 +379,11 @@ function pushCycleFrame(stack, state, version, id) {
|
|
|
216
379
|
stack.push({ id, edges: slotEdges(node), next: 0 });
|
|
217
380
|
}
|
|
218
381
|
|
|
219
|
-
// src/
|
|
220
|
-
function
|
|
221
|
-
const state = /* @__PURE__ */ new Map();
|
|
382
|
+
// src/findCyclesFrom.ts
|
|
383
|
+
function findCyclesFrom(version, root, state) {
|
|
222
384
|
const stack = [];
|
|
223
385
|
const issues = [];
|
|
224
|
-
pushCycleFrame(stack, state, version,
|
|
386
|
+
pushCycleFrame(stack, state, version, root);
|
|
225
387
|
while (stack.length > 0) {
|
|
226
388
|
const frame = stack.at(-1);
|
|
227
389
|
if (frame === void 0) break;
|
|
@@ -246,17 +408,17 @@ function findCycles(version) {
|
|
|
246
408
|
return issues;
|
|
247
409
|
}
|
|
248
410
|
|
|
411
|
+
// src/findCycles.ts
|
|
412
|
+
function findCycles(version) {
|
|
413
|
+
const state = /* @__PURE__ */ new Map();
|
|
414
|
+
return version.roots.flatMap(
|
|
415
|
+
(root) => state.has(root) ? [] : findCyclesFrom(version, root, state)
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
|
|
249
419
|
// src/findDanglingChildren.ts
|
|
250
420
|
function findDanglingChildren(version) {
|
|
251
421
|
const issues = [];
|
|
252
|
-
if (version.elements[version.root] === void 0) {
|
|
253
|
-
issues.push({
|
|
254
|
-
nodeId: version.root,
|
|
255
|
-
path: "root",
|
|
256
|
-
code: "dangling-child",
|
|
257
|
-
message: `root "${version.root}" has no matching element`
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
422
|
for (const node of Object.values(version.elements)) {
|
|
261
423
|
for (const edge of slotEdges(node)) {
|
|
262
424
|
if (version.elements[edge.childId] === void 0) {
|
|
@@ -272,6 +434,30 @@ function findDanglingChildren(version) {
|
|
|
272
434
|
return issues;
|
|
273
435
|
}
|
|
274
436
|
|
|
437
|
+
// src/findRootIssues.ts
|
|
438
|
+
function findRootIssues(version) {
|
|
439
|
+
if (version.roots.length === 0) {
|
|
440
|
+
return [
|
|
441
|
+
{
|
|
442
|
+
nodeId: "",
|
|
443
|
+
path: "roots",
|
|
444
|
+
code: "no-roots",
|
|
445
|
+
message: "a document needs at least one root, and this one names none"
|
|
446
|
+
}
|
|
447
|
+
];
|
|
448
|
+
}
|
|
449
|
+
return version.roots.flatMap(
|
|
450
|
+
(root) => version.elements[root] === void 0 ? [
|
|
451
|
+
{
|
|
452
|
+
nodeId: root,
|
|
453
|
+
path: "roots",
|
|
454
|
+
code: "dangling-child",
|
|
455
|
+
message: `root "${root}" has no matching element`
|
|
456
|
+
}
|
|
457
|
+
] : []
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
|
|
275
461
|
// src/disallowedChildren.ts
|
|
276
462
|
function disallowedChildren(parent, path, childIds, allow, version) {
|
|
277
463
|
if (allow === void 0) return [];
|
|
@@ -358,8 +544,8 @@ function findUnknownBlocks(version, registry) {
|
|
|
358
544
|
|
|
359
545
|
// src/reachableIds.ts
|
|
360
546
|
function reachableIds(version) {
|
|
361
|
-
const seen =
|
|
362
|
-
const queue = [version.
|
|
547
|
+
const seen = new Set(version.roots);
|
|
548
|
+
const queue = [...version.roots];
|
|
363
549
|
while (queue.length > 0) {
|
|
364
550
|
const id = queue.pop();
|
|
365
551
|
const node = id === void 0 ? void 0 : version.elements[id];
|
|
@@ -383,7 +569,7 @@ function findUnreachable(version) {
|
|
|
383
569
|
nodeId: node.id,
|
|
384
570
|
path: "",
|
|
385
571
|
code: "unreachable",
|
|
386
|
-
message: `no slot reaches "${node.id}" from
|
|
572
|
+
message: `no slot reaches "${node.id}" from any root`
|
|
387
573
|
});
|
|
388
574
|
}
|
|
389
575
|
return issues;
|
|
@@ -391,8 +577,11 @@ function findUnreachable(version) {
|
|
|
391
577
|
|
|
392
578
|
// src/validateStructure.ts
|
|
393
579
|
function validateStructure(version, registry) {
|
|
580
|
+
const rootIssues = findRootIssues(version);
|
|
581
|
+
if (version.roots.length === 0) return rootIssues;
|
|
394
582
|
return [
|
|
395
583
|
...findUnknownBlocks(version, registry),
|
|
584
|
+
...rootIssues,
|
|
396
585
|
...findDanglingChildren(version),
|
|
397
586
|
...findCycles(version),
|
|
398
587
|
...findUnreachable(version),
|
|
@@ -401,7 +590,7 @@ function validateStructure(version, registry) {
|
|
|
401
590
|
}
|
|
402
591
|
|
|
403
592
|
// src/version.constants.ts
|
|
404
|
-
var NUBBIN_VERSION = "0.1.0-rc.
|
|
593
|
+
var NUBBIN_VERSION = "0.1.0-rc.6";
|
|
405
594
|
|
|
406
595
|
// src/compile.ts
|
|
407
596
|
function compile(version, catalog, registry, route) {
|
|
@@ -469,17 +658,6 @@ function assertBlockVersion(name, version) {
|
|
|
469
658
|
}
|
|
470
659
|
}
|
|
471
660
|
|
|
472
|
-
// src/assertMigrateKeys.ts
|
|
473
|
-
var FIRST_MIGRATABLE_VERSION = 2;
|
|
474
|
-
function assertMigrateKeys(name, version, migrate) {
|
|
475
|
-
for (const key of Object.keys(migrate ?? {})) {
|
|
476
|
-
const target = Number(key);
|
|
477
|
-
if (target < FIRST_MIGRATABLE_VERSION || target > version) {
|
|
478
|
-
throw new Error(`${name}: migrate key ${key} is outside the reachable range 2..${version}`);
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
|
|
483
661
|
// src/assertSlotBounds.ts
|
|
484
662
|
function assertSlotBounds(name, slots) {
|
|
485
663
|
for (const [slot, { min, max }] of Object.entries(slots)) {
|
|
@@ -493,118 +671,9 @@ function assertSlotBounds(name, slots) {
|
|
|
493
671
|
function defineBlock(block) {
|
|
494
672
|
assertBlockVersion(block.name, block.version);
|
|
495
673
|
assertSlotBounds(block.name, block.slots);
|
|
496
|
-
assertMigrateKeys(block.name, block.version, block.migrate);
|
|
497
674
|
return block;
|
|
498
675
|
}
|
|
499
676
|
|
|
500
|
-
// src/adapters/isStandardJsonSchemaCapable.ts
|
|
501
|
-
function isStandardJsonSchemaCapable(value) {
|
|
502
|
-
if (typeof value !== "object" || value === null || !("~standard" in value)) return false;
|
|
503
|
-
const props = value["~standard"];
|
|
504
|
-
if (typeof props !== "object" || props === null || !("jsonSchema" in props)) return false;
|
|
505
|
-
const converter = props.jsonSchema;
|
|
506
|
-
if (typeof converter !== "object" || converter === null || !("input" in converter)) return false;
|
|
507
|
-
return typeof converter.input === "function";
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
// src/adapters/projectJsonSchema.ts
|
|
511
|
-
var OPTIONS = {
|
|
512
|
-
target: "draft-2020-12",
|
|
513
|
-
/** zod's option name: a type JSON Schema cannot express throws here, at registration. */
|
|
514
|
-
libraryOptions: { unrepresentable: "throw" }
|
|
515
|
-
};
|
|
516
|
-
function projectJsonSchema(schema) {
|
|
517
|
-
if (!isStandardJsonSchemaCapable(schema)) {
|
|
518
|
-
throw new Error("Schema does not expose the Standard JSON Schema converter (spec >= 1.1)");
|
|
519
|
-
}
|
|
520
|
-
return schema["~standard"].jsonSchema.input(OPTIONS);
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
// src/adapters/kindOfJsonSchema.ts
|
|
524
|
-
function kindOfJsonSchema(node) {
|
|
525
|
-
if (Array.isArray(node.enum)) return "enum";
|
|
526
|
-
if (Array.isArray(node.oneOf) || Array.isArray(node.anyOf)) return "union";
|
|
527
|
-
switch (node.type) {
|
|
528
|
-
case "string":
|
|
529
|
-
return "string";
|
|
530
|
-
case "number":
|
|
531
|
-
case "integer":
|
|
532
|
-
return "number";
|
|
533
|
-
case "boolean":
|
|
534
|
-
return "boolean";
|
|
535
|
-
case "array":
|
|
536
|
-
return "array";
|
|
537
|
-
case "object":
|
|
538
|
-
return "object";
|
|
539
|
-
default:
|
|
540
|
-
return "unknown";
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
// src/adapters/fieldNodeAt.ts
|
|
545
|
-
function fieldNodeAt(path, node, optional) {
|
|
546
|
-
const kind = kindOfJsonSchema(node);
|
|
547
|
-
if (kind === "enum" && Array.isArray(node.enum)) {
|
|
548
|
-
return { path, kind, optional, members: node.enum.map(String) };
|
|
549
|
-
}
|
|
550
|
-
return { path, kind, optional };
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
// src/adapters/isJsonSchemaNode.ts
|
|
554
|
-
function isJsonSchemaNode(value) {
|
|
555
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
// src/adapters/walkArrayItems.ts
|
|
559
|
-
function walkArrayItems(arrayNode, path, descend) {
|
|
560
|
-
if (!isJsonSchemaNode(arrayNode.items)) return [];
|
|
561
|
-
const itemPath = `${path}[]`;
|
|
562
|
-
return [fieldNodeAt(itemPath, arrayNode.items, false), ...descend(arrayNode.items, itemPath)];
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
// src/adapters/walkObjectProperties.ts
|
|
566
|
-
function walkObjectProperties(objectNode, basePath, descend) {
|
|
567
|
-
const { properties, required } = objectNode;
|
|
568
|
-
if (!isJsonSchemaNode(properties)) return [];
|
|
569
|
-
const requiredNames = Array.isArray(required) ? required : [];
|
|
570
|
-
const fields = [];
|
|
571
|
-
for (const [name, child] of Object.entries(properties)) {
|
|
572
|
-
if (!isJsonSchemaNode(child)) continue;
|
|
573
|
-
const path = basePath === "" ? name : `${basePath}.${name}`;
|
|
574
|
-
fields.push(fieldNodeAt(path, child, !requiredNames.includes(name)));
|
|
575
|
-
fields.push(...descend(child, path));
|
|
576
|
-
}
|
|
577
|
-
return fields;
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
// src/adapters/walkUnionBranches.ts
|
|
581
|
-
function walkUnionBranches(unionNode, path, descend) {
|
|
582
|
-
const branches = [unionNode.oneOf, unionNode.anyOf].filter(Array.isArray).flat();
|
|
583
|
-
return branches.flatMap((branch) => isJsonSchemaNode(branch) ? descend(branch, path) : []);
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
// src/adapters/walkJsonSchema.ts
|
|
587
|
-
function walkJsonSchema(node, basePath) {
|
|
588
|
-
const kind = kindOfJsonSchema(node);
|
|
589
|
-
if (kind === "object") return walkObjectProperties(node, basePath, walkJsonSchema);
|
|
590
|
-
if (kind === "array") return walkArrayItems(node, basePath, walkJsonSchema);
|
|
591
|
-
if (kind === "union") return walkUnionBranches(node, basePath, walkJsonSchema);
|
|
592
|
-
return [];
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
// src/adapters/zodAdapter.ts
|
|
596
|
-
var zodAdapter = {
|
|
597
|
-
describe(schema) {
|
|
598
|
-
const fields = walkJsonSchema(projectJsonSchema(schema), "");
|
|
599
|
-
const seen = /* @__PURE__ */ new Set();
|
|
600
|
-
return fields.filter((field) => {
|
|
601
|
-
if (seen.has(field.path)) return false;
|
|
602
|
-
seen.add(field.path);
|
|
603
|
-
return true;
|
|
604
|
-
});
|
|
605
|
-
}
|
|
606
|
-
};
|
|
607
|
-
|
|
608
677
|
// src/adapters/resolveHintPaths.ts
|
|
609
678
|
function resolveHintPaths(blockName, schema, fields) {
|
|
610
679
|
const known = new Set(zodAdapter.describe(schema).map((field) => field.path));
|
|
@@ -616,6 +685,28 @@ function resolveHintPaths(blockName, schema, fields) {
|
|
|
616
685
|
}
|
|
617
686
|
}
|
|
618
687
|
|
|
688
|
+
// src/assertDataHintAddressable.ts
|
|
689
|
+
function assertDataHintAddressable(blockName, fields) {
|
|
690
|
+
const seen = [];
|
|
691
|
+
for (const [path, hint] of Object.entries(fields)) {
|
|
692
|
+
if (hint.data === void 0) continue;
|
|
693
|
+
if (path.includes("[]")) {
|
|
694
|
+
throw new Error(
|
|
695
|
+
`${blockName}: ui.fields["${path}"] sets \`data\`, but a hole cannot address an array member \u2014 "[]" has no single target`
|
|
696
|
+
);
|
|
697
|
+
}
|
|
698
|
+
const nested = seen.find(
|
|
699
|
+
(other) => path.startsWith(`${other}.`) || other.startsWith(`${path}.`)
|
|
700
|
+
);
|
|
701
|
+
if (nested !== void 0) {
|
|
702
|
+
throw new Error(
|
|
703
|
+
`${blockName}: ui.fields["${nested}"] and ui.fields["${path}"] both set \`data\`, but their paths overlap \u2014 two holes over one value have no defined order of application`
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
seen.push(path);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
619
710
|
// src/assertValidDefaults.ts
|
|
620
711
|
function assertValidDefaults(blockName, schema, defaults) {
|
|
621
712
|
const result = standardValidate(schema, defaults);
|
|
@@ -629,6 +720,7 @@ function defineCatalog(entries) {
|
|
|
629
720
|
for (const [blockName, entry] of Object.entries(entries)) {
|
|
630
721
|
if (entry.ui?.fields !== void 0) {
|
|
631
722
|
resolveHintPaths(blockName, entry.schema, entry.ui.fields);
|
|
723
|
+
assertDataHintAddressable(blockName, entry.ui.fields);
|
|
632
724
|
}
|
|
633
725
|
if (entry.defaults !== void 0) {
|
|
634
726
|
assertValidDefaults(blockName, entry.schema, entry.defaults);
|
|
@@ -637,6 +729,27 @@ function defineCatalog(entries) {
|
|
|
637
729
|
return entries;
|
|
638
730
|
}
|
|
639
731
|
|
|
732
|
+
// src/formatBlockDrift.ts
|
|
733
|
+
function formatBlockDrift(drift) {
|
|
734
|
+
return drift.registered === null ? `${drift.block}: page needs v${drift.live}, no longer in the registry` : `${drift.block}: page needs v${drift.live}, registry has v${drift.registered}`;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
// src/formatRouteIncompatibility.ts
|
|
738
|
+
function formatRouteIncompatibility(incompatibility) {
|
|
739
|
+
const heading = ` ${incompatibility.route} (artifact ${incompatibility.hash})`;
|
|
740
|
+
const reasons = incompatibility.reason === "unreadable-artifact" ? ["the store holds no artifact at this hash"] : incompatibility.drifted.map(formatBlockDrift);
|
|
741
|
+
return [heading, ...reasons.map((reason) => ` ${reason}`)].join("\n");
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// src/formatCompatibilityReport.ts
|
|
745
|
+
function formatCompatibilityReport(report) {
|
|
746
|
+
if (report.compatible) {
|
|
747
|
+
return `${report.checked} live route pointer(s) checked; every one is compatible with this registry.`;
|
|
748
|
+
}
|
|
749
|
+
const summary = `${report.incompatible.length} of ${report.checked} live route pointer(s) are incompatible with this registry:`;
|
|
750
|
+
return [summary, ...report.incompatible.map(formatRouteIncompatibility)].join("\n");
|
|
751
|
+
}
|
|
752
|
+
|
|
640
753
|
// src/parseMatchKind.ts
|
|
641
754
|
function parseMatchKind(route) {
|
|
642
755
|
if (route.endsWith("/*")) {
|
|
@@ -647,12 +760,185 @@ function parseMatchKind(route) {
|
|
|
647
760
|
}
|
|
648
761
|
return "exact";
|
|
649
762
|
}
|
|
763
|
+
|
|
764
|
+
// src/defineStandardSchema.ts
|
|
765
|
+
var STANDARD_SCHEMA_VERSION = 1;
|
|
766
|
+
function defineStandardSchema(issuesOf, jsonSchemaOf) {
|
|
767
|
+
return {
|
|
768
|
+
"~standard": {
|
|
769
|
+
version: STANDARD_SCHEMA_VERSION,
|
|
770
|
+
vendor: "nubbin",
|
|
771
|
+
validate: (value) => {
|
|
772
|
+
const issues = issuesOf(value);
|
|
773
|
+
return issues.length > 0 ? { issues } : { value };
|
|
774
|
+
},
|
|
775
|
+
jsonSchema: { input: jsonSchemaOf, output: jsonSchemaOf }
|
|
776
|
+
}
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
// src/richText.constants.ts
|
|
781
|
+
var RICH_TEXT_MARKS = ["strong", "em", "code"];
|
|
782
|
+
var RICH_TEXT_BLOCK_KINDS = ["paragraph", "listItem"];
|
|
783
|
+
var RICH_TEXT_SPAN_KEYS = ["text", "marks", "href"];
|
|
784
|
+
var RICH_TEXT_BLOCK_KEYS = ["kind", "spans"];
|
|
785
|
+
|
|
786
|
+
// src/isRichTextBlockKind.ts
|
|
787
|
+
function isRichTextBlockKind(value) {
|
|
788
|
+
return RICH_TEXT_BLOCK_KINDS.some((kind) => kind === value);
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
// src/nestedSchemaIssues.ts
|
|
792
|
+
function nestedSchemaIssues(schema, value, prefix) {
|
|
793
|
+
const result = standardValidate(schema, value);
|
|
794
|
+
if (result.issues === void 0) return [];
|
|
795
|
+
return result.issues.map((issue) => ({
|
|
796
|
+
message: issue.message,
|
|
797
|
+
path: [...prefix, ...issue.path ?? []]
|
|
798
|
+
}));
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
// src/isRichTextMark.ts
|
|
802
|
+
function isRichTextMark(value) {
|
|
803
|
+
return RICH_TEXT_MARKS.some((mark) => mark === value);
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
// src/richTextMarkIssues.ts
|
|
807
|
+
function richTextMarkIssues(marks) {
|
|
808
|
+
if (marks === void 0) return [];
|
|
809
|
+
if (!Array.isArray(marks)) return [{ message: "marks must be an array", path: ["marks"] }];
|
|
810
|
+
return marks.flatMap(
|
|
811
|
+
(mark, index) => isRichTextMark(mark) ? [] : [
|
|
812
|
+
{
|
|
813
|
+
message: `unknown mark ${JSON.stringify(mark)}; expected one of ${RICH_TEXT_MARKS.join(", ")}`,
|
|
814
|
+
path: ["marks", index]
|
|
815
|
+
}
|
|
816
|
+
]
|
|
817
|
+
);
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
// src/unexpectedKeyIssues.ts
|
|
821
|
+
function unexpectedKeyIssues(value, allowed) {
|
|
822
|
+
return Object.keys(value).filter((key) => !allowed.includes(key)).map((key) => ({ message: `unexpected key "${key}"`, path: [key] }));
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
// src/richTextSpanIssues.ts
|
|
826
|
+
function richTextSpanIssues(value) {
|
|
827
|
+
if (!isUnknownProps(value)) return [{ message: "a span must be an object", path: [] }];
|
|
828
|
+
const issues = [];
|
|
829
|
+
if (typeof value.text !== "string") {
|
|
830
|
+
issues.push({ message: "text must be a string", path: ["text"] });
|
|
831
|
+
}
|
|
832
|
+
issues.push(...richTextMarkIssues(value.marks));
|
|
833
|
+
if (value.href !== void 0 && typeof value.href !== "string") {
|
|
834
|
+
issues.push({ message: "href must be a string", path: ["href"] });
|
|
835
|
+
}
|
|
836
|
+
issues.push(...unexpectedKeyIssues(value, RICH_TEXT_SPAN_KEYS));
|
|
837
|
+
return issues;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
// src/richTextSpanSchema.ts
|
|
841
|
+
var richTextSpanSchema = defineStandardSchema(richTextSpanIssues, () => ({
|
|
842
|
+
type: "object",
|
|
843
|
+
properties: {
|
|
844
|
+
text: { type: "string" },
|
|
845
|
+
marks: { type: "array", items: { type: "string", enum: [...RICH_TEXT_MARKS] } },
|
|
846
|
+
href: { type: "string" }
|
|
847
|
+
},
|
|
848
|
+
required: ["text"],
|
|
849
|
+
additionalProperties: false
|
|
850
|
+
}));
|
|
851
|
+
|
|
852
|
+
// src/richTextBlockIssues.ts
|
|
853
|
+
function richTextBlockIssues(value) {
|
|
854
|
+
if (!isUnknownProps(value)) return [{ message: "a block must be an object", path: [] }];
|
|
855
|
+
const issues = [];
|
|
856
|
+
if (!isRichTextBlockKind(value.kind)) {
|
|
857
|
+
issues.push({
|
|
858
|
+
message: `unknown kind ${JSON.stringify(value.kind)}; expected one of ${RICH_TEXT_BLOCK_KINDS.join(", ")}`,
|
|
859
|
+
path: ["kind"]
|
|
860
|
+
});
|
|
861
|
+
}
|
|
862
|
+
if (Array.isArray(value.spans)) {
|
|
863
|
+
issues.push(
|
|
864
|
+
...value.spans.flatMap(
|
|
865
|
+
(span, index) => nestedSchemaIssues(richTextSpanSchema, span, ["spans", index])
|
|
866
|
+
)
|
|
867
|
+
);
|
|
868
|
+
} else {
|
|
869
|
+
issues.push({ message: "spans must be an array", path: ["spans"] });
|
|
870
|
+
}
|
|
871
|
+
issues.push(...unexpectedKeyIssues(value, RICH_TEXT_BLOCK_KEYS));
|
|
872
|
+
return issues;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
// src/richTextBlockSchema.ts
|
|
876
|
+
var richTextBlockSchema = defineStandardSchema(
|
|
877
|
+
richTextBlockIssues,
|
|
878
|
+
(options) => ({
|
|
879
|
+
type: "object",
|
|
880
|
+
properties: {
|
|
881
|
+
kind: { type: "string", enum: [...RICH_TEXT_BLOCK_KINDS] },
|
|
882
|
+
spans: { type: "array", items: richTextSpanSchema["~standard"].jsonSchema.input(options) }
|
|
883
|
+
},
|
|
884
|
+
required: ["kind", "spans"],
|
|
885
|
+
additionalProperties: false
|
|
886
|
+
})
|
|
887
|
+
);
|
|
888
|
+
|
|
889
|
+
// src/richTextIssues.ts
|
|
890
|
+
function richTextIssues(value) {
|
|
891
|
+
if (!Array.isArray(value)) {
|
|
892
|
+
return [{ message: "rich text must be an array of blocks", path: [] }];
|
|
893
|
+
}
|
|
894
|
+
return value.flatMap(
|
|
895
|
+
(block, index) => nestedSchemaIssues(richTextBlockSchema, block, [index])
|
|
896
|
+
);
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
// src/richTextSchema.ts
|
|
900
|
+
var richTextSchema = defineStandardSchema(richTextIssues, (options) => ({
|
|
901
|
+
type: "array",
|
|
902
|
+
items: richTextBlockSchema["~standard"].jsonSchema.input(options)
|
|
903
|
+
}));
|
|
904
|
+
|
|
905
|
+
// src/richText.ts
|
|
906
|
+
function richText() {
|
|
907
|
+
return richTextSchema;
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
// src/setAtPath.ts
|
|
911
|
+
function setAtPath(target, path, value) {
|
|
912
|
+
const { head, tail } = splitPath(path);
|
|
913
|
+
if (tail.length === 0) {
|
|
914
|
+
return { ...target, [head]: value };
|
|
915
|
+
}
|
|
916
|
+
const child = target[head];
|
|
917
|
+
const base = typeof child === "object" && child !== null ? child : {};
|
|
918
|
+
return { ...target, [head]: setAtPath(base, tail.join("."), value) };
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
// src/setNodeProp.ts
|
|
922
|
+
function setNodeProp(version, nodeId, path, value) {
|
|
923
|
+
const node = version.elements[nodeId];
|
|
924
|
+
if (node === void 0) {
|
|
925
|
+
throw new Error(`no node "${nodeId}" in document "${version.documentId}"`);
|
|
926
|
+
}
|
|
927
|
+
const edited = { ...node, props: setAtPath(node.props, path, value) };
|
|
928
|
+
return { ...version, elements: { ...version.elements, [nodeId]: edited } };
|
|
929
|
+
}
|
|
650
930
|
export {
|
|
651
931
|
CompileError,
|
|
932
|
+
checkCompatibility,
|
|
652
933
|
checkRollback,
|
|
653
934
|
compile,
|
|
654
935
|
createRegistry,
|
|
655
936
|
defineBlock,
|
|
656
937
|
defineCatalog,
|
|
657
|
-
|
|
938
|
+
formatCompatibilityReport,
|
|
939
|
+
parseMatchKind,
|
|
940
|
+
richText,
|
|
941
|
+
setAtPath,
|
|
942
|
+
setNodeProp,
|
|
943
|
+
zodAdapter
|
|
658
944
|
};
|
package/package.json
CHANGED