@texuli/deck 0.0.0-stage → 0.5.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.
Files changed (139) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +183 -2
  3. package/dist/authoring/builder.d.ts +351 -0
  4. package/dist/authoring/builder.js +64 -0
  5. package/dist/authoring/index.d.ts +6 -0
  6. package/dist/authoring/index.js +6 -0
  7. package/dist/authoring/lint.d.ts +36 -0
  8. package/dist/authoring/lint.js +325 -0
  9. package/dist/authoring/outlines.d.ts +34 -0
  10. package/dist/authoring/outlines.js +255 -0
  11. package/dist/authoring/patch.d.ts +23 -0
  12. package/dist/authoring/patch.js +166 -0
  13. package/dist/authoring/select-layouts.d.ts +19 -0
  14. package/dist/authoring/select-layouts.js +90 -0
  15. package/dist/engine/auto-slide.d.ts +32 -0
  16. package/dist/engine/auto-slide.js +67 -0
  17. package/dist/engine/canvas.d.ts +56 -0
  18. package/dist/engine/canvas.js +48 -0
  19. package/dist/engine/controller.d.ts +126 -0
  20. package/dist/engine/controller.js +248 -0
  21. package/dist/engine/controls.d.ts +128 -0
  22. package/dist/engine/controls.js +460 -0
  23. package/dist/engine/hash.d.ts +42 -0
  24. package/dist/engine/hash.js +86 -0
  25. package/dist/engine/index.d.ts +88 -0
  26. package/dist/engine/index.js +88 -0
  27. package/dist/engine/messages.d.ts +138 -0
  28. package/dist/engine/messages.js +140 -0
  29. package/dist/engine/motion.d.ts +64 -0
  30. package/dist/engine/motion.js +112 -0
  31. package/dist/engine/navigation.d.ts +87 -0
  32. package/dist/engine/navigation.js +156 -0
  33. package/dist/engine/presenter.d.ts +58 -0
  34. package/dist/engine/presenter.js +157 -0
  35. package/dist/index.d.ts +9 -0
  36. package/dist/index.js +9 -0
  37. package/dist/markdown/attributes.d.ts +24 -0
  38. package/dist/markdown/attributes.js +94 -0
  39. package/dist/markdown/attrs-closed.d.ts +52 -0
  40. package/dist/markdown/attrs-closed.js +520 -0
  41. package/dist/markdown/blocks.d.ts +112 -0
  42. package/dist/markdown/blocks.js +766 -0
  43. package/dist/markdown/frontmatter.d.ts +30 -0
  44. package/dist/markdown/frontmatter.js +165 -0
  45. package/dist/markdown/index.d.ts +29 -0
  46. package/dist/markdown/index.js +29 -0
  47. package/dist/markdown/issues.d.ts +68 -0
  48. package/dist/markdown/issues.js +83 -0
  49. package/dist/markdown/json.d.ts +21 -0
  50. package/dist/markdown/json.js +236 -0
  51. package/dist/markdown/limits.d.ts +32 -0
  52. package/dist/markdown/limits.js +32 -0
  53. package/dist/markdown/parse.d.ts +31 -0
  54. package/dist/markdown/parse.js +283 -0
  55. package/dist/markdown/scan.d.ts +26 -0
  56. package/dist/markdown/scan.js +138 -0
  57. package/dist/markdown/slides.d.ts +32 -0
  58. package/dist/markdown/slides.js +854 -0
  59. package/dist/markdown/text.d.ts +76 -0
  60. package/dist/markdown/text.js +447 -0
  61. package/dist/markdown/twin.d.ts +41 -0
  62. package/dist/markdown/twin.js +374 -0
  63. package/dist/markdown/write.d.ts +17 -0
  64. package/dist/markdown/write.js +688 -0
  65. package/dist/math/index.d.ts +14 -0
  66. package/dist/math/index.js +14 -0
  67. package/dist/math/parse.d.ts +9 -0
  68. package/dist/math/parse.js +648 -0
  69. package/dist/math/symbols.d.ts +44 -0
  70. package/dist/math/symbols.js +333 -0
  71. package/dist/math/types.d.ts +59 -0
  72. package/dist/math/types.js +69 -0
  73. package/dist/model/backgrounds/contrast.d.ts +77 -0
  74. package/dist/model/backgrounds/contrast.js +186 -0
  75. package/dist/model/backgrounds/index.d.ts +7 -0
  76. package/dist/model/backgrounds/index.js +7 -0
  77. package/dist/model/backgrounds/resolve.d.ts +86 -0
  78. package/dist/model/backgrounds/resolve.js +215 -0
  79. package/dist/model/backgrounds/schema.d.ts +178 -0
  80. package/dist/model/backgrounds/schema.js +203 -0
  81. package/dist/model/codes.d.ts +73 -0
  82. package/dist/model/codes.js +67 -0
  83. package/dist/model/deck.d.ts +392 -0
  84. package/dist/model/deck.js +338 -0
  85. package/dist/model/index.d.ts +19 -0
  86. package/dist/model/index.js +19 -0
  87. package/dist/model/layouts/catalog.d.ts +433 -0
  88. package/dist/model/layouts/catalog.js +495 -0
  89. package/dist/model/layouts/index.d.ts +5 -0
  90. package/dist/model/layouts/index.js +5 -0
  91. package/dist/model/layouts/props.d.ts +302 -0
  92. package/dist/model/layouts/props.js +272 -0
  93. package/dist/model/loader.d.ts +37 -0
  94. package/dist/model/loader.js +317 -0
  95. package/dist/model/migrate.d.ts +34 -0
  96. package/dist/model/migrate.js +453 -0
  97. package/dist/model/motion/index.d.ts +6 -0
  98. package/dist/model/motion/index.js +6 -0
  99. package/dist/model/motion/transition.d.ts +96 -0
  100. package/dist/model/motion/transition.js +127 -0
  101. package/dist/model/parse.d.ts +57 -0
  102. package/dist/model/parse.js +307 -0
  103. package/dist/model/plain.d.ts +40 -0
  104. package/dist/model/plain.js +275 -0
  105. package/dist/model/shared.d.ts +63 -0
  106. package/dist/model/shared.js +86 -0
  107. package/dist/model/steps/index.d.ts +7 -0
  108. package/dist/model/steps/index.js +7 -0
  109. package/dist/model/steps/phase.d.ts +34 -0
  110. package/dist/model/steps/phase.js +50 -0
  111. package/dist/model/steps/slide-steps.d.ts +129 -0
  112. package/dist/model/steps/slide-steps.js +256 -0
  113. package/dist/model/style/index.d.ts +8 -0
  114. package/dist/model/style/index.js +8 -0
  115. package/dist/model/style/resolve.d.ts +87 -0
  116. package/dist/model/style/resolve.js +174 -0
  117. package/dist/model/style/schema.d.ts +85 -0
  118. package/dist/model/style/schema.js +53 -0
  119. package/dist/model/style/theme.d.ts +69 -0
  120. package/dist/model/style/theme.js +84 -0
  121. package/dist/schema/builders.d.ts +146 -0
  122. package/dist/schema/builders.js +285 -0
  123. package/dist/schema/diagnostics.d.ts +78 -0
  124. package/dist/schema/diagnostics.js +81 -0
  125. package/dist/schema/index.d.ts +15 -0
  126. package/dist/schema/index.js +14 -0
  127. package/dist/schema/internal.d.ts +40 -0
  128. package/dist/schema/internal.js +209 -0
  129. package/dist/schema/json-schema.d.ts +12 -0
  130. package/dist/schema/json-schema.js +213 -0
  131. package/dist/schema/types.d.ts +166 -0
  132. package/dist/schema/types.js +1 -0
  133. package/dist/schema/validate.d.ts +51 -0
  134. package/dist/schema/validate.js +765 -0
  135. package/dist/templates/index.d.ts +2 -0
  136. package/dist/templates/index.js +2 -0
  137. package/dist/templates/templates.d.ts +11 -0
  138. package/dist/templates/templates.js +47 -0
  139. package/package.json +60 -4
@@ -0,0 +1,53 @@
1
+ /**
2
+ * `DeckStyle` (model v2 §3.1, §3.2): every field is optional. The schema
3
+ * keeps the authored document as written (no defaults injected, so text
4
+ * twins, patches and migration stay faithful); `DECK_STYLE_DEFAULTS` holds
5
+ * the documented defaults and `resolveDeckStyle` applies them.
6
+ */
7
+ import { t } from "../../schema/index.js";
8
+ import { deckBackground } from "../backgrounds/schema.js";
9
+ import { DECK_ASPECTS, deckTransition, line, MEDIA_SRC_PATTERN } from "../shared.js";
10
+ import { deckThemeReference, styleSlug } from "./theme.js";
11
+ export const DECK_DENSITIES = ['compact', 'default', 'comfortable', 'spacious'];
12
+ export const DECK_RADII = ['none', 'small', 'medium', 'large', 'full'];
13
+ /** A ceiling: the host's reduced-motion preference only lowers it. */
14
+ export const DECK_MOTIONS = ['full', 'subtle', 'none'];
15
+ export const DECK_PROGRESS = ['none', 'top', 'bottom'];
16
+ /** `style.logo`: media syntax (an image), at most 512 code points. */
17
+ export const logoSrc = t.string({ min: 1, max: 512, pattern: MEDIA_SRC_PATTERN });
18
+ export const deckStyle = t.object({
19
+ theme: t.optional(deckThemeReference, {
20
+ description: 'Catalog id or reference; absent → the host default theme.',
21
+ }),
22
+ fontPair: t.optional(styleSlug, {
23
+ description: 'Font pairing id; unknown → warning, theme font.',
24
+ }),
25
+ aspect: t.optional(t.enum(DECK_ASPECTS), { description: "Default '16:9'." }),
26
+ density: t.optional(t.enum(DECK_DENSITIES), {
27
+ description: "Default 'default' (0.875 / 1 / 1.125 / 1.25).",
28
+ }),
29
+ radius: t.optional(t.enum(DECK_RADII), { description: "Default 'medium'." }),
30
+ motion: t.optional(t.enum(DECK_MOTIONS), { description: "Default 'full'." }),
31
+ transition: t.optional(deckTransition, { description: "Default 'fade'." }),
32
+ background: t.optional(deckBackground, { description: "Default 'solid-color'." }),
33
+ logo: t.optional(logoSrc),
34
+ footer: t.optional(line(160)),
35
+ slideNumbers: t.optional(t.boolean(), { description: 'Default true.' }),
36
+ progress: t.optional(t.enum(DECK_PROGRESS), { description: "Default 'bottom'." }),
37
+ codeTheme: t.optional(styleSlug, {
38
+ description: 'Code highlight theme id; unknown → warning.',
39
+ }),
40
+ math: t.optional(t.boolean(), { description: 'Enables `$…$` TeX math. Default false.' }),
41
+ }, { description: 'Deck style: theme, typography, canvas, motion and chrome.' });
42
+ /** The documented defaults for every field that has one. */
43
+ export const DECK_STYLE_DEFAULTS = Object.freeze({
44
+ aspect: '16:9',
45
+ density: 'default',
46
+ radius: 'medium',
47
+ motion: 'full',
48
+ transition: 'fade',
49
+ background: 'solid-color',
50
+ slideNumbers: true,
51
+ progress: 'bottom',
52
+ math: false,
53
+ });
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Theme references (model v2 §3.8). A deck names its theme and the stage
3
+ * resolves it with `@texuli/tokens`; this L0 module only defines the data:
4
+ *
5
+ * - `catalog`: a Texuli library theme id (the string shorthand means this);
6
+ * - `embedded`: a theme object, bounded JSON here, validated by the stage
7
+ * with `defineTheme` (its errors become issues there);
8
+ * - `seed`: Protoyard's `paletteSeed`, a uint32 the stage turns into a
9
+ * generated theme.
10
+ *
11
+ * `mode` comes from the reference, else the host, else `light` (so exports
12
+ * are deterministic). `system` defers to the host.
13
+ */
14
+ import { type Infer } from '../../schema/index.js';
15
+ export declare const DECK_THEME_MODES: readonly ["light", "dark", "high-contrast", "system"];
16
+ export type DeckThemeMode = (typeof DECK_THEME_MODES)[number];
17
+ /** A concrete mode (what `system` and an absent mode become). */
18
+ export type DeckResolvedThemeMode = Exclude<DeckThemeMode, 'system'>;
19
+ /** Theme, font-pair and code-theme ids: lowercase slugs, 1 to 64 characters. */
20
+ export declare const STYLE_SLUG_PATTERN = "^[a-z0-9][a-z0-9-]{0,63}$";
21
+ export declare const styleSlug: import("../../index.js").StringSchema;
22
+ /** The largest seed (`0xffffffff`). */
23
+ export declare const DECK_THEME_SEED_MAX = 4294967295;
24
+ /** Limits for an embedded theme: generous for a full `ThemeInput`, bounded for hostile input. */
25
+ export declare const EMBEDDED_THEME_LIMITS: Readonly<{
26
+ depth: 6;
27
+ maxKeys: 100;
28
+ maxItems: 64;
29
+ maxString: 512;
30
+ }>;
31
+ export declare const THEME_CODES: Readonly<{
32
+ readonly unknownThemeKind: "unknown-theme-kind";
33
+ readonly embeddedTheme: "embedded-theme";
34
+ readonly unknownTheme: "unknown-theme";
35
+ }>;
36
+ /** The object form of a theme reference, told apart by `kind`. */
37
+ export declare const deckThemeObject: import("../../index.js").UnionSchema<readonly [import("../../index.js").ObjectSchema<{
38
+ readonly kind: import("../../index.js").LiteralSchema<"catalog">;
39
+ readonly id: import("../../index.js").StringSchema;
40
+ readonly mode: import("../../index.js").OptionalSchema<import("../../index.js").EnumSchema<"dark" | "light" | "high-contrast" | "system">, false>;
41
+ }>, import("../../index.js").ObjectSchema<{
42
+ readonly kind: import("../../index.js").LiteralSchema<"embedded">;
43
+ readonly theme: import("../../index.js").RefineSchema<import("../../index.js").JsonValueSchema>;
44
+ readonly mode: import("../../index.js").OptionalSchema<import("../../index.js").EnumSchema<"dark" | "light" | "high-contrast" | "system">, false>;
45
+ }>, import("../../index.js").ObjectSchema<{
46
+ readonly kind: import("../../index.js").LiteralSchema<"seed">;
47
+ readonly seed: import("../../index.js").NumberSchema;
48
+ readonly mode: import("../../index.js").OptionalSchema<import("../../index.js").EnumSchema<"dark" | "light" | "high-contrast" | "system">, false>;
49
+ }>]>;
50
+ export type DeckThemeReference = Infer<typeof deckThemeObject>;
51
+ /** `style.theme` and `slides[].theme`: a catalog id shorthand or a reference object. */
52
+ export declare const deckThemeReference: import("../../index.js").UnionSchema<readonly [import("../../index.js").StringSchema, import("../../index.js").UnionSchema<readonly [import("../../index.js").ObjectSchema<{
53
+ readonly kind: import("../../index.js").LiteralSchema<"catalog">;
54
+ readonly id: import("../../index.js").StringSchema;
55
+ readonly mode: import("../../index.js").OptionalSchema<import("../../index.js").EnumSchema<"dark" | "light" | "high-contrast" | "system">, false>;
56
+ }>, import("../../index.js").ObjectSchema<{
57
+ readonly kind: import("../../index.js").LiteralSchema<"embedded">;
58
+ readonly theme: import("../../index.js").RefineSchema<import("../../index.js").JsonValueSchema>;
59
+ readonly mode: import("../../index.js").OptionalSchema<import("../../index.js").EnumSchema<"dark" | "light" | "high-contrast" | "system">, false>;
60
+ }>, import("../../index.js").ObjectSchema<{
61
+ readonly kind: import("../../index.js").LiteralSchema<"seed">;
62
+ readonly seed: import("../../index.js").NumberSchema;
63
+ readonly mode: import("../../index.js").OptionalSchema<import("../../index.js").EnumSchema<"dark" | "light" | "high-contrast" | "system">, false>;
64
+ }>]>]>;
65
+ export type DeckThemeInput = Infer<typeof deckThemeReference>;
66
+ /** The object form of a validated reference (a string becomes `{ kind: 'catalog', id }`). */
67
+ export declare function normalizeThemeReference(reference: DeckThemeInput | undefined): DeckThemeReference | undefined;
68
+ /** The mode a deck renders in: the reference's, else the host's, else `light`. */
69
+ export declare function resolveThemeMode(reference: DeckThemeInput | undefined, hostMode?: DeckResolvedThemeMode): DeckResolvedThemeMode;
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Theme references (model v2 §3.8). A deck names its theme and the stage
3
+ * resolves it with `@texuli/tokens`; this L0 module only defines the data:
4
+ *
5
+ * - `catalog`: a Texuli library theme id (the string shorthand means this);
6
+ * - `embedded`: a theme object, bounded JSON here, validated by the stage
7
+ * with `defineTheme` (its errors become issues there);
8
+ * - `seed`: Protoyard's `paletteSeed`, a uint32 the stage turns into a
9
+ * generated theme.
10
+ *
11
+ * `mode` comes from the reference, else the host, else `light` (so exports
12
+ * are deterministic). `system` defers to the host.
13
+ */
14
+ import { t } from "../../schema/index.js";
15
+ export const DECK_THEME_MODES = ['light', 'dark', 'high-contrast', 'system'];
16
+ /** Theme, font-pair and code-theme ids: lowercase slugs, 1 to 64 characters. */
17
+ export const STYLE_SLUG_PATTERN = '^[a-z0-9][a-z0-9-]{0,63}$';
18
+ export const styleSlug = t.string({ pattern: STYLE_SLUG_PATTERN, max: 64 });
19
+ /** The largest seed (`0xffffffff`). */
20
+ export const DECK_THEME_SEED_MAX = 0xffff_ffff;
21
+ /** Limits for an embedded theme: generous for a full `ThemeInput`, bounded for hostile input. */
22
+ export const EMBEDDED_THEME_LIMITS = Object.freeze({
23
+ depth: 6,
24
+ maxKeys: 100,
25
+ maxItems: 64,
26
+ maxString: 512,
27
+ });
28
+ export const THEME_CODES = Object.freeze({
29
+ unknownThemeKind: 'unknown-theme-kind',
30
+ embeddedTheme: 'embedded-theme',
31
+ unknownTheme: 'unknown-theme',
32
+ });
33
+ const mode = t.optional(t.enum(DECK_THEME_MODES));
34
+ const catalogTheme = t.object({
35
+ kind: t.literal('catalog'),
36
+ id: styleSlug,
37
+ mode,
38
+ });
39
+ const embeddedTheme = t.object({
40
+ kind: t.literal('embedded'),
41
+ theme: t.refine(t.json({ ...EMBEDDED_THEME_LIMITS, text: 'line' }), {
42
+ code: THEME_CODES.embeddedTheme,
43
+ description: 'An embedded theme is a JSON object (validated by the stage with defineTheme).',
44
+ check(value, context) {
45
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) {
46
+ context.issue({
47
+ message: 'An embedded theme must be an object.',
48
+ fix: 'Pass a theme object, or use { kind: "catalog", id } for a library theme.',
49
+ });
50
+ }
51
+ },
52
+ }),
53
+ mode,
54
+ });
55
+ const seedTheme = t.object({
56
+ kind: t.literal('seed'),
57
+ seed: t.int({ min: 0, max: DECK_THEME_SEED_MAX }),
58
+ mode,
59
+ });
60
+ /** The object form of a theme reference, told apart by `kind`. */
61
+ export const deckThemeObject = t.union([catalogTheme, embeddedTheme, seedTheme], {
62
+ discriminator: 'kind',
63
+ code: THEME_CODES.unknownThemeKind,
64
+ description: 'A theme reference: catalog id, embedded theme or palette seed.',
65
+ });
66
+ /** `style.theme` and `slides[].theme`: a catalog id shorthand or a reference object. */
67
+ export const deckThemeReference = t.union([styleSlug, deckThemeObject], {
68
+ description: 'A theme: a catalog id (shorthand), or a reference object.',
69
+ });
70
+ /** The object form of a validated reference (a string becomes `{ kind: 'catalog', id }`). */
71
+ export function normalizeThemeReference(reference) {
72
+ if (reference === undefined)
73
+ return undefined;
74
+ if (typeof reference === 'string')
75
+ return { kind: 'catalog', id: reference };
76
+ return { ...reference };
77
+ }
78
+ /** The mode a deck renders in: the reference's, else the host's, else `light`. */
79
+ export function resolveThemeMode(reference, hostMode) {
80
+ const own = typeof reference === 'object' ? reference.mode : undefined;
81
+ if (own !== undefined && own !== 'system')
82
+ return own;
83
+ return hostMode ?? 'light';
84
+ }
@@ -0,0 +1,146 @@
1
+ /**
2
+ * The combinators. Each returns a frozen plain object describing a shape;
3
+ * `validate` and `toJsonSchema` interpret it, and `Infer` reads its type.
4
+ * Builders throw `TypeError` on a malformed schema (a developer error, at
5
+ * module load); validation itself never throws.
6
+ */
7
+ import { UNSAFE_KEYS } from './internal.js';
8
+ import type { ArraySchema, BooleanSchema, EnumSchema, Infer, JsonValueSchema, LazySchema, LiteralSchema, LiteralValue, NullableSchema, NullSchema, NumberSchema, ObjectSchema, OptionalSchema, RecordSchema, RefineContext, RefineSchema, Schema, Shape, StringSchema, TextMode, UnionSchema } from './types.js';
9
+ export interface Described {
10
+ description?: string;
11
+ }
12
+ export interface StringOptions extends Described {
13
+ /** Code points, counted after trimming. */
14
+ min?: number;
15
+ /** Code points, counted after trimming. */
16
+ max?: number;
17
+ pattern?: RegExp | string;
18
+ not?: readonly string[];
19
+ trim?: boolean;
20
+ text?: TextMode;
21
+ }
22
+ declare function string(options?: StringOptions): StringSchema;
23
+ export interface NumberOptions extends Described {
24
+ min?: number;
25
+ max?: number;
26
+ /** Refuse ±Infinity (default true). */
27
+ finite?: boolean;
28
+ }
29
+ declare function number(options?: NumberOptions): NumberSchema;
30
+ declare function int(options?: NumberOptions): NumberSchema;
31
+ declare function boolean(options?: Described): BooleanSchema;
32
+ declare function nullValue(options?: Described): NullSchema;
33
+ export interface CodeOptions extends Described {
34
+ /** The code a mismatch reports instead of `enum`. */
35
+ code?: string;
36
+ }
37
+ declare function literal<const V extends LiteralValue>(value: V, options?: CodeOptions): LiteralSchema<V>;
38
+ declare function enumOf<const V extends readonly [string, ...string[]]>(values: V, options?: CodeOptions): EnumSchema<V[number]>;
39
+ export interface SizeOptions extends Described {
40
+ min?: number;
41
+ max?: number;
42
+ }
43
+ declare function array<I extends Schema>(item: I, options?: SizeOptions): ArraySchema<I>;
44
+ export interface ObjectOptions extends Described {
45
+ /** `true` (default) reports unknown keys; `false` drops them. */
46
+ strict?: boolean;
47
+ }
48
+ declare function object<const S extends Shape>(shape: S, options?: ObjectOptions): ObjectSchema<S>;
49
+ export interface RecordOptions extends SizeOptions {
50
+ /** Validates every key; `__proto__`, `constructor` and `prototype` are always refused. */
51
+ key?: StringSchema;
52
+ }
53
+ declare function record<V extends Schema>(value: V, options?: RecordOptions): RecordSchema<V>;
54
+ export interface UnionOptions extends Described {
55
+ /** Pick the option by this key's value (one issue when it is unknown). */
56
+ discriminator?: string;
57
+ /** The code an unknown discriminator reports instead of `unknown-kind`. */
58
+ code?: string;
59
+ }
60
+ declare function union<const O extends readonly [Schema, ...Schema[]]>(options: O, settings?: UnionOptions): UnionSchema<O>;
61
+ declare function nullable<I extends Schema>(inner: I, options?: Described): NullableSchema<I>;
62
+ export interface OptionalOptions<T> extends Described {
63
+ /** Applied as a fresh copy when the key is missing; must be valid for the inner schema. */
64
+ default?: T;
65
+ }
66
+ declare function optional<I extends Schema>(inner: I, options: Described & {
67
+ default: Infer<I>;
68
+ }): OptionalSchema<I, true>;
69
+ declare function optional<I extends Schema>(inner: I, options?: Described): OptionalSchema<I, false>;
70
+ export interface LazyOptions extends Described {
71
+ /** The `$defs` name in the JSON Schema export. */
72
+ name?: string;
73
+ /** How many times this schema may nest along one path. */
74
+ maxDepth?: number;
75
+ }
76
+ /**
77
+ * A recursive schema. TypeScript cannot infer a recursive type from a value,
78
+ * so annotate it: `const Node: Schema<TreeNode> = t.object({ kids: t.array(t.lazy(() => Node)) })`.
79
+ */
80
+ declare function lazy<T>(get: () => Schema<T>, options?: LazyOptions): LazySchema<T>;
81
+ export interface RefineOptions<T> extends Described {
82
+ /** The default code of the issues `check` raises (stable kebab-case). */
83
+ code: string;
84
+ /** Raise issues through `context.issue`; runs only when the inner schema passed. */
85
+ check(value: T, context: RefineContext): void;
86
+ }
87
+ /**
88
+ * A named custom check. The `description` documents the rule and is copied
89
+ * into the JSON Schema `description`, since JSON Schema cannot express it.
90
+ */
91
+ declare function refine<I extends Schema>(inner: I, options: RefineOptions<Infer<I>>): RefineSchema<I>;
92
+ export interface JsonOptions extends Described {
93
+ /** Nested containers allowed (default 4). */
94
+ depth?: number;
95
+ /** Keys per object (default 100). */
96
+ maxKeys?: number;
97
+ /** Items per array (default 200). */
98
+ maxItems?: number;
99
+ /** Code points per string (default 2,000). */
100
+ maxString?: number;
101
+ /** Code points per key (default 64). */
102
+ maxKeyLength?: number;
103
+ text?: TextMode;
104
+ }
105
+ /** Protoyard's props limits (`DECK_LIMITS`): depth 4, 100 keys, 200 items, 2,000-character strings. */
106
+ export declare const JSON_DEFAULTS: Readonly<{
107
+ depth: 4;
108
+ maxKeys: 100;
109
+ maxItems: 200;
110
+ maxString: 2000;
111
+ maxKeyLength: 64;
112
+ }>;
113
+ /** Bounded JSON data: plain objects, arrays, finite numbers, strings, booleans, null. */
114
+ declare function json(options?: JsonOptions): JsonValueSchema;
115
+ /** A copy of `schema` with a description (shown in the JSON Schema). */
116
+ declare function describe<S extends Schema>(schema: S, description: string): S;
117
+ /** An object schema with more (or replaced) fields; keeps strictness unless overridden. */
118
+ declare function extend<S extends Shape, E extends Shape>(base: ObjectSchema<S>, shape: E, options?: ObjectOptions): ObjectSchema<Omit<S, keyof E> & E>;
119
+ declare function pick<S extends Shape, const K extends keyof S & string>(base: ObjectSchema<S>, keys: readonly K[]): ObjectSchema<Pick<S, K>>;
120
+ declare function omit<S extends Shape, const K extends keyof S & string>(base: ObjectSchema<S>, keys: readonly K[]): ObjectSchema<Omit<S, K>>;
121
+ /** The field schema of a discriminated union option, after unwrapping refinements. */
122
+ export declare function objectOf(schema: Schema): ObjectSchema | undefined;
123
+ /** The combinators, as one frozen object: `t.object({ title: t.string({ max: 200 }) })`. */
124
+ export declare const t: Readonly<{
125
+ string: typeof string;
126
+ number: typeof number;
127
+ int: typeof int;
128
+ boolean: typeof boolean;
129
+ null: typeof nullValue;
130
+ literal: typeof literal;
131
+ enum: typeof enumOf;
132
+ array: typeof array;
133
+ object: typeof object;
134
+ record: typeof record;
135
+ union: typeof union;
136
+ nullable: typeof nullable;
137
+ optional: typeof optional;
138
+ lazy: typeof lazy;
139
+ refine: typeof refine;
140
+ json: typeof json;
141
+ describe: typeof describe;
142
+ extend: typeof extend;
143
+ pick: typeof pick;
144
+ omit: typeof omit;
145
+ }>;
146
+ export { UNSAFE_KEYS };
@@ -0,0 +1,285 @@
1
+ /**
2
+ * The combinators. Each returns a frozen plain object describing a shape;
3
+ * `validate` and `toJsonSchema` interpret it, and `Infer` reads its type.
4
+ * Builders throw `TypeError` on a malformed schema (a developer error, at
5
+ * module load); validation itself never throws.
6
+ */
7
+ import { compilePattern, discriminatorIndex, isUnsafeKey, UNSAFE_KEYS, unwrapObject, } from "./internal.js";
8
+ import { validate } from "./validate.js";
9
+ function freeze(node) {
10
+ const out = {};
11
+ for (const [key, value] of Object.entries(node))
12
+ if (value !== undefined)
13
+ out[key] = value;
14
+ return Object.freeze(out);
15
+ }
16
+ function count(name, value) {
17
+ if (value !== undefined && !(Number.isInteger(value) && value >= 0)) {
18
+ throw new TypeError(`${name} must be a whole number, got ${value}.`);
19
+ }
20
+ }
21
+ function bounds(min, max) {
22
+ count('min', min);
23
+ count('max', max);
24
+ if (min !== undefined && max !== undefined && min > max) {
25
+ throw new TypeError(`min (${min}) is greater than max (${max}).`);
26
+ }
27
+ }
28
+ function string(options = {}) {
29
+ bounds(options.min, options.max);
30
+ let pattern;
31
+ if (options.pattern !== undefined) {
32
+ if (typeof options.pattern === 'string')
33
+ pattern = options.pattern;
34
+ else {
35
+ if (/[^uv]/.test(options.pattern.flags)) {
36
+ throw new TypeError('Patterns take no flags (they compile with "u" and must port to JSON Schema).');
37
+ }
38
+ pattern = options.pattern.source;
39
+ }
40
+ compilePattern(pattern);
41
+ }
42
+ return freeze({
43
+ kind: 'string',
44
+ min: options.min,
45
+ max: options.max,
46
+ pattern,
47
+ not: options.not ? Object.freeze([...options.not]) : undefined,
48
+ trim: options.trim || undefined,
49
+ text: options.text,
50
+ description: options.description,
51
+ });
52
+ }
53
+ function numeric(int, options) {
54
+ if (options.min !== undefined && options.max !== undefined && options.min > options.max) {
55
+ throw new TypeError(`min (${options.min}) is greater than max (${options.max}).`);
56
+ }
57
+ return freeze({
58
+ kind: 'number',
59
+ int,
60
+ min: options.min,
61
+ max: options.max,
62
+ finite: options.finite ?? true,
63
+ description: options.description,
64
+ });
65
+ }
66
+ function number(options = {}) {
67
+ return numeric(false, options);
68
+ }
69
+ function int(options = {}) {
70
+ return numeric(true, options);
71
+ }
72
+ function boolean(options = {}) {
73
+ return freeze({ kind: 'boolean', description: options.description });
74
+ }
75
+ function nullValue(options = {}) {
76
+ return freeze({ kind: 'null', description: options.description });
77
+ }
78
+ function literal(value, options = {}) {
79
+ if (typeof value === 'number' && !Number.isFinite(value)) {
80
+ throw new TypeError('A literal number must be finite.');
81
+ }
82
+ return freeze({
83
+ kind: 'literal',
84
+ value,
85
+ code: options.code,
86
+ description: options.description,
87
+ });
88
+ }
89
+ function enumOf(values, options = {}) {
90
+ if (values.length === 0)
91
+ throw new TypeError('An enum needs at least one value.');
92
+ if (new Set(values).size !== values.length)
93
+ throw new TypeError('Enum values must be unique.');
94
+ return freeze({
95
+ kind: 'enum',
96
+ values: Object.freeze([...values]),
97
+ code: options.code,
98
+ description: options.description,
99
+ });
100
+ }
101
+ function array(item, options = {}) {
102
+ bounds(options.min, options.max);
103
+ return freeze({
104
+ kind: 'array',
105
+ item,
106
+ min: options.min,
107
+ max: options.max,
108
+ description: options.description,
109
+ });
110
+ }
111
+ function object(shape, options = {}) {
112
+ const copy = {};
113
+ for (const key of Object.keys(shape)) {
114
+ if (isUnsafeKey(key))
115
+ throw new TypeError(`The key "${key}" cannot be declared.`);
116
+ copy[key] = shape[key];
117
+ }
118
+ return freeze({
119
+ kind: 'object',
120
+ shape: Object.freeze(copy),
121
+ strict: options.strict ?? true,
122
+ description: options.description,
123
+ });
124
+ }
125
+ function record(value, options = {}) {
126
+ bounds(options.min, options.max);
127
+ return freeze({
128
+ kind: 'record',
129
+ key: options.key,
130
+ value,
131
+ min: options.min,
132
+ max: options.max,
133
+ description: options.description,
134
+ });
135
+ }
136
+ function union(options, settings = {}) {
137
+ if (options.length === 0)
138
+ throw new TypeError('A union needs at least one option.');
139
+ const node = freeze({
140
+ kind: 'union',
141
+ options: Object.freeze([...options]),
142
+ discriminator: settings.discriminator,
143
+ code: settings.code,
144
+ description: settings.description,
145
+ });
146
+ if (node.discriminator !== undefined)
147
+ discriminatorIndex(node); // throws when malformed
148
+ return node;
149
+ }
150
+ function nullable(inner, options = {}) {
151
+ return freeze({ kind: 'nullable', inner, description: options.description });
152
+ }
153
+ function optional(inner, options = {}) {
154
+ const hasDefault = Object.hasOwn(options, 'default');
155
+ if (hasDefault) {
156
+ const check = validate(inner, options.default);
157
+ if (!check.ok) {
158
+ throw new TypeError(`The default is not valid: ${check.issues[0]?.message ?? 'invalid'}`);
159
+ }
160
+ }
161
+ return freeze({
162
+ kind: 'optional',
163
+ inner,
164
+ hasDefault,
165
+ default: hasDefault ? deepFreeze(options.default) : undefined,
166
+ description: options.description,
167
+ });
168
+ }
169
+ function deepFreeze(value) {
170
+ if (value !== null && typeof value === 'object' && !Object.isFrozen(value)) {
171
+ for (const child of Object.values(value))
172
+ deepFreeze(child);
173
+ Object.freeze(value);
174
+ }
175
+ return value;
176
+ }
177
+ /**
178
+ * A recursive schema. TypeScript cannot infer a recursive type from a value,
179
+ * so annotate it: `const Node: Schema<TreeNode> = t.object({ kids: t.array(t.lazy(() => Node)) })`.
180
+ */
181
+ function lazy(get, options = {}) {
182
+ count('maxDepth', options.maxDepth);
183
+ if (options.name !== undefined && !/^[A-Za-z][A-Za-z0-9_-]*$/.test(options.name)) {
184
+ throw new TypeError(`Invalid lazy schema name "${options.name}".`);
185
+ }
186
+ return freeze({
187
+ kind: 'lazy',
188
+ get,
189
+ name: options.name,
190
+ maxDepth: options.maxDepth,
191
+ description: options.description,
192
+ });
193
+ }
194
+ /**
195
+ * A named custom check. The `description` documents the rule and is copied
196
+ * into the JSON Schema `description`, since JSON Schema cannot express it.
197
+ */
198
+ function refine(inner, options) {
199
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(options.code)) {
200
+ throw new TypeError(`Refinement codes are kebab-case, got "${options.code}".`);
201
+ }
202
+ return freeze({
203
+ kind: 'refine',
204
+ inner,
205
+ code: options.code,
206
+ check: options.check,
207
+ description: options.description,
208
+ });
209
+ }
210
+ /** Protoyard's props limits (`DECK_LIMITS`): depth 4, 100 keys, 200 items, 2,000-character strings. */
211
+ export const JSON_DEFAULTS = Object.freeze({
212
+ depth: 4,
213
+ maxKeys: 100,
214
+ maxItems: 200,
215
+ maxString: 2_000,
216
+ maxKeyLength: 64,
217
+ });
218
+ /** Bounded JSON data: plain objects, arrays, finite numbers, strings, booleans, null. */
219
+ function json(options = {}) {
220
+ const node = {
221
+ kind: 'json',
222
+ depth: options.depth ?? JSON_DEFAULTS.depth,
223
+ maxKeys: options.maxKeys ?? JSON_DEFAULTS.maxKeys,
224
+ maxItems: options.maxItems ?? JSON_DEFAULTS.maxItems,
225
+ maxString: options.maxString ?? JSON_DEFAULTS.maxString,
226
+ maxKeyLength: options.maxKeyLength ?? JSON_DEFAULTS.maxKeyLength,
227
+ text: options.text,
228
+ description: options.description,
229
+ };
230
+ for (const key of ['depth', 'maxKeys', 'maxItems', 'maxString', 'maxKeyLength']) {
231
+ count(key, node[key]);
232
+ }
233
+ return freeze(node);
234
+ }
235
+ /** A copy of `schema` with a description (shown in the JSON Schema). */
236
+ function describe(schema, description) {
237
+ return freeze({ ...schema, description });
238
+ }
239
+ /** An object schema with more (or replaced) fields; keeps strictness unless overridden. */
240
+ function extend(base, shape, options = {}) {
241
+ return object({ ...base.shape, ...shape }, {
242
+ strict: options.strict ?? base.strict,
243
+ description: options.description ?? base.description,
244
+ });
245
+ }
246
+ function pick(base, keys) {
247
+ const shape = {};
248
+ for (const key of keys)
249
+ shape[key] = base.shape[key];
250
+ return object(shape, { strict: base.strict });
251
+ }
252
+ function omit(base, keys) {
253
+ const shape = { ...base.shape };
254
+ for (const key of keys)
255
+ delete shape[key];
256
+ return object(shape, { strict: base.strict });
257
+ }
258
+ /** The field schema of a discriminated union option, after unwrapping refinements. */
259
+ export function objectOf(schema) {
260
+ return unwrapObject(schema);
261
+ }
262
+ /** The combinators, as one frozen object: `t.object({ title: t.string({ max: 200 }) })`. */
263
+ export const t = Object.freeze({
264
+ string,
265
+ number,
266
+ int,
267
+ boolean,
268
+ null: nullValue,
269
+ literal,
270
+ enum: enumOf,
271
+ array,
272
+ object,
273
+ record,
274
+ union,
275
+ nullable,
276
+ optional,
277
+ lazy,
278
+ refine,
279
+ json,
280
+ describe,
281
+ extend,
282
+ pick,
283
+ omit,
284
+ });
285
+ export { UNSAFE_KEYS };