@clipboard-health/contract-core 2.3.47 → 3.1.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
@@ -29,14 +29,29 @@ This package provides four enum validation helpers to cover different use cases:
29
29
 
30
30
  **Fallback validation (with coalescing):**
31
31
 
32
- - `requiredEnumWithFallback(values, fallback)` - Invalid values are coerced to the fallback value. `undefined` fails validation.
33
- - `optionalEnumWithFallback(values, fallback)` - Invalid values are coerced to the fallback value. `undefined` passes through as `undefined`.
32
+ - `requiredEnumWithFallback(values)` - Invalid values are coerced to `ENUM_FALLBACK` (`"UNRECOGNIZED_"`), a business-context-neutral sentinel automatically appended to the enum type. `undefined` fails validation.
33
+ - `optionalEnumWithFallback(values)` - Invalid values are coerced to `ENUM_FALLBACK` (`"UNRECOGNIZED_"`). `undefined` passes through as `undefined`.
34
34
 
35
35
  **Strict validation (no fallback):**
36
36
 
37
37
  - `requiredEnum(values)` - Wraps `z.enum()` for required strict validation. Invalid values fail validation.
38
38
  - `optionalEnum(values)` - Wraps `z.enum()` for optional strict validation. Invalid values fail validation, but `undefined` is allowed.
39
39
 
40
+ **Type narrowing:** All helpers reject widened `string[]` arrays at compile time. When passing a pre-declared variable, use `as const` to preserve literal types:
41
+
42
+ ```ts
43
+ // Inline arrays work as-is
44
+ requiredEnum(["a", "b"]);
45
+
46
+ // Pre-declared variables require `as const`
47
+ const VALUES = ["a", "b"] as const;
48
+ requiredEnum(VALUES);
49
+
50
+ // Without `as const`, the type widens to string[] and is rejected
51
+ const widened = ["a", "b"];
52
+ requiredEnum(widened); // TS error
53
+ ```
54
+
40
55
  <embedex source="packages/contract-core/examples/schemas.ts">
41
56
 
42
57
  ```ts
@@ -44,6 +59,7 @@ import {
44
59
  apiErrors,
45
60
  booleanString,
46
61
  dateTimeSchema,
62
+ ENUM_FALLBACK,
47
63
  nonEmptyString,
48
64
  optionalEnum,
49
65
  optionalEnumWithFallback,
@@ -134,12 +150,13 @@ const someDate = schema.parse("2026-03-15T10:30:00.000Z");
134
150
  console.log(someDate);
135
151
 
136
152
  // Enum with fallback examples
153
+ // ENUM_FALLBACK is a neutral sentinel ("UNRECOGNIZED_") automatically appended
154
+ // to the enum type. Consumers cannot choose their own fallback value, preventing
155
+ // misuse where a business-meaningful value is treated as a default.
156
+
137
157
  /* -- required -- */
138
- const requiredStatusEnumSchema = requiredEnumWithFallback(
139
- ["unspecified", "pending", "completed", "failed"],
140
- "unspecified",
141
- );
142
- // type RequiredStatusEnum = "unspecified" | "pending" | "completed" | "failed"
158
+ const requiredStatusEnumSchema = requiredEnumWithFallback(["pending", "completed", "failed"]);
159
+ // type RequiredStatusEnum = "pending" | "completed" | "failed" | "UNRECOGNIZED_"
143
160
  type RequiredStatusEnum = z.infer<typeof requiredStatusEnumSchema>;
144
161
 
145
162
  const completedStatus: RequiredStatusEnum = requiredStatusEnumSchema.parse("completed");
@@ -147,8 +164,9 @@ const completedStatus: RequiredStatusEnum = requiredStatusEnumSchema.parse("comp
147
164
  console.log(completedStatus);
148
165
 
149
166
  const additionalStatus = requiredStatusEnumSchema.parse("additional");
150
- // => "unspecified"
167
+ // => "UNRECOGNIZED_" (ENUM_FALLBACK)
151
168
  console.log(additionalStatus);
169
+ console.log(additionalStatus === ENUM_FALLBACK); // true
152
170
 
153
171
  try {
154
172
  // eslint-disable-next-line unicorn/no-useless-undefined
@@ -159,11 +177,8 @@ try {
159
177
  }
160
178
 
161
179
  /* -- optional -- */
162
- const optionalStatusEnumSchema = optionalEnumWithFallback(
163
- ["unspecified", "pending", "completed", "failed"],
164
- "unspecified",
165
- );
166
- // type OptionalStatusEnum = "unspecified" | "pending" | "completed" | "failed" | undefined
180
+ const optionalStatusEnumSchema = optionalEnumWithFallback(["pending", "completed", "failed"]);
181
+ // type OptionalStatusEnum = "pending" | "completed" | "failed" | "UNRECOGNIZED_" | undefined
167
182
  type OptionalStatusEnum = z.infer<typeof optionalStatusEnumSchema>;
168
183
 
169
184
  const failedStatus: OptionalStatusEnum = optionalStatusEnumSchema.parse("failed");
@@ -171,7 +186,7 @@ const failedStatus: OptionalStatusEnum = optionalStatusEnumSchema.parse("failed"
171
186
  console.log(failedStatus);
172
187
 
173
188
  const extraStatus = optionalStatusEnumSchema.parse("extra");
174
- // => "unspecified"
189
+ // => "UNRECOGNIZED_" (ENUM_FALLBACK)
175
190
  console.log(extraStatus);
176
191
 
177
192
  // eslint-disable-next-line unicorn/no-useless-undefined
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clipboard-health/contract-core",
3
- "version": "2.3.47",
3
+ "version": "3.1.0",
4
4
  "description": "Shared Zod schemas for Clipboard's contracts.",
5
5
  "keywords": [
6
6
  "contract",
@@ -1,10 +1,17 @@
1
1
  import { z } from "zod";
2
2
  type EnumValues = readonly [string, ...string[]];
3
- export declare function enumWithFallback<const V extends EnumValues, const F extends V[number]>(values: V, fallback: F, options: {
3
+ type NarrowEnum<V extends EnumValues> = string extends V[number] ? never : V;
4
+ /**
5
+ * A business-context-neutral sentinel returned when an enum field
6
+ * receives a value the consumer does not recognize.
7
+ */
8
+ export declare const ENUM_FALLBACK: "UNRECOGNIZED_";
9
+ type EnumFallback = typeof ENUM_FALLBACK;
10
+ export declare function enumWithFallback<const V extends EnumValues>(values: NarrowEnum<V>, options: {
4
11
  optional: true;
5
- }): z.ZodEffects<z.ZodOptional<z.ZodEnum<[...V]>>, V[number] | undefined, unknown>;
6
- export declare const optionalEnumWithFallback: <const V extends EnumValues, const F extends V[number]>(values: V, fallback: F) => z.ZodEffects<z.ZodOptional<z.ZodEnum<[...V]>>, V[number] | undefined, unknown>;
7
- export declare const requiredEnumWithFallback: <const V extends EnumValues, const F extends V[number]>(values: V, fallback: F) => z.ZodEffects<z.ZodEnum<[...V]>, V[number], unknown>;
8
- export declare function requiredEnum<const V extends EnumValues>(values: V): z.ZodEnum<[...V]>;
9
- export declare function optionalEnum<const V extends EnumValues>(values: V): z.ZodOptional<z.ZodEnum<[...V]>>;
12
+ }): z.ZodEffects<z.ZodOptional<z.ZodEnum<[...V, EnumFallback]>>, V[number] | EnumFallback | undefined, unknown>;
13
+ export declare function requiredEnumWithFallback<const V extends EnumValues>(values: NarrowEnum<V>): z.ZodEffects<z.ZodEnum<[...V, "UNRECOGNIZED_"]>, "UNRECOGNIZED_" | V[number], unknown>;
14
+ export declare function optionalEnumWithFallback<const V extends EnumValues>(values: NarrowEnum<V>): z.ZodEffects<z.ZodOptional<z.ZodEnum<[...V, "UNRECOGNIZED_"]>>, "UNRECOGNIZED_" | V[number] | undefined, unknown>;
15
+ export declare function requiredEnum<const V extends EnumValues>(values: NarrowEnum<V>): z.ZodEnum<[...V]>;
16
+ export declare function optionalEnum<const V extends EnumValues>(values: NarrowEnum<V>): z.ZodOptional<z.ZodEnum<[...V]>>;
10
17
  export {};
@@ -1,25 +1,39 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.requiredEnumWithFallback = exports.optionalEnumWithFallback = void 0;
3
+ exports.ENUM_FALLBACK = void 0;
4
4
  exports.enumWithFallback = enumWithFallback;
5
+ exports.requiredEnumWithFallback = requiredEnumWithFallback;
6
+ exports.optionalEnumWithFallback = optionalEnumWithFallback;
5
7
  exports.requiredEnum = requiredEnum;
6
8
  exports.optionalEnum = optionalEnum;
7
9
  const zod_1 = require("zod");
8
- function enumWithFallback(values, fallback, options = {}) {
9
- const Enum = zod_1.z.enum([...values]);
10
+ /**
11
+ * A business-context-neutral sentinel returned when an enum field
12
+ * receives a value the consumer does not recognize.
13
+ */
14
+ exports.ENUM_FALLBACK = "UNRECOGNIZED_";
15
+ function enumWithFallback(values, options = {}) {
16
+ if (values.includes(exports.ENUM_FALLBACK)) {
17
+ throw new Error(`Enum values must not include "${exports.ENUM_FALLBACK}". It is appended automatically.`);
18
+ }
19
+ const OriginalEnum = zod_1.z.enum([...values]);
20
+ const ExpandedEnum = zod_1.z.enum([...values, exports.ENUM_FALLBACK]);
10
21
  const optional = options.optional ?? false;
11
- const schema = optional ? Enum.optional() : Enum;
22
+ const schema = optional ? ExpandedEnum.optional() : ExpandedEnum;
23
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
12
24
  return zod_1.z.preprocess((value) => {
13
25
  if (value === undefined) {
14
26
  return optional ? undefined : value;
15
27
  }
16
- return Enum.safeParse(value).success ? value : fallback;
28
+ return OriginalEnum.safeParse(value).success ? value : exports.ENUM_FALLBACK;
17
29
  }, schema);
18
30
  }
19
- const optionalEnumWithFallback = (values, fallback) => enumWithFallback(values, fallback, { optional: true });
20
- exports.optionalEnumWithFallback = optionalEnumWithFallback;
21
- const requiredEnumWithFallback = (values, fallback) => enumWithFallback(values, fallback, { optional: false });
22
- exports.requiredEnumWithFallback = requiredEnumWithFallback;
31
+ function requiredEnumWithFallback(values) {
32
+ return enumWithFallback(values, { optional: false });
33
+ }
34
+ function optionalEnumWithFallback(values) {
35
+ return enumWithFallback(values, { optional: true });
36
+ }
23
37
  function requiredEnum(values) {
24
38
  return zod_1.z.enum([...values]);
25
39
  }
@@ -1 +1 @@
1
- {"version":3,"file":"enum.js","sourceRoot":"","sources":["../../../../../../packages/contract-core/src/lib/schemas/enum.ts"],"names":[],"mappings":";;;AAwBA,4CAeC;AAYD,oCAEC;AAED,oCAIC;AA3DD,6BAAwB;AAwBxB,SAAgB,gBAAgB,CAC9B,MAAS,EACT,QAAW,EACX,UAAkC,EAAE;IAEpC,MAAM,IAAI,GAAG,OAAC,CAAC,IAAI,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC;IACjC,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,KAAK,CAAC;IAC3C,MAAM,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IAEjD,OAAO,OAAC,CAAC,UAAU,CAAC,CAAC,KAAK,EAAE,EAAE;QAC5B,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,OAAO,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC;QACtC,CAAC;QACD,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC;IAC1D,CAAC,EAAE,MAAM,CAAC,CAAC;AACb,CAAC;AAEM,MAAM,wBAAwB,GAAG,CACtC,MAAS,EACT,QAAW,EACX,EAAE,CAAC,gBAAgB,CAAC,MAAM,EAAE,QAAQ,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;AAH/C,QAAA,wBAAwB,4BAGuB;AAErD,MAAM,wBAAwB,GAAG,CACtC,MAAS,EACT,QAAW,EACX,EAAE,CAAC,gBAAgB,CAAC,MAAM,EAAE,QAAQ,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;AAHhD,QAAA,wBAAwB,4BAGwB;AAE7D,SAAgB,YAAY,CAA6B,MAAS;IAChE,OAAO,OAAC,CAAC,IAAI,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC;AAC7B,CAAC;AAED,SAAgB,YAAY,CAC1B,MAAS;IAET,OAAO,OAAC,CAAC,IAAI,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;AACxC,CAAC"}
1
+ {"version":3,"file":"enum.js","sourceRoot":"","sources":["../../../../../../packages/contract-core/src/lib/schemas/enum.ts"],"names":[],"mappings":";;;AAkCA,4CAuBC;AAED,4DAEC;AAED,4DAEC;AAED,oCAEC;AAED,oCAIC;AA3ED,6BAAwB;AAKxB;;;GAGG;AACU,QAAA,aAAa,GAAG,eAAwB,CAAC;AAyBtD,SAAgB,gBAAgB,CAC9B,MAAS,EACT,UAAkC,EAAE;IAEpC,IAAK,MAA4B,CAAC,QAAQ,CAAC,qBAAa,CAAC,EAAE,CAAC;QAC1D,MAAM,IAAI,KAAK,CACb,iCAAiC,qBAAa,kCAAkC,CACjF,CAAC;IACJ,CAAC;IAED,MAAM,YAAY,GAAG,OAAC,CAAC,IAAI,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC;IACzC,MAAM,YAAY,GAAG,OAAC,CAAC,IAAI,CAAC,CAAC,GAAG,MAAM,EAAE,qBAAa,CAAC,CAAC,CAAC;IACxD,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,KAAK,CAAC;IAC3C,MAAM,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,YAAY,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC;IAEjE,+DAA+D;IAC/D,OAAO,OAAC,CAAC,UAAU,CAAC,CAAC,KAAK,EAAE,EAAE;QAC5B,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,OAAO,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC;QACtC,CAAC;QAED,OAAO,YAAY,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,qBAAa,CAAC;IACvE,CAAC,EAAE,MAAM,CAAC,CAAC;AACb,CAAC;AAED,SAAgB,wBAAwB,CAA6B,MAAqB;IACxF,OAAO,gBAAgB,CAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;AACvD,CAAC;AAED,SAAgB,wBAAwB,CAA6B,MAAqB;IACxF,OAAO,gBAAgB,CAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;AACtD,CAAC;AAED,SAAgB,YAAY,CAA6B,MAAqB;IAC5E,OAAO,OAAC,CAAC,IAAI,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC;AAC7B,CAAC;AAED,SAAgB,YAAY,CAC1B,MAAqB;IAErB,OAAO,OAAC,CAAC,IAAI,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;AACxC,CAAC"}