pocketbase-zod-schema 0.7.0 → 0.7.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 (70) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/cli/index.cjs +449 -50
  3. package/dist/cli/index.cjs.map +1 -1
  4. package/dist/cli/index.d.cts +1 -1
  5. package/dist/cli/index.d.ts +1 -1
  6. package/dist/cli/index.js +449 -50
  7. package/dist/cli/index.js.map +1 -1
  8. package/dist/cli/migrate.cjs +455 -53
  9. package/dist/cli/migrate.cjs.map +1 -1
  10. package/dist/cli/migrate.js +455 -53
  11. package/dist/cli/migrate.js.map +1 -1
  12. package/dist/cli/utils/index.cjs +8 -1
  13. package/dist/cli/utils/index.cjs.map +1 -1
  14. package/dist/cli/utils/index.d.cts +1 -1
  15. package/dist/cli/utils/index.d.ts +1 -1
  16. package/dist/cli/utils/index.js +8 -1
  17. package/dist/cli/utils/index.js.map +1 -1
  18. package/dist/index.cjs +90 -1
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.d.cts +1 -1
  21. package/dist/index.d.ts +1 -1
  22. package/dist/index.js +87 -2
  23. package/dist/index.js.map +1 -1
  24. package/dist/migration/analyzer.cjs +87 -14
  25. package/dist/migration/analyzer.cjs.map +1 -1
  26. package/dist/migration/analyzer.d.cts +12 -4
  27. package/dist/migration/analyzer.d.ts +12 -4
  28. package/dist/migration/analyzer.js +87 -15
  29. package/dist/migration/analyzer.js.map +1 -1
  30. package/dist/migration/diff.cjs +64 -9
  31. package/dist/migration/diff.cjs.map +1 -1
  32. package/dist/migration/diff.d.cts +12 -2
  33. package/dist/migration/diff.d.ts +12 -2
  34. package/dist/migration/diff.js +64 -10
  35. package/dist/migration/diff.js.map +1 -1
  36. package/dist/migration/generator.cjs +206 -39
  37. package/dist/migration/generator.cjs.map +1 -1
  38. package/dist/migration/generator.d.cts +54 -2
  39. package/dist/migration/generator.d.ts +54 -2
  40. package/dist/migration/generator.js +203 -40
  41. package/dist/migration/generator.js.map +1 -1
  42. package/dist/migration/index.cjs +501 -63
  43. package/dist/migration/index.cjs.map +1 -1
  44. package/dist/migration/index.d.cts +2 -2
  45. package/dist/migration/index.d.ts +2 -2
  46. package/dist/migration/index.js +501 -63
  47. package/dist/migration/index.js.map +1 -1
  48. package/dist/migration/snapshot.cjs +147 -1
  49. package/dist/migration/snapshot.cjs.map +1 -1
  50. package/dist/migration/snapshot.d.cts +3 -1
  51. package/dist/migration/snapshot.d.ts +3 -1
  52. package/dist/migration/snapshot.js +147 -1
  53. package/dist/migration/snapshot.js.map +1 -1
  54. package/dist/migration/utils/index.d.cts +1 -1
  55. package/dist/migration/utils/index.d.ts +1 -1
  56. package/dist/schema.cjs +90 -1
  57. package/dist/schema.cjs.map +1 -1
  58. package/dist/schema.d.cts +128 -2
  59. package/dist/schema.d.ts +128 -2
  60. package/dist/schema.js +87 -2
  61. package/dist/schema.js.map +1 -1
  62. package/dist/server.cjs +545 -65
  63. package/dist/server.cjs.map +1 -1
  64. package/dist/server.d.cts +2 -2
  65. package/dist/server.d.ts +2 -2
  66. package/dist/server.js +542 -66
  67. package/dist/server.js.map +1 -1
  68. package/dist/{types-CWHV6ATd.d.cts → types-CnzfX6JH.d.cts} +20 -2
  69. package/dist/{types-C2nGWHLV.d.ts → types-Do3jyFBm.d.ts} +20 -2
  70. package/package.json +1 -1
package/dist/schema.d.cts CHANGED
@@ -255,11 +255,23 @@ interface CollectionConfig {
255
255
  * Optional collection type
256
256
  * - "base": Standard collection (default)
257
257
  * - "auth": Authentication collection with system auth fields
258
+ * - "view": Read-only collection backed by a SQL query (requires viewQuery)
258
259
  *
259
260
  * If not specified, the type will be auto-detected based on field presence
260
261
  * (collections with both email and password fields are detected as auth)
262
+ *
263
+ * Prefer defineView() over type: "view" - it enforces the constraints
264
+ * PocketBase places on view collections at compile time.
265
+ */
266
+ type?: "base" | "auth" | "view";
267
+ /**
268
+ * SQL SELECT statement backing a view collection
269
+ *
270
+ * Only valid when type is "view", where it is required. PocketBase derives
271
+ * the collection's fields by running this query, so the Zod schema is used
272
+ * for TypeScript types only.
261
273
  */
262
- type?: "base" | "auth";
274
+ viewQuery?: string;
263
275
  /**
264
276
  * Future extensibility - additional options can be added here
265
277
  */
@@ -344,6 +356,120 @@ interface CollectionConfig {
344
356
  */
345
357
  declare function defineCollection(config: CollectionConfig): z.ZodObject<any>;
346
358
 
359
+ /**
360
+ * Permission rules that apply to a view collection
361
+ *
362
+ * View collections are read-only: PocketBase rejects createRule, updateRule,
363
+ * deleteRule and manageRule on them, so only the two read rules are accepted.
364
+ */
365
+ interface ViewPermissionSchema {
366
+ /** Controls who can list/query the view's rows */
367
+ listRule?: RuleExpression;
368
+ /** Controls who can view an individual row */
369
+ viewRule?: RuleExpression;
370
+ }
371
+ /**
372
+ * Configuration options for defining a view collection
373
+ */
374
+ interface ViewCollectionConfig {
375
+ /**
376
+ * The name of the PocketBase collection
377
+ */
378
+ collectionName: string;
379
+ /**
380
+ * The Zod schema describing the shape of a row
381
+ *
382
+ * For views this is used for TypeScript type generation and application-side
383
+ * parsing only - PocketBase derives the collection's actual fields from the
384
+ * SQL query when the collection is saved.
385
+ */
386
+ schema: z.ZodObject<any>;
387
+ /**
388
+ * The SQL SELECT statement backing the view
389
+ *
390
+ * Use the `sql` tagged template for consistent formatting.
391
+ */
392
+ viewQuery: string;
393
+ /**
394
+ * Optional read permissions (listRule / viewRule)
395
+ */
396
+ permissions?: ViewPermissionSchema;
397
+ }
398
+ /**
399
+ * Removes the common leading indentation from a multi-line string and trims
400
+ * blank leading/trailing lines
401
+ *
402
+ * Keeps generated migrations stable when a query is re-indented in the source
403
+ * file, since only the relative indentation is preserved. Also used when
404
+ * reading a query back out of a migration file, where the generator has
405
+ * indented it to fit the surrounding code.
406
+ *
407
+ * @param value - Raw multi-line string
408
+ * @returns Dedented string
409
+ */
410
+ declare function dedentSql(value: string): string;
411
+ /**
412
+ * Tagged template for SQL view queries
413
+ *
414
+ * Interpolates values, strips the common leading indentation and trims blank
415
+ * leading/trailing lines. Returns a plain string, so a regular string literal
416
+ * works anywhere `sql` does.
417
+ *
418
+ * @example
419
+ * const query = sql`
420
+ * SELECT p.id AS id, p.title AS title
421
+ * FROM Projects p
422
+ * `;
423
+ * // "SELECT p.id AS id, p.title AS title\n FROM Projects p"
424
+ */
425
+ declare function sql(strings: TemplateStringsArray, ...values: Array<string | number>): string;
426
+ /**
427
+ * Validates a view query, throwing with an actionable message when it can't work
428
+ *
429
+ * @param collectionName - Collection name, used in error messages
430
+ * @param viewQuery - The SQL query to validate
431
+ */
432
+ declare function validateViewQuery(collectionName: string, viewQuery: unknown): asserts viewQuery is string;
433
+ /**
434
+ * Defines a read-only PocketBase view collection backed by a SQL query
435
+ *
436
+ * PocketBase derives the collection's fields by running the query, so the Zod
437
+ * schema only describes the shape for TypeScript types and application-side
438
+ * parsing. Views cannot have indexes and only support listRule / viewRule.
439
+ *
440
+ * The generated migration creates the collection with `type: "view"` and the
441
+ * query; changing the SQL later produces an in-place `viewQuery` update, which
442
+ * keeps the collection id stable.
443
+ *
444
+ * @param config - View collection configuration
445
+ * @returns The schema with view metadata attached
446
+ *
447
+ * @example
448
+ * export default defineView({
449
+ * collectionName: "ProjectStats",
450
+ * schema: ProjectStatsSchema,
451
+ * viewQuery: sql`
452
+ * SELECT p.OwnerUser AS id,
453
+ * p.OwnerUser AS OwnerUser,
454
+ * COUNT(*) AS projectCount
455
+ * FROM Projects p
456
+ * GROUP BY p.OwnerUser
457
+ * `,
458
+ * permissions: {
459
+ * listRule: "OwnerUser = @request.auth.id",
460
+ * viewRule: "OwnerUser = @request.auth.id",
461
+ * },
462
+ * });
463
+ *
464
+ * @example
465
+ * // Requirements PocketBase places on the query:
466
+ * // - the outermost SELECT must expose an `id` column
467
+ * // - a relation column must be selected bare (e.g. `p.OwnerUser AS OwnerUser`)
468
+ * // from the outer table for PocketBase to infer it as a relation field
469
+ * // - no top-level UNION (wrap unions in a subquery)
470
+ */
471
+ declare function defineView(config: ViewCollectionConfig): z.ZodObject<any>;
472
+
347
473
  /**
348
474
  * Predefined permission templates for common access control patterns
349
475
  */
@@ -434,4 +560,4 @@ declare function createPermissions(permissions: Partial<PermissionSchema>): Perm
434
560
  */
435
561
  declare function mergePermissions(...schemas: Partial<PermissionSchema>[]): PermissionSchema;
436
562
 
437
- export { type CollectionConfig, PermissionSchema, PermissionTemplateConfig, PermissionTemplates, type PermissionValidationResult, type RelationConfig, RelationField, type RelationsConfig, RelationsField, RuleExpression, baseImageFileSchema, baseSchema, baseSchemaWithTimestamps, createPermissions, defineCollection, extractRelationMetadata, inputImageFileSchema, isPermissionSchema, isTemplateConfig, mergePermissions, omitImageFilesSchema, resolveTemplate, validatePermissionConfig, validateRuleExpression, withIndexes, withPermissions };
563
+ export { type CollectionConfig, PermissionSchema, PermissionTemplateConfig, PermissionTemplates, type PermissionValidationResult, type RelationConfig, RelationField, type RelationsConfig, RelationsField, RuleExpression, type ViewCollectionConfig, type ViewPermissionSchema, baseImageFileSchema, baseSchema, baseSchemaWithTimestamps, createPermissions, dedentSql, defineCollection, defineView, extractRelationMetadata, inputImageFileSchema, isPermissionSchema, isTemplateConfig, mergePermissions, omitImageFilesSchema, resolveTemplate, sql, validatePermissionConfig, validateRuleExpression, validateViewQuery, withIndexes, withPermissions };
package/dist/schema.d.ts CHANGED
@@ -255,11 +255,23 @@ interface CollectionConfig {
255
255
  * Optional collection type
256
256
  * - "base": Standard collection (default)
257
257
  * - "auth": Authentication collection with system auth fields
258
+ * - "view": Read-only collection backed by a SQL query (requires viewQuery)
258
259
  *
259
260
  * If not specified, the type will be auto-detected based on field presence
260
261
  * (collections with both email and password fields are detected as auth)
262
+ *
263
+ * Prefer defineView() over type: "view" - it enforces the constraints
264
+ * PocketBase places on view collections at compile time.
265
+ */
266
+ type?: "base" | "auth" | "view";
267
+ /**
268
+ * SQL SELECT statement backing a view collection
269
+ *
270
+ * Only valid when type is "view", where it is required. PocketBase derives
271
+ * the collection's fields by running this query, so the Zod schema is used
272
+ * for TypeScript types only.
261
273
  */
262
- type?: "base" | "auth";
274
+ viewQuery?: string;
263
275
  /**
264
276
  * Future extensibility - additional options can be added here
265
277
  */
@@ -344,6 +356,120 @@ interface CollectionConfig {
344
356
  */
345
357
  declare function defineCollection(config: CollectionConfig): z.ZodObject<any>;
346
358
 
359
+ /**
360
+ * Permission rules that apply to a view collection
361
+ *
362
+ * View collections are read-only: PocketBase rejects createRule, updateRule,
363
+ * deleteRule and manageRule on them, so only the two read rules are accepted.
364
+ */
365
+ interface ViewPermissionSchema {
366
+ /** Controls who can list/query the view's rows */
367
+ listRule?: RuleExpression;
368
+ /** Controls who can view an individual row */
369
+ viewRule?: RuleExpression;
370
+ }
371
+ /**
372
+ * Configuration options for defining a view collection
373
+ */
374
+ interface ViewCollectionConfig {
375
+ /**
376
+ * The name of the PocketBase collection
377
+ */
378
+ collectionName: string;
379
+ /**
380
+ * The Zod schema describing the shape of a row
381
+ *
382
+ * For views this is used for TypeScript type generation and application-side
383
+ * parsing only - PocketBase derives the collection's actual fields from the
384
+ * SQL query when the collection is saved.
385
+ */
386
+ schema: z.ZodObject<any>;
387
+ /**
388
+ * The SQL SELECT statement backing the view
389
+ *
390
+ * Use the `sql` tagged template for consistent formatting.
391
+ */
392
+ viewQuery: string;
393
+ /**
394
+ * Optional read permissions (listRule / viewRule)
395
+ */
396
+ permissions?: ViewPermissionSchema;
397
+ }
398
+ /**
399
+ * Removes the common leading indentation from a multi-line string and trims
400
+ * blank leading/trailing lines
401
+ *
402
+ * Keeps generated migrations stable when a query is re-indented in the source
403
+ * file, since only the relative indentation is preserved. Also used when
404
+ * reading a query back out of a migration file, where the generator has
405
+ * indented it to fit the surrounding code.
406
+ *
407
+ * @param value - Raw multi-line string
408
+ * @returns Dedented string
409
+ */
410
+ declare function dedentSql(value: string): string;
411
+ /**
412
+ * Tagged template for SQL view queries
413
+ *
414
+ * Interpolates values, strips the common leading indentation and trims blank
415
+ * leading/trailing lines. Returns a plain string, so a regular string literal
416
+ * works anywhere `sql` does.
417
+ *
418
+ * @example
419
+ * const query = sql`
420
+ * SELECT p.id AS id, p.title AS title
421
+ * FROM Projects p
422
+ * `;
423
+ * // "SELECT p.id AS id, p.title AS title\n FROM Projects p"
424
+ */
425
+ declare function sql(strings: TemplateStringsArray, ...values: Array<string | number>): string;
426
+ /**
427
+ * Validates a view query, throwing with an actionable message when it can't work
428
+ *
429
+ * @param collectionName - Collection name, used in error messages
430
+ * @param viewQuery - The SQL query to validate
431
+ */
432
+ declare function validateViewQuery(collectionName: string, viewQuery: unknown): asserts viewQuery is string;
433
+ /**
434
+ * Defines a read-only PocketBase view collection backed by a SQL query
435
+ *
436
+ * PocketBase derives the collection's fields by running the query, so the Zod
437
+ * schema only describes the shape for TypeScript types and application-side
438
+ * parsing. Views cannot have indexes and only support listRule / viewRule.
439
+ *
440
+ * The generated migration creates the collection with `type: "view"` and the
441
+ * query; changing the SQL later produces an in-place `viewQuery` update, which
442
+ * keeps the collection id stable.
443
+ *
444
+ * @param config - View collection configuration
445
+ * @returns The schema with view metadata attached
446
+ *
447
+ * @example
448
+ * export default defineView({
449
+ * collectionName: "ProjectStats",
450
+ * schema: ProjectStatsSchema,
451
+ * viewQuery: sql`
452
+ * SELECT p.OwnerUser AS id,
453
+ * p.OwnerUser AS OwnerUser,
454
+ * COUNT(*) AS projectCount
455
+ * FROM Projects p
456
+ * GROUP BY p.OwnerUser
457
+ * `,
458
+ * permissions: {
459
+ * listRule: "OwnerUser = @request.auth.id",
460
+ * viewRule: "OwnerUser = @request.auth.id",
461
+ * },
462
+ * });
463
+ *
464
+ * @example
465
+ * // Requirements PocketBase places on the query:
466
+ * // - the outermost SELECT must expose an `id` column
467
+ * // - a relation column must be selected bare (e.g. `p.OwnerUser AS OwnerUser`)
468
+ * // from the outer table for PocketBase to infer it as a relation field
469
+ * // - no top-level UNION (wrap unions in a subquery)
470
+ */
471
+ declare function defineView(config: ViewCollectionConfig): z.ZodObject<any>;
472
+
347
473
  /**
348
474
  * Predefined permission templates for common access control patterns
349
475
  */
@@ -434,4 +560,4 @@ declare function createPermissions(permissions: Partial<PermissionSchema>): Perm
434
560
  */
435
561
  declare function mergePermissions(...schemas: Partial<PermissionSchema>[]): PermissionSchema;
436
562
 
437
- export { type CollectionConfig, PermissionSchema, PermissionTemplateConfig, PermissionTemplates, type PermissionValidationResult, type RelationConfig, RelationField, type RelationsConfig, RelationsField, RuleExpression, baseImageFileSchema, baseSchema, baseSchemaWithTimestamps, createPermissions, defineCollection, extractRelationMetadata, inputImageFileSchema, isPermissionSchema, isTemplateConfig, mergePermissions, omitImageFilesSchema, resolveTemplate, validatePermissionConfig, validateRuleExpression, withIndexes, withPermissions };
563
+ export { type CollectionConfig, PermissionSchema, PermissionTemplateConfig, PermissionTemplates, type PermissionValidationResult, type RelationConfig, RelationField, type RelationsConfig, RelationsField, RuleExpression, type ViewCollectionConfig, type ViewPermissionSchema, baseImageFileSchema, baseSchema, baseSchemaWithTimestamps, createPermissions, dedentSql, defineCollection, defineView, extractRelationMetadata, inputImageFileSchema, isPermissionSchema, isTemplateConfig, mergePermissions, omitImageFilesSchema, resolveTemplate, sql, validatePermissionConfig, validateRuleExpression, validateViewQuery, withIndexes, withPermissions };
package/dist/schema.js CHANGED
@@ -91,13 +91,16 @@ function withIndexes(schema, indexes) {
91
91
  return schema.describe(JSON.stringify(metadata));
92
92
  }
93
93
  function defineCollection(config) {
94
- const { collectionName, schema, permissions, indexes, type, ...futureOptions } = config;
94
+ const { collectionName, schema, permissions, indexes, type, viewQuery, ...futureOptions } = config;
95
95
  const metadata = {
96
96
  collectionName
97
97
  };
98
98
  if (type) {
99
99
  metadata.type = type;
100
100
  }
101
+ if (viewQuery !== void 0) {
102
+ metadata.viewQuery = viewQuery;
103
+ }
101
104
  if (permissions) {
102
105
  metadata.permissions = permissions;
103
106
  }
@@ -363,6 +366,88 @@ function GeoPointField() {
363
366
  };
364
367
  return schema.describe(JSON.stringify(metadata));
365
368
  }
369
+ function dedentSql(value) {
370
+ const lines = value.replace(/\r\n/g, "\n").split("\n");
371
+ while (lines.length > 0 && lines[0].trim() === "") {
372
+ lines.shift();
373
+ }
374
+ while (lines.length > 0 && lines[lines.length - 1].trim() === "") {
375
+ lines.pop();
376
+ }
377
+ if (lines.length === 0) {
378
+ return "";
379
+ }
380
+ let minIndent = Infinity;
381
+ for (const line of lines) {
382
+ if (line.trim() === "") continue;
383
+ const indent = line.length - line.trimStart().length;
384
+ if (indent < minIndent) {
385
+ minIndent = indent;
386
+ }
387
+ }
388
+ if (!Number.isFinite(minIndent) || minIndent === 0) {
389
+ return lines.map((line) => line.trimEnd()).join("\n");
390
+ }
391
+ return lines.map((line) => line.slice(minIndent).trimEnd()).join("\n");
392
+ }
393
+ function sql(strings, ...values) {
394
+ let result = "";
395
+ for (let i = 0; i < strings.length; i++) {
396
+ result += strings[i];
397
+ if (i < values.length) {
398
+ result += String(values[i]);
399
+ }
400
+ }
401
+ return dedentSql(result);
402
+ }
403
+ function stripLeadingComments(query) {
404
+ let remaining = query.trim();
405
+ while (remaining.length > 0) {
406
+ if (remaining.startsWith("--")) {
407
+ const newline = remaining.indexOf("\n");
408
+ remaining = newline === -1 ? "" : remaining.slice(newline + 1).trim();
409
+ continue;
410
+ }
411
+ if (remaining.startsWith("/*")) {
412
+ const end = remaining.indexOf("*/");
413
+ remaining = end === -1 ? "" : remaining.slice(end + 2).trim();
414
+ continue;
415
+ }
416
+ break;
417
+ }
418
+ return remaining;
419
+ }
420
+ function validateViewQuery(collectionName, viewQuery) {
421
+ if (typeof viewQuery !== "string" || viewQuery.trim() === "") {
422
+ throw new Error(
423
+ `View collection "${collectionName}" requires a non-empty viewQuery. Provide the SQL SELECT statement backing the view.`
424
+ );
425
+ }
426
+ const body = stripLeadingComments(viewQuery);
427
+ if (!/^(select|with)\b/i.test(body)) {
428
+ throw new Error(
429
+ `View collection "${collectionName}" has an invalid viewQuery: it must start with SELECT or WITH. Received: ${body.slice(0, 40)}${body.length > 40 ? "..." : ""}`
430
+ );
431
+ }
432
+ }
433
+ function defineView(config) {
434
+ const { collectionName, schema, viewQuery, permissions } = config;
435
+ validateViewQuery(collectionName, viewQuery);
436
+ return defineCollection({
437
+ collectionName,
438
+ schema,
439
+ type: "view",
440
+ viewQuery,
441
+ // Write rules are always locked for views - PocketBase rejects anything else
442
+ permissions: {
443
+ listRule: permissions?.listRule ?? null,
444
+ viewRule: permissions?.viewRule ?? null,
445
+ createRule: null,
446
+ updateRule: null,
447
+ deleteRule: null
448
+ }
449
+ });
450
+ }
366
451
 
367
452
  // src/utils/permission-templates.ts
368
453
  var PermissionTemplates = {
@@ -584,6 +669,6 @@ function mergePermissions(...schemas) {
584
669
  return merged;
585
670
  }
586
671
 
587
- export { AutodateField, BoolField, DateField, EditorField, EmailField, FIELD_METADATA_KEY, FileField, FilesField, GeoPointField, JSONField, MultiSelectField, NumberField, PermissionTemplates, RelationField, RelationsField, SelectField, SingleSelectField, TextField, URLField, baseImageFileSchema, baseSchema, baseSchemaWithTimestamps, createPermissions, defineCollection, extractFieldMetadata, extractRelationMetadata, inputImageFileSchema, isPermissionSchema, isTemplateConfig, mergePermissions, omitImageFilesSchema, resolveTemplate, validatePermissionConfig, validateRuleExpression, withIndexes, withPermissions };
672
+ export { AutodateField, BoolField, DateField, EditorField, EmailField, FIELD_METADATA_KEY, FileField, FilesField, GeoPointField, JSONField, MultiSelectField, NumberField, PermissionTemplates, RelationField, RelationsField, SelectField, SingleSelectField, TextField, URLField, baseImageFileSchema, baseSchema, baseSchemaWithTimestamps, createPermissions, dedentSql, defineCollection, defineView, extractFieldMetadata, extractRelationMetadata, inputImageFileSchema, isPermissionSchema, isTemplateConfig, mergePermissions, omitImageFilesSchema, resolveTemplate, sql, validatePermissionConfig, validateRuleExpression, validateViewQuery, withIndexes, withPermissions };
588
673
  //# sourceMappingURL=schema.js.map
589
674
  //# sourceMappingURL=schema.js.map