@contentrail/core 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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ContentRail contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,144 @@
1
- # Temporary Holding Version
1
+ # `@contentrail/core`
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Framework-independent ContentRail schemas, validation, and inferred content types.
4
+
5
+ This package defines **collections and validation only**. It does not read files, talk to networks, store content, authenticate users, or publish sites.
6
+
7
+ ## Requirements
8
+
9
+ - Node.js **≥ 22** (`engines.node`)
10
+ - TypeScript 5.9+ recommended for consumers
11
+ - [Zod](https://zod.dev) `^3.23` (dependency + peer `zod@^3.23.0`; shipped/tested on **3.25.76**)
12
+
13
+ Prefer `validateEntry` / `validateCollection` / `assertValidEntry` for untrusted
14
+ input. Calling `schema.safeParse()` directly bypasses ContentRail JSON bounds
15
+ (ADR 025).
16
+
17
+ ## Install
18
+
19
+ > **Not on the public npm registry yet.** Packages remain `"private": true` until
20
+ > publication is explicitly authorized. See `docs/PUBLISHING.md`.
21
+
22
+ ### Monorepo (current)
23
+
24
+ ```sh
25
+ pnpm install
26
+ pnpm --filter @contentrail/core build
27
+ ```
28
+
29
+ ### Intended after first public `0.1.0` release
30
+
31
+ ```sh
32
+ pnpm add @contentrail/core zod
33
+ ```
34
+
35
+ ## Define a collection
36
+
37
+ ```ts
38
+ import { defineCollection } from "@contentrail/core";
39
+ import { z } from "zod";
40
+
41
+ const articles = defineCollection({
42
+ name: "articles",
43
+ schema: z.object({
44
+ title: z.string().min(1),
45
+ slug: z.string().min(1),
46
+ published: z.boolean(),
47
+ publishedAt: z.string().optional(),
48
+ }),
49
+ });
50
+ ```
51
+
52
+ Collection names must match `^[a-z][a-z0-9_-]*$`.
53
+
54
+ ### Unexpected fields
55
+
56
+ | `unknownKeys` | Behaviour |
57
+ | ------------------- | ----------------------------------------- |
58
+ | `"strip"` (default) | Unknown root-object keys are removed. |
59
+ | `"strict"` | Unknown root-object keys fail validation. |
60
+
61
+ The policy is applied when the schema is a Zod object. Non-object schemas are left unchanged.
62
+
63
+ ## Validate an entry
64
+
65
+ ```ts
66
+ import { validateEntry } from "@contentrail/core";
67
+
68
+ const result = validateEntry(articles, {
69
+ title: "Welcome",
70
+ slug: "welcome",
71
+ published: true,
72
+ });
73
+
74
+ if (!result.success) {
75
+ console.error(result.error.issues);
76
+ } else {
77
+ console.log(result.data.title);
78
+ }
79
+ ```
80
+
81
+ `validateEntry` never throws for invalid content. Use `assertValidEntry` when you prefer exceptions.
82
+
83
+ ## Validate a collection (array of entries)
84
+
85
+ ```ts
86
+ import { validateCollection } from "@contentrail/core";
87
+
88
+ const result = validateCollection(articles, [
89
+ { title: "Welcome", slug: "welcome", published: true },
90
+ { title: "Next", slug: "next", published: false },
91
+ ]);
92
+ ```
93
+
94
+ Failing issues are prefixed with the entry index, for example `1.title`.
95
+
96
+ ## Type inference
97
+
98
+ ```ts
99
+ import type { InferEntry } from "@contentrail/core";
100
+
101
+ type Article = InferEntry<typeof articles>;
102
+ // { title: string; slug: string; published: boolean; publishedAt?: string }
103
+ ```
104
+
105
+ ## Errors
106
+
107
+ | Error | When |
108
+ | ------------------------ | ----------------------------------------------------------- |
109
+ | `ContentDefinitionError` | Invalid `defineCollection` input (thrown). |
110
+ | `ContentValidationError` | Invalid content (`validate*` result or `assertValidEntry`). |
111
+
112
+ `ContentValidationError` includes:
113
+
114
+ - `collection` — collection name
115
+ - `issues` — `{ path, code, message, expected?, received? }[]`
116
+ - `toJSON()` — stable serializable shape
117
+
118
+ ## Public exports
119
+
120
+ From `@contentrail/core`:
121
+
122
+ - `defineCollection`
123
+ - `validateEntry`
124
+ - `validateCollection`
125
+ - `assertValidEntry`
126
+ - `ContentDefinitionError`
127
+ - `ContentValidationError`
128
+ - Types: `ContentCollection`, `DefineCollectionInput`, `InferEntry`, `UnknownKeysPolicy`, `ValidationResult`, `ValidationSuccess`, `ValidationFailure`, `ValidationIssue`
129
+
130
+ There is a single browser-safe export map entry (`.`). No server-only subpath exists in this package.
131
+
132
+ ## Compatibility
133
+
134
+ - Runtime: any modern ESM environment (browsers, Node, Workers) that can run Zod
135
+ - Module format: ESM only
136
+ - Not coupled to Astro, Vite, Cloudflare, or React
137
+
138
+ ## Current limitations
139
+
140
+ - No content I/O, Git, R2, or publishing APIs
141
+ - No editor schema introspection helpers beyond Zod itself
142
+ - No JSON Schema export
143
+ - Zod 4 is not targeted; peer range is Zod 3.x
144
+ - Unpublished while `"private": true` — see root README / `docs/PUBLISHING.md`
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Structural limits applied **before** Zod validation.
3
+ *
4
+ * Zod 3 (and Zod 4's `zod/v3` entry) can accumulate one validation issue per
5
+ * array element without early termination (GHSA-mrhx-c7xc-m3w5). ContentRail
6
+ * stays on the Zod 3 public schema API; these bounds reduce resource-exhaustion
7
+ * risk at trust boundaries without migrating consumers to Zod 4.
8
+ *
9
+ * Limitations (explicit): these checks bound nesting, array length, and object
10
+ * key count. They do **not** cap string byte length, total serialized size, or
11
+ * CPU spent validating values that are within the structural limits.
12
+ */
13
+ export interface JsonBoundLimits {
14
+ /** Maximum length of any array in the value tree. Default: 10_000. */
15
+ readonly maxArrayLength?: number;
16
+ /** Maximum number of own keys on any object. Default: 10_000. */
17
+ readonly maxObjectKeys?: number;
18
+ /** Maximum nesting depth (root = 0). Default: 32. */
19
+ readonly maxDepth?: number;
20
+ }
21
+ export interface JsonBoundViolation {
22
+ readonly path: ReadonlyArray<string | number>;
23
+ readonly code: "too_big" | "too_deep" | "cyclic";
24
+ readonly message: string;
25
+ }
26
+ /**
27
+ * Walks a JSON-like value and returns the first structural bound violation.
28
+ * Returns `null` when the value is within limits.
29
+ *
30
+ * Cyclic object graphs are rejected (`cyclic`) so the walker itself cannot be
31
+ * forced into unbounded recursion. Primitives (including large strings) are
32
+ * accepted by this helper — size must be constrained by callers (e.g. Worker
33
+ * body limits).
34
+ */
35
+ export declare function findJsonBoundViolation(value: unknown, limits?: JsonBoundLimits): JsonBoundViolation | null;
36
+ //# sourceMappingURL=bounds.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bounds.d.ts","sourceRoot":"","sources":["../src/bounds.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,WAAW,eAAe;IAC9B,sEAAsE;IACtE,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,iEAAiE;IACjE,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,qDAAqD;IACrD,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC;IAC9C,QAAQ,CAAC,IAAI,EAAE,SAAS,GAAG,UAAU,GAAG,QAAQ,CAAC;IACjD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAMD;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,OAAO,EACd,MAAM,GAAE,eAAoB,GAC3B,kBAAkB,GAAG,IAAI,CAc3B"}
package/dist/bounds.js ADDED
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Structural limits applied **before** Zod validation.
3
+ *
4
+ * Zod 3 (and Zod 4's `zod/v3` entry) can accumulate one validation issue per
5
+ * array element without early termination (GHSA-mrhx-c7xc-m3w5). ContentRail
6
+ * stays on the Zod 3 public schema API; these bounds reduce resource-exhaustion
7
+ * risk at trust boundaries without migrating consumers to Zod 4.
8
+ *
9
+ * Limitations (explicit): these checks bound nesting, array length, and object
10
+ * key count. They do **not** cap string byte length, total serialized size, or
11
+ * CPU spent validating values that are within the structural limits.
12
+ */
13
+ const DEFAULT_MAX_ARRAY_LENGTH = 10_000;
14
+ const DEFAULT_MAX_OBJECT_KEYS = 10_000;
15
+ const DEFAULT_MAX_DEPTH = 32;
16
+ /**
17
+ * Walks a JSON-like value and returns the first structural bound violation.
18
+ * Returns `null` when the value is within limits.
19
+ *
20
+ * Cyclic object graphs are rejected (`cyclic`) so the walker itself cannot be
21
+ * forced into unbounded recursion. Primitives (including large strings) are
22
+ * accepted by this helper — size must be constrained by callers (e.g. Worker
23
+ * body limits).
24
+ */
25
+ export function findJsonBoundViolation(value, limits = {}) {
26
+ const maxArrayLength = limits.maxArrayLength ?? DEFAULT_MAX_ARRAY_LENGTH;
27
+ const maxObjectKeys = limits.maxObjectKeys ?? DEFAULT_MAX_OBJECT_KEYS;
28
+ const maxDepth = limits.maxDepth ?? DEFAULT_MAX_DEPTH;
29
+ return walk(value, [], 0, maxArrayLength, maxObjectKeys, maxDepth, new WeakSet());
30
+ }
31
+ function walk(value, path, depth, maxArrayLength, maxObjectKeys, maxDepth, seen) {
32
+ if (depth > maxDepth) {
33
+ return {
34
+ path,
35
+ code: "too_deep",
36
+ message: `Value exceeds maximum nesting depth of ${maxDepth}.`,
37
+ };
38
+ }
39
+ if (value === null || typeof value !== "object") {
40
+ return null;
41
+ }
42
+ if (seen.has(value)) {
43
+ return {
44
+ path,
45
+ code: "cyclic",
46
+ message: "Cyclic object graph is not allowed.",
47
+ };
48
+ }
49
+ seen.add(value);
50
+ if (Array.isArray(value)) {
51
+ if (value.length > maxArrayLength) {
52
+ return {
53
+ path,
54
+ code: "too_big",
55
+ message: `Array length ${value.length} exceeds limit ${maxArrayLength}.`,
56
+ };
57
+ }
58
+ for (let index = 0; index < value.length; index += 1) {
59
+ const nested = walk(value[index], [...path, index], depth + 1, maxArrayLength, maxObjectKeys, maxDepth, seen);
60
+ if (nested !== null) {
61
+ return nested;
62
+ }
63
+ }
64
+ return null;
65
+ }
66
+ const keys = Object.keys(value);
67
+ if (keys.length > maxObjectKeys) {
68
+ return {
69
+ path,
70
+ code: "too_big",
71
+ message: `Object key count ${keys.length} exceeds limit ${maxObjectKeys}.`,
72
+ };
73
+ }
74
+ const record = value;
75
+ for (const key of keys) {
76
+ const nested = walk(record[key], [...path, key], depth + 1, maxArrayLength, maxObjectKeys, maxDepth, seen);
77
+ if (nested !== null) {
78
+ return nested;
79
+ }
80
+ }
81
+ return null;
82
+ }
83
+ //# sourceMappingURL=bounds.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bounds.js","sourceRoot":"","sources":["../src/bounds.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import { type z } from "zod";
2
+ import type { ContentCollection, DefineCollectionInput } from "./types.js";
3
+ /**
4
+ * Defines a content collection bound to a Zod schema.
5
+ * Does not touch storage or publishing — schema and validation only.
6
+ */
7
+ export declare function defineCollection<TSchema extends z.ZodTypeAny>(input: DefineCollectionInput<TSchema>): ContentCollection<TSchema>;
8
+ //# sourceMappingURL=collection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collection.d.ts","sourceRoot":"","sources":["../src/collection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,KAAK,CAAC,EAAE,MAAM,KAAK,CAAC;AAExC,OAAO,KAAK,EACV,iBAAiB,EACjB,qBAAqB,EAEtB,MAAM,YAAY,CAAC;AAIpB;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,SAAS,CAAC,CAAC,UAAU,EAC3D,KAAK,EAAE,qBAAqB,CAAC,OAAO,CAAC,GACpC,iBAAiB,CAAC,OAAO,CAAC,CA4C5B"}
@@ -0,0 +1,45 @@
1
+ import { ZodObject } from "zod";
2
+ import { ContentDefinitionError } from "./errors.js";
3
+ const COLLECTION_NAME_PATTERN = /^[a-z][a-z0-9_-]*$/;
4
+ /**
5
+ * Defines a content collection bound to a Zod schema.
6
+ * Does not touch storage or publishing — schema and validation only.
7
+ */
8
+ export function defineCollection(input) {
9
+ if (input === null || typeof input !== "object") {
10
+ throw new ContentDefinitionError("Collection definition must be a non-null object.");
11
+ }
12
+ const name = input.name;
13
+ if (typeof name !== "string" || name.trim() === "") {
14
+ throw new ContentDefinitionError('Collection "name" must be a non-empty string.');
15
+ }
16
+ if (!COLLECTION_NAME_PATTERN.test(name)) {
17
+ throw new ContentDefinitionError(`Collection "name" must match ${COLLECTION_NAME_PATTERN}: received "${name}".`);
18
+ }
19
+ if (input.schema === undefined || input.schema === null) {
20
+ throw new ContentDefinitionError(`Collection "${name}" requires a Zod "schema".`);
21
+ }
22
+ if (typeof input.schema.safeParse !== "function") {
23
+ throw new ContentDefinitionError(`Collection "${name}" "schema" must be a Zod schema with safeParse().`);
24
+ }
25
+ const unknownKeys = input.unknownKeys ?? "strip";
26
+ if (unknownKeys !== "strip" && unknownKeys !== "strict") {
27
+ throw new ContentDefinitionError(`Collection "${name}" "unknownKeys" must be "strip" or "strict".`);
28
+ }
29
+ const schema = applyUnknownKeysPolicy(input.schema, unknownKeys);
30
+ return Object.freeze({
31
+ name,
32
+ schema,
33
+ unknownKeys,
34
+ });
35
+ }
36
+ function applyUnknownKeysPolicy(schema, policy) {
37
+ if (!(schema instanceof ZodObject)) {
38
+ return schema;
39
+ }
40
+ if (policy === "strict") {
41
+ return schema.strict();
42
+ }
43
+ return schema.strip();
44
+ }
45
+ //# sourceMappingURL=collection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collection.js","sourceRoot":"","sources":["../src/collection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAU,MAAM,KAAK,CAAC;AACxC,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAOrD,MAAM,uBAAuB,GAAG,oBAAoB,CAAC;AAErD;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAC9B,KAAqC;IAErC,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAChD,MAAM,IAAI,sBAAsB,CAC9B,kDAAkD,CACnD,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;IACxB,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QACnD,MAAM,IAAI,sBAAsB,CAC9B,+CAA+C,CAChD,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,uBAAuB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACxC,MAAM,IAAI,sBAAsB,CAC9B,gCAAgC,uBAAuB,eAAe,IAAI,IAAI,CAC/E,CAAC;IACJ,CAAC;IAED,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS,IAAI,KAAK,CAAC,MAAM,KAAK,IAAI,EAAE,CAAC;QACxD,MAAM,IAAI,sBAAsB,CAC9B,eAAe,IAAI,4BAA4B,CAChD,CAAC;IACJ,CAAC;IACD,IAAI,OAAO,KAAK,CAAC,MAAM,CAAC,SAAS,KAAK,UAAU,EAAE,CAAC;QACjD,MAAM,IAAI,sBAAsB,CAC9B,eAAe,IAAI,mDAAmD,CACvE,CAAC;IACJ,CAAC;IAED,MAAM,WAAW,GAAsB,KAAK,CAAC,WAAW,IAAI,OAAO,CAAC;IACpE,IAAI,WAAW,KAAK,OAAO,IAAI,WAAW,KAAK,QAAQ,EAAE,CAAC;QACxD,MAAM,IAAI,sBAAsB,CAC9B,eAAe,IAAI,8CAA8C,CAClE,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,sBAAsB,CAAC,KAAK,CAAC,MAAM,EAAE,WAAW,CAAY,CAAC;IAE5E,OAAO,MAAM,CAAC,MAAM,CAAC;QACnB,IAAI;QACJ,MAAM;QACN,WAAW;KACZ,CAAC,CAAC;AACL,CAAC;AAED,SAAS,sBAAsB,CAC7B,MAAe,EACf,MAAyB;IAEzB,IAAI,CAAC,CAAC,MAAM,YAAY,SAAS,CAAC,EAAE,CAAC;QACnC,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,IAAI,MAAM,KAAK,QAAQ,EAAE,CAAC;QACxB,OAAO,MAAM,CAAC,MAAM,EAAE,CAAC;IACzB,CAAC;IAED,OAAO,MAAM,CAAC,KAAK,EAAE,CAAC;AACxB,CAAC"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * A single structured validation issue.
3
+ * Paths use Zod-style segments (string keys and numeric array indexes).
4
+ */
5
+ export interface ValidationIssue {
6
+ readonly path: ReadonlyArray<string | number>;
7
+ readonly code: string;
8
+ readonly message: string;
9
+ readonly expected?: string;
10
+ readonly received?: string;
11
+ }
12
+ export declare class ContentDefinitionError extends Error {
13
+ readonly code: "CONTENT_DEFINITION_ERROR";
14
+ constructor(message: string);
15
+ }
16
+ export declare class ContentValidationError extends Error {
17
+ readonly code: "CONTENT_VALIDATION_ERROR";
18
+ readonly collection: string;
19
+ readonly issues: readonly ValidationIssue[];
20
+ constructor(collection: string, issues: readonly ValidationIssue[]);
21
+ toJSON(): {
22
+ name: string;
23
+ code: "CONTENT_VALIDATION_ERROR";
24
+ collection: string;
25
+ message: string;
26
+ issues: readonly ValidationIssue[];
27
+ };
28
+ }
29
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC;IAC9C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,qBAAa,sBAAuB,SAAQ,KAAK;IAC/C,QAAQ,CAAC,IAAI,EAAG,0BAA0B,CAAU;gBAExC,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,sBAAuB,SAAQ,KAAK;IAC/C,QAAQ,CAAC,IAAI,EAAG,0BAA0B,CAAU;IACpD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,SAAS,eAAe,EAAE,CAAC;gBAEhC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,eAAe,EAAE;IAWlE,MAAM,IAAI;QACR,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,0BAA0B,CAAC;QACjC,UAAU,EAAE,MAAM,CAAC;QACnB,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,SAAS,eAAe,EAAE,CAAC;KACpC;CASF"}
package/dist/errors.js ADDED
@@ -0,0 +1,35 @@
1
+ export class ContentDefinitionError extends Error {
2
+ code = "CONTENT_DEFINITION_ERROR";
3
+ constructor(message) {
4
+ super(message);
5
+ this.name = "ContentDefinitionError";
6
+ }
7
+ }
8
+ export class ContentValidationError extends Error {
9
+ code = "CONTENT_VALIDATION_ERROR";
10
+ collection;
11
+ issues;
12
+ constructor(collection, issues) {
13
+ const summary = issues.length === 1
14
+ ? formatIssue(issues[0])
15
+ : `${issues.length} validation issues`;
16
+ super(`Invalid content for collection "${collection}": ${summary}`);
17
+ this.name = "ContentValidationError";
18
+ this.collection = collection;
19
+ this.issues = issues;
20
+ }
21
+ toJSON() {
22
+ return {
23
+ name: this.name,
24
+ code: this.code,
25
+ collection: this.collection,
26
+ message: this.message,
27
+ issues: this.issues,
28
+ };
29
+ }
30
+ }
31
+ function formatIssue(issue) {
32
+ const path = issue.path.length > 0 ? issue.path.join(".") : "(root)";
33
+ return `${path}: ${issue.message}`;
34
+ }
35
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAYA,MAAM,OAAO,sBAAuB,SAAQ,KAAK;IACtC,IAAI,GAAG,0BAAmC,CAAC;IAEpD,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAC;IACvC,CAAC;CACF;AAED,MAAM,OAAO,sBAAuB,SAAQ,KAAK;IACtC,IAAI,GAAG,0BAAmC,CAAC;IAC3C,UAAU,CAAS;IACnB,MAAM,CAA6B;IAE5C,YAAY,UAAkB,EAAE,MAAkC;QAChE,MAAM,OAAO,GACX,MAAM,CAAC,MAAM,KAAK,CAAC;YACjB,CAAC,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAE,CAAC;YACzB,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,oBAAoB,CAAC;QAC3C,KAAK,CAAC,mCAAmC,UAAU,MAAM,OAAO,EAAE,CAAC,CAAC;QACpE,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAC;QACrC,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;IAED,MAAM;QAOJ,OAAO;YACL,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,UAAU,EAAE,IAAI,CAAC,UAAU;YAC3B,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,MAAM,EAAE,IAAI,CAAC,MAAM;SACpB,CAAC;IACJ,CAAC;CACF;AAED,SAAS,WAAW,CAAC,KAAsB;IACzC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;IACrE,OAAO,GAAG,IAAI,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC;AACrC,CAAC"}
@@ -0,0 +1,6 @@
1
+ export { findJsonBoundViolation, type JsonBoundLimits, type JsonBoundViolation, } from "./bounds.js";
2
+ export { defineCollection } from "./collection.js";
3
+ export { ContentDefinitionError, ContentValidationError, type ValidationIssue, } from "./errors.js";
4
+ export type { ContentCollection, DefineCollectionInput, InferEntry, UnknownKeysPolicy, ValidationFailure, ValidationResult, ValidationSuccess, } from "./types.js";
5
+ export { DEFAULT_MAX_COLLECTION_ENTRIES, assertValidEntry, validateCollection, validateEntry, type ValidateCollectionOptions, type ValidateEntryOptions, } from "./validate.js";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,KAAK,eAAe,EACpB,KAAK,kBAAkB,GACxB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EACL,sBAAsB,EACtB,sBAAsB,EACtB,KAAK,eAAe,GACrB,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,iBAAiB,EACjB,qBAAqB,EACrB,UAAU,EACV,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,8BAA8B,EAC9B,gBAAgB,EAChB,kBAAkB,EAClB,aAAa,EACb,KAAK,yBAAyB,EAC9B,KAAK,oBAAoB,GAC1B,MAAM,eAAe,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export { findJsonBoundViolation, } from "./bounds.js";
2
+ export { defineCollection } from "./collection.js";
3
+ export { ContentDefinitionError, ContentValidationError, } from "./errors.js";
4
+ export { DEFAULT_MAX_COLLECTION_ENTRIES, assertValidEntry, validateCollection, validateEntry, } from "./validate.js";
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,GAGvB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EACL,sBAAsB,EACtB,sBAAsB,GAEvB,MAAM,aAAa,CAAC;AAUrB,OAAO,EACL,8BAA8B,EAC9B,gBAAgB,EAChB,kBAAkB,EAClB,aAAa,GAGd,MAAM,eAAe,CAAC"}
@@ -0,0 +1,40 @@
1
+ import type { z } from "zod";
2
+ import type { ContentValidationError } from "./errors.js";
3
+ /** How root object schemas treat keys not declared in the schema. */
4
+ export type UnknownKeysPolicy = "strip" | "strict";
5
+ export interface DefineCollectionInput<TSchema extends z.ZodTypeAny> {
6
+ /** Stable collection identifier used in errors and tooling. */
7
+ readonly name: string;
8
+ /** Zod schema describing one content entry. */
9
+ readonly schema: TSchema;
10
+ /**
11
+ * Unexpected root-object field policy.
12
+ * - `strip` (default): remove unknown keys (Zod object default).
13
+ * - `strict`: reject unknown keys.
14
+ *
15
+ * Applies only when `schema` is a Zod object schema (including `.strict()` /
16
+ * `.passthrough()` wrappers replaced according to this policy).
17
+ */
18
+ readonly unknownKeys?: UnknownKeysPolicy;
19
+ }
20
+ /**
21
+ * A defined content collection. This is a schema-bound definition only —
22
+ * it does not read, write, or publish content.
23
+ */
24
+ export interface ContentCollection<TSchema extends z.ZodTypeAny = z.ZodTypeAny> {
25
+ readonly name: string;
26
+ readonly schema: TSchema;
27
+ readonly unknownKeys: UnknownKeysPolicy;
28
+ }
29
+ /** Inferred entry type for a collection. */
30
+ export type InferEntry<TCollection extends ContentCollection> = z.infer<TCollection["schema"]>;
31
+ export type ValidationSuccess<T> = {
32
+ readonly success: true;
33
+ readonly data: T;
34
+ };
35
+ export type ValidationFailure = {
36
+ readonly success: false;
37
+ readonly error: ContentValidationError;
38
+ };
39
+ export type ValidationResult<T> = ValidationSuccess<T> | ValidationFailure;
40
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAE1D,qEAAqE;AACrE,MAAM,MAAM,iBAAiB,GAAG,OAAO,GAAG,QAAQ,CAAC;AAEnD,MAAM,WAAW,qBAAqB,CAAC,OAAO,SAAS,CAAC,CAAC,UAAU;IACjE,+DAA+D;IAC/D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,+CAA+C;IAC/C,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB;;;;;;;OAOG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,iBAAiB,CAAC;CAC1C;AAED;;;GAGG;AACH,MAAM,WAAW,iBAAiB,CAChC,OAAO,SAAS,CAAC,CAAC,UAAU,GAAG,CAAC,CAAC,UAAU;IAE3C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,WAAW,EAAE,iBAAiB,CAAC;CACzC;AAED,4CAA4C;AAC5C,MAAM,MAAM,UAAU,CAAC,WAAW,SAAS,iBAAiB,IAAI,CAAC,CAAC,KAAK,CACrE,WAAW,CAAC,QAAQ,CAAC,CACtB,CAAC;AAEF,MAAM,MAAM,iBAAiB,CAAC,CAAC,IAAI;IACjC,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,sBAAsB,CAAC;CACxC,CAAC;AAEF,MAAM,MAAM,gBAAgB,CAAC,CAAC,IAAI,iBAAiB,CAAC,CAAC,CAAC,GAAG,iBAAiB,CAAC"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,27 @@
1
+ import { type JsonBoundLimits } from "./bounds.js";
2
+ import type { ContentCollection, InferEntry, ValidationResult } from "./types.js";
3
+ /** Default cap for `validateCollection` input length (entries, not nested fields). */
4
+ export declare const DEFAULT_MAX_COLLECTION_ENTRIES = 10000;
5
+ export interface ValidateEntryOptions {
6
+ readonly bounds?: JsonBoundLimits;
7
+ }
8
+ export interface ValidateCollectionOptions extends ValidateEntryOptions {
9
+ /** Maximum number of top-level entries. Default: 10_000. */
10
+ readonly maxEntries?: number;
11
+ }
12
+ /**
13
+ * Validates a single content entry against the collection schema.
14
+ * Never throws for invalid content — returns a structured result.
15
+ *
16
+ * Structural JSON bounds run before Zod so oversized nested arrays/objects
17
+ * cannot drive unbounded issue accumulation in Zod 3 (GHSA-mrhx-c7xc-m3w5).
18
+ */
19
+ export declare function validateEntry<TCollection extends ContentCollection>(collection: TCollection, entry: unknown, options?: ValidateEntryOptions): ValidationResult<InferEntry<TCollection>>;
20
+ /**
21
+ * Validates an ordered list of entries for a collection.
22
+ * Aggregates issues from every failing entry; paths are prefixed with the index.
23
+ */
24
+ export declare function validateCollection<TCollection extends ContentCollection>(collection: TCollection, entries: unknown, options?: ValidateCollectionOptions): ValidationResult<Array<InferEntry<TCollection>>>;
25
+ /** Narrow helper for callers that prefer throwing on failure. */
26
+ export declare function assertValidEntry<TCollection extends ContentCollection>(collection: TCollection, entry: unknown, options?: ValidateEntryOptions): InferEntry<TCollection>;
27
+ //# sourceMappingURL=validate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AACA,OAAO,EAA0B,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AAE3E,OAAO,KAAK,EACV,iBAAiB,EACjB,UAAU,EACV,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAEpB,sFAAsF;AACtF,eAAO,MAAM,8BAA8B,QAAS,CAAC;AAErD,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,MAAM,CAAC,EAAE,eAAe,CAAC;CACnC;AAED,MAAM,WAAW,yBAA0B,SAAQ,oBAAoB;IACrE,4DAA4D;IAC5D,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,WAAW,SAAS,iBAAiB,EACjE,UAAU,EAAE,WAAW,EACvB,KAAK,EAAE,OAAO,EACd,OAAO,GAAE,oBAAyB,GACjC,gBAAgB,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC,CAgC3C;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,SAAS,iBAAiB,EACtE,UAAU,EAAE,WAAW,EACvB,OAAO,EAAE,OAAO,EAChB,OAAO,GAAE,yBAA8B,GACtC,gBAAgB,CAAC,KAAK,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC,CA+DlD;AA0DD,iEAAiE;AACjE,wBAAgB,gBAAgB,CAAC,WAAW,SAAS,iBAAiB,EACpE,UAAU,EAAE,WAAW,EACvB,KAAK,EAAE,OAAO,EACd,OAAO,GAAE,oBAAyB,GACjC,UAAU,CAAC,WAAW,CAAC,CAMzB"}
@@ -0,0 +1,151 @@
1
+ import { findJsonBoundViolation } from "./bounds.js";
2
+ import { ContentValidationError } from "./errors.js";
3
+ /** Default cap for `validateCollection` input length (entries, not nested fields). */
4
+ export const DEFAULT_MAX_COLLECTION_ENTRIES = 10_000;
5
+ /**
6
+ * Validates a single content entry against the collection schema.
7
+ * Never throws for invalid content — returns a structured result.
8
+ *
9
+ * Structural JSON bounds run before Zod so oversized nested arrays/objects
10
+ * cannot drive unbounded issue accumulation in Zod 3 (GHSA-mrhx-c7xc-m3w5).
11
+ */
12
+ export function validateEntry(collection, entry, options = {}) {
13
+ assertCollection(collection);
14
+ const bound = findJsonBoundViolation(entry, options.bounds);
15
+ if (bound !== null) {
16
+ return {
17
+ success: false,
18
+ error: new ContentValidationError(collection.name, [
19
+ {
20
+ path: [...bound.path],
21
+ code: bound.code,
22
+ message: bound.message,
23
+ },
24
+ ]),
25
+ };
26
+ }
27
+ const parsed = collection.schema.safeParse(entry);
28
+ if (parsed.success) {
29
+ return {
30
+ success: true,
31
+ data: parsed.data,
32
+ };
33
+ }
34
+ return {
35
+ success: false,
36
+ error: new ContentValidationError(collection.name, parsed.error.issues.map(toValidationIssue)),
37
+ };
38
+ }
39
+ /**
40
+ * Validates an ordered list of entries for a collection.
41
+ * Aggregates issues from every failing entry; paths are prefixed with the index.
42
+ */
43
+ export function validateCollection(collection, entries, options = {}) {
44
+ assertCollection(collection);
45
+ if (!Array.isArray(entries)) {
46
+ return {
47
+ success: false,
48
+ error: new ContentValidationError(collection.name, [
49
+ {
50
+ path: [],
51
+ code: "invalid_type",
52
+ message: "Collection entries must be an array.",
53
+ expected: "array",
54
+ received: typeName(entries),
55
+ },
56
+ ]),
57
+ };
58
+ }
59
+ const maxEntries = options.maxEntries ?? DEFAULT_MAX_COLLECTION_ENTRIES;
60
+ if (entries.length > maxEntries) {
61
+ return {
62
+ success: false,
63
+ error: new ContentValidationError(collection.name, [
64
+ {
65
+ path: [],
66
+ code: "too_big",
67
+ message: `Collection has ${entries.length} entries; limit is ${maxEntries}.`,
68
+ },
69
+ ]),
70
+ };
71
+ }
72
+ const data = [];
73
+ const issues = [];
74
+ for (let index = 0; index < entries.length; index += 1) {
75
+ const entryOptions = options.bounds === undefined ? {} : { bounds: options.bounds };
76
+ const result = validateEntry(collection, entries[index], entryOptions);
77
+ if (result.success) {
78
+ data.push(result.data);
79
+ continue;
80
+ }
81
+ for (const issue of result.error.issues) {
82
+ issues.push({
83
+ path: [index, ...issue.path],
84
+ code: issue.code,
85
+ message: issue.message,
86
+ ...(issue.expected !== undefined ? { expected: issue.expected } : {}),
87
+ ...(issue.received !== undefined ? { received: issue.received } : {}),
88
+ });
89
+ }
90
+ }
91
+ if (issues.length > 0) {
92
+ return {
93
+ success: false,
94
+ error: new ContentValidationError(collection.name, issues),
95
+ };
96
+ }
97
+ return { success: true, data };
98
+ }
99
+ function assertCollection(collection) {
100
+ if (collection === null ||
101
+ typeof collection !== "object" ||
102
+ typeof collection.name !== "string" ||
103
+ collection.schema === undefined ||
104
+ typeof collection.schema.safeParse !== "function") {
105
+ throw new TypeError("validateEntry/validateCollection require a ContentCollection from defineCollection().");
106
+ }
107
+ }
108
+ function toValidationIssue(issue) {
109
+ const base = {
110
+ path: issue.path.map((segment) => {
111
+ if (typeof segment === "string" || typeof segment === "number") {
112
+ return segment;
113
+ }
114
+ return String(segment);
115
+ }),
116
+ code: issue.code,
117
+ message: issue.message,
118
+ };
119
+ if (issue.code === "invalid_type") {
120
+ return {
121
+ ...base,
122
+ expected: issue.expected,
123
+ received: issue.received,
124
+ };
125
+ }
126
+ if (issue.code === "unrecognized_keys") {
127
+ return {
128
+ ...base,
129
+ received: issue.keys.join(", "),
130
+ };
131
+ }
132
+ return base;
133
+ }
134
+ function typeName(value) {
135
+ if (value === null) {
136
+ return "null";
137
+ }
138
+ if (Array.isArray(value)) {
139
+ return "array";
140
+ }
141
+ return typeof value;
142
+ }
143
+ /** Narrow helper for callers that prefer throwing on failure. */
144
+ export function assertValidEntry(collection, entry, options = {}) {
145
+ const result = validateEntry(collection, entry, options);
146
+ if (!result.success) {
147
+ throw result.error;
148
+ }
149
+ return result.data;
150
+ }
151
+ //# sourceMappingURL=validate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.js","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,52 @@
1
1
  {
2
2
  "name": "@contentrail/core",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "type": "module",
6
+ "description": "Framework-independent ContentRail schemas, validation, and content types.",
7
+ "license": "MIT",
8
+ "engines": {
9
+ "node": ">=22"
10
+ },
11
+ "sideEffects": false,
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.js"
16
+ },
17
+ "./package.json": "./package.json"
18
+ },
19
+ "files": [
20
+ "dist",
21
+ "README.md",
22
+ "LICENSE"
23
+ ],
24
+ "dependencies": {
25
+ "zod": "^3.25.76"
26
+ },
27
+ "peerDependencies": {
28
+ "zod": "^3.23.0"
29
+ },
30
+ "devDependencies": {
31
+ "@types/node": "^22.20.5",
32
+ "typescript": "5.9.3",
33
+ "zod": "3.25.76"
34
+ },
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "https://github.com/Hridhin-k/contentrail.git",
38
+ "directory": "packages/core"
39
+ },
40
+ "bugs": {
41
+ "url": "https://github.com/Hridhin-k/contentrail/issues"
42
+ },
43
+ "homepage": "https://github.com/Hridhin-k/contentrail#readme",
44
+ "publishConfig": {
45
+ "access": "public"
46
+ },
47
+ "scripts": {
48
+ "build": "tsc -p tsconfig.json",
49
+ "typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.type-tests.json",
50
+ "test": "pnpm run build && tsc -p tsconfig.test.json && node --test test-dist/**/*.test.js"
51
+ }
6
52
  }