lazypock 0.7.0 → 0.8.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 +21 -0
- package/dist/{chunk-HOBNUW5Z.js → chunk-BJQUGPKE.js} +33 -5
- package/dist/chunk-BJQUGPKE.js.map +1 -0
- package/dist/cli.cjs +32 -4
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.global.js +32 -4
- package/dist/cli.global.js.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/index.cjs +61 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +35 -12
- package/dist/index.d.ts +35 -12
- package/dist/index.global.js +61 -10
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +30 -7
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/codegen.ts +53 -4
- package/src/collection.ts +47 -15
- package/src/lazypock.ts +14 -0
- package/src/schema.ts +2 -0
- package/dist/chunk-HOBNUW5Z.js.map +0 -1
package/package.json
CHANGED
package/src/codegen.ts
CHANGED
|
@@ -85,6 +85,19 @@ export function generateTypes(
|
|
|
85
85
|
body,
|
|
86
86
|
})}`,
|
|
87
87
|
);
|
|
88
|
+
|
|
89
|
+
// Write-only create data: what `create()`/`update()` accept. This
|
|
90
|
+
// intentionally includes password fields (and hidden fields) that are
|
|
91
|
+
// excluded from the read model — the server accepts them on write,
|
|
92
|
+
// hashes passwords, and never returns them in responses.
|
|
93
|
+
const createLines = fields
|
|
94
|
+
.map((f) => createDataMemberLine(f))
|
|
95
|
+
.filter((l) => l !== "");
|
|
96
|
+
sections.push(
|
|
97
|
+
`export interface ${typeName}CreateData${renderInterface({
|
|
98
|
+
body: createLines.join("\n"),
|
|
99
|
+
})}`,
|
|
100
|
+
);
|
|
88
101
|
}
|
|
89
102
|
|
|
90
103
|
// Auth collection type
|
|
@@ -104,6 +117,15 @@ export function generateTypes(
|
|
|
104
117
|
.join("\n");
|
|
105
118
|
sections.push(`export interface LazypockCollections {
|
|
106
119
|
${mapEntries}
|
|
120
|
+
}`);
|
|
121
|
+
|
|
122
|
+
// Create-data map — typed `create()`/`update()` payloads, including
|
|
123
|
+
// write-only password fields.
|
|
124
|
+
const createMapEntries = filtered
|
|
125
|
+
.map((c) => ` "${c.name}": ${collectionTypeName(c.name)}CreateData;`)
|
|
126
|
+
.join("\n");
|
|
127
|
+
sections.push(`export interface LazypockCreateData {
|
|
128
|
+
${createMapEntries}
|
|
107
129
|
}`);
|
|
108
130
|
|
|
109
131
|
// Runtime schema snapshot — lets the client exclude hidden fields by
|
|
@@ -135,6 +157,10 @@ ${schemaEntries}
|
|
|
135
157
|
* Collection access is fully type-checked:
|
|
136
158
|
* client.collection("posts").create({ title: "x" }) // title must exist
|
|
137
159
|
*
|
|
160
|
+
* For auth collections the generated *CreateData type includes the
|
|
161
|
+
* write-only password field, so creating a user is type-safe:
|
|
162
|
+
* client.collection("users").create({ email, password }) // ✓
|
|
163
|
+
*
|
|
138
164
|
* The schema snapshot is wired into the client automatically, so hidden
|
|
139
165
|
* fields are excluded from responses and select/expand are validated.
|
|
140
166
|
*/
|
|
@@ -149,10 +175,16 @@ export function createClient(options: LazypockClientOptions): TypedClient {
|
|
|
149
175
|
}
|
|
150
176
|
|
|
151
177
|
export class TypedClient extends LazypockClient {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
178
|
+
// K extends keyof LazypockCollections (rather than a generic string with a
|
|
179
|
+
// conditional return) so the IDE suggests collection names and unknown names
|
|
180
|
+
// are rejected at compile time:
|
|
181
|
+
// client.collection("posts") // suggested + typed
|
|
182
|
+
// client.collection("nope") // TS error
|
|
183
|
+
//
|
|
184
|
+
// create()/update() take the collection's *CreateData — the read model
|
|
185
|
+
// omits password/hidden fields, but the write model carries them.
|
|
186
|
+
override collection<K extends keyof LazypockCollections>(name: K): CollectionService<LazypockCollections[K], LazypockCreateData[K]> {
|
|
187
|
+
return super.collection(name) as CollectionService<LazypockCollections[K], LazypockCreateData[K]>;
|
|
156
188
|
}
|
|
157
189
|
}
|
|
158
190
|
`);
|
|
@@ -182,3 +214,20 @@ function memberLine(f: SchemaField): string {
|
|
|
182
214
|
if (type === "never") return "";
|
|
183
215
|
return ` ${JSON.stringify(key)}${req}: ${type};`;
|
|
184
216
|
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Render a write-only create-data member for a field.
|
|
220
|
+
*
|
|
221
|
+
* Unlike the read model, create/update data includes password fields (sent as
|
|
222
|
+
* plain strings — the server hashes them) and hidden fields: the server
|
|
223
|
+
* accepts them on write and never returns them in responses.
|
|
224
|
+
*/
|
|
225
|
+
function createDataMemberLine(f: SchemaField): string {
|
|
226
|
+
// Autodate columns are server-managed; never writable.
|
|
227
|
+
if (f.type === "autodate") return "";
|
|
228
|
+
const key = fieldKey(f.name);
|
|
229
|
+
const req = f.required ? "" : "?";
|
|
230
|
+
const type = f.type === "password" ? "string" : fieldTypeScriptType(f);
|
|
231
|
+
if (type === "never") return "";
|
|
232
|
+
return ` ${JSON.stringify(key)}${req}: ${type};`;
|
|
233
|
+
}
|
package/src/collection.ts
CHANGED
|
@@ -90,7 +90,7 @@ function normalizeAction(
|
|
|
90
90
|
*
|
|
91
91
|
* @typeParam T — The record shape for this collection. Defaults to {@link ApiRecord}.
|
|
92
92
|
*/
|
|
93
|
-
export class CollectionService<T = ApiRecord> {
|
|
93
|
+
export class CollectionService<T = ApiRecord, TData = never> {
|
|
94
94
|
private http: HttpClient;
|
|
95
95
|
private collectionName: string;
|
|
96
96
|
private authStore?: AuthStore;
|
|
@@ -99,8 +99,9 @@ export class CollectionService<T = ApiRecord> {
|
|
|
99
99
|
private schema?: CollectionSchema;
|
|
100
100
|
/**
|
|
101
101
|
* Active field projection from {@link select}. `"*"` (or unset) means
|
|
102
|
-
* "all visible (non-hidden) fields
|
|
103
|
-
* one is available, otherwise left to
|
|
102
|
+
* "all visible (non-hidden) fields plus the implicit system keys" —
|
|
103
|
+
* resolved against the schema when one is available, otherwise left to
|
|
104
|
+
* the server.
|
|
104
105
|
*/
|
|
105
106
|
private fieldsPreset?: string;
|
|
106
107
|
|
|
@@ -137,18 +138,19 @@ export class CollectionService<T = ApiRecord> {
|
|
|
137
138
|
* const t = await client.collection("posts").select("id", "title").getList();
|
|
138
139
|
* // GET /api/posts?fields=id,title
|
|
139
140
|
*
|
|
140
|
-
* // All visible fields (
|
|
141
|
+
* // All visible fields (implicit system keys id/created/updated/… are
|
|
142
|
+
* // kept; hidden fields are excluded via the projection)
|
|
141
143
|
* const all = await client.collection("posts").select("*").getList();
|
|
142
|
-
* // GET /api/posts?fields=id,title,published,… (schema known)
|
|
144
|
+
* // GET /api/posts?fields=id,created,updated,collectionId,collectionName,title,published,… (schema known)
|
|
143
145
|
* ```
|
|
144
146
|
*
|
|
145
147
|
* When no schema is known, the default (no `select()` call) sends no
|
|
146
148
|
* `fields` param — the server returns every non-password field.
|
|
147
149
|
* Pass `select("*")` to restore the default after a projection.
|
|
148
150
|
*/
|
|
149
|
-
select<K extends FieldKey<T> | "*">(...fields: K[]): CollectionService<T> {
|
|
151
|
+
select<K extends FieldKey<T> | "*">(...fields: K[]): CollectionService<T, TData> {
|
|
150
152
|
const preset = fields.length === 0 ? "*" : fields.join(",");
|
|
151
|
-
const derived = new CollectionService<T>(
|
|
153
|
+
const derived = new CollectionService<T, TData>(
|
|
152
154
|
this.http,
|
|
153
155
|
this.collectionName,
|
|
154
156
|
this.authStore,
|
|
@@ -173,7 +175,8 @@ export class CollectionService<T = ApiRecord> {
|
|
|
173
175
|
* Fields to send with reads:
|
|
174
176
|
* 1. explicit `options.fields` (caller wins)
|
|
175
177
|
* 2. `select()` preset
|
|
176
|
-
* 3. schema default — all visible (non-hidden
|
|
178
|
+
* 3. schema default — all visible (non-hidden) fields plus the implicit
|
|
179
|
+
* system keys (`id`, `created`, `updated`, `collectionId`, `collectionName`)
|
|
177
180
|
*/
|
|
178
181
|
private effectiveFields(optionsFields?: string): string | undefined {
|
|
179
182
|
if (optionsFields !== undefined) return optionsFields;
|
|
@@ -187,8 +190,18 @@ export class CollectionService<T = ApiRecord> {
|
|
|
187
190
|
}
|
|
188
191
|
|
|
189
192
|
/**
|
|
190
|
-
* All non-hidden, non-
|
|
191
|
-
*
|
|
193
|
+
* All non-hidden, non-password, non-autodate field names from the schema,
|
|
194
|
+
* prefixed with the implicit system keys that are part of every record
|
|
195
|
+
* (`id`, `created`, `updated`, `collectionId`, `collectionName`).
|
|
196
|
+
*
|
|
197
|
+
* The system keys never appear in the schema (they are implied for every
|
|
198
|
+
* collection) but MUST be listed explicitly when projecting — the server
|
|
199
|
+
* applies strict projection, so unrequested fields are dropped. Without
|
|
200
|
+
* them, `getList`/`getOne` responses lose `id` (and `created`/`updated`),
|
|
201
|
+
* breaking selection, edits, and relation references.
|
|
202
|
+
*
|
|
203
|
+
* `undefined` when no schema fields are available (server decides — its
|
|
204
|
+
* default returns every non-password field, including the system keys).
|
|
192
205
|
*/
|
|
193
206
|
visibleFields(): string | undefined {
|
|
194
207
|
if (!this.schema?.fields) return undefined;
|
|
@@ -200,7 +213,18 @@ export class CollectionService<T = ApiRecord> {
|
|
|
200
213
|
f.type !== "autodate",
|
|
201
214
|
)
|
|
202
215
|
.map((f) => f.name);
|
|
203
|
-
|
|
216
|
+
if (visible.length === 0) return undefined;
|
|
217
|
+
// Dedupe in case a schema ever names a field after a system key.
|
|
218
|
+
return [
|
|
219
|
+
...new Set([
|
|
220
|
+
"id",
|
|
221
|
+
"created",
|
|
222
|
+
"updated",
|
|
223
|
+
"collectionId",
|
|
224
|
+
"collectionName",
|
|
225
|
+
...visible,
|
|
226
|
+
]),
|
|
227
|
+
].join(",");
|
|
204
228
|
}
|
|
205
229
|
|
|
206
230
|
/** Warn once when an expand field is not a relation (schema known). */
|
|
@@ -366,7 +390,11 @@ export class CollectionService<T = ApiRecord> {
|
|
|
366
390
|
* @param options Optional request options.
|
|
367
391
|
*/
|
|
368
392
|
create(
|
|
369
|
-
data:
|
|
393
|
+
data: [TData] extends [never]
|
|
394
|
+
? T extends ApiRecord
|
|
395
|
+
? Record<string, unknown>
|
|
396
|
+
: CreateData<T>
|
|
397
|
+
: TData,
|
|
370
398
|
options?: RequestOptions,
|
|
371
399
|
): Promise<T | null> {
|
|
372
400
|
return this.http.post<T>(
|
|
@@ -385,7 +413,11 @@ export class CollectionService<T = ApiRecord> {
|
|
|
385
413
|
*/
|
|
386
414
|
update(
|
|
387
415
|
id: string,
|
|
388
|
-
data:
|
|
416
|
+
data: [TData] extends [never]
|
|
417
|
+
? T extends ApiRecord
|
|
418
|
+
? Record<string, unknown>
|
|
419
|
+
: UpdateData<T>
|
|
420
|
+
: Partial<TData>,
|
|
389
421
|
options?: RequestOptions,
|
|
390
422
|
): Promise<T | null> {
|
|
391
423
|
return this.http.patch<T>(
|
|
@@ -457,8 +489,8 @@ export class CollectionService<T = ApiRecord> {
|
|
|
457
489
|
* `types.schemas`). Enables schema-aware defaults: hidden fields are
|
|
458
490
|
* excluded from responses, select/expand are validated.
|
|
459
491
|
*/
|
|
460
|
-
withSchema(schema: CollectionSchema): CollectionService<T> {
|
|
461
|
-
const derived = new CollectionService<T>(
|
|
492
|
+
withSchema(schema: CollectionSchema): CollectionService<T, TData> {
|
|
493
|
+
const derived = new CollectionService<T, TData>(
|
|
462
494
|
this.http,
|
|
463
495
|
this.collectionName,
|
|
464
496
|
this.authStore,
|
package/src/lazypock.ts
CHANGED
|
@@ -120,6 +120,14 @@ export interface LazypockClientOptions {
|
|
|
120
120
|
export class LazypockClient {
|
|
121
121
|
readonly http: HttpClient;
|
|
122
122
|
readonly authStore: AuthStore;
|
|
123
|
+
/**
|
|
124
|
+
* Resolves once persisted auth state has been loaded from storage.
|
|
125
|
+
* The client auto-initializes its auth store on construction, so a
|
|
126
|
+
* previously persisted token is ready immediately (no manual
|
|
127
|
+
* `authStore.init()` needed). Await this only if you must gate on
|
|
128
|
+
* the restored session.
|
|
129
|
+
*/
|
|
130
|
+
readonly authReady: Promise<void>;
|
|
123
131
|
readonly realtime: RealtimeService;
|
|
124
132
|
readonly collections: CollectionsService;
|
|
125
133
|
readonly files: FilesService;
|
|
@@ -134,6 +142,12 @@ export class LazypockClient {
|
|
|
134
142
|
const baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
135
143
|
this.authStore =
|
|
136
144
|
options.authStore ?? new AuthStore(options.storage ?? memoryStorage);
|
|
145
|
+
// Auto-init the auth store so persisted tokens are restored without a
|
|
146
|
+
// manual `await client.authStore.init()`. A caller-provided store is
|
|
147
|
+
// left untouched — it owns its own initialization lifecycle.
|
|
148
|
+
this.authReady = options.authStore
|
|
149
|
+
? Promise.resolve()
|
|
150
|
+
: this.authStore.init();
|
|
137
151
|
this.http = new HttpClient(baseUrl, this.authStore);
|
|
138
152
|
this.realtime = options.realtime ?? new RealtimeService();
|
|
139
153
|
// Cache the socket URL so collection-level subscribe() can auto-connect.
|
package/src/schema.ts
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/typegen.ts","../src/codegen.ts"],"sourcesContent":["// ── Type mapping (runtime + codegen) ────────────────────\n// Maps server field types to TypeScript types.\n// Shared by the runtime `SchemaTypes` helper and the codegen CLI.\n\nimport type { SchemaField } from \"./schema\";\n\n/**\n * Map a single server field to its TypeScript type string.\n * Used by the codegen CLI to emit interface members.\n *\n * @param field The field definition.\n * @param fallback Fallback type for unknown field types (default `unknown`).\n */\nexport function fieldTypeScriptType(\n\tfield: SchemaField,\n\tfallback = \"unknown\",\n): string {\n\tconst opts = field.options ?? {};\n\tswitch (field.type) {\n\t\tcase \"text\":\n\t\tcase \"email\":\n\t\tcase \"url\":\n\t\tcase \"editor\":\n\t\tcase \"date\":\n\t\tcase \"datetime\":\n\t\t\treturn \"string\";\n\t\tcase \"number\":\n\t\t\treturn \"number\";\n\t\tcase \"bool\":\n\t\t\treturn \"boolean\";\n\t\tcase \"select\": {\n\t\t\tconst values = Array.isArray(opts.values) ? opts.values : [];\n\t\t\tif (values.length > 0) {\n\t\t\t\treturn values.map((v) => JSON.stringify(String(v))).join(\" | \");\n\t\t\t}\n\t\t\treturn \"string\";\n\t\t}\n\t\tcase \"multi_select\": {\n\t\t\tconst values = Array.isArray(opts.values) ? opts.values : [];\n\t\t\tif (values.length > 0) {\n\t\t\t\treturn `(${values.map((v) => JSON.stringify(String(v))).join(\" | \")})[]`;\n\t\t\t}\n\t\t\treturn \"string[]\";\n\t\t}\n\t\tcase \"file\":\n\t\t\treturn \"string\";\n\t\tcase \"multi_file\":\n\t\t\treturn \"string[]\";\n\t\tcase \"json\":\n\t\tcase \"geo\":\n\t\t\treturn \"Record<string, unknown>\";\n\t\tcase \"relation\":\n\t\t\t// Relations store the target record's ID (string) — or an array\n\t\t\t// of IDs when multi-relation (maxSelect > 1).\n\t\t\treturn (opts.maxSelect ?? 1) > 1 ? \"string[]\" : \"string\";\n\t\tcase \"password\":\n\t\t\t// Passwords are write-only; never expose on read models.\n\t\t\treturn \"never\";\n\t\tdefault:\n\t\t\treturn fallback;\n\t}\n}\n\n/**\n * Returns the runtime type kind for a field — used by {@link schemaFieldType}\n * to build structural types at runtime.\n */\nexport type FieldTypeKind =\n\t| \"string\"\n\t| \"number\"\n\t| \"boolean\"\n\t| \"string-array\"\n\t| \"json\"\n\t| \"relation\"\n\t| \"relation-many\"\n\t| \"password\"\n\t| \"unknown\";\n\n/** Map a server field to its runtime type kind. */\nexport function fieldTypeKind(field: SchemaField): FieldTypeKind {\n\tconst opts = field.options ?? {};\n\tswitch (field.type) {\n\t\tcase \"text\":\n\t\tcase \"email\":\n\t\tcase \"url\":\n\t\tcase \"editor\":\n\t\tcase \"date\":\n\t\tcase \"datetime\":\n\t\tcase \"select\":\n\t\tcase \"file\":\n\t\t\treturn \"string\";\n\t\tcase \"number\":\n\t\t\treturn \"number\";\n\t\tcase \"bool\":\n\t\t\treturn \"boolean\";\n\t\tcase \"multi_select\":\n\t\tcase \"multi_file\":\n\t\t\treturn \"string-array\";\n\t\tcase \"json\":\n\t\tcase \"geo\":\n\t\t\treturn \"json\";\n\t\tcase \"relation\":\n\t\t\treturn (opts.maxSelect ?? 1) > 1 ? \"relation-many\" : \"relation\";\n\t\tcase \"password\":\n\t\t\treturn \"password\";\n\t\tdefault:\n\t\t\treturn \"unknown\";\n\t}\n}\n\n/**\n * Derive a TypeScript field type from a {@link SchemaField} — the runtime\n * counterpart to the codegen mapper. Lets consumers build typed clients\n * from a fetched schema without running the CLI.\n */\nexport function schemaFieldType(field: SchemaField): unknown {\n\tswitch (fieldTypeKind(field)) {\n\t\tcase \"string\":\n\t\t\treturn String;\n\t\tcase \"number\":\n\t\t\treturn Number;\n\t\tcase \"boolean\":\n\t\t\treturn Boolean;\n\t\tcase \"string-array\":\n\t\t\treturn [String] as const;\n\t\tcase \"relation\":\n\t\t\treturn String;\n\t\tcase \"relation-many\":\n\t\t\treturn [String] as const;\n\t\tcase \"json\":\n\t\t\treturn Object;\n\t\tcase \"password\":\n\t\t\treturn undefined;\n\t\tcase \"unknown\":\n\t\t\treturn undefined;\n\t}\n}\n","// ── Codegen ─────────────────────────────────────────────\n// Generates a `lazypock.types.ts` module from the live API schema.\n//\n// The generated file exports:\n// - One interface per collection (e.g. `PostsRecord`)\n// - A `LazypockCollections` map: { posts: PostsRecord; users: UsersRecord; ... }\n// - A `createClient()` factory pre-bound to those types, so\n// `client.collection(\"posts\").create({ title: \"x\" })` is fully type-checked.\n//\n// The CLI in `src/cli.ts` wires this to `GET /collections`.\n\nimport type { CollectionSchema, SchemaField } from \"./schema\";\nimport { fieldTypeScriptType } from \"./typegen\";\n\n/** Format a raw collection name into a valid TS identifier (PascalCase). */\nexport function collectionTypeName(name: string): string {\n\treturn name\n\t\t.split(/[^a-zA-Z0-9]+/)\n\t\t.filter(Boolean)\n\t\t.map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n\t\t.join(\"\")\n\t\t.replace(/^[0-9]/, \"_$&\");\n}\n\n/** Interface member name — sanitize hyphens/spaces but keep readability. */\nexport function fieldKey(name: string): string {\n\treturn name.replace(/[^a-zA-Z0-9_]/g, \"_\");\n}\n\n\n\n/**\n * Generate the full TypeScript source for the typed SDK module.\n *\n * @param collections Collections fetched from the API.\n * @param options Generation options.\n */\nexport function generateTypes(\n\tcollections: CollectionSchema[],\n\toptions: {\n\t\t/** Import specifier for the lazypock package (default `lazypock`). */\n\t\tpackageName?: string;\n\t\t/** Emit base record fields (id, created, updated, …). Default true. */\n\t\tincludeBaseFields?: boolean;\n\t\t/** Skip system collections (names starting with `_` or `users`). Default false. */\n\t\tskipSystem?: boolean;\n\t} = {},\n): string {\n\tconst {\n\t\tpackageName = \"lazypock\",\n\t\tincludeBaseFields = true,\n\t\tskipSystem = false,\n\t} = options;\n\n\tconst filtered = skipSystem\n\t\t? collections.filter(\n\t\t\t\t(c) => !c.system && !c.name.startsWith(\"_\") && c.name !== \"users\",\n\t\t\t)\n\t\t: collections;\n\n\tconst sections: string[] = [];\n\tsections.push(`// ── Auto-generated by lazypock-ts ──────────────────────────\n// Do not edit by hand. Regenerate with: npx lazypock-gen\n// Schema snapshot: ${new Date().toISOString()}`);\n\n\tif (includeBaseFields) {\n\t\tsections.push(`export interface BaseRecord {\n id: string;\n collectionId: string;\n collectionName: string;\n created: string;\n updated: string;\n}`);\n\t}\n\n\t// One interface per collection\n\tfor (const coll of filtered) {\n\t\tconst typeName = collectionTypeName(coll.name);\n\t\tconst fields = coll.fields ?? [];\n\t\tconst lines = fields.map((f) => memberLine(f)).filter((l) => l !== \"\");\n\t\tconst body = lines.join(\"\\n\");\n\t\tsections.push(\n\t\t\t`export interface ${typeName}Record${renderInterface({\n\t\t\t\textends: includeBaseFields ? \"BaseRecord\" : undefined,\n\t\t\t\tbody,\n\t\t\t})}`,\n\t\t);\n\t}\n\n\t// Auth collection type\n\tsections.push(`export interface AuthRecord extends BaseRecord {\n email: string;\n verified: boolean;\n}`);\n\n\t// Collections map\n\tconst mapEntries = filtered\n\t\t.map(\n\t\t\t(c) =>\n\t\t\t\t` \"${c.name}\": ${collectionTypeName(c.name)}Record${\n\t\t\t\t\tc.type === \"auth\" ? \" & AuthRecord\" : \"\"\n\t\t\t\t};`,\n\t\t)\n\t\t.join(\"\\n\");\n\tsections.push(`export interface LazypockCollections {\n${mapEntries}\n}`);\n\n\t// Runtime schema snapshot — lets the client exclude hidden fields by\n\t// default, validate select/expand, and derive visible field lists.\n\tconst schemaEntries = collections\n\t\t.map(\n\t\t\t(c) =>\n\t\t\t\t` {\n id: ${JSON.stringify(c.id ?? null)},\n name: ${JSON.stringify(c.name)},\n type: ${JSON.stringify(c.type)},\n system: ${JSON.stringify(c.system ?? false)},\n fields: ${JSON.stringify(c.fields ?? [], null, 2)},\n rules: ${JSON.stringify(c.rules ?? {})},\n options: ${JSON.stringify(c.options ?? {})},\n }`,\n\t\t)\n\t\t.join(\",\\n\");\n\tsections.push(`// Schema snapshot (runtime) — hidden-field exclusion + query validation.\nexport const lazypockSchema: import(\"${packageName}\").CollectionSchema[] = [\n${schemaEntries}\n];`);\n\n\t// createClient factory\n\tsections.push(`import { LazypockClient, type LazypockClientOptions, type CollectionService } from \"${packageName}\";\n\n/**\n * Create a Lazypock client typed against this schema snapshot.\n * Collection access is fully type-checked:\n * client.collection(\"posts\").create({ title: \"x\" }) // title must exist\n *\n * The schema snapshot is wired into the client automatically, so hidden\n * fields are excluded from responses and select/expand are validated.\n */\nexport function createClient(options: LazypockClientOptions): TypedClient {\n return new TypedClient({\n ...options,\n types: {\n ...options.types,\n schemas: options.types?.schemas ?? lazypockSchema,\n },\n });\n}\n\nexport class TypedClient extends LazypockClient {\n override collection<T extends string>(name: T): T extends keyof LazypockCollections\n ? CollectionService<LazypockCollections[T]>\n : CollectionService<unknown> {\n return super.collection(name) as T extends keyof LazypockCollections ? CollectionService<LazypockCollections[T]> : CollectionService<unknown>;\n }\n}\n`);\n\n\treturn sections.join(\"\\n\\n\") + \"\\n\";\n}\n\n/**\n * Render the interface body including the extends clause.\n * Empty body → ` extends BaseRecord {}` (valid TS).\n */\nfunction renderInterface(opts: { extends?: string; body: string }): string {\n\tconst ext = opts.extends ? ` extends ${opts.extends}` : \"\";\n\tif (!opts.body) return `${ext} {}`;\n\treturn `${ext} {\\n${opts.body}\\n}`;\n}\n\n/** Render a single interface member line for a field. */\nfunction memberLine(f: SchemaField): string {\n\t// Hidden fields are excluded from API responses by the schema-aware\n\t// client default — don't expose them on the read model either.\n\tif (f.hidden) return \"\";\n\tconst key = fieldKey(f.name);\n\tconst req = f.required || f.type === \"password\" ? \"\" : \"?\";\n\tconst type = fieldTypeScriptType(f);\n\t// `never` members (passwords) are omitted from read models.\n\tif (type === \"never\") return \"\";\n\treturn ` ${JSON.stringify(key)}${req}: ${type};`;\n}\n"],"mappings":"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","names":[]}
|