{"_id":"@byearlybird/schema","_rev":"2-271ec3d9486156ac39d022baaa5c348b","name":"@byearlybird/schema","dist-tags":{"latest":"0.2.0"},"versions":{"0.1.0":{"name":"@byearlybird/schema","version":"0.1.0","author":"Early Bird","license":"MIT","_id":"@byearlybird/schema@0.1.0","maintainers":[{"name":"nickmurphy","email":"nickrmurphy@icloud.com"}],"homepage":"https://github.com/byearlybird/sdk#readme","bugs":{"url":"https://github.com/byearlybird/sdk/issues"},"dist":{"shasum":"4a8d36dc2c6e79c31ff0e329eed65a3d7a59a7bf","tarball":"https://registry.npmjs.org/@byearlybird/schema/-/schema-0.1.0.tgz","fileCount":4,"integrity":"sha512-CN3hKDIN335hfmMqO4TwPrkFYGa8lijgYSoSIVa6BU5ak/EL+CbWe1AVXSTnobD6CNeQ1ynWwjvQIL7UmDYrcQ==","signatures":[{"sig":"MEUCIFXrntjlhpWdbMzp8Kw3/vqJa9xMgq/v4wOGRpFbf3XGAiEAyYxgKj2EYJfVZJ/8s74WbOc/iM2BkJpLhKeJ1PK5t+0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":12023},"type":"module","exports":{".":"./dist/index.mjs","./package.json":"./package.json"},"scripts":{"dev":"vp pack --watch","test":"vp test","build":"vp pack","check":"vp check"},"_npmUser":{"name":"nickmurphy","email":"nickrmurphy@icloud.com"},"repository":{"url":"git+https://github.com/byearlybird/sdk.git","type":"git","directory":"packages/schema"},"description":"A lightweight Standard Schema library for strict, JSON-serializable data.","directories":{},"sideEffects":false,"_nodeVersion":"24.19.0","dependencies":{"@standard-schema/spec":"^1.1.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"hono":"^4.12.31","vite":"npm:@voidzero-dev/vite-plus-core@0.2.6","vite-plus":"0.2.6","typescript":"^7.0.2","@hono/standard-validator":"^0.3.0"},"_npmOperationalInternal":{"tmp":"tmp/schema_0.1.0_1785899015017_0.21688883544137671","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@byearlybird/schema","version":"0.2.0","description":"A lightweight Standard Schema library for strict, JSON-serializable data.","homepage":"https://github.com/byearlybird/sdk#readme","bugs":{"url":"https://github.com/byearlybird/sdk/issues"},"license":"MIT","author":{"name":"Early Bird"},"repository":{"type":"git","url":"git+https://github.com/byearlybird/sdk.git","directory":"packages/schema"},"type":"module","sideEffects":false,"exports":{".":"./dist/index.mjs","./package.json":"./package.json"},"publishConfig":{"access":"public"},"dependencies":{"@standard-schema/spec":"^1.1.0"},"devDependencies":{"@hono/standard-validator":"^0.3.0","hono":"^4.12.31","typescript":"^7.0.2","vite":"npm:@voidzero-dev/vite-plus-core@0.2.6","vite-plus":"0.2.6"},"scripts":{"build":"vp pack","dev":"vp pack --watch","test":"vp test","check":"vp check"},"_id":"@byearlybird/schema@0.2.0","_integrity":"sha512-7bOkmKDB3AY0NqUprSkNS8WHOnTSEg5oNfFYrUjTaq/EXfm9Td7m/JCoVj1GuVOBQgZE/RUFbZhMtsQV39U+3w==","_resolved":"/private/var/folders/4h/bb9c_tbs151_51ycndd342740000gn/T/ad9260988679e36a04c3caabfbe08076/byearlybird-schema-0.2.0.tgz","_from":"file:byearlybird-schema-0.2.0.tgz","_nodeVersion":"24.19.0","_npmVersion":"11.17.0","dist":{"integrity":"sha512-7bOkmKDB3AY0NqUprSkNS8WHOnTSEg5oNfFYrUjTaq/EXfm9Td7m/JCoVj1GuVOBQgZE/RUFbZhMtsQV39U+3w==","shasum":"c6377fa45ae272d1bc621a37493dff54709cd1e0","tarball":"https://registry.npmjs.org/@byearlybird/schema/-/schema-0.2.0.tgz","fileCount":5,"unpackedSize":21537,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCYVaqEeAf7Ijmjjum1t5r4jk07zhRKdM6x67kBApoxwAIgacknOUg/MS2h1GtfsZTtsrGbr8fem2YHLlsqiqNrgKU="}]},"_npmUser":{"name":"nickmurphy","email":"nickrmurphy@icloud.com"},"directories":{},"maintainers":[{"name":"nickmurphy","email":"nickrmurphy@icloud.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/schema_0.2.0_1786495754186_0.3372191043197794"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-05T03:03:34.904Z","modified":"2026-08-12T00:49:14.469Z","0.1.0":"2026-08-05T03:03:35.155Z","0.2.0":"2026-08-12T00:49:14.333Z"},"bugs":{"url":"https://github.com/byearlybird/sdk/issues"},"author":{"name":"Early Bird"},"license":"MIT","homepage":"https://github.com/byearlybird/sdk#readme","repository":{"type":"git","url":"git+https://github.com/byearlybird/sdk.git","directory":"packages/schema"},"description":"A lightweight Standard Schema library for strict, JSON-serializable data.","maintainers":[{"name":"nickmurphy","email":"nickrmurphy@icloud.com"}],"readme":"# Schema by Early Bird\n\nA seriously lightweight [Standard Schema](https://standardschema.dev) library.\n\nThis one gives you just the absolute basics needed to define strict, simple schemas for JSON-serializable data: strings, numbers, booleans, null, plain objects, and arrays.\n\nThat's the whole library, and it's small on purpose. If you're validating JSON at a boundary and want the smallest thing that does it strictly, I think this is a good fit. If you need unions, transforms, coercion, or anything else in [Limits](#limits), a fuller validator is probably the better call.\n\n> [!NOTE]\n> **Status: Beta.** The public API is mostly settled, but I'm not calling it done yet. Breaking\n> changes are still possible before 1.0, and I'll call them out in the changelog rather than ship\n> them quietly.\n\n## Install\n\n```sh\npnpm add @byearlybird/schema\n```\n\n## Example\n\n```ts\nimport { array, boolean, number, object, string } from \"@byearlybird/schema\";\n\nconst taskSchema = object({\n  id: string(),\n  title: string({ minLength: 1, maxLength: 120 }),\n  status: string({ values: [\"do\", \"doing\", \"done\"], default: \"do\" }),\n  effort: number({ integer: true, min: 0, nullable: true, default: null }),\n  done: boolean({ default: false }),\n  tags: array(string(), { uniqueItems: true, default: [] }),\n});\n\nconst result = taskSchema.validate({ id: \"t1\", title: \"Write the docs\" });\n\nif (result.issues) {\n  for (const issue of result.issues) {\n    console.error(issue.path?.join(\".\"), issue.message);\n  }\n} else {\n  result.value;\n  // { id: \"t1\", title: \"Write the docs\", status: \"do\",\n  //   effort: null, done: false, tags: [] }\n}\n```\n\nValidation hands back a result rather than throwing. On success the result has a `value`; on failure it has `issues`, and every issue carries a `message` plus the `path` to the value that failed:\n\n```ts\ntaskSchema.validate({ id: \"t1\", title: \"\", tags: [\"a\", \"a\"] }).issues;\n// [\n//   { message: \"Expected length at least 1.\", path: [\"title\"] },\n//   { message: \"Expected unique items.\", path: [\"tags\", 1] },\n// ]\n```\n\nEach schema is a Standard Schema, so it also works anywhere that spec is accepted:\n\n```ts\nimport { sValidator } from \"@hono/standard-validator\";\n\napp.post(\"/tasks\", sValidator(\"json\", taskSchema), (c) => c.json(c.req.valid(\"json\")));\n```\n\n## API\n\nEvery schema accepts two shared options:\n\n- `nullable` — allow `null` in addition to the base value.\n- `default` — value substituted when the input is `undefined`, which makes the input optional. Either a fixed value or a synchronous function called each time a default is needed, e.g. `default: () => Date.now()`. Either way, the result must be a value the schema accepts. A default that is (or can return) `null` requires `nullable: true`.\n\n### `string(options?)`\n\n- `values` — restrict to a fixed set, narrowing the inferred type to a union.\n- `minLength` / `maxLength` — bounds on string length.\n- `pattern` — a `RegExp` the value must match.\n\n### `number(options?)`\n\nAccepts finite numbers only; `NaN` and `Infinity` are rejected.\n\n- `values` — restrict to a fixed set, narrowing the inferred type to a union.\n- `min` / `max` — bounds on the value.\n- `integer` — require an integer.\n\n### `boolean(options?)`\n\nNo options beyond `nullable` and `default`.\n\n### `object(fields, options?)`\n\nAccepts plain objects only — class instances, `Date`, `Map`, and arrays are rejected. **Unknown keys are rejected**, each reported as its own issue. Field names must be strings.\n\n### `array(itemSchema, options?)`\n\n- `uniqueItems` — reject duplicates. Items are compared by their validated JSON value, so key order does not affect the comparison.\n\n### Type inference\n\n`InferInput` is what a schema accepts, `InferOutput` is what it produces. They differ wherever a default is set: the key is optional on input and always present on output.\n\n```ts\nimport type { InferInput, InferOutput } from \"@byearlybird/schema\";\n\ntype TaskInput = InferInput<typeof taskSchema>;\n// { id: string; title: string; status?: \"do\" | \"doing\" | \"done\";\n//   effort?: number | null; done?: boolean; tags?: string[] }\n\ntype Task = InferOutput<typeof taskSchema>;\n// { id: string; title: string; status: \"do\" | \"doing\" | \"done\";\n//   effort: number | null; done: boolean; tags: string[] }\n```\n\n### Invalid options throw\n\nOptions are checked when the schema is built, not when a value is validated, so a mistake shows up at startup instead of at some later request:\n\n```ts\nnumber({ min: 2, default: 1 }); // TypeError: Invalid default. Expected at least 2.\nstring({ pattern: \"task\" }); // TypeError: Invalid pattern.\n```\n\nFixed defaults get snapshotted at that point too. Mutating an object or array you passed as a `default` afterwards won't change what the schema produces, and a filled default is never shared between validations.\n\nA default factory is different: it isn't called until a value actually needs defaulting, so a broken factory doesn't throw until then, and its return value is validated on every call rather than once upfront.\n\n```ts\nconst rowSchema = object({\n  id: string({ default: () => crypto.randomUUID() }),\n  createdAt: number({ default: () => Date.now() }),\n});\n```\n\n`undefined` is not a default, so just omit the option instead. This always throws at runtime, and if your project sets [`exactOptionalPropertyTypes`](https://www.typescriptlang.org/tsconfig/#exactOptionalPropertyTypes) it's a compile error as well. That setting isn't required to use this package.\n\n```ts\nstring({ default: undefined }); // TypeError: Invalid default. Expected a defined value.\n```\n\n### JSON values only\n\nEvery schema accepts only values that are already valid JSON, and every validated result is a JSON value tree. Non-JSON values are rejected rather than coerced:\n\n- `NaN`, `Infinity`, `-Infinity`, and `BigInt` are not numbers here.\n- `Date`, `Map`, `Set`, `RegExp`, functions, class instances, and boxed primitives such as `new String(\"a\")` are not objects here.\n- `undefined` is never a value. A field set to `undefined` counts as missing, which is an error unless that field has a `default`.\n- Symbols are rejected as values and as keys.\n- Array holes are rejected; non-index array properties are dropped.\n\nObjects and arrays are rebuilt during validation, so the result is a fresh structure that can go straight to `JSON.stringify`: unknown input keys never survive, and a `__proto__` key stays an own property without reaching the prototype chain.\n\n## Limits\n\nThis package is small on purpose. No unions, records, tuples, intersections, transforms, or coercion, and none of those are planned.\n\nA few other things worth knowing:\n\n- **ESM only.** There's no CommonJS build, so `require()` won't work.\n- **Synchronous.** `validate` never returns a promise.\n- **Composition only takes Early Bird schemas.** `object()` and `array()` want schemas from this package, not arbitrary Standard Schema implementations.\n- **Objects are strict.** Unknown keys are an error, and there's no passthrough or strip mode.\n- **No optional-without-default.** Every key in the output type is present. To model an absent value, use `nullable: true, default: null` and read it as `null`.\n\n## License\n\nMIT\n","readmeFilename":"README.md"}