pocketbase-zod-schema 0.1.2 → 0.1.4

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 (69) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +329 -99
  3. package/dist/cli/index.cjs +577 -152
  4. package/dist/cli/index.cjs.map +1 -1
  5. package/dist/cli/index.js +575 -150
  6. package/dist/cli/index.js.map +1 -1
  7. package/dist/cli/migrate.cjs +595 -153
  8. package/dist/cli/migrate.cjs.map +1 -1
  9. package/dist/cli/migrate.js +592 -151
  10. package/dist/cli/migrate.js.map +1 -1
  11. package/dist/cli/utils/index.cjs +1 -1
  12. package/dist/cli/utils/index.cjs.map +1 -1
  13. package/dist/cli/utils/index.js +1 -1
  14. package/dist/cli/utils/index.js.map +1 -1
  15. package/dist/index.cjs +688 -231
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.cts +3 -3
  18. package/dist/index.d.ts +3 -3
  19. package/dist/index.js +685 -230
  20. package/dist/index.js.map +1 -1
  21. package/dist/migration/analyzer.cjs +101 -28
  22. package/dist/migration/analyzer.cjs.map +1 -1
  23. package/dist/migration/analyzer.js +101 -28
  24. package/dist/migration/analyzer.js.map +1 -1
  25. package/dist/migration/diff.cjs +21 -3
  26. package/dist/migration/diff.cjs.map +1 -1
  27. package/dist/migration/diff.js +21 -3
  28. package/dist/migration/diff.js.map +1 -1
  29. package/dist/migration/generator.cjs +60 -25
  30. package/dist/migration/generator.cjs.map +1 -1
  31. package/dist/migration/generator.d.cts +9 -5
  32. package/dist/migration/generator.d.ts +9 -5
  33. package/dist/migration/generator.js +60 -25
  34. package/dist/migration/generator.js.map +1 -1
  35. package/dist/migration/index.cjs +614 -171
  36. package/dist/migration/index.cjs.map +1 -1
  37. package/dist/migration/index.d.cts +1 -1
  38. package/dist/migration/index.d.ts +1 -1
  39. package/dist/migration/index.js +613 -171
  40. package/dist/migration/index.js.map +1 -1
  41. package/dist/migration/snapshot.cjs +432 -117
  42. package/dist/migration/snapshot.cjs.map +1 -1
  43. package/dist/migration/snapshot.d.cts +34 -12
  44. package/dist/migration/snapshot.d.ts +34 -12
  45. package/dist/migration/snapshot.js +430 -116
  46. package/dist/migration/snapshot.js.map +1 -1
  47. package/dist/migration/utils/index.cjs +19 -17
  48. package/dist/migration/utils/index.cjs.map +1 -1
  49. package/dist/migration/utils/index.d.cts +3 -1
  50. package/dist/migration/utils/index.d.ts +3 -1
  51. package/dist/migration/utils/index.js +19 -17
  52. package/dist/migration/utils/index.js.map +1 -1
  53. package/dist/mutator.cjs +9 -11
  54. package/dist/mutator.cjs.map +1 -1
  55. package/dist/mutator.d.cts +5 -9
  56. package/dist/mutator.d.ts +5 -9
  57. package/dist/mutator.js +9 -11
  58. package/dist/mutator.js.map +1 -1
  59. package/dist/schema.cjs +54 -23
  60. package/dist/schema.cjs.map +1 -1
  61. package/dist/schema.d.cts +94 -12
  62. package/dist/schema.d.ts +94 -12
  63. package/dist/schema.js +54 -24
  64. package/dist/schema.js.map +1 -1
  65. package/dist/types.d.cts +1 -1
  66. package/dist/types.d.ts +1 -1
  67. package/dist/{user-jS1aYoeD.d.cts → user-_AM523hb.d.cts} +6 -6
  68. package/dist/{user-jS1aYoeD.d.ts → user-_AM523hb.d.ts} +6 -6
  69. package/package.json +2 -4
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.4](https://github.com/dastron/pocketbase-zod-schema/compare/pocketbase-zod-schema-v0.1.3...pocketbase-zod-schema-v0.1.4) (2025-12-20)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * Migrations Ignored When Snapshot Exists ([5f9ee2b](https://github.com/dastron/pocketbase-zod-schema/commit/5f9ee2bd0739030fb200302caa40b219f48723d0))
9
+
10
+ ## [0.1.3](https://github.com/dastron/pocketbase-zod-schema/compare/pocketbase-zod-schema-v0.1.2...pocketbase-zod-schema-v0.1.3) (2025-12-19)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * Add Relationship Field ([7cc8ff4](https://github.com/dastron/pocketbase-zod-schema/commit/7cc8ff4264219014feeb6ca0a8d8a411dca02eb2))
16
+ * Repair Initial State and Generated Files ([4cd5c62](https://github.com/dastron/pocketbase-zod-schema/commit/4cd5c62ed995f3150ec7eacd39ddeaf1958b2f07))
17
+
3
18
  ## [0.1.2](https://github.com/dastron/pocketbase-zod-schema/compare/pocketbase-zod-schema-v0.1.1...pocketbase-zod-schema-v0.1.2) (2025-12-19)
4
19
 
5
20
 
package/README.md CHANGED
@@ -1,22 +1,17 @@
1
- # PocketBase Zod Migration Package
1
+ # pocketbase-zod-schema
2
2
 
3
- This is the **published NPM package**: `pocketbase-zod-schema`.
3
+ Define your PocketBase collections using Zod schemas and automatically generate migration files.
4
4
 
5
- If you're here from NPM, you can ignore the monorepo details — everything you need to **install and use** the library/CLI is below.
5
+ ## Features
6
6
 
7
- ## Structure
7
+ - **Type-safe schema definitions** - Use Zod to define your PocketBase collections with full TypeScript support
8
+ - **Automatic migrations** - Generate PocketBase-compatible migration files from your schema changes
9
+ - **Relation support** - Easily define single and multiple relations between collections
10
+ - **Permission templates** - Built-in templates for common permission patterns
11
+ - **Index definitions** - Declare indexes alongside your schema
12
+ - **CLI & programmatic API** - Use the CLI for quick generation or the API for custom workflows
8
13
 
9
- - `src/` - TypeScript source code
10
- - `dist/` - Built output (generated by tsup)
11
- - `package.json` - Package configuration and dependencies
12
- - `tsconfig.json` - TypeScript configuration
13
- - `tsup.config.ts` - Build configuration
14
- - `vitest.config.ts` - Test configuration
15
- - `.prettierrc` - Code formatting configuration
16
- - `nodemon.json` - Development server configuration
17
- - `.npmignore` - Files to exclude from npm package
18
-
19
- ## Installation (NPM)
14
+ ## Installation
20
15
 
21
16
  ```bash
22
17
  npm install pocketbase-zod-schema
@@ -26,28 +21,47 @@ yarn add pocketbase-zod-schema
26
21
  pnpm add pocketbase-zod-schema
27
22
  ```
28
23
 
29
- ## Usage
30
-
31
- ### CLI (`pocketbase-migrate`)
32
-
33
- The package ships a CLI named `pocketbase-migrate`.
24
+ ## Quick Start
34
25
 
35
- ```bash
36
- # generate migrations from schema changes
37
- npx pocketbase-migrate generate
26
+ ### 1. Create a schema file
38
27
 
39
- # show migration status without writing files
40
- npx pocketbase-migrate status
28
+ Create a schema file in your project (e.g., `src/schema/post.ts`):
41
29
 
42
- # force generation even if destructive changes are detected
43
- npx pocketbase-migrate generate --force
30
+ ```typescript
31
+ import { z } from "zod";
32
+ import {
33
+ relationField,
34
+ relationsField,
35
+ withPermissions,
36
+ } from "pocketbase-zod-schema/schema";
37
+
38
+ export const PostSchema = withPermissions(
39
+ z.object({
40
+ title: z.string().min(1).max(200),
41
+ content: z.string(),
42
+ published: z.boolean().default(false),
43
+
44
+ // Single relation to users collection
45
+ author: relationField({ collection: "users" }),
46
+
47
+ // Multiple relations to tags collection
48
+ tags: relationsField({ collection: "tags", maxSelect: 10 }),
49
+ }),
50
+ {
51
+ listRule: '@request.auth.id != ""',
52
+ viewRule: "",
53
+ createRule: '@request.auth.id != ""',
54
+ updateRule: "author = @request.auth.id",
55
+ deleteRule: "author = @request.auth.id",
56
+ }
57
+ );
44
58
  ```
45
59
 
46
- ### Configuration
60
+ ### 2. Configure the CLI
47
61
 
48
- Create a `pocketbase-migrate.config.js` at your project root:
62
+ Create `pocketbase-migrate.config.js` at your project root:
49
63
 
50
- ```js
64
+ ```javascript
51
65
  export default {
52
66
  schema: {
53
67
  directory: "./src/schema",
@@ -55,113 +69,329 @@ export default {
55
69
  },
56
70
  migrations: {
57
71
  directory: "./pocketbase/pb_migrations",
58
- format: "js",
59
- },
60
- diff: {
61
- warnOnDelete: true,
62
- requireForceForDestructive: true,
63
72
  },
64
73
  };
65
74
  ```
66
75
 
67
- ### Defining schemas (Zod)
76
+ ### 3. Generate migrations
68
77
 
69
- ```ts
70
- import { z } from "zod";
71
- import { baseSchema, withPermissions } from "pocketbase-zod-schema/schema";
78
+ ```bash
79
+ npx pocketbase-migrate generate
80
+ ```
72
81
 
73
- export const UserInputSchema = z.object({
74
- name: z.string().min(1),
75
- email: z.string().email(),
76
- avatar: z.string().optional(),
77
- role: z.enum(["user", "admin"]).default("user"),
82
+ This will create a migration file in your PocketBase migrations directory.
83
+
84
+ ---
85
+
86
+ ## Schema Definition
87
+
88
+ ### Field Types
89
+
90
+ The library maps Zod types to PocketBase field types automatically:
91
+
92
+ | Zod Type | PocketBase Type | Example |
93
+ |----------|-----------------|---------|
94
+ | `z.string()` | text | `title: z.string()` |
95
+ | `z.string().email()` | email | `email: z.string().email()` |
96
+ | `z.string().url()` | url | `website: z.string().url()` |
97
+ | `z.number()` | number | `price: z.number()` |
98
+ | `z.boolean()` | bool | `active: z.boolean()` |
99
+ | `z.date()` | date | `birthdate: z.date()` |
100
+ | `z.enum([...])` | select | `status: z.enum(["draft", "published"])` |
101
+ | `z.instanceof(File)` | file | `avatar: z.instanceof(File)` |
102
+ | `relationField()` | relation | `author: relationField({ collection: "users" })` |
103
+ | `relationsField()` | relation | `tags: relationsField({ collection: "tags" })` |
104
+
105
+ ### Defining Relations
106
+
107
+ Use `relationField()` for single relations and `relationsField()` for multiple relations:
108
+
109
+ ```typescript
110
+ import { relationField, relationsField } from "pocketbase-zod-schema/schema";
111
+
112
+ const ProjectSchema = z.object({
113
+ name: z.string(),
114
+
115
+ // Single relation (maxSelect: 1)
116
+ owner: relationField({ collection: "users" }),
117
+
118
+ // Single relation with cascade delete
119
+ category: relationField({
120
+ collection: "categories",
121
+ cascadeDelete: true,
122
+ }),
123
+
124
+ // Multiple relations (maxSelect: 999 by default)
125
+ collaborators: relationsField({ collection: "users" }),
126
+
127
+ // Multiple relations with constraints
128
+ tags: relationsField({
129
+ collection: "tags",
130
+ minSelect: 1,
131
+ maxSelect: 5,
132
+ }),
78
133
  });
134
+ ```
79
135
 
80
- export const UserSchema = withPermissions(
81
- baseSchema.extend(UserInputSchema.shape),
136
+ #### Relation Options
137
+
138
+ **`relationField(config)`** - Single relation
139
+ - `collection: string` - Target collection name (required)
140
+ - `cascadeDelete?: boolean` - Delete related records when this record is deleted (default: `false`)
141
+
142
+ **`relationsField(config)`** - Multiple relations
143
+ - `collection: string` - Target collection name (required)
144
+ - `cascadeDelete?: boolean` - Delete related records when this record is deleted (default: `false`)
145
+ - `minSelect?: number` - Minimum number of relations required (default: `0`)
146
+ - `maxSelect?: number` - Maximum number of relations allowed (default: `999`)
147
+
148
+ ### Defining Permissions
149
+
150
+ Use `withPermissions()` to attach API rules to your schema:
151
+
152
+ ```typescript
153
+ import { withPermissions } from "pocketbase-zod-schema/schema";
154
+
155
+ // Using direct rules
156
+ const PostSchema = withPermissions(
157
+ z.object({ title: z.string() }),
82
158
  {
83
- listRule: '@request.auth.id != ""',
84
- viewRule: '@request.auth.id != ""',
85
- createRule: "",
86
- updateRule: "@request.auth.id = id",
87
- deleteRule: "@request.auth.id = id",
159
+ listRule: '@request.auth.id != ""', // Authenticated users can list
160
+ viewRule: "", // Anyone can view (public)
161
+ createRule: '@request.auth.id != ""', // Authenticated users can create
162
+ updateRule: "author = @request.auth.id", // Only author can update
163
+ deleteRule: "author = @request.auth.id", // Only author can delete
164
+ }
165
+ );
166
+
167
+ // Using templates
168
+ const ProjectSchema = withPermissions(
169
+ z.object({ title: z.string(), owner: relationField({ collection: "users" }) }),
170
+ {
171
+ template: "owner-only",
172
+ ownerField: "owner",
88
173
  }
89
174
  );
90
175
  ```
91
176
 
92
- ### Programmatic usage
177
+ #### Permission Templates
93
178
 
94
- ```ts
95
- import {
96
- parseSchemaFiles,
97
- compare,
98
- generate,
99
- loadSnapshotIfExists,
100
- } from "pocketbase-zod-schema/migration";
179
+ | Template | Description |
180
+ |----------|-------------|
181
+ | `"public"` | All operations are public (no auth required) |
182
+ | `"authenticated"` | All operations require authentication |
183
+ | `"owner-only"` | Only the owner can perform operations |
101
184
 
102
- const migrationsDir = "./pocketbase/pb_migrations";
185
+ #### Template with Custom Overrides
103
186
 
104
- const currentSchema = await parseSchemaFiles("./src/schema");
105
- const previousSnapshot = loadSnapshotIfExists({ migrationsPath: migrationsDir });
187
+ ```typescript
188
+ const PostSchema = withPermissions(schema, {
189
+ template: "owner-only",
190
+ ownerField: "author",
191
+ customRules: {
192
+ listRule: '@request.auth.id != ""', // Override just the list rule
193
+ viewRule: "", // Make viewing public
194
+ },
195
+ });
196
+ ```
106
197
 
107
- const diff = compare(currentSchema, previousSnapshot);
108
- const migrationPath = generate(diff, migrationsDir);
109
- console.log({ migrationPath });
198
+ ### Defining Indexes
199
+
200
+ Use `withIndexes()` to define database indexes:
201
+
202
+ ```typescript
203
+ import { withIndexes, withPermissions } from "pocketbase-zod-schema/schema";
204
+
205
+ const UserSchema = withIndexes(
206
+ withPermissions(
207
+ z.object({
208
+ email: z.string().email(),
209
+ username: z.string(),
210
+ }),
211
+ { template: "authenticated" }
212
+ ),
213
+ [
214
+ 'CREATE UNIQUE INDEX idx_users_email ON users (email)',
215
+ 'CREATE INDEX idx_users_username ON users (username)',
216
+ ]
217
+ );
110
218
  ```
111
219
 
112
- ## Development (repo contributors)
220
+ ---
113
221
 
114
- From the root directory, you can run:
222
+ ## CLI Reference
223
+
224
+ ### Commands
115
225
 
116
226
  ```bash
117
- # Build the package
118
- yarn build
227
+ # Generate migration from schema changes
228
+ npx pocketbase-migrate generate
119
229
 
120
- # Run tests
121
- yarn test
230
+ # Show what would be generated without writing files
231
+ npx pocketbase-migrate status
122
232
 
123
- # Start development mode
124
- yarn dev
233
+ # Force generation even with destructive changes
234
+ npx pocketbase-migrate generate --force
235
+ ```
125
236
 
126
- # Format code
127
- yarn format
237
+ ### Configuration Options
128
238
 
129
- # Lint code
130
- yarn lint
239
+ ```javascript
240
+ // pocketbase-migrate.config.js
241
+ export default {
242
+ schema: {
243
+ // Directory containing your Zod schema files
244
+ directory: "./src/schema",
245
+
246
+ // Files to exclude from schema discovery
247
+ exclude: ["*.test.ts", "*.spec.ts", "base.ts", "index.ts"],
248
+ },
249
+ migrations: {
250
+ // Directory to output migration files
251
+ directory: "./pocketbase/pb_migrations",
252
+ },
253
+ diff: {
254
+ // Warn when collections or fields would be deleted
255
+ warnOnDelete: true,
256
+
257
+ // Require --force flag for destructive changes
258
+ requireForceForDestructive: true,
259
+ },
260
+ };
131
261
  ```
132
262
 
133
- All commands are proxied through the root package.json to this workspace.
263
+ ---
134
264
 
135
- ## Publishing
265
+ ## Programmatic API
136
266
 
137
- Publishing is handled by GitHub Actions (Release Please).
267
+ For custom workflows, use the programmatic API:
138
268
 
139
- ### Manual publish (maintainers)
140
-
141
- If you need to publish manually, use the **same command as CI**. It only requires `NPM_TOKEN`.
269
+ ```typescript
270
+ import {
271
+ parseSchemaFiles,
272
+ compare,
273
+ generate,
274
+ loadSnapshotIfExists,
275
+ } from "pocketbase-zod-schema/migration";
142
276
 
143
- ```bash
144
- export NPM_TOKEN="***"
145
- yarn publish:npm
277
+ async function generateMigration() {
278
+ const schemaDir = "./src/schema";
279
+ const migrationsDir = "./pocketbase/pb_migrations";
280
+
281
+ // Parse all schema files
282
+ const currentSchema = await parseSchemaFiles(schemaDir);
283
+
284
+ // Load the last known state from existing migrations
285
+ const previousSnapshot = loadSnapshotIfExists({
286
+ migrationsPath: migrationsDir
287
+ });
288
+
289
+ // Compare schemas and detect changes
290
+ const diff = compare(currentSchema, previousSnapshot);
291
+
292
+ // Generate migration file
293
+ if (diff.collectionsToCreate.length > 0 ||
294
+ diff.collectionsToModify.length > 0 ||
295
+ diff.collectionsToDelete.length > 0) {
296
+ const migrationPath = generate(diff, migrationsDir);
297
+ console.log(`Migration created: ${migrationPath}`);
298
+ } else {
299
+ console.log("No changes detected");
300
+ }
301
+ }
146
302
  ```
147
303
 
148
- If you'd rather keep the token in a local `.env` file (gitignored), you can do:
304
+ ---
149
305
 
150
- ```bash
151
- echo 'NPM_TOKEN="***"' > .env
152
- yarn publish:npm:env
153
- ```
306
+ ## Complete Example
154
307
 
155
- If your npm org/account requires publish-time 2FA, either:
308
+ Here's a complete example of a blog schema with users, posts, and comments:
156
309
 
157
- - use an **automation / granular token** configured to bypass 2FA for publishing (recommended for CI), or
158
- - set a one-time password for local publishing:
310
+ ```typescript
311
+ // src/schema/user.ts
312
+ import { z } from "zod";
313
+ import { withPermissions, withIndexes } from "pocketbase-zod-schema/schema";
314
+
315
+ export const UserSchema = withIndexes(
316
+ withPermissions(
317
+ z.object({
318
+ name: z.string().optional(),
319
+ email: z.string().email(),
320
+ password: z.string().min(8),
321
+ avatar: z.instanceof(File).optional(),
322
+ }),
323
+ {
324
+ listRule: "id = @request.auth.id",
325
+ viewRule: "id = @request.auth.id",
326
+ createRule: "",
327
+ updateRule: "id = @request.auth.id",
328
+ deleteRule: "id = @request.auth.id",
329
+ }
330
+ ),
331
+ [
332
+ 'CREATE UNIQUE INDEX idx_users_email ON users (email)',
333
+ ]
334
+ );
335
+ ```
159
336
 
160
- ```bash
161
- export NPM_OTP="123456"
162
- yarn publish:npm:env
337
+ ```typescript
338
+ // src/schema/post.ts
339
+ import { z } from "zod";
340
+ import {
341
+ relationField,
342
+ relationsField,
343
+ withPermissions
344
+ } from "pocketbase-zod-schema/schema";
345
+
346
+ export const PostSchema = withPermissions(
347
+ z.object({
348
+ title: z.string().min(1).max(200),
349
+ slug: z.string(),
350
+ content: z.string(),
351
+ excerpt: z.string().optional(),
352
+ published: z.boolean().default(false),
353
+ publishedAt: z.date().optional(),
354
+
355
+ // Relations
356
+ author: relationField({ collection: "users" }),
357
+ category: relationField({ collection: "categories" }),
358
+ tags: relationsField({ collection: "tags", maxSelect: 10 }),
359
+ }),
360
+ {
361
+ listRule: 'published = true || author = @request.auth.id',
362
+ viewRule: 'published = true || author = @request.auth.id',
363
+ createRule: '@request.auth.id != ""',
364
+ updateRule: "author = @request.auth.id",
365
+ deleteRule: "author = @request.auth.id",
366
+ }
367
+ );
368
+ ```
369
+
370
+ ```typescript
371
+ // src/schema/comment.ts
372
+ import { z } from "zod";
373
+ import { relationField, withPermissions } from "pocketbase-zod-schema/schema";
374
+
375
+ export const CommentSchema = withPermissions(
376
+ z.object({
377
+ content: z.string().min(1),
378
+
379
+ // Relations with cascade delete
380
+ post: relationField({ collection: "posts", cascadeDelete: true }),
381
+ author: relationField({ collection: "users" }),
382
+ }),
383
+ {
384
+ listRule: "",
385
+ viewRule: "",
386
+ createRule: '@request.auth.id != ""',
387
+ updateRule: "author = @request.auth.id",
388
+ deleteRule: "author = @request.auth.id || @request.auth.role = 'admin'",
389
+ }
390
+ );
163
391
  ```
164
392
 
165
- Authentication options:
393
+ ---
394
+
395
+ ## License
166
396
 
167
- - set `NPM_TOKEN` in your shell for the command
397
+ MIT