lazypock 0.10.0 → 0.10.1
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/README.md +8 -2
- package/dist/{chunk-JYKRCEC3.js → chunk-LVEOMG6A.js} +36 -5
- package/dist/chunk-LVEOMG6A.js.map +1 -0
- package/dist/cli.cjs +35 -4
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.global.js +35 -4
- package/dist/cli.global.js.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/index.cjs +35 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -7
- package/dist/index.d.ts +13 -7
- package/dist/index.global.js +35 -4
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/codegen.ts +50 -4
- package/src/collection.ts +46 -14
- package/dist/chunk-JYKRCEC3.js.map +0 -1
package/README.md
CHANGED
|
@@ -253,6 +253,13 @@ await postsSvc.getOne('abc', { expand: 'author' });
|
|
|
253
253
|
- `expand` — comma-separated relation field names, including nested dot-paths
|
|
254
254
|
(`author.user`); non-relation fields warn at runtime when a schema is
|
|
255
255
|
available.
|
|
256
|
+
|
|
257
|
+
**Hidden fields are queryable.** A hidden relation is excluded from the read
|
|
258
|
+
model (no `record.user`) but the server still resolves it for
|
|
259
|
+
`filter`/`sort`/`expand`/`select` — the generated `*QueryFields` type keeps
|
|
260
|
+
those keys accepted, so `getFullList({ expand: "user" })` typechecks for a
|
|
261
|
+
hidden relation.
|
|
262
|
+
|
|
256
263
|
- The **untyped** client (`client.collection('posts')` without `typed<T>()`)
|
|
257
264
|
still accepts any string — suggestions kick in once the service is typed.
|
|
258
265
|
|
|
@@ -623,7 +630,6 @@ const off = client.realtime.subscribe('chat:room1', (event) => {
|
|
|
623
630
|
off(); // or client.realtime.unsubscribe('chat:room1');
|
|
624
631
|
```
|
|
625
632
|
|
|
626
|
-
|
|
627
633
|
## Releasing (automatic)
|
|
628
634
|
|
|
629
635
|
Releases are fully automatic via [release-please](https://github.com/googleapis/release-please):
|
|
@@ -648,7 +654,7 @@ There is no manual `workflow_dispatch` step and no local `npm publish`.
|
|
|
648
654
|
### One-time setup
|
|
649
655
|
|
|
650
656
|
- Add an npm access token (Automation or Publish scope, from
|
|
651
|
-
|
|
657
|
+
<<https://www.npmjs.com/settings/><you>/tokens>) as the repo secret
|
|
652
658
|
**`NPM_TOKEN`** under Settings → Secrets and variables → Actions.
|
|
653
659
|
|
|
654
660
|
## License
|
|
@@ -149,6 +149,20 @@ function generateTypes(collections, options = {}) {
|
|
|
149
149
|
body: createLines.join("\n")
|
|
150
150
|
})}`
|
|
151
151
|
);
|
|
152
|
+
const queryLines = [];
|
|
153
|
+
for (const f of fields) {
|
|
154
|
+
if (f.type === "password") continue;
|
|
155
|
+
const key = fieldKey(f.name);
|
|
156
|
+
const type = fieldTypeScriptType(f);
|
|
157
|
+
if (type === "never") continue;
|
|
158
|
+
queryLines.push(` ${JSON.stringify(key)}?: ${type};`);
|
|
159
|
+
}
|
|
160
|
+
sections.push(
|
|
161
|
+
`export interface ${typeName}QueryFields${renderInterface({
|
|
162
|
+
extends: includeBaseFields ? "BaseRecord" : void 0,
|
|
163
|
+
body: queryLines.join("\n")
|
|
164
|
+
})}`
|
|
165
|
+
);
|
|
152
166
|
}
|
|
153
167
|
sections.push(`export interface AuthRecord extends BaseRecord {
|
|
154
168
|
email: string;
|
|
@@ -163,6 +177,12 @@ ${mapEntries}
|
|
|
163
177
|
const createMapEntries = filtered.map((c) => ` "${c.name}": ${collectionTypeName(c.name)}CreateData;`).join("\n");
|
|
164
178
|
sections.push(`export interface LazypockCreateData {
|
|
165
179
|
${createMapEntries}
|
|
180
|
+
}`);
|
|
181
|
+
const queryMapEntries = filtered.map(
|
|
182
|
+
(c) => ` "${c.name}": ${collectionTypeName(c.name)}QueryFields${c.type === "auth" ? " & AuthRecord" : ""};`
|
|
183
|
+
).join("\n");
|
|
184
|
+
sections.push(`export interface LazypockQueryFields {
|
|
185
|
+
${queryMapEntries}
|
|
166
186
|
}`);
|
|
167
187
|
const schemaEntries = collections.map(
|
|
168
188
|
(c) => ` {
|
|
@@ -216,15 +236,26 @@ export class TypedClient extends LazypockClient {
|
|
|
216
236
|
// The (string & {}) intersection keeps the union from collapsing to plain
|
|
217
237
|
// string (which would silently kill the suggestions).
|
|
218
238
|
//
|
|
219
|
-
//
|
|
220
|
-
//
|
|
239
|
+
// The service binds three types: the read model, the write-only *CreateData,
|
|
240
|
+
// and the *QueryFields (filter/sort/expand/select keys). QueryFields is what
|
|
241
|
+
// lets hidden relation fields \u2014 excluded from the read model but still
|
|
242
|
+
// expandable/filterable at runtime \u2014 typecheck:
|
|
243
|
+
// client.collection("project_members").getFullList({ expand: "user" }) // \u2713
|
|
221
244
|
override collection<K extends keyof LazypockCollections | (string & {})>(
|
|
222
245
|
name: K,
|
|
223
246
|
): K extends keyof LazypockCollections
|
|
224
|
-
? CollectionService<
|
|
247
|
+
? CollectionService<
|
|
248
|
+
LazypockCollections[K],
|
|
249
|
+
LazypockCreateData[K],
|
|
250
|
+
LazypockQueryFields[K]
|
|
251
|
+
>
|
|
225
252
|
: CollectionService<unknown> {
|
|
226
253
|
return super.collection(name) as K extends keyof LazypockCollections
|
|
227
|
-
? CollectionService<
|
|
254
|
+
? CollectionService<
|
|
255
|
+
LazypockCollections[K],
|
|
256
|
+
LazypockCreateData[K],
|
|
257
|
+
LazypockQueryFields[K]
|
|
258
|
+
>
|
|
228
259
|
: CollectionService<unknown>;
|
|
229
260
|
}
|
|
230
261
|
}
|
|
@@ -272,4 +303,4 @@ export {
|
|
|
272
303
|
collectionTypeName,
|
|
273
304
|
generateTypes
|
|
274
305
|
};
|
|
275
|
-
//# sourceMappingURL=chunk-
|
|
306
|
+
//# sourceMappingURL=chunk-LVEOMG6A.js.map
|
|
@@ -0,0 +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\n\t\t// Write-only create data: what `create()`/`update()` accept. This\n\t\t// intentionally includes password fields (and hidden fields) that are\n\t\t// excluded from the read model — the server accepts them on write,\n\t\t// hashes passwords, and never returns them in responses.\n\t\tconst createLines: string[] = [];\n\t\tconst seenKeys = new Set<string>();\n\t\tfor (const f of fields) {\n\t\t\tfor (const { key, line } of createDataMemberLines(f)) {\n\t\t\t\tif (seenKeys.has(key)) continue;\n\t\t\t\tseenKeys.add(key);\n\t\t\t\tcreateLines.push(line);\n\t\t\t}\n\t\t}\n\t\tsections.push(\n\t\t\t`export interface ${typeName}CreateData${renderInterface({\n\t\t\t\tbody: createLines.join(\"\\n\"),\n\t\t\t})}`,\n\t\t);\n\n\t\t// Query fields — the key set for filter/sort/expand/select. This\n\t\t// intentionally includes hidden fields: a hidden relation is still\n\t\t// expandable/filterable at runtime (the server resolves it) even\n\t\t// though it is excluded from the read model. Only password fields\n\t\t// are never queryable.\n\t\tconst queryLines: string[] = [];\n\t\tfor (const f of fields) {\n\t\t\tif (f.type === \"password\") continue;\n\t\t\tconst key = fieldKey(f.name);\n\t\t\tconst type = fieldTypeScriptType(f);\n\t\t\tif (type === \"never\") continue;\n\t\t\tqueryLines.push(` ${JSON.stringify(key)}?: ${type};`);\n\t\t}\n\t\tsections.push(\n\t\t\t`export interface ${typeName}QueryFields${renderInterface({\n\t\t\t\textends: includeBaseFields ? \"BaseRecord\" : undefined,\n\t\t\t\tbody: queryLines.join(\"\\n\"),\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// Create-data map — typed `create()`/`update()` payloads, including\n\t// write-only password fields.\n\tconst createMapEntries = filtered\n\t\t.map((c) => ` \"${c.name}\": ${collectionTypeName(c.name)}CreateData;`)\n\t\t.join(\"\\n\");\n\tsections.push(`export interface LazypockCreateData {\n${createMapEntries}\n}`);\n\n\t// Query-fields map — filter/sort/expand/select keys per collection,\n\t// including hidden fields (valid at runtime despite being excluded from\n\t// the read model). Auth collections get the auth system keys too.\n\tconst queryMapEntries = filtered\n\t\t.map(\n\t\t\t(c) =>\n\t\t\t\t` \"${c.name}\": ${collectionTypeName(c.name)}QueryFields${\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 LazypockQueryFields {\n${queryMapEntries}\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 * For auth collections the generated *CreateData type includes the\n * write-only password field, so creating a user is type-safe:\n * client.collection(\"users\").create({ email, password }) // ✓\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 // The constraint is \"keyof LazypockCollections | (string & {})\" rather than a\n // bare generic string or a strict keyof:\n // - the keyof LazypockCollections member is what makes the IDE suggest\n // collection names, and known literals resolve to the typed service:\n // client.collection(\"posts\") // suggested + typed\n // - the string & {} member keeps unknown/dynamic names compiling — they\n // resolve to the untyped service, so a studio that manages\n // user-created collections can still call collection(someString).\n //\n // The (string & {}) intersection keeps the union from collapsing to plain\n // string (which would silently kill the suggestions).\n //\n // The service binds three types: the read model, the write-only *CreateData,\n // and the *QueryFields (filter/sort/expand/select keys). QueryFields is what\n // lets hidden relation fields — excluded from the read model but still\n // expandable/filterable at runtime — typecheck:\n // client.collection(\"project_members\").getFullList({ expand: \"user\" }) // ✓\n override collection<K extends keyof LazypockCollections | (string & {})>(\n name: K,\n ): K extends keyof LazypockCollections\n ? CollectionService<\n LazypockCollections[K],\n LazypockCreateData[K],\n LazypockQueryFields[K]\n >\n : CollectionService<unknown> {\n return super.collection(name) as K extends keyof LazypockCollections\n ? CollectionService<\n LazypockCollections[K],\n LazypockCreateData[K],\n LazypockQueryFields[K]\n >\n : 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\n/**\n * Render the write-only create-data members for a field.\n *\n * Unlike the read model, create/update data includes password fields (sent as\n * plain strings — the server hashes them) and hidden fields: the server\n * accepts them on write and never returns them in responses.\n *\n * Password fields are exposed under the canonical PocketBase API key\n * `password` (the server accepts `password` for any single password field and\n * hashes it into the backing column, e.g. `password_hash`), with the raw\n * metadata name kept as a backward-compat alias.\n *\n * A field is only marked required when it actually needs a client value:\n * fields with a server-side default (`options.defaultValue`) and the auth\n * system fields `verified` / `emailVisibility` (the server defaults them to\n * false/true even though the snapshot doesn't carry `defaultValue`) stay\n * optional, so `create({ email, password })` typechecks without forcing\n * callers to send values the server would fill in anyway. Password fields\n * are optional (accounts may exist without a password, e.g. OAuth-only).\n */\nfunction createDataMemberLines(\n\tf: SchemaField,\n): { key: string; line: string }[] {\n\t// Autodate columns are server-managed; never writable.\n\tif (f.type === \"autodate\") return [];\n\tconst rawName = fieldKey(f.name);\n\tconst serverDefaulted =\n\t\tf.options?.defaultValue !== undefined ||\n\t\t(f.system && (f.name === \"verified\" || f.name === \"emailVisibility\"));\n\tconst req = f.required && !serverDefaulted ? \"\" : \"?\";\n\tconst type = f.type === \"password\" ? \"string\" : fieldTypeScriptType(f);\n\tif (type === \"never\") return [];\n\n\tconst members = [\n\t\t{ key: rawName, line: ` ${JSON.stringify(rawName)}${req}: ${type};` },\n\t];\n\t// Write-only password: canonical key is `password` (PocketBase parity) —\n\t// the server accepts `password` for any single password field and hashes\n\t// it. The raw metadata name (e.g. `password_hash`) stays as a compat alias.\n\tif (f.type === \"password\" && rawName !== \"password\") {\n\t\tmembers.unshift({\n\t\t\tkey: \"password\",\n\t\t\tline: ` ${JSON.stringify(\"password\")}${req}: ${type};`,\n\t\t});\n\t}\n\treturn members;\n}\n"],"mappings":"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","names":[]}
|
package/dist/cli.cjs
CHANGED
|
@@ -104,6 +104,20 @@ function generateTypes(collections, options = {}) {
|
|
|
104
104
|
body: createLines.join("\n")
|
|
105
105
|
})}`
|
|
106
106
|
);
|
|
107
|
+
const queryLines = [];
|
|
108
|
+
for (const f of fields) {
|
|
109
|
+
if (f.type === "password") continue;
|
|
110
|
+
const key = fieldKey(f.name);
|
|
111
|
+
const type = fieldTypeScriptType(f);
|
|
112
|
+
if (type === "never") continue;
|
|
113
|
+
queryLines.push(` ${JSON.stringify(key)}?: ${type};`);
|
|
114
|
+
}
|
|
115
|
+
sections.push(
|
|
116
|
+
`export interface ${typeName}QueryFields${renderInterface({
|
|
117
|
+
extends: includeBaseFields ? "BaseRecord" : void 0,
|
|
118
|
+
body: queryLines.join("\n")
|
|
119
|
+
})}`
|
|
120
|
+
);
|
|
107
121
|
}
|
|
108
122
|
sections.push(`export interface AuthRecord extends BaseRecord {
|
|
109
123
|
email: string;
|
|
@@ -118,6 +132,12 @@ ${mapEntries}
|
|
|
118
132
|
const createMapEntries = filtered.map((c) => ` "${c.name}": ${collectionTypeName(c.name)}CreateData;`).join("\n");
|
|
119
133
|
sections.push(`export interface LazypockCreateData {
|
|
120
134
|
${createMapEntries}
|
|
135
|
+
}`);
|
|
136
|
+
const queryMapEntries = filtered.map(
|
|
137
|
+
(c) => ` "${c.name}": ${collectionTypeName(c.name)}QueryFields${c.type === "auth" ? " & AuthRecord" : ""};`
|
|
138
|
+
).join("\n");
|
|
139
|
+
sections.push(`export interface LazypockQueryFields {
|
|
140
|
+
${queryMapEntries}
|
|
121
141
|
}`);
|
|
122
142
|
const schemaEntries = collections.map(
|
|
123
143
|
(c) => ` {
|
|
@@ -171,15 +191,26 @@ export class TypedClient extends LazypockClient {
|
|
|
171
191
|
// The (string & {}) intersection keeps the union from collapsing to plain
|
|
172
192
|
// string (which would silently kill the suggestions).
|
|
173
193
|
//
|
|
174
|
-
//
|
|
175
|
-
//
|
|
194
|
+
// The service binds three types: the read model, the write-only *CreateData,
|
|
195
|
+
// and the *QueryFields (filter/sort/expand/select keys). QueryFields is what
|
|
196
|
+
// lets hidden relation fields \u2014 excluded from the read model but still
|
|
197
|
+
// expandable/filterable at runtime \u2014 typecheck:
|
|
198
|
+
// client.collection("project_members").getFullList({ expand: "user" }) // \u2713
|
|
176
199
|
override collection<K extends keyof LazypockCollections | (string & {})>(
|
|
177
200
|
name: K,
|
|
178
201
|
): K extends keyof LazypockCollections
|
|
179
|
-
? CollectionService<
|
|
202
|
+
? CollectionService<
|
|
203
|
+
LazypockCollections[K],
|
|
204
|
+
LazypockCreateData[K],
|
|
205
|
+
LazypockQueryFields[K]
|
|
206
|
+
>
|
|
180
207
|
: CollectionService<unknown> {
|
|
181
208
|
return super.collection(name) as K extends keyof LazypockCollections
|
|
182
|
-
? CollectionService<
|
|
209
|
+
? CollectionService<
|
|
210
|
+
LazypockCollections[K],
|
|
211
|
+
LazypockCreateData[K],
|
|
212
|
+
LazypockQueryFields[K]
|
|
213
|
+
>
|
|
183
214
|
: CollectionService<unknown>;
|
|
184
215
|
}
|
|
185
216
|
}
|
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\n\t\t// Write-only create data: what `create()`/`update()` accept. This\n\t\t// intentionally includes password fields (and hidden fields) that are\n\t\t// excluded from the read model — the server accepts them on write,\n\t\t// hashes passwords, and never returns them in responses.\n\t\tconst createLines: string[] = [];\n\t\tconst seenKeys = new Set<string>();\n\t\tfor (const f of fields) {\n\t\t\tfor (const { key, line } of createDataMemberLines(f)) {\n\t\t\t\tif (seenKeys.has(key)) continue;\n\t\t\t\tseenKeys.add(key);\n\t\t\t\tcreateLines.push(line);\n\t\t\t}\n\t\t}\n\t\tsections.push(\n\t\t\t`export interface ${typeName}CreateData${renderInterface({\n\t\t\t\tbody: createLines.join(\"\\n\"),\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// Create-data map — typed `create()`/`update()` payloads, including\n\t// write-only password fields.\n\tconst createMapEntries = filtered\n\t\t.map((c) => ` \"${c.name}\": ${collectionTypeName(c.name)}CreateData;`)\n\t\t.join(\"\\n\");\n\tsections.push(`export interface LazypockCreateData {\n${createMapEntries}\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 * For auth collections the generated *CreateData type includes the\n * write-only password field, so creating a user is type-safe:\n * client.collection(\"users\").create({ email, password }) // ✓\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 // The constraint is \"keyof LazypockCollections | (string & {})\" rather than a\n // bare generic string or a strict keyof:\n // - the keyof LazypockCollections member is what makes the IDE suggest\n // collection names, and known literals resolve to the typed service:\n // client.collection(\"posts\") // suggested + typed\n // - the string & {} member keeps unknown/dynamic names compiling — they\n // resolve to the untyped service, so a studio that manages\n // user-created collections can still call collection(someString).\n //\n // The (string & {}) intersection keeps the union from collapsing to plain\n // string (which would silently kill the suggestions).\n //\n // create()/update() take the collection's *CreateData — the read model\n // omits password/hidden fields, but the write model carries them.\n override collection<K extends keyof LazypockCollections | (string & {})>(\n name: K,\n ): K extends keyof LazypockCollections\n ? CollectionService<LazypockCollections[K], LazypockCreateData[K]>\n : CollectionService<unknown> {\n return super.collection(name) as K extends keyof LazypockCollections\n ? CollectionService<LazypockCollections[K], LazypockCreateData[K]>\n : 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\n/**\n * Render the write-only create-data members for a field.\n *\n * Unlike the read model, create/update data includes password fields (sent as\n * plain strings — the server hashes them) and hidden fields: the server\n * accepts them on write and never returns them in responses.\n *\n * Password fields are exposed under the canonical PocketBase API key\n * `password` (the server accepts `password` for any single password field and\n * hashes it into the backing column, e.g. `password_hash`), with the raw\n * metadata name kept as a backward-compat alias.\n *\n * A field is only marked required when it actually needs a client value:\n * fields with a server-side default (`options.defaultValue`) and the auth\n * system fields `verified` / `emailVisibility` (the server defaults them to\n * false/true even though the snapshot doesn't carry `defaultValue`) stay\n * optional, so `create({ email, password })` typechecks without forcing\n * callers to send values the server would fill in anyway. Password fields\n * are optional (accounts may exist without a password, e.g. OAuth-only).\n */\nfunction createDataMemberLines(\n\tf: SchemaField,\n): { key: string; line: string }[] {\n\t// Autodate columns are server-managed; never writable.\n\tif (f.type === \"autodate\") return [];\n\tconst rawName = fieldKey(f.name);\n\tconst serverDefaulted =\n\t\tf.options?.defaultValue !== undefined ||\n\t\t(f.system && (f.name === \"verified\" || f.name === \"emailVisibility\"));\n\tconst req = f.required && !serverDefaulted ? \"\" : \"?\";\n\tconst type = f.type === \"password\" ? \"string\" : fieldTypeScriptType(f);\n\tif (type === \"never\") return [];\n\n\tconst members = [\n\t\t{ key: rawName, line: ` ${JSON.stringify(rawName)}${req}: ${type};` },\n\t];\n\t// Write-only password: canonical key is `password` (PocketBase parity) —\n\t// the server accepts `password` for any single password field and hashes\n\t// it. The raw metadata name (e.g. `password_hash`) stays as a compat alias.\n\tif (f.type === \"password\" && rawName !== \"password\") {\n\t\tmembers.unshift({\n\t\t\tkey: \"password\",\n\t\t\tline: ` ${JSON.stringify(\"password\")}${req}: ${type};`,\n\t\t});\n\t}\n\treturn members;\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\n\t\t// Write-only create data: what `create()`/`update()` accept. This\n\t\t// intentionally includes password fields (and hidden fields) that are\n\t\t// excluded from the read model — the server accepts them on write,\n\t\t// hashes passwords, and never returns them in responses.\n\t\tconst createLines: string[] = [];\n\t\tconst seenKeys = new Set<string>();\n\t\tfor (const f of fields) {\n\t\t\tfor (const { key, line } of createDataMemberLines(f)) {\n\t\t\t\tif (seenKeys.has(key)) continue;\n\t\t\t\tseenKeys.add(key);\n\t\t\t\tcreateLines.push(line);\n\t\t\t}\n\t\t}\n\t\tsections.push(\n\t\t\t`export interface ${typeName}CreateData${renderInterface({\n\t\t\t\tbody: createLines.join(\"\\n\"),\n\t\t\t})}`,\n\t\t);\n\n\t\t// Query fields — the key set for filter/sort/expand/select. This\n\t\t// intentionally includes hidden fields: a hidden relation is still\n\t\t// expandable/filterable at runtime (the server resolves it) even\n\t\t// though it is excluded from the read model. Only password fields\n\t\t// are never queryable.\n\t\tconst queryLines: string[] = [];\n\t\tfor (const f of fields) {\n\t\t\tif (f.type === \"password\") continue;\n\t\t\tconst key = fieldKey(f.name);\n\t\t\tconst type = fieldTypeScriptType(f);\n\t\t\tif (type === \"never\") continue;\n\t\t\tqueryLines.push(` ${JSON.stringify(key)}?: ${type};`);\n\t\t}\n\t\tsections.push(\n\t\t\t`export interface ${typeName}QueryFields${renderInterface({\n\t\t\t\textends: includeBaseFields ? \"BaseRecord\" : undefined,\n\t\t\t\tbody: queryLines.join(\"\\n\"),\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// Create-data map — typed `create()`/`update()` payloads, including\n\t// write-only password fields.\n\tconst createMapEntries = filtered\n\t\t.map((c) => ` \"${c.name}\": ${collectionTypeName(c.name)}CreateData;`)\n\t\t.join(\"\\n\");\n\tsections.push(`export interface LazypockCreateData {\n${createMapEntries}\n}`);\n\n\t// Query-fields map — filter/sort/expand/select keys per collection,\n\t// including hidden fields (valid at runtime despite being excluded from\n\t// the read model). Auth collections get the auth system keys too.\n\tconst queryMapEntries = filtered\n\t\t.map(\n\t\t\t(c) =>\n\t\t\t\t` \"${c.name}\": ${collectionTypeName(c.name)}QueryFields${\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 LazypockQueryFields {\n${queryMapEntries}\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 * For auth collections the generated *CreateData type includes the\n * write-only password field, so creating a user is type-safe:\n * client.collection(\"users\").create({ email, password }) // ✓\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 // The constraint is \"keyof LazypockCollections | (string & {})\" rather than a\n // bare generic string or a strict keyof:\n // - the keyof LazypockCollections member is what makes the IDE suggest\n // collection names, and known literals resolve to the typed service:\n // client.collection(\"posts\") // suggested + typed\n // - the string & {} member keeps unknown/dynamic names compiling — they\n // resolve to the untyped service, so a studio that manages\n // user-created collections can still call collection(someString).\n //\n // The (string & {}) intersection keeps the union from collapsing to plain\n // string (which would silently kill the suggestions).\n //\n // The service binds three types: the read model, the write-only *CreateData,\n // and the *QueryFields (filter/sort/expand/select keys). QueryFields is what\n // lets hidden relation fields — excluded from the read model but still\n // expandable/filterable at runtime — typecheck:\n // client.collection(\"project_members\").getFullList({ expand: \"user\" }) // ✓\n override collection<K extends keyof LazypockCollections | (string & {})>(\n name: K,\n ): K extends keyof LazypockCollections\n ? CollectionService<\n LazypockCollections[K],\n LazypockCreateData[K],\n LazypockQueryFields[K]\n >\n : CollectionService<unknown> {\n return super.collection(name) as K extends keyof LazypockCollections\n ? CollectionService<\n LazypockCollections[K],\n LazypockCreateData[K],\n LazypockQueryFields[K]\n >\n : 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\n/**\n * Render the write-only create-data members for a field.\n *\n * Unlike the read model, create/update data includes password fields (sent as\n * plain strings — the server hashes them) and hidden fields: the server\n * accepts them on write and never returns them in responses.\n *\n * Password fields are exposed under the canonical PocketBase API key\n * `password` (the server accepts `password` for any single password field and\n * hashes it into the backing column, e.g. `password_hash`), with the raw\n * metadata name kept as a backward-compat alias.\n *\n * A field is only marked required when it actually needs a client value:\n * fields with a server-side default (`options.defaultValue`) and the auth\n * system fields `verified` / `emailVisibility` (the server defaults them to\n * false/true even though the snapshot doesn't carry `defaultValue`) stay\n * optional, so `create({ email, password })` typechecks without forcing\n * callers to send values the server would fill in anyway. Password fields\n * are optional (accounts may exist without a password, e.g. OAuth-only).\n */\nfunction createDataMemberLines(\n\tf: SchemaField,\n): { key: string; line: string }[] {\n\t// Autodate columns are server-managed; never writable.\n\tif (f.type === \"autodate\") return [];\n\tconst rawName = fieldKey(f.name);\n\tconst serverDefaulted =\n\t\tf.options?.defaultValue !== undefined ||\n\t\t(f.system && (f.name === \"verified\" || f.name === \"emailVisibility\"));\n\tconst req = f.required && !serverDefaulted ? \"\" : \"?\";\n\tconst type = f.type === \"password\" ? \"string\" : fieldTypeScriptType(f);\n\tif (type === \"never\") return [];\n\n\tconst members = [\n\t\t{ key: rawName, line: ` ${JSON.stringify(rawName)}${req}: ${type};` },\n\t];\n\t// Write-only password: canonical key is `password` (PocketBase parity) —\n\t// the server accepts `password` for any single password field and hashes\n\t// it. The raw metadata name (e.g. `password_hash`) stays as a compat alias.\n\tif (f.type === \"password\" && rawName !== \"password\") {\n\t\tmembers.unshift({\n\t\t\tkey: \"password\",\n\t\t\tline: ` ${JSON.stringify(\"password\")}${req}: ${type};`,\n\t\t});\n\t}\n\treturn members;\n}\n"],"mappings":"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","names":[]}
|
package/dist/cli.global.js
CHANGED
|
@@ -111,6 +111,20 @@ var Lazypock = (() => {
|
|
|
111
111
|
body: createLines.join("\n")
|
|
112
112
|
})}`
|
|
113
113
|
);
|
|
114
|
+
const queryLines = [];
|
|
115
|
+
for (const f of fields) {
|
|
116
|
+
if (f.type === "password") continue;
|
|
117
|
+
const key = fieldKey(f.name);
|
|
118
|
+
const type = fieldTypeScriptType(f);
|
|
119
|
+
if (type === "never") continue;
|
|
120
|
+
queryLines.push(` ${JSON.stringify(key)}?: ${type};`);
|
|
121
|
+
}
|
|
122
|
+
sections.push(
|
|
123
|
+
`export interface ${typeName}QueryFields${renderInterface({
|
|
124
|
+
extends: includeBaseFields ? "BaseRecord" : void 0,
|
|
125
|
+
body: queryLines.join("\n")
|
|
126
|
+
})}`
|
|
127
|
+
);
|
|
114
128
|
}
|
|
115
129
|
sections.push(`export interface AuthRecord extends BaseRecord {
|
|
116
130
|
email: string;
|
|
@@ -125,6 +139,12 @@ ${mapEntries}
|
|
|
125
139
|
const createMapEntries = filtered.map((c) => ` "${c.name}": ${collectionTypeName(c.name)}CreateData;`).join("\n");
|
|
126
140
|
sections.push(`export interface LazypockCreateData {
|
|
127
141
|
${createMapEntries}
|
|
142
|
+
}`);
|
|
143
|
+
const queryMapEntries = filtered.map(
|
|
144
|
+
(c) => ` "${c.name}": ${collectionTypeName(c.name)}QueryFields${c.type === "auth" ? " & AuthRecord" : ""};`
|
|
145
|
+
).join("\n");
|
|
146
|
+
sections.push(`export interface LazypockQueryFields {
|
|
147
|
+
${queryMapEntries}
|
|
128
148
|
}`);
|
|
129
149
|
const schemaEntries = collections.map(
|
|
130
150
|
(c) => ` {
|
|
@@ -178,15 +198,26 @@ export class TypedClient extends LazypockClient {
|
|
|
178
198
|
// The (string & {}) intersection keeps the union from collapsing to plain
|
|
179
199
|
// string (which would silently kill the suggestions).
|
|
180
200
|
//
|
|
181
|
-
//
|
|
182
|
-
//
|
|
201
|
+
// The service binds three types: the read model, the write-only *CreateData,
|
|
202
|
+
// and the *QueryFields (filter/sort/expand/select keys). QueryFields is what
|
|
203
|
+
// lets hidden relation fields \u2014 excluded from the read model but still
|
|
204
|
+
// expandable/filterable at runtime \u2014 typecheck:
|
|
205
|
+
// client.collection("project_members").getFullList({ expand: "user" }) // \u2713
|
|
183
206
|
override collection<K extends keyof LazypockCollections | (string & {})>(
|
|
184
207
|
name: K,
|
|
185
208
|
): K extends keyof LazypockCollections
|
|
186
|
-
? CollectionService<
|
|
209
|
+
? CollectionService<
|
|
210
|
+
LazypockCollections[K],
|
|
211
|
+
LazypockCreateData[K],
|
|
212
|
+
LazypockQueryFields[K]
|
|
213
|
+
>
|
|
187
214
|
: CollectionService<unknown> {
|
|
188
215
|
return super.collection(name) as K extends keyof LazypockCollections
|
|
189
|
-
? CollectionService<
|
|
216
|
+
? CollectionService<
|
|
217
|
+
LazypockCollections[K],
|
|
218
|
+
LazypockCreateData[K],
|
|
219
|
+
LazypockQueryFields[K]
|
|
220
|
+
>
|
|
190
221
|
: CollectionService<unknown>;
|
|
191
222
|
}
|
|
192
223
|
}
|
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\n\t\t// Write-only create data: what `create()`/`update()` accept. This\n\t\t// intentionally includes password fields (and hidden fields) that are\n\t\t// excluded from the read model — the server accepts them on write,\n\t\t// hashes passwords, and never returns them in responses.\n\t\tconst createLines: string[] = [];\n\t\tconst seenKeys = new Set<string>();\n\t\tfor (const f of fields) {\n\t\t\tfor (const { key, line } of createDataMemberLines(f)) {\n\t\t\t\tif (seenKeys.has(key)) continue;\n\t\t\t\tseenKeys.add(key);\n\t\t\t\tcreateLines.push(line);\n\t\t\t}\n\t\t}\n\t\tsections.push(\n\t\t\t`export interface ${typeName}CreateData${renderInterface({\n\t\t\t\tbody: createLines.join(\"\\n\"),\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// Create-data map — typed `create()`/`update()` payloads, including\n\t// write-only password fields.\n\tconst createMapEntries = filtered\n\t\t.map((c) => ` \"${c.name}\": ${collectionTypeName(c.name)}CreateData;`)\n\t\t.join(\"\\n\");\n\tsections.push(`export interface LazypockCreateData {\n${createMapEntries}\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 * For auth collections the generated *CreateData type includes the\n * write-only password field, so creating a user is type-safe:\n * client.collection(\"users\").create({ email, password }) // ✓\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 // The constraint is \"keyof LazypockCollections | (string & {})\" rather than a\n // bare generic string or a strict keyof:\n // - the keyof LazypockCollections member is what makes the IDE suggest\n // collection names, and known literals resolve to the typed service:\n // client.collection(\"posts\") // suggested + typed\n // - the string & {} member keeps unknown/dynamic names compiling — they\n // resolve to the untyped service, so a studio that manages\n // user-created collections can still call collection(someString).\n //\n // The (string & {}) intersection keeps the union from collapsing to plain\n // string (which would silently kill the suggestions).\n //\n // create()/update() take the collection's *CreateData — the read model\n // omits password/hidden fields, but the write model carries them.\n override collection<K extends keyof LazypockCollections | (string & {})>(\n name: K,\n ): K extends keyof LazypockCollections\n ? CollectionService<LazypockCollections[K], LazypockCreateData[K]>\n : CollectionService<unknown> {\n return super.collection(name) as K extends keyof LazypockCollections\n ? CollectionService<LazypockCollections[K], LazypockCreateData[K]>\n : 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\n/**\n * Render the write-only create-data members for a field.\n *\n * Unlike the read model, create/update data includes password fields (sent as\n * plain strings — the server hashes them) and hidden fields: the server\n * accepts them on write and never returns them in responses.\n *\n * Password fields are exposed under the canonical PocketBase API key\n * `password` (the server accepts `password` for any single password field and\n * hashes it into the backing column, e.g. `password_hash`), with the raw\n * metadata name kept as a backward-compat alias.\n *\n * A field is only marked required when it actually needs a client value:\n * fields with a server-side default (`options.defaultValue`) and the auth\n * system fields `verified` / `emailVisibility` (the server defaults them to\n * false/true even though the snapshot doesn't carry `defaultValue`) stay\n * optional, so `create({ email, password })` typechecks without forcing\n * callers to send values the server would fill in anyway. Password fields\n * are optional (accounts may exist without a password, e.g. OAuth-only).\n */\nfunction createDataMemberLines(\n\tf: SchemaField,\n): { key: string; line: string }[] {\n\t// Autodate columns are server-managed; never writable.\n\tif (f.type === \"autodate\") return [];\n\tconst rawName = fieldKey(f.name);\n\tconst serverDefaulted =\n\t\tf.options?.defaultValue !== undefined ||\n\t\t(f.system && (f.name === \"verified\" || f.name === \"emailVisibility\"));\n\tconst req = f.required && !serverDefaulted ? \"\" : \"?\";\n\tconst type = f.type === \"password\" ? \"string\" : fieldTypeScriptType(f);\n\tif (type === \"never\") return [];\n\n\tconst members = [\n\t\t{ key: rawName, line: ` ${JSON.stringify(rawName)}${req}: ${type};` },\n\t];\n\t// Write-only password: canonical key is `password` (PocketBase parity) —\n\t// the server accepts `password` for any single password field and hashes\n\t// it. The raw metadata name (e.g. `password_hash`) stays as a compat alias.\n\tif (f.type === \"password\" && rawName !== \"password\") {\n\t\tmembers.unshift({\n\t\t\tkey: \"password\",\n\t\t\tline: ` ${JSON.stringify(\"password\")}${req}: ${type};`,\n\t\t});\n\t}\n\treturn members;\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\n\t\t// Write-only create data: what `create()`/`update()` accept. This\n\t\t// intentionally includes password fields (and hidden fields) that are\n\t\t// excluded from the read model — the server accepts them on write,\n\t\t// hashes passwords, and never returns them in responses.\n\t\tconst createLines: string[] = [];\n\t\tconst seenKeys = new Set<string>();\n\t\tfor (const f of fields) {\n\t\t\tfor (const { key, line } of createDataMemberLines(f)) {\n\t\t\t\tif (seenKeys.has(key)) continue;\n\t\t\t\tseenKeys.add(key);\n\t\t\t\tcreateLines.push(line);\n\t\t\t}\n\t\t}\n\t\tsections.push(\n\t\t\t`export interface ${typeName}CreateData${renderInterface({\n\t\t\t\tbody: createLines.join(\"\\n\"),\n\t\t\t})}`,\n\t\t);\n\n\t\t// Query fields — the key set for filter/sort/expand/select. This\n\t\t// intentionally includes hidden fields: a hidden relation is still\n\t\t// expandable/filterable at runtime (the server resolves it) even\n\t\t// though it is excluded from the read model. Only password fields\n\t\t// are never queryable.\n\t\tconst queryLines: string[] = [];\n\t\tfor (const f of fields) {\n\t\t\tif (f.type === \"password\") continue;\n\t\t\tconst key = fieldKey(f.name);\n\t\t\tconst type = fieldTypeScriptType(f);\n\t\t\tif (type === \"never\") continue;\n\t\t\tqueryLines.push(` ${JSON.stringify(key)}?: ${type};`);\n\t\t}\n\t\tsections.push(\n\t\t\t`export interface ${typeName}QueryFields${renderInterface({\n\t\t\t\textends: includeBaseFields ? \"BaseRecord\" : undefined,\n\t\t\t\tbody: queryLines.join(\"\\n\"),\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// Create-data map — typed `create()`/`update()` payloads, including\n\t// write-only password fields.\n\tconst createMapEntries = filtered\n\t\t.map((c) => ` \"${c.name}\": ${collectionTypeName(c.name)}CreateData;`)\n\t\t.join(\"\\n\");\n\tsections.push(`export interface LazypockCreateData {\n${createMapEntries}\n}`);\n\n\t// Query-fields map — filter/sort/expand/select keys per collection,\n\t// including hidden fields (valid at runtime despite being excluded from\n\t// the read model). Auth collections get the auth system keys too.\n\tconst queryMapEntries = filtered\n\t\t.map(\n\t\t\t(c) =>\n\t\t\t\t` \"${c.name}\": ${collectionTypeName(c.name)}QueryFields${\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 LazypockQueryFields {\n${queryMapEntries}\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 * For auth collections the generated *CreateData type includes the\n * write-only password field, so creating a user is type-safe:\n * client.collection(\"users\").create({ email, password }) // ✓\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 // The constraint is \"keyof LazypockCollections | (string & {})\" rather than a\n // bare generic string or a strict keyof:\n // - the keyof LazypockCollections member is what makes the IDE suggest\n // collection names, and known literals resolve to the typed service:\n // client.collection(\"posts\") // suggested + typed\n // - the string & {} member keeps unknown/dynamic names compiling — they\n // resolve to the untyped service, so a studio that manages\n // user-created collections can still call collection(someString).\n //\n // The (string & {}) intersection keeps the union from collapsing to plain\n // string (which would silently kill the suggestions).\n //\n // The service binds three types: the read model, the write-only *CreateData,\n // and the *QueryFields (filter/sort/expand/select keys). QueryFields is what\n // lets hidden relation fields — excluded from the read model but still\n // expandable/filterable at runtime — typecheck:\n // client.collection(\"project_members\").getFullList({ expand: \"user\" }) // ✓\n override collection<K extends keyof LazypockCollections | (string & {})>(\n name: K,\n ): K extends keyof LazypockCollections\n ? CollectionService<\n LazypockCollections[K],\n LazypockCreateData[K],\n LazypockQueryFields[K]\n >\n : CollectionService<unknown> {\n return super.collection(name) as K extends keyof LazypockCollections\n ? CollectionService<\n LazypockCollections[K],\n LazypockCreateData[K],\n LazypockQueryFields[K]\n >\n : 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\n/**\n * Render the write-only create-data members for a field.\n *\n * Unlike the read model, create/update data includes password fields (sent as\n * plain strings — the server hashes them) and hidden fields: the server\n * accepts them on write and never returns them in responses.\n *\n * Password fields are exposed under the canonical PocketBase API key\n * `password` (the server accepts `password` for any single password field and\n * hashes it into the backing column, e.g. `password_hash`), with the raw\n * metadata name kept as a backward-compat alias.\n *\n * A field is only marked required when it actually needs a client value:\n * fields with a server-side default (`options.defaultValue`) and the auth\n * system fields `verified` / `emailVisibility` (the server defaults them to\n * false/true even though the snapshot doesn't carry `defaultValue`) stay\n * optional, so `create({ email, password })` typechecks without forcing\n * callers to send values the server would fill in anyway. Password fields\n * are optional (accounts may exist without a password, e.g. OAuth-only).\n */\nfunction createDataMemberLines(\n\tf: SchemaField,\n): { key: string; line: string }[] {\n\t// Autodate columns are server-managed; never writable.\n\tif (f.type === \"autodate\") return [];\n\tconst rawName = fieldKey(f.name);\n\tconst serverDefaulted =\n\t\tf.options?.defaultValue !== undefined ||\n\t\t(f.system && (f.name === \"verified\" || f.name === \"emailVisibility\"));\n\tconst req = f.required && !serverDefaulted ? \"\" : \"?\";\n\tconst type = f.type === \"password\" ? \"string\" : fieldTypeScriptType(f);\n\tif (type === \"never\") return [];\n\n\tconst members = [\n\t\t{ key: rawName, line: ` ${JSON.stringify(rawName)}${req}: ${type};` },\n\t];\n\t// Write-only password: canonical key is `password` (PocketBase parity) —\n\t// the server accepts `password` for any single password field and hashes\n\t// it. The raw metadata name (e.g. `password_hash`) stays as a compat alias.\n\tif (f.type === \"password\" && rawName !== \"password\") {\n\t\tmembers.unshift({\n\t\t\tkey: \"password\",\n\t\t\tline: ` ${JSON.stringify(\"password\")}${req}: ${type};`,\n\t\t});\n\t}\n\treturn members;\n}\n"],"mappings":"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","names":[]}
|