@nubbin/core 0.1.0-rc.5 → 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 +56 -8
- package/dist/index.js +243 -47
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { StandardSchemaV1 } from '@standard-schema/spec';
|
|
1
|
+
import { StandardSchemaV1, StandardJSONSchemaV1 } from '@standard-schema/spec';
|
|
2
2
|
|
|
3
3
|
type FieldKind = "string" | "number" | "boolean" | "enum" | "array" | "object" | "union" | "unknown";
|
|
4
4
|
interface FieldNode {
|
|
@@ -48,8 +48,6 @@ interface Block<Schema extends StandardSchemaV1 = StandardSchemaV1, Component =
|
|
|
48
48
|
/** A deprecated block still resolves; the studio hides it from the palette. */
|
|
49
49
|
status?: "active" | "deprecated";
|
|
50
50
|
slots: Record<string, SlotConstraint>;
|
|
51
|
-
/** Same-node prop reshaping only. It cannot touch slots, or split or delete a block. */
|
|
52
|
-
migrate?: Record<number, (props: UnknownProps) => UnknownProps>;
|
|
53
51
|
}
|
|
54
52
|
|
|
55
53
|
/** How a field's value resolves at render. Absent means static — the value freezes into props. */
|
|
@@ -99,7 +97,11 @@ interface Node {
|
|
|
99
97
|
interface DocumentVersion {
|
|
100
98
|
documentId: string;
|
|
101
99
|
version: number;
|
|
102
|
-
|
|
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[];
|
|
103
105
|
elements: Record<string, Node>;
|
|
104
106
|
meta: DocumentMeta;
|
|
105
107
|
createdAt: string;
|
|
@@ -154,7 +156,7 @@ interface ArtifactStore {
|
|
|
154
156
|
unpublish(route: string): Promise<void>;
|
|
155
157
|
}
|
|
156
158
|
|
|
157
|
-
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";
|
|
158
160
|
interface CompileIssue {
|
|
159
161
|
nodeId: string;
|
|
160
162
|
/** Where in the node the problem sits: `block`, `slots.items`, or a dotted prop path. */
|
|
@@ -255,8 +257,7 @@ declare function createRegistry(blocks: readonly Block[]): Registry;
|
|
|
255
257
|
/**
|
|
256
258
|
* Identity at runtime; its job is to fix the generic parameters at the call site so props are
|
|
257
259
|
* inferred from the schema rather than declared beside it. The checks here are the ones the
|
|
258
|
-
* type system cannot make — a slot that no composition could satisfy
|
|
259
|
-
* a version this block never reaches.
|
|
260
|
+
* type system cannot make — a version below 1, or a slot that no composition could satisfy.
|
|
260
261
|
*/
|
|
261
262
|
declare function defineBlock<Schema extends StandardSchemaV1, Component>(block: Block<Schema, Component>): Block<Schema, Component>;
|
|
262
263
|
|
|
@@ -280,6 +281,53 @@ declare function formatCompatibilityReport(report: CompatibilityReport): string;
|
|
|
280
281
|
*/
|
|
281
282
|
declare function parseMatchKind(route: string): RoutePointer["matchKind"];
|
|
282
283
|
|
|
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
|
+
|
|
283
331
|
/** Copy-on-write down one dotted path. Paths address object fields only; `[]` has no single target. */
|
|
284
332
|
declare function setAtPath(target: Record<string, unknown>, path: string, value: unknown): Record<string, unknown>;
|
|
285
333
|
|
|
@@ -295,4 +343,4 @@ declare function setAtPath(target: Record<string, unknown>, path: string, value:
|
|
|
295
343
|
*/
|
|
296
344
|
declare function setNodeProp(version: DocumentVersion, nodeId: string, path: string, value: unknown): DocumentVersion;
|
|
297
345
|
|
|
298
|
-
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 RollbackCheck, type RouteIncompatibility, type RoutePointer, type SchemaAdapter, type SlotConstraint, type UnknownProps, checkCompatibility, checkRollback, compile, createRegistry, defineBlock, defineCatalog, formatCompatibilityReport, parseMatchKind, setAtPath, setNodeProp, zodAdapter };
|
|
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
|
@@ -191,7 +191,7 @@ function wireSlots(version, built) {
|
|
|
191
191
|
|
|
192
192
|
// src/denormalize.ts
|
|
193
193
|
function denormalize(version, resolve) {
|
|
194
|
-
const pending = [version.
|
|
194
|
+
const pending = [...version.roots];
|
|
195
195
|
const built = /* @__PURE__ */ new Map();
|
|
196
196
|
while (pending.length > 0) {
|
|
197
197
|
const id = pending.pop();
|
|
@@ -204,8 +204,10 @@ function denormalize(version, resolve) {
|
|
|
204
204
|
}
|
|
205
205
|
}
|
|
206
206
|
wireSlots(version, built);
|
|
207
|
-
|
|
208
|
-
|
|
207
|
+
return version.roots.flatMap((id) => {
|
|
208
|
+
const root = built.get(id);
|
|
209
|
+
return root === void 0 ? [] : [root];
|
|
210
|
+
});
|
|
209
211
|
}
|
|
210
212
|
|
|
211
213
|
// src/fnv1a.ts
|
|
@@ -232,18 +234,43 @@ function hashArtifact(artifact) {
|
|
|
232
234
|
return fnv1a(JSON.stringify(artifact, sortKeys));
|
|
233
235
|
}
|
|
234
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
|
+
|
|
235
264
|
// src/partitionProps.ts
|
|
236
265
|
function partitionProps(validated, hints) {
|
|
237
|
-
|
|
266
|
+
let props = { ...validated };
|
|
238
267
|
const holes = {};
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
const
|
|
242
|
-
if (
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
holes[key] = data;
|
|
246
|
-
}
|
|
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;
|
|
247
274
|
}
|
|
248
275
|
return { props, holes };
|
|
249
276
|
}
|
|
@@ -352,12 +379,11 @@ function pushCycleFrame(stack, state, version, id) {
|
|
|
352
379
|
stack.push({ id, edges: slotEdges(node), next: 0 });
|
|
353
380
|
}
|
|
354
381
|
|
|
355
|
-
// src/
|
|
356
|
-
function
|
|
357
|
-
const state = /* @__PURE__ */ new Map();
|
|
382
|
+
// src/findCyclesFrom.ts
|
|
383
|
+
function findCyclesFrom(version, root, state) {
|
|
358
384
|
const stack = [];
|
|
359
385
|
const issues = [];
|
|
360
|
-
pushCycleFrame(stack, state, version,
|
|
386
|
+
pushCycleFrame(stack, state, version, root);
|
|
361
387
|
while (stack.length > 0) {
|
|
362
388
|
const frame = stack.at(-1);
|
|
363
389
|
if (frame === void 0) break;
|
|
@@ -382,17 +408,17 @@ function findCycles(version) {
|
|
|
382
408
|
return issues;
|
|
383
409
|
}
|
|
384
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
|
+
|
|
385
419
|
// src/findDanglingChildren.ts
|
|
386
420
|
function findDanglingChildren(version) {
|
|
387
421
|
const issues = [];
|
|
388
|
-
if (version.elements[version.root] === void 0) {
|
|
389
|
-
issues.push({
|
|
390
|
-
nodeId: version.root,
|
|
391
|
-
path: "root",
|
|
392
|
-
code: "dangling-child",
|
|
393
|
-
message: `root "${version.root}" has no matching element`
|
|
394
|
-
});
|
|
395
|
-
}
|
|
396
422
|
for (const node of Object.values(version.elements)) {
|
|
397
423
|
for (const edge of slotEdges(node)) {
|
|
398
424
|
if (version.elements[edge.childId] === void 0) {
|
|
@@ -408,6 +434,30 @@ function findDanglingChildren(version) {
|
|
|
408
434
|
return issues;
|
|
409
435
|
}
|
|
410
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
|
+
|
|
411
461
|
// src/disallowedChildren.ts
|
|
412
462
|
function disallowedChildren(parent, path, childIds, allow, version) {
|
|
413
463
|
if (allow === void 0) return [];
|
|
@@ -494,8 +544,8 @@ function findUnknownBlocks(version, registry) {
|
|
|
494
544
|
|
|
495
545
|
// src/reachableIds.ts
|
|
496
546
|
function reachableIds(version) {
|
|
497
|
-
const seen =
|
|
498
|
-
const queue = [version.
|
|
547
|
+
const seen = new Set(version.roots);
|
|
548
|
+
const queue = [...version.roots];
|
|
499
549
|
while (queue.length > 0) {
|
|
500
550
|
const id = queue.pop();
|
|
501
551
|
const node = id === void 0 ? void 0 : version.elements[id];
|
|
@@ -519,7 +569,7 @@ function findUnreachable(version) {
|
|
|
519
569
|
nodeId: node.id,
|
|
520
570
|
path: "",
|
|
521
571
|
code: "unreachable",
|
|
522
|
-
message: `no slot reaches "${node.id}" from
|
|
572
|
+
message: `no slot reaches "${node.id}" from any root`
|
|
523
573
|
});
|
|
524
574
|
}
|
|
525
575
|
return issues;
|
|
@@ -527,8 +577,11 @@ function findUnreachable(version) {
|
|
|
527
577
|
|
|
528
578
|
// src/validateStructure.ts
|
|
529
579
|
function validateStructure(version, registry) {
|
|
580
|
+
const rootIssues = findRootIssues(version);
|
|
581
|
+
if (version.roots.length === 0) return rootIssues;
|
|
530
582
|
return [
|
|
531
583
|
...findUnknownBlocks(version, registry),
|
|
584
|
+
...rootIssues,
|
|
532
585
|
...findDanglingChildren(version),
|
|
533
586
|
...findCycles(version),
|
|
534
587
|
...findUnreachable(version),
|
|
@@ -537,7 +590,7 @@ function validateStructure(version, registry) {
|
|
|
537
590
|
}
|
|
538
591
|
|
|
539
592
|
// src/version.constants.ts
|
|
540
|
-
var NUBBIN_VERSION = "0.1.0-rc.
|
|
593
|
+
var NUBBIN_VERSION = "0.1.0-rc.6";
|
|
541
594
|
|
|
542
595
|
// src/compile.ts
|
|
543
596
|
function compile(version, catalog, registry, route) {
|
|
@@ -605,17 +658,6 @@ function assertBlockVersion(name, version) {
|
|
|
605
658
|
}
|
|
606
659
|
}
|
|
607
660
|
|
|
608
|
-
// src/assertMigrateKeys.ts
|
|
609
|
-
var FIRST_MIGRATABLE_VERSION = 2;
|
|
610
|
-
function assertMigrateKeys(name, version, migrate) {
|
|
611
|
-
for (const key of Object.keys(migrate ?? {})) {
|
|
612
|
-
const target = Number(key);
|
|
613
|
-
if (target < FIRST_MIGRATABLE_VERSION || target > version) {
|
|
614
|
-
throw new Error(`${name}: migrate key ${key} is outside the reachable range 2..${version}`);
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
|
|
619
661
|
// src/assertSlotBounds.ts
|
|
620
662
|
function assertSlotBounds(name, slots) {
|
|
621
663
|
for (const [slot, { min, max }] of Object.entries(slots)) {
|
|
@@ -629,7 +671,6 @@ function assertSlotBounds(name, slots) {
|
|
|
629
671
|
function defineBlock(block) {
|
|
630
672
|
assertBlockVersion(block.name, block.version);
|
|
631
673
|
assertSlotBounds(block.name, block.slots);
|
|
632
|
-
assertMigrateKeys(block.name, block.version, block.migrate);
|
|
633
674
|
return block;
|
|
634
675
|
}
|
|
635
676
|
|
|
@@ -646,12 +687,23 @@ function resolveHintPaths(blockName, schema, fields) {
|
|
|
646
687
|
|
|
647
688
|
// src/assertDataHintAddressable.ts
|
|
648
689
|
function assertDataHintAddressable(blockName, fields) {
|
|
690
|
+
const seen = [];
|
|
649
691
|
for (const [path, hint] of Object.entries(fields)) {
|
|
650
|
-
if (hint.data
|
|
692
|
+
if (hint.data === void 0) continue;
|
|
693
|
+
if (path.includes("[]")) {
|
|
651
694
|
throw new Error(
|
|
652
695
|
`${blockName}: ui.fields["${path}"] sets \`data\`, but a hole cannot address an array member \u2014 "[]" has no single target`
|
|
653
696
|
);
|
|
654
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);
|
|
655
707
|
}
|
|
656
708
|
}
|
|
657
709
|
|
|
@@ -709,18 +761,161 @@ function parseMatchKind(route) {
|
|
|
709
761
|
return "exact";
|
|
710
762
|
}
|
|
711
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
|
+
|
|
712
910
|
// src/setAtPath.ts
|
|
713
911
|
function setAtPath(target, path, value) {
|
|
714
|
-
const
|
|
715
|
-
if (
|
|
716
|
-
throw new Error(`path "${path}" is not addressable`);
|
|
717
|
-
}
|
|
718
|
-
if (rest.length === 0) {
|
|
912
|
+
const { head, tail } = splitPath(path);
|
|
913
|
+
if (tail.length === 0) {
|
|
719
914
|
return { ...target, [head]: value };
|
|
720
915
|
}
|
|
721
916
|
const child = target[head];
|
|
722
917
|
const base = typeof child === "object" && child !== null ? child : {};
|
|
723
|
-
return { ...target, [head]: setAtPath(base,
|
|
918
|
+
return { ...target, [head]: setAtPath(base, tail.join("."), value) };
|
|
724
919
|
}
|
|
725
920
|
|
|
726
921
|
// src/setNodeProp.ts
|
|
@@ -742,6 +937,7 @@ export {
|
|
|
742
937
|
defineCatalog,
|
|
743
938
|
formatCompatibilityReport,
|
|
744
939
|
parseMatchKind,
|
|
940
|
+
richText,
|
|
745
941
|
setAtPath,
|
|
746
942
|
setNodeProp,
|
|
747
943
|
zodAdapter
|
package/package.json
CHANGED