@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.
- package/LICENSE +21 -0
- package/README.md +447 -2
- package/dist/derive-BtbsOqS7.js +479 -0
- package/dist/derive.d.ts +2 -0
- package/dist/derive.js +1 -0
- package/dist/document-shape-CrKjPIEf.d.ts +195 -0
- package/dist/drizzle.d.ts +76 -0
- package/dist/drizzle.js +95 -0
- package/dist/envelope-DQS7tt8f.js +87 -0
- package/dist/index-DlgceBHe.d.ts +266 -0
- package/dist/index.d.ts +203 -0
- package/dist/index.js +723 -0
- package/dist/prosekit.d.ts +3218 -0
- package/dist/prosekit.js +827 -0
- package/dist/rolldown-runtime-D8jQpDG7.js +12 -0
- package/package.json +71 -3
|
@@ -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 };
|
package/dist/drizzle.js
ADDED
|
@@ -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 };
|