lazypock 0.6.0 → 0.8.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/dist/{chunk-HOBNUW5Z.js → chunk-TBBHGOSH.js} +8 -5
- package/dist/{chunk-HOBNUW5Z.js.map → chunk-TBBHGOSH.js.map} +1 -1
- package/dist/cli.cjs +7 -4
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.global.js +7 -4
- package/dist/cli.global.js.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/index.cjs +36 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +30 -7
- package/dist/index.d.ts +30 -7
- package/dist/index.global.js +36 -10
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +30 -7
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/codegen.ts +7 -4
- package/src/collection.ts +32 -8
- package/src/lazypock.ts +14 -0
- package/src/schema.ts +2 -0
|
@@ -182,10 +182,13 @@ export function createClient(options: LazypockClientOptions): TypedClient {
|
|
|
182
182
|
}
|
|
183
183
|
|
|
184
184
|
export class TypedClient extends LazypockClient {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
185
|
+
// K extends keyof LazypockCollections (rather than a generic string with a
|
|
186
|
+
// conditional return) so the IDE suggests collection names and unknown names
|
|
187
|
+
// are rejected at compile time:
|
|
188
|
+
// client.collection("posts") // suggested + typed
|
|
189
|
+
// client.collection("nope") // TS error
|
|
190
|
+
override collection<K extends keyof LazypockCollections>(name: K): CollectionService<LazypockCollections[K]> {
|
|
191
|
+
return super.collection(name) as CollectionService<LazypockCollections[K]>;
|
|
189
192
|
}
|
|
190
193
|
}
|
|
191
194
|
`);
|
|
@@ -214,4 +217,4 @@ export {
|
|
|
214
217
|
collectionTypeName,
|
|
215
218
|
generateTypes
|
|
216
219
|
};
|
|
217
|
-
//# sourceMappingURL=chunk-
|
|
220
|
+
//# sourceMappingURL=chunk-TBBHGOSH.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/typegen.ts","../src/codegen.ts"],"sourcesContent":["// ── Type mapping (runtime + codegen) ────────────────────\n// Maps server field types to TypeScript types.\n// Shared by the runtime `SchemaTypes` helper and the codegen CLI.\n\nimport type { SchemaField } from \"./schema\";\n\n/**\n * Map a single server field to its TypeScript type string.\n * Used by the codegen CLI to emit interface members.\n *\n * @param field The field definition.\n * @param fallback Fallback type for unknown field types (default `unknown`).\n */\nexport function fieldTypeScriptType(\n\tfield: SchemaField,\n\tfallback = \"unknown\",\n): string {\n\tconst opts = field.options ?? {};\n\tswitch (field.type) {\n\t\tcase \"text\":\n\t\tcase \"email\":\n\t\tcase \"url\":\n\t\tcase \"editor\":\n\t\tcase \"date\":\n\t\tcase \"datetime\":\n\t\t\treturn \"string\";\n\t\tcase \"number\":\n\t\t\treturn \"number\";\n\t\tcase \"bool\":\n\t\t\treturn \"boolean\";\n\t\tcase \"select\": {\n\t\t\tconst values = Array.isArray(opts.values) ? opts.values : [];\n\t\t\tif (values.length > 0) {\n\t\t\t\treturn values.map((v) => JSON.stringify(String(v))).join(\" | \");\n\t\t\t}\n\t\t\treturn \"string\";\n\t\t}\n\t\tcase \"multi_select\": {\n\t\t\tconst values = Array.isArray(opts.values) ? opts.values : [];\n\t\t\tif (values.length > 0) {\n\t\t\t\treturn `(${values.map((v) => JSON.stringify(String(v))).join(\" | \")})[]`;\n\t\t\t}\n\t\t\treturn \"string[]\";\n\t\t}\n\t\tcase \"file\":\n\t\t\treturn \"string\";\n\t\tcase \"multi_file\":\n\t\t\treturn \"string[]\";\n\t\tcase \"json\":\n\t\tcase \"geo\":\n\t\t\treturn \"Record<string, unknown>\";\n\t\tcase \"relation\":\n\t\t\t// Relations store the target record's ID (string) — or an array\n\t\t\t// of IDs when multi-relation (maxSelect > 1).\n\t\t\treturn (opts.maxSelect ?? 1) > 1 ? \"string[]\" : \"string\";\n\t\tcase \"password\":\n\t\t\t// Passwords are write-only; never expose on read models.\n\t\t\treturn \"never\";\n\t\tdefault:\n\t\t\treturn fallback;\n\t}\n}\n\n/**\n * Returns the runtime type kind for a field — used by {@link schemaFieldType}\n * to build structural types at runtime.\n */\nexport type FieldTypeKind =\n\t| \"string\"\n\t| \"number\"\n\t| \"boolean\"\n\t| \"string-array\"\n\t| \"json\"\n\t| \"relation\"\n\t| \"relation-many\"\n\t| \"password\"\n\t| \"unknown\";\n\n/** Map a server field to its runtime type kind. */\nexport function fieldTypeKind(field: SchemaField): FieldTypeKind {\n\tconst opts = field.options ?? {};\n\tswitch (field.type) {\n\t\tcase \"text\":\n\t\tcase \"email\":\n\t\tcase \"url\":\n\t\tcase \"editor\":\n\t\tcase \"date\":\n\t\tcase \"datetime\":\n\t\tcase \"select\":\n\t\tcase \"file\":\n\t\t\treturn \"string\";\n\t\tcase \"number\":\n\t\t\treturn \"number\";\n\t\tcase \"bool\":\n\t\t\treturn \"boolean\";\n\t\tcase \"multi_select\":\n\t\tcase \"multi_file\":\n\t\t\treturn \"string-array\";\n\t\tcase \"json\":\n\t\tcase \"geo\":\n\t\t\treturn \"json\";\n\t\tcase \"relation\":\n\t\t\treturn (opts.maxSelect ?? 1) > 1 ? \"relation-many\" : \"relation\";\n\t\tcase \"password\":\n\t\t\treturn \"password\";\n\t\tdefault:\n\t\t\treturn \"unknown\";\n\t}\n}\n\n/**\n * Derive a TypeScript field type from a {@link SchemaField} — the runtime\n * counterpart to the codegen mapper. Lets consumers build typed clients\n * from a fetched schema without running the CLI.\n */\nexport function schemaFieldType(field: SchemaField): unknown {\n\tswitch (fieldTypeKind(field)) {\n\t\tcase \"string\":\n\t\t\treturn String;\n\t\tcase \"number\":\n\t\t\treturn Number;\n\t\tcase \"boolean\":\n\t\t\treturn Boolean;\n\t\tcase \"string-array\":\n\t\t\treturn [String] as const;\n\t\tcase \"relation\":\n\t\t\treturn String;\n\t\tcase \"relation-many\":\n\t\t\treturn [String] as const;\n\t\tcase \"json\":\n\t\t\treturn Object;\n\t\tcase \"password\":\n\t\t\treturn undefined;\n\t\tcase \"unknown\":\n\t\t\treturn undefined;\n\t}\n}\n","// ── Codegen ─────────────────────────────────────────────\n// Generates a `lazypock.types.ts` module from the live API schema.\n//\n// The generated file exports:\n// - One interface per collection (e.g. `PostsRecord`)\n// - A `LazypockCollections` map: { posts: PostsRecord; users: UsersRecord; ... }\n// - A `createClient()` factory pre-bound to those types, so\n// `client.collection(\"posts\").create({ title: \"x\" })` is fully type-checked.\n//\n// The CLI in `src/cli.ts` wires this to `GET /collections`.\n\nimport type { CollectionSchema, SchemaField } from \"./schema\";\nimport { fieldTypeScriptType } from \"./typegen\";\n\n/** Format a raw collection name into a valid TS identifier (PascalCase). */\nexport function collectionTypeName(name: string): string {\n\treturn name\n\t\t.split(/[^a-zA-Z0-9]+/)\n\t\t.filter(Boolean)\n\t\t.map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n\t\t.join(\"\")\n\t\t.replace(/^[0-9]/, \"_$&\");\n}\n\n/** Interface member name — sanitize hyphens/spaces but keep readability. */\nexport function fieldKey(name: string): string {\n\treturn name.replace(/[^a-zA-Z0-9_]/g, \"_\");\n}\n\n\n\n/**\n * Generate the full TypeScript source for the typed SDK module.\n *\n * @param collections Collections fetched from the API.\n * @param options Generation options.\n */\nexport function generateTypes(\n\tcollections: CollectionSchema[],\n\toptions: {\n\t\t/** Import specifier for the lazypock package (default `lazypock`). */\n\t\tpackageName?: string;\n\t\t/** Emit base record fields (id, created, updated, …). Default true. */\n\t\tincludeBaseFields?: boolean;\n\t\t/** Skip system collections (names starting with `_` or `users`). Default false. */\n\t\tskipSystem?: boolean;\n\t} = {},\n): string {\n\tconst {\n\t\tpackageName = \"lazypock\",\n\t\tincludeBaseFields = true,\n\t\tskipSystem = false,\n\t} = options;\n\n\tconst filtered = skipSystem\n\t\t? collections.filter(\n\t\t\t\t(c) => !c.system && !c.name.startsWith(\"_\") && c.name !== \"users\",\n\t\t\t)\n\t\t: collections;\n\n\tconst sections: string[] = [];\n\tsections.push(`// ── Auto-generated by lazypock-ts ──────────────────────────\n// Do not edit by hand. Regenerate with: npx lazypock-gen\n// Schema snapshot: ${new Date().toISOString()}`);\n\n\tif (includeBaseFields) {\n\t\tsections.push(`export interface BaseRecord {\n id: string;\n collectionId: string;\n collectionName: string;\n created: string;\n updated: string;\n}`);\n\t}\n\n\t// One interface per collection\n\tfor (const coll of filtered) {\n\t\tconst typeName = collectionTypeName(coll.name);\n\t\tconst fields = coll.fields ?? [];\n\t\tconst lines = fields.map((f) => memberLine(f)).filter((l) => l !== \"\");\n\t\tconst body = lines.join(\"\\n\");\n\t\tsections.push(\n\t\t\t`export interface ${typeName}Record${renderInterface({\n\t\t\t\textends: includeBaseFields ? \"BaseRecord\" : undefined,\n\t\t\t\tbody,\n\t\t\t})}`,\n\t\t);\n\t}\n\n\t// Auth collection type\n\tsections.push(`export interface AuthRecord extends BaseRecord {\n email: string;\n verified: boolean;\n}`);\n\n\t// Collections map\n\tconst mapEntries = filtered\n\t\t.map(\n\t\t\t(c) =>\n\t\t\t\t` \"${c.name}\": ${collectionTypeName(c.name)}Record${\n\t\t\t\t\tc.type === \"auth\" ? \" & AuthRecord\" : \"\"\n\t\t\t\t};`,\n\t\t)\n\t\t.join(\"\\n\");\n\tsections.push(`export interface LazypockCollections {\n${mapEntries}\n}`);\n\n\t// Runtime schema snapshot — lets the client exclude hidden fields by\n\t// default, validate select/expand, and derive visible field lists.\n\tconst schemaEntries = collections\n\t\t.map(\n\t\t\t(c) =>\n\t\t\t\t` {\n id: ${JSON.stringify(c.id ?? null)},\n name: ${JSON.stringify(c.name)},\n type: ${JSON.stringify(c.type)},\n system: ${JSON.stringify(c.system ?? false)},\n fields: ${JSON.stringify(c.fields ?? [], null, 2)},\n rules: ${JSON.stringify(c.rules ?? {})},\n options: ${JSON.stringify(c.options ?? {})},\n }`,\n\t\t)\n\t\t.join(\",\\n\");\n\tsections.push(`// Schema snapshot (runtime) — hidden-field exclusion + query validation.\nexport const lazypockSchema: import(\"${packageName}\").CollectionSchema[] = [\n${schemaEntries}\n];`);\n\n\t// createClient factory\n\tsections.push(`import { LazypockClient, type LazypockClientOptions, type CollectionService } from \"${packageName}\";\n\n/**\n * Create a Lazypock client typed against this schema snapshot.\n * Collection access is fully type-checked:\n * client.collection(\"posts\").create({ title: \"x\" }) // title must exist\n *\n * The schema snapshot is wired into the client automatically, so hidden\n * fields are excluded from responses and select/expand are validated.\n */\nexport function createClient(options: LazypockClientOptions): TypedClient {\n return new TypedClient({\n ...options,\n types: {\n ...options.types,\n schemas: options.types?.schemas ?? lazypockSchema,\n },\n });\n}\n\nexport class TypedClient extends LazypockClient {\n override collection<T extends string>(name: T): T extends keyof LazypockCollections\n ? CollectionService<LazypockCollections[T]>\n : CollectionService<unknown> {\n return super.collection(name) as T extends keyof LazypockCollections ? CollectionService<LazypockCollections[T]> : CollectionService<unknown>;\n }\n}\n`);\n\n\treturn sections.join(\"\\n\\n\") + \"\\n\";\n}\n\n/**\n * Render the interface body including the extends clause.\n * Empty body → ` extends BaseRecord {}` (valid TS).\n */\nfunction renderInterface(opts: { extends?: string; body: string }): string {\n\tconst ext = opts.extends ? ` extends ${opts.extends}` : \"\";\n\tif (!opts.body) return `${ext} {}`;\n\treturn `${ext} {\\n${opts.body}\\n}`;\n}\n\n/** Render a single interface member line for a field. */\nfunction memberLine(f: SchemaField): string {\n\t// Hidden fields are excluded from API responses by the schema-aware\n\t// client default — don't expose them on the read model either.\n\tif (f.hidden) return \"\";\n\tconst key = fieldKey(f.name);\n\tconst req = f.required || f.type === \"password\" ? \"\" : \"?\";\n\tconst type = fieldTypeScriptType(f);\n\t// `never` members (passwords) are omitted from read models.\n\tif (type === \"never\") return \"\";\n\treturn ` ${JSON.stringify(key)}${req}: ${type};`;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/typegen.ts","../src/codegen.ts"],"sourcesContent":["// ── Type mapping (runtime + codegen) ────────────────────\n// Maps server field types to TypeScript types.\n// Shared by the runtime `SchemaTypes` helper and the codegen CLI.\n\nimport type { SchemaField } from \"./schema\";\n\n/**\n * Map a single server field to its TypeScript type string.\n * Used by the codegen CLI to emit interface members.\n *\n * @param field The field definition.\n * @param fallback Fallback type for unknown field types (default `unknown`).\n */\nexport function fieldTypeScriptType(\n\tfield: SchemaField,\n\tfallback = \"unknown\",\n): string {\n\tconst opts = field.options ?? {};\n\tswitch (field.type) {\n\t\tcase \"text\":\n\t\tcase \"email\":\n\t\tcase \"url\":\n\t\tcase \"editor\":\n\t\tcase \"date\":\n\t\tcase \"datetime\":\n\t\t\treturn \"string\";\n\t\tcase \"number\":\n\t\t\treturn \"number\";\n\t\tcase \"bool\":\n\t\t\treturn \"boolean\";\n\t\tcase \"select\": {\n\t\t\tconst values = Array.isArray(opts.values) ? opts.values : [];\n\t\t\tif (values.length > 0) {\n\t\t\t\treturn values.map((v) => JSON.stringify(String(v))).join(\" | \");\n\t\t\t}\n\t\t\treturn \"string\";\n\t\t}\n\t\tcase \"multi_select\": {\n\t\t\tconst values = Array.isArray(opts.values) ? opts.values : [];\n\t\t\tif (values.length > 0) {\n\t\t\t\treturn `(${values.map((v) => JSON.stringify(String(v))).join(\" | \")})[]`;\n\t\t\t}\n\t\t\treturn \"string[]\";\n\t\t}\n\t\tcase \"file\":\n\t\t\treturn \"string\";\n\t\tcase \"multi_file\":\n\t\t\treturn \"string[]\";\n\t\tcase \"json\":\n\t\tcase \"geo\":\n\t\t\treturn \"Record<string, unknown>\";\n\t\tcase \"relation\":\n\t\t\t// Relations store the target record's ID (string) — or an array\n\t\t\t// of IDs when multi-relation (maxSelect > 1).\n\t\t\treturn (opts.maxSelect ?? 1) > 1 ? \"string[]\" : \"string\";\n\t\tcase \"password\":\n\t\t\t// Passwords are write-only; never expose on read models.\n\t\t\treturn \"never\";\n\t\tdefault:\n\t\t\treturn fallback;\n\t}\n}\n\n/**\n * Returns the runtime type kind for a field — used by {@link schemaFieldType}\n * to build structural types at runtime.\n */\nexport type FieldTypeKind =\n\t| \"string\"\n\t| \"number\"\n\t| \"boolean\"\n\t| \"string-array\"\n\t| \"json\"\n\t| \"relation\"\n\t| \"relation-many\"\n\t| \"password\"\n\t| \"unknown\";\n\n/** Map a server field to its runtime type kind. */\nexport function fieldTypeKind(field: SchemaField): FieldTypeKind {\n\tconst opts = field.options ?? {};\n\tswitch (field.type) {\n\t\tcase \"text\":\n\t\tcase \"email\":\n\t\tcase \"url\":\n\t\tcase \"editor\":\n\t\tcase \"date\":\n\t\tcase \"datetime\":\n\t\tcase \"select\":\n\t\tcase \"file\":\n\t\t\treturn \"string\";\n\t\tcase \"number\":\n\t\t\treturn \"number\";\n\t\tcase \"bool\":\n\t\t\treturn \"boolean\";\n\t\tcase \"multi_select\":\n\t\tcase \"multi_file\":\n\t\t\treturn \"string-array\";\n\t\tcase \"json\":\n\t\tcase \"geo\":\n\t\t\treturn \"json\";\n\t\tcase \"relation\":\n\t\t\treturn (opts.maxSelect ?? 1) > 1 ? \"relation-many\" : \"relation\";\n\t\tcase \"password\":\n\t\t\treturn \"password\";\n\t\tdefault:\n\t\t\treturn \"unknown\";\n\t}\n}\n\n/**\n * Derive a TypeScript field type from a {@link SchemaField} — the runtime\n * counterpart to the codegen mapper. Lets consumers build typed clients\n * from a fetched schema without running the CLI.\n */\nexport function schemaFieldType(field: SchemaField): unknown {\n\tswitch (fieldTypeKind(field)) {\n\t\tcase \"string\":\n\t\t\treturn String;\n\t\tcase \"number\":\n\t\t\treturn Number;\n\t\tcase \"boolean\":\n\t\t\treturn Boolean;\n\t\tcase \"string-array\":\n\t\t\treturn [String] as const;\n\t\tcase \"relation\":\n\t\t\treturn String;\n\t\tcase \"relation-many\":\n\t\t\treturn [String] as const;\n\t\tcase \"json\":\n\t\t\treturn Object;\n\t\tcase \"password\":\n\t\t\treturn undefined;\n\t\tcase \"unknown\":\n\t\t\treturn undefined;\n\t}\n}\n","// ── Codegen ─────────────────────────────────────────────\n// Generates a `lazypock.types.ts` module from the live API schema.\n//\n// The generated file exports:\n// - One interface per collection (e.g. `PostsRecord`)\n// - A `LazypockCollections` map: { posts: PostsRecord; users: UsersRecord; ... }\n// - A `createClient()` factory pre-bound to those types, so\n// `client.collection(\"posts\").create({ title: \"x\" })` is fully type-checked.\n//\n// The CLI in `src/cli.ts` wires this to `GET /collections`.\n\nimport type { CollectionSchema, SchemaField } from \"./schema\";\nimport { fieldTypeScriptType } from \"./typegen\";\n\n/** Format a raw collection name into a valid TS identifier (PascalCase). */\nexport function collectionTypeName(name: string): string {\n\treturn name\n\t\t.split(/[^a-zA-Z0-9]+/)\n\t\t.filter(Boolean)\n\t\t.map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n\t\t.join(\"\")\n\t\t.replace(/^[0-9]/, \"_$&\");\n}\n\n/** Interface member name — sanitize hyphens/spaces but keep readability. */\nexport function fieldKey(name: string): string {\n\treturn name.replace(/[^a-zA-Z0-9_]/g, \"_\");\n}\n\n\n\n/**\n * Generate the full TypeScript source for the typed SDK module.\n *\n * @param collections Collections fetched from the API.\n * @param options Generation options.\n */\nexport function generateTypes(\n\tcollections: CollectionSchema[],\n\toptions: {\n\t\t/** Import specifier for the lazypock package (default `lazypock`). */\n\t\tpackageName?: string;\n\t\t/** Emit base record fields (id, created, updated, …). Default true. */\n\t\tincludeBaseFields?: boolean;\n\t\t/** Skip system collections (names starting with `_` or `users`). Default false. */\n\t\tskipSystem?: boolean;\n\t} = {},\n): string {\n\tconst {\n\t\tpackageName = \"lazypock\",\n\t\tincludeBaseFields = true,\n\t\tskipSystem = false,\n\t} = options;\n\n\tconst filtered = skipSystem\n\t\t? collections.filter(\n\t\t\t\t(c) => !c.system && !c.name.startsWith(\"_\") && c.name !== \"users\",\n\t\t\t)\n\t\t: collections;\n\n\tconst sections: string[] = [];\n\tsections.push(`// ── Auto-generated by lazypock-ts ──────────────────────────\n// Do not edit by hand. Regenerate with: npx lazypock-gen\n// Schema snapshot: ${new Date().toISOString()}`);\n\n\tif (includeBaseFields) {\n\t\tsections.push(`export interface BaseRecord {\n id: string;\n collectionId: string;\n collectionName: string;\n created: string;\n updated: string;\n}`);\n\t}\n\n\t// One interface per collection\n\tfor (const coll of filtered) {\n\t\tconst typeName = collectionTypeName(coll.name);\n\t\tconst fields = coll.fields ?? [];\n\t\tconst lines = fields.map((f) => memberLine(f)).filter((l) => l !== \"\");\n\t\tconst body = lines.join(\"\\n\");\n\t\tsections.push(\n\t\t\t`export interface ${typeName}Record${renderInterface({\n\t\t\t\textends: includeBaseFields ? \"BaseRecord\" : undefined,\n\t\t\t\tbody,\n\t\t\t})}`,\n\t\t);\n\t}\n\n\t// Auth collection type\n\tsections.push(`export interface AuthRecord extends BaseRecord {\n email: string;\n verified: boolean;\n}`);\n\n\t// Collections map\n\tconst mapEntries = filtered\n\t\t.map(\n\t\t\t(c) =>\n\t\t\t\t` \"${c.name}\": ${collectionTypeName(c.name)}Record${\n\t\t\t\t\tc.type === \"auth\" ? \" & AuthRecord\" : \"\"\n\t\t\t\t};`,\n\t\t)\n\t\t.join(\"\\n\");\n\tsections.push(`export interface LazypockCollections {\n${mapEntries}\n}`);\n\n\t// Runtime schema snapshot — lets the client exclude hidden fields by\n\t// default, validate select/expand, and derive visible field lists.\n\tconst schemaEntries = collections\n\t\t.map(\n\t\t\t(c) =>\n\t\t\t\t` {\n id: ${JSON.stringify(c.id ?? null)},\n name: ${JSON.stringify(c.name)},\n type: ${JSON.stringify(c.type)},\n system: ${JSON.stringify(c.system ?? false)},\n fields: ${JSON.stringify(c.fields ?? [], null, 2)},\n rules: ${JSON.stringify(c.rules ?? {})},\n options: ${JSON.stringify(c.options ?? {})},\n }`,\n\t\t)\n\t\t.join(\",\\n\");\n\tsections.push(`// Schema snapshot (runtime) — hidden-field exclusion + query validation.\nexport const lazypockSchema: import(\"${packageName}\").CollectionSchema[] = [\n${schemaEntries}\n];`);\n\n\t// createClient factory\n\tsections.push(`import { LazypockClient, type LazypockClientOptions, type CollectionService } from \"${packageName}\";\n\n/**\n * Create a Lazypock client typed against this schema snapshot.\n * Collection access is fully type-checked:\n * client.collection(\"posts\").create({ title: \"x\" }) // title must exist\n *\n * The schema snapshot is wired into the client automatically, so hidden\n * fields are excluded from responses and select/expand are validated.\n */\nexport function createClient(options: LazypockClientOptions): TypedClient {\n return new TypedClient({\n ...options,\n types: {\n ...options.types,\n schemas: options.types?.schemas ?? lazypockSchema,\n },\n });\n}\n\nexport class TypedClient extends LazypockClient {\n // K extends keyof LazypockCollections (rather than a generic string with a\n // conditional return) so the IDE suggests collection names and unknown names\n // are rejected at compile time:\n // client.collection(\"posts\") // suggested + typed\n // client.collection(\"nope\") // TS error\n override collection<K extends keyof LazypockCollections>(name: K): CollectionService<LazypockCollections[K]> {\n return super.collection(name) as CollectionService<LazypockCollections[K]>;\n }\n}\n`);\n\n\treturn sections.join(\"\\n\\n\") + \"\\n\";\n}\n\n/**\n * Render the interface body including the extends clause.\n * Empty body → ` extends BaseRecord {}` (valid TS).\n */\nfunction renderInterface(opts: { extends?: string; body: string }): string {\n\tconst ext = opts.extends ? ` extends ${opts.extends}` : \"\";\n\tif (!opts.body) return `${ext} {}`;\n\treturn `${ext} {\\n${opts.body}\\n}`;\n}\n\n/** Render a single interface member line for a field. */\nfunction memberLine(f: SchemaField): string {\n\t// Hidden fields are excluded from API responses by the schema-aware\n\t// client default — don't expose them on the read model either.\n\tif (f.hidden) return \"\";\n\tconst key = fieldKey(f.name);\n\tconst req = f.required || f.type === \"password\" ? \"\" : \"?\";\n\tconst type = fieldTypeScriptType(f);\n\t// `never` members (passwords) are omitted from read models.\n\tif (type === \"never\") return \"\";\n\treturn ` ${JSON.stringify(key)}${req}: ${type};`;\n}\n"],"mappings":"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","names":[]}
|
package/dist/cli.cjs
CHANGED
|
@@ -137,10 +137,13 @@ export function createClient(options: LazypockClientOptions): TypedClient {
|
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
export class TypedClient extends LazypockClient {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
140
|
+
// K extends keyof LazypockCollections (rather than a generic string with a
|
|
141
|
+
// conditional return) so the IDE suggests collection names and unknown names
|
|
142
|
+
// are rejected at compile time:
|
|
143
|
+
// client.collection("posts") // suggested + typed
|
|
144
|
+
// client.collection("nope") // TS error
|
|
145
|
+
override collection<K extends keyof LazypockCollections>(name: K): CollectionService<LazypockCollections[K]> {
|
|
146
|
+
return super.collection(name) as CollectionService<LazypockCollections[K]>;
|
|
144
147
|
}
|
|
145
148
|
}
|
|
146
149
|
`);
|
package/dist/cli.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts","../src/typegen.ts","../src/codegen.ts"],"sourcesContent":["#!/usr/bin/env node\n// ── Codegen CLI ─────────────────────────────────────────\n// `lazypock` — fetches the live collection schema from a Lazypock\n// API and writes a fully-typed `lazypock.types.ts` module.\n//\n// Auth methods (pick one):\n// 1. Superuser email + password:\n// npx lazypock --url http://localhost:4000/api --email admin@... --password ...\n// 2. API key (recommended, generated from the Settings dashboard):\n// npx lazypock --url http://localhost:4000/api --apikey <key>\n//\n// Or via env vars (no flags):\n// LAZYPOCK_URL=... LAZYPOCK_API_KEY=... npx lazypock\n// LAZYPOCK_URL=... LAZYPOCK_EMAIL=... LAZYPOCK_PASSWORD=... npx lazypock\n//\n// NOTE: `lazypock-gen` remains as a deprecated alias for backwards\n// compatibility. Both invoke the same executable.\n\nimport { writeFile } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\nimport { generateTypes } from \"./codegen\";\nimport type { CollectionsResponse } from \"./schema\";\n\ninterface CliOptions {\n\turl: string;\n\temail: string;\n\tpassword: string;\n\tapiKey: string;\n\tout: string;\n\tpackageName: string;\n\tskipSystem: boolean;\n}\n\nfunction fail(msg: string): never {\n\tconsole.error(`\\n❌ ${msg}\\n`);\n\tprocess.exit(1);\n}\n\nfunction parseArgs(argv: string[]): CliOptions {\n\tconst args = [...argv];\n\tconst get = (flag: string, envKey: string, def = \"\"): string => {\n\t\tconst i = args.indexOf(flag);\n\t\tif (i !== -1 && i + 1 < args.length) return args[i + 1];\n\t\treturn process.env[envKey] ?? def;\n\t};\n\tconst has = (flag: string): boolean => args.includes(flag);\n\n\tconst url = get(\"--url\", \"LAZYPOCK_URL\");\n\tconst email = get(\"--email\", \"LAZYPOCK_EMAIL\");\n\tconst password = get(\"--password\", \"LAZYPOCK_PASSWORD\");\n\tconst apiKey =\n\t\tget(\"--apikey\", \"LAZYPOCK_API_KEY\") || get(\"--api-key\", \"LAZYPOCK_API_KEY\");\n\n\tif (!url) fail(\"Missing API URL. Pass --url or set LAZYPOCK_URL.\");\n\tif (!apiKey) {\n\t\tif (!email)\n\t\t\tfail(\n\t\t\t\t\"Missing credentials. Pass --apikey, or --email + --password, or set LAZYPOCK_API_KEY / LAZYPOCK_EMAIL.\",\n\t\t\t);\n\t\tif (!password)\n\t\t\tfail(\"Missing password. Pass --password, or set LAZYPOCK_PASSWORD.\");\n\t}\n\n\tconst out =\n\t\tget(\"--output\", \"LAZYPOCK_OUT\") ||\n\t\tget(\"--out\", \"LAZYPOCK_OUT\", \"lazypock.types.ts\");\n\tconst packageName = get(\"--package\", \"LAZYPOCK_PACKAGE\", \"lazypock\");\n\n\treturn {\n\t\turl,\n\t\temail,\n\t\tpassword,\n\t\tapiKey,\n\t\tout,\n\t\tpackageName,\n\t\tskipSystem: has(\"--skip-system\"),\n\t};\n}\n\nasync function fetchCollections(\n\topts: CliOptions,\n): Promise<CollectionsResponse> {\n\tconst base = opts.url.replace(/\\/+$/, \"\");\n\n\tlet authKey: string;\n\tif (opts.apiKey) {\n\t\t// Step 1: use a stored API key directly (no login round-trip).\n\t\t// The key is sent as `Authorization: Bearer <key>` and recognised\n\t\t// by the backend's Auth.Plug as an API key.\n\t\tauthKey = opts.apiKey;\n\t} else {\n\t\t// Step 1: login as superuser to get a token.\n\t\t// Prefer the PocketBase-parity `_superusers` auth collection endpoint,\n\t\t// fall back to the legacy /superusers/login for older servers.\n\t\tlet loginRes = await fetch(base + \"/_superusers/auth-with-password\", {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\tbody: JSON.stringify({ identity: opts.email, password: opts.password }),\n\t\t});\n\t\tif (!loginRes.ok) {\n\t\t\tloginRes = await fetch(base + \"/superusers/login\", {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t\tbody: JSON.stringify({ email: opts.email, password: opts.password }),\n\t\t\t});\n\t\t}\n\t\tif (!loginRes.ok) {\n\t\t\tconst text = await loginRes.text();\n\t\t\tfail(\n\t\t\t\t`Superuser login failed (${loginRes.status}). ${text.slice(0, 200)}`,\n\t\t\t);\n\t\t}\n\t\tconst loginData = (await loginRes.json()) as { token?: string };\n\t\tif (!loginData.token) fail(\"Login response did not include a token.\");\n\t\tauthKey = loginData.token;\n\t}\n\n\t// Step 2: fetch collections\n\tconst collRes = await fetch(base + \"/collections\", {\n\t\theaders: { Authorization: \"Bearer \" + authKey },\n\t});\n\tif (!collRes.ok) {\n\t\tconst text = await collRes.text();\n\t\tfail(\n\t\t\t`Failed to fetch collections (${collRes.status}). ${text.slice(0, 200)}`,\n\t\t);\n\t}\n\treturn (await collRes.json()) as CollectionsResponse;\n}\n\nasync function main(): Promise<void> {\n\tconst opts = parseArgs(process.argv.slice(2));\n\tconst authLabel = opts.apiKey\n\t\t? `API key ${opts.apiKey.slice(0, 4)}…${opts.apiKey.slice(-4)}`\n\t\t: opts.email;\n\tconsole.log(`\\n🔌 Connecting to ${opts.url} as ${authLabel} …`);\n\n\tconst { items } = await fetchCollections(opts);\n\tconsole.log(`📦 Found ${items.length} collection(s).`);\n\n\tconst source = generateTypes(items, {\n\t\tpackageName: opts.packageName,\n\t\tskipSystem: opts.skipSystem,\n\t});\n\n\tconst outPath = resolve(process.cwd(), opts.out);\n\tawait writeFile(outPath, source, \"utf8\");\n\tconsole.log(`✅ Wrote ${outPath} (${source.length} bytes).`);\n\tconsole.log(\n\t\t`\\nImport it in your app:\\n import { createClient } from './${opts.out.replace(/\\.ts$/, \"\")}';\\n`,\n\t);\n}\n\nmain().catch((err) => {\n\tconsole.error(err);\n\tprocess.exit(1);\n});\n","// ── Type mapping (runtime + codegen) ────────────────────\n// Maps server field types to TypeScript types.\n// Shared by the runtime `SchemaTypes` helper and the codegen CLI.\n\nimport type { SchemaField } from \"./schema\";\n\n/**\n * Map a single server field to its TypeScript type string.\n * Used by the codegen CLI to emit interface members.\n *\n * @param field The field definition.\n * @param fallback Fallback type for unknown field types (default `unknown`).\n */\nexport function fieldTypeScriptType(\n\tfield: SchemaField,\n\tfallback = \"unknown\",\n): string {\n\tconst opts = field.options ?? {};\n\tswitch (field.type) {\n\t\tcase \"text\":\n\t\tcase \"email\":\n\t\tcase \"url\":\n\t\tcase \"editor\":\n\t\tcase \"date\":\n\t\tcase \"datetime\":\n\t\t\treturn \"string\";\n\t\tcase \"number\":\n\t\t\treturn \"number\";\n\t\tcase \"bool\":\n\t\t\treturn \"boolean\";\n\t\tcase \"select\": {\n\t\t\tconst values = Array.isArray(opts.values) ? opts.values : [];\n\t\t\tif (values.length > 0) {\n\t\t\t\treturn values.map((v) => JSON.stringify(String(v))).join(\" | \");\n\t\t\t}\n\t\t\treturn \"string\";\n\t\t}\n\t\tcase \"multi_select\": {\n\t\t\tconst values = Array.isArray(opts.values) ? opts.values : [];\n\t\t\tif (values.length > 0) {\n\t\t\t\treturn `(${values.map((v) => JSON.stringify(String(v))).join(\" | \")})[]`;\n\t\t\t}\n\t\t\treturn \"string[]\";\n\t\t}\n\t\tcase \"file\":\n\t\t\treturn \"string\";\n\t\tcase \"multi_file\":\n\t\t\treturn \"string[]\";\n\t\tcase \"json\":\n\t\tcase \"geo\":\n\t\t\treturn \"Record<string, unknown>\";\n\t\tcase \"relation\":\n\t\t\t// Relations store the target record's ID (string) — or an array\n\t\t\t// of IDs when multi-relation (maxSelect > 1).\n\t\t\treturn (opts.maxSelect ?? 1) > 1 ? \"string[]\" : \"string\";\n\t\tcase \"password\":\n\t\t\t// Passwords are write-only; never expose on read models.\n\t\t\treturn \"never\";\n\t\tdefault:\n\t\t\treturn fallback;\n\t}\n}\n\n/**\n * Returns the runtime type kind for a field — used by {@link schemaFieldType}\n * to build structural types at runtime.\n */\nexport type FieldTypeKind =\n\t| \"string\"\n\t| \"number\"\n\t| \"boolean\"\n\t| \"string-array\"\n\t| \"json\"\n\t| \"relation\"\n\t| \"relation-many\"\n\t| \"password\"\n\t| \"unknown\";\n\n/** Map a server field to its runtime type kind. */\nexport function fieldTypeKind(field: SchemaField): FieldTypeKind {\n\tconst opts = field.options ?? {};\n\tswitch (field.type) {\n\t\tcase \"text\":\n\t\tcase \"email\":\n\t\tcase \"url\":\n\t\tcase \"editor\":\n\t\tcase \"date\":\n\t\tcase \"datetime\":\n\t\tcase \"select\":\n\t\tcase \"file\":\n\t\t\treturn \"string\";\n\t\tcase \"number\":\n\t\t\treturn \"number\";\n\t\tcase \"bool\":\n\t\t\treturn \"boolean\";\n\t\tcase \"multi_select\":\n\t\tcase \"multi_file\":\n\t\t\treturn \"string-array\";\n\t\tcase \"json\":\n\t\tcase \"geo\":\n\t\t\treturn \"json\";\n\t\tcase \"relation\":\n\t\t\treturn (opts.maxSelect ?? 1) > 1 ? \"relation-many\" : \"relation\";\n\t\tcase \"password\":\n\t\t\treturn \"password\";\n\t\tdefault:\n\t\t\treturn \"unknown\";\n\t}\n}\n\n/**\n * Derive a TypeScript field type from a {@link SchemaField} — the runtime\n * counterpart to the codegen mapper. Lets consumers build typed clients\n * from a fetched schema without running the CLI.\n */\nexport function schemaFieldType(field: SchemaField): unknown {\n\tswitch (fieldTypeKind(field)) {\n\t\tcase \"string\":\n\t\t\treturn String;\n\t\tcase \"number\":\n\t\t\treturn Number;\n\t\tcase \"boolean\":\n\t\t\treturn Boolean;\n\t\tcase \"string-array\":\n\t\t\treturn [String] as const;\n\t\tcase \"relation\":\n\t\t\treturn String;\n\t\tcase \"relation-many\":\n\t\t\treturn [String] as const;\n\t\tcase \"json\":\n\t\t\treturn Object;\n\t\tcase \"password\":\n\t\t\treturn undefined;\n\t\tcase \"unknown\":\n\t\t\treturn undefined;\n\t}\n}\n","// ── Codegen ─────────────────────────────────────────────\n// Generates a `lazypock.types.ts` module from the live API schema.\n//\n// The generated file exports:\n// - One interface per collection (e.g. `PostsRecord`)\n// - A `LazypockCollections` map: { posts: PostsRecord; users: UsersRecord; ... }\n// - A `createClient()` factory pre-bound to those types, so\n// `client.collection(\"posts\").create({ title: \"x\" })` is fully type-checked.\n//\n// The CLI in `src/cli.ts` wires this to `GET /collections`.\n\nimport type { CollectionSchema, SchemaField } from \"./schema\";\nimport { fieldTypeScriptType } from \"./typegen\";\n\n/** Format a raw collection name into a valid TS identifier (PascalCase). */\nexport function collectionTypeName(name: string): string {\n\treturn name\n\t\t.split(/[^a-zA-Z0-9]+/)\n\t\t.filter(Boolean)\n\t\t.map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n\t\t.join(\"\")\n\t\t.replace(/^[0-9]/, \"_$&\");\n}\n\n/** Interface member name — sanitize hyphens/spaces but keep readability. */\nexport function fieldKey(name: string): string {\n\treturn name.replace(/[^a-zA-Z0-9_]/g, \"_\");\n}\n\n\n\n/**\n * Generate the full TypeScript source for the typed SDK module.\n *\n * @param collections Collections fetched from the API.\n * @param options Generation options.\n */\nexport function generateTypes(\n\tcollections: CollectionSchema[],\n\toptions: {\n\t\t/** Import specifier for the lazypock package (default `lazypock`). */\n\t\tpackageName?: string;\n\t\t/** Emit base record fields (id, created, updated, …). Default true. */\n\t\tincludeBaseFields?: boolean;\n\t\t/** Skip system collections (names starting with `_` or `users`). Default false. */\n\t\tskipSystem?: boolean;\n\t} = {},\n): string {\n\tconst {\n\t\tpackageName = \"lazypock\",\n\t\tincludeBaseFields = true,\n\t\tskipSystem = false,\n\t} = options;\n\n\tconst filtered = skipSystem\n\t\t? collections.filter(\n\t\t\t\t(c) => !c.system && !c.name.startsWith(\"_\") && c.name !== \"users\",\n\t\t\t)\n\t\t: collections;\n\n\tconst sections: string[] = [];\n\tsections.push(`// ── Auto-generated by lazypock-ts ──────────────────────────\n// Do not edit by hand. Regenerate with: npx lazypock-gen\n// Schema snapshot: ${new Date().toISOString()}`);\n\n\tif (includeBaseFields) {\n\t\tsections.push(`export interface BaseRecord {\n id: string;\n collectionId: string;\n collectionName: string;\n created: string;\n updated: string;\n}`);\n\t}\n\n\t// One interface per collection\n\tfor (const coll of filtered) {\n\t\tconst typeName = collectionTypeName(coll.name);\n\t\tconst fields = coll.fields ?? [];\n\t\tconst lines = fields.map((f) => memberLine(f)).filter((l) => l !== \"\");\n\t\tconst body = lines.join(\"\\n\");\n\t\tsections.push(\n\t\t\t`export interface ${typeName}Record${renderInterface({\n\t\t\t\textends: includeBaseFields ? \"BaseRecord\" : undefined,\n\t\t\t\tbody,\n\t\t\t})}`,\n\t\t);\n\t}\n\n\t// Auth collection type\n\tsections.push(`export interface AuthRecord extends BaseRecord {\n email: string;\n verified: boolean;\n}`);\n\n\t// Collections map\n\tconst mapEntries = filtered\n\t\t.map(\n\t\t\t(c) =>\n\t\t\t\t` \"${c.name}\": ${collectionTypeName(c.name)}Record${\n\t\t\t\t\tc.type === \"auth\" ? \" & AuthRecord\" : \"\"\n\t\t\t\t};`,\n\t\t)\n\t\t.join(\"\\n\");\n\tsections.push(`export interface LazypockCollections {\n${mapEntries}\n}`);\n\n\t// Runtime schema snapshot — lets the client exclude hidden fields by\n\t// default, validate select/expand, and derive visible field lists.\n\tconst schemaEntries = collections\n\t\t.map(\n\t\t\t(c) =>\n\t\t\t\t` {\n id: ${JSON.stringify(c.id ?? null)},\n name: ${JSON.stringify(c.name)},\n type: ${JSON.stringify(c.type)},\n system: ${JSON.stringify(c.system ?? false)},\n fields: ${JSON.stringify(c.fields ?? [], null, 2)},\n rules: ${JSON.stringify(c.rules ?? {})},\n options: ${JSON.stringify(c.options ?? {})},\n }`,\n\t\t)\n\t\t.join(\",\\n\");\n\tsections.push(`// Schema snapshot (runtime) — hidden-field exclusion + query validation.\nexport const lazypockSchema: import(\"${packageName}\").CollectionSchema[] = [\n${schemaEntries}\n];`);\n\n\t// createClient factory\n\tsections.push(`import { LazypockClient, type LazypockClientOptions, type CollectionService } from \"${packageName}\";\n\n/**\n * Create a Lazypock client typed against this schema snapshot.\n * Collection access is fully type-checked:\n * client.collection(\"posts\").create({ title: \"x\" }) // title must exist\n *\n * The schema snapshot is wired into the client automatically, so hidden\n * fields are excluded from responses and select/expand are validated.\n */\nexport function createClient(options: LazypockClientOptions): TypedClient {\n return new TypedClient({\n ...options,\n types: {\n ...options.types,\n schemas: options.types?.schemas ?? lazypockSchema,\n },\n });\n}\n\nexport class TypedClient extends LazypockClient {\n override collection<T extends string>(name: T): T extends keyof LazypockCollections\n ? CollectionService<LazypockCollections[T]>\n : CollectionService<unknown> {\n return super.collection(name) as T extends keyof LazypockCollections ? CollectionService<LazypockCollections[T]> : CollectionService<unknown>;\n }\n}\n`);\n\n\treturn sections.join(\"\\n\\n\") + \"\\n\";\n}\n\n/**\n * Render the interface body including the extends clause.\n * Empty body → ` extends BaseRecord {}` (valid TS).\n */\nfunction renderInterface(opts: { extends?: string; body: string }): string {\n\tconst ext = opts.extends ? ` extends ${opts.extends}` : \"\";\n\tif (!opts.body) return `${ext} {}`;\n\treturn `${ext} {\\n${opts.body}\\n}`;\n}\n\n/** Render a single interface member line for a field. */\nfunction memberLine(f: SchemaField): string {\n\t// Hidden fields are excluded from API responses by the schema-aware\n\t// client default — don't expose them on the read model either.\n\tif (f.hidden) return \"\";\n\tconst key = fieldKey(f.name);\n\tconst req = f.required || f.type === \"password\" ? \"\" : \"?\";\n\tconst type = fieldTypeScriptType(f);\n\t// `never` members (passwords) are omitted from read models.\n\tif (type === \"never\") return \"\";\n\treturn ` ${JSON.stringify(key)}${req}: ${type};`;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/typegen.ts","../src/codegen.ts"],"sourcesContent":["#!/usr/bin/env node\n// ── Codegen CLI ─────────────────────────────────────────\n// `lazypock` — fetches the live collection schema from a Lazypock\n// API and writes a fully-typed `lazypock.types.ts` module.\n//\n// Auth methods (pick one):\n// 1. Superuser email + password:\n// npx lazypock --url http://localhost:4000/api --email admin@... --password ...\n// 2. API key (recommended, generated from the Settings dashboard):\n// npx lazypock --url http://localhost:4000/api --apikey <key>\n//\n// Or via env vars (no flags):\n// LAZYPOCK_URL=... LAZYPOCK_API_KEY=... npx lazypock\n// LAZYPOCK_URL=... LAZYPOCK_EMAIL=... LAZYPOCK_PASSWORD=... npx lazypock\n//\n// NOTE: `lazypock-gen` remains as a deprecated alias for backwards\n// compatibility. Both invoke the same executable.\n\nimport { writeFile } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\nimport { generateTypes } from \"./codegen\";\nimport type { CollectionsResponse } from \"./schema\";\n\ninterface CliOptions {\n\turl: string;\n\temail: string;\n\tpassword: string;\n\tapiKey: string;\n\tout: string;\n\tpackageName: string;\n\tskipSystem: boolean;\n}\n\nfunction fail(msg: string): never {\n\tconsole.error(`\\n❌ ${msg}\\n`);\n\tprocess.exit(1);\n}\n\nfunction parseArgs(argv: string[]): CliOptions {\n\tconst args = [...argv];\n\tconst get = (flag: string, envKey: string, def = \"\"): string => {\n\t\tconst i = args.indexOf(flag);\n\t\tif (i !== -1 && i + 1 < args.length) return args[i + 1];\n\t\treturn process.env[envKey] ?? def;\n\t};\n\tconst has = (flag: string): boolean => args.includes(flag);\n\n\tconst url = get(\"--url\", \"LAZYPOCK_URL\");\n\tconst email = get(\"--email\", \"LAZYPOCK_EMAIL\");\n\tconst password = get(\"--password\", \"LAZYPOCK_PASSWORD\");\n\tconst apiKey =\n\t\tget(\"--apikey\", \"LAZYPOCK_API_KEY\") || get(\"--api-key\", \"LAZYPOCK_API_KEY\");\n\n\tif (!url) fail(\"Missing API URL. Pass --url or set LAZYPOCK_URL.\");\n\tif (!apiKey) {\n\t\tif (!email)\n\t\t\tfail(\n\t\t\t\t\"Missing credentials. Pass --apikey, or --email + --password, or set LAZYPOCK_API_KEY / LAZYPOCK_EMAIL.\",\n\t\t\t);\n\t\tif (!password)\n\t\t\tfail(\"Missing password. Pass --password, or set LAZYPOCK_PASSWORD.\");\n\t}\n\n\tconst out =\n\t\tget(\"--output\", \"LAZYPOCK_OUT\") ||\n\t\tget(\"--out\", \"LAZYPOCK_OUT\", \"lazypock.types.ts\");\n\tconst packageName = get(\"--package\", \"LAZYPOCK_PACKAGE\", \"lazypock\");\n\n\treturn {\n\t\turl,\n\t\temail,\n\t\tpassword,\n\t\tapiKey,\n\t\tout,\n\t\tpackageName,\n\t\tskipSystem: has(\"--skip-system\"),\n\t};\n}\n\nasync function fetchCollections(\n\topts: CliOptions,\n): Promise<CollectionsResponse> {\n\tconst base = opts.url.replace(/\\/+$/, \"\");\n\n\tlet authKey: string;\n\tif (opts.apiKey) {\n\t\t// Step 1: use a stored API key directly (no login round-trip).\n\t\t// The key is sent as `Authorization: Bearer <key>` and recognised\n\t\t// by the backend's Auth.Plug as an API key.\n\t\tauthKey = opts.apiKey;\n\t} else {\n\t\t// Step 1: login as superuser to get a token.\n\t\t// Prefer the PocketBase-parity `_superusers` auth collection endpoint,\n\t\t// fall back to the legacy /superusers/login for older servers.\n\t\tlet loginRes = await fetch(base + \"/_superusers/auth-with-password\", {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\tbody: JSON.stringify({ identity: opts.email, password: opts.password }),\n\t\t});\n\t\tif (!loginRes.ok) {\n\t\t\tloginRes = await fetch(base + \"/superusers/login\", {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t\tbody: JSON.stringify({ email: opts.email, password: opts.password }),\n\t\t\t});\n\t\t}\n\t\tif (!loginRes.ok) {\n\t\t\tconst text = await loginRes.text();\n\t\t\tfail(\n\t\t\t\t`Superuser login failed (${loginRes.status}). ${text.slice(0, 200)}`,\n\t\t\t);\n\t\t}\n\t\tconst loginData = (await loginRes.json()) as { token?: string };\n\t\tif (!loginData.token) fail(\"Login response did not include a token.\");\n\t\tauthKey = loginData.token;\n\t}\n\n\t// Step 2: fetch collections\n\tconst collRes = await fetch(base + \"/collections\", {\n\t\theaders: { Authorization: \"Bearer \" + authKey },\n\t});\n\tif (!collRes.ok) {\n\t\tconst text = await collRes.text();\n\t\tfail(\n\t\t\t`Failed to fetch collections (${collRes.status}). ${text.slice(0, 200)}`,\n\t\t);\n\t}\n\treturn (await collRes.json()) as CollectionsResponse;\n}\n\nasync function main(): Promise<void> {\n\tconst opts = parseArgs(process.argv.slice(2));\n\tconst authLabel = opts.apiKey\n\t\t? `API key ${opts.apiKey.slice(0, 4)}…${opts.apiKey.slice(-4)}`\n\t\t: opts.email;\n\tconsole.log(`\\n🔌 Connecting to ${opts.url} as ${authLabel} …`);\n\n\tconst { items } = await fetchCollections(opts);\n\tconsole.log(`📦 Found ${items.length} collection(s).`);\n\n\tconst source = generateTypes(items, {\n\t\tpackageName: opts.packageName,\n\t\tskipSystem: opts.skipSystem,\n\t});\n\n\tconst outPath = resolve(process.cwd(), opts.out);\n\tawait writeFile(outPath, source, \"utf8\");\n\tconsole.log(`✅ Wrote ${outPath} (${source.length} bytes).`);\n\tconsole.log(\n\t\t`\\nImport it in your app:\\n import { createClient } from './${opts.out.replace(/\\.ts$/, \"\")}';\\n`,\n\t);\n}\n\nmain().catch((err) => {\n\tconsole.error(err);\n\tprocess.exit(1);\n});\n","// ── Type mapping (runtime + codegen) ────────────────────\n// Maps server field types to TypeScript types.\n// Shared by the runtime `SchemaTypes` helper and the codegen CLI.\n\nimport type { SchemaField } from \"./schema\";\n\n/**\n * Map a single server field to its TypeScript type string.\n * Used by the codegen CLI to emit interface members.\n *\n * @param field The field definition.\n * @param fallback Fallback type for unknown field types (default `unknown`).\n */\nexport function fieldTypeScriptType(\n\tfield: SchemaField,\n\tfallback = \"unknown\",\n): string {\n\tconst opts = field.options ?? {};\n\tswitch (field.type) {\n\t\tcase \"text\":\n\t\tcase \"email\":\n\t\tcase \"url\":\n\t\tcase \"editor\":\n\t\tcase \"date\":\n\t\tcase \"datetime\":\n\t\t\treturn \"string\";\n\t\tcase \"number\":\n\t\t\treturn \"number\";\n\t\tcase \"bool\":\n\t\t\treturn \"boolean\";\n\t\tcase \"select\": {\n\t\t\tconst values = Array.isArray(opts.values) ? opts.values : [];\n\t\t\tif (values.length > 0) {\n\t\t\t\treturn values.map((v) => JSON.stringify(String(v))).join(\" | \");\n\t\t\t}\n\t\t\treturn \"string\";\n\t\t}\n\t\tcase \"multi_select\": {\n\t\t\tconst values = Array.isArray(opts.values) ? opts.values : [];\n\t\t\tif (values.length > 0) {\n\t\t\t\treturn `(${values.map((v) => JSON.stringify(String(v))).join(\" | \")})[]`;\n\t\t\t}\n\t\t\treturn \"string[]\";\n\t\t}\n\t\tcase \"file\":\n\t\t\treturn \"string\";\n\t\tcase \"multi_file\":\n\t\t\treturn \"string[]\";\n\t\tcase \"json\":\n\t\tcase \"geo\":\n\t\t\treturn \"Record<string, unknown>\";\n\t\tcase \"relation\":\n\t\t\t// Relations store the target record's ID (string) — or an array\n\t\t\t// of IDs when multi-relation (maxSelect > 1).\n\t\t\treturn (opts.maxSelect ?? 1) > 1 ? \"string[]\" : \"string\";\n\t\tcase \"password\":\n\t\t\t// Passwords are write-only; never expose on read models.\n\t\t\treturn \"never\";\n\t\tdefault:\n\t\t\treturn fallback;\n\t}\n}\n\n/**\n * Returns the runtime type kind for a field — used by {@link schemaFieldType}\n * to build structural types at runtime.\n */\nexport type FieldTypeKind =\n\t| \"string\"\n\t| \"number\"\n\t| \"boolean\"\n\t| \"string-array\"\n\t| \"json\"\n\t| \"relation\"\n\t| \"relation-many\"\n\t| \"password\"\n\t| \"unknown\";\n\n/** Map a server field to its runtime type kind. */\nexport function fieldTypeKind(field: SchemaField): FieldTypeKind {\n\tconst opts = field.options ?? {};\n\tswitch (field.type) {\n\t\tcase \"text\":\n\t\tcase \"email\":\n\t\tcase \"url\":\n\t\tcase \"editor\":\n\t\tcase \"date\":\n\t\tcase \"datetime\":\n\t\tcase \"select\":\n\t\tcase \"file\":\n\t\t\treturn \"string\";\n\t\tcase \"number\":\n\t\t\treturn \"number\";\n\t\tcase \"bool\":\n\t\t\treturn \"boolean\";\n\t\tcase \"multi_select\":\n\t\tcase \"multi_file\":\n\t\t\treturn \"string-array\";\n\t\tcase \"json\":\n\t\tcase \"geo\":\n\t\t\treturn \"json\";\n\t\tcase \"relation\":\n\t\t\treturn (opts.maxSelect ?? 1) > 1 ? \"relation-many\" : \"relation\";\n\t\tcase \"password\":\n\t\t\treturn \"password\";\n\t\tdefault:\n\t\t\treturn \"unknown\";\n\t}\n}\n\n/**\n * Derive a TypeScript field type from a {@link SchemaField} — the runtime\n * counterpart to the codegen mapper. Lets consumers build typed clients\n * from a fetched schema without running the CLI.\n */\nexport function schemaFieldType(field: SchemaField): unknown {\n\tswitch (fieldTypeKind(field)) {\n\t\tcase \"string\":\n\t\t\treturn String;\n\t\tcase \"number\":\n\t\t\treturn Number;\n\t\tcase \"boolean\":\n\t\t\treturn Boolean;\n\t\tcase \"string-array\":\n\t\t\treturn [String] as const;\n\t\tcase \"relation\":\n\t\t\treturn String;\n\t\tcase \"relation-many\":\n\t\t\treturn [String] as const;\n\t\tcase \"json\":\n\t\t\treturn Object;\n\t\tcase \"password\":\n\t\t\treturn undefined;\n\t\tcase \"unknown\":\n\t\t\treturn undefined;\n\t}\n}\n","// ── Codegen ─────────────────────────────────────────────\n// Generates a `lazypock.types.ts` module from the live API schema.\n//\n// The generated file exports:\n// - One interface per collection (e.g. `PostsRecord`)\n// - A `LazypockCollections` map: { posts: PostsRecord; users: UsersRecord; ... }\n// - A `createClient()` factory pre-bound to those types, so\n// `client.collection(\"posts\").create({ title: \"x\" })` is fully type-checked.\n//\n// The CLI in `src/cli.ts` wires this to `GET /collections`.\n\nimport type { CollectionSchema, SchemaField } from \"./schema\";\nimport { fieldTypeScriptType } from \"./typegen\";\n\n/** Format a raw collection name into a valid TS identifier (PascalCase). */\nexport function collectionTypeName(name: string): string {\n\treturn name\n\t\t.split(/[^a-zA-Z0-9]+/)\n\t\t.filter(Boolean)\n\t\t.map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n\t\t.join(\"\")\n\t\t.replace(/^[0-9]/, \"_$&\");\n}\n\n/** Interface member name — sanitize hyphens/spaces but keep readability. */\nexport function fieldKey(name: string): string {\n\treturn name.replace(/[^a-zA-Z0-9_]/g, \"_\");\n}\n\n\n\n/**\n * Generate the full TypeScript source for the typed SDK module.\n *\n * @param collections Collections fetched from the API.\n * @param options Generation options.\n */\nexport function generateTypes(\n\tcollections: CollectionSchema[],\n\toptions: {\n\t\t/** Import specifier for the lazypock package (default `lazypock`). */\n\t\tpackageName?: string;\n\t\t/** Emit base record fields (id, created, updated, …). Default true. */\n\t\tincludeBaseFields?: boolean;\n\t\t/** Skip system collections (names starting with `_` or `users`). Default false. */\n\t\tskipSystem?: boolean;\n\t} = {},\n): string {\n\tconst {\n\t\tpackageName = \"lazypock\",\n\t\tincludeBaseFields = true,\n\t\tskipSystem = false,\n\t} = options;\n\n\tconst filtered = skipSystem\n\t\t? collections.filter(\n\t\t\t\t(c) => !c.system && !c.name.startsWith(\"_\") && c.name !== \"users\",\n\t\t\t)\n\t\t: collections;\n\n\tconst sections: string[] = [];\n\tsections.push(`// ── Auto-generated by lazypock-ts ──────────────────────────\n// Do not edit by hand. Regenerate with: npx lazypock-gen\n// Schema snapshot: ${new Date().toISOString()}`);\n\n\tif (includeBaseFields) {\n\t\tsections.push(`export interface BaseRecord {\n id: string;\n collectionId: string;\n collectionName: string;\n created: string;\n updated: string;\n}`);\n\t}\n\n\t// One interface per collection\n\tfor (const coll of filtered) {\n\t\tconst typeName = collectionTypeName(coll.name);\n\t\tconst fields = coll.fields ?? [];\n\t\tconst lines = fields.map((f) => memberLine(f)).filter((l) => l !== \"\");\n\t\tconst body = lines.join(\"\\n\");\n\t\tsections.push(\n\t\t\t`export interface ${typeName}Record${renderInterface({\n\t\t\t\textends: includeBaseFields ? \"BaseRecord\" : undefined,\n\t\t\t\tbody,\n\t\t\t})}`,\n\t\t);\n\t}\n\n\t// Auth collection type\n\tsections.push(`export interface AuthRecord extends BaseRecord {\n email: string;\n verified: boolean;\n}`);\n\n\t// Collections map\n\tconst mapEntries = filtered\n\t\t.map(\n\t\t\t(c) =>\n\t\t\t\t` \"${c.name}\": ${collectionTypeName(c.name)}Record${\n\t\t\t\t\tc.type === \"auth\" ? \" & AuthRecord\" : \"\"\n\t\t\t\t};`,\n\t\t)\n\t\t.join(\"\\n\");\n\tsections.push(`export interface LazypockCollections {\n${mapEntries}\n}`);\n\n\t// Runtime schema snapshot — lets the client exclude hidden fields by\n\t// default, validate select/expand, and derive visible field lists.\n\tconst schemaEntries = collections\n\t\t.map(\n\t\t\t(c) =>\n\t\t\t\t` {\n id: ${JSON.stringify(c.id ?? null)},\n name: ${JSON.stringify(c.name)},\n type: ${JSON.stringify(c.type)},\n system: ${JSON.stringify(c.system ?? false)},\n fields: ${JSON.stringify(c.fields ?? [], null, 2)},\n rules: ${JSON.stringify(c.rules ?? {})},\n options: ${JSON.stringify(c.options ?? {})},\n }`,\n\t\t)\n\t\t.join(\",\\n\");\n\tsections.push(`// Schema snapshot (runtime) — hidden-field exclusion + query validation.\nexport const lazypockSchema: import(\"${packageName}\").CollectionSchema[] = [\n${schemaEntries}\n];`);\n\n\t// createClient factory\n\tsections.push(`import { LazypockClient, type LazypockClientOptions, type CollectionService } from \"${packageName}\";\n\n/**\n * Create a Lazypock client typed against this schema snapshot.\n * Collection access is fully type-checked:\n * client.collection(\"posts\").create({ title: \"x\" }) // title must exist\n *\n * The schema snapshot is wired into the client automatically, so hidden\n * fields are excluded from responses and select/expand are validated.\n */\nexport function createClient(options: LazypockClientOptions): TypedClient {\n return new TypedClient({\n ...options,\n types: {\n ...options.types,\n schemas: options.types?.schemas ?? lazypockSchema,\n },\n });\n}\n\nexport class TypedClient extends LazypockClient {\n // K extends keyof LazypockCollections (rather than a generic string with a\n // conditional return) so the IDE suggests collection names and unknown names\n // are rejected at compile time:\n // client.collection(\"posts\") // suggested + typed\n // client.collection(\"nope\") // TS error\n override collection<K extends keyof LazypockCollections>(name: K): CollectionService<LazypockCollections[K]> {\n return super.collection(name) as CollectionService<LazypockCollections[K]>;\n }\n}\n`);\n\n\treturn sections.join(\"\\n\\n\") + \"\\n\";\n}\n\n/**\n * Render the interface body including the extends clause.\n * Empty body → ` extends BaseRecord {}` (valid TS).\n */\nfunction renderInterface(opts: { extends?: string; body: string }): string {\n\tconst ext = opts.extends ? ` extends ${opts.extends}` : \"\";\n\tif (!opts.body) return `${ext} {}`;\n\treturn `${ext} {\\n${opts.body}\\n}`;\n}\n\n/** Render a single interface member line for a field. */\nfunction memberLine(f: SchemaField): string {\n\t// Hidden fields are excluded from API responses by the schema-aware\n\t// client default — don't expose them on the read model either.\n\tif (f.hidden) return \"\";\n\tconst key = fieldKey(f.name);\n\tconst req = f.required || f.type === \"password\" ? \"\" : \"?\";\n\tconst type = fieldTypeScriptType(f);\n\t// `never` members (passwords) are omitted from read models.\n\tif (type === \"never\") return \"\";\n\treturn ` ${JSON.stringify(key)}${req}: ${type};`;\n}\n"],"mappings":"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","names":[]}
|
package/dist/cli.global.js
CHANGED
|
@@ -144,10 +144,13 @@ export function createClient(options: LazypockClientOptions): TypedClient {
|
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
export class TypedClient extends LazypockClient {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
147
|
+
// K extends keyof LazypockCollections (rather than a generic string with a
|
|
148
|
+
// conditional return) so the IDE suggests collection names and unknown names
|
|
149
|
+
// are rejected at compile time:
|
|
150
|
+
// client.collection("posts") // suggested + typed
|
|
151
|
+
// client.collection("nope") // TS error
|
|
152
|
+
override collection<K extends keyof LazypockCollections>(name: K): CollectionService<LazypockCollections[K]> {
|
|
153
|
+
return super.collection(name) as CollectionService<LazypockCollections[K]>;
|
|
151
154
|
}
|
|
152
155
|
}
|
|
153
156
|
`);
|
package/dist/cli.global.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts","../src/typegen.ts","../src/codegen.ts"],"sourcesContent":["#!/usr/bin/env node\n// ── Codegen CLI ─────────────────────────────────────────\n// `lazypock` — fetches the live collection schema from a Lazypock\n// API and writes a fully-typed `lazypock.types.ts` module.\n//\n// Auth methods (pick one):\n// 1. Superuser email + password:\n// npx lazypock --url http://localhost:4000/api --email admin@... --password ...\n// 2. API key (recommended, generated from the Settings dashboard):\n// npx lazypock --url http://localhost:4000/api --apikey <key>\n//\n// Or via env vars (no flags):\n// LAZYPOCK_URL=... LAZYPOCK_API_KEY=... npx lazypock\n// LAZYPOCK_URL=... LAZYPOCK_EMAIL=... LAZYPOCK_PASSWORD=... npx lazypock\n//\n// NOTE: `lazypock-gen` remains as a deprecated alias for backwards\n// compatibility. Both invoke the same executable.\n\nimport { writeFile } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\nimport { generateTypes } from \"./codegen\";\nimport type { CollectionsResponse } from \"./schema\";\n\ninterface CliOptions {\n\turl: string;\n\temail: string;\n\tpassword: string;\n\tapiKey: string;\n\tout: string;\n\tpackageName: string;\n\tskipSystem: boolean;\n}\n\nfunction fail(msg: string): never {\n\tconsole.error(`\\n❌ ${msg}\\n`);\n\tprocess.exit(1);\n}\n\nfunction parseArgs(argv: string[]): CliOptions {\n\tconst args = [...argv];\n\tconst get = (flag: string, envKey: string, def = \"\"): string => {\n\t\tconst i = args.indexOf(flag);\n\t\tif (i !== -1 && i + 1 < args.length) return args[i + 1];\n\t\treturn process.env[envKey] ?? def;\n\t};\n\tconst has = (flag: string): boolean => args.includes(flag);\n\n\tconst url = get(\"--url\", \"LAZYPOCK_URL\");\n\tconst email = get(\"--email\", \"LAZYPOCK_EMAIL\");\n\tconst password = get(\"--password\", \"LAZYPOCK_PASSWORD\");\n\tconst apiKey =\n\t\tget(\"--apikey\", \"LAZYPOCK_API_KEY\") || get(\"--api-key\", \"LAZYPOCK_API_KEY\");\n\n\tif (!url) fail(\"Missing API URL. Pass --url or set LAZYPOCK_URL.\");\n\tif (!apiKey) {\n\t\tif (!email)\n\t\t\tfail(\n\t\t\t\t\"Missing credentials. Pass --apikey, or --email + --password, or set LAZYPOCK_API_KEY / LAZYPOCK_EMAIL.\",\n\t\t\t);\n\t\tif (!password)\n\t\t\tfail(\"Missing password. Pass --password, or set LAZYPOCK_PASSWORD.\");\n\t}\n\n\tconst out =\n\t\tget(\"--output\", \"LAZYPOCK_OUT\") ||\n\t\tget(\"--out\", \"LAZYPOCK_OUT\", \"lazypock.types.ts\");\n\tconst packageName = get(\"--package\", \"LAZYPOCK_PACKAGE\", \"lazypock\");\n\n\treturn {\n\t\turl,\n\t\temail,\n\t\tpassword,\n\t\tapiKey,\n\t\tout,\n\t\tpackageName,\n\t\tskipSystem: has(\"--skip-system\"),\n\t};\n}\n\nasync function fetchCollections(\n\topts: CliOptions,\n): Promise<CollectionsResponse> {\n\tconst base = opts.url.replace(/\\/+$/, \"\");\n\n\tlet authKey: string;\n\tif (opts.apiKey) {\n\t\t// Step 1: use a stored API key directly (no login round-trip).\n\t\t// The key is sent as `Authorization: Bearer <key>` and recognised\n\t\t// by the backend's Auth.Plug as an API key.\n\t\tauthKey = opts.apiKey;\n\t} else {\n\t\t// Step 1: login as superuser to get a token.\n\t\t// Prefer the PocketBase-parity `_superusers` auth collection endpoint,\n\t\t// fall back to the legacy /superusers/login for older servers.\n\t\tlet loginRes = await fetch(base + \"/_superusers/auth-with-password\", {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\tbody: JSON.stringify({ identity: opts.email, password: opts.password }),\n\t\t});\n\t\tif (!loginRes.ok) {\n\t\t\tloginRes = await fetch(base + \"/superusers/login\", {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t\tbody: JSON.stringify({ email: opts.email, password: opts.password }),\n\t\t\t});\n\t\t}\n\t\tif (!loginRes.ok) {\n\t\t\tconst text = await loginRes.text();\n\t\t\tfail(\n\t\t\t\t`Superuser login failed (${loginRes.status}). ${text.slice(0, 200)}`,\n\t\t\t);\n\t\t}\n\t\tconst loginData = (await loginRes.json()) as { token?: string };\n\t\tif (!loginData.token) fail(\"Login response did not include a token.\");\n\t\tauthKey = loginData.token;\n\t}\n\n\t// Step 2: fetch collections\n\tconst collRes = await fetch(base + \"/collections\", {\n\t\theaders: { Authorization: \"Bearer \" + authKey },\n\t});\n\tif (!collRes.ok) {\n\t\tconst text = await collRes.text();\n\t\tfail(\n\t\t\t`Failed to fetch collections (${collRes.status}). ${text.slice(0, 200)}`,\n\t\t);\n\t}\n\treturn (await collRes.json()) as CollectionsResponse;\n}\n\nasync function main(): Promise<void> {\n\tconst opts = parseArgs(process.argv.slice(2));\n\tconst authLabel = opts.apiKey\n\t\t? `API key ${opts.apiKey.slice(0, 4)}…${opts.apiKey.slice(-4)}`\n\t\t: opts.email;\n\tconsole.log(`\\n🔌 Connecting to ${opts.url} as ${authLabel} …`);\n\n\tconst { items } = await fetchCollections(opts);\n\tconsole.log(`📦 Found ${items.length} collection(s).`);\n\n\tconst source = generateTypes(items, {\n\t\tpackageName: opts.packageName,\n\t\tskipSystem: opts.skipSystem,\n\t});\n\n\tconst outPath = resolve(process.cwd(), opts.out);\n\tawait writeFile(outPath, source, \"utf8\");\n\tconsole.log(`✅ Wrote ${outPath} (${source.length} bytes).`);\n\tconsole.log(\n\t\t`\\nImport it in your app:\\n import { createClient } from './${opts.out.replace(/\\.ts$/, \"\")}';\\n`,\n\t);\n}\n\nmain().catch((err) => {\n\tconsole.error(err);\n\tprocess.exit(1);\n});\n","// ── Type mapping (runtime + codegen) ────────────────────\n// Maps server field types to TypeScript types.\n// Shared by the runtime `SchemaTypes` helper and the codegen CLI.\n\nimport type { SchemaField } from \"./schema\";\n\n/**\n * Map a single server field to its TypeScript type string.\n * Used by the codegen CLI to emit interface members.\n *\n * @param field The field definition.\n * @param fallback Fallback type for unknown field types (default `unknown`).\n */\nexport function fieldTypeScriptType(\n\tfield: SchemaField,\n\tfallback = \"unknown\",\n): string {\n\tconst opts = field.options ?? {};\n\tswitch (field.type) {\n\t\tcase \"text\":\n\t\tcase \"email\":\n\t\tcase \"url\":\n\t\tcase \"editor\":\n\t\tcase \"date\":\n\t\tcase \"datetime\":\n\t\t\treturn \"string\";\n\t\tcase \"number\":\n\t\t\treturn \"number\";\n\t\tcase \"bool\":\n\t\t\treturn \"boolean\";\n\t\tcase \"select\": {\n\t\t\tconst values = Array.isArray(opts.values) ? opts.values : [];\n\t\t\tif (values.length > 0) {\n\t\t\t\treturn values.map((v) => JSON.stringify(String(v))).join(\" | \");\n\t\t\t}\n\t\t\treturn \"string\";\n\t\t}\n\t\tcase \"multi_select\": {\n\t\t\tconst values = Array.isArray(opts.values) ? opts.values : [];\n\t\t\tif (values.length > 0) {\n\t\t\t\treturn `(${values.map((v) => JSON.stringify(String(v))).join(\" | \")})[]`;\n\t\t\t}\n\t\t\treturn \"string[]\";\n\t\t}\n\t\tcase \"file\":\n\t\t\treturn \"string\";\n\t\tcase \"multi_file\":\n\t\t\treturn \"string[]\";\n\t\tcase \"json\":\n\t\tcase \"geo\":\n\t\t\treturn \"Record<string, unknown>\";\n\t\tcase \"relation\":\n\t\t\t// Relations store the target record's ID (string) — or an array\n\t\t\t// of IDs when multi-relation (maxSelect > 1).\n\t\t\treturn (opts.maxSelect ?? 1) > 1 ? \"string[]\" : \"string\";\n\t\tcase \"password\":\n\t\t\t// Passwords are write-only; never expose on read models.\n\t\t\treturn \"never\";\n\t\tdefault:\n\t\t\treturn fallback;\n\t}\n}\n\n/**\n * Returns the runtime type kind for a field — used by {@link schemaFieldType}\n * to build structural types at runtime.\n */\nexport type FieldTypeKind =\n\t| \"string\"\n\t| \"number\"\n\t| \"boolean\"\n\t| \"string-array\"\n\t| \"json\"\n\t| \"relation\"\n\t| \"relation-many\"\n\t| \"password\"\n\t| \"unknown\";\n\n/** Map a server field to its runtime type kind. */\nexport function fieldTypeKind(field: SchemaField): FieldTypeKind {\n\tconst opts = field.options ?? {};\n\tswitch (field.type) {\n\t\tcase \"text\":\n\t\tcase \"email\":\n\t\tcase \"url\":\n\t\tcase \"editor\":\n\t\tcase \"date\":\n\t\tcase \"datetime\":\n\t\tcase \"select\":\n\t\tcase \"file\":\n\t\t\treturn \"string\";\n\t\tcase \"number\":\n\t\t\treturn \"number\";\n\t\tcase \"bool\":\n\t\t\treturn \"boolean\";\n\t\tcase \"multi_select\":\n\t\tcase \"multi_file\":\n\t\t\treturn \"string-array\";\n\t\tcase \"json\":\n\t\tcase \"geo\":\n\t\t\treturn \"json\";\n\t\tcase \"relation\":\n\t\t\treturn (opts.maxSelect ?? 1) > 1 ? \"relation-many\" : \"relation\";\n\t\tcase \"password\":\n\t\t\treturn \"password\";\n\t\tdefault:\n\t\t\treturn \"unknown\";\n\t}\n}\n\n/**\n * Derive a TypeScript field type from a {@link SchemaField} — the runtime\n * counterpart to the codegen mapper. Lets consumers build typed clients\n * from a fetched schema without running the CLI.\n */\nexport function schemaFieldType(field: SchemaField): unknown {\n\tswitch (fieldTypeKind(field)) {\n\t\tcase \"string\":\n\t\t\treturn String;\n\t\tcase \"number\":\n\t\t\treturn Number;\n\t\tcase \"boolean\":\n\t\t\treturn Boolean;\n\t\tcase \"string-array\":\n\t\t\treturn [String] as const;\n\t\tcase \"relation\":\n\t\t\treturn String;\n\t\tcase \"relation-many\":\n\t\t\treturn [String] as const;\n\t\tcase \"json\":\n\t\t\treturn Object;\n\t\tcase \"password\":\n\t\t\treturn undefined;\n\t\tcase \"unknown\":\n\t\t\treturn undefined;\n\t}\n}\n","// ── Codegen ─────────────────────────────────────────────\n// Generates a `lazypock.types.ts` module from the live API schema.\n//\n// The generated file exports:\n// - One interface per collection (e.g. `PostsRecord`)\n// - A `LazypockCollections` map: { posts: PostsRecord; users: UsersRecord; ... }\n// - A `createClient()` factory pre-bound to those types, so\n// `client.collection(\"posts\").create({ title: \"x\" })` is fully type-checked.\n//\n// The CLI in `src/cli.ts` wires this to `GET /collections`.\n\nimport type { CollectionSchema, SchemaField } from \"./schema\";\nimport { fieldTypeScriptType } from \"./typegen\";\n\n/** Format a raw collection name into a valid TS identifier (PascalCase). */\nexport function collectionTypeName(name: string): string {\n\treturn name\n\t\t.split(/[^a-zA-Z0-9]+/)\n\t\t.filter(Boolean)\n\t\t.map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n\t\t.join(\"\")\n\t\t.replace(/^[0-9]/, \"_$&\");\n}\n\n/** Interface member name — sanitize hyphens/spaces but keep readability. */\nexport function fieldKey(name: string): string {\n\treturn name.replace(/[^a-zA-Z0-9_]/g, \"_\");\n}\n\n\n\n/**\n * Generate the full TypeScript source for the typed SDK module.\n *\n * @param collections Collections fetched from the API.\n * @param options Generation options.\n */\nexport function generateTypes(\n\tcollections: CollectionSchema[],\n\toptions: {\n\t\t/** Import specifier for the lazypock package (default `lazypock`). */\n\t\tpackageName?: string;\n\t\t/** Emit base record fields (id, created, updated, …). Default true. */\n\t\tincludeBaseFields?: boolean;\n\t\t/** Skip system collections (names starting with `_` or `users`). Default false. */\n\t\tskipSystem?: boolean;\n\t} = {},\n): string {\n\tconst {\n\t\tpackageName = \"lazypock\",\n\t\tincludeBaseFields = true,\n\t\tskipSystem = false,\n\t} = options;\n\n\tconst filtered = skipSystem\n\t\t? collections.filter(\n\t\t\t\t(c) => !c.system && !c.name.startsWith(\"_\") && c.name !== \"users\",\n\t\t\t)\n\t\t: collections;\n\n\tconst sections: string[] = [];\n\tsections.push(`// ── Auto-generated by lazypock-ts ──────────────────────────\n// Do not edit by hand. Regenerate with: npx lazypock-gen\n// Schema snapshot: ${new Date().toISOString()}`);\n\n\tif (includeBaseFields) {\n\t\tsections.push(`export interface BaseRecord {\n id: string;\n collectionId: string;\n collectionName: string;\n created: string;\n updated: string;\n}`);\n\t}\n\n\t// One interface per collection\n\tfor (const coll of filtered) {\n\t\tconst typeName = collectionTypeName(coll.name);\n\t\tconst fields = coll.fields ?? [];\n\t\tconst lines = fields.map((f) => memberLine(f)).filter((l) => l !== \"\");\n\t\tconst body = lines.join(\"\\n\");\n\t\tsections.push(\n\t\t\t`export interface ${typeName}Record${renderInterface({\n\t\t\t\textends: includeBaseFields ? \"BaseRecord\" : undefined,\n\t\t\t\tbody,\n\t\t\t})}`,\n\t\t);\n\t}\n\n\t// Auth collection type\n\tsections.push(`export interface AuthRecord extends BaseRecord {\n email: string;\n verified: boolean;\n}`);\n\n\t// Collections map\n\tconst mapEntries = filtered\n\t\t.map(\n\t\t\t(c) =>\n\t\t\t\t` \"${c.name}\": ${collectionTypeName(c.name)}Record${\n\t\t\t\t\tc.type === \"auth\" ? \" & AuthRecord\" : \"\"\n\t\t\t\t};`,\n\t\t)\n\t\t.join(\"\\n\");\n\tsections.push(`export interface LazypockCollections {\n${mapEntries}\n}`);\n\n\t// Runtime schema snapshot — lets the client exclude hidden fields by\n\t// default, validate select/expand, and derive visible field lists.\n\tconst schemaEntries = collections\n\t\t.map(\n\t\t\t(c) =>\n\t\t\t\t` {\n id: ${JSON.stringify(c.id ?? null)},\n name: ${JSON.stringify(c.name)},\n type: ${JSON.stringify(c.type)},\n system: ${JSON.stringify(c.system ?? false)},\n fields: ${JSON.stringify(c.fields ?? [], null, 2)},\n rules: ${JSON.stringify(c.rules ?? {})},\n options: ${JSON.stringify(c.options ?? {})},\n }`,\n\t\t)\n\t\t.join(\",\\n\");\n\tsections.push(`// Schema snapshot (runtime) — hidden-field exclusion + query validation.\nexport const lazypockSchema: import(\"${packageName}\").CollectionSchema[] = [\n${schemaEntries}\n];`);\n\n\t// createClient factory\n\tsections.push(`import { LazypockClient, type LazypockClientOptions, type CollectionService } from \"${packageName}\";\n\n/**\n * Create a Lazypock client typed against this schema snapshot.\n * Collection access is fully type-checked:\n * client.collection(\"posts\").create({ title: \"x\" }) // title must exist\n *\n * The schema snapshot is wired into the client automatically, so hidden\n * fields are excluded from responses and select/expand are validated.\n */\nexport function createClient(options: LazypockClientOptions): TypedClient {\n return new TypedClient({\n ...options,\n types: {\n ...options.types,\n schemas: options.types?.schemas ?? lazypockSchema,\n },\n });\n}\n\nexport class TypedClient extends LazypockClient {\n override collection<T extends string>(name: T): T extends keyof LazypockCollections\n ? CollectionService<LazypockCollections[T]>\n : CollectionService<unknown> {\n return super.collection(name) as T extends keyof LazypockCollections ? CollectionService<LazypockCollections[T]> : CollectionService<unknown>;\n }\n}\n`);\n\n\treturn sections.join(\"\\n\\n\") + \"\\n\";\n}\n\n/**\n * Render the interface body including the extends clause.\n * Empty body → ` extends BaseRecord {}` (valid TS).\n */\nfunction renderInterface(opts: { extends?: string; body: string }): string {\n\tconst ext = opts.extends ? ` extends ${opts.extends}` : \"\";\n\tif (!opts.body) return `${ext} {}`;\n\treturn `${ext} {\\n${opts.body}\\n}`;\n}\n\n/** Render a single interface member line for a field. */\nfunction memberLine(f: SchemaField): string {\n\t// Hidden fields are excluded from API responses by the schema-aware\n\t// client default — don't expose them on the read model either.\n\tif (f.hidden) return \"\";\n\tconst key = fieldKey(f.name);\n\tconst req = f.required || f.type === \"password\" ? \"\" : \"?\";\n\tconst type = fieldTypeScriptType(f);\n\t// `never` members (passwords) are omitted from read models.\n\tif (type === \"never\") return \"\";\n\treturn ` ${JSON.stringify(key)}${req}: ${type};`;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/typegen.ts","../src/codegen.ts"],"sourcesContent":["#!/usr/bin/env node\n// ── Codegen CLI ─────────────────────────────────────────\n// `lazypock` — fetches the live collection schema from a Lazypock\n// API and writes a fully-typed `lazypock.types.ts` module.\n//\n// Auth methods (pick one):\n// 1. Superuser email + password:\n// npx lazypock --url http://localhost:4000/api --email admin@... --password ...\n// 2. API key (recommended, generated from the Settings dashboard):\n// npx lazypock --url http://localhost:4000/api --apikey <key>\n//\n// Or via env vars (no flags):\n// LAZYPOCK_URL=... LAZYPOCK_API_KEY=... npx lazypock\n// LAZYPOCK_URL=... LAZYPOCK_EMAIL=... LAZYPOCK_PASSWORD=... npx lazypock\n//\n// NOTE: `lazypock-gen` remains as a deprecated alias for backwards\n// compatibility. Both invoke the same executable.\n\nimport { writeFile } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\nimport { generateTypes } from \"./codegen\";\nimport type { CollectionsResponse } from \"./schema\";\n\ninterface CliOptions {\n\turl: string;\n\temail: string;\n\tpassword: string;\n\tapiKey: string;\n\tout: string;\n\tpackageName: string;\n\tskipSystem: boolean;\n}\n\nfunction fail(msg: string): never {\n\tconsole.error(`\\n❌ ${msg}\\n`);\n\tprocess.exit(1);\n}\n\nfunction parseArgs(argv: string[]): CliOptions {\n\tconst args = [...argv];\n\tconst get = (flag: string, envKey: string, def = \"\"): string => {\n\t\tconst i = args.indexOf(flag);\n\t\tif (i !== -1 && i + 1 < args.length) return args[i + 1];\n\t\treturn process.env[envKey] ?? def;\n\t};\n\tconst has = (flag: string): boolean => args.includes(flag);\n\n\tconst url = get(\"--url\", \"LAZYPOCK_URL\");\n\tconst email = get(\"--email\", \"LAZYPOCK_EMAIL\");\n\tconst password = get(\"--password\", \"LAZYPOCK_PASSWORD\");\n\tconst apiKey =\n\t\tget(\"--apikey\", \"LAZYPOCK_API_KEY\") || get(\"--api-key\", \"LAZYPOCK_API_KEY\");\n\n\tif (!url) fail(\"Missing API URL. Pass --url or set LAZYPOCK_URL.\");\n\tif (!apiKey) {\n\t\tif (!email)\n\t\t\tfail(\n\t\t\t\t\"Missing credentials. Pass --apikey, or --email + --password, or set LAZYPOCK_API_KEY / LAZYPOCK_EMAIL.\",\n\t\t\t);\n\t\tif (!password)\n\t\t\tfail(\"Missing password. Pass --password, or set LAZYPOCK_PASSWORD.\");\n\t}\n\n\tconst out =\n\t\tget(\"--output\", \"LAZYPOCK_OUT\") ||\n\t\tget(\"--out\", \"LAZYPOCK_OUT\", \"lazypock.types.ts\");\n\tconst packageName = get(\"--package\", \"LAZYPOCK_PACKAGE\", \"lazypock\");\n\n\treturn {\n\t\turl,\n\t\temail,\n\t\tpassword,\n\t\tapiKey,\n\t\tout,\n\t\tpackageName,\n\t\tskipSystem: has(\"--skip-system\"),\n\t};\n}\n\nasync function fetchCollections(\n\topts: CliOptions,\n): Promise<CollectionsResponse> {\n\tconst base = opts.url.replace(/\\/+$/, \"\");\n\n\tlet authKey: string;\n\tif (opts.apiKey) {\n\t\t// Step 1: use a stored API key directly (no login round-trip).\n\t\t// The key is sent as `Authorization: Bearer <key>` and recognised\n\t\t// by the backend's Auth.Plug as an API key.\n\t\tauthKey = opts.apiKey;\n\t} else {\n\t\t// Step 1: login as superuser to get a token.\n\t\t// Prefer the PocketBase-parity `_superusers` auth collection endpoint,\n\t\t// fall back to the legacy /superusers/login for older servers.\n\t\tlet loginRes = await fetch(base + \"/_superusers/auth-with-password\", {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\tbody: JSON.stringify({ identity: opts.email, password: opts.password }),\n\t\t});\n\t\tif (!loginRes.ok) {\n\t\t\tloginRes = await fetch(base + \"/superusers/login\", {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t\tbody: JSON.stringify({ email: opts.email, password: opts.password }),\n\t\t\t});\n\t\t}\n\t\tif (!loginRes.ok) {\n\t\t\tconst text = await loginRes.text();\n\t\t\tfail(\n\t\t\t\t`Superuser login failed (${loginRes.status}). ${text.slice(0, 200)}`,\n\t\t\t);\n\t\t}\n\t\tconst loginData = (await loginRes.json()) as { token?: string };\n\t\tif (!loginData.token) fail(\"Login response did not include a token.\");\n\t\tauthKey = loginData.token;\n\t}\n\n\t// Step 2: fetch collections\n\tconst collRes = await fetch(base + \"/collections\", {\n\t\theaders: { Authorization: \"Bearer \" + authKey },\n\t});\n\tif (!collRes.ok) {\n\t\tconst text = await collRes.text();\n\t\tfail(\n\t\t\t`Failed to fetch collections (${collRes.status}). ${text.slice(0, 200)}`,\n\t\t);\n\t}\n\treturn (await collRes.json()) as CollectionsResponse;\n}\n\nasync function main(): Promise<void> {\n\tconst opts = parseArgs(process.argv.slice(2));\n\tconst authLabel = opts.apiKey\n\t\t? `API key ${opts.apiKey.slice(0, 4)}…${opts.apiKey.slice(-4)}`\n\t\t: opts.email;\n\tconsole.log(`\\n🔌 Connecting to ${opts.url} as ${authLabel} …`);\n\n\tconst { items } = await fetchCollections(opts);\n\tconsole.log(`📦 Found ${items.length} collection(s).`);\n\n\tconst source = generateTypes(items, {\n\t\tpackageName: opts.packageName,\n\t\tskipSystem: opts.skipSystem,\n\t});\n\n\tconst outPath = resolve(process.cwd(), opts.out);\n\tawait writeFile(outPath, source, \"utf8\");\n\tconsole.log(`✅ Wrote ${outPath} (${source.length} bytes).`);\n\tconsole.log(\n\t\t`\\nImport it in your app:\\n import { createClient } from './${opts.out.replace(/\\.ts$/, \"\")}';\\n`,\n\t);\n}\n\nmain().catch((err) => {\n\tconsole.error(err);\n\tprocess.exit(1);\n});\n","// ── Type mapping (runtime + codegen) ────────────────────\n// Maps server field types to TypeScript types.\n// Shared by the runtime `SchemaTypes` helper and the codegen CLI.\n\nimport type { SchemaField } from \"./schema\";\n\n/**\n * Map a single server field to its TypeScript type string.\n * Used by the codegen CLI to emit interface members.\n *\n * @param field The field definition.\n * @param fallback Fallback type for unknown field types (default `unknown`).\n */\nexport function fieldTypeScriptType(\n\tfield: SchemaField,\n\tfallback = \"unknown\",\n): string {\n\tconst opts = field.options ?? {};\n\tswitch (field.type) {\n\t\tcase \"text\":\n\t\tcase \"email\":\n\t\tcase \"url\":\n\t\tcase \"editor\":\n\t\tcase \"date\":\n\t\tcase \"datetime\":\n\t\t\treturn \"string\";\n\t\tcase \"number\":\n\t\t\treturn \"number\";\n\t\tcase \"bool\":\n\t\t\treturn \"boolean\";\n\t\tcase \"select\": {\n\t\t\tconst values = Array.isArray(opts.values) ? opts.values : [];\n\t\t\tif (values.length > 0) {\n\t\t\t\treturn values.map((v) => JSON.stringify(String(v))).join(\" | \");\n\t\t\t}\n\t\t\treturn \"string\";\n\t\t}\n\t\tcase \"multi_select\": {\n\t\t\tconst values = Array.isArray(opts.values) ? opts.values : [];\n\t\t\tif (values.length > 0) {\n\t\t\t\treturn `(${values.map((v) => JSON.stringify(String(v))).join(\" | \")})[]`;\n\t\t\t}\n\t\t\treturn \"string[]\";\n\t\t}\n\t\tcase \"file\":\n\t\t\treturn \"string\";\n\t\tcase \"multi_file\":\n\t\t\treturn \"string[]\";\n\t\tcase \"json\":\n\t\tcase \"geo\":\n\t\t\treturn \"Record<string, unknown>\";\n\t\tcase \"relation\":\n\t\t\t// Relations store the target record's ID (string) — or an array\n\t\t\t// of IDs when multi-relation (maxSelect > 1).\n\t\t\treturn (opts.maxSelect ?? 1) > 1 ? \"string[]\" : \"string\";\n\t\tcase \"password\":\n\t\t\t// Passwords are write-only; never expose on read models.\n\t\t\treturn \"never\";\n\t\tdefault:\n\t\t\treturn fallback;\n\t}\n}\n\n/**\n * Returns the runtime type kind for a field — used by {@link schemaFieldType}\n * to build structural types at runtime.\n */\nexport type FieldTypeKind =\n\t| \"string\"\n\t| \"number\"\n\t| \"boolean\"\n\t| \"string-array\"\n\t| \"json\"\n\t| \"relation\"\n\t| \"relation-many\"\n\t| \"password\"\n\t| \"unknown\";\n\n/** Map a server field to its runtime type kind. */\nexport function fieldTypeKind(field: SchemaField): FieldTypeKind {\n\tconst opts = field.options ?? {};\n\tswitch (field.type) {\n\t\tcase \"text\":\n\t\tcase \"email\":\n\t\tcase \"url\":\n\t\tcase \"editor\":\n\t\tcase \"date\":\n\t\tcase \"datetime\":\n\t\tcase \"select\":\n\t\tcase \"file\":\n\t\t\treturn \"string\";\n\t\tcase \"number\":\n\t\t\treturn \"number\";\n\t\tcase \"bool\":\n\t\t\treturn \"boolean\";\n\t\tcase \"multi_select\":\n\t\tcase \"multi_file\":\n\t\t\treturn \"string-array\";\n\t\tcase \"json\":\n\t\tcase \"geo\":\n\t\t\treturn \"json\";\n\t\tcase \"relation\":\n\t\t\treturn (opts.maxSelect ?? 1) > 1 ? \"relation-many\" : \"relation\";\n\t\tcase \"password\":\n\t\t\treturn \"password\";\n\t\tdefault:\n\t\t\treturn \"unknown\";\n\t}\n}\n\n/**\n * Derive a TypeScript field type from a {@link SchemaField} — the runtime\n * counterpart to the codegen mapper. Lets consumers build typed clients\n * from a fetched schema without running the CLI.\n */\nexport function schemaFieldType(field: SchemaField): unknown {\n\tswitch (fieldTypeKind(field)) {\n\t\tcase \"string\":\n\t\t\treturn String;\n\t\tcase \"number\":\n\t\t\treturn Number;\n\t\tcase \"boolean\":\n\t\t\treturn Boolean;\n\t\tcase \"string-array\":\n\t\t\treturn [String] as const;\n\t\tcase \"relation\":\n\t\t\treturn String;\n\t\tcase \"relation-many\":\n\t\t\treturn [String] as const;\n\t\tcase \"json\":\n\t\t\treturn Object;\n\t\tcase \"password\":\n\t\t\treturn undefined;\n\t\tcase \"unknown\":\n\t\t\treturn undefined;\n\t}\n}\n","// ── Codegen ─────────────────────────────────────────────\n// Generates a `lazypock.types.ts` module from the live API schema.\n//\n// The generated file exports:\n// - One interface per collection (e.g. `PostsRecord`)\n// - A `LazypockCollections` map: { posts: PostsRecord; users: UsersRecord; ... }\n// - A `createClient()` factory pre-bound to those types, so\n// `client.collection(\"posts\").create({ title: \"x\" })` is fully type-checked.\n//\n// The CLI in `src/cli.ts` wires this to `GET /collections`.\n\nimport type { CollectionSchema, SchemaField } from \"./schema\";\nimport { fieldTypeScriptType } from \"./typegen\";\n\n/** Format a raw collection name into a valid TS identifier (PascalCase). */\nexport function collectionTypeName(name: string): string {\n\treturn name\n\t\t.split(/[^a-zA-Z0-9]+/)\n\t\t.filter(Boolean)\n\t\t.map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n\t\t.join(\"\")\n\t\t.replace(/^[0-9]/, \"_$&\");\n}\n\n/** Interface member name — sanitize hyphens/spaces but keep readability. */\nexport function fieldKey(name: string): string {\n\treturn name.replace(/[^a-zA-Z0-9_]/g, \"_\");\n}\n\n\n\n/**\n * Generate the full TypeScript source for the typed SDK module.\n *\n * @param collections Collections fetched from the API.\n * @param options Generation options.\n */\nexport function generateTypes(\n\tcollections: CollectionSchema[],\n\toptions: {\n\t\t/** Import specifier for the lazypock package (default `lazypock`). */\n\t\tpackageName?: string;\n\t\t/** Emit base record fields (id, created, updated, …). Default true. */\n\t\tincludeBaseFields?: boolean;\n\t\t/** Skip system collections (names starting with `_` or `users`). Default false. */\n\t\tskipSystem?: boolean;\n\t} = {},\n): string {\n\tconst {\n\t\tpackageName = \"lazypock\",\n\t\tincludeBaseFields = true,\n\t\tskipSystem = false,\n\t} = options;\n\n\tconst filtered = skipSystem\n\t\t? collections.filter(\n\t\t\t\t(c) => !c.system && !c.name.startsWith(\"_\") && c.name !== \"users\",\n\t\t\t)\n\t\t: collections;\n\n\tconst sections: string[] = [];\n\tsections.push(`// ── Auto-generated by lazypock-ts ──────────────────────────\n// Do not edit by hand. Regenerate with: npx lazypock-gen\n// Schema snapshot: ${new Date().toISOString()}`);\n\n\tif (includeBaseFields) {\n\t\tsections.push(`export interface BaseRecord {\n id: string;\n collectionId: string;\n collectionName: string;\n created: string;\n updated: string;\n}`);\n\t}\n\n\t// One interface per collection\n\tfor (const coll of filtered) {\n\t\tconst typeName = collectionTypeName(coll.name);\n\t\tconst fields = coll.fields ?? [];\n\t\tconst lines = fields.map((f) => memberLine(f)).filter((l) => l !== \"\");\n\t\tconst body = lines.join(\"\\n\");\n\t\tsections.push(\n\t\t\t`export interface ${typeName}Record${renderInterface({\n\t\t\t\textends: includeBaseFields ? \"BaseRecord\" : undefined,\n\t\t\t\tbody,\n\t\t\t})}`,\n\t\t);\n\t}\n\n\t// Auth collection type\n\tsections.push(`export interface AuthRecord extends BaseRecord {\n email: string;\n verified: boolean;\n}`);\n\n\t// Collections map\n\tconst mapEntries = filtered\n\t\t.map(\n\t\t\t(c) =>\n\t\t\t\t` \"${c.name}\": ${collectionTypeName(c.name)}Record${\n\t\t\t\t\tc.type === \"auth\" ? \" & AuthRecord\" : \"\"\n\t\t\t\t};`,\n\t\t)\n\t\t.join(\"\\n\");\n\tsections.push(`export interface LazypockCollections {\n${mapEntries}\n}`);\n\n\t// Runtime schema snapshot — lets the client exclude hidden fields by\n\t// default, validate select/expand, and derive visible field lists.\n\tconst schemaEntries = collections\n\t\t.map(\n\t\t\t(c) =>\n\t\t\t\t` {\n id: ${JSON.stringify(c.id ?? null)},\n name: ${JSON.stringify(c.name)},\n type: ${JSON.stringify(c.type)},\n system: ${JSON.stringify(c.system ?? false)},\n fields: ${JSON.stringify(c.fields ?? [], null, 2)},\n rules: ${JSON.stringify(c.rules ?? {})},\n options: ${JSON.stringify(c.options ?? {})},\n }`,\n\t\t)\n\t\t.join(\",\\n\");\n\tsections.push(`// Schema snapshot (runtime) — hidden-field exclusion + query validation.\nexport const lazypockSchema: import(\"${packageName}\").CollectionSchema[] = [\n${schemaEntries}\n];`);\n\n\t// createClient factory\n\tsections.push(`import { LazypockClient, type LazypockClientOptions, type CollectionService } from \"${packageName}\";\n\n/**\n * Create a Lazypock client typed against this schema snapshot.\n * Collection access is fully type-checked:\n * client.collection(\"posts\").create({ title: \"x\" }) // title must exist\n *\n * The schema snapshot is wired into the client automatically, so hidden\n * fields are excluded from responses and select/expand are validated.\n */\nexport function createClient(options: LazypockClientOptions): TypedClient {\n return new TypedClient({\n ...options,\n types: {\n ...options.types,\n schemas: options.types?.schemas ?? lazypockSchema,\n },\n });\n}\n\nexport class TypedClient extends LazypockClient {\n // K extends keyof LazypockCollections (rather than a generic string with a\n // conditional return) so the IDE suggests collection names and unknown names\n // are rejected at compile time:\n // client.collection(\"posts\") // suggested + typed\n // client.collection(\"nope\") // TS error\n override collection<K extends keyof LazypockCollections>(name: K): CollectionService<LazypockCollections[K]> {\n return super.collection(name) as CollectionService<LazypockCollections[K]>;\n }\n}\n`);\n\n\treturn sections.join(\"\\n\\n\") + \"\\n\";\n}\n\n/**\n * Render the interface body including the extends clause.\n * Empty body → ` extends BaseRecord {}` (valid TS).\n */\nfunction renderInterface(opts: { extends?: string; body: string }): string {\n\tconst ext = opts.extends ? ` extends ${opts.extends}` : \"\";\n\tif (!opts.body) return `${ext} {}`;\n\treturn `${ext} {\\n${opts.body}\\n}`;\n}\n\n/** Render a single interface member line for a field. */\nfunction memberLine(f: SchemaField): string {\n\t// Hidden fields are excluded from API responses by the schema-aware\n\t// client default — don't expose them on the read model either.\n\tif (f.hidden) return \"\";\n\tconst key = fieldKey(f.name);\n\tconst req = f.required || f.type === \"password\" ? \"\" : \"?\";\n\tconst type = fieldTypeScriptType(f);\n\t// `never` members (passwords) are omitted from read models.\n\tif (type === \"never\") return \"\";\n\treturn ` ${JSON.stringify(key)}${req}: ${type};`;\n}\n"],"mappings":"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","names":[]}
|
package/dist/cli.js
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -519,9 +519,10 @@ var CollectionService = class _CollectionService {
|
|
|
519
519
|
* const t = await client.collection("posts").select("id", "title").getList();
|
|
520
520
|
* // GET /api/posts?fields=id,title
|
|
521
521
|
*
|
|
522
|
-
* // All visible fields (
|
|
522
|
+
* // All visible fields (implicit system keys id/created/updated/… are
|
|
523
|
+
* // kept; hidden fields are excluded via the projection)
|
|
523
524
|
* const all = await client.collection("posts").select("*").getList();
|
|
524
|
-
* // GET /api/posts?fields=id,title,published,… (schema known)
|
|
525
|
+
* // GET /api/posts?fields=id,created,updated,collectionId,collectionName,title,published,… (schema known)
|
|
525
526
|
* ```
|
|
526
527
|
*
|
|
527
528
|
* When no schema is known, the default (no `select()` call) sends no
|
|
@@ -554,7 +555,8 @@ var CollectionService = class _CollectionService {
|
|
|
554
555
|
* Fields to send with reads:
|
|
555
556
|
* 1. explicit `options.fields` (caller wins)
|
|
556
557
|
* 2. `select()` preset
|
|
557
|
-
* 3. schema default — all visible (non-hidden
|
|
558
|
+
* 3. schema default — all visible (non-hidden) fields plus the implicit
|
|
559
|
+
* system keys (`id`, `created`, `updated`, `collectionId`, `collectionName`)
|
|
558
560
|
*/
|
|
559
561
|
effectiveFields(optionsFields) {
|
|
560
562
|
if (optionsFields !== void 0) return optionsFields;
|
|
@@ -567,15 +569,35 @@ var CollectionService = class _CollectionService {
|
|
|
567
569
|
return this.visibleFields();
|
|
568
570
|
}
|
|
569
571
|
/**
|
|
570
|
-
* All non-hidden, non-
|
|
571
|
-
*
|
|
572
|
+
* All non-hidden, non-password, non-autodate field names from the schema,
|
|
573
|
+
* prefixed with the implicit system keys that are part of every record
|
|
574
|
+
* (`id`, `created`, `updated`, `collectionId`, `collectionName`).
|
|
575
|
+
*
|
|
576
|
+
* The system keys never appear in the schema (they are implied for every
|
|
577
|
+
* collection) but MUST be listed explicitly when projecting — the server
|
|
578
|
+
* applies strict projection, so unrequested fields are dropped. Without
|
|
579
|
+
* them, `getList`/`getOne` responses lose `id` (and `created`/`updated`),
|
|
580
|
+
* breaking selection, edits, and relation references.
|
|
581
|
+
*
|
|
582
|
+
* `undefined` when no schema fields are available (server decides — its
|
|
583
|
+
* default returns every non-password field, including the system keys).
|
|
572
584
|
*/
|
|
573
585
|
visibleFields() {
|
|
574
586
|
if (!this.schema?.fields) return void 0;
|
|
575
587
|
const visible = this.schema.fields.filter(
|
|
576
588
|
(f) => !f.hidden && f.type !== "password" && f.type !== "autodate"
|
|
577
589
|
).map((f) => f.name);
|
|
578
|
-
|
|
590
|
+
if (visible.length === 0) return void 0;
|
|
591
|
+
return [
|
|
592
|
+
.../* @__PURE__ */ new Set([
|
|
593
|
+
"id",
|
|
594
|
+
"created",
|
|
595
|
+
"updated",
|
|
596
|
+
"collectionId",
|
|
597
|
+
"collectionName",
|
|
598
|
+
...visible
|
|
599
|
+
])
|
|
600
|
+
].join(",");
|
|
579
601
|
}
|
|
580
602
|
/** Warn once when an expand field is not a relation (schema known). */
|
|
581
603
|
validateExpand(expand) {
|
|
@@ -1508,10 +1530,13 @@ export function createClient(options: LazypockClientOptions): TypedClient {
|
|
|
1508
1530
|
}
|
|
1509
1531
|
|
|
1510
1532
|
export class TypedClient extends LazypockClient {
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1533
|
+
// K extends keyof LazypockCollections (rather than a generic string with a
|
|
1534
|
+
// conditional return) so the IDE suggests collection names and unknown names
|
|
1535
|
+
// are rejected at compile time:
|
|
1536
|
+
// client.collection("posts") // suggested + typed
|
|
1537
|
+
// client.collection("nope") // TS error
|
|
1538
|
+
override collection<K extends keyof LazypockCollections>(name: K): CollectionService<LazypockCollections[K]> {
|
|
1539
|
+
return super.collection(name) as CollectionService<LazypockCollections[K]>;
|
|
1515
1540
|
}
|
|
1516
1541
|
}
|
|
1517
1542
|
`);
|
|
@@ -1543,6 +1568,7 @@ var LazypockClient = class {
|
|
|
1543
1568
|
this.collectionCache = /* @__PURE__ */ new Map();
|
|
1544
1569
|
const baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
1545
1570
|
this.authStore = options.authStore ?? new AuthStore(options.storage ?? memoryStorage);
|
|
1571
|
+
this.authReady = options.authStore ? Promise.resolve() : this.authStore.init();
|
|
1546
1572
|
this.http = new HttpClient(baseUrl, this.authStore);
|
|
1547
1573
|
this.realtime = options.realtime ?? new RealtimeService();
|
|
1548
1574
|
if (!options.realtime) {
|