@effected/schemastore 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/SchemaFile.js ADDED
@@ -0,0 +1,119 @@
1
+ import { Context, Effect, FileSystem, Layer, Path, Schema } from "effect";
2
+
3
+ //#region src/SchemaFile.ts
4
+ /**
5
+ * Indicates that a schema file could not be read from the filesystem (a
6
+ * filesystem error other than not-found).
7
+ *
8
+ * @public
9
+ */
10
+ var SchemaFileReadError = class extends Schema.TaggedErrorClass()("SchemaFileReadError", {
11
+ /** The path that could not be read. */
12
+ path: Schema.String,
13
+ /** The underlying filesystem failure, preserved structurally. */
14
+ cause: Schema.Defect()
15
+ }) {
16
+ get message() {
17
+ return `Failed to read schema file from "${this.path}"`;
18
+ }
19
+ };
20
+ /**
21
+ * Indicates that no schema file exists at the expected path. Carries its
22
+ * own tag for `catchTag` routing.
23
+ *
24
+ * @public
25
+ */
26
+ var SchemaFileNotFoundError = class extends Schema.TaggedErrorClass()("SchemaFileNotFoundError", {
27
+ /** The path where the schema file was expected. */
28
+ path: Schema.String }) {
29
+ get message() {
30
+ return `Schema file not found at "${this.path}"`;
31
+ }
32
+ };
33
+ /**
34
+ * Indicates that a schema file could not be written to the filesystem.
35
+ * Narrowed to the filesystem failure only — a serialization failure
36
+ * surfaces as its own `CanonicalJsonError`, never wrapped here.
37
+ *
38
+ * @public
39
+ */
40
+ var SchemaFileWriteError = class extends Schema.TaggedErrorClass()("SchemaFileWriteError", {
41
+ /** The path that could not be written. */
42
+ path: Schema.String,
43
+ /** The underlying filesystem failure, preserved structurally. */
44
+ cause: Schema.Defect()
45
+ }) {
46
+ get message() {
47
+ return `Failed to write schema file to "${this.path}"`;
48
+ }
49
+ };
50
+ /**
51
+ * Reads and writes emitted schema documents over core `FileSystem` /
52
+ * `Path` — the package's one IO surface. The layer requires those
53
+ * services; provide `@effect/platform-node`'s `NodeFileSystem` / `NodePath`
54
+ * (or a bun equivalent) at the application boundary.
55
+ *
56
+ * `write` is write-if-changed: serialization goes through the owned
57
+ * `CanonicalJson` (equal documents serialize to equal bytes), so an
58
+ * unchanged document never touches the file — a generator committed to a
59
+ * repo does not churn mtimes, and its CI drift check is `read` + compare.
60
+ *
61
+ * @example
62
+ * ```ts
63
+ * import { SchemaFile, StoreDocument } from "@effected/schemastore";
64
+ * import { NodeFileSystem, NodePath } from "@effect/platform-node";
65
+ * import { Effect, Layer, Schema } from "effect";
66
+ *
67
+ * const program = Effect.gen(function* () {
68
+ * const files = yield* SchemaFile;
69
+ * const document = yield* StoreDocument.fromSchema(Schema.Struct({ name: Schema.String }), {
70
+ * $id: "https://example.com/config.schema.json",
71
+ * });
72
+ * return yield* files.write("schemas/config.schema.json", document);
73
+ * }).pipe(
74
+ * Effect.provide(SchemaFile.layer),
75
+ * Effect.provide(Layer.mergeAll(NodeFileSystem.layer, NodePath.layer)),
76
+ * );
77
+ * ```
78
+ *
79
+ * @public
80
+ */
81
+ var SchemaFile = class SchemaFile extends Context.Service()("@effected/schemastore/SchemaFile") {
82
+ /** Build the service implementation from `FileSystem` / `Path` in context; use {@link SchemaFile.layer} to provide it. */
83
+ static make = Effect.gen(function* () {
84
+ const fs = yield* FileSystem.FileSystem;
85
+ const path = yield* Path.Path;
86
+ return {
87
+ read: Effect.fn("SchemaFile.read")(function* (target) {
88
+ return yield* fs.readFileString(target).pipe(Effect.mapError((cause) => cause.reason._tag === "NotFound" ? SchemaFileNotFoundError.make({ path: target }) : SchemaFileReadError.make({
89
+ path: target,
90
+ cause
91
+ })));
92
+ }),
93
+ write: Effect.fn("SchemaFile.write")(function* (target, document, options) {
94
+ const text = yield* Effect.fromResult(document.serializeResult(options));
95
+ if ((yield* fs.readFileString(target).pipe(Effect.catch((cause) => cause.reason._tag === "NotFound" ? Effect.succeed(void 0) : Effect.fail(SchemaFileReadError.make({
96
+ path: target,
97
+ cause
98
+ }))))) === text) return "unchanged";
99
+ yield* fs.makeDirectory(path.dirname(target), { recursive: true }).pipe(Effect.mapError((cause) => SchemaFileWriteError.make({
100
+ path: target,
101
+ cause
102
+ })));
103
+ yield* fs.writeFileString(target, text).pipe(Effect.mapError((cause) => SchemaFileWriteError.make({
104
+ path: target,
105
+ cause
106
+ })));
107
+ return "written";
108
+ })
109
+ };
110
+ });
111
+ /**
112
+ * The live layer. Requires core `FileSystem` / `Path`, provided by the
113
+ * consumer's platform implementation at the edge.
114
+ */
115
+ static layer = Layer.effect(SchemaFile, SchemaFile.make);
116
+ };
117
+
118
+ //#endregion
119
+ export { SchemaFile, SchemaFileNotFoundError, SchemaFileReadError, SchemaFileWriteError };
@@ -0,0 +1,30 @@
1
+ //#region src/SchemaTarget.ts
2
+ /**
3
+ * Constructors for `SchemaTarget` values.
4
+ *
5
+ * @public
6
+ */
7
+ var SchemaTarget = class {
8
+ constructor() {}
9
+ /**
10
+ * Builds a target. `$id`, `name` and `path` must be non-empty — an
11
+ * empty identity is a wiring mistake and throws.
12
+ */
13
+ static make(options) {
14
+ for (const key of [
15
+ "$id",
16
+ "name",
17
+ "path"
18
+ ]) if (options[key].length === 0) throw new Error(`SchemaTarget.make requires a non-empty "${key}"`);
19
+ return {
20
+ schema: options.schema,
21
+ $id: options.$id,
22
+ name: options.name,
23
+ path: options.path,
24
+ ...options.version !== void 0 ? { version: options.version } : {}
25
+ };
26
+ }
27
+ };
28
+
29
+ //#endregion
30
+ export { SchemaTarget };
@@ -0,0 +1,91 @@
1
+ import { Context, Effect, Layer, Schema } from "effect";
2
+
3
+ //#region src/SchemaValidator.ts
4
+ /**
5
+ * Indicates that the validation engine behind the {@link SchemaValidator}
6
+ * contract failed as a *mechanism* — it could not run at all.
7
+ *
8
+ * By convention the error channel is reserved for exactly that: a document
9
+ * that fails the engine's gate is a {@link ValidationFinding} list (a
10
+ * value), never an error. Raised by implementations of
11
+ * {@link SchemaValidatorShape.validate}.
12
+ *
13
+ * @public
14
+ */
15
+ var SchemaValidatorError = class extends Schema.TaggedErrorClass()("SchemaValidatorError", {
16
+ /** The underlying engine failure, preserved structurally. */
17
+ cause: Schema.Defect() }) {
18
+ get message() {
19
+ return "Schema validation engine failed";
20
+ }
21
+ };
22
+ /**
23
+ * One problem a validation engine found with a document: a value in a
24
+ * report, never an error channel — the consumer decides what a finding
25
+ * gates.
26
+ *
27
+ * @public
28
+ */
29
+ var ValidationFinding = class extends Schema.Class("ValidationFinding")({
30
+ /** JSON pointer into the flat document (`""` is the root schema). */
31
+ path: Schema.String,
32
+ /** Human-readable explanation from the engine. */
33
+ message: Schema.String,
34
+ /** The JSON Schema keyword the finding is about, when the engine names one. */
35
+ keyword: Schema.optionalKey(Schema.String)
36
+ }) {};
37
+ /** The default for an unstubbed {@link SchemaValidator.makeTest} member. */
38
+ const notStubbed = (method) => () => Effect.die(/* @__PURE__ */ new Error(`SchemaValidator.makeTest: ${method}() was called but not stubbed — no honest default exists for a test double; pass a \`${method}\` override.`));
39
+ /**
40
+ * Contract for real-engine JSON Schema document validation — the seam
41
+ * through which SchemaStore's own gate (ajv strict mode) reaches this
42
+ * package without ajv ever entering its dependency graph.
43
+ *
44
+ * This is a contract-only service: {@link SchemaValidator.noop} is the sole
45
+ * implementation this package ships, and it validates nothing. The consumer
46
+ * closes the seam with a real engine at the application edge — e.g. an ajv
47
+ * adapter whose `validate` compiles the document with
48
+ * `new Ajv({ strict: true, allErrors: true })` and answers compile failures
49
+ * as findings. `DocumentLint` remains the owned, always-available
50
+ * structural half of the validation story.
51
+ *
52
+ * @example
53
+ * ```ts
54
+ * import { SchemaValidator } from "@effected/schemastore";
55
+ * import { Effect } from "effect";
56
+ *
57
+ * const program = Effect.gen(function* () {
58
+ * const validator = yield* SchemaValidator;
59
+ * return yield* validator.validate({ type: "object" });
60
+ * });
61
+ *
62
+ * Effect.runPromise(Effect.provide(program, SchemaValidator.noop));
63
+ * // => []
64
+ * ```
65
+ *
66
+ * @public
67
+ */
68
+ var SchemaValidator = class SchemaValidator extends Context.Service()("@effected/schemastore/SchemaValidator") {
69
+ /**
70
+ * No-op default: `validate` always succeeds with no findings, never
71
+ * consulting an engine. A pure `Layer.succeed`, bound to a const so the
72
+ * layer memoizes by reference.
73
+ */
74
+ static noop = Layer.succeed(SchemaValidator, { validate: () => Effect.succeed([]) });
75
+ /**
76
+ * An in-memory double: stub only the members the test exercises; every
77
+ * other member **dies** with a defect naming itself. No member has an
78
+ * honest default — a fabricated clean pass would leak into consumer
79
+ * logic as fact (use {@link SchemaValidator.noop} when a test genuinely
80
+ * wants an always-clean validator).
81
+ */
82
+ static makeTest = (overrides = {}) => ({
83
+ validate: notStubbed("validate"),
84
+ ...overrides
85
+ });
86
+ /** {@link SchemaValidator.makeTest} behind `Layer.succeed`. */
87
+ static layerTest = (overrides = {}) => Layer.succeed(SchemaValidator, SchemaValidator.makeTest(overrides));
88
+ };
89
+
90
+ //#endregion
91
+ export { SchemaValidator, SchemaValidatorError, ValidationFinding };
@@ -0,0 +1,153 @@
1
+ import { Effect, Option, Order, Result, Schema } from "effect";
2
+ import { SemVer } from "@effected/semver";
3
+
4
+ //#region src/SchemaVersioning.ts
5
+ /**
6
+ * SchemaStore version labels are dotted numerics with one to three
7
+ * components and an optional prerelease — `1`, `1.2`, `1.2.3`,
8
+ * `1.2-beta.1` — per the catalog's versioned-schema convention
9
+ * (`agripparc-1.2.json`). They are deliberately NOT strict SemVer: most
10
+ * real catalog labels are two-part. Leading zeros are rejected on every
11
+ * numeric identifier — core components and numeric prerelease identifiers
12
+ * alike (SemVer §9 semantics: `0` is legal, `01` is not, alphanumerics
13
+ * like `0abc` are) — so no two distinct labels can collide under numeric
14
+ * ordering, and every accepted label survives the SemVer pad the ordering
15
+ * performs (see `orderingKey`).
16
+ */
17
+ const VERSION_PATTERN = /^(0|[1-9]\d*)(\.(0|[1-9]\d*)){0,2}(-(0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*)(\.(0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*))*)?$/;
18
+ /**
19
+ * Indicates that a string is not a valid SchemaStore version label.
20
+ *
21
+ * Raised by {@link SchemaVersioning.parse}.
22
+ *
23
+ * @public
24
+ */
25
+ var InvalidSchemaVersionError = class extends Schema.TaggedErrorClass()("InvalidSchemaVersionError", {
26
+ /** The raw input string that failed to parse. */
27
+ input: Schema.String }) {
28
+ get message() {
29
+ return `Invalid schema version label: "${this.input}"`;
30
+ }
31
+ };
32
+ /**
33
+ * A SchemaStore version label: a branded string validated against the
34
+ * catalog's version grammar (`major[.minor[.patch]][-prerelease]`). The
35
+ * label round-trips verbatim into file names and catalog `versions` keys;
36
+ * ordering pads it to a full SemVer internally (see
37
+ * {@link SchemaVersioning.Order}).
38
+ *
39
+ * @public
40
+ */
41
+ const SchemaVersion = Schema.String.check(Schema.isPattern(VERSION_PATTERN)).pipe(Schema.brand("SchemaVersion"));
42
+ const orderingKey = (label) => {
43
+ const hyphen = label.indexOf("-");
44
+ const core = hyphen === -1 ? label : label.slice(0, hyphen);
45
+ const prerelease = hyphen === -1 ? "" : label.slice(hyphen);
46
+ const parts = core.split(".");
47
+ while (parts.length < 3) parts.push("0");
48
+ const result = SemVer.parseResult(`${parts.join(".")}${prerelease}`);
49
+ if (Result.isFailure(result)) throw new Error(`SchemaVersion ordering invariant violated for label "${label}"`);
50
+ return result.success;
51
+ };
52
+ const assertSimpleName = (name) => {
53
+ if (name.length === 0 || /[/\\\s]/.test(name)) throw new Error(`Schema name must be a non-empty simple file base name, got "${name}"`);
54
+ };
55
+ const joinUrl = (baseUrl, file) => {
56
+ let end = baseUrl.length;
57
+ while (end > 0 && baseUrl.charCodeAt(end - 1) === 47) end -= 1;
58
+ return `${baseUrl.slice(0, end)}/${file}`;
59
+ };
60
+ /**
61
+ * Both SchemaStore catalog modes as pure derivations: unversioned (a plain
62
+ * `name.json` file, `url` only) and versioned (`name-<version>.json` files,
63
+ * a `versions` map, and `url` pointing at the latest version).
64
+ *
65
+ * Version ordering follows SemVer precedence over labels padded to three
66
+ * components, so `1.10` sorts above `1.9` and `2-beta` below `2`.
67
+ *
68
+ * @public
69
+ */
70
+ var SchemaVersioning = class SchemaVersioning {
71
+ constructor() {}
72
+ /**
73
+ * Parses a version label. Pure and synchronous — the primitive form;
74
+ * {@link SchemaVersioning.parse} is the same check behind a span.
75
+ */
76
+ static parseResult(input) {
77
+ return VERSION_PATTERN.test(input) ? Result.succeed(input) : Result.fail(InvalidSchemaVersionError.make({ input }));
78
+ }
79
+ /**
80
+ * Effect form of {@link SchemaVersioning.parseResult}, adding only the
81
+ * `SchemaVersioning.parse` span. Defined in terms of the `Result`
82
+ * primitive — synchronous callers can use that variant directly.
83
+ */
84
+ static parse = Effect.fn("SchemaVersioning.parse")((input) => Effect.fromResult(SchemaVersioning.parseResult(input)));
85
+ /**
86
+ * `Order` instance over version labels: SemVer precedence after padding
87
+ * missing components with zeros. `1.10` sorts above `1.9` (numeric, not
88
+ * lexical) and `2-beta` below `2` (prerelease precedence).
89
+ */
90
+ static Order = Order.make((a, b) => SemVer.Order(orderingKey(a), orderingKey(b)));
91
+ /**
92
+ * The highest version label by {@link SchemaVersioning.Order}, or
93
+ * `Option.none()` for an empty collection.
94
+ */
95
+ static latest(versions) {
96
+ return versions.length === 0 ? Option.none() : Option.some(versions.reduce((max, v) => SchemaVersioning.Order(v, max) > 0 ? v : max));
97
+ }
98
+ /**
99
+ * Derives the schema file name for a catalog name: `name.json`
100
+ * unversioned, `name-<version>.json` versioned.
101
+ *
102
+ * The name must be a simple file base name (no separators, no
103
+ * whitespace); anything else is a wiring mistake and throws.
104
+ */
105
+ static fileName(name, version) {
106
+ assertSimpleName(name);
107
+ return version === void 0 ? `${name}.json` : `${name}-${version}.json`;
108
+ }
109
+ /**
110
+ * The canonical URL a schema file is hosted at: `baseUrl` joined with
111
+ * {@link SchemaVersioning.fileName}.
112
+ */
113
+ static schemaUrl(baseUrl, name, version) {
114
+ return joinUrl(baseUrl, SchemaVersioning.fileName(name, version));
115
+ }
116
+ /**
117
+ * Assembles the `url`/`versions` half of a catalog entry.
118
+ *
119
+ * Omitting `versions` selects the unversioned mode (`url` only,
120
+ * pointing at the plain `name.json`). Providing them selects the
121
+ * versioned mode: the `versions` map carries every label, and `url`
122
+ * points at the latest version's file. An **empty** `versions` array is
123
+ * a contradiction (versioned mode with no versions) and throws — pass
124
+ * `undefined` for the unversioned mode.
125
+ *
126
+ * Labels are inserted in ascending {@link SchemaVersioning.Order}, but
127
+ * the SchemaStore catalog format requires `versions` to be a JSON
128
+ * *object*, and JavaScript enumerates array-index-like keys first: a
129
+ * bare-major label (`"2"`) always enumerates — and therefore
130
+ * serializes — before every dotted label, regardless of insertion. The
131
+ * resulting enumeration order is: bare-major labels ascending
132
+ * numerically, then all other labels ascending. For label sets with no
133
+ * bare majors this is fully ascending; mixed sets interleave, so
134
+ * consumers must derive ordering from the labels themselves (as
135
+ * {@link SchemaVersioning.latest} does), never from key position.
136
+ */
137
+ static catalogUrls(options) {
138
+ const { baseUrl, name, versions } = options;
139
+ if (versions === void 0) return { url: SchemaVersioning.schemaUrl(baseUrl, name) };
140
+ if (versions.length === 0) throw new Error(`catalogUrls received an empty versions array for "${name}": pass undefined for the unversioned mode`);
141
+ const ascending = [...versions].sort(SchemaVersioning.Order);
142
+ const map = {};
143
+ for (const version of ascending) map[version] = SchemaVersioning.schemaUrl(baseUrl, name, version);
144
+ const newest = ascending[ascending.length - 1];
145
+ return {
146
+ url: SchemaVersioning.schemaUrl(baseUrl, name, newest),
147
+ versions: map
148
+ };
149
+ }
150
+ };
151
+
152
+ //#endregion
153
+ export { InvalidSchemaVersionError, SchemaVersion, SchemaVersioning };
@@ -0,0 +1,142 @@
1
+ import { KeywordFamilies } from "./KeywordFamilies.js";
2
+ import { AnnotationCarriers } from "./AnnotationCarriers.js";
3
+ import { CanonicalJson } from "./CanonicalJson.js";
4
+ import { Effect, JsonSchema, Result, Schema } from "effect";
5
+
6
+ //#region src/StoreDocument.ts
7
+ /**
8
+ * The Draft-07 meta-schema URL SchemaStore documents declare as `$schema`.
9
+ *
10
+ * Deliberately carries the trailing `#` fragment: the SchemaStore corpus
11
+ * (and the extraction source's committed files) use the fragment form,
12
+ * where core's `JsonSchema.META_SCHEMA_URI_DRAFT_07` omits it.
13
+ *
14
+ * @public
15
+ */
16
+ const DRAFT_07_META_SCHEMA = "http://json-schema.org/draft-07/schema#";
17
+ /**
18
+ * Indicates that an Effect Schema could not be converted into a SchemaStore
19
+ * document — core's JSON Schema generation rejected the schema, or the
20
+ * generated document nested past the hardening cap.
21
+ *
22
+ * Raised by {@link StoreDocument.fromSchema}. The `cause` carries the
23
+ * underlying failure for the operator; calling code branches on the tag.
24
+ *
25
+ * @public
26
+ */
27
+ var SchemaConversionError = class extends Schema.TaggedErrorClass()("SchemaConversionError", {
28
+ /** The `$id` of the document that failed to build. */
29
+ $id: Schema.String,
30
+ /** The underlying conversion failure. */
31
+ cause: Schema.Defect()
32
+ }) {
33
+ get message() {
34
+ return `Failed to build SchemaStore document "${this.$id}"`;
35
+ }
36
+ };
37
+ const DEFINITIONS_REF_PREFIX = /^#\/definitions(?=\/|$)/;
38
+ var RewriteDepthExceeded = class {
39
+ _tag = "RewriteDepthExceeded";
40
+ };
41
+ const carry = (source, target) => {
42
+ const result = AnnotationCarriers.carryResult(source, target);
43
+ if (Result.isFailure(result)) throw result.failure;
44
+ return result.success;
45
+ };
46
+ const restoreDefsRefs = (node, depth) => {
47
+ if (depth >= 256) throw new RewriteDepthExceeded();
48
+ if (Array.isArray(node)) return node.map((item) => restoreDefsRefs(item, depth + 1));
49
+ if (typeof node === "object" && node !== null) {
50
+ const out = Object.create(null);
51
+ for (const [key, value] of Object.entries(node)) out[key] = key === "$ref" && typeof value === "string" ? value.replace(DEFINITIONS_REF_PREFIX, "#/$defs") : restoreDefsRefs(value, depth + 1);
52
+ return out;
53
+ }
54
+ return node;
55
+ };
56
+ /**
57
+ * A SchemaStore-shaped Draft-07 JSON Schema document assembled from an
58
+ * Effect Schema source: `$schema` (the Draft-07 meta-schema) + `$id` + the
59
+ * root schema + the `$defs` pool.
60
+ *
61
+ * {@link StoreDocument.fromSchema} owns the whole pipeline: core's
62
+ * `Schema.toJsonSchemaDocument` (Draft 2020-12), core's
63
+ * `JsonSchema.toDocumentDraft07` lowering, the `#/definitions` →
64
+ * `#/$defs` `$ref` rewrite the lowering makes necessary — so every `$ref`
65
+ * in a built document already resolves against the `$defs` pool — and the
66
+ * {@link AnnotationCarriers} re-graft, so annotated non-standard keyword
67
+ * families ({@link KeywordFamilies}) survive into the built document. The
68
+ * package owns assembly and publication shape, not a JSON Schema engine.
69
+ *
70
+ * @public
71
+ */
72
+ var StoreDocument = class StoreDocument extends Schema.Class("StoreDocument")({
73
+ /** The meta-schema URL ({@link DRAFT_07_META_SCHEMA}). */
74
+ $schema: Schema.String,
75
+ /** The canonical `$id` URL. */
76
+ $id: Schema.String,
77
+ /** The root schema's keywords, without the definitions pool. */
78
+ root: Schema.Record(Schema.String, Schema.Unknown),
79
+ /** The definitions pool, emitted under `$defs`. */
80
+ defs: Schema.Record(Schema.String, Schema.Unknown)
81
+ }) {
82
+ /**
83
+ * Builds the document for an Effect Schema source. Pure and
84
+ * synchronous — the primitive form; {@link StoreDocument.fromSchema} is
85
+ * the same pipeline behind a span.
86
+ */
87
+ static fromSchemaResult(source, options) {
88
+ try {
89
+ const userIncludes = options.jsonSchema?.includeAnnotationKey;
90
+ const document = Schema.toJsonSchemaDocument(source, {
91
+ ...options.jsonSchema,
92
+ includeAnnotationKey: (key) => KeywordFamilies.isDeclared(key) || userIncludes?.(key) === true
93
+ });
94
+ const lowered = JsonSchema.toDocumentDraft07(document);
95
+ const root = carry(document.schema, restoreDefsRefs(lowered.schema, 0));
96
+ const defs = Object.create(null);
97
+ for (const [name, definition] of Object.entries(lowered.definitions)) defs[name] = carry(document.definitions[name], restoreDefsRefs(definition, 1));
98
+ return Result.succeed(StoreDocument.make({
99
+ $schema: DRAFT_07_META_SCHEMA,
100
+ $id: options.$id,
101
+ root,
102
+ defs
103
+ }));
104
+ } catch (cause) {
105
+ return Result.fail(SchemaConversionError.make({
106
+ $id: options.$id,
107
+ cause
108
+ }));
109
+ }
110
+ }
111
+ /**
112
+ * Effect form of {@link StoreDocument.fromSchemaResult}, adding only the
113
+ * `StoreDocument.fromSchema` span. Defined in terms of the `Result`
114
+ * primitive — synchronous callers can use that variant directly.
115
+ */
116
+ static fromSchema = Effect.fn("StoreDocument.fromSchema")((source, options) => Effect.fromResult(StoreDocument.fromSchemaResult(source, options)));
117
+ /**
118
+ * The flat SchemaStore publication shape: `$schema`, `$id`, the root
119
+ * schema's keywords spread at the top level, then the `$defs` pool.
120
+ * `$defs` is omitted when the pool is empty (a deliberate divergence
121
+ * from the extraction source, which always emitted the key).
122
+ */
123
+ toJson() {
124
+ return {
125
+ $schema: this.$schema,
126
+ $id: this.$id,
127
+ ...this.root,
128
+ ...Object.keys(this.defs).length > 0 ? { $defs: this.defs } : {}
129
+ };
130
+ }
131
+ /**
132
+ * Canonical JSON text of {@link StoreDocument.toJson}, via
133
+ * {@link CanonicalJson.serializeResult} — one serializer, so the
134
+ * document and any consumer-serialized value cannot drift.
135
+ */
136
+ serializeResult(options) {
137
+ return CanonicalJson.serializeResult(this.toJson(), options);
138
+ }
139
+ };
140
+
141
+ //#endregion
142
+ export { DRAFT_07_META_SCHEMA, SchemaConversionError, StoreDocument };