@nifrajs/schema 0.1.0-alpha.2 → 0.1.0-beta.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.
@@ -1 +1 @@
1
- {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiC,gBAAgB,EAAiB,MAAM,eAAe,CAAA;AACnG,OAAO,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAA;AAIxD;;;;;;;GAOG;AACH,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,IAAI,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG;IAC9F,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAA;CACvB,CAAA;AAeD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,OAAO,EAAE,MAAM,EAAE,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CA6BxE"}
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiC,gBAAgB,EAAiB,MAAM,eAAe,CAAA;AACnG,OAAO,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAA;AAKxD;;;;;;;GAOG;AACH,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,IAAI,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG;IAC9F,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAA;CACvB,CAAA;AAeD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,OAAO,EAAE,MAAM,EAAE,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CAiCxE"}
package/dist/adapter.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { TypeCompiler } from "@sinclair/typebox/compiler";
2
2
  import { Value } from "@sinclair/typebox/value";
3
+ import { ensureDefaultFormats } from "./formats.js";
3
4
  /**
4
5
  * Map a TypeBox validation error to a Standard Schema issue. TypeBox reports JSON
5
6
  * Pointers (`"/name"`, `"/items/0"`); Standard Schema wants a segment array (`""` is
@@ -38,6 +39,10 @@ export function fromTypeBox(schema) {
38
39
  version: 1,
39
40
  vendor: "nifra",
40
41
  validate: (value) => {
42
+ // Install the standard string formats before the first Compile/Check. Driven from this
43
+ // reachable path (not a top-level import side effect) so a production bundle can't
44
+ // tree-shake the registration away — see ./formats.ts. Idempotent, ~free after first call.
45
+ ensureDefaultFormats();
41
46
  if (compiled === undefined && !evalFree) {
42
47
  try {
43
48
  compiled = TypeCompiler.Compile(schema);
@@ -1 +1 @@
1
- {"version":3,"file":"adapter.js","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAEA,OAAO,EAAkB,YAAY,EAAE,MAAM,4BAA4B,CAAA;AACzE,OAAO,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAA;AAc/C;;;;;GAKG;AACH,SAAS,OAAO,CAAC,KAA0D;IACzE,OAAO;QACL,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,IAAI,EAAE,KAAK,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC;KACrE,CAAA;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,WAAW,CAAoB,MAAS;IACtD,IAAI,QAAkC,CAAA;IACtC,IAAI,QAAQ,GAAG,KAAK,CAAA;IACpB,OAAO;QACL,WAAW,EAAE;YACX,OAAO,EAAE,CAAC;YACV,MAAM,EAAE,OAAO;YACf,QAAQ,EAAE,CAAC,KAAc,EAA6B,EAAE;gBACtD,IAAI,QAAQ,KAAK,SAAS,IAAI,CAAC,QAAQ,EAAE,CAAC;oBACxC,IAAI,CAAC;wBACH,QAAQ,GAAG,YAAY,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;oBACzC,CAAC;oBAAC,MAAM,CAAC;wBACP,8EAA8E;wBAC9E,QAAQ,GAAG,IAAI,CAAA;oBACjB,CAAC;gBACH,CAAC;gBACD,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;oBAC3B,IAAI,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC;wBAAE,OAAO,EAAE,KAAK,EAAE,CAAA;oBAC3C,OAAO,EAAE,MAAM,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAA;gBAC7D,CAAC;gBACD,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC;oBAAE,OAAO,EAAE,KAAK,EAAE,CAAA;gBAChD,OAAO,EAAE,MAAM,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAA;YAClE,CAAC;YACD,oEAAoE;YACpE,8EAA8E;YAC9E,KAAK,EAAE,SAA2D;SACnE;QACD,UAAU,EAAE,MAAM;KACnB,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"adapter.js","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAEA,OAAO,EAAkB,YAAY,EAAE,MAAM,4BAA4B,CAAA;AACzE,OAAO,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAA;AAC/C,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AAcnD;;;;;GAKG;AACH,SAAS,OAAO,CAAC,KAA0D;IACzE,OAAO;QACL,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,IAAI,EAAE,KAAK,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC;KACrE,CAAA;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,WAAW,CAAoB,MAAS;IACtD,IAAI,QAAkC,CAAA;IACtC,IAAI,QAAQ,GAAG,KAAK,CAAA;IACpB,OAAO;QACL,WAAW,EAAE;YACX,OAAO,EAAE,CAAC;YACV,MAAM,EAAE,OAAO;YACf,QAAQ,EAAE,CAAC,KAAc,EAA6B,EAAE;gBACtD,uFAAuF;gBACvF,mFAAmF;gBACnF,2FAA2F;gBAC3F,oBAAoB,EAAE,CAAA;gBACtB,IAAI,QAAQ,KAAK,SAAS,IAAI,CAAC,QAAQ,EAAE,CAAC;oBACxC,IAAI,CAAC;wBACH,QAAQ,GAAG,YAAY,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;oBACzC,CAAC;oBAAC,MAAM,CAAC;wBACP,8EAA8E;wBAC9E,QAAQ,GAAG,IAAI,CAAA;oBACjB,CAAC;gBACH,CAAC;gBACD,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;oBAC3B,IAAI,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC;wBAAE,OAAO,EAAE,KAAK,EAAE,CAAA;oBAC3C,OAAO,EAAE,MAAM,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAA;gBAC7D,CAAC;gBACD,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC;oBAAE,OAAO,EAAE,KAAK,EAAE,CAAA;gBAChD,OAAO,EAAE,MAAM,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAA;YAClE,CAAC;YACD,oEAAoE;YACpE,8EAA8E;YAC9E,KAAK,EAAE,SAA2D;SACnE;QACD,UAAU,EAAE,MAAM;KACnB,CAAA;AACH,CAAC"}
package/dist/formats.d.ts CHANGED
@@ -1,3 +1,19 @@
1
1
  /** Register (or override) a string format usable as `t.string({ format: name })`. */
2
2
  export declare function registerFormat(name: string, validate: (value: string) => boolean): void;
3
+ /**
4
+ * Install the standard string formats into TypeBox's global `FormatRegistry`, once.
5
+ *
6
+ * This is driven from the validate path (see `./adapter.ts`) rather than run as a bare
7
+ * top-level import side effect *on purpose*: a production bundler (`Bun.build`, esbuild)
8
+ * tree-shakes a module that is only imported for its effect, and Bun.build does **not**
9
+ * honor the `sideEffects` package.json field — so the old top-level registration loop was
10
+ * dropped from prod server bundles, making every `t.string({ format })` reject with
11
+ * "Unknown format" while unbundled `nifra dev` worked. Calling this from code the validator
12
+ * actually reaches keeps the registration in the bundle.
13
+ *
14
+ * Idempotent and cheap to call on every validate (a single boolean check after the first
15
+ * run). The `Has` guard means a format a consumer registered first via {@link registerFormat}
16
+ * is left untouched.
17
+ */
18
+ export declare function ensureDefaultFormats(): void;
3
19
  //# sourceMappingURL=formats.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"formats.d.ts","sourceRoot":"","sources":["../src/formats.ts"],"names":[],"mappings":"AAyBA,qFAAqF;AACrF,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,GAAG,IAAI,CAEvF"}
1
+ {"version":3,"file":"formats.d.ts","sourceRoot":"","sources":["../src/formats.ts"],"names":[],"mappings":"AAyBA,qFAAqF;AACrF,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,GAAG,IAAI,CAEvF;AAID;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,oBAAoB,IAAI,IAAI,CAM3C"}
package/dist/formats.js CHANGED
@@ -8,9 +8,9 @@ import { FormatRegistry } from "@sinclair/typebox";
8
8
  *
9
9
  * Patterns are pragmatic (not full RFC grammars) but reject obvious garbage. Need a
10
10
  * stricter rule or a format not listed here (e.g. `ipv6`, `hostname`)? Register it
11
- * with {@link registerFormat}. Importing this module performs the registration as a
12
- * one-time side effect; the `Has` guard means an app that registered its own
13
- * validator first wins.
11
+ * with {@link registerFormat}. The defaults are installed lazily by
12
+ * {@link ensureDefaultFormats} from the validate path; the `Has` guard means an app
13
+ * that registered its own validator first wins.
14
14
  */
15
15
  const DEFAULT_FORMATS = {
16
16
  email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
@@ -25,8 +25,29 @@ const DEFAULT_FORMATS = {
25
25
  export function registerFormat(name, validate) {
26
26
  FormatRegistry.Set(name, validate);
27
27
  }
28
- for (const [name, pattern] of Object.entries(DEFAULT_FORMATS)) {
29
- if (!FormatRegistry.Has(name))
30
- FormatRegistry.Set(name, (value) => pattern.test(value));
28
+ let defaultsRegistered = false;
29
+ /**
30
+ * Install the standard string formats into TypeBox's global `FormatRegistry`, once.
31
+ *
32
+ * This is driven from the validate path (see `./adapter.ts`) rather than run as a bare
33
+ * top-level import side effect *on purpose*: a production bundler (`Bun.build`, esbuild)
34
+ * tree-shakes a module that is only imported for its effect, and Bun.build does **not**
35
+ * honor the `sideEffects` package.json field — so the old top-level registration loop was
36
+ * dropped from prod server bundles, making every `t.string({ format })` reject with
37
+ * "Unknown format" while unbundled `nifra dev` worked. Calling this from code the validator
38
+ * actually reaches keeps the registration in the bundle.
39
+ *
40
+ * Idempotent and cheap to call on every validate (a single boolean check after the first
41
+ * run). The `Has` guard means a format a consumer registered first via {@link registerFormat}
42
+ * is left untouched.
43
+ */
44
+ export function ensureDefaultFormats() {
45
+ if (defaultsRegistered)
46
+ return;
47
+ defaultsRegistered = true;
48
+ for (const [name, pattern] of Object.entries(DEFAULT_FORMATS)) {
49
+ if (!FormatRegistry.Has(name))
50
+ FormatRegistry.Set(name, (value) => pattern.test(value));
51
+ }
31
52
  }
32
53
  //# sourceMappingURL=formats.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"formats.js","sourceRoot":"","sources":["../src/formats.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAElD;;;;;;;;;;;;GAYG;AACH,MAAM,eAAe,GAAqC;IACxD,KAAK,EAAE,4BAA4B;IACnC,IAAI,EAAE,iEAAiE;IACvE,WAAW,EAAE,4EAA4E;IACzF,IAAI,EAAE,qBAAqB;IAC3B,IAAI,EAAE,wDAAwD;IAC9D,GAAG,EAAE,0BAA0B;IAC/B,IAAI,EAAE,qEAAqE;CAC5E,CAAA;AAED,qFAAqF;AACrF,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,QAAoC;IAC/E,cAAc,CAAC,GAAG,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAA;AACpC,CAAC;AAED,KAAK,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,eAAe,CAAC,EAAE,CAAC;IAC9D,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,cAAc,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAA;AACzF,CAAC"}
1
+ {"version":3,"file":"formats.js","sourceRoot":"","sources":["../src/formats.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAElD;;;;;;;;;;;;GAYG;AACH,MAAM,eAAe,GAAqC;IACxD,KAAK,EAAE,4BAA4B;IACnC,IAAI,EAAE,iEAAiE;IACvE,WAAW,EAAE,4EAA4E;IACzF,IAAI,EAAE,qBAAqB;IAC3B,IAAI,EAAE,wDAAwD;IAC9D,GAAG,EAAE,0BAA0B;IAC/B,IAAI,EAAE,qEAAqE;CAC5E,CAAA;AAED,qFAAqF;AACrF,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,QAAoC;IAC/E,cAAc,CAAC,GAAG,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAA;AACpC,CAAC;AAED,IAAI,kBAAkB,GAAG,KAAK,CAAA;AAE9B;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,oBAAoB;IAClC,IAAI,kBAAkB;QAAE,OAAM;IAC9B,kBAAkB,GAAG,IAAI,CAAA;IACzB,KAAK,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,eAAe,CAAC,EAAE,CAAC;QAC9D,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,cAAc,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAA;IACzF,CAAC;AACH,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,WAAW,EAAE,KAAK,WAAW,EAAE,MAAM,cAAc,CAAA;AAG5D,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAC7C,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,WAAW,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAChF,OAAO,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,WAAW,EAAE,KAAK,WAAW,EAAE,MAAM,cAAc,CAAA;AAI5D,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAC7C,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,WAAW,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAChF,OAAO,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAA"}
package/dist/index.js CHANGED
@@ -7,8 +7,9 @@
7
7
  * a validator; this package is the opt-in default.
8
8
  */
9
9
  export { fromTypeBox } from "./adapter.js";
10
- // Re-exporting evaluates ./formats.ts, registering the standard string formats so
11
- // `t.string({ format })` validates instead of rejecting everything.
10
+ // `registerFormat` adds/overrides a custom string format. The standard formats (email, uuid, …)
11
+ // install themselves lazily from the validate path (see ./adapter.ts) — NOT as an import side
12
+ // effect of this module, which a production bundler would tree-shake away.
12
13
  export { registerFormat } from "./formats.js";
13
14
  export { toOpenAPI } from "./openapi.js";
14
15
  export { t } from "./t.js";
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,WAAW,EAAoB,MAAM,cAAc,CAAA;AAC5D,kFAAkF;AAClF,oEAAoE;AACpE,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAC7C,OAAO,EAA0C,SAAS,EAAE,MAAM,cAAc,CAAA;AAChF,OAAO,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,WAAW,EAAoB,MAAM,cAAc,CAAA;AAC5D,gGAAgG;AAChG,8FAA8F;AAC9F,2EAA2E;AAC3E,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAC7C,OAAO,EAA0C,SAAS,EAAE,MAAM,cAAc,CAAA;AAChF,OAAO,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nifrajs/schema",
3
- "version": "0.1.0-alpha.2",
3
+ "version": "0.1.0-beta.0",
4
4
  "description": "Built-in schema builder `t` — TypeBox-backed (free JSON Schema + compiled validators), implements Standard Schema so nifra routes validate it natively.",
5
5
  "keywords": [
6
6
  "bun",
@@ -35,11 +35,8 @@
35
35
  "dist",
36
36
  "src"
37
37
  ],
38
- "comment:sideEffects": "formats.ts registers string formats into TypeBox's global FormatRegistry on import — that file must not be tree-shaken away, so it (not the package) is marked side-effectful.",
39
- "sideEffects": [
40
- "./src/formats.ts",
41
- "./dist/formats.js"
42
- ],
38
+ "comment:sideEffects": "No module here runs work at import time: the standard string formats install lazily from the validate path (src/adapter.ts → src/formats.ts ensureDefaultFormats), not as a top-level import side effect. The previous `sideEffects: [formats]` array did not survive bundling — Bun.build ignores the field — which is why formats were dropped from prod bundles. `false` is accurate now and lets consumers tree-shake freely.",
39
+ "sideEffects": false,
43
40
  "scripts": {
44
41
  "build": "tsc -p tsconfig.build.json && bun run ../../scripts/fix-dts.ts dist"
45
42
  },
package/src/adapter.ts CHANGED
@@ -2,6 +2,7 @@ import type { StandardIssue, StandardResult, StandardSchemaV1, StandardTypes } f
2
2
  import type { Static, TSchema } from "@sinclair/typebox"
3
3
  import { type TypeCheck, TypeCompiler } from "@sinclair/typebox/compiler"
4
4
  import { Value } from "@sinclair/typebox/value"
5
+ import { ensureDefaultFormats } from "./formats.ts"
5
6
 
6
7
  /**
7
8
  * A `t` schema. It is a Standard Schema (so any nifra route validates it with no
@@ -54,6 +55,10 @@ export function fromTypeBox<T extends TSchema>(schema: T): NifraSchema<T> {
54
55
  version: 1,
55
56
  vendor: "nifra",
56
57
  validate: (value: unknown): StandardResult<Static<T>> => {
58
+ // Install the standard string formats before the first Compile/Check. Driven from this
59
+ // reachable path (not a top-level import side effect) so a production bundle can't
60
+ // tree-shake the registration away — see ./formats.ts. Idempotent, ~free after first call.
61
+ ensureDefaultFormats()
57
62
  if (compiled === undefined && !evalFree) {
58
63
  try {
59
64
  compiled = TypeCompiler.Compile(schema)
package/src/formats.ts CHANGED
@@ -9,9 +9,9 @@ import { FormatRegistry } from "@sinclair/typebox"
9
9
  *
10
10
  * Patterns are pragmatic (not full RFC grammars) but reject obvious garbage. Need a
11
11
  * stricter rule or a format not listed here (e.g. `ipv6`, `hostname`)? Register it
12
- * with {@link registerFormat}. Importing this module performs the registration as a
13
- * one-time side effect; the `Has` guard means an app that registered its own
14
- * validator first wins.
12
+ * with {@link registerFormat}. The defaults are installed lazily by
13
+ * {@link ensureDefaultFormats} from the validate path; the `Has` guard means an app
14
+ * that registered its own validator first wins.
15
15
  */
16
16
  const DEFAULT_FORMATS: Readonly<Record<string, RegExp>> = {
17
17
  email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
@@ -28,6 +28,27 @@ export function registerFormat(name: string, validate: (value: string) => boolea
28
28
  FormatRegistry.Set(name, validate)
29
29
  }
30
30
 
31
- for (const [name, pattern] of Object.entries(DEFAULT_FORMATS)) {
32
- if (!FormatRegistry.Has(name)) FormatRegistry.Set(name, (value) => pattern.test(value))
31
+ let defaultsRegistered = false
32
+
33
+ /**
34
+ * Install the standard string formats into TypeBox's global `FormatRegistry`, once.
35
+ *
36
+ * This is driven from the validate path (see `./adapter.ts`) rather than run as a bare
37
+ * top-level import side effect *on purpose*: a production bundler (`Bun.build`, esbuild)
38
+ * tree-shakes a module that is only imported for its effect, and Bun.build does **not**
39
+ * honor the `sideEffects` package.json field — so the old top-level registration loop was
40
+ * dropped from prod server bundles, making every `t.string({ format })` reject with
41
+ * "Unknown format" while unbundled `nifra dev` worked. Calling this from code the validator
42
+ * actually reaches keeps the registration in the bundle.
43
+ *
44
+ * Idempotent and cheap to call on every validate (a single boolean check after the first
45
+ * run). The `Has` guard means a format a consumer registered first via {@link registerFormat}
46
+ * is left untouched.
47
+ */
48
+ export function ensureDefaultFormats(): void {
49
+ if (defaultsRegistered) return
50
+ defaultsRegistered = true
51
+ for (const [name, pattern] of Object.entries(DEFAULT_FORMATS)) {
52
+ if (!FormatRegistry.Has(name)) FormatRegistry.Set(name, (value) => pattern.test(value))
53
+ }
33
54
  }
package/src/index.ts CHANGED
@@ -7,8 +7,9 @@
7
7
  * a validator; this package is the opt-in default.
8
8
  */
9
9
  export { fromTypeBox, type NifraSchema } from "./adapter.ts"
10
- // Re-exporting evaluates ./formats.ts, registering the standard string formats so
11
- // `t.string({ format })` validates instead of rejecting everything.
10
+ // `registerFormat` adds/overrides a custom string format. The standard formats (email, uuid, …)
11
+ // install themselves lazily from the validate path (see ./adapter.ts) — NOT as an import side
12
+ // effect of this module, which a production bundler would tree-shake away.
12
13
  export { registerFormat } from "./formats.ts"
13
14
  export { type OpenAPIDocument, type OpenAPIInfo, toOpenAPI } from "./openapi.ts"
14
15
  export { t } from "./t.ts"