lazypock 0.10.2 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -5
- package/dist/{chunk-LVEOMG6A.js → chunk-RYUQWFG2.js} +38 -7
- package/dist/chunk-RYUQWFG2.js.map +1 -0
- package/dist/cli.cjs +37 -6
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.global.js +37 -6
- package/dist/cli.global.js.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/index.cjs +37 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +27 -12
- package/dist/index.d.ts +27 -12
- package/dist/index.global.js +37 -6
- 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 +66 -6
- package/src/collection.ts +22 -13
- package/src/types.ts +15 -5
- package/dist/chunk-LVEOMG6A.js.map +0 -1
package/package.json
CHANGED
package/src/codegen.ts
CHANGED
|
@@ -73,6 +73,15 @@ export function generateTypes(
|
|
|
73
73
|
}`);
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
// Resolve relation targets (collectionId → record type) for the expand maps.
|
|
77
|
+
// Only collections with a real id are indexed — schemas without ids (e.g.
|
|
78
|
+
// minimal hand-written snapshots) fall back to the name lookup below.
|
|
79
|
+
const collById = new Map(
|
|
80
|
+
filtered
|
|
81
|
+
.filter((c) => c.id !== undefined && c.id !== "")
|
|
82
|
+
.map((c) => [String(c.id), c]),
|
|
83
|
+
);
|
|
84
|
+
|
|
76
85
|
// One interface per collection
|
|
77
86
|
for (const coll of filtered) {
|
|
78
87
|
const typeName = collectionTypeName(coll.name);
|
|
@@ -124,6 +133,42 @@ export function generateTypes(
|
|
|
124
133
|
body: queryLines.join("\n"),
|
|
125
134
|
})}`,
|
|
126
135
|
);
|
|
136
|
+
|
|
137
|
+
// Expand map — relation field → target record type, so expanded
|
|
138
|
+
// records are typed (`record.expand.user` is `UsersRecord` instead of
|
|
139
|
+
// `unknown`). Built from each relation's `options.collectionId`; auth
|
|
140
|
+
// targets get `& AuthRecord`, multi-relations (maxSelect > 1) get
|
|
141
|
+
// arrays, and unknown targets (e.g. skipped collections) fall back to
|
|
142
|
+
// `unknown` via the `ExpandValue` fallback.
|
|
143
|
+
const expandLines: string[] = [];
|
|
144
|
+
for (const f of fields) {
|
|
145
|
+
if (f.type !== "relation") continue;
|
|
146
|
+
// Resolve the target collection: real PocketBase schemas carry
|
|
147
|
+
// `options.collectionId` (the target collection's id), while some
|
|
148
|
+
// tooling/snapshots use `options.collection` (the target's name).
|
|
149
|
+
// Unknown/absent targets fall back to `unknown` via ExpandValue.
|
|
150
|
+
// SAFETY: both option keys are strings; guard with String().
|
|
151
|
+
const target =
|
|
152
|
+
collById.get(String(f.options?.collectionId ?? "")) ??
|
|
153
|
+
filtered.find(
|
|
154
|
+
(c) => c.name === String(f.options?.collection ?? ""),
|
|
155
|
+
);
|
|
156
|
+
if (!target) continue;
|
|
157
|
+
const targetType = `${collectionTypeName(target.name)}Record${
|
|
158
|
+
target.type === "auth" ? " & AuthRecord" : ""
|
|
159
|
+
}`;
|
|
160
|
+
const many = (f.options?.maxSelect ?? 1) > 1;
|
|
161
|
+
expandLines.push(
|
|
162
|
+
` ${JSON.stringify(fieldKey(f.name))}: ${
|
|
163
|
+
many ? `Array<${targetType}>` : targetType
|
|
164
|
+
};`,
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
sections.push(
|
|
168
|
+
`export interface ${typeName}ExpandMap${renderInterface({
|
|
169
|
+
body: expandLines.join("\n"),
|
|
170
|
+
})}`,
|
|
171
|
+
);
|
|
127
172
|
}
|
|
128
173
|
|
|
129
174
|
// Auth collection type
|
|
@@ -167,6 +212,15 @@ ${createMapEntries}
|
|
|
167
212
|
.join("\n");
|
|
168
213
|
sections.push(`export interface LazypockQueryFields {
|
|
169
214
|
${queryMapEntries}
|
|
215
|
+
}`);
|
|
216
|
+
|
|
217
|
+
// Expand map — relation field → target record type per collection, bound
|
|
218
|
+
// as the 4th CollectionService generic so expanded records are typed.
|
|
219
|
+
const expandMapEntries = filtered
|
|
220
|
+
.map((c) => ` "${c.name}": ${collectionTypeName(c.name)}ExpandMap;`)
|
|
221
|
+
.join("\n");
|
|
222
|
+
sections.push(`export interface LazypockExpandMaps {
|
|
223
|
+
${expandMapEntries}
|
|
170
224
|
}`);
|
|
171
225
|
|
|
172
226
|
// Runtime schema snapshot — lets the client exclude hidden fields by
|
|
@@ -228,25 +282,31 @@ export class TypedClient extends LazypockClient {
|
|
|
228
282
|
// The (string & {}) intersection keeps the union from collapsing to plain
|
|
229
283
|
// string (which would silently kill the suggestions).
|
|
230
284
|
//
|
|
231
|
-
// The service binds
|
|
232
|
-
//
|
|
233
|
-
//
|
|
234
|
-
//
|
|
285
|
+
// The service binds four types: the read model, the write-only *CreateData,
|
|
286
|
+
// the *QueryFields (filter/sort/expand/select keys), and the *ExpandMap
|
|
287
|
+
// (relation field → target record type). QueryFields is what lets hidden
|
|
288
|
+
// relation fields — excluded from the read model but still expandable/
|
|
289
|
+
// filterable at runtime — typecheck:
|
|
235
290
|
// client.collection("project_members").getFullList({ expand: "user" }) // ✓
|
|
291
|
+
// ExpandMap is what types the expanded data on the result:
|
|
292
|
+
// const [rec] = await client.collection("posts").getFullList({ expand: "user" });
|
|
293
|
+
// rec.expand?.user // ✓ UsersRecord, not unknown
|
|
236
294
|
override collection<K extends keyof LazypockCollections | (string & {})>(
|
|
237
295
|
name: K,
|
|
238
296
|
): K extends keyof LazypockCollections
|
|
239
297
|
? CollectionService<
|
|
240
298
|
LazypockCollections[K],
|
|
241
299
|
LazypockCreateData[K],
|
|
242
|
-
LazypockQueryFields[K]
|
|
300
|
+
LazypockQueryFields[K],
|
|
301
|
+
LazypockExpandMaps[K]
|
|
243
302
|
>
|
|
244
303
|
: CollectionService<unknown> {
|
|
245
304
|
return super.collection(name) as K extends keyof LazypockCollections
|
|
246
305
|
? CollectionService<
|
|
247
306
|
LazypockCollections[K],
|
|
248
307
|
LazypockCreateData[K],
|
|
249
|
-
LazypockQueryFields[K]
|
|
308
|
+
LazypockQueryFields[K],
|
|
309
|
+
LazypockExpandMaps[K]
|
|
250
310
|
>
|
|
251
311
|
: CollectionService<unknown>;
|
|
252
312
|
}
|
package/src/collection.ts
CHANGED
|
@@ -128,8 +128,17 @@ function normalizeAction(
|
|
|
128
128
|
* Defaults to `T`. The codegen client binds a *QueryFields type that includes
|
|
129
129
|
* hidden fields — excluded from the read model but still expandable/filterable
|
|
130
130
|
* at runtime — so `expand: "hiddenRelation"` typechecks.
|
|
131
|
+
* @typeParam TExpand — Map of relation field → target record type. Defaults to
|
|
132
|
+
* `Record<string, unknown>`. The codegen client binds a per-collection
|
|
133
|
+
* *ExpandMap, so `record.expand.user` is typed as the target `UsersRecord`
|
|
134
|
+
* instead of `unknown`.
|
|
131
135
|
*/
|
|
132
|
-
export class CollectionService<
|
|
136
|
+
export class CollectionService<
|
|
137
|
+
T = ApiRecord,
|
|
138
|
+
TData = never,
|
|
139
|
+
TFields = T,
|
|
140
|
+
TExpand = Record<string, unknown>,
|
|
141
|
+
> {
|
|
133
142
|
private http: HttpClient;
|
|
134
143
|
private collectionName: string;
|
|
135
144
|
private authStore?: AuthStore;
|
|
@@ -189,9 +198,9 @@ export class CollectionService<T = ApiRecord, TData = never, TFields = T> {
|
|
|
189
198
|
*/
|
|
190
199
|
select<K extends FieldKey<TFields> | "*">(
|
|
191
200
|
...fields: K[]
|
|
192
|
-
): CollectionService<T, TData, TFields> {
|
|
201
|
+
): CollectionService<T, TData, TFields, TExpand> {
|
|
193
202
|
const preset = fields.length === 0 ? "*" : fields.join(",");
|
|
194
|
-
const derived = new CollectionService<T, TData, TFields>(
|
|
203
|
+
const derived = new CollectionService<T, TData, TFields, TExpand>(
|
|
195
204
|
this.http,
|
|
196
205
|
this.collectionName,
|
|
197
206
|
this.authStore,
|
|
@@ -302,7 +311,7 @@ export class CollectionService<T = ApiRecord, TData = never, TFields = T> {
|
|
|
302
311
|
page = 1,
|
|
303
312
|
perPage = 30,
|
|
304
313
|
options?: ListOptions<TFields, E, S, F> & RequestOptions,
|
|
305
|
-
): Promise<ListResult<T2 & { expand?: ExpandObj<E> }> | null> {
|
|
314
|
+
): Promise<ListResult<T2 & { expand?: ExpandObj<E, TExpand> }> | null> {
|
|
306
315
|
const {
|
|
307
316
|
requestKey,
|
|
308
317
|
autoCancel,
|
|
@@ -332,7 +341,7 @@ export class CollectionService<T = ApiRecord, TData = never, TFields = T> {
|
|
|
332
341
|
}).map(([k, v]) => [k, String(v)]),
|
|
333
342
|
),
|
|
334
343
|
).toString();
|
|
335
|
-
return this.http.get<ListResult<T2 & { expand?: ExpandObj<E> }>>(
|
|
344
|
+
return this.http.get<ListResult<T2 & { expand?: ExpandObj<E, TExpand> }>>(
|
|
336
345
|
"/" + this.encodeId(this.collectionName) + "?" + qs,
|
|
337
346
|
{
|
|
338
347
|
requestKey,
|
|
@@ -360,7 +369,7 @@ export class CollectionService<T = ApiRecord, TData = never, TFields = T> {
|
|
|
360
369
|
F extends string = never,
|
|
361
370
|
>(
|
|
362
371
|
options?: ListOptions<TFields, E, S, F> & RequestOptions,
|
|
363
|
-
): Promise<Array<T2 & { expand?: ExpandObj<E> }>> {
|
|
372
|
+
): Promise<Array<T2 & { expand?: ExpandObj<E, TExpand> }>> {
|
|
364
373
|
const { batch = 1000, ...rest } = options ?? {};
|
|
365
374
|
|
|
366
375
|
// Build a stable request key for the whole full-list fetch (NOT per page,
|
|
@@ -373,7 +382,7 @@ export class CollectionService<T = ApiRecord, TData = never, TFields = T> {
|
|
|
373
382
|
? rest.requestKey
|
|
374
383
|
: `getFullList:${this.collectionName}:${stableStringify(rest)}`;
|
|
375
384
|
|
|
376
|
-
const items: Array<T2 & { expand?: ExpandObj<E> }> = [];
|
|
385
|
+
const items: Array<T2 & { expand?: ExpandObj<E, TExpand> }> = [];
|
|
377
386
|
let page = 1;
|
|
378
387
|
for (;;) {
|
|
379
388
|
const res = await this.getList<T2>(
|
|
@@ -386,7 +395,7 @@ export class CollectionService<T = ApiRecord, TData = never, TFields = T> {
|
|
|
386
395
|
} as ListOptions<TFields> & RequestOptions,
|
|
387
396
|
);
|
|
388
397
|
if (!res || !res.items || res.items.length === 0) break;
|
|
389
|
-
items.push(...(res.items as Array<T2 & { expand?: ExpandObj<E> }>));
|
|
398
|
+
items.push(...(res.items as Array<T2 & { expand?: ExpandObj<E, TExpand> }>));
|
|
390
399
|
if (page >= (res.totalPages ?? page)) break;
|
|
391
400
|
page += 1;
|
|
392
401
|
}
|
|
@@ -407,7 +416,7 @@ export class CollectionService<T = ApiRecord, TData = never, TFields = T> {
|
|
|
407
416
|
>(
|
|
408
417
|
filter: FilterString<TFields, F>,
|
|
409
418
|
options?: ListOptions<TFields, E, S, F> & RequestOptions,
|
|
410
|
-
): Promise<(T2 & { expand?: ExpandObj<E> }) | null> {
|
|
419
|
+
): Promise<(T2 & { expand?: ExpandObj<E, TExpand> }) | null> {
|
|
411
420
|
const res = await this.getList<T2, E, S, F>(1, 1, {
|
|
412
421
|
...options,
|
|
413
422
|
filter,
|
|
@@ -423,7 +432,7 @@ export class CollectionService<T = ApiRecord, TData = never, TFields = T> {
|
|
|
423
432
|
getOne<E extends string = never>(
|
|
424
433
|
id: string,
|
|
425
434
|
options?: ReadOptions<TFields, E> & RequestOptions,
|
|
426
|
-
): Promise<(T & { expand?: ExpandObj<E> }) | null> {
|
|
435
|
+
): Promise<(T & { expand?: ExpandObj<E, TExpand> }) | null> {
|
|
427
436
|
const fields = this.effectiveFields(options?.fields);
|
|
428
437
|
if (typeof options?.expand === "string") {
|
|
429
438
|
this.validateExpand(options.expand);
|
|
@@ -432,7 +441,7 @@ export class CollectionService<T = ApiRecord, TData = never, TFields = T> {
|
|
|
432
441
|
if (fields !== undefined) qs.set("fields", fields);
|
|
433
442
|
if (typeof options?.expand === "string") qs.set("expand", options.expand);
|
|
434
443
|
const qsStr = qs.toString();
|
|
435
|
-
return this.http.get<T & { expand?: ExpandObj<E> }>(
|
|
444
|
+
return this.http.get<T & { expand?: ExpandObj<E, TExpand> }>(
|
|
436
445
|
"/" +
|
|
437
446
|
this.encodeId(this.collectionName) +
|
|
438
447
|
"/" +
|
|
@@ -550,8 +559,8 @@ export class CollectionService<T = ApiRecord, TData = never, TFields = T> {
|
|
|
550
559
|
* `types.schemas`). Enables schema-aware defaults: hidden fields are
|
|
551
560
|
* excluded from responses, select/expand are validated.
|
|
552
561
|
*/
|
|
553
|
-
withSchema(schema: CollectionSchema): CollectionService<T, TData, TFields> {
|
|
554
|
-
const derived = new CollectionService<T, TData, TFields>(
|
|
562
|
+
withSchema(schema: CollectionSchema): CollectionService<T, TData, TFields, TExpand> {
|
|
563
|
+
const derived = new CollectionService<T, TData, TFields, TExpand>(
|
|
555
564
|
this.http,
|
|
556
565
|
this.collectionName,
|
|
557
566
|
this.authStore,
|
package/src/types.ts
CHANGED
|
@@ -412,17 +412,27 @@ type TopExpandKey<S extends string> = S extends `${infer A}.${string}` ? A : S;
|
|
|
412
412
|
type ExpandKeys<S extends string> = S extends "" ? never :
|
|
413
413
|
S extends `${infer H},${infer T}` ? TopExpandKey<H> | ExpandKeys<T> : TopExpandKey<S>;
|
|
414
414
|
|
|
415
|
+
/**
|
|
416
|
+
* Value type of one expanded relation: the target record from the expand
|
|
417
|
+
* map (`TExpand`, e.g. the codegen-generated `PostsExpandMap`) when the key
|
|
418
|
+
* is a known relation, `unknown` otherwise.
|
|
419
|
+
*/
|
|
420
|
+
type ExpandValue<K extends string, TExpand> = K extends keyof TExpand ? TExpand[K] : unknown;
|
|
421
|
+
|
|
415
422
|
/**
|
|
416
423
|
* The `expand` property attached to records when `expand` is used. Keys are
|
|
417
424
|
* parsed from the caller's expand literal (dot-paths collapse to their first
|
|
418
|
-
* segment)
|
|
419
|
-
*
|
|
420
|
-
* `
|
|
425
|
+
* segment) and each value is typed from `TExpand` — a per-collection map of
|
|
426
|
+
* relation field → target record type (the codegen CLI emits these), so
|
|
427
|
+
* `record.expand.user` is `UsersRecord`, not `unknown`.
|
|
428
|
+
*
|
|
429
|
+
* Falls back to `Record<string, unknown>` when the expand value is dynamic
|
|
430
|
+
* or absent, or when no expand map is bound (hand-written services).
|
|
421
431
|
*/
|
|
422
|
-
export type ExpandObj<E extends string
|
|
432
|
+
export type ExpandObj<E extends string, TExpand = Record<string, unknown>> =
|
|
423
433
|
[E] extends [never] ? Record<string, unknown> :
|
|
424
434
|
[string] extends [E] ? Record<string, unknown> :
|
|
425
|
-
Partial<
|
|
435
|
+
Partial<{ [K in ExpandKeys<E>]: ExpandValue<K, TExpand> }>;
|
|
426
436
|
|
|
427
437
|
/**
|
|
428
438
|
* Query options for list/read operations, typed against a record shape `T`.
|
|
@@ -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\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":[]}
|