pocketbase-zod-schema 0.2.3 → 0.2.5

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 (71) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +209 -24
  3. package/dist/cli/index.cjs +34 -0
  4. package/dist/cli/index.cjs.map +1 -1
  5. package/dist/cli/index.d.cts +3 -1
  6. package/dist/cli/index.d.ts +3 -1
  7. package/dist/cli/index.js +34 -0
  8. package/dist/cli/index.js.map +1 -1
  9. package/dist/cli/migrate.cjs +34 -0
  10. package/dist/cli/migrate.cjs.map +1 -1
  11. package/dist/cli/migrate.js +34 -0
  12. package/dist/cli/migrate.js.map +1 -1
  13. package/dist/cli/utils/index.d.cts +3 -1
  14. package/dist/cli/utils/index.d.ts +3 -1
  15. package/dist/fields-YjcpBXVp.d.cts +348 -0
  16. package/dist/fields-YjcpBXVp.d.ts +348 -0
  17. package/dist/index.cjs +833 -694
  18. package/dist/index.cjs.map +1 -1
  19. package/dist/index.d.cts +3 -4
  20. package/dist/index.d.ts +3 -4
  21. package/dist/index.js +818 -687
  22. package/dist/index.js.map +1 -1
  23. package/dist/migration/analyzer.cjs +34 -0
  24. package/dist/migration/analyzer.cjs.map +1 -1
  25. package/dist/migration/analyzer.d.cts +2 -1
  26. package/dist/migration/analyzer.d.ts +2 -1
  27. package/dist/migration/analyzer.js +34 -0
  28. package/dist/migration/analyzer.js.map +1 -1
  29. package/dist/migration/diff.d.cts +3 -1
  30. package/dist/migration/diff.d.ts +3 -1
  31. package/dist/migration/generator.d.cts +3 -1
  32. package/dist/migration/generator.d.ts +3 -1
  33. package/dist/migration/index.cjs +36 -0
  34. package/dist/migration/index.cjs.map +1 -1
  35. package/dist/migration/index.d.cts +2 -1
  36. package/dist/migration/index.d.ts +2 -1
  37. package/dist/migration/index.js +35 -1
  38. package/dist/migration/index.js.map +1 -1
  39. package/dist/migration/snapshot.cjs.map +1 -1
  40. package/dist/migration/snapshot.d.cts +3 -1
  41. package/dist/migration/snapshot.d.ts +3 -1
  42. package/dist/migration/snapshot.js.map +1 -1
  43. package/dist/migration/utils/index.cjs +16 -0
  44. package/dist/migration/utils/index.cjs.map +1 -1
  45. package/dist/migration/utils/index.d.cts +2 -2
  46. package/dist/migration/utils/index.d.ts +2 -2
  47. package/dist/migration/utils/index.js +15 -1
  48. package/dist/migration/utils/index.js.map +1 -1
  49. package/dist/mutator.cjs +2 -98
  50. package/dist/mutator.cjs.map +1 -1
  51. package/dist/mutator.d.cts +4 -31
  52. package/dist/mutator.d.ts +4 -31
  53. package/dist/mutator.js +2 -98
  54. package/dist/mutator.js.map +1 -1
  55. package/dist/schema.cjs +200 -78
  56. package/dist/schema.cjs.map +1 -1
  57. package/dist/schema.d.cts +1 -1
  58. package/dist/schema.d.ts +1 -1
  59. package/dist/schema.js +186 -72
  60. package/dist/schema.js.map +1 -1
  61. package/dist/{types-z1Dkjg8m.d.ts → types-LFBGHl9Y.d.ts} +2 -2
  62. package/dist/{types-BbTgmg6H.d.cts → types-mhQXWNi3.d.cts} +2 -2
  63. package/package.json +1 -6
  64. package/dist/types.cjs +0 -4
  65. package/dist/types.cjs.map +0 -1
  66. package/dist/types.d.cts +0 -14
  67. package/dist/types.d.ts +0 -14
  68. package/dist/types.js +0 -3
  69. package/dist/types.js.map +0 -1
  70. package/dist/user-BnFWg5tw.d.cts +0 -161
  71. package/dist/user-BnFWg5tw.d.ts +0 -161
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.2.5](https://github.com/dastron/pocketbase-zod-schema/compare/pocketbase-zod-schema-v0.2.4...pocketbase-zod-schema-v0.2.5) (2026-01-03)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * Add Field Helpers ([bbe8185](https://github.com/dastron/pocketbase-zod-schema/commit/bbe81850a9fd6eba9c73d08cb0b21c7f944a6673))
9
+
10
+ ## [0.2.4](https://github.com/dastron/pocketbase-zod-schema/compare/pocketbase-zod-schema-v0.2.3...pocketbase-zod-schema-v0.2.4) (2026-01-03)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * Create E2E test for Package ([8b554df](https://github.com/dastron/pocketbase-zod-schema/commit/8b554dfebb8b360cf4d482e0bb789ddee1541f0e))
16
+
3
17
  ## [0.2.3](https://github.com/dastron/pocketbase-zod-schema/compare/pocketbase-zod-schema-v0.2.2...pocketbase-zod-schema-v0.2.3) (2025-12-20)
4
18
 
5
19
 
package/README.md CHANGED
@@ -31,15 +31,18 @@ Create a schema file in your project (e.g., `src/schema/post.ts`):
31
31
  import { z } from "zod";
32
32
  import {
33
33
  defineCollection,
34
+ TextField,
35
+ EditorField,
36
+ BoolField,
34
37
  RelationField,
35
38
  RelationsField,
36
39
  } from "pocketbase-zod-schema/schema";
37
40
 
38
41
  // Define the Zod schema
39
42
  export const PostSchema = z.object({
40
- title: z.string().min(1).max(200),
41
- content: z.string(),
42
- published: z.boolean().default(false),
43
+ title: TextField({ min: 1, max: 200 }),
44
+ content: EditorField(),
45
+ published: BoolField(),
43
46
 
44
47
  // Single relation to users collection
45
48
  author: RelationField({ collection: "users" }),
@@ -98,11 +101,11 @@ The recommended way to define collections is using `defineCollection()`, which p
98
101
 
99
102
  ```typescript
100
103
  import { z } from "zod";
101
- import { defineCollection, RelationField } from "pocketbase-zod-schema/schema";
104
+ import { defineCollection, TextField, EditorField, RelationField } from "pocketbase-zod-schema/schema";
102
105
 
103
106
  export const PostCollectionSchema = z.object({
104
- title: z.string(),
105
- content: z.string(),
107
+ title: TextField({ min: 1, max: 200 }),
108
+ content: EditorField(),
106
109
  author: RelationField({ collection: "users" }),
107
110
  });
108
111
 
@@ -151,7 +154,185 @@ This pattern allows:
151
154
 
152
155
  ### Field Types
153
156
 
154
- The library maps Zod types to PocketBase field types automatically:
157
+ The library provides explicit field helper functions for all PocketBase field types. These helpers embed PocketBase-specific metadata and provide type-safe configuration options.
158
+
159
+ #### Field Helper Functions
160
+
161
+ | Field Helper | PocketBase Type | Description | Example |
162
+ |--------------|-----------------|-------------|---------|
163
+ | `BoolField()` | bool | Boolean field | `active: BoolField()` |
164
+ | `NumberField(options?)` | number | Number field with optional constraints | `price: NumberField({ min: 0 })` |
165
+ | `TextField(options?)` | text | Text field with optional constraints | `name: TextField({ min: 1, max: 200 })` |
166
+ | `EmailField()` | email | Email field with validation | `email: EmailField()` |
167
+ | `URLField()` | url | URL field with validation | `website: URLField()` |
168
+ | `EditorField()` | editor | Rich text editor field | `content: EditorField()` |
169
+ | `DateField(options?)` | date | Date field with optional constraints | `birthdate: DateField()` |
170
+ | `AutodateField(options?)` | autodate | Auto-managed timestamp field | `createdAt: AutodateField({ onCreate: true })` |
171
+ | `SelectField(values, options?)` | select | Single or multiple select field | `status: SelectField(["draft", "published"])` |
172
+ | `FileField(options?)` | file | Single file upload field | `avatar: FileField({ mimeTypes: ["image/*"] })` |
173
+ | `FilesField(options?)` | file | Multiple file upload field | `images: FilesField({ maxSelect: 5 })` |
174
+ | `JSONField(schema?)` | json | JSON field with optional schema | `metadata: JSONField()` |
175
+ | `GeoPointField()` | geoPoint | Geographic coordinates field | `location: GeoPointField()` |
176
+ | `RelationField(config)` | relation | Single relation field | `author: RelationField({ collection: "users" })` |
177
+ | `RelationsField(config)` | relation | Multiple relation field | `tags: RelationsField({ collection: "tags" })` |
178
+
179
+ #### Field Options
180
+
181
+ **BoolField()**
182
+ - No options
183
+ - Returns: `z.ZodBoolean`
184
+
185
+ **NumberField(options?)**
186
+ - `min?: number` - Minimum value constraint
187
+ - `max?: number` - Maximum value constraint
188
+ - `noDecimal?: boolean` - Disallow decimal values (integers only)
189
+ - Returns: `z.ZodNumber`
190
+
191
+ **TextField(options?)**
192
+ - `min?: number` - Minimum length constraint
193
+ - `max?: number` - Maximum length constraint
194
+ - `pattern?: RegExp | string` - Pattern constraint (regex)
195
+ - `autogeneratePattern?: string` - Auto-generate pattern (e.g., `"[A-Z]{3}-[0-9]{6}"`)
196
+ - Returns: `z.ZodString`
197
+
198
+ **EmailField()**
199
+ - No options (includes email validation)
200
+ - Returns: `z.ZodString`
201
+
202
+ **URLField()**
203
+ - No options (includes URL validation)
204
+ - Returns: `z.ZodString`
205
+
206
+ **EditorField()**
207
+ - No options
208
+ - Returns: `z.ZodString`
209
+
210
+ **DateField(options?)**
211
+ - `min?: Date | string` - Minimum date constraint
212
+ - `max?: Date | string` - Maximum date constraint
213
+ - Returns: `z.ZodString`
214
+
215
+ **AutodateField(options?)**
216
+ - `onCreate?: boolean` - Set date automatically on record creation
217
+ - `onUpdate?: boolean` - Update date automatically on record update
218
+ - Returns: `z.ZodString`
219
+
220
+ **SelectField(values, options?)**
221
+ - `values: [string, ...string[]]` - Array of allowed values (required)
222
+ - `maxSelect?: number` - Maximum selections (default: 1, >1 enables multiple selection)
223
+ - Returns: `z.ZodEnum<T>` or `z.ZodArray<z.ZodEnum<T>>`
224
+
225
+ **FileField(options?)**
226
+ - `mimeTypes?: string[]` - Allowed MIME types (e.g., `["image/*", "application/pdf"]`)
227
+ - `maxSize?: number` - Maximum file size in bytes
228
+ - `thumbs?: string[]` - Thumbnail sizes to generate (e.g., `["100x100", "200x200"]`)
229
+ - `protected?: boolean` - Whether file requires auth to access
230
+ - Returns: `z.ZodType<File>`
231
+
232
+ **FilesField(options?)**
233
+ - All `FileField` options plus:
234
+ - `minSelect?: number` - Minimum number of files required
235
+ - `maxSelect?: number` - Maximum number of files allowed
236
+ - Returns: `z.ZodArray<z.ZodType<File>>`
237
+
238
+ **JSONField(schema?)**
239
+ - `schema?: z.ZodTypeAny` - Optional Zod schema for JSON structure validation
240
+ - Returns: `T | z.ZodRecord<z.ZodString, z.ZodAny>`
241
+
242
+ **GeoPointField()**
243
+ - No options
244
+ - Returns: `z.ZodObject<{ lon: z.ZodNumber; lat: z.ZodNumber }>`
245
+
246
+ #### Field Helper Examples
247
+
248
+ ```typescript
249
+ import { z } from "zod";
250
+ import {
251
+ defineCollection,
252
+ BoolField,
253
+ NumberField,
254
+ TextField,
255
+ EmailField,
256
+ URLField,
257
+ EditorField,
258
+ DateField,
259
+ AutodateField,
260
+ SelectField,
261
+ FileField,
262
+ FilesField,
263
+ JSONField,
264
+ GeoPointField,
265
+ RelationField,
266
+ RelationsField,
267
+ } from "pocketbase-zod-schema/schema";
268
+
269
+ const ProductSchema = z.object({
270
+ // Text fields
271
+ name: TextField({ min: 1, max: 200 }),
272
+ sku: TextField({ autogeneratePattern: "[A-Z]{3}-[0-9]{6}" }),
273
+ description: EditorField(),
274
+ website: URLField().optional(),
275
+
276
+ // Number fields
277
+ price: NumberField({ min: 0 }),
278
+ quantity: NumberField({ min: 0, noDecimal: true }),
279
+ rating: NumberField({ min: 0, max: 5 }).optional(),
280
+
281
+ // Boolean field
282
+ active: BoolField(),
283
+ featured: BoolField().optional(),
284
+
285
+ // Date fields
286
+ releaseDate: DateField().optional(),
287
+ createdAt: AutodateField({ onCreate: true }),
288
+ updatedAt: AutodateField({ onUpdate: true }),
289
+
290
+ // Select fields
291
+ status: SelectField(["draft", "published", "archived"]),
292
+ categories: SelectField(["electronics", "clothing", "food"], { maxSelect: 3 }),
293
+
294
+ // File fields
295
+ thumbnail: FileField({
296
+ mimeTypes: ["image/*"],
297
+ maxSize: 5242880, // 5MB
298
+ thumbs: ["100x100", "200x200"],
299
+ }),
300
+ images: FilesField({
301
+ mimeTypes: ["image/*"],
302
+ maxSelect: 5,
303
+ }),
304
+
305
+ // JSON field
306
+ metadata: JSONField(),
307
+ settings: JSONField(z.object({
308
+ theme: z.string(),
309
+ notifications: z.boolean(),
310
+ })).optional(),
311
+
312
+ // GeoPoint field
313
+ location: GeoPointField().optional(),
314
+
315
+ // Relation fields
316
+ vendor: RelationField({ collection: "vendors" }),
317
+ tags: RelationsField({ collection: "tags", maxSelect: 10 }),
318
+ });
319
+
320
+ export const ProductCollection = defineCollection({
321
+ collectionName: "products",
322
+ schema: ProductSchema,
323
+ permissions: {
324
+ listRule: "",
325
+ viewRule: "",
326
+ createRule: '@request.auth.id != ""',
327
+ updateRule: "vendor.owner = @request.auth.id",
328
+ deleteRule: "vendor.owner = @request.auth.id",
329
+ },
330
+ });
331
+ ```
332
+
333
+ #### Backward Compatibility
334
+
335
+ The library still supports plain Zod types for backward compatibility. The migration generator will infer PocketBase field types from Zod types when field helpers are not used:
155
336
 
156
337
  | Zod Type | PocketBase Type | Example |
157
338
  |----------|-----------------|---------|
@@ -163,8 +344,8 @@ The library maps Zod types to PocketBase field types automatically:
163
344
  | `z.date()` | date | `birthdate: z.date()` |
164
345
  | `z.enum([...])` | select | `status: z.enum(["draft", "published"])` |
165
346
  | `z.instanceof(File)` | file | `avatar: z.instanceof(File)` |
166
- | `RelationField()` | relation | `author: RelationField({ collection: "users" })` |
167
- | `RelationsField()` | relation | `tags: RelationsField({ collection: "tags" })` |
347
+
348
+ **Recommendation:** Use field helpers for new schemas to get explicit field type declarations and access to PocketBase-specific options.
168
349
 
169
350
  ### Defining Relations
170
351
 
@@ -420,22 +601,22 @@ Here's a complete example of a blog schema with users, posts, and comments:
420
601
  ```typescript
421
602
  // src/schema/user.ts
422
603
  import { z } from "zod";
423
- import { baseSchema, defineCollection } from "pocketbase-zod-schema/schema";
604
+ import { baseSchema, defineCollection, TextField, EmailField } from "pocketbase-zod-schema/schema";
424
605
 
425
606
  // Input schema for forms (includes passwordConfirm for validation)
426
607
  export const UserInputSchema = z.object({
427
- name: z.string().optional(),
428
- email: z.string().email(),
429
- password: z.string().min(8),
608
+ name: TextField({ max: 100 }).optional(),
609
+ email: EmailField(),
610
+ password: TextField({ min: 8 }),
430
611
  passwordConfirm: z.string(),
431
612
  avatar: z.instanceof(File).optional(),
432
613
  });
433
614
 
434
615
  // Database schema (excludes passwordConfirm)
435
616
  const UserCollectionSchema = z.object({
436
- name: z.string().optional(),
437
- email: z.string().email(),
438
- password: z.string().min(8),
617
+ name: TextField({ max: 100 }).optional(),
618
+ email: EmailField(),
619
+ password: TextField({ min: 8 }),
439
620
  avatar: z.instanceof(File).optional(),
440
621
  });
441
622
 
@@ -464,18 +645,22 @@ export const UserCollection = defineCollection({
464
645
  import { z } from "zod";
465
646
  import {
466
647
  defineCollection,
648
+ TextField,
649
+ EditorField,
650
+ BoolField,
651
+ DateField,
467
652
  RelationField,
468
653
  RelationsField,
469
654
  } from "pocketbase-zod-schema/schema";
470
655
 
471
656
  // Define the Zod schema
472
657
  export const PostSchema = z.object({
473
- title: z.string().min(1).max(200),
474
- slug: z.string(),
475
- content: z.string(),
476
- excerpt: z.string().optional(),
477
- published: z.boolean().default(false),
478
- publishedAt: z.date().optional(),
658
+ title: TextField({ min: 1, max: 200 }),
659
+ slug: TextField({ pattern: /^[a-z0-9-]+$/ }),
660
+ content: EditorField(),
661
+ excerpt: TextField({ max: 500 }).optional(),
662
+ published: BoolField(),
663
+ publishedAt: DateField().optional(),
479
664
 
480
665
  // Relations
481
666
  author: RelationField({ collection: "users" }),
@@ -500,11 +685,11 @@ export const PostCollection = defineCollection({
500
685
  ```typescript
501
686
  // src/schema/comment.ts
502
687
  import { z } from "zod";
503
- import { defineCollection, RelationField } from "pocketbase-zod-schema/schema";
688
+ import { defineCollection, TextField, RelationField } from "pocketbase-zod-schema/schema";
504
689
 
505
690
  // Define the Zod schema
506
691
  export const CommentSchema = z.object({
507
- content: z.string().min(1),
692
+ content: TextField({ min: 1 }),
508
693
 
509
694
  // Relations with cascade delete
510
695
  post: RelationField({ collection: "posts", cascadeDelete: true }),
@@ -63,6 +63,18 @@ function extractRelationMetadata(description) {
63
63
  }
64
64
  return null;
65
65
  }
66
+ var FIELD_METADATA_KEY = "__pocketbase_field__";
67
+ function extractFieldMetadata(description) {
68
+ if (!description) return null;
69
+ try {
70
+ const parsed = JSON.parse(description);
71
+ if (parsed[FIELD_METADATA_KEY]) {
72
+ return parsed[FIELD_METADATA_KEY];
73
+ }
74
+ } catch {
75
+ }
76
+ return null;
77
+ }
66
78
 
67
79
  // src/migration/errors.ts
68
80
  var MigrationError = class _MigrationError extends Error {
@@ -1301,6 +1313,28 @@ function isAuthCollection(fields) {
1301
1313
  return hasEmail && hasPassword;
1302
1314
  }
1303
1315
  function buildFieldDefinition(fieldName, zodType) {
1316
+ const fieldMetadata = extractFieldMetadata(zodType.description);
1317
+ if (fieldMetadata) {
1318
+ const required2 = isFieldRequired(zodType);
1319
+ const fieldDef2 = {
1320
+ name: fieldName,
1321
+ type: fieldMetadata.type,
1322
+ required: required2,
1323
+ options: fieldMetadata.options
1324
+ };
1325
+ if (fieldMetadata.type === "relation") {
1326
+ const relationMetadata2 = extractRelationMetadata(zodType.description);
1327
+ if (relationMetadata2) {
1328
+ fieldDef2.relation = {
1329
+ collection: relationMetadata2.collection,
1330
+ maxSelect: relationMetadata2.maxSelect,
1331
+ minSelect: relationMetadata2.minSelect,
1332
+ cascadeDelete: relationMetadata2.cascadeDelete
1333
+ };
1334
+ }
1335
+ }
1336
+ return fieldDef2;
1337
+ }
1304
1338
  const fieldType = mapZodTypeToPocketBase(zodType, fieldName);
1305
1339
  const required = isFieldRequired(zodType);
1306
1340
  const options = extractFieldOptions(zodType);