@lankajs/typebox 1.0.1 → 2.0.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/README.md CHANGED
@@ -18,27 +18,37 @@ A library in the same box. The app imports and calls it; core does not know it e
18
18
  ## Why a bridge rather than a name
19
19
 
20
20
  TypeBox publishes no `~standard` at all — not asynchronously like yup, not at all —
21
- so core's port cannot be handed a TypeBox schema. The bridge reads
22
- `Value`/`TypeCompiler` and produces the same two lists every package in the family
23
- produces.
21
+ so core's port cannot be handed a TypeBox schema. The bridge reads `Compile` and
22
+ `typebox/value` and produces the same two lists every package in the family produces.
23
+
24
+ ## TypeBox 1.x, and where 0.34 went
25
+
26
+ TypeBox 1.x is a different package — `typebox`, not `@sinclair/typebox` — with a
27
+ different API, so this package's 2.x binds it and its 1.x line binds 0.34. That line
28
+ depends on lanka 2: an application on 0.34 stays on `@lankajs/typebox@1`, and moving
29
+ it to a later lanka means moving to TypeBox 1.x.
30
+
31
+ A TypeBox 1.x schema is recognised by its `~kind`, the mark every builder sets. Not by
32
+ `IsSchema`: that answers true for any object, since any object is a JSON Schema, and a
33
+ schema from another library would then validate as "accept everything".
24
34
 
25
35
  ## Why the compiled checker is cached, and why that is not premature
26
36
 
27
- `TypeCompiler.Compile(schema)` turns a schema into a function, and the function is the
37
+ `Compile(schema)` turns a schema into a function, and the function is the
28
38
  fastest validator in JavaScript. Compilation itself is not fast. Compiling on every
29
39
  call would make this the SLOWEST package in the family while advertising the
30
40
  opposite — the exact shape of a claim that measures well in a microbenchmark and
31
41
  loses in an application.
32
42
 
33
43
  So a `WeakMap` keyed by the schema object holds the compiled checker and whether the
34
- schema contains a transform. Weak because the key is the consumer's schema: a strong
44
+ schema contains a codec. Weak because the key is the consumer's schema: a strong
35
45
  map here would keep every schema a screen ever built alive for the life of the tab.
36
46
 
37
47
  ## Why `Decode` is not simply always called
38
48
 
39
- `Value.Decode` applies transforms AND re-checks, so calling it after the compiled
40
- check would validate every body twice. `HasTransform` is asked once per schema and
41
- cached beside the checker, so a schema without transforms — which is most of them —
49
+ `Decode` applies codecs AND re-checks, so calling it after the compiled
50
+ check would validate every body twice. `HasCodec` is asked once per schema and
51
+ cached beside the checker, so a schema without codecs — which is most of them —
42
52
  pays for one pass.
43
53
 
44
54
  ---
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { TSchema, Static } from '@sinclair/typebox';
1
+ import { TSchema, StaticDecode } from 'typebox';
2
2
  import { TLankaValidationResult } from 'lanka/validation';
3
3
 
4
4
  /**
@@ -12,9 +12,9 @@ import { TLankaValidationResult } from 'lanka/validation';
12
12
  */
13
13
  interface ILankaTypeBoxValidator {
14
14
  /** Validates and returns the parsed value, or throws. */
15
- validate<TSchemaType extends TSchema>(schema: TSchemaType, data: unknown, context: string): Static<TSchemaType>;
15
+ validate<TSchemaType extends TSchema>(schema: TSchemaType, data: unknown, context: string): StaticDecode<TSchemaType>;
16
16
  /** Validates and returns an outcome, throwing nothing. */
17
- validateSafe<TSchemaType extends TSchema>(schema: TSchemaType, data: unknown): TLankaValidationResult<Static<TSchemaType>>;
17
+ validateSafe<TSchemaType extends TSchema>(schema: TSchemaType, data: unknown): TLankaValidationResult<StaticDecode<TSchemaType>>;
18
18
  }
19
19
  /**
20
20
  * The validator for TypeBox schemas.
@@ -27,10 +27,10 @@ interface ILankaTypeBoxValidator {
27
27
  *
28
28
  * ## What it costs, and why the cache is not premature
29
29
  *
30
- * `TypeCompiler.Compile(schema)` produces the fastest validator in JavaScript,
31
- * and compiling is the slow part. Compiled per call, this would be the slowest
32
- * package in the family while claiming to be the fastest. The checker is cached
33
- * per schema instead — see `compiledTypeBoxSchema`.
30
+ * `Compile(schema)` produces the fastest validator in JavaScript, and compiling
31
+ * is the slow part. Compiled per call, this would be the slowest package in the
32
+ * family while claiming to be the fastest. The checker is cached per schema
33
+ * instead — see `compiledTypeBoxSchema`.
34
34
  *
35
35
  * A schema built inside a component body is a new object on every render and
36
36
  * therefore a new cache key. Declare schemas at module level, which is where they
@@ -38,7 +38,12 @@ interface ILankaTypeBoxValidator {
38
38
  */
39
39
  declare const lankaTypeBoxValidator: ILankaTypeBoxValidator;
40
40
 
41
- /** The type a schema infers. Shorter than `Static<typeof schema>` everywhere. */
42
- type TLankaInferred<TSchemaType extends TSchema> = Static<TSchemaType>;
41
+ /**
42
+ * The type a schema infers: what `validate` returns.
43
+ *
44
+ * `StaticDecode` rather than `Static`, which in TypeBox 1.x is the ENCODED
45
+ * side — the wire shape a codec reads, not the value it produces.
46
+ */
47
+ type TLankaInferred<TSchemaType extends TSchema> = StaticDecode<TSchemaType>;
43
48
 
44
49
  export { type TLankaInferred, lankaTypeBoxValidator };
package/dist/index.js CHANGED
@@ -1,19 +1,18 @@
1
1
  // src/lanka-type-box-validator/lankaTypeBoxValidator.ts
2
- import { KindGuard } from "@sinclair/typebox";
3
- import { Value } from "@sinclair/typebox/value";
2
+ import { Clone, DecodeUnsafe } from "typebox/value";
4
3
  import { LankaValidationError } from "lanka/validation";
5
4
  import { lankaForeignSchemaMessage, lankaValueOrThrow } from "lanka/internal";
6
5
 
7
6
  // src/_internal/compiled-type-box-schema/compiledTypeBoxSchema.ts
8
- import { TypeCompiler } from "@sinclair/typebox/compiler";
9
- import { HasTransform } from "@sinclair/typebox/value";
7
+ import { Compile } from "typebox/compile";
8
+ import { HasCodec } from "typebox/value";
10
9
  var compiled = /* @__PURE__ */ new WeakMap();
11
10
  var compiledTypeBoxSchema = (schema) => {
12
11
  const known = compiled.get(schema);
13
12
  if (known) return known;
14
13
  const fresh = {
15
- check: TypeCompiler.Compile(schema),
16
- transforms: HasTransform(schema, [])
14
+ check: Compile(schema),
15
+ codecs: HasCodec(schema)
17
16
  };
18
17
  compiled.set(schema, fresh);
19
18
  return fresh;
@@ -38,38 +37,46 @@ var lankaTypeBoxValidator = Object.freeze({
38
37
  }
39
38
  });
40
39
  function runCompiled(schema, data) {
41
- if (!KindGuard.IsSchema(schema)) throw notATypeBoxSchema(schema);
42
- const { check, transforms } = compiledTypeBoxSchema(schema);
43
- if (!check.Check(data)) return describeFailure([...check.Errors(data)]);
44
- if (!transforms) return { success: true, data };
40
+ if (!isTypeBoxSchema(schema)) throw notATypeBoxSchema(schema);
41
+ const { check, codecs } = compiledTypeBoxSchema(schema);
42
+ if (!check.Check(data)) return describeFailure(check.Errors(data));
43
+ if (!codecs) return { success: true, data };
45
44
  return decode(schema, data);
46
45
  }
46
+ function isTypeBoxSchema(schema) {
47
+ if (typeof schema !== "object" || schema === null) return false;
48
+ return typeof schema["~kind"] === "string";
49
+ }
47
50
  function notATypeBoxSchema(schema) {
48
51
  return new LankaValidationError(
49
52
  lankaForeignSchemaMessage(schema, {
50
- lead: "This is not a TypeBox schema: it carries no `Kind`."
53
+ lead: "This is not a TypeBox schema: it carries no `~kind`."
51
54
  }),
52
55
  []
53
56
  );
54
57
  }
55
58
  function decode(schema, data) {
56
59
  try {
57
- return { success: true, data: Value.Decode(schema, data) };
60
+ return {
61
+ success: true,
62
+ data: DecodeUnsafe({}, schema, Clone(data))
63
+ };
58
64
  } catch (error) {
59
65
  const message = decodeFailureMessage(error);
60
66
  return { success: false, errors: [message], fields: [{ path: [], message }] };
61
67
  }
62
68
  }
63
69
  function decodeFailureMessage(error) {
64
- const original = error.error ?? error;
65
- return original instanceof Error ? original.message : String(original);
70
+ if (error instanceof Error) return error.message;
71
+ if (typeof error === "string") return error;
72
+ return "The schema's decode function refused the value without saying why.";
66
73
  }
67
74
  function describeFailure(errors) {
68
75
  const fields = errors.map(toFieldError);
69
76
  return { success: false, errors: fields.map(describeField), fields };
70
77
  }
71
78
  function toFieldError(error) {
72
- return { path: typeBoxPointerSegments(error.path), message: error.message };
79
+ return { path: typeBoxPointerSegments(error.instancePath), message: error.message };
73
80
  }
74
81
  function describeField(field) {
75
82
  const path = field.path.join(".");
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/lanka-type-box-validator/lankaTypeBoxValidator.ts","../src/_internal/compiled-type-box-schema/compiledTypeBoxSchema.ts","../src/_utils/type-box-pointer-segments/typeBoxPointerSegments.ts"],"sourcesContent":["import { KindGuard } from \"@sinclair/typebox\";\nimport { Value } from \"@sinclair/typebox/value\";\nimport { LankaValidationError } from \"lanka/validation\";\nimport { lankaForeignSchemaMessage, lankaValueOrThrow } from \"lanka/internal\";\nimport type { Static, TSchema } from \"@sinclair/typebox\";\nimport type { ValueError } from \"@sinclair/typebox/value\";\nimport type { TLankaValidationResult } from \"lanka/validation\";\nimport type { ILankaFieldError } from \"lanka/errors\";\nimport { compiledTypeBoxSchema } from \"../_internal/compiled-type-box-schema/compiledTypeBoxSchema\";\nimport { typeBoxPointerSegments } from \"../_utils/type-box-pointer-segments/typeBoxPointerSegments\";\n\n/**\n * The validator's shape, typed by a TypeBox schema rather than by core's port.\n *\n * `ILankaValidator` takes a `TLankaSchema` — a Standard Schema — and TypeBox\n * publishes none, so a validator declared as the port would reject every schema a\n * consumer of this package has. The SHAPE is the port's, member for member, and\n * `check:family` holds the barrel to the family's surface; what differs is the\n * one type the library makes impossible to share.\n */\nexport interface ILankaTypeBoxValidator {\n\t/** Validates and returns the parsed value, or throws. */\n\tvalidate<TSchemaType extends TSchema>(\n\t\tschema: TSchemaType,\n\t\tdata: unknown,\n\t\tcontext: string,\n\t): Static<TSchemaType>;\n\t/** Validates and returns an outcome, throwing nothing. */\n\tvalidateSafe<TSchemaType extends TSchema>(\n\t\tschema: TSchemaType,\n\t\tdata: unknown,\n\t): TLankaValidationResult<Static<TSchemaType>>;\n}\n\n/**\n * The validator for TypeBox schemas.\n *\n * ## Why this one is a bridge\n *\n * TypeBox publishes no `~standard` at all — not asynchronously as yup does, not\n * at all — so core's port cannot be handed a TypeBox schema and the package is\n * what makes the library usable here.\n *\n * ## What it costs, and why the cache is not premature\n *\n * `TypeCompiler.Compile(schema)` produces the fastest validator in JavaScript,\n * and compiling is the slow part. Compiled per call, this would be the slowest\n * package in the family while claiming to be the fastest. The checker is cached\n * per schema instead — see `compiledTypeBoxSchema`.\n *\n * A schema built inside a component body is a new object on every render and\n * therefore a new cache key. Declare schemas at module level, which is where they\n * belong for every other reason too.\n */\nexport const lankaTypeBoxValidator: ILankaTypeBoxValidator = Object.freeze<ILankaTypeBoxValidator>({\n\tvalidate<TSchemaType extends TSchema>(\n\t\tschema: TSchemaType,\n\t\tdata: unknown,\n\t\tcontext: string,\n\t): Static<TSchemaType> {\n\t\treturn lankaValueOrThrow(runCompiled(schema, data), context);\n\t},\n\n\tvalidateSafe<TSchemaType extends TSchema>(\n\t\tschema: TSchemaType,\n\t\tdata: unknown,\n\t): TLankaValidationResult<Static<TSchemaType>> {\n\t\treturn runCompiled(schema, data);\n\t},\n});\n\n/**\n * One pass through the compiled checker, and a second only if the schema asks.\n *\n * `Value.Decode` applies transforms AND re-checks, so calling it unconditionally\n * would validate every body twice. Whether the schema transforms at all is\n * answered once per schema and cached beside the checker.\n */\nfunction runCompiled<TSchemaType extends TSchema>(\n\tschema: TSchemaType,\n\tdata: unknown,\n): TLankaValidationResult<Static<TSchemaType>> {\n\tif (!KindGuard.IsSchema(schema)) throw notATypeBoxSchema(schema);\n\n\tconst { check, transforms } = compiledTypeBoxSchema(schema);\n\n\tif (!check.Check(data)) return describeFailure([...check.Errors(data)]);\n\tif (!transforms) return { success: true, data: data as Static<TSchemaType> };\n\n\treturn decode(schema, data);\n}\n\n/**\n * The refusal for a schema from another library.\n *\n * An application whose schemas come from two libraries eventually hands one to\n * the wrong validator. Unguarded, `TypeCompiler.Compile` was handed a zod schema\n * and threw `TypeCompilerTypeGuardError: Preflight validation check failed` —\n * accurate, and useless to anyone who has not read TypeBox's source. It escaped\n * `validateSafe`, which promises to throw nothing, as a raw library error.\n *\n * `KindGuard.IsSchema` is TypeBox's own answer to the question, so the guard\n * cannot drift from what `Compile` will accept.\n *\n * It throws from `validateSafe` too, deliberately: a refused VALUE is an outcome\n * a form renders, while a schema this package cannot read is a wiring mistake,\n * and putting it in `errors` would show a programmer's error to a user beside an\n * input.\n */\nfunction notATypeBoxSchema(schema: unknown): LankaValidationError {\n\treturn new LankaValidationError(\n\t\tlankaForeignSchemaMessage(schema, {\n\t\t\tlead: \"This is not a TypeBox schema: it carries no `Kind`.\",\n\t\t}),\n\t\t[],\n\t);\n}\n\n/**\n * The transform pass, for a schema that has one.\n *\n * A decode function is consumer code and may throw — a date that does not parse,\n * an enum with no case for the value. That is a refusal of the body, not a crash\n * of the validator, so it comes back as one: `validateSafe` promises to throw\n * nothing.\n */\nfunction decode<TSchemaType extends TSchema>(\n\tschema: TSchemaType,\n\tdata: unknown,\n): TLankaValidationResult<Static<TSchemaType>> {\n\ttry {\n\t\treturn { success: true, data: Value.Decode(schema, data) };\n\t} catch (error) {\n\t\tconst message = decodeFailureMessage(error);\n\n\t\treturn { success: false, errors: [message], fields: [{ path: [], message }] };\n\t}\n}\n\n/**\n * What a failed decode should SAY.\n *\n * TypeBox wraps whatever the decode function threw in a `TransformDecodeError`\n * and copies across a message only when the thrown value was an `Error` —\n * anything else becomes the literal string \"Unknown error\", which tells a\n * consumer nothing about their own code. The original is kept on `.error`, so\n * that is what is read first.\n */\nfunction decodeFailureMessage(error: unknown): string {\n\tconst original = (error as { error?: unknown }).error ?? error;\n\n\treturn original instanceof Error ? original.message : String(original);\n}\n\n/** A TypeBox failure as the two lists the port promises: one for a banner, one for a form. */\nfunction describeFailure<TOutput>(errors: ValueError[]): TLankaValidationResult<TOutput> {\n\tconst fields = errors.map(toFieldError);\n\n\treturn { success: false, errors: fields.map(describeField), fields };\n}\n\nfunction toFieldError(error: ValueError): ILankaFieldError {\n\treturn { path: typeBoxPointerSegments(error.path), message: error.message };\n}\n\n/**\n * A field path plus its message, for a banner.\n *\n * Dots rather than TypeBox's pointer spelling, because this string is read beside\n * the other five packages' and the family reads one way.\n */\nfunction describeField(field: ILankaFieldError): string {\n\tconst path = field.path.join(\".\");\n\n\treturn path ? `${path}: ${field.message}` : field.message;\n}\n","import { TypeCompiler } from \"@sinclair/typebox/compiler\";\nimport { HasTransform } from \"@sinclair/typebox/value\";\nimport type { TSchema } from \"@sinclair/typebox\";\nimport type { TypeCheck } from \"@sinclair/typebox/compiler\";\n\n/** What is known about one schema once, and re-read on every call after that. */\nexport interface ICompiledTypeBoxSchema {\n\t/** The compiled checker: a function TypeBox generated for this schema. */\n\tcheck: TypeCheck<TSchema>;\n\t/**\n\t * Whether anything in the schema transforms.\n\t *\n\t * Asked once because the answer cannot change — a schema is a value — and\n\t * because it decides whether a second pass is needed at all. Most schemas do\n\t * not transform, and those must not pay for `Value.Decode`'s re-check.\n\t */\n\ttransforms: boolean;\n}\n\n/**\n * The compiled checker for a schema, compiled at most once.\n *\n * ## Why this exists\n *\n * `TypeCompiler.Compile` turns a schema into a generated function, and that\n * function is the fastest validator in JavaScript. Compiling is not fast.\n * Compiling per call would make this the SLOWEST package in the family while the\n * README advertised the opposite — the exact shape of a claim that wins a\n * microbenchmark and loses in an application.\n *\n * ## Why a WeakMap\n *\n * The key is the CONSUMER'S schema object. A strong map would keep every schema\n * any screen ever built alive for the life of the tab, which is a leak the\n * consumer cannot see, cannot measure and cannot clear. Weak, the entry goes when\n * the schema does.\n *\n * A schema rebuilt on every render defeats the cache — it is a new key each\n * time — and that is a fact worth knowing rather than a case to work around:\n * declare schemas at module level, which is where they belong anyway.\n */\nconst compiled = new WeakMap<TSchema, ICompiledTypeBoxSchema>();\n\nexport const compiledTypeBoxSchema = (schema: TSchema): ICompiledTypeBoxSchema => {\n\tconst known = compiled.get(schema);\n\tif (known) return known;\n\n\tconst fresh: ICompiledTypeBoxSchema = {\n\t\tcheck: TypeCompiler.Compile(schema),\n\t\ttransforms: HasTransform(schema, []),\n\t};\n\n\tcompiled.set(schema, fresh);\n\n\treturn fresh;\n};\n","/**\n * A TypeBox error pointer as segments: `/tags/0/id` → `[\"tags\", 0, \"id\"]`.\n *\n * TypeBox addresses a field with a JSON Pointer (RFC 6901). `ILankaFieldError.path`\n * is segments, on purpose: the two form libraries the shape was designed against\n * spell the same address differently, and neither can be parsed back out of a\n * joined string safely.\n *\n * The pointer's two escapes are not decoration. `~1` is a literal `/` in a key\n * and `~0` a literal `~`; unescaped, a key containing a slash would split into\n * two segments addressing nothing. **In this order** — `~1` first, then `~0` —\n * because the reverse turns `~01` into `/` instead of the literal `~1` it is.\n *\n * A segment of digits becomes a NUMBER. A form distinguishes the second element\n * of a list from a key spelled `\"1\"`, and JSON Pointer does not carry the\n * difference itself — it is recovered from the shape of the segment, which is\n * what every JSON Pointer implementation does.\n *\n * The root pointer is the empty string, and an empty array is what the port's\n * consumers already read as \"the value as a whole\".\n */\nexport const typeBoxPointerSegments = (pointer: string | undefined): (string | number)[] => {\n\tif (!pointer) return [];\n\n\treturn pointer\n\t\t.split(\"/\")\n\t\t.slice(1)\n\t\t.map((segment) => {\n\t\t\tconst key = segment.replace(/~1/g, \"/\").replace(/~0/g, \"~\");\n\n\t\t\treturn /^\\d+$/.test(key) ? Number(key) : key;\n\t\t});\n};\n"],"mappings":";AAAA,SAAS,iBAAiB;AAC1B,SAAS,aAAa;AACtB,SAAS,4BAA4B;AACrC,SAAS,2BAA2B,yBAAyB;;;ACH7D,SAAS,oBAAoB;AAC7B,SAAS,oBAAoB;AAwC7B,IAAM,WAAW,oBAAI,QAAyC;AAEvD,IAAM,wBAAwB,CAAC,WAA4C;AACjF,QAAM,QAAQ,SAAS,IAAI,MAAM;AACjC,MAAI,MAAO,QAAO;AAElB,QAAM,QAAgC;AAAA,IACrC,OAAO,aAAa,QAAQ,MAAM;AAAA,IAClC,YAAY,aAAa,QAAQ,CAAC,CAAC;AAAA,EACpC;AAEA,WAAS,IAAI,QAAQ,KAAK;AAE1B,SAAO;AACR;;;AClCO,IAAM,yBAAyB,CAAC,YAAqD;AAC3F,MAAI,CAAC,QAAS,QAAO,CAAC;AAEtB,SAAO,QACL,MAAM,GAAG,EACT,MAAM,CAAC,EACP,IAAI,CAAC,YAAY;AACjB,UAAM,MAAM,QAAQ,QAAQ,OAAO,GAAG,EAAE,QAAQ,OAAO,GAAG;AAE1D,WAAO,QAAQ,KAAK,GAAG,IAAI,OAAO,GAAG,IAAI;AAAA,EAC1C,CAAC;AACH;;;AFsBO,IAAM,wBAAgD,OAAO,OAA+B;AAAA,EAClG,SACC,QACA,MACA,SACsB;AACtB,WAAO,kBAAkB,YAAY,QAAQ,IAAI,GAAG,OAAO;AAAA,EAC5D;AAAA,EAEA,aACC,QACA,MAC8C;AAC9C,WAAO,YAAY,QAAQ,IAAI;AAAA,EAChC;AACD,CAAC;AASD,SAAS,YACR,QACA,MAC8C;AAC9C,MAAI,CAAC,UAAU,SAAS,MAAM,EAAG,OAAM,kBAAkB,MAAM;AAE/D,QAAM,EAAE,OAAO,WAAW,IAAI,sBAAsB,MAAM;AAE1D,MAAI,CAAC,MAAM,MAAM,IAAI,EAAG,QAAO,gBAAgB,CAAC,GAAG,MAAM,OAAO,IAAI,CAAC,CAAC;AACtE,MAAI,CAAC,WAAY,QAAO,EAAE,SAAS,MAAM,KAAkC;AAE3E,SAAO,OAAO,QAAQ,IAAI;AAC3B;AAmBA,SAAS,kBAAkB,QAAuC;AACjE,SAAO,IAAI;AAAA,IACV,0BAA0B,QAAQ;AAAA,MACjC,MAAM;AAAA,IACP,CAAC;AAAA,IACD,CAAC;AAAA,EACF;AACD;AAUA,SAAS,OACR,QACA,MAC8C;AAC9C,MAAI;AACH,WAAO,EAAE,SAAS,MAAM,MAAM,MAAM,OAAO,QAAQ,IAAI,EAAE;AAAA,EAC1D,SAAS,OAAO;AACf,UAAM,UAAU,qBAAqB,KAAK;AAE1C,WAAO,EAAE,SAAS,OAAO,QAAQ,CAAC,OAAO,GAAG,QAAQ,CAAC,EAAE,MAAM,CAAC,GAAG,QAAQ,CAAC,EAAE;AAAA,EAC7E;AACD;AAWA,SAAS,qBAAqB,OAAwB;AACrD,QAAM,WAAY,MAA8B,SAAS;AAEzD,SAAO,oBAAoB,QAAQ,SAAS,UAAU,OAAO,QAAQ;AACtE;AAGA,SAAS,gBAAyB,QAAuD;AACxF,QAAM,SAAS,OAAO,IAAI,YAAY;AAEtC,SAAO,EAAE,SAAS,OAAO,QAAQ,OAAO,IAAI,aAAa,GAAG,OAAO;AACpE;AAEA,SAAS,aAAa,OAAqC;AAC1D,SAAO,EAAE,MAAM,uBAAuB,MAAM,IAAI,GAAG,SAAS,MAAM,QAAQ;AAC3E;AAQA,SAAS,cAAc,OAAiC;AACvD,QAAM,OAAO,MAAM,KAAK,KAAK,GAAG;AAEhC,SAAO,OAAO,GAAG,IAAI,KAAK,MAAM,OAAO,KAAK,MAAM;AACnD;","names":[]}
1
+ {"version":3,"sources":["../src/lanka-type-box-validator/lankaTypeBoxValidator.ts","../src/_internal/compiled-type-box-schema/compiledTypeBoxSchema.ts","../src/_utils/type-box-pointer-segments/typeBoxPointerSegments.ts"],"sourcesContent":["import { Clone, DecodeUnsafe } from \"typebox/value\";\nimport { LankaValidationError } from \"lanka/validation\";\nimport { lankaForeignSchemaMessage, lankaValueOrThrow } from \"lanka/internal\";\nimport type { StaticDecode, TSchema } from \"typebox\";\nimport type { TLocalizedValidationError } from \"typebox/error\";\nimport type { TLankaValidationResult } from \"lanka/validation\";\nimport type { ILankaFieldError } from \"lanka/errors\";\nimport { compiledTypeBoxSchema } from \"../_internal/compiled-type-box-schema/compiledTypeBoxSchema\";\nimport { typeBoxPointerSegments } from \"../_utils/type-box-pointer-segments/typeBoxPointerSegments\";\n\n/**\n * The validator's shape, typed by a TypeBox schema rather than by core's port.\n *\n * `ILankaValidator` takes a `TLankaSchema` — a Standard Schema — and TypeBox\n * publishes none, so a validator declared as the port would reject every schema a\n * consumer of this package has. The SHAPE is the port's, member for member, and\n * `check:family` holds the barrel to the family's surface; what differs is the\n * one type the library makes impossible to share.\n */\nexport interface ILankaTypeBoxValidator {\n\t/** Validates and returns the parsed value, or throws. */\n\tvalidate<TSchemaType extends TSchema>(\n\t\tschema: TSchemaType,\n\t\tdata: unknown,\n\t\tcontext: string,\n\t): StaticDecode<TSchemaType>;\n\t/** Validates and returns an outcome, throwing nothing. */\n\tvalidateSafe<TSchemaType extends TSchema>(\n\t\tschema: TSchemaType,\n\t\tdata: unknown,\n\t): TLankaValidationResult<StaticDecode<TSchemaType>>;\n}\n\n/**\n * The validator for TypeBox schemas.\n *\n * ## Why this one is a bridge\n *\n * TypeBox publishes no `~standard` at all — not asynchronously as yup does, not\n * at all — so core's port cannot be handed a TypeBox schema and the package is\n * what makes the library usable here.\n *\n * ## What it costs, and why the cache is not premature\n *\n * `Compile(schema)` produces the fastest validator in JavaScript, and compiling\n * is the slow part. Compiled per call, this would be the slowest package in the\n * family while claiming to be the fastest. The checker is cached per schema\n * instead — see `compiledTypeBoxSchema`.\n *\n * A schema built inside a component body is a new object on every render and\n * therefore a new cache key. Declare schemas at module level, which is where they\n * belong for every other reason too.\n */\nexport const lankaTypeBoxValidator: ILankaTypeBoxValidator = Object.freeze<ILankaTypeBoxValidator>({\n\tvalidate<TSchemaType extends TSchema>(\n\t\tschema: TSchemaType,\n\t\tdata: unknown,\n\t\tcontext: string,\n\t): StaticDecode<TSchemaType> {\n\t\treturn lankaValueOrThrow(runCompiled(schema, data), context);\n\t},\n\n\tvalidateSafe<TSchemaType extends TSchema>(\n\t\tschema: TSchemaType,\n\t\tdata: unknown,\n\t): TLankaValidationResult<StaticDecode<TSchemaType>> {\n\t\treturn runCompiled(schema, data);\n\t},\n});\n\n/**\n * One pass through the compiled checker, and a second only if the schema asks.\n *\n * Whether the schema has a codec at all is answered once per schema and cached\n * beside the checker, so the common schema — no codec — costs one generated\n * function call and nothing else.\n */\nfunction runCompiled<TSchemaType extends TSchema>(\n\tschema: TSchemaType,\n\tdata: unknown,\n): TLankaValidationResult<StaticDecode<TSchemaType>> {\n\tif (!isTypeBoxSchema(schema)) throw notATypeBoxSchema(schema);\n\n\tconst { check, codecs } = compiledTypeBoxSchema(schema);\n\n\tif (!check.Check(data)) return describeFailure(check.Errors(data));\n\tif (!codecs) return { success: true, data: data as StaticDecode<TSchemaType> };\n\n\treturn decode(schema, data);\n}\n\n/**\n * Whether a value was built by TypeBox 1.x.\n *\n * Not `IsSchema`: that answers true for ANY object, because any object is a JSON\n * Schema — and `Compile` agrees, so a zod schema handed here would compile to a\n * checker that accepts everything. `~kind` is the mark every TypeBox builder\n * sets, as a non-enumerable own property, and it is what this reads.\n */\nfunction isTypeBoxSchema(schema: unknown): schema is TSchema {\n\tif (typeof schema !== \"object\" || schema === null) return false;\n\n\treturn typeof (schema as { \"~kind\"?: unknown })[\"~kind\"] === \"string\";\n}\n\n/**\n * The refusal for a schema from another library.\n *\n * An application whose schemas come from two libraries eventually hands one to\n * the wrong validator. Unguarded, TypeBox 0.34's compiler threw\n * `TypeCompilerTypeGuardError: Preflight validation check failed` — accurate, and\n * useless to anyone who has not read TypeBox's source — and TypeBox 1.x would say\n * nothing at all. Either way, a raw library outcome escaped `validateSafe`.\n *\n * It throws from `validateSafe` too, deliberately: a refused VALUE is an outcome\n * a form renders, while a schema this package cannot read is a wiring mistake,\n * and putting it in `errors` would show a programmer's error to a user beside an\n * input.\n */\nfunction notATypeBoxSchema(schema: unknown): LankaValidationError {\n\treturn new LankaValidationError(\n\t\tlankaForeignSchemaMessage(schema, {\n\t\t\tlead: \"This is not a TypeBox schema: it carries no `~kind`.\",\n\t\t}),\n\t\t[],\n\t);\n}\n\n/**\n * The codec pass, for a schema that has one.\n *\n * `DecodeUnsafe` rather than `Decode`, because `Decode` is a pipeline — clone,\n * default, convert, CLEAN, check, decode — and two of those steps would make this\n * path disagree with the one without a codec: the check was already done, and\n * cleaning drops every property the schema did not name, which the path without\n * a codec keeps. `DecodeUnsafe` runs the decode functions and nothing else — and\n * writes their results into the object it is handed, so it is handed a copy: the\n * body is the caller's.\n *\n * A decode function is consumer code and may throw — a date that does not parse,\n * an enum with no case for the value. That is a refusal of the body, not a crash\n * of the validator, so it comes back as one: `validateSafe` promises to throw\n * nothing.\n */\nfunction decode<TSchemaType extends TSchema>(\n\tschema: TSchemaType,\n\tdata: unknown,\n): TLankaValidationResult<StaticDecode<TSchemaType>> {\n\ttry {\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: DecodeUnsafe({}, schema, Clone(data)) as StaticDecode<TSchemaType>,\n\t\t};\n\t} catch (error) {\n\t\tconst message = decodeFailureMessage(error);\n\n\t\treturn { success: false, errors: [message], fields: [{ path: [], message }] };\n\t}\n}\n\n/**\n * What a failed decode should SAY.\n *\n * TypeBox 1.x rethrows whatever the decode function threw, unwrapped. An `Error`\n * has a message and a string is one; anything else has nothing to read, and\n * `String()` of it would put \"undefined\" or \"[object Object]\" in front of a user.\n */\nfunction decodeFailureMessage(error: unknown): string {\n\tif (error instanceof Error) return error.message;\n\tif (typeof error === \"string\") return error;\n\n\treturn \"The schema's decode function refused the value without saying why.\";\n}\n\n/** A TypeBox failure as the two lists the port promises: one for a banner, one for a form. */\nfunction describeFailure<TOutput>(\n\terrors: readonly TLocalizedValidationError[],\n): TLankaValidationResult<TOutput> {\n\tconst fields = errors.map(toFieldError);\n\n\treturn { success: false, errors: fields.map(describeField), fields };\n}\n\nfunction toFieldError(error: TLocalizedValidationError): ILankaFieldError {\n\treturn { path: typeBoxPointerSegments(error.instancePath), message: error.message };\n}\n\n/**\n * A field path plus its message, for a banner.\n *\n * Dots rather than TypeBox's pointer spelling, because this string is read beside\n * the other five packages' and the family reads one way.\n */\nfunction describeField(field: ILankaFieldError): string {\n\tconst path = field.path.join(\".\");\n\n\treturn path ? `${path}: ${field.message}` : field.message;\n}\n","import { Compile } from \"typebox/compile\";\nimport { HasCodec } from \"typebox/value\";\nimport type { TSchema } from \"typebox\";\nimport type { Validator } from \"typebox/compile\";\n\n/** What is known about one schema once, and re-read on every call after that. */\nexport interface ICompiledTypeBoxSchema {\n\t/** The compiled checker: a function TypeBox generated for this schema. */\n\tcheck: Validator;\n\t/**\n\t * Whether anything in the schema has a codec.\n\t *\n\t * Asked once because the answer cannot change — a schema is a value — and\n\t * because it decides whether a second pass is needed at all. Most schemas have\n\t * no codec, and those must not pay for a copy and a decode.\n\t */\n\tcodecs: boolean;\n}\n\n/**\n * The compiled checker for a schema, compiled at most once.\n *\n * ## Why this exists\n *\n * `Compile` turns a schema into a generated function, and that\n * function is the fastest validator in JavaScript. Compiling is not fast.\n * Compiling per call would make this the SLOWEST package in the family while the\n * README advertised the opposite — the exact shape of a claim that wins a\n * microbenchmark and loses in an application.\n *\n * ## Why a WeakMap\n *\n * The key is the CONSUMER'S schema object. A strong map would keep every schema\n * any screen ever built alive for the life of the tab, which is a leak the\n * consumer cannot see, cannot measure and cannot clear. Weak, the entry goes when\n * the schema does.\n *\n * A schema rebuilt on every render defeats the cache — it is a new key each\n * time — and that is a fact worth knowing rather than a case to work around:\n * declare schemas at module level, which is where they belong anyway.\n */\nconst compiled = new WeakMap<TSchema, ICompiledTypeBoxSchema>();\n\nexport const compiledTypeBoxSchema = (schema: TSchema): ICompiledTypeBoxSchema => {\n\tconst known = compiled.get(schema);\n\tif (known) return known;\n\n\tconst fresh: ICompiledTypeBoxSchema = {\n\t\tcheck: Compile(schema),\n\t\tcodecs: HasCodec(schema),\n\t};\n\n\tcompiled.set(schema, fresh);\n\n\treturn fresh;\n};\n","/**\n * A TypeBox error pointer as segments: `/tags/0/id` → `[\"tags\", 0, \"id\"]`.\n *\n * TypeBox addresses a field with a JSON Pointer (RFC 6901). `ILankaFieldError.path`\n * is segments, on purpose: the two form libraries the shape was designed against\n * spell the same address differently, and neither can be parsed back out of a\n * joined string safely.\n *\n * The pointer's two escapes are not decoration. `~1` is a literal `/` in a key\n * and `~0` a literal `~`; unescaped, a key containing a slash would split into\n * two segments addressing nothing. **In this order** — `~1` first, then `~0` —\n * because the reverse turns `~01` into `/` instead of the literal `~1` it is.\n *\n * A segment of digits becomes a NUMBER. A form distinguishes the second element\n * of a list from a key spelled `\"1\"`, and JSON Pointer does not carry the\n * difference itself — it is recovered from the shape of the segment, which is\n * what every JSON Pointer implementation does.\n *\n * The root pointer is the empty string, and an empty array is what the port's\n * consumers already read as \"the value as a whole\".\n */\nexport const typeBoxPointerSegments = (pointer: string | undefined): (string | number)[] => {\n\tif (!pointer) return [];\n\n\treturn pointer\n\t\t.split(\"/\")\n\t\t.slice(1)\n\t\t.map((segment) => {\n\t\t\tconst key = segment.replace(/~1/g, \"/\").replace(/~0/g, \"~\");\n\n\t\t\treturn /^\\d+$/.test(key) ? Number(key) : key;\n\t\t});\n};\n"],"mappings":";AAAA,SAAS,OAAO,oBAAoB;AACpC,SAAS,4BAA4B;AACrC,SAAS,2BAA2B,yBAAyB;;;ACF7D,SAAS,eAAe;AACxB,SAAS,gBAAgB;AAwCzB,IAAM,WAAW,oBAAI,QAAyC;AAEvD,IAAM,wBAAwB,CAAC,WAA4C;AACjF,QAAM,QAAQ,SAAS,IAAI,MAAM;AACjC,MAAI,MAAO,QAAO;AAElB,QAAM,QAAgC;AAAA,IACrC,OAAO,QAAQ,MAAM;AAAA,IACrB,QAAQ,SAAS,MAAM;AAAA,EACxB;AAEA,WAAS,IAAI,QAAQ,KAAK;AAE1B,SAAO;AACR;;;AClCO,IAAM,yBAAyB,CAAC,YAAqD;AAC3F,MAAI,CAAC,QAAS,QAAO,CAAC;AAEtB,SAAO,QACL,MAAM,GAAG,EACT,MAAM,CAAC,EACP,IAAI,CAAC,YAAY;AACjB,UAAM,MAAM,QAAQ,QAAQ,OAAO,GAAG,EAAE,QAAQ,OAAO,GAAG;AAE1D,WAAO,QAAQ,KAAK,GAAG,IAAI,OAAO,GAAG,IAAI;AAAA,EAC1C,CAAC;AACH;;;AFqBO,IAAM,wBAAgD,OAAO,OAA+B;AAAA,EAClG,SACC,QACA,MACA,SAC4B;AAC5B,WAAO,kBAAkB,YAAY,QAAQ,IAAI,GAAG,OAAO;AAAA,EAC5D;AAAA,EAEA,aACC,QACA,MACoD;AACpD,WAAO,YAAY,QAAQ,IAAI;AAAA,EAChC;AACD,CAAC;AASD,SAAS,YACR,QACA,MACoD;AACpD,MAAI,CAAC,gBAAgB,MAAM,EAAG,OAAM,kBAAkB,MAAM;AAE5D,QAAM,EAAE,OAAO,OAAO,IAAI,sBAAsB,MAAM;AAEtD,MAAI,CAAC,MAAM,MAAM,IAAI,EAAG,QAAO,gBAAgB,MAAM,OAAO,IAAI,CAAC;AACjE,MAAI,CAAC,OAAQ,QAAO,EAAE,SAAS,MAAM,KAAwC;AAE7E,SAAO,OAAO,QAAQ,IAAI;AAC3B;AAUA,SAAS,gBAAgB,QAAoC;AAC5D,MAAI,OAAO,WAAW,YAAY,WAAW,KAAM,QAAO;AAE1D,SAAO,OAAQ,OAAiC,OAAO,MAAM;AAC9D;AAgBA,SAAS,kBAAkB,QAAuC;AACjE,SAAO,IAAI;AAAA,IACV,0BAA0B,QAAQ;AAAA,MACjC,MAAM;AAAA,IACP,CAAC;AAAA,IACD,CAAC;AAAA,EACF;AACD;AAkBA,SAAS,OACR,QACA,MACoD;AACpD,MAAI;AACH,WAAO;AAAA,MACN,SAAS;AAAA,MACT,MAAM,aAAa,CAAC,GAAG,QAAQ,MAAM,IAAI,CAAC;AAAA,IAC3C;AAAA,EACD,SAAS,OAAO;AACf,UAAM,UAAU,qBAAqB,KAAK;AAE1C,WAAO,EAAE,SAAS,OAAO,QAAQ,CAAC,OAAO,GAAG,QAAQ,CAAC,EAAE,MAAM,CAAC,GAAG,QAAQ,CAAC,EAAE;AAAA,EAC7E;AACD;AASA,SAAS,qBAAqB,OAAwB;AACrD,MAAI,iBAAiB,MAAO,QAAO,MAAM;AACzC,MAAI,OAAO,UAAU,SAAU,QAAO;AAEtC,SAAO;AACR;AAGA,SAAS,gBACR,QACkC;AAClC,QAAM,SAAS,OAAO,IAAI,YAAY;AAEtC,SAAO,EAAE,SAAS,OAAO,QAAQ,OAAO,IAAI,aAAa,GAAG,OAAO;AACpE;AAEA,SAAS,aAAa,OAAoD;AACzE,SAAO,EAAE,MAAM,uBAAuB,MAAM,YAAY,GAAG,SAAS,MAAM,QAAQ;AACnF;AAQA,SAAS,cAAc,OAAiC;AACvD,QAAM,OAAO,MAAM,KAAK,KAAK,GAAG;AAEhC,SAAO,OAAO,GAAG,IAAI,KAAK,MAAM,OAAO,KAAK,MAAM;AACnD;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lankajs/typebox",
3
- "version": "1.0.1",
3
+ "version": "2.0.0",
4
4
  "type": "module",
5
5
  "description": "module: A bridge for the one library in the family with no Standard Schema, and a compiled checker it caches.",
6
6
  "license": "MIT",
@@ -26,13 +26,14 @@
26
26
  "skills"
27
27
  ],
28
28
  "dependencies": {
29
- "lanka": "^2.0.0"
29
+ "lanka": "^2.2.0"
30
30
  },
31
31
  "devDependencies": {
32
- "@lankajs/tool-testing": "^2.0.0"
32
+ "typebox": "^1.3.34",
33
+ "@lankajs/tool-testing": "^2.2.0"
33
34
  },
34
35
  "peerDependencies": {
35
- "@sinclair/typebox": "^0.34.52"
36
+ "typebox": "^1.3.34"
36
37
  },
37
38
  "scripts": {
38
39
  "build": "tsup",
@@ -5,7 +5,7 @@ license: MIT
5
5
  metadata:
6
6
  author: lankajs
7
7
  package: @lankajs/typebox
8
- version: "1.0.1"
8
+ version: "2.0.0"
9
9
  ---
10
10
 
11
11
  # @lankajs/typebox
@@ -28,7 +28,7 @@ Install this **or** another package from `modules/validators/`, never two.
28
28
  ## Declare schemas at MODULE level
29
29
 
30
30
  This is the one rule that costs real performance if ignored.
31
- `TypeCompiler.Compile(schema)` produces the fastest validator in JavaScript, and
31
+ `Compile(schema)` produces the fastest validator in JavaScript, and
32
32
  compiling is the slow part. The package compiles each schema once and caches it
33
33
  in a `WeakMap` keyed by the schema OBJECT.
34
34
 
@@ -80,7 +80,7 @@ as a **number**, and `~1` / `~0` escapes are decoded. A root failure has an
80
80
  ## Mapping a wire format
81
81
 
82
82
  ```ts
83
- const todoFromApi = Type.Transform(Type.Object({ todo_id: Type.Number() }))
83
+ const todoFromApi = Type.Codec(Type.Object({ todo_id: Type.Number() }))
84
84
  .Decode((wire) => ({ id: wire.todo_id }))
85
85
  .Encode((domain) => ({ todo_id: domain.id }));
86
86
 
@@ -88,7 +88,7 @@ const domain = lankaTypeBoxValidator.validate(todoFromApi, wire, "todos.map");
88
88
  return lankaTypeBoxValidator.validate(todoSchema, domain, "todos.check");
89
89
  ```
90
90
 
91
- A schema without transforms never pays for the decode pass. A decode function
91
+ A schema without a codec never pays for the decode pass. A decode function
92
92
  that throws is a refused body, not a crash.
93
93
 
94
94
  ## Never do these
@@ -101,16 +101,16 @@ that throws is a refused body, not a crash.
101
101
  - **Never install two validation packages.**
102
102
  - **Never expect `validate` to return a result object.** It throws;
103
103
  `validateSafe` returns.
104
- - **Never trust `format: "email"` without registering the format.** Unregistered,
105
- it is reported as `Unknown format` and checks nothing. Use `pattern`, or
106
- register it at start-up.
104
+ - **Never trust a `format` TypeBox does not know.** The standard names are
105
+ built in; any other name a typo, a custom format never registered — is
106
+ accepted silently and checks nothing. Use `pattern`, or register it at start-up.
107
107
 
108
108
  ## Symptom → cause
109
109
 
110
110
  | What you see | What it is |
111
111
  | ------------------------------------- | ----------------------------------------------- |
112
112
  | validation slower than expected | a schema rebuilt per render — nothing is cached |
113
- | "Unknown format" in a message | a `format` keyword nothing registered |
113
+ | a `format` that lets everything pass | a name TypeBox does not know, or a typo |
114
114
  | a path like `/a/b` reaching your form | not from here — this package returns segments |
115
115
  | "invalid response" with no idea which | a label that does not identify the call |
116
116
 
@@ -1,8 +1,8 @@
1
1
  <!-- Generated from modules/validators/typebox/GUIDE.md by scripts/skills.mjs. Edit the guide. -->
2
2
 
3
- > **`@lankajs/typebox@1.0.1`** — this document describes that version.
3
+ > **`@lankajs/typebox@2.0.0`** — this document describes that version.
4
4
  >
5
- > Install: `npm install @lankajs/typebox @sinclair/typebox zustand` (the peers are not optional; only npm adds a missing one for you).
5
+ > Install: `npm install @lankajs/typebox typebox zustand` (the peers are not optional; only npm adds a missing one for you).
6
6
  >
7
7
  > Complete code, compiled and run in CI: [modules/validators/typebox/_playground/playground.test.ts](https://github.com/lankajs/lanka/blob/main/modules/validators/typebox/_playground/playground.test.ts)
8
8
 
@@ -15,7 +15,7 @@ compiled checker it caches, plus a `TLankaInferred` helper.
15
15
 
16
16
  - why this package is required rather than a matter of taste
17
17
  - why your schemas must live at module level, and what it costs when they do not
18
- - how to map a wire format with `Type.Transform`
18
+ - how to map a wire format with `Type.Codec`
19
19
 
20
20
  ## When to reach for this
21
21
 
@@ -30,9 +30,14 @@ The moment your schemas are TypeBox. Core's validator cannot be handed one.
30
30
  ## Install
31
31
 
32
32
  ```bash
33
- npm install @lankajs/typebox @sinclair/typebox zustand
33
+ npm install @lankajs/typebox typebox zustand
34
34
  ```
35
35
 
36
+ > [!NOTE]
37
+ > This is TypeBox 1.x — the `typebox` package. An application on TypeBox 0.34
38
+ > (`@sinclair/typebox`) stays on `@lankajs/typebox@1`, whose API is the one
39
+ > below with `Type.Transform` in place of `Type.Codec`.
40
+
36
41
  > [!IMPORTANT]
37
42
  > `zustand` is `lanka`'s own peer: npm adds a missing peer for you and pnpm
38
43
  > does not, so the line names it.
@@ -48,7 +53,7 @@ version of "just use `lankaStandardValidator`" that compiles or runs.
48
53
 
49
54
  ```ts
50
55
  import { lankaTypeBoxValidator } from "@lankajs/typebox";
51
- import { Type } from "@sinclair/typebox";
56
+ import { Type } from "typebox";
52
57
 
53
58
  // at MODULE level — see below, this is not a style point
54
59
  const todoSchema = Type.Object({
@@ -70,7 +75,7 @@ branch to handle. `validateSafe` is for a form, where failure is ordinary.
70
75
 
71
76
  ## Declare schemas at module level
72
77
 
73
- `TypeCompiler.Compile(schema)` turns a schema into a generated function — the
78
+ `Compile(schema)` turns a schema into a generated function — the
74
79
  fastest validator in JavaScript — and **compiling is the slow part**. This
75
80
  package compiles each schema once and keeps the result in a `WeakMap` keyed by
76
81
  the schema object.
@@ -119,10 +124,10 @@ the form's root, not an input named `""`.
119
124
  ## Mapping a wire format
120
125
 
121
126
  A mapping is a schema, not an adapter layer. In TypeBox it is
122
- `Type.Transform(...).Decode(...)`:
127
+ `Type.Codec(...).Decode(...)`:
123
128
 
124
129
  ```ts
125
- const todoFromApi = Type.Transform(
130
+ const todoFromApi = Type.Codec(
126
131
  Type.Object({
127
132
  todo_id: Type.Number(),
128
133
  is_done: Type.Union([Type.Literal(0), Type.Literal(1)]),
@@ -135,19 +140,21 @@ const domain = lankaTypeBoxValidator.validate(todoFromApi, wire, "todos.map");
135
140
  return lankaTypeBoxValidator.validate(todoSchema, domain, "todos.check");
136
141
  ```
137
142
 
138
- A schema **without** transforms never pays for the decode pass: whether it has
139
- one is asked once and cached beside the compiled checker, because `Value.Decode`
140
- re-checks and calling it unconditionally would validate every body twice.
143
+ A schema **without** a codec never pays for the decode pass: whether it has one
144
+ is asked once and cached beside the compiled checker. A schema with one is
145
+ decoded on a COPY — your body is left as it arrived and keeps the properties
146
+ the schema did not name, exactly as a schema without a codec does.
141
147
 
142
148
  A decode function that throws is a **refused body**, not a crash — it comes back
143
149
  as a failure, because `validateSafe` promises to throw nothing.
144
150
 
145
151
  ## Formats are a registry, not a keyword
146
152
 
147
- `Type.String({ format: "email" })` checks nothing until the application registers
148
- that format with TypeBox. Unregistered, it is reported as `Unknown format` a
149
- rule that looks present in the schema and is not. Use `pattern`, or register the
150
- format at start-up.
153
+ TypeBox 1.x checks the standard formats — `email`, `uuid`, `date-time` and the
154
+ rest by itself. A format name it does NOT know is accepted silently: a typo, or
155
+ a format of your own that was never registered, is a rule that looks present in
156
+ the schema and checks nothing. Register your own formats at start-up, and prefer
157
+ `pattern` for anything that is not a standard name.
151
158
 
152
159
  ## Where to validate
153
160
 
@@ -165,14 +172,14 @@ level.
165
172
  **Expecting `validate` to return a result object.** It throws. `validateSafe`
166
173
  returns.
167
174
 
168
- **Trusting `format` without registering it.**
175
+ **Trusting a `format` TypeBox does not know.** It accepts everything.
169
176
 
170
177
  ## Recap
171
178
 
172
179
  - TypeBox has no Standard Schema, so this package is what makes it work with lanka.
173
180
  - Schemas at module level: the compiled checker is cached by object identity.
174
181
  - Paths come back as segments, with JSON Pointer escapes decoded.
175
- - A mapping is `Type.Transform`, and only a schema that has one pays for the decode pass.
182
+ - A mapping is `Type.Codec`, and only a schema that has one pays for the decode pass.
176
183
 
177
184
  ---
178
185