pocketbase-zod-schema 0.3.9 → 0.4.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/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.4.0](https://github.com/dastron/pocketbase-zod-schema/compare/pocketbase-zod-schema-v0.3.9...pocketbase-zod-schema-v0.4.0) (2026-01-19)
4
+
5
+
6
+ ### Features
7
+
8
+ * add strongly typed expand support and type generation CLI ([2ca3f39](https://github.com/dastron/pocketbase-zod-schema/commit/2ca3f39d0af6bcf92f212ed64bab20e368c1297d))
9
+ * add strongly typed expand support and type generation CLI ([f8a1b4b](https://github.com/dastron/pocketbase-zod-schema/commit/f8a1b4bfbe3da3fc5b98977b53bc211fb3d44f0d))
10
+
3
11
  ## [0.3.9](https://github.com/dastron/pocketbase-zod-schema/compare/pocketbase-zod-schema-v0.3.8...pocketbase-zod-schema-v0.3.9) (2026-01-19)
4
12
 
5
13
 
@@ -179,10 +179,10 @@ var FileSystemError = class _FileSystemError extends MigrationError {
179
179
  operation;
180
180
  code;
181
181
  originalError;
182
- constructor(message, path7, operation, code, originalError) {
182
+ constructor(message, path8, operation, code, originalError) {
183
183
  super(message);
184
184
  this.name = "FileSystemError";
185
- this.path = path7;
185
+ this.path = path8;
186
186
  this.operation = operation;
187
187
  this.code = code;
188
188
  this.originalError = originalError;
@@ -4245,6 +4245,184 @@ Examples:
4245
4245
  `
4246
4246
  ).action(executeGenerate);
4247
4247
  }
4248
+
4249
+ // src/type-gen/generator.ts
4250
+ var TypeGenerator = class {
4251
+ schema;
4252
+ constructor(schema) {
4253
+ this.schema = schema;
4254
+ }
4255
+ generate() {
4256
+ const lines = [];
4257
+ lines.push(`/**`);
4258
+ lines.push(` * This file was auto-generated by pocketbase-zod-schema.`);
4259
+ lines.push(` * Do not modify it manually.`);
4260
+ lines.push(` */`);
4261
+ lines.push(``);
4262
+ lines.push(`import type { RecordModel } from "pocketbase";`);
4263
+ lines.push(``);
4264
+ const collectionNames = Array.from(this.schema.collections.keys());
4265
+ for (const [_, collection] of this.schema.collections) {
4266
+ lines.push(this.generateCollectionType(collection));
4267
+ lines.push(``);
4268
+ }
4269
+ lines.push(`export type CollectionResponses = {`);
4270
+ for (const name of collectionNames) {
4271
+ const typeName = this.toPascalCase(name);
4272
+ lines.push(` ${name}: ${typeName}Response;`);
4273
+ }
4274
+ lines.push(`};`);
4275
+ lines.push(``);
4276
+ lines.push(`import PocketBase from "pocketbase";`);
4277
+ lines.push(`import { RecordService } from "pocketbase";`);
4278
+ lines.push(``);
4279
+ lines.push(`export interface TypedPocketBase extends PocketBase {`);
4280
+ lines.push(` collection(idOrName: string): RecordService;`);
4281
+ for (const name of collectionNames) {
4282
+ const typeName = this.toPascalCase(name);
4283
+ lines.push(` collection(idOrName: "${name}"): RecordService<${typeName}Response>;`);
4284
+ }
4285
+ lines.push(`}`);
4286
+ lines.push(``);
4287
+ return lines.join("\n");
4288
+ }
4289
+ generateCollectionType(collection) {
4290
+ const typeName = this.toPascalCase(collection.name);
4291
+ const lines = [];
4292
+ lines.push(`export interface ${typeName}Record {`);
4293
+ lines.push(` id: string;`);
4294
+ lines.push(` created: string;`);
4295
+ lines.push(` updated: string;`);
4296
+ lines.push(` collectionId: string;`);
4297
+ lines.push(` collectionName: "${collection.name}";`);
4298
+ for (const field of collection.fields) {
4299
+ if (["id", "created", "updated"].includes(field.name)) continue;
4300
+ const fieldType = this.getFieldType(field);
4301
+ const optional = !field.required ? "?" : "";
4302
+ lines.push(` ${field.name}${optional}: ${fieldType};`);
4303
+ }
4304
+ lines.push(`}`);
4305
+ lines.push(``);
4306
+ lines.push(`export interface ${typeName}Response extends ${typeName}Record {`);
4307
+ const expandFields = [];
4308
+ for (const field of collection.fields) {
4309
+ if (field.type === "relation" && field.relation) {
4310
+ const targetCollection = field.relation.collection;
4311
+ let targetType = "RecordModel";
4312
+ if (targetCollection.toLowerCase() === "users" && !this.schema.collections.has("users")) ;
4313
+ if (this.schema.collections.has(targetCollection)) {
4314
+ targetType = `${this.toPascalCase(targetCollection)}Response`;
4315
+ } else if (targetCollection === "users") {
4316
+ targetType = "RecordModel";
4317
+ }
4318
+ const isMultiple = (field.relation.maxSelect ?? 1) > 1;
4319
+ const expandType = isMultiple ? `${targetType}[]` : targetType;
4320
+ expandFields.push(`${field.name}?: ${expandType}`);
4321
+ }
4322
+ }
4323
+ if (expandFields.length > 0) {
4324
+ lines.push(` expand?: {`);
4325
+ for (const ef of expandFields) {
4326
+ lines.push(` ${ef};`);
4327
+ }
4328
+ lines.push(` };`);
4329
+ }
4330
+ lines.push(`}`);
4331
+ return lines.join("\n");
4332
+ }
4333
+ getFieldType(field) {
4334
+ switch (field.type) {
4335
+ case "text":
4336
+ case "email":
4337
+ case "url":
4338
+ case "editor":
4339
+ case "date":
4340
+ case "autodate":
4341
+ return "string";
4342
+ case "number":
4343
+ return "number";
4344
+ case "bool":
4345
+ return "boolean";
4346
+ case "json":
4347
+ return "any";
4348
+ case "file":
4349
+ if (field.options?.maxSelect && field.options.maxSelect > 1) {
4350
+ return "string[]";
4351
+ }
4352
+ return "string";
4353
+ case "select":
4354
+ if (field.options?.values && Array.isArray(field.options.values) && field.options.values.length > 0) {
4355
+ const union = field.options.values.map((v) => `"${v}"`).join(" | ");
4356
+ if (field.options?.maxSelect && field.options.maxSelect > 1) {
4357
+ return `(${union})[]`;
4358
+ }
4359
+ return union;
4360
+ }
4361
+ if (field.options?.maxSelect && field.options.maxSelect > 1) {
4362
+ return "string[]";
4363
+ }
4364
+ return "string";
4365
+ case "relation":
4366
+ if (field.relation?.maxSelect && field.relation.maxSelect > 1) {
4367
+ return "string[]";
4368
+ }
4369
+ return "string";
4370
+ default:
4371
+ return "any";
4372
+ }
4373
+ }
4374
+ toPascalCase(str) {
4375
+ return str.replace(/(?:^\w|[A-Z]|\b\w)/g, (word) => {
4376
+ return word.toUpperCase();
4377
+ }).replace(/[\s_-]+/g, "");
4378
+ }
4379
+ };
4380
+
4381
+ // src/cli/commands/generate-types.ts
4382
+ async function executeGenerateTypes(options) {
4383
+ try {
4384
+ const parentOpts = options.parent?.opts?.() || {};
4385
+ if (parentOpts.verbose) {
4386
+ setVerbosity("verbose");
4387
+ } else if (parentOpts.quiet) {
4388
+ setVerbosity("quiet");
4389
+ }
4390
+ logDebug("Starting type generation...");
4391
+ logDebug(`Options: ${JSON.stringify(options, null, 2)}`);
4392
+ const config = await loadConfig(options);
4393
+ const schemaDir = getSchemaDirectory(config);
4394
+ logSection("\u{1F50D} Analyzing Schema");
4395
+ const analyzerConfig = {
4396
+ schemaDir,
4397
+ excludePatterns: config.schema.exclude,
4398
+ useCompiledFiles: false
4399
+ };
4400
+ const currentSchema = await withProgress("Parsing Zod schemas...", () => parseSchemaFiles(analyzerConfig));
4401
+ logSuccess(`Found ${currentSchema.collections.size} collection(s)`);
4402
+ logSection("\u{1F4DD} Generating Types");
4403
+ const generator = new TypeGenerator(currentSchema);
4404
+ const output = await withProgress("Generating TypeScript definitions...", () => Promise.resolve(generator.generate()));
4405
+ const outputPath = options.output || "pocketbase-types.ts";
4406
+ const resolvedPath = path5__namespace.resolve(process.cwd(), outputPath);
4407
+ fs5__namespace.writeFileSync(resolvedPath, output);
4408
+ logSuccess(`Types generated successfully at: ${outputPath}`);
4409
+ logSection("\u2705 Next Steps");
4410
+ console.log();
4411
+ console.log(" 1. Import types in your application:");
4412
+ console.log(` import { TypedPocketBase } from "./${path5__namespace.basename(outputPath).replace(/\.ts$/, "")}";`);
4413
+ console.log();
4414
+ } catch (error) {
4415
+ logError(`Failed to generate types: ${error}`);
4416
+ if (error instanceof Error && error.stack) {
4417
+ console.error();
4418
+ console.error(error.stack);
4419
+ }
4420
+ process.exit(1);
4421
+ }
4422
+ }
4423
+ function createGenerateTypesCommand() {
4424
+ return new commander.Command("generate-types").description("Generate TypeScript definitions from Zod schemas").option("-o, --output <path>", "Output file path", "pocketbase-types.ts").option("--schema-dir <directory>", "Directory containing Zod schema files").action(executeGenerateTypes);
4425
+ }
4248
4426
  function hasChanges3(diff) {
4249
4427
  return diff.collectionsToCreate.length > 0 || diff.collectionsToDelete.length > 0 || diff.collectionsToModify.length > 0;
4250
4428
  }
@@ -4487,6 +4665,7 @@ program.name("pocketbase-migrate").description(
4487
4665
  }
4488
4666
  });
4489
4667
  program.addCommand(createGenerateCommand());
4668
+ program.addCommand(createGenerateTypesCommand());
4490
4669
  program.addCommand(createStatusCommand());
4491
4670
  program.addHelpText(
4492
4671
  "after",
@@ -4494,6 +4673,7 @@ program.addHelpText(
4494
4673
  ${chalk__default.default.bold("Examples:")}
4495
4674
  $ pocketbase-migrate status Check for pending schema changes
4496
4675
  $ pocketbase-migrate generate Generate migration from schema changes
4676
+ $ pocketbase-migrate generate-types Generate TypeScript definitions
4497
4677
  $ pocketbase-migrate generate --force Generate migration with destructive changes
4498
4678
  $ pocketbase-migrate --help Show this help message
4499
4679