@thomasfosterau/prosekit-utilities 0.0.0-stage → 0.1.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.
@@ -0,0 +1,76 @@
1
+ import * as Schema from "effect/Schema";
2
+ import { AnyPgColumn } from "drizzle-orm/pg-core";
3
+ import { SQL } from "drizzle-orm";
4
+ /**
5
+ * Builds a typed JSONB Drizzle custom column backed by an Effect Schema
6
+ * codec.
7
+ *
8
+ * Values are stored as PostgreSQL `jsonb`. The schema validates on read
9
+ * (`fromDriver`) only — `toDriver` runs `Schema.encodeSync` over the raw
10
+ * value's `Schema.fromJsonString(Schema.Unknown)` wire form, exactly as the
11
+ * apps' `inlineDocument` / `blockDocument` columns do, and does **not** run
12
+ * `schema` itself on the way out. That is deliberate, not an oversight: the
13
+ * write path is validated upstream, at the API edge and by whatever schema
14
+ * governs the value before it reaches this column, and the read path is
15
+ * where a jsonb row earns the strict, derived schema back. Read validation
16
+ * is what closes SEC-XSS-001 on both directions in practice — a hostile row
17
+ * written by any means other than the validated path is refused on the way
18
+ * back out.
19
+ *
20
+ * The runtime driver is expected to be node-pg (`pg`), which already parses
21
+ * `jsonb` columns for you, so `fromDriver`'s `!Predicate.isString(value)`
22
+ * branch is the common case and the `Predicate.isString` branch (parse a
23
+ * JSON string driver value, then validate) exists for a driver that instead
24
+ * hands back raw text.
25
+ *
26
+ * `null` passes through untouched — Drizzle applies the schema only to
27
+ * non-null values, so a nullable column needs no `.nullable()` wrapping here.
28
+ */
29
+ export declare const jsonbSchemaColumn: <A>(schema: Schema.Codec<A, A>) => (name: string) => import("drizzle-orm/pg-core").PgCustomColumnBuilder<{
30
+ dataType: "custom";
31
+ data: A;
32
+ driverParam: string;
33
+ }>;
34
+ /**
35
+ * The one piece of `DocumentSchema` (from
36
+ * `@thomasfosterau/prosekit-utilities/prosekit`) this module needs. The
37
+ * dependency is written structurally, not as an import of that module,
38
+ * because the `./prosekit` family is authored in parallel and the `./drizzle`
39
+ * entry must not import ProseKit at the value or type level (see the root
40
+ * `AGENTS.md`'s "keep ProseKit off the root entry" rule — the same reasoning
41
+ * applies one level over: this entry stays free of the `./prosekit` module
42
+ * graph too, so a consumer can depend on `./drizzle` without pulling in
43
+ * ProseKit transitively).
44
+ */
45
+ interface HasDocumentSchema<A> {
46
+ readonly Document: Schema.Codec<A, A>;
47
+ }
48
+ /**
49
+ * Builds a `jsonb` document column from a `DocumentSchema` (the value
50
+ * `defineDocumentSchema` returns from `@thomasfosterau/prosekit-utilities/prosekit`).
51
+ *
52
+ * `jsonbSchemaColumn(schema.Document)` — the strict, ProseKit-derived schema,
53
+ * so a row is held to the full document grammar on read (see
54
+ * {@link jsonbSchemaColumn}'s doc comment for why the write path is not
55
+ * re-validated here).
56
+ */
57
+ export declare const documentColumn: <A>(schema: HasDocumentSchema<A>) => (name: string) => import("drizzle-orm/pg-core").PgCustomColumnBuilder<{
58
+ dataType: "custom";
59
+ data: A;
60
+ driverParam: string;
61
+ }>;
62
+ /**
63
+ * A SQL fragment yielding a jsonb document's `text` leaves joined by single
64
+ * spaces — the database twin of `extractDocumentText` (the root entry), for a
65
+ * generated or materialised search column.
66
+ *
67
+ * `jsonb_path_query_array(column, 'strict $.** ? (@.type == "text").text')`
68
+ * collects every `text` leaf's `.text` value, in document order, as a jsonb
69
+ * array; `jsonb_array_elements_text` unnests it to `text` rows;
70
+ * `array_to_string(array(...), ' ')` joins them with the same `' '`
71
+ * separator `extractDocumentText`'s default uses — so a row's generated or
72
+ * materialised text column agrees with what the JS function would compute
73
+ * over the same document.
74
+ */
75
+ export declare const documentTextSql: (column: AnyPgColumn | SQL) => SQL<string>;
76
+ export type { HasDocumentSchema };
@@ -0,0 +1,95 @@
1
+ import * as Option from "effect/Option";
2
+ import * as Predicate from "effect/Predicate";
3
+ import * as Schema from "effect/Schema";
4
+ import { customType } from "drizzle-orm/pg-core";
5
+ import { sql } from "drizzle-orm";
6
+ // src/drizzle/jsonb-column.ts
7
+ /**
8
+ * A typed `jsonb` Drizzle custom column backed by an Effect Schema.
9
+ *
10
+ * Generalises the apps' `customType` body (Gardeneria's
11
+ * `packages/database/src/columns.ts`, byte-identical to Website's) over an
12
+ * arbitrary codec `A`, so a consumer supplies its own document (or any other
13
+ * JSON-shaped) schema instead of the library naming one.
14
+ *
15
+ * @module
16
+ */
17
+ /**
18
+ * The JSON text boundary, expressed as a schema rather than the `JSON`
19
+ * global: encoding runs `Schema.fromJsonString(Schema.Unknown)` forward
20
+ * (`JSON.stringify`) and decoding runs it as a total operation whose `None`
21
+ * replaces the `catch` a hand-rolled `JSON.parse` would need.
22
+ */
23
+ const JsonText = /* @__PURE__ */ Schema.fromJsonString(Schema.Unknown);
24
+ const encodeJson = /* @__PURE__ */ Schema.encodeSync(JsonText);
25
+ const decodeJson = /* @__PURE__ */ Schema.decodeUnknownOption(JsonText);
26
+ /**
27
+ * Builds a typed JSONB Drizzle custom column backed by an Effect Schema
28
+ * codec.
29
+ *
30
+ * Values are stored as PostgreSQL `jsonb`. The schema validates on read
31
+ * (`fromDriver`) only — `toDriver` runs `Schema.encodeSync` over the raw
32
+ * value's `Schema.fromJsonString(Schema.Unknown)` wire form, exactly as the
33
+ * apps' `inlineDocument` / `blockDocument` columns do, and does **not** run
34
+ * `schema` itself on the way out. That is deliberate, not an oversight: the
35
+ * write path is validated upstream, at the API edge and by whatever schema
36
+ * governs the value before it reaches this column, and the read path is
37
+ * where a jsonb row earns the strict, derived schema back. Read validation
38
+ * is what closes SEC-XSS-001 on both directions in practice — a hostile row
39
+ * written by any means other than the validated path is refused on the way
40
+ * back out.
41
+ *
42
+ * The runtime driver is expected to be node-pg (`pg`), which already parses
43
+ * `jsonb` columns for you, so `fromDriver`'s `!Predicate.isString(value)`
44
+ * branch is the common case and the `Predicate.isString` branch (parse a
45
+ * JSON string driver value, then validate) exists for a driver that instead
46
+ * hands back raw text.
47
+ *
48
+ * `null` passes through untouched — Drizzle applies the schema only to
49
+ * non-null values, so a nullable column needs no `.nullable()` wrapping here.
50
+ */
51
+ const jsonbSchemaColumn = (schema) => (name) => {
52
+ const parse = Schema.decodeUnknownSync(schema);
53
+ return customType({
54
+ dataType() {
55
+ return "jsonb";
56
+ },
57
+ toDriver(value) {
58
+ return encodeJson(value);
59
+ },
60
+ fromDriver(value) {
61
+ if (!Predicate.isString(value)) return parse(value);
62
+ return Option.match(decodeJson(value), {
63
+ onNone: () => parse(value),
64
+ onSome: (parsed) => parse(parsed)
65
+ });
66
+ }
67
+ })(name);
68
+ };
69
+ // src/drizzle/document-column.ts
70
+ /**
71
+ * Builds a `jsonb` document column from a `DocumentSchema` (the value
72
+ * `defineDocumentSchema` returns from `@thomasfosterau/prosekit-utilities/prosekit`).
73
+ *
74
+ * `jsonbSchemaColumn(schema.Document)` — the strict, ProseKit-derived schema,
75
+ * so a row is held to the full document grammar on read (see
76
+ * {@link jsonbSchemaColumn}'s doc comment for why the write path is not
77
+ * re-validated here).
78
+ */
79
+ const documentColumn = (schema) => jsonbSchemaColumn(schema.Document);
80
+ // src/drizzle/document-text-sql.ts
81
+ /**
82
+ * A SQL fragment yielding a jsonb document's `text` leaves joined by single
83
+ * spaces — the database twin of `extractDocumentText` (the root entry), for a
84
+ * generated or materialised search column.
85
+ *
86
+ * `jsonb_path_query_array(column, 'strict $.** ? (@.type == "text").text')`
87
+ * collects every `text` leaf's `.text` value, in document order, as a jsonb
88
+ * array; `jsonb_array_elements_text` unnests it to `text` rows;
89
+ * `array_to_string(array(...), ' ')` joins them with the same `' '`
90
+ * separator `extractDocumentText`'s default uses — so a row's generated or
91
+ * materialised text column agrees with what the JS function would compute
92
+ * over the same document.
93
+ */
94
+ const documentTextSql = (column) => sql`array_to_string(array(SELECT jsonb_array_elements_text(jsonb_path_query_array(${column}, 'strict $.** ? (@.type == "text").text'))), ' ')`;
95
+ export { documentColumn, documentTextSql, jsonbSchemaColumn };
@@ -0,0 +1,87 @@
1
+ import * as Predicate from "effect/Predicate";
2
+ import * as Result from "effect/Result";
3
+ import * as String from "effect/String";
4
+ // src/url-safety.ts
5
+ /**
6
+ * The safe-URL allow-list shared by every serialiser in this package.
7
+ *
8
+ * Link `href`s and image `src`s are passed through this allow-list before
9
+ * being embedded, so a serialiser never emits a `javascript:` / `data:` /
10
+ * `vbscript:` URI. Single-sourced here so the Markdown serialiser, the HTML
11
+ * serialiser (the ProseKit entry point) and the allow-list write-seam check can never
12
+ * drift apart — the SEC-XSS-001 defence-in-depth both apps this package was
13
+ * extracted from rely on.
14
+ *
15
+ * @module
16
+ */
17
+ /**
18
+ * The schemes {@link isSafeHref} accepts when the caller names none.
19
+ *
20
+ * Deliberately short: these are the three a rich-text document has a reason
21
+ * to link to. An application that needs `tel:` or a custom scheme passes it
22
+ * in rather than widening the default for everyone.
23
+ */
24
+ const DEFAULT_SAFE_SCHEMES = [
25
+ "http",
26
+ "https",
27
+ "mailto"
28
+ ];
29
+ /** Normalises `https:` / `HTTPS` / `https` to the `https:` form `URL#protocol` reports. */
30
+ const asProtocol = (scheme) => {
31
+ const lower = scheme.trim().toLowerCase();
32
+ return lower.endsWith(":") ? lower : `${lower}:`;
33
+ };
34
+ /**
35
+ * Returns true when `href` is safe to embed as a link target or image
36
+ * source.
37
+ *
38
+ * Allowed: any of `options.schemes` (default `http`, `https`, `mailto`),
39
+ * root-relative paths (`/foo`, but not `//evil.example` or `/\evil.example`,
40
+ * both of which resolve off-origin), and fragment links (`#bar`).
41
+ *
42
+ * The scheme is read off `new URL()` rather than compared as a string
43
+ * prefix, which is what makes the obfuscated forms fail: the WHATWG parser
44
+ * lowercases the scheme and strips leading and inner ASCII whitespace and
45
+ * control characters before reporting it, so `JaVaScRiPt:`, ` javascript:`
46
+ * and `java\tscript:` all normalise to `javascript:` and are rejected. A
47
+ * `startsWith('javascript:')` test would pass all three.
48
+ */
49
+ function isSafeHref(href, options) {
50
+ if (!Predicate.isString(href)) return false;
51
+ const trimmed = href.trim();
52
+ if (String.isEmpty(trimmed)) return false;
53
+ if (trimmed.startsWith("/") && !trimmed.startsWith("//") && !trimmed.startsWith("/\\")) return true;
54
+ if (trimmed.startsWith("#")) return true;
55
+ const parsed = Result.try(() => new URL(trimmed));
56
+ if (Result.isFailure(parsed)) return false;
57
+ const protocol = parsed.success.protocol.toLowerCase();
58
+ return (options?.schemes ?? DEFAULT_SAFE_SCHEMES).some((scheme) => asProtocol(scheme) === protocol);
59
+ }
60
+ // src/envelope.ts
61
+ /**
62
+ * Rewrites a document's root `type` to `doc`, ready for a ProseMirror
63
+ * schema.
64
+ *
65
+ * Anything that is not already `doc` is treated as an envelope name — the
66
+ * remap does not need to know which one, which is what lets one component
67
+ * serve every shape an application declares.
68
+ */
69
+ const toEditorDocument = (doc) => ({
70
+ ...doc,
71
+ type: "doc"
72
+ });
73
+ /**
74
+ * Rewrites an editor document's root `type` back to the envelope `name`,
75
+ * ready to persist.
76
+ *
77
+ * Curried on the name so a component can bind it once
78
+ * (`const toStored = fromEditorDocument(shape.name)`). A document with no
79
+ * `content` at all becomes an empty one rather than an envelope missing its
80
+ * required field.
81
+ */
82
+ const fromEditorDocument = (name) => (doc) => ({
83
+ ...doc,
84
+ type: name,
85
+ content: doc.content ?? []
86
+ });
87
+ export { isSafeHref as i, toEditorDocument as n, DEFAULT_SAFE_SCHEMES as r, fromEditorDocument as t };
@@ -0,0 +1,266 @@
1
+ import * as Option from "effect/Option";
2
+ import * as Schema$1 from "effect/Schema";
3
+ import { AttributeSpec, MarkType, NodeType, Schema } from "@prosekit/pm/model";
4
+ declare namespace Options_d_exports {
5
+ export { AttributeOverlay, AttributeOwner, DeriveDocumentOptions, DeriveOptions, DerivedMarkJSON, DerivedNodeJSON, RequiredAttribute, attributeOverlay, isRequiredAttribute, required };
6
+ }
7
+ /** Identifies the node or mark type that owns an attribute being derived. */
8
+ interface AttributeOwner {
9
+ /** Whether the attribute belongs to a node type or a mark type. */
10
+ readonly kind: 'node' | 'mark';
11
+ /** The owning type's name in the ProseMirror schema. */
12
+ readonly name: string;
13
+ }
14
+ declare const RequiredAttributeTypeId: unique symbol;
15
+ /**
16
+ * Wraps an overlay schema so the derived attribute key is REQUIRED, overriding
17
+ * ProseMirror's own default-implies-optional rule (a `default` of any value,
18
+ * `null` included, normally makes the key omissible — see
19
+ * {@link import("./Attribute.ts").deriveAttribute}). Use this when the live
20
+ * ProseMirror node always serialises the attribute in practice (every
21
+ * `Node.toJSON()` call includes defaulted attrs, so a genuinely
22
+ * editor-produced document always has the key) and the generative contract
23
+ * should hold a machine-authored document to the same standard.
24
+ */
25
+ interface RequiredAttribute {
26
+ readonly [RequiredAttributeTypeId]: true;
27
+ readonly schema: Schema$1.Top;
28
+ }
29
+ /** Wraps `schema` as a {@link RequiredAttribute}. */
30
+ declare const required: (schema: Schema$1.Top) => RequiredAttribute;
31
+ /** True when `value` is a {@link RequiredAttribute} produced by {@link required}. */
32
+ declare const isRequiredAttribute: (value: unknown) => value is RequiredAttribute;
33
+ /**
34
+ * A caller-supplied override for a single attribute's schema.
35
+ *
36
+ * Return `Option.some` of an Effect {@link Schema.Top} to replace the derived
37
+ * attribute schema (key optionality still follows ProseMirror's `default`), of
38
+ * a {@link required} wrapper to additionally force the key required, or
39
+ * `Option.none()` to fall back to the automatic derivation (validate hint →
40
+ * default `typeof` → `Schema.Unknown`). This is where the attribute value types
41
+ * ProseMirror cannot introspect are supplied by hand.
42
+ */
43
+ type AttributeOverlay = (owner: AttributeOwner, attribute: string, spec: AttributeSpec) => Option.Option<Schema$1.Top | RequiredAttribute>;
44
+ /** Options common to every derivation entry point. */
45
+ interface DeriveOptions {
46
+ /**
47
+ * Overrides derived attribute schemas. Consulted before the automatic
48
+ * derivation for every node and mark attribute.
49
+ */
50
+ readonly attributes?: AttributeOverlay;
51
+ /**
52
+ * Whether to attach a `marks` field to nodes that admit marks. Defaults to
53
+ * `true`; set `false` to omit marks entirely (e.g. a plain-text contract).
54
+ */
55
+ readonly includeMarks?: boolean;
56
+ /**
57
+ * Emit `content: "block+"` and other required content models as a
58
+ * non-empty array (`Schema.NonEmptyArray`) rather than a possibly-empty
59
+ * `Schema.Array`. Defaults to `true` — it mirrors ProseMirror's own
60
+ * non-emptiness guarantee. Set `false` for a more lenient generative
61
+ * contract.
62
+ */
63
+ readonly nonEmptyRequiredContent?: boolean;
64
+ }
65
+ /** Options for {@link deriveDocumentSchema}. */
66
+ interface DeriveDocumentOptions extends DeriveOptions {
67
+ /**
68
+ * Overrides the top node's `type` literal. ProseMirror's top node is
69
+ * usually named `doc`; an application that renames it at its own layer
70
+ * (e.g. `doc` → `blockDocument`) passes the app-layer name here so the
71
+ * derived envelope matches its persisted JSON.
72
+ */
73
+ readonly topTypeName?: string;
74
+ }
75
+ /** A derived ProseMirror mark, as its JSON shape. */
76
+ interface DerivedMarkJSON {
77
+ readonly type: string;
78
+ readonly attrs?: Record<string, unknown>;
79
+ }
80
+ /** A derived ProseMirror node, as its recursive JSON shape. */
81
+ interface DerivedNodeJSON {
82
+ readonly type: string;
83
+ readonly attrs?: Record<string, unknown>;
84
+ readonly content?: ReadonlyArray<DerivedNodeJSON>;
85
+ readonly marks?: ReadonlyArray<DerivedMarkJSON>;
86
+ readonly text?: string;
87
+ }
88
+ /**
89
+ * Builds an {@link AttributeOverlay} from a nested record keyed by owner name
90
+ * then attribute name. A convenience over writing the function by hand when the
91
+ * overrides are static:
92
+ *
93
+ * ```ts
94
+ * const overlay = attributeOverlay({
95
+ * heading: { level: Schema.Literals([1, 2, 3, 4, 5, 6]) },
96
+ * image: { src: Schema.NullOr(Schema.String) },
97
+ * });
98
+ * ```
99
+ *
100
+ * Keys match on the owning type's name regardless of whether it is a node or a
101
+ * mark; a node and a mark sharing a name (unusual) would share overrides.
102
+ */
103
+ declare function attributeOverlay(overrides: Readonly<Record<string, Readonly<Record<string, Schema$1.Top | RequiredAttribute>>>>): AttributeOverlay;
104
+ declare namespace Derive_d_exports {
105
+ export { DocumentSchemaSpec, deriveDocumentSchema, deriveDocumentSchemas, deriveMarkSchema, deriveNodeSchema };
106
+ }
107
+ /**
108
+ * Derives an Effect schema for a single ProseMirror mark type: a struct with a
109
+ * `type` literal and, when the mark declares attributes, an `attrs` field.
110
+ */
111
+ declare function deriveMarkSchema(markType: MarkType, options?: DeriveOptions): Schema$1.Codec<DerivedMarkJSON>;
112
+ /**
113
+ * Derives an Effect schema for a single ProseMirror node type, recursively
114
+ * modelling its content via the node's own schema. The result validates a JSON
115
+ * subtree rooted at this node.
116
+ */
117
+ declare function deriveNodeSchema(nodeType: NodeType, options?: DeriveOptions): Schema$1.Codec<DerivedNodeJSON>;
118
+ /**
119
+ * Derives an Effect schema for a whole document from a ProseMirror schema,
120
+ * rooted at its top node (`schema.topNodeType`, usually `doc`).
121
+ *
122
+ * Pass `topTypeName` to rename the top node's `type` literal — for an
123
+ * application that persists the top node under a different name than
124
+ * ProseMirror's internal `doc` (e.g. `blockDocument`).
125
+ */
126
+ declare function deriveDocumentSchema(schema: Schema, options?: DeriveDocumentOptions): Schema$1.Codec<DerivedNodeJSON>;
127
+ /** One entry in a {@link deriveDocumentSchemas} call. */
128
+ interface DocumentSchemaSpec {
129
+ /** The compiled ProseMirror schema to derive a document schema from. */
130
+ readonly schema: Schema;
131
+ /** Overrides this document's top node `type` literal, as {@link DeriveDocumentOptions.topTypeName}. */
132
+ readonly topTypeName?: string;
133
+ }
134
+ /**
135
+ * Derives Effect schemas for two or more related ProseMirror document
136
+ * schemas in one call, sharing a single {@link Registry} — and so its
137
+ * mark/node memoization — across all of them.
138
+ *
139
+ * Use this instead of separate {@link deriveDocumentSchema} calls when the
140
+ * schemas are assembled from overlapping ProseKit extensions (e.g. two
141
+ * document types that both compose the same `defineAllMarks()` or
142
+ * `defineText()`): the marks and nodes the schemas have in common are then
143
+ * derived once rather than once per document. The results stay fully
144
+ * independent `Schema.Codec`s — this shares derivation machinery, not the
145
+ * documents' wire shapes.
146
+ *
147
+ * **Precondition:** sharing is by mark/node *name*, so this is only correct
148
+ * when every schema in the call defines same-named marks/nodes identically
149
+ * (the same extension composed into each schema, as opposed to two schemas
150
+ * that coincidentally both declare a differently-shaped type under the same
151
+ * name) **and admits the same set of marks** — an inline node's derived
152
+ * `marks` field is the schema's whole inline mark union, so a schema with an
153
+ * extra mark derives a different `text`. A mismatch would silently resolve
154
+ * to whichever schema's version was derived first, for every schema after
155
+ * it, with no error.
156
+ *
157
+ * All entries are built eagerly in this one call — pass already-compiled
158
+ * schemas. A caller that needs each document to stay independently lazy
159
+ * (e.g. never compiling schema B just because schema A was validated) should
160
+ * instead share a {@link import("./Registry.ts").RegistryCache} across
161
+ * separate `Registry` instances directly, one per schema, each built inside
162
+ * its own deferred call site — the pattern
163
+ * `@thomasfosterau/prosekit-utilities/prosekit`'s `defineDocumentSchema`
164
+ * implements.
165
+ *
166
+ * `options` applies to every schema in the call; use each entry's own
167
+ * `topTypeName` for a per-document top-node rename.
168
+ */
169
+ declare function deriveDocumentSchemas<Specs extends Record<string, DocumentSchemaSpec>>(schemas: Specs, options?: DeriveOptions): { readonly [K in keyof Specs]: Schema$1.Codec<DerivedNodeJSON>; };
170
+ declare namespace Attribute_d_exports {
171
+ export { DerivedAttribute, deriveAttribute, deriveAttrsStruct, parseValidateGrammar };
172
+ }
173
+ /** A single derived attribute: its value schema and whether the key is optional. */
174
+ interface DerivedAttribute {
175
+ readonly schema: Schema$1.Top;
176
+ /** True when the attribute has a default and may be omitted from the JSON. */
177
+ readonly optional: boolean;
178
+ }
179
+ /**
180
+ * Parses ProseMirror's `validate` string grammar — a `|`-separated list of the
181
+ * primitive names `"string"`, `"number"`, `"boolean"`, `"null"`, and
182
+ * `"undefined"` — into an Effect schema.
183
+ *
184
+ * The `"undefined"` member is not represented as a value (there is no
185
+ * first-class `undefined` schema for a present key); it is reported via the
186
+ * returned `optional` flag so the caller can drop the key instead.
187
+ *
188
+ * An empty or all-unknown grammar yields `Schema.Unknown`.
189
+ */
190
+ declare function parseValidateGrammar(validate: string): {
191
+ readonly schema: Schema$1.Top;
192
+ readonly optional: boolean;
193
+ };
194
+ /**
195
+ * Derives the schema and key-optionality for one node or mark attribute.
196
+ *
197
+ * @param owner The node or mark type the attribute belongs to (for the overlay).
198
+ * @param name The attribute name.
199
+ * @param spec The ProseMirror attribute spec.
200
+ * @param overlay Optional caller override consulted first.
201
+ */
202
+ declare function deriveAttribute(owner: AttributeOwner, name: string, spec: AttributeSpec, overlay?: AttributeOverlay): DerivedAttribute;
203
+ /**
204
+ * Derives the `attrs` struct schema for a node or mark from its attribute
205
+ * specs, or `Option.none()` when the type declares no attributes.
206
+ *
207
+ * `attrs` is an `Option` because ProseMirror's own `NodeSpec`/`MarkSpec` leaves
208
+ * `attrs` absent for an attribute-less type — the caller converts that foreign
209
+ * `undefined` at the boundary (`Option.fromNullishOr(nodeType.spec.attrs)`).
210
+ */
211
+ declare function deriveAttrsStruct(owner: AttributeOwner, attrs: Option.Option<Readonly<Record<string, AttributeSpec>>>, overlay?: AttributeOverlay): Option.Option<Schema$1.Top>;
212
+ declare namespace Content_d_exports {
213
+ export { ContentCardinality, allowedContentTypes, contentCardinality };
214
+ }
215
+ /** How a node's content model constrains the number of children. */
216
+ type ContentCardinality =
217
+ /** Leaf/atom: no content expression, the node takes no children. */
218
+ 'none' |
219
+ /** `*` / `?`: empty content is valid; the `content` key may be omitted. */
220
+ 'optional' |
221
+ /** `+`: at least one child is required. */
222
+ 'required';
223
+ /**
224
+ * Collects the distinct set of node types that may appear anywhere in a node
225
+ * type's content, by breadth-first walk of its content-match automaton.
226
+ *
227
+ * Order follows first-discovery, which for the typical single-group
228
+ * expression (`block+`, `inline*`) matches the schema's node declaration
229
+ * order.
230
+ */
231
+ declare function allowedContentTypes(nodeType: NodeType): ReadonlyArray<NodeType>;
232
+ /**
233
+ * Classifies a node type's content cardinality from its content-match
234
+ * automaton: `none` when it admits no children, `optional` when the empty
235
+ * fragment is a valid end (`*` / `?`), and `required` otherwise (`+`).
236
+ */
237
+ declare function contentCardinality(nodeType: NodeType): ContentCardinality;
238
+ /**
239
+ * The memoised mark/node maps a {@link Registry} reads and writes.
240
+ *
241
+ * Keys are type names. Share one cache only between schemas whose mark sets
242
+ * match and whose same-named types are defined identically; see the module
243
+ * doc for what goes wrong otherwise.
244
+ */
245
+ interface RegistryCache {
246
+ readonly nodeCache: Map<string, Schema$1.Codec<DerivedNodeJSON>>;
247
+ readonly markCache: Map<string, Schema$1.Codec<DerivedMarkJSON>>;
248
+ }
249
+ /** A fresh, empty {@link RegistryCache} — private to one `Registry` unless explicitly shared. */
250
+ declare function createRegistryCache(): RegistryCache;
251
+ /** Builds and memoises the derived schemas for a single ProseMirror schema. */
252
+ declare class Registry {
253
+ #private;
254
+ constructor(schema: Schema, options?: DeriveOptions, cache?: RegistryCache);
255
+ /** The derived schema for a mark type, built once and memoised by name. */
256
+ mark(markType: MarkType): Schema$1.Codec<DerivedMarkJSON>;
257
+ /** The derived schema for a node type, built once and memoised by name. */
258
+ node(nodeType: NodeType): Schema$1.Codec<DerivedNodeJSON>;
259
+ /**
260
+ * The derived schema for a node type with its `type` literal overridden —
261
+ * used for a renamed top node (e.g. `doc` → `blockDocument`). Not memoised,
262
+ * since the override makes it distinct from the canonical node schema.
263
+ */
264
+ nodeAs(nodeType: NodeType, typeName: string): Schema$1.Codec<DerivedNodeJSON>;
265
+ }
266
+ export { required as S, DerivedNodeJSON as _, Attribute_d_exports as a, attributeOverlay as b, deriveDocumentSchema as c, deriveNodeSchema as d, AttributeOverlay as f, DerivedMarkJSON as g, DeriveOptions as h, Content_d_exports as i, deriveDocumentSchemas as l, DeriveDocumentOptions as m, RegistryCache as n, Derive_d_exports as o, AttributeOwner as p, createRegistryCache as r, DocumentSchemaSpec as s, Registry as t, deriveMarkSchema as u, Options_d_exports as v, isRequiredAttribute as x, RequiredAttribute as y };