zodselect 1.0.5 → 1.0.6

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,8 +1,9 @@
1
- import { ZodBoolean, ZodLazy, ZodObject, ZodOptional, ZodType, ZodUnion } from 'zod';
1
+ import { ZodBoolean, ZodLazy, ZodObject, ZodOptional, ZodRecord, ZodType, ZodUnion } from 'zod';
2
+ import { $ZodRecordKey } from 'zod/v4/core';
2
3
  import { ZodSelect } from './select.js';
3
4
  export type BasicSelect = ZodObject<Readonly<{
4
5
  [k: string]: Clause | ZodLazy<Clause>;
5
- }>>;
6
+ }>> | ZodRecord<$ZodRecordKey, Clause | ZodLazy<Clause>>;
6
7
  type Clause = ZodOptional<ZodBoolean> | ZodOptional<ZodUnion<readonly [ZodBoolean, BasicSelect]>>;
7
8
  export declare function buildSelectSchema<T extends ZodType<object> | ZodUnion>(schema: T): ZodType<ZodSelect<T, true>>;
8
9
  export {};
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { getInnerType, isZodLazy, isZodObject, isZodObjectLoose } from './utilities.js';
2
+ import { getInnerType, isZodLazy, isZodObject, isZodObjectLoose, isZodRecord } from './utilities.js';
3
3
  function buildSelect(schema, cache = new Map()) {
4
4
  schema = getInnerType(schema, false);
5
5
  if (cache.has(schema)) {
@@ -26,6 +26,27 @@ function buildSelect(schema, cache = new Map()) {
26
26
  cache.set(schema, result);
27
27
  return result;
28
28
  }
29
+ if (isZodRecord(schema)) {
30
+ const valueType = schema.def.valueType;
31
+ const select = buildSelect(valueType, cache);
32
+ // A record select means: either a boolean, or a record of the same key type with select-values
33
+ const result = z.union([
34
+ z.boolean(),
35
+ z.record(schema.def.keyType, select)
36
+ ]).optional();
37
+ cache.set(schema, result);
38
+ return result;
39
+ }
40
+ if (schema.def.type === 'unknown') {
41
+ const result = z
42
+ .union([
43
+ z.boolean(),
44
+ z.record(z.string(), z.lazy(() => buildSelect(schema, cache)))
45
+ ])
46
+ .optional();
47
+ cache.set(schema, result);
48
+ return result;
49
+ }
29
50
  let types;
30
51
  if (schema.def.type === 'intersection') {
31
52
  types = [schema.def.left, schema.def.right];
@@ -137,4 +137,42 @@ describe('.buildSelectSchema()', () => {
137
137
  // Should be the same object (by reference)
138
138
  assert.strictEqual(rolesSelect, nestedRoleSelect);
139
139
  });
140
+ it('should support record types', () => {
141
+ const schema = z.object({
142
+ tags: z.record(z.string(), z.string())
143
+ });
144
+ const select = buildSelectSchema(schema);
145
+ const tagsSelect = select.shape.tags;
146
+ assert.strictEqual(tagsSelect.type, 'optional');
147
+ const union = tagsSelect.def.innerType;
148
+ assert.strictEqual(union.type, 'union');
149
+ assert.strictEqual(union.def.options[0].type, 'boolean');
150
+ assert.strictEqual(union.def.options[1].type, 'record');
151
+ assert.strictEqual(union.def.options[1].def.keyType.type, 'string');
152
+ assert.strictEqual(union.def.options[1].def.valueType.type, 'optional');
153
+ assert.strictEqual(union.def.options[1].def.valueType.def.innerType.type, 'boolean');
154
+ });
155
+ it('should support unknown types', () => {
156
+ const schema = z.object({
157
+ tags: z.unknown()
158
+ });
159
+ const select = buildSelectSchema(schema);
160
+ const result = select.safeParse({
161
+ tags: {
162
+ any: {
163
+ damn: true
164
+ },
165
+ thing: true
166
+ }
167
+ });
168
+ assert.strictEqual(result.success, true);
169
+ assert.deepStrictEqual(result.data, {
170
+ tags: {
171
+ any: {
172
+ damn: true
173
+ },
174
+ thing: true
175
+ }
176
+ });
177
+ });
140
178
  });
@@ -1,4 +1,4 @@
1
- import { ZodArray, ZodLazy, ZodNullable, ZodObject, ZodOptional, ZodType, ZodUnion, ZodUnknown } from 'zod';
1
+ import { ZodArray, ZodLazy, ZodNullable, ZodObject, ZodOptional, ZodRecord, ZodType, ZodUnion, ZodUnknown } from 'zod';
2
2
  export declare function getInnerType(schema: ZodType, unwrapLazy?: boolean): ZodType;
3
3
  export declare function isZodArray(obj: unknown): obj is ZodArray<ZodType>;
4
4
  export declare function isZodLazy(obj: unknown): obj is ZodLazy;
@@ -6,6 +6,7 @@ export declare function isZodNullable(obj: unknown): obj is ZodNullable<ZodType>
6
6
  export declare function isZodObject(obj: unknown, isLoose?: boolean): obj is ZodObject;
7
7
  export declare function isZodObjectLoose(obj: ZodObject): boolean;
8
8
  export declare function isZodOptional(obj: unknown): obj is ZodOptional<ZodType>;
9
+ export declare function isZodRecord(obj: unknown): obj is ZodRecord;
9
10
  export declare function isZodType(obj: unknown): obj is ZodType;
10
11
  export declare function isZodUnion(obj: unknown): obj is ZodUnion<ZodType[]>;
11
12
  export declare function isZodUnknown(obj: unknown): obj is ZodUnknown;
package/dist/utilities.js CHANGED
@@ -38,6 +38,9 @@ export function isZodObjectLoose(obj) {
38
38
  export function isZodOptional(obj) {
39
39
  return isZodType(obj) && (obj.def.type === 'optional');
40
40
  }
41
+ export function isZodRecord(obj) {
42
+ return isZodType(obj) && (obj.def.type === 'record');
43
+ }
41
44
  export function isZodType(obj) {
42
45
  return typeof obj === 'object' && obj !== null && 'def' in obj && 'safeParse' in obj && typeof obj.safeParse === 'function';
43
46
  }
@@ -1,7 +1,7 @@
1
1
  import assert from 'node:assert';
2
2
  import { describe, it } from 'node:test';
3
3
  import { z } from 'zod';
4
- import { getInnerType, isZodObject, isZodOptional, isZodUnion, isZodUnknown } from './utilities.js';
4
+ import { getInnerType, isZodObject, isZodOptional, isZodRecord, isZodUnion, isZodUnknown } from './utilities.js';
5
5
  describe('.utilities()', () => {
6
6
  describe('.getInnerType()', () => {
7
7
  it('getInnerType identifies inner types', () => {
@@ -54,6 +54,33 @@ describe('.utilities()', () => {
54
54
  assert.ok(isZodOptional(optionalSchema));
55
55
  assert.ok(!isZodOptional(stringSchema));
56
56
  });
57
+ it('isZodRecord detects complex record schemas with optional fields', () => {
58
+ // Create a sample RecordAttachmentSchema
59
+ const RecordAttachmentSchema = z.object({
60
+ name: z.string(),
61
+ size: z.number(),
62
+ type: z.string()
63
+ });
64
+ // Create a record schema with optional field
65
+ const attachmentsSchema = z.record(z.string(), RecordAttachmentSchema).optional();
66
+ const nonRecordSchema = z.object({ field: z.string() });
67
+ // Test record detection
68
+ assert.ok(isZodRecord(z.record(z.string(), RecordAttachmentSchema)), 'Should detect record schema');
69
+ assert.ok(isZodRecord(attachmentsSchema.unwrap()), 'Should detect optional record schema when unwrapped');
70
+ assert.ok(!isZodRecord(nonRecordSchema), 'Should not detect object schema as record');
71
+ // Test with actual record data
72
+ const validRecord = z.record(z.string(), RecordAttachmentSchema).parse({
73
+ 'file1': { name: 'test.pdf', size: 1024, type: 'application/pdf' },
74
+ 'file2': { name: 'image.jpg', size: 2048, type: 'image/jpeg' }
75
+ });
76
+ assert.ok(validRecord, 'Should successfully parse valid record data');
77
+ });
78
+ it('isZodRecord detects ZodRecord', () => {
79
+ const recordSchema = z.record(z.string(), z.string());
80
+ const stringSchema = z.string();
81
+ assert.ok(isZodRecord(recordSchema));
82
+ assert.ok(!isZodRecord(stringSchema));
83
+ });
57
84
  it('isZodUnion detects ZodUnion', () => {
58
85
  const unionSchema = z.union([z.string(), z.number()]);
59
86
  const stringSchema = z.string();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zodselect",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "description": "Type-safe selection utility built on top of Zod. Allows for strongly typed field selection, schema merging, and runtime refinement for frontend or backend use.",
5
5
  "author": "SecureDataSystems",
6
6
  "license": "MIT",
@@ -33,7 +33,7 @@
33
33
  "typescript": "^5.9.2"
34
34
  },
35
35
  "dependencies": {
36
- "zod": "^4.0.17"
36
+ "zod": "^4.1.1"
37
37
  },
38
38
  "scripts": {
39
39
  "build": "node scripts/build.mjs && tsc --project tsconfig.build.json",