@fougere/schema 0.2.0-alpha.1 → 0.2.0-alpha.2

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.
Files changed (188) hide show
  1. package/dist/entity.d.ts +9 -106
  2. package/dist/entity.d.ts.map +1 -1
  3. package/dist/entity.js +16 -266
  4. package/dist/entity.js.map +1 -1
  5. package/dist/field/boundary.d.ts +26 -43
  6. package/dist/field/boundary.d.ts.map +1 -1
  7. package/dist/field/boundary.js +30 -42
  8. package/dist/field/boundary.js.map +1 -1
  9. package/dist/field/field.d.ts +13 -56
  10. package/dist/field/field.d.ts.map +1 -1
  11. package/dist/field/field.js +22 -26
  12. package/dist/field/field.js.map +1 -1
  13. package/dist/field/format.d.ts +29 -0
  14. package/dist/field/format.d.ts.map +1 -0
  15. package/dist/field/format.js +24 -0
  16. package/dist/field/format.js.map +1 -0
  17. package/dist/field/index.d.ts +7 -6
  18. package/dist/field/index.d.ts.map +1 -1
  19. package/dist/field/index.js +6 -5
  20. package/dist/field/index.js.map +1 -1
  21. package/dist/field/lifecycle.d.ts +15 -24
  22. package/dist/field/lifecycle.d.ts.map +1 -1
  23. package/dist/field/lifecycle.js +10 -17
  24. package/dist/field/lifecycle.js.map +1 -1
  25. package/dist/field/meta.d.ts +1 -5
  26. package/dist/field/meta.d.ts.map +1 -1
  27. package/dist/field/meta.js +2 -4
  28. package/dist/field/meta.js.map +1 -1
  29. package/dist/field/role.d.ts +15 -22
  30. package/dist/field/role.d.ts.map +1 -1
  31. package/dist/field/role.js +11 -31
  32. package/dist/field/role.js.map +1 -1
  33. package/dist/field/shape.d.ts +24 -12
  34. package/dist/field/shape.d.ts.map +1 -1
  35. package/dist/field/shape.js +45 -27
  36. package/dist/field/shape.js.map +1 -1
  37. package/dist/hints.d.ts +10 -15
  38. package/dist/hints.d.ts.map +1 -1
  39. package/dist/hints.js +22 -1
  40. package/dist/hints.js.map +1 -1
  41. package/dist/index.d.ts +37 -36
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js +37 -36
  44. package/dist/index.js.map +1 -1
  45. package/dist/name.d.ts +4 -12
  46. package/dist/name.d.ts.map +1 -1
  47. package/dist/name.js +4 -12
  48. package/dist/name.js.map +1 -1
  49. package/dist/projections/card.d.ts +39 -58
  50. package/dist/projections/card.d.ts.map +1 -1
  51. package/dist/projections/card.js.map +1 -1
  52. package/dist/projections/describe.d.ts +4 -4
  53. package/dist/projections/describe.d.ts.map +1 -1
  54. package/dist/projections/describe.js +4 -9
  55. package/dist/projections/describe.js.map +1 -1
  56. package/dist/projections/io.d.ts +5 -11
  57. package/dist/projections/io.d.ts.map +1 -1
  58. package/dist/projections/io.js +5 -11
  59. package/dist/projections/io.js.map +1 -1
  60. package/dist/projections/lifecycle.d.ts +5 -19
  61. package/dist/projections/lifecycle.d.ts.map +1 -1
  62. package/dist/projections/lifecycle.js +7 -29
  63. package/dist/projections/lifecycle.js.map +1 -1
  64. package/dist/projections/reconstruct.d.ts +11 -16
  65. package/dist/projections/reconstruct.d.ts.map +1 -1
  66. package/dist/projections/reconstruct.js +29 -32
  67. package/dist/projections/reconstruct.js.map +1 -1
  68. package/dist/projections/source.d.ts +11 -34
  69. package/dist/projections/source.d.ts.map +1 -1
  70. package/dist/projections/source.js +4 -23
  71. package/dist/projections/source.js.map +1 -1
  72. package/dist/projections/typescript.d.ts +6 -22
  73. package/dist/projections/typescript.d.ts.map +1 -1
  74. package/dist/projections/typescript.js +16 -43
  75. package/dist/projections/typescript.js.map +1 -1
  76. package/dist/schema/index.d.ts +3 -0
  77. package/dist/schema/index.d.ts.map +1 -0
  78. package/dist/schema/index.js +2 -0
  79. package/dist/schema/index.js.map +1 -0
  80. package/dist/schema/schema.d.ts +74 -0
  81. package/dist/schema/schema.d.ts.map +1 -0
  82. package/dist/schema/schema.js +162 -0
  83. package/dist/schema/schema.js.map +1 -0
  84. package/dist/schema/view.d.ts +26 -0
  85. package/dist/schema/view.d.ts.map +1 -0
  86. package/dist/schema/view.js +2 -0
  87. package/dist/schema/view.js.map +1 -0
  88. package/dist/unique.d.ts +12 -37
  89. package/dist/unique.d.ts.map +1 -1
  90. package/dist/unique.js +11 -34
  91. package/dist/unique.js.map +1 -1
  92. package/dist/validation/index.d.ts +4 -0
  93. package/dist/validation/index.d.ts.map +1 -0
  94. package/dist/validation/index.js +2 -0
  95. package/dist/validation/index.js.map +1 -0
  96. package/dist/validation/judge.d.ts +55 -0
  97. package/dist/validation/judge.d.ts.map +1 -0
  98. package/dist/validation/judge.js +351 -0
  99. package/dist/validation/judge.js.map +1 -0
  100. package/dist/validation/options.d.ts +6 -0
  101. package/dist/validation/options.d.ts.map +1 -0
  102. package/dist/validation/options.js +3 -0
  103. package/dist/validation/options.js.map +1 -0
  104. package/dist/validation/result.d.ts +20 -0
  105. package/dist/validation/result.d.ts.map +1 -0
  106. package/dist/validation/result.js +3 -0
  107. package/dist/validation/result.js.map +1 -0
  108. package/dist/vocabulary/bool.d.ts +1 -1
  109. package/dist/vocabulary/bool.d.ts.map +1 -1
  110. package/dist/vocabulary/bool.js +2 -2
  111. package/dist/vocabulary/bool.js.map +1 -1
  112. package/dist/vocabulary/created.d.ts +4 -10
  113. package/dist/vocabulary/created.d.ts.map +1 -1
  114. package/dist/vocabulary/created.js +5 -12
  115. package/dist/vocabulary/created.js.map +1 -1
  116. package/dist/vocabulary/date.d.ts +1 -1
  117. package/dist/vocabulary/date.d.ts.map +1 -1
  118. package/dist/vocabulary/date.js +2 -2
  119. package/dist/vocabulary/date.js.map +1 -1
  120. package/dist/vocabulary/immutable.d.ts +4 -6
  121. package/dist/vocabulary/immutable.d.ts.map +1 -1
  122. package/dist/vocabulary/immutable.js +3 -6
  123. package/dist/vocabulary/immutable.js.map +1 -1
  124. package/dist/vocabulary/indexed.d.ts +5 -16
  125. package/dist/vocabulary/indexed.d.ts.map +1 -1
  126. package/dist/vocabulary/indexed.js +4 -16
  127. package/dist/vocabulary/indexed.js.map +1 -1
  128. package/dist/vocabulary/json.d.ts +8 -13
  129. package/dist/vocabulary/json.d.ts.map +1 -1
  130. package/dist/vocabulary/json.js +3 -3
  131. package/dist/vocabulary/json.js.map +1 -1
  132. package/dist/vocabulary/list.d.ts +3 -6
  133. package/dist/vocabulary/list.d.ts.map +1 -1
  134. package/dist/vocabulary/list.js +6 -5
  135. package/dist/vocabulary/list.js.map +1 -1
  136. package/dist/vocabulary/many.d.ts +3 -4
  137. package/dist/vocabulary/many.d.ts.map +1 -1
  138. package/dist/vocabulary/many.js +5 -5
  139. package/dist/vocabulary/many.js.map +1 -1
  140. package/dist/vocabulary/nullable.d.ts +2 -4
  141. package/dist/vocabulary/nullable.d.ts.map +1 -1
  142. package/dist/vocabulary/nullable.js +4 -8
  143. package/dist/vocabulary/nullable.js.map +1 -1
  144. package/dist/vocabulary/number.d.ts +1 -1
  145. package/dist/vocabulary/number.d.ts.map +1 -1
  146. package/dist/vocabulary/number.js +2 -2
  147. package/dist/vocabulary/number.js.map +1 -1
  148. package/dist/vocabulary/oneOf.d.ts +1 -1
  149. package/dist/vocabulary/oneOf.d.ts.map +1 -1
  150. package/dist/vocabulary/oneOf.js +2 -2
  151. package/dist/vocabulary/oneOf.js.map +1 -1
  152. package/dist/vocabulary/optional.d.ts +1 -1
  153. package/dist/vocabulary/optional.d.ts.map +1 -1
  154. package/dist/vocabulary/optional.js +3 -3
  155. package/dist/vocabulary/optional.js.map +1 -1
  156. package/dist/vocabulary/primary.d.ts +5 -5
  157. package/dist/vocabulary/primary.d.ts.map +1 -1
  158. package/dist/vocabulary/primary.js +14 -11
  159. package/dist/vocabulary/primary.js.map +1 -1
  160. package/dist/vocabulary/readOnly.d.ts +4 -7
  161. package/dist/vocabulary/readOnly.d.ts.map +1 -1
  162. package/dist/vocabulary/readOnly.js +4 -7
  163. package/dist/vocabulary/readOnly.js.map +1 -1
  164. package/dist/vocabulary/ref.d.ts +1 -1
  165. package/dist/vocabulary/ref.d.ts.map +1 -1
  166. package/dist/vocabulary/ref.js +2 -2
  167. package/dist/vocabulary/ref.js.map +1 -1
  168. package/dist/vocabulary/text.d.ts +1 -1
  169. package/dist/vocabulary/text.d.ts.map +1 -1
  170. package/dist/vocabulary/text.js +2 -2
  171. package/dist/vocabulary/text.js.map +1 -1
  172. package/dist/vocabulary/unique.d.ts +8 -18
  173. package/dist/vocabulary/unique.d.ts.map +1 -1
  174. package/dist/vocabulary/unique.js +7 -18
  175. package/dist/vocabulary/unique.js.map +1 -1
  176. package/dist/vocabulary/updated.d.ts +2 -2
  177. package/dist/vocabulary/updated.d.ts.map +1 -1
  178. package/dist/vocabulary/updated.js +1 -2
  179. package/dist/vocabulary/updated.js.map +1 -1
  180. package/dist/vocabulary/writeOnly.d.ts +4 -6
  181. package/dist/vocabulary/writeOnly.d.ts.map +1 -1
  182. package/dist/vocabulary/writeOnly.js +4 -6
  183. package/dist/vocabulary/writeOnly.js.map +1 -1
  184. package/package.json +2 -2
  185. package/dist/projections/validation.d.ts +0 -57
  186. package/dist/projections/validation.d.ts.map +0 -1
  187. package/dist/projections/validation.js +0 -148
  188. package/dist/projections/validation.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"optional.d.ts","sourceRoot":"","sources":["../../src/vocabulary/optional.ts"],"names":[],"mappings":"AAAA,OAAO,EAA6B,KAAK,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAE1E;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG,IAAI,EAAE,IAAI,CAAC,CAKlE"}
1
+ {"version":3,"file":"optional.d.ts","sourceRoot":"","sources":["../../src/vocabulary/optional.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAE9D;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC,CAK5D"}
@@ -1,4 +1,4 @@
1
- import { cloneField, nullableShape } from '../field/index.js';
1
+ import { nullableShape } from '../field/index.js';
2
2
  /**
3
3
  * Make a field nullable AND omissible at creation — `null` enters the shape's
4
4
  * grammar (the `[T,'null']` union) and the lifecycle permits absence. The two
@@ -7,8 +7,8 @@ import { cloneField, nullableShape } from '../field/index.js';
7
7
  * answers absence, and with a value rather than an omission.
8
8
  */
9
9
  export function optional(field) {
10
- return cloneField(field, {
11
- shape: field.shape && nullableShape(field.shape),
10
+ return field.with({
11
+ shape: nullableShape(field.shape),
12
12
  lifecycle: { ...field.lifecycle, create: field.lifecycle?.create ?? 'optional' },
13
13
  });
14
14
  }
@@ -1 +1 @@
1
- {"version":3,"file":"optional.js","sourceRoot":"","sources":["../../src/vocabulary/optional.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,aAAa,EAAc,MAAM,mBAAmB,CAAC;AAE1E;;;;;;GAMG;AACH,MAAM,UAAU,QAAQ,CAAI,KAAe;IACzC,OAAO,UAAU,CAAC,KAAK,EAAE;QACvB,KAAK,EAAE,KAAK,CAAC,KAAK,IAAI,aAAa,CAAC,KAAK,CAAC,KAAK,CAAC;QAChD,SAAS,EAAE,EAAE,GAAG,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC,SAAS,EAAE,MAAM,IAAI,UAAU,EAAE;KACjF,CAA0B,CAAC;AAC9B,CAAC"}
1
+ {"version":3,"file":"optional.js","sourceRoot":"","sources":["../../src/vocabulary/optional.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAc,MAAM,mBAAmB,CAAC;AAE9D;;;;;;GAMG;AACH,MAAM,UAAU,QAAQ,CAAI,KAAe;IACzC,OAAO,KAAK,CAAC,IAAI,CAAW;QAC1B,KAAK,EAAE,aAAa,CAAC,KAAK,CAAC,KAAK,CAAC;QACjC,SAAS,EAAE,EAAE,GAAG,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC,SAAS,EAAE,MAAM,IAAI,UAAU,EAAE;KACjF,CAAC,CAAC;AACL,CAAC"}
@@ -1,4 +1,4 @@
1
- import { type Field, type GeneratorRef } from '../field/index.js';
1
+ import { Field, type GeneratorRef } from "../field/index.js";
2
2
  interface PrimaryOptions {
3
3
  /**
4
4
  * Generator token: a built-in preset, a name registered via
@@ -13,10 +13,10 @@ interface PrimaryOptions {
13
13
  * - `primary()` — auto-generated CUID2 string ID (default)
14
14
  * - `primary({ generate: 'uuid' })` — auto-generated UUID v4
15
15
  * - `primary({ generate: ['monId', fn] })` — custom generator, registered + named
16
- * - `primary(field)` — promote any field to primary key (no auto-generation;
17
- * the value must be supplied, so its `auto` flag is preserved)
16
+ * - `primary(field)` — promote any field to primary key (no auto-generation:
17
+ * the value must be supplied, so no `create` rule is stated)
18
18
  */
19
- export declare function primary(opts?: PrimaryOptions): Field<string, true>;
20
- export declare function primary<T, A extends boolean>(field: Field<T, A>): Field<T, A>;
19
+ export declare function primary(opts?: PrimaryOptions): Field<string>;
20
+ export declare function primary<T>(field: Field<T>): Field<T>;
21
21
  export {};
22
22
  //# sourceMappingURL=primary.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"primary.d.ts","sourceRoot":"","sources":["../../src/vocabulary/primary.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,KAAK,EACV,KAAK,YAAY,EAClB,MAAM,mBAAmB,CAAC;AAE3B,UAAU,cAAc;IACtB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,YAAY,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,MAAM,CAAC,CAAC;CAC5D;AAED;;;;;;;;GAQG;AACH,wBAAgB,OAAO,CAAC,IAAI,CAAC,EAAE,cAAc,GAAG,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;AACpE,wBAAgB,OAAO,CAAC,CAAC,EAAE,CAAC,SAAS,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"primary.d.ts","sourceRoot":"","sources":["../../src/vocabulary/primary.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAqB,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAGhF,UAAU,cAAc;IACtB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,YAAY,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,MAAM,CAAC,CAAC;CAC5D;AAED;;;;;;;;GAQG;AACH,wBAAgB,OAAO,CAAC,IAAI,CAAC,EAAE,cAAc,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;AAC9D,wBAAgB,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC"}
@@ -1,13 +1,16 @@
1
- import { cloneField, createField, registerGenerator, } from '../field/index.js';
1
+ import { Field, registerGenerator } from "../field/index.js";
2
+ import { Judge } from "../validation/index.js";
2
3
  export function primary(fieldOrOptions) {
3
- // primary(field) — promote an existing field to primary key. cloneField keeps
4
- // every axis (boundary, meta…); the role gains `primary`, and identity in the
5
- // graph implies immutability in time: `update: 'forbidden'` (decided 2026-07-15).
6
- if (fieldOrOptions && '__brand' in fieldOrOptions && fieldOrOptions.__brand === 'fougere_field') {
4
+ // primary(field) — promote an existing field to primary key. `with` keeps every axis
5
+ // (boundary, meta…); the role gains `primary`, and identity in the graph implies
6
+ // immutability in time: `update: 'forbidden'` (decided 2026-07-15).
7
+ // `isField` and not a brand: the two overloads are told apart by SHAPE, which a field
8
+ // always states and `PrimaryOptions` (a lone `generate?`) never does.
9
+ if (Judge.isField(fieldOrOptions)) {
7
10
  const field = fieldOrOptions;
8
- return cloneField(field, {
11
+ return field.with({
9
12
  role: { ...field.role, primary: true },
10
- lifecycle: { ...field.lifecycle, update: 'forbidden' },
13
+ lifecycle: { ...field.lifecycle, update: "forbidden" },
11
14
  });
12
15
  }
13
16
  // primary() or primary({ generate }) — auto-generated string ID
@@ -19,13 +22,13 @@ export function primary(fieldOrOptions) {
19
22
  generate = name;
20
23
  }
21
24
  else {
22
- generate = opts.generate ?? 'cuid2';
25
+ generate = opts.generate ?? "cuid2";
23
26
  }
24
- return createField({
25
- shape: { type: 'string' },
27
+ return new Field({
28
+ shape: { type: "string" },
26
29
  role: { primary: true },
27
30
  // An id is immutable by default: re-supplying it in a patch is an error.
28
- lifecycle: { create: { generate }, update: 'forbidden' },
31
+ lifecycle: { create: { generate }, update: "forbidden" },
29
32
  });
30
33
  }
31
34
  //# sourceMappingURL=primary.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"primary.js","sourceRoot":"","sources":["../../src/vocabulary/primary.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,WAAW,EACX,iBAAiB,GAIlB,MAAM,mBAAmB,CAAC;AAsB3B,MAAM,UAAU,OAAO,CAAC,cAA0C;IAChE,8EAA8E;IAC9E,8EAA8E;IAC9E,kFAAkF;IAClF,IAAI,cAAc,IAAI,SAAS,IAAI,cAAc,IAAI,cAAc,CAAC,OAAO,KAAK,eAAe,EAAE,CAAC;QAChG,MAAM,KAAK,GAAG,cAAc,CAAC;QAC7B,OAAO,UAAU,CAAC,KAAK,EAAE;YACvB,IAAI,EAAE,EAAE,GAAG,KAAK,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE;YACtC,SAAS,EAAE,EAAE,GAAG,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE;SACvD,CAAC,CAAC;IACL,CAAC;IAED,gEAAgE;IAChE,MAAM,IAAI,GAAG,CAAC,cAAc,IAAI,EAAE,CAAmB,CAAC;IACtD,IAAI,QAAsB,CAAC;IAC3B,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QACjC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC;QACjC,iBAAiB,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QAC5B,QAAQ,GAAG,IAAI,CAAC;IAClB,CAAC;SAAM,CAAC;QACN,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,OAAO,CAAC;IACtC,CAAC;IACD,OAAO,WAAW,CAAe;QAC/B,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;QACzB,IAAI,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE;QACvB,yEAAyE;QACzE,SAAS,EAAE,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE;KACzD,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"primary.js","sourceRoot":"","sources":["../../src/vocabulary/primary.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,iBAAiB,EAAqB,MAAM,mBAAmB,CAAC;AAChF,OAAO,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAC;AAsB/C,MAAM,UAAU,OAAO,CAAC,cAAuC;IAC7D,qFAAqF;IACrF,iFAAiF;IACjF,oEAAoE;IACpE,sFAAsF;IACtF,sEAAsE;IACtE,IAAI,KAAK,CAAC,OAAO,CAAC,cAAc,CAAC,EAAE,CAAC;QAClC,MAAM,KAAK,GAAG,cAAc,CAAC;QAC7B,OAAO,KAAK,CAAC,IAAI,CAAC;YAChB,IAAI,EAAE,EAAE,GAAG,KAAK,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE;YACtC,SAAS,EAAE,EAAE,GAAG,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE;SACvD,CAAC,CAAC;IACL,CAAC;IAED,gEAAgE;IAChE,MAAM,IAAI,GAAG,CAAC,cAAc,IAAI,EAAE,CAAmB,CAAC;IACtD,IAAI,QAAsB,CAAC;IAC3B,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QACjC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC;QACjC,iBAAiB,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QAC5B,QAAQ,GAAG,IAAI,CAAC;IAClB,CAAC;SAAM,CAAC;QACN,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,OAAO,CAAC;IACtC,CAAC;IACD,OAAO,IAAI,KAAK,CAAS;QACvB,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;QACzB,IAAI,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE;QACvB,yEAAyE;QACzE,SAAS,EAAE,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE;KACzD,CAAC,CAAC;AACL,CAAC"}
@@ -1,10 +1,7 @@
1
- import { type Field } from '../field/index.js';
1
+ import { Field } from '../field/index.js';
2
2
  /**
3
- * Close the field's INBOUND direction — emitted normally, never accepted from
4
- * a client (a server-owned field, e.g. a computed counter). Supplying it in a
5
- * create or patch input is a validation error ("Read-only"); its absence is
6
- * never "Required" (the server provides it). Input types exclude it.
7
- * The outbound direction (and its conversion) is untouched.
3
+ * Close the INBOUND direction — emitted normally, never accepted from a client. Supplying
4
+ * it is a "Read-only" error; its absence is never "Required". Outbound is untouched.
8
5
  */
9
- export declare function readOnly<T, A extends boolean>(field: Field<T, A>): Field<T, A>;
6
+ export declare function readOnly<T>(field: Field<T>): Field<T>;
10
7
  //# sourceMappingURL=readOnly.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"readOnly.d.ts","sourceRoot":"","sources":["../../src/vocabulary/readOnly.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiC,KAAK,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAE9E;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,CAAC,SAAS,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAE9E"}
1
+ {"version":3,"file":"readOnly.d.ts","sourceRoot":"","sources":["../../src/vocabulary/readOnly.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAoB,MAAM,mBAAmB,CAAC;AAE5D;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAErD"}
@@ -1,12 +1,9 @@
1
- import { createField, declaredBoundary } from '../field/index.js';
1
+ import { declaredBoundary } from '../field/index.js';
2
2
  /**
3
- * Close the field's INBOUND direction — emitted normally, never accepted from
4
- * a client (a server-owned field, e.g. a computed counter). Supplying it in a
5
- * create or patch input is a validation error ("Read-only"); its absence is
6
- * never "Required" (the server provides it). Input types exclude it.
7
- * The outbound direction (and its conversion) is untouched.
3
+ * Close the INBOUND direction — emitted normally, never accepted from a client. Supplying
4
+ * it is a "Read-only" error; its absence is never "Required". Outbound is untouched.
8
5
  */
9
6
  export function readOnly(field) {
10
- return createField({ ...field, boundary: { ...declaredBoundary(field), in: 'closed' } });
7
+ return field.with({ boundary: { ...declaredBoundary(field), in: 'closed' } });
11
8
  }
12
9
  //# sourceMappingURL=readOnly.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"readOnly.js","sourceRoot":"","sources":["../../src/vocabulary/readOnly.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAc,MAAM,mBAAmB,CAAC;AAE9E;;;;;;GAMG;AACH,MAAM,UAAU,QAAQ,CAAuB,KAAkB;IAC/D,OAAO,WAAW,CAAO,EAAE,GAAG,KAAK,EAAE,QAAQ,EAAE,EAAE,GAAG,gBAAgB,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC;AACjG,CAAC"}
1
+ {"version":3,"file":"readOnly.js","sourceRoot":"","sources":["../../src/vocabulary/readOnly.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAE5D;;;GAGG;AACH,MAAM,UAAU,QAAQ,CAAI,KAAe;IACzC,OAAO,KAAK,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,EAAE,GAAG,gBAAgB,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC;AAChF,CAAC"}
@@ -1,4 +1,4 @@
1
- import { type EntityConstructor, type Field } from '../field/index.js';
1
+ import { Field, type EntityConstructor } from '../field/index.js';
2
2
  export interface RefOptions {
3
3
  cascade?: boolean;
4
4
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ref.d.ts","sourceRoot":"","sources":["../../src/vocabulary/ref.ts"],"names":[],"mappings":"AAAA,OAAO,EAA8B,KAAK,iBAAiB,EAAE,KAAK,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAEnG,MAAM,WAAW,UAAU;IACzB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;;;;GAKG;AACH,wBAAgB,GAAG,CAAC,CAAC,SAAS,iBAAiB,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,UAAU,GAAG,KAAK,CAAC,MAAM,CAAC,CAWxG"}
1
+ {"version":3,"file":"ref.d.ts","sourceRoot":"","sources":["../../src/vocabulary/ref.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,EAAE,KAAK,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAEjF,MAAM,WAAW,UAAU;IACzB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;;;;GAKG;AACH,wBAAgB,GAAG,CAAC,CAAC,SAAS,iBAAiB,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,UAAU,GAAG,KAAK,CAAC,MAAM,CAAC,CAWxG"}
@@ -1,4 +1,4 @@
1
- import { createField, toTargetThunk } from '../field/index.js';
1
+ import { toTargetThunk, Field } from '../field/index.js';
2
2
  /**
3
3
  * A reference to another entity — a foreign-key string plus a one-relation role.
4
4
  * Pass the target class (`ref(Author)`) or, for circular/forward references, a
@@ -6,7 +6,7 @@ import { createField, toTargetThunk } from '../field/index.js';
6
6
  * — that path also flips the type to `string | null` (which `ref` alone never did).
7
7
  */
8
8
  export function ref(target, opts) {
9
- return createField({
9
+ return new Field({
10
10
  shape: { type: 'string' },
11
11
  role: {
12
12
  relation: {
@@ -1 +1 @@
1
- {"version":3,"file":"ref.js","sourceRoot":"","sources":["../../src/vocabulary/ref.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,aAAa,EAAsC,MAAM,mBAAmB,CAAC;AAMnG;;;;;GAKG;AACH,MAAM,UAAU,GAAG,CAA8B,MAAqB,EAAE,IAAiB;IACvF,OAAO,WAAW,CAAS;QACzB,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;QACzB,IAAI,EAAE;YACJ,QAAQ,EAAE;gBACR,EAAE,EAAE,aAAa,CAAC,MAAM,CAAC;gBACzB,IAAI,EAAE,KAAK;gBACX,QAAQ,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS;aAChD;SACF;KACF,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"ref.js","sourceRoot":"","sources":["../../src/vocabulary/ref.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,KAAK,EAA0B,MAAM,mBAAmB,CAAC;AAMjF;;;;;GAKG;AACH,MAAM,UAAU,GAAG,CAA8B,MAAqB,EAAE,IAAiB;IACvF,OAAO,IAAI,KAAK,CAAS;QACvB,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;QACzB,IAAI,EAAE;YACJ,QAAQ,EAAE;gBACR,EAAE,EAAE,aAAa,CAAC,MAAM,CAAC;gBACzB,IAAI,EAAE,KAAK;gBACX,QAAQ,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS;aAChD;SACF;KACF,CAAC,CAAC;AACL,CAAC"}
@@ -1,4 +1,4 @@
1
- import { type Field, type StringFormat } from '../field/index.js';
1
+ import { Field, type StringFormat } from '../field/index.js';
2
2
  export interface TextOptions {
3
3
  min?: number;
4
4
  max?: number;
@@ -1 +1 @@
1
- {"version":3,"file":"text.d.ts","sourceRoot":"","sources":["../../src/vocabulary/text.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,KAAK,EAAE,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAE/E,MAAM,WAAW,WAAW;IAC1B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,4FAA4F;IAC5F,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,wBAAgB,IAAI,CAAC,IAAI,CAAC,EAAE,WAAW,GAAG,KAAK,CAAC,MAAM,CAAC,CAMtD"}
1
+ {"version":3,"file":"text.d.ts","sourceRoot":"","sources":["../../src/vocabulary/text.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAE7D,MAAM,WAAW,WAAW;IAC1B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,4FAA4F;IAC5F,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,wBAAgB,IAAI,CAAC,IAAI,CAAC,EAAE,WAAW,GAAG,KAAK,CAAC,MAAM,CAAC,CAMtD"}
@@ -1,6 +1,6 @@
1
- import { createField } from '../field/index.js';
1
+ import { Field } from '../field/index.js';
2
2
  export function text(opts) {
3
- return createField({
3
+ return new Field({
4
4
  shape: { type: 'string', minLength: opts?.min, maxLength: opts?.max, pattern: opts?.pattern, format: opts?.format },
5
5
  lifecycle: opts?.default !== undefined ? { create: { value: opts.default } } : undefined,
6
6
  meta: opts?.description !== undefined ? { description: opts.description } : undefined,
@@ -1 +1 @@
1
- {"version":3,"file":"text.js","sourceRoot":"","sources":["../../src/vocabulary/text.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAiC,MAAM,mBAAmB,CAAC;AAY/E,MAAM,UAAU,IAAI,CAAC,IAAkB;IACrC,OAAO,WAAW,CAAS;QACzB,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE;QACnH,SAAS,EAAE,IAAI,EAAE,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS;QACxF,IAAI,EAAE,IAAI,EAAE,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,SAAS;KACtF,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"text.js","sourceRoot":"","sources":["../../src/vocabulary/text.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAqB,MAAM,mBAAmB,CAAC;AAY7D,MAAM,UAAU,IAAI,CAAC,IAAkB;IACrC,OAAO,IAAI,KAAK,CAAS;QACvB,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE;QACnH,SAAS,EAAE,IAAI,EAAE,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS;QACxF,IAAI,EAAE,IAAI,EAAE,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,SAAS;KACtF,CAAC,CAAC;AACL,CAAC"}
@@ -1,23 +1,13 @@
1
- import { type Field } from '../field/index.js';
1
+ import { Field } from '../field/index.js';
2
2
  /**
3
- * No two rows may carry the same value — a unique constraint of one, on the role axis.
3
+ * No two rows carry the same value — a unique constraint of one, on the role axis.
4
4
  *
5
- * The role axis is where it belongs because uniqueness is a fact about the field's
6
- * place in the entity, not about the values it accepts: `text({ max: 80 })` says what
7
- * a slug looks like, `unique(...)` says there is only one of each. A shape cannot
8
- * express it — judging a single value can never see the other rows.
5
+ * A shape cannot express it: judging one value never sees the other rows. So only the
6
+ * storage realizes it, and a collision surfaces as the driver's error. It travels on the
7
+ * card, so a consumer in another language reads the same promise.
9
8
  *
10
- * So the realization is the storage's, and only the storage's: the DDL emits the
11
- * constraint, the database enforces it on every write, including writes Fougere never
12
- * saw. A collision surfaces as the driver's error, not as a `validate()` failure.
13
- *
14
- * It also travels: `describe()` carries `role.unique` on the card, so a consumer in
15
- * another language reads the same promise — one that is now kept.
16
- *
17
- * The group is `[]` — the empty member list denoting THIS field, because a field does not
18
- * know its own key here: `slug` is named by the object literal `entity({...})` receives,
19
- * one call up. Keeping it empty rather than filling it in later means nothing to remap
20
- * when `rename()` moves the key, and one shape shared with the composite form.
9
+ * The group is `[]` — the empty list denoting THIS field, which does not know its own key.
10
+ * See the ⚠️ on the role axis.
21
11
  */
22
- export declare function unique<T, A extends boolean>(field: Field<T, A>): Field<T, A>;
12
+ export declare function unique<T>(field: Field<T>): Field<T>;
23
13
  //# sourceMappingURL=unique.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"unique.d.ts","sourceRoot":"","sources":["../../src/vocabulary/unique.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAE5D;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,MAAM,CAAC,CAAC,EAAE,CAAC,SAAS,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAE5E"}
1
+ {"version":3,"file":"unique.d.ts","sourceRoot":"","sources":["../../src/vocabulary/unique.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAE1C;;;;;;;;;GASG;AACH,wBAAgB,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAEnD"}
@@ -1,25 +1,14 @@
1
- import { createField } from '../field/index.js';
2
1
  /**
3
- * No two rows may carry the same value — a unique constraint of one, on the role axis.
2
+ * No two rows carry the same value — a unique constraint of one, on the role axis.
4
3
  *
5
- * The role axis is where it belongs because uniqueness is a fact about the field's
6
- * place in the entity, not about the values it accepts: `text({ max: 80 })` says what
7
- * a slug looks like, `unique(...)` says there is only one of each. A shape cannot
8
- * express it — judging a single value can never see the other rows.
4
+ * A shape cannot express it: judging one value never sees the other rows. So only the
5
+ * storage realizes it, and a collision surfaces as the driver's error. It travels on the
6
+ * card, so a consumer in another language reads the same promise.
9
7
  *
10
- * So the realization is the storage's, and only the storage's: the DDL emits the
11
- * constraint, the database enforces it on every write, including writes Fougere never
12
- * saw. A collision surfaces as the driver's error, not as a `validate()` failure.
13
- *
14
- * It also travels: `describe()` carries `role.unique` on the card, so a consumer in
15
- * another language reads the same promise — one that is now kept.
16
- *
17
- * The group is `[]` — the empty member list denoting THIS field, because a field does not
18
- * know its own key here: `slug` is named by the object literal `entity({...})` receives,
19
- * one call up. Keeping it empty rather than filling it in later means nothing to remap
20
- * when `rename()` moves the key, and one shape shared with the composite form.
8
+ * The group is `[]` — the empty list denoting THIS field, which does not know its own key.
9
+ * See the ⚠️ on the role axis.
21
10
  */
22
11
  export function unique(field) {
23
- return createField({ ...field, role: { ...field.role, unique: [[]] } });
12
+ return field.with({ role: { ...field.role, unique: [[]] } });
24
13
  }
25
14
  //# sourceMappingURL=unique.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"unique.js","sourceRoot":"","sources":["../../src/vocabulary/unique.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAc,MAAM,mBAAmB,CAAC;AAE5D;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,MAAM,CAAuB,KAAkB;IAC7D,OAAO,WAAW,CAAO,EAAE,GAAG,KAAK,EAAE,IAAI,EAAE,EAAE,GAAG,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;AAChF,CAAC"}
1
+ {"version":3,"file":"unique.js","sourceRoot":"","sources":["../../src/vocabulary/unique.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AACH,MAAM,UAAU,MAAM,CAAI,KAAe;IACvC,OAAO,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,GAAG,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;AAC/D,CAAC"}
@@ -1,8 +1,8 @@
1
- import { type Field } from '../field/index.js';
1
+ import { Field } from '../field/index.js';
2
2
  /**
3
3
  * `auto()`, plus the update stamp — the canonical `updatedAt` (Prisma
4
4
  * `@updatedAt`, Kysely-side `$onUpdate` equivalents). The only new fact is `update: 'now'`;
5
5
  * shape and creation rule are auto()'s, stated once there.
6
6
  */
7
- export declare function updated(): Field<Date, true>;
7
+ export declare function updated(): Field<Date>;
8
8
  //# sourceMappingURL=updated.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"updated.d.ts","sourceRoot":"","sources":["../../src/vocabulary/updated.ts"],"names":[],"mappings":"AACA,OAAO,EAAe,KAAK,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAE5D;;;;GAIG;AACH,wBAAgB,OAAO,IAAI,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,CAG3C"}
1
+ {"version":3,"file":"updated.d.ts","sourceRoot":"","sources":["../../src/vocabulary/updated.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAE1C;;;;GAIG;AACH,wBAAgB,OAAO,IAAI,KAAK,CAAC,IAAI,CAAC,CAGrC"}
@@ -1,5 +1,4 @@
1
1
  import { created } from './created.js';
2
- import { createField } from '../field/index.js';
3
2
  /**
4
3
  * `auto()`, plus the update stamp — the canonical `updatedAt` (Prisma
5
4
  * `@updatedAt`, Kysely-side `$onUpdate` equivalents). The only new fact is `update: 'now'`;
@@ -7,6 +6,6 @@ import { createField } from '../field/index.js';
7
6
  */
8
7
  export function updated() {
9
8
  const base = created();
10
- return createField({ ...base, lifecycle: { ...base.lifecycle, update: 'now' } });
9
+ return base.with({ lifecycle: { ...base.lifecycle, update: 'now' } });
11
10
  }
12
11
  //# sourceMappingURL=updated.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"updated.js","sourceRoot":"","sources":["../../src/vocabulary/updated.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,WAAW,EAAc,MAAM,mBAAmB,CAAC;AAE5D;;;;GAIG;AACH,MAAM,UAAU,OAAO;IACrB,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;IACvB,OAAO,WAAW,CAAa,EAAE,GAAG,IAAI,EAAE,SAAS,EAAE,EAAE,GAAG,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;AAC/F,CAAC"}
1
+ {"version":3,"file":"updated.js","sourceRoot":"","sources":["../../src/vocabulary/updated.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAGvC;;;;GAIG;AACH,MAAM,UAAU,OAAO;IACrB,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;IACvB,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,EAAE,GAAG,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;AACxE,CAAC"}
@@ -1,9 +1,7 @@
1
- import { type Field } from '../field/index.js';
1
+ import { Field } from '../field/index.js';
2
2
  /**
3
- * Close the field's OUTBOUND direction — accepted at create/patch, never
4
- * emitted (the canonical password). `encodeFields` omits it and the adapters
5
- * exclude it from their output types. The inbound direction (and its
6
- * conversion) is untouched.
3
+ * Close the OUTBOUND direction — accepted at create/patch, never emitted (the canonical
4
+ * password). `encodeFields` omits it. Inbound is untouched.
7
5
  */
8
- export declare function writeOnly<T, A extends boolean>(field: Field<T, A>): Field<T, A>;
6
+ export declare function writeOnly<T>(field: Field<T>): Field<T>;
9
7
  //# sourceMappingURL=writeOnly.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"writeOnly.d.ts","sourceRoot":"","sources":["../../src/vocabulary/writeOnly.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiC,KAAK,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAE9E;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,CAAC,SAAS,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAE/E"}
1
+ {"version":3,"file":"writeOnly.d.ts","sourceRoot":"","sources":["../../src/vocabulary/writeOnly.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAoB,MAAM,mBAAmB,CAAC;AAE5D;;;GAGG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAEtD"}
@@ -1,11 +1,9 @@
1
- import { createField, declaredBoundary } from '../field/index.js';
1
+ import { declaredBoundary } from '../field/index.js';
2
2
  /**
3
- * Close the field's OUTBOUND direction — accepted at create/patch, never
4
- * emitted (the canonical password). `encodeFields` omits it and the adapters
5
- * exclude it from their output types. The inbound direction (and its
6
- * conversion) is untouched.
3
+ * Close the OUTBOUND direction — accepted at create/patch, never emitted (the canonical
4
+ * password). `encodeFields` omits it. Inbound is untouched.
7
5
  */
8
6
  export function writeOnly(field) {
9
- return createField({ ...field, boundary: { ...declaredBoundary(field), out: 'closed' } });
7
+ return field.with({ boundary: { ...declaredBoundary(field), out: 'closed' } });
10
8
  }
11
9
  //# sourceMappingURL=writeOnly.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"writeOnly.js","sourceRoot":"","sources":["../../src/vocabulary/writeOnly.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAc,MAAM,mBAAmB,CAAC;AAE9E;;;;;GAKG;AACH,MAAM,UAAU,SAAS,CAAuB,KAAkB;IAChE,OAAO,WAAW,CAAO,EAAE,GAAG,KAAK,EAAE,QAAQ,EAAE,EAAE,GAAG,gBAAgB,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC;AAClG,CAAC"}
1
+ {"version":3,"file":"writeOnly.js","sourceRoot":"","sources":["../../src/vocabulary/writeOnly.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAE5D;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAI,KAAe;IAC1C,OAAO,KAAK,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,EAAE,GAAG,gBAAgB,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC;AACjF,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@fougere/schema",
3
- "version": "0.2.0-alpha.1",
4
- "description": "The entity and its 4 axes — shape, role, lifecycle, boundary. No engine dependency.",
3
+ "version": "0.2.0-alpha.2",
4
+ "description": "The entity and its 4 axes — shape, role, lifecycle, boundary. One judge, edge-safe.",
5
5
  "keywords": [
6
6
  "fougere",
7
7
  "typescript",
@@ -1,57 +0,0 @@
1
- import type { AnyField, Fields } from '../field/index.js';
2
- /**
3
- * Validation — the Field metadata IS the source of truth.
4
- *
5
- * Validation is a projection of one axis: `shape` (the value), a PURE predicate —
6
- * it answers "is this value valid?" and never mutates the value. Null included:
7
- * the shape's `[T,'null']` union is judged by the engine, there is no hand-rolled
8
- * null branch. It never looks at `role` (persistence/domain) — a foreign key is
9
- * just a string here. Converting a valid value (e.g. an ISO string → a `Date`)
10
- * is a separate step, the `boundary` axis, applied after the predicate passes.
11
- * Absence is judged by `lifecycle.create` — any create rule makes it legal and
12
- * the field is OMITTED from the result, never filled: realising the rule
13
- * (stamp 'now', apply { value }, generate) belongs to the storage adapter at
14
- * the point of persistence, whose insert returns the complete row.
15
- */
16
- export interface ValidationError {
17
- path: string;
18
- message: string;
19
- }
20
- export type ValidationResult<T> = {
21
- success: true;
22
- data: T;
23
- } | {
24
- success: false;
25
- errors: ValidationError[];
26
- };
27
- export type Checked = {
28
- value: unknown;
29
- } | {
30
- error: string;
31
- };
32
- /**
33
- * Validate a present value (null included) against its field — pure, never mutates.
34
- *
35
- * Reads `shape` and nothing else, so it answers "is this a legal value?" without ever
36
- * asking who is speaking. That is what makes it usable on the way OUT of the domain,
37
- * where the client-only axes (`boundary`, `lifecycle`) do not apply.
38
- */
39
- export declare function checkValue(field: AnyField, value: unknown): Checked;
40
- export interface ValidateOptions {
41
- /**
42
- * Patch mode (update). An absent field is OMITTED from the result — not filled,
43
- * not required — so `validate({ name })` touches only `name`. Set by `partial()`.
44
- * Distinguishes "absent → don't touch" (patch) from "absent → null" (create).
45
- */
46
- patch?: boolean;
47
- }
48
- /**
49
- * A path is a field NAME here, never a dotted trail: `json(Entity)` is judged as one
50
- * opaque shape by the engine, so a nested failure answers `{ path: 'addr' }` with the
51
- * inner field in the prose. This used to take a `pathPrefix` that every call site passed
52
- * as `''` — a parameter for a recursion nobody wrote, which made the code read as if
53
- * nesting were handled. Writing that recursion changes what `FougereError.details`
54
- * carries across processes and languages, so it is a contract decision, not a patch.
55
- */
56
- export declare function validateFields(fields: Fields, input: unknown, opts?: ValidateOptions): ValidationResult<Record<string, unknown>>;
57
- //# sourceMappingURL=validation.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/projections/validation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAS,MAAM,mBAAmB,CAAC;AAIjE;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,gBAAgB,CAAC,CAAC,IAC1B;IAAE,OAAO,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,CAAC,CAAA;CAAE,GAC1B;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,eAAe,EAAE,CAAA;CAAE,CAAC;AAElD,MAAM,MAAM,OAAO,GAAG;IAAE,KAAK,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAmB7D;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAgCnE;AAED,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,OAAO,EACd,IAAI,GAAE,eAAoB,GACzB,gBAAgB,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CA8E3C"}
@@ -1,148 +0,0 @@
1
- import { anatomy, boundaryOf, resolveBoundary } from '../field/index.js';
2
- import { Validator } from '@cfworker/json-schema';
3
- /**
4
- * One validator per shape — the shape object IS the source JSON Schema (the closed
5
- * grammar the helpers emit), handed straight to the engine. Keyed by the shape
6
- * reference (stable: derivations copy field refs, `partial()` spreads the field but
7
- * keeps the same `shape`), built once and reused so the parse cost is amortised.
8
- */
9
- const validators = new WeakMap();
10
- function validatorFor(shape) {
11
- let v = validators.get(shape);
12
- if (!v) {
13
- v = new Validator(shape, '2020-12', true);
14
- validators.set(shape, v);
15
- }
16
- return v;
17
- }
18
- /**
19
- * Validate a present value (null included) against its field — pure, never mutates.
20
- *
21
- * Reads `shape` and nothing else, so it answers "is this a legal value?" without ever
22
- * asking who is speaking. That is what makes it usable on the way OUT of the domain,
23
- * where the client-only axes (`boundary`, `lifecycle`) do not apply.
24
- */
25
- export function checkValue(field, value) {
26
- const shape = field.shape;
27
- // Relation-only field (`many`): no value shape, just an array of related rows.
28
- if (!shape) {
29
- if (field.role?.relation?.kind === 'many') {
30
- return Array.isArray(value) ? { value } : { error: 'Expected an array' };
31
- }
32
- return { value };
33
- }
34
- // The pre-engine guards dispatch on the BASE type — `shape.type` itself may be
35
- // the nullable union. They only short-circuit NON-null values: null always goes
36
- // to the engine, whose union judges it (that is the whole nullability model).
37
- const base = anatomy(shape).base;
38
- if (value !== null) {
39
- // Opaque JSON (`json()`): no nested shape → passes through unchecked.
40
- // `json(Entity)` carries `properties` and falls through to the engine,
41
- // which validates the nesting natively (it's plain JSON Schema).
42
- if (base?.type === 'object' && !base.properties)
43
- return { value };
44
- // A date-time field also accepts a live Date — the domain value, not its JSON wire
45
- // string — which the boundary then passes through. The engine validates JSON, so a
46
- // Date (a non-JSON value) skips the schema check here; a date STRING is validated below.
47
- if (base?.type === 'string' && base.format === 'date-time' && value instanceof Date) {
48
- return Number.isNaN(value.getTime()) ? { error: 'Invalid date' } : { value };
49
- }
50
- // NaN is `typeof number` yet has no JSON form, so the JSON-centric engine accepts it —
51
- // guard it here (same nature as the Date case: a JS value JSON can't represent).
52
- if ((base?.type === 'number' || base?.type === 'integer') && typeof value === 'number' && Number.isNaN(value)) {
53
- return { error: 'Expected a number' };
54
- }
55
- }
56
- const result = validatorFor(shape).validate(value);
57
- return result.valid ? { value } : { error: result.errors[0]?.error ?? 'Invalid value' };
58
- }
59
- /**
60
- * A path is a field NAME here, never a dotted trail: `json(Entity)` is judged as one
61
- * opaque shape by the engine, so a nested failure answers `{ path: 'addr' }` with the
62
- * inner field in the prose. This used to take a `pathPrefix` that every call site passed
63
- * as `''` — a parameter for a recursion nobody wrote, which made the code read as if
64
- * nesting were handled. Writing that recursion changes what `FougereError.details`
65
- * carries across processes and languages, so it is a contract decision, not a patch.
66
- */
67
- export function validateFields(fields, input, opts = {}) {
68
- if (typeof input !== 'object' || input === null) {
69
- return { success: false, errors: [{ path: '.', message: 'Expected an object' }] };
70
- }
71
- const data = input;
72
- const errors = [];
73
- const out = {};
74
- // Refuse, never strip: a key outside the contract is an error, not a silent
75
- // drop. The client derives from the same contract (a form cannot emit a
76
- // foreign key), so a stranger is a bug or an attack — both deserve the 400.
77
- // Stripping would also let the envelope diverge from GraphQL, which refuses
78
- // unknown input keys by construction.
79
- //
80
- // A FACT is judged by this same rule, deliberately — the one place it was tempting to
81
- // relax it. A subscriber's copy of a fact can be older than the sender's, so tolerating
82
- // a stranger key would let a rolling deployment through; it would also mean a reader
83
- // silently ignoring a field it was supposed to handle. If the judge refuses, that is
84
- // the end of it: the sender re-syncs its readers before it ships.
85
- for (const key of Object.keys(data)) {
86
- if (!(key in fields)) {
87
- errors.push({ path: key, message: 'Unknown field' });
88
- }
89
- }
90
- for (const [key, field] of Object.entries(fields)) {
91
- const path = key;
92
- const value = data[key];
93
- if (value === undefined) {
94
- if (opts.patch)
95
- continue; // patch: an unsent field is left untouched
96
- // A read-only field is server-owned: its absence from a client input is
97
- // never "Required" (same stance as OpenAPI readOnly+required).
98
- if (boundaryOf(field).in === 'closed')
99
- continue;
100
- // Absence is answered by `lifecycle.create` — a key access on the normal
101
- // form. The judge only asks "is there a rule?": any rule ('now',
102
- // 'optional', { value }, { generate }) makes absence legal, and the field
103
- // is omitted from the result. Realisation is the storage adapter's role.
104
- if (field.lifecycle?.create !== undefined)
105
- continue;
106
- // No create rule: a `many` relation defaults to the empty collection
107
- // (graph semantics, read on `role` — never a shared `{ value: [] }`).
108
- if (field.role?.relation?.kind === 'many') {
109
- out[key] = [];
110
- continue;
111
- }
112
- errors.push({ path, message: 'Required' });
113
- continue;
114
- }
115
- // A PRESENT value can be illegal by an axis other than shape:
116
- // boundary `in: 'closed'` — a read-only field never crosses inbound;
117
- // lifecycle `update: 'forbidden'` — re-supplying an immutable field in a patch.
118
- if (boundaryOf(field).in === 'closed') {
119
- errors.push({ path, message: 'Read-only' });
120
- continue;
121
- }
122
- if (opts.patch && field.lifecycle?.update === 'forbidden') {
123
- errors.push({ path, message: 'Immutable' });
124
- continue;
125
- }
126
- // The shape predicate judges the value — null included, via the `[T,'null']`
127
- // union. Then the boundary's decode converts wire→domain; null skips decode
128
- // (a legal null is already the domain value, there is nothing to convert).
129
- const checked = checkValue(field, value);
130
- if ('error' in checked) {
131
- errors.push({ path, message: checked.error });
132
- continue;
133
- }
134
- if (checked.value === null) {
135
- out[key] = null;
136
- continue;
137
- }
138
- const decoded = resolveBoundary(field).decode(checked.value);
139
- if ('error' in decoded)
140
- errors.push({ path, message: decoded.error });
141
- else
142
- out[key] = decoded.value;
143
- }
144
- if (errors.length > 0)
145
- return { success: false, errors };
146
- return { success: true, data: out };
147
- }
148
- //# sourceMappingURL=validation.js.map