@contentrail/core 0.0.0-stage → 0.2.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,165 @@
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) `^4.6.7` (dependency + peer; native `zod` entry — not `zod/v3`)
12
+
13
+ Prefer `validateEntry` / `validateCollection` / `assertValidEntry` for untrusted
14
+ input. Calling `schema.safeParse()` directly bypasses ContentRail JSON bounds.
15
+
16
+ Upgrading from ContentRail `0.1.0` (Zod 3): see `docs/MIGRATION_ZOD4.md`.
17
+
18
+ ## Install
19
+
20
+ ### Monorepo (current)
21
+
22
+ ```sh
23
+ pnpm install
24
+ pnpm --filter @contentrail/core build
25
+ ```
26
+
27
+ ### From npm (upcoming `0.2.0` after authorized publish)
28
+
29
+ ```sh
30
+ pnpm add @contentrail/core@0.2.0 zod@^4.6.7
31
+ ```
32
+
33
+ ## Define a collection
34
+
35
+ ```ts
36
+ import { defineCollection } from "@contentrail/core";
37
+ import { z } from "zod";
38
+
39
+ const articles = defineCollection({
40
+ name: "articles",
41
+ schema: z.object({
42
+ title: z.string().min(1),
43
+ slug: z.string().min(1),
44
+ published: z.boolean(),
45
+ publishedAt: z.string().optional(),
46
+ }),
47
+ });
48
+ ```
49
+
50
+ Collection names must match `^[a-z][a-z0-9_-]*$`.
51
+
52
+ ### Unexpected fields
53
+
54
+ | `unknownKeys` | Behaviour |
55
+ | ------------------- | ----------------------------------------- |
56
+ | `"strip"` (default) | Unknown root-object keys are removed. |
57
+ | `"strict"` | Unknown root-object keys fail validation. |
58
+
59
+ The policy is applied when the schema is a Zod object. Non-object schemas are left unchanged.
60
+
61
+ ## Validate an entry
62
+
63
+ ```ts
64
+ import { validateEntry } from "@contentrail/core";
65
+
66
+ const result = validateEntry(articles, {
67
+ title: "Welcome",
68
+ slug: "welcome",
69
+ published: true,
70
+ });
71
+
72
+ if (!result.success) {
73
+ console.error(result.error.issues);
74
+ } else {
75
+ console.log(result.data.title);
76
+ }
77
+ ```
78
+
79
+ `validateEntry` never throws for invalid content. Use `assertValidEntry` when you prefer exceptions.
80
+
81
+ ## Validate a collection (array of entries)
82
+
83
+ ```ts
84
+ import { validateCollection } from "@contentrail/core";
85
+
86
+ const result = validateCollection(articles, [
87
+ { title: "Welcome", slug: "welcome", published: true },
88
+ { title: "Next", slug: "next", published: false },
89
+ ]);
90
+ ```
91
+
92
+ Failing issues are prefixed with the entry index, for example `1.title`.
93
+
94
+ ## Type inference
95
+
96
+ ```ts
97
+ import type { InferEntry } from "@contentrail/core";
98
+
99
+ type Article = InferEntry<typeof articles>;
100
+ // { title: string; slug: string; published: boolean; publishedAt?: string }
101
+ ```
102
+
103
+ ## `ContentSchema`
104
+
105
+ `ContentSchema` is the public Zod 4 schema constraint used by
106
+ `defineCollection` / `ContentCollection` generics. It is an alias of
107
+ `z.ZodType` from the native `zod` entry:
108
+
109
+ ```ts
110
+ import { defineCollection, type ContentSchema } from "@contentrail/core";
111
+ import { z } from "zod";
112
+
113
+ const schema: ContentSchema = z.object({
114
+ title: z.string().min(1),
115
+ });
116
+
117
+ const pages = defineCollection({
118
+ name: "pages",
119
+ schema,
120
+ });
121
+ ```
122
+
123
+ Prefer defining schemas with `z` and letting `defineCollection` infer types;
124
+ use `ContentSchema` when you need an explicit annotation.
125
+
126
+ ## Errors
127
+
128
+ | Error | When |
129
+ | ------------------------ | ----------------------------------------------------------- |
130
+ | `ContentDefinitionError` | Invalid `defineCollection` input (thrown). |
131
+ | `ContentValidationError` | Invalid content (`validate*` result or `assertValidEntry`). |
132
+
133
+ `ContentValidationError` includes:
134
+
135
+ - `collection` — collection name
136
+ - `issues` — `{ path, code, message, expected?, received? }[]`
137
+ - `toJSON()` — stable serializable shape
138
+
139
+ ## Public exports
140
+
141
+ From `@contentrail/core`:
142
+
143
+ - `defineCollection`
144
+ - `validateEntry`
145
+ - `validateCollection`
146
+ - `assertValidEntry`
147
+ - `ContentDefinitionError`
148
+ - `ContentValidationError`
149
+ - Types: `ContentSchema`, `ContentCollection`, `DefineCollectionInput`, `InferEntry`, `UnknownKeysPolicy`, `ValidationResult`, `ValidationSuccess`, `ValidationFailure`, `ValidationIssue`
150
+
151
+ There is a single browser-safe export map entry (`.`). No server-only subpath exists in this package.
152
+
153
+ ## Compatibility
154
+
155
+ - Runtime: any modern ESM environment (browsers, Node, Workers) that can run Zod
156
+ - Module format: ESM only
157
+ - Not coupled to Astro, Vite, Cloudflare, or React
158
+
159
+ ## Current limitations
160
+
161
+ - No content I/O, Git, R2, or publishing APIs
162
+ - No editor schema introspection helpers beyond Zod itself
163
+ - No JSON Schema export
164
+ - Requires Zod **4.6.7+**; Zod 3 schemas are not supported
165
+ - See root README / `docs/PUBLISHING.md` for `0.2.0` publication status
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Structural limits applied **before** Zod validation.
3
+ *
4
+ * ContentRail's public schema API targets Zod **4.6.7+** (native `zod` entry),
5
+ * which remediates GHSA-mrhx-c7xc-m3w5. These bounds remain as defense-in-depth
6
+ * at trust boundaries and provide stable ContentRail error codes (`too_big`,
7
+ * `too_deep`, `cyclic`) independent of Zod issue shaping.
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
+ * ContentRail's public schema API targets Zod **4.6.7+** (native `zod` entry),
5
+ * which remediates GHSA-mrhx-c7xc-m3w5. These bounds remain as defense-in-depth
6
+ * at trust boundaries and provide stable ContentRail error codes (`too_big`,
7
+ * `too_deep`, `cyclic`) independent of Zod issue shaping.
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,7 @@
1
+ import type { ContentCollection, ContentSchema, DefineCollectionInput } from "./types.js";
2
+ /**
3
+ * Defines a content collection bound to a Zod schema.
4
+ * Does not touch storage or publishing — schema and validation only.
5
+ */
6
+ export declare function defineCollection<TSchema extends ContentSchema>(input: DefineCollectionInput<TSchema>): ContentCollection<TSchema>;
7
+ //# sourceMappingURL=collection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collection.d.ts","sourceRoot":"","sources":["../src/collection.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,iBAAiB,EACjB,aAAa,EACb,qBAAqB,EAEtB,MAAM,YAAY,CAAC;AAIpB;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,SAAS,aAAa,EAC5D,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,EAAE,MAAM,KAAK,CAAC;AAChC,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAQrD,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, ContentSchema, 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,aAAa,EACb,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;AAWrB,OAAO,EACL,8BAA8B,EAC9B,gBAAgB,EAChB,kBAAkB,EAClB,aAAa,GAGd,MAAM,eAAe,CAAC"}
@@ -0,0 +1,42 @@
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
+ /** Zod 4 schema constraint used across ContentRail public generics. */
6
+ export type ContentSchema = z.ZodType;
7
+ export interface DefineCollectionInput<TSchema extends ContentSchema> {
8
+ /** Stable collection identifier used in errors and tooling. */
9
+ readonly name: string;
10
+ /** Zod schema describing one content entry. */
11
+ readonly schema: TSchema;
12
+ /**
13
+ * Unexpected root-object field policy.
14
+ * - `strip` (default): remove unknown keys (Zod object default).
15
+ * - `strict`: reject unknown keys.
16
+ *
17
+ * Applies only when `schema` is a Zod object schema (including `.strict()` /
18
+ * `.passthrough()` wrappers replaced according to this policy).
19
+ */
20
+ readonly unknownKeys?: UnknownKeysPolicy;
21
+ }
22
+ /**
23
+ * A defined content collection. This is a schema-bound definition only —
24
+ * it does not read, write, or publish content.
25
+ */
26
+ export interface ContentCollection<TSchema extends ContentSchema = ContentSchema> {
27
+ readonly name: string;
28
+ readonly schema: TSchema;
29
+ readonly unknownKeys: UnknownKeysPolicy;
30
+ }
31
+ /** Inferred entry type for a collection. */
32
+ export type InferEntry<TCollection extends ContentCollection> = z.infer<TCollection["schema"]>;
33
+ export type ValidationSuccess<T> = {
34
+ readonly success: true;
35
+ readonly data: T;
36
+ };
37
+ export type ValidationFailure = {
38
+ readonly success: false;
39
+ readonly error: ContentValidationError;
40
+ };
41
+ export type ValidationResult<T> = ValidationSuccess<T> | ValidationFailure;
42
+ //# 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,uEAAuE;AACvE,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,OAAO,CAAC;AAEtC,MAAM,WAAW,qBAAqB,CAAC,OAAO,SAAS,aAAa;IAClE,+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,aAAa,GAAG,aAAa;IAE7C,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,28 @@
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 as defense-in-depth against oversized
17
+ * nested arrays/objects (GHSA-mrhx-c7xc-m3w5 was fixed in Zod 4.6.6+; bounds
18
+ * remain for defense-in-depth and consistent ContentRail error codes).
19
+ */
20
+ export declare function validateEntry<TCollection extends ContentCollection>(collection: TCollection, entry: unknown, options?: ValidateEntryOptions): ValidationResult<InferEntry<TCollection>>;
21
+ /**
22
+ * Validates an ordered list of entries for a collection.
23
+ * Aggregates issues from every failing entry; paths are prefixed with the index.
24
+ */
25
+ export declare function validateCollection<TCollection extends ContentCollection>(collection: TCollection, entries: unknown, options?: ValidateCollectionOptions): ValidationResult<Array<InferEntry<TCollection>>>;
26
+ /** Narrow helper for callers that prefer throwing on failure. */
27
+ export declare function assertValidEntry<TCollection extends ContentCollection>(collection: TCollection, entry: unknown, options?: ValidateEntryOptions): InferEntry<TCollection>;
28
+ //# 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;;;;;;;GAOG;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;AA6ED,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,170 @@
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 as defense-in-depth against oversized
10
+ * nested arrays/objects (GHSA-mrhx-c7xc-m3w5 was fixed in Zod 4.6.6+; bounds
11
+ * remain for defense-in-depth and consistent ContentRail error codes).
12
+ */
13
+ export function validateEntry(collection, entry, options = {}) {
14
+ assertCollection(collection);
15
+ const bound = findJsonBoundViolation(entry, options.bounds);
16
+ if (bound !== null) {
17
+ return {
18
+ success: false,
19
+ error: new ContentValidationError(collection.name, [
20
+ {
21
+ path: [...bound.path],
22
+ code: bound.code,
23
+ message: bound.message,
24
+ },
25
+ ]),
26
+ };
27
+ }
28
+ const parsed = collection.schema.safeParse(entry);
29
+ if (parsed.success) {
30
+ return {
31
+ success: true,
32
+ data: parsed.data,
33
+ };
34
+ }
35
+ return {
36
+ success: false,
37
+ error: new ContentValidationError(collection.name, parsed.error.issues.map(toValidationIssue)),
38
+ };
39
+ }
40
+ /**
41
+ * Validates an ordered list of entries for a collection.
42
+ * Aggregates issues from every failing entry; paths are prefixed with the index.
43
+ */
44
+ export function validateCollection(collection, entries, options = {}) {
45
+ assertCollection(collection);
46
+ if (!Array.isArray(entries)) {
47
+ return {
48
+ success: false,
49
+ error: new ContentValidationError(collection.name, [
50
+ {
51
+ path: [],
52
+ code: "invalid_type",
53
+ message: "Collection entries must be an array.",
54
+ expected: "array",
55
+ received: typeName(entries),
56
+ },
57
+ ]),
58
+ };
59
+ }
60
+ const maxEntries = options.maxEntries ?? DEFAULT_MAX_COLLECTION_ENTRIES;
61
+ if (entries.length > maxEntries) {
62
+ return {
63
+ success: false,
64
+ error: new ContentValidationError(collection.name, [
65
+ {
66
+ path: [],
67
+ code: "too_big",
68
+ message: `Collection has ${entries.length} entries; limit is ${maxEntries}.`,
69
+ },
70
+ ]),
71
+ };
72
+ }
73
+ const data = [];
74
+ const issues = [];
75
+ for (let index = 0; index < entries.length; index += 1) {
76
+ const entryOptions = options.bounds === undefined ? {} : { bounds: options.bounds };
77
+ const result = validateEntry(collection, entries[index], entryOptions);
78
+ if (result.success) {
79
+ data.push(result.data);
80
+ continue;
81
+ }
82
+ for (const issue of result.error.issues) {
83
+ issues.push({
84
+ path: [index, ...issue.path],
85
+ code: issue.code,
86
+ message: issue.message,
87
+ ...(issue.expected !== undefined ? { expected: issue.expected } : {}),
88
+ ...(issue.received !== undefined ? { received: issue.received } : {}),
89
+ });
90
+ }
91
+ }
92
+ if (issues.length > 0) {
93
+ return {
94
+ success: false,
95
+ error: new ContentValidationError(collection.name, issues),
96
+ };
97
+ }
98
+ return { success: true, data };
99
+ }
100
+ function assertCollection(collection) {
101
+ if (collection === null ||
102
+ typeof collection !== "object" ||
103
+ typeof collection.name !== "string" ||
104
+ collection.schema === undefined ||
105
+ typeof collection.schema.safeParse !== "function") {
106
+ throw new TypeError("validateEntry/validateCollection require a ContentCollection from defineCollection().");
107
+ }
108
+ }
109
+ function toValidationIssue(issue) {
110
+ const record = issue;
111
+ const base = {
112
+ path: issue.path.map((segment) => {
113
+ if (typeof segment === "string" || typeof segment === "number") {
114
+ return segment;
115
+ }
116
+ return String(segment);
117
+ }),
118
+ code: issue.code,
119
+ message: issue.message,
120
+ };
121
+ if (issue.code === "invalid_type") {
122
+ const expected = record.expected;
123
+ const input = record.input;
124
+ const received = record.received;
125
+ return {
126
+ ...base,
127
+ ...(expected !== undefined ? { expected: String(expected) } : {}),
128
+ ...(input !== undefined
129
+ ? { received: typeName(input) }
130
+ : received !== undefined
131
+ ? { received: String(received) }
132
+ : {}),
133
+ };
134
+ }
135
+ if (issue.code === "unrecognized_keys") {
136
+ const keys = Array.isArray(record.keys) ? record.keys.map(String) : [];
137
+ return {
138
+ ...base,
139
+ received: keys.join(", "),
140
+ };
141
+ }
142
+ if (issue.code === "invalid_format") {
143
+ const format = record.format;
144
+ const input = record.input;
145
+ return {
146
+ ...base,
147
+ ...(format !== undefined ? { expected: String(format) } : {}),
148
+ ...(input !== undefined ? { received: String(input) } : {}),
149
+ };
150
+ }
151
+ return base;
152
+ }
153
+ function typeName(value) {
154
+ if (value === null) {
155
+ return "null";
156
+ }
157
+ if (Array.isArray(value)) {
158
+ return "array";
159
+ }
160
+ return typeof value;
161
+ }
162
+ /** Narrow helper for callers that prefer throwing on failure. */
163
+ export function assertValidEntry(collection, entry, options = {}) {
164
+ const result = validateEntry(collection, entry, options);
165
+ if (!result.success) {
166
+ throw result.error;
167
+ }
168
+ return result.data;
169
+ }
170
+ //# 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.2.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": "^4.6.7"
26
+ },
27
+ "peerDependencies": {
28
+ "zod": "^4.6.7"
29
+ },
30
+ "devDependencies": {
31
+ "@types/node": "^22.20.5",
32
+ "typescript": "5.9.3",
33
+ "zod": "4.6.7"
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
  }