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/dist/index.d.cts CHANGED
@@ -231,18 +231,29 @@ type FilterString<T, F extends string = never> = F & (F extends ValidFilter<T, F
231
231
  type TopExpandKey<S extends string> = S extends `${infer A}.${string}` ? A : S;
232
232
  /** Top-level keys of a comma-separated expand literal (`"a,b"` → `"a" | "b"`). */
233
233
  type ExpandKeys<S extends string> = S extends "" ? never : S extends `${infer H},${infer T}` ? TopExpandKey<H> | ExpandKeys<T> : TopExpandKey<S>;
234
+ /**
235
+ * Value type of one expanded relation: the target record from the expand
236
+ * map (`TExpand`, e.g. the codegen-generated `PostsExpandMap`) when the key
237
+ * is a known relation, `unknown` otherwise.
238
+ */
239
+ type ExpandValue<K extends string, TExpand> = K extends keyof TExpand ? TExpand[K] : unknown;
234
240
  /**
235
241
  * The `expand` property attached to records when `expand` is used. Keys are
236
242
  * parsed from the caller's expand literal (dot-paths collapse to their first
237
- * segment), so `record.expand.user` typechecks and the nested record is
238
- * `unknown` (its shape belongs to the target collection). Falls back to
239
- * `Record<string, unknown>` when the expand value is dynamic or absent.
243
+ * segment) and each value is typed from `TExpand` — a per-collection map of
244
+ * relation field → target record type (the codegen CLI emits these), so
245
+ * `record.expand.user` is `UsersRecord`, not `unknown`.
246
+ *
247
+ * Falls back to `Record<string, unknown>` when the expand value is dynamic
248
+ * or absent, or when no expand map is bound (hand-written services).
240
249
  */
241
- type ExpandObj<E extends string> = [
250
+ type ExpandObj<E extends string, TExpand = Record<string, unknown>> = [
242
251
  E
243
252
  ] extends [never] ? Record<string, unknown> : [
244
253
  string
245
- ] extends [E] ? Record<string, unknown> : Partial<Record<ExpandKeys<E>, unknown>>;
254
+ ] extends [E] ? Record<string, unknown> : Partial<{
255
+ [K in ExpandKeys<E>]: ExpandValue<K, TExpand>;
256
+ }>;
246
257
  /**
247
258
  * Query options for list/read operations, typed against a record shape `T`.
248
259
  *
@@ -640,8 +651,12 @@ interface RealtimeSubscribeOptions {
640
651
  * Defaults to `T`. The codegen client binds a *QueryFields type that includes
641
652
  * hidden fields — excluded from the read model but still expandable/filterable
642
653
  * at runtime — so `expand: "hiddenRelation"` typechecks.
654
+ * @typeParam TExpand — Map of relation field → target record type. Defaults to
655
+ * `Record<string, unknown>`. The codegen client binds a per-collection
656
+ * *ExpandMap, so `record.expand.user` is typed as the target `UsersRecord`
657
+ * instead of `unknown`.
643
658
  */
644
- declare class CollectionService<T = ApiRecord, TData = never, TFields = T> {
659
+ declare class CollectionService<T = ApiRecord, TData = never, TFields = T, TExpand = Record<string, unknown>> {
645
660
  private http;
646
661
  private collectionName;
647
662
  private authStore?;
@@ -680,7 +695,7 @@ declare class CollectionService<T = ApiRecord, TData = never, TFields = T> {
680
695
  * `fields` param — the server returns every non-password field.
681
696
  * Pass `select("*")` to restore the default after a projection.
682
697
  */
683
- select<K extends FieldKey<TFields> | "*">(...fields: K[]): CollectionService<T, TData, TFields>;
698
+ select<K extends FieldKey<TFields> | "*">(...fields: K[]): CollectionService<T, TData, TFields, TExpand>;
684
699
  /**
685
700
  * Fields to send with reads:
686
701
  * 1. explicit `options.fields` (caller wins)
@@ -714,7 +729,7 @@ declare class CollectionService<T = ApiRecord, TData = never, TFields = T> {
714
729
  * @param options Query params (`filter`, `sort`, `expand`, `fields`) + request options.
715
730
  */
716
731
  getList<T2 = T, E extends string = never, S extends string = never, F extends string = never>(page?: number, perPage?: number, options?: ListOptions<TFields, E, S, F> & RequestOptions): Promise<ListResult<T2 & {
717
- expand?: ExpandObj<E>;
732
+ expand?: ExpandObj<E, TExpand>;
718
733
  }> | null>;
719
734
  /**
720
735
  * Fetch all records at once (auto-paginates). Mirrors PocketBase's
@@ -723,7 +738,7 @@ declare class CollectionService<T = ApiRecord, TData = never, TFields = T> {
723
738
  * @param options Query params (`sort`, `filter`, `batch`, etc.) + request options.
724
739
  */
725
740
  getFullList<T2 = T, E extends string = never, S extends string = never, F extends string = never>(options?: ListOptions<TFields, E, S, F> & RequestOptions): Promise<Array<T2 & {
726
- expand?: ExpandObj<E>;
741
+ expand?: ExpandObj<E, TExpand>;
727
742
  }>>;
728
743
  /**
729
744
  * Fetch the first record matching a filter (PocketBase `getFirstListItem`).
@@ -732,7 +747,7 @@ declare class CollectionService<T = ApiRecord, TData = never, TFields = T> {
732
747
  * @param options Optional request options.
733
748
  */
734
749
  getFirstListItem<T2 = T, F extends string = never, E extends string = never, S extends string = never>(filter: FilterString<TFields, F>, options?: ListOptions<TFields, E, S, F> & RequestOptions): Promise<(T2 & {
735
- expand?: ExpandObj<E>;
750
+ expand?: ExpandObj<E, TExpand>;
736
751
  }) | null>;
737
752
  /**
738
753
  * Get a single record by ID.
@@ -740,7 +755,7 @@ declare class CollectionService<T = ApiRecord, TData = never, TFields = T> {
740
755
  * @param options Optional request options.
741
756
  */
742
757
  getOne<E extends string = never>(id: string, options?: ReadOptions<TFields, E> & RequestOptions): Promise<(T & {
743
- expand?: ExpandObj<E>;
758
+ expand?: ExpandObj<E, TExpand>;
744
759
  }) | null>;
745
760
  /**
746
761
  * Create a new record.
@@ -794,7 +809,7 @@ declare class CollectionService<T = ApiRecord, TData = never, TFields = T> {
794
809
  * `types.schemas`). Enables schema-aware defaults: hidden fields are
795
810
  * excluded from responses, select/expand are validated.
796
811
  */
797
- withSchema(schema: CollectionSchema): CollectionService<T, TData, TFields>;
812
+ withSchema(schema: CollectionSchema): CollectionService<T, TData, TFields, TExpand>;
798
813
  /**
799
814
  * Subscribe to realtime changes for this collection.
800
815
  * The event's `action` is one of `"create" | "update" | "delete"` and the
package/dist/index.d.ts CHANGED
@@ -231,18 +231,29 @@ type FilterString<T, F extends string = never> = F & (F extends ValidFilter<T, F
231
231
  type TopExpandKey<S extends string> = S extends `${infer A}.${string}` ? A : S;
232
232
  /** Top-level keys of a comma-separated expand literal (`"a,b"` → `"a" | "b"`). */
233
233
  type ExpandKeys<S extends string> = S extends "" ? never : S extends `${infer H},${infer T}` ? TopExpandKey<H> | ExpandKeys<T> : TopExpandKey<S>;
234
+ /**
235
+ * Value type of one expanded relation: the target record from the expand
236
+ * map (`TExpand`, e.g. the codegen-generated `PostsExpandMap`) when the key
237
+ * is a known relation, `unknown` otherwise.
238
+ */
239
+ type ExpandValue<K extends string, TExpand> = K extends keyof TExpand ? TExpand[K] : unknown;
234
240
  /**
235
241
  * The `expand` property attached to records when `expand` is used. Keys are
236
242
  * parsed from the caller's expand literal (dot-paths collapse to their first
237
- * segment), so `record.expand.user` typechecks and the nested record is
238
- * `unknown` (its shape belongs to the target collection). Falls back to
239
- * `Record<string, unknown>` when the expand value is dynamic or absent.
243
+ * segment) and each value is typed from `TExpand` — a per-collection map of
244
+ * relation field → target record type (the codegen CLI emits these), so
245
+ * `record.expand.user` is `UsersRecord`, not `unknown`.
246
+ *
247
+ * Falls back to `Record<string, unknown>` when the expand value is dynamic
248
+ * or absent, or when no expand map is bound (hand-written services).
240
249
  */
241
- type ExpandObj<E extends string> = [
250
+ type ExpandObj<E extends string, TExpand = Record<string, unknown>> = [
242
251
  E
243
252
  ] extends [never] ? Record<string, unknown> : [
244
253
  string
245
- ] extends [E] ? Record<string, unknown> : Partial<Record<ExpandKeys<E>, unknown>>;
254
+ ] extends [E] ? Record<string, unknown> : Partial<{
255
+ [K in ExpandKeys<E>]: ExpandValue<K, TExpand>;
256
+ }>;
246
257
  /**
247
258
  * Query options for list/read operations, typed against a record shape `T`.
248
259
  *
@@ -640,8 +651,12 @@ interface RealtimeSubscribeOptions {
640
651
  * Defaults to `T`. The codegen client binds a *QueryFields type that includes
641
652
  * hidden fields — excluded from the read model but still expandable/filterable
642
653
  * at runtime — so `expand: "hiddenRelation"` typechecks.
654
+ * @typeParam TExpand — Map of relation field → target record type. Defaults to
655
+ * `Record<string, unknown>`. The codegen client binds a per-collection
656
+ * *ExpandMap, so `record.expand.user` is typed as the target `UsersRecord`
657
+ * instead of `unknown`.
643
658
  */
644
- declare class CollectionService<T = ApiRecord, TData = never, TFields = T> {
659
+ declare class CollectionService<T = ApiRecord, TData = never, TFields = T, TExpand = Record<string, unknown>> {
645
660
  private http;
646
661
  private collectionName;
647
662
  private authStore?;
@@ -680,7 +695,7 @@ declare class CollectionService<T = ApiRecord, TData = never, TFields = T> {
680
695
  * `fields` param — the server returns every non-password field.
681
696
  * Pass `select("*")` to restore the default after a projection.
682
697
  */
683
- select<K extends FieldKey<TFields> | "*">(...fields: K[]): CollectionService<T, TData, TFields>;
698
+ select<K extends FieldKey<TFields> | "*">(...fields: K[]): CollectionService<T, TData, TFields, TExpand>;
684
699
  /**
685
700
  * Fields to send with reads:
686
701
  * 1. explicit `options.fields` (caller wins)
@@ -714,7 +729,7 @@ declare class CollectionService<T = ApiRecord, TData = never, TFields = T> {
714
729
  * @param options Query params (`filter`, `sort`, `expand`, `fields`) + request options.
715
730
  */
716
731
  getList<T2 = T, E extends string = never, S extends string = never, F extends string = never>(page?: number, perPage?: number, options?: ListOptions<TFields, E, S, F> & RequestOptions): Promise<ListResult<T2 & {
717
- expand?: ExpandObj<E>;
732
+ expand?: ExpandObj<E, TExpand>;
718
733
  }> | null>;
719
734
  /**
720
735
  * Fetch all records at once (auto-paginates). Mirrors PocketBase's
@@ -723,7 +738,7 @@ declare class CollectionService<T = ApiRecord, TData = never, TFields = T> {
723
738
  * @param options Query params (`sort`, `filter`, `batch`, etc.) + request options.
724
739
  */
725
740
  getFullList<T2 = T, E extends string = never, S extends string = never, F extends string = never>(options?: ListOptions<TFields, E, S, F> & RequestOptions): Promise<Array<T2 & {
726
- expand?: ExpandObj<E>;
741
+ expand?: ExpandObj<E, TExpand>;
727
742
  }>>;
728
743
  /**
729
744
  * Fetch the first record matching a filter (PocketBase `getFirstListItem`).
@@ -732,7 +747,7 @@ declare class CollectionService<T = ApiRecord, TData = never, TFields = T> {
732
747
  * @param options Optional request options.
733
748
  */
734
749
  getFirstListItem<T2 = T, F extends string = never, E extends string = never, S extends string = never>(filter: FilterString<TFields, F>, options?: ListOptions<TFields, E, S, F> & RequestOptions): Promise<(T2 & {
735
- expand?: ExpandObj<E>;
750
+ expand?: ExpandObj<E, TExpand>;
736
751
  }) | null>;
737
752
  /**
738
753
  * Get a single record by ID.
@@ -740,7 +755,7 @@ declare class CollectionService<T = ApiRecord, TData = never, TFields = T> {
740
755
  * @param options Optional request options.
741
756
  */
742
757
  getOne<E extends string = never>(id: string, options?: ReadOptions<TFields, E> & RequestOptions): Promise<(T & {
743
- expand?: ExpandObj<E>;
758
+ expand?: ExpandObj<E, TExpand>;
744
759
  }) | null>;
745
760
  /**
746
761
  * Create a new record.
@@ -794,7 +809,7 @@ declare class CollectionService<T = ApiRecord, TData = never, TFields = T> {
794
809
  * `types.schemas`). Enables schema-aware defaults: hidden fields are
795
810
  * excluded from responses, select/expand are validated.
796
811
  */
797
- withSchema(schema: CollectionSchema): CollectionService<T, TData, TFields>;
812
+ withSchema(schema: CollectionSchema): CollectionService<T, TData, TFields, TExpand>;
798
813
  /**
799
814
  * Subscribe to realtime changes for this collection.
800
815
  * The event's `action` is one of `"create" | "update" | "delete"` and the
@@ -1553,6 +1553,9 @@ var Lazypock = (() => {
1553
1553
  updated: string;
1554
1554
  }`);
1555
1555
  }
1556
+ const collById = new Map(
1557
+ filtered.filter((c) => c.id !== void 0 && c.id !== "").map((c) => [String(c.id), c])
1558
+ );
1556
1559
  for (const coll of filtered) {
1557
1560
  const typeName = collectionTypeName(coll.name);
1558
1561
  const fields = coll.fields ?? [];
@@ -1592,6 +1595,24 @@ var Lazypock = (() => {
1592
1595
  body: queryLines.join("\n")
1593
1596
  })}`
1594
1597
  );
1598
+ const expandLines = [];
1599
+ for (const f of fields) {
1600
+ if (f.type !== "relation") continue;
1601
+ const target = collById.get(String(f.options?.collectionId ?? "")) ?? filtered.find(
1602
+ (c) => c.name === String(f.options?.collection ?? "")
1603
+ );
1604
+ if (!target) continue;
1605
+ const targetType = `${collectionTypeName(target.name)}Record${target.type === "auth" ? " & AuthRecord" : ""}`;
1606
+ const many = (f.options?.maxSelect ?? 1) > 1;
1607
+ expandLines.push(
1608
+ ` ${JSON.stringify(fieldKey(f.name))}: ${many ? `Array<${targetType}>` : targetType};`
1609
+ );
1610
+ }
1611
+ sections.push(
1612
+ `export interface ${typeName}ExpandMap${renderInterface({
1613
+ body: expandLines.join("\n")
1614
+ })}`
1615
+ );
1595
1616
  }
1596
1617
  sections.push(`export interface AuthRecord extends BaseRecord {
1597
1618
  email: string;
@@ -1612,6 +1633,10 @@ ${createMapEntries}
1612
1633
  ).join("\n");
1613
1634
  sections.push(`export interface LazypockQueryFields {
1614
1635
  ${queryMapEntries}
1636
+ }`);
1637
+ const expandMapEntries = filtered.map((c) => ` "${c.name}": ${collectionTypeName(c.name)}ExpandMap;`).join("\n");
1638
+ sections.push(`export interface LazypockExpandMaps {
1639
+ ${expandMapEntries}
1615
1640
  }`);
1616
1641
  const schemaEntries = collections.map(
1617
1642
  (c) => ` {
@@ -1665,25 +1690,31 @@ export class TypedClient extends LazypockClient {
1665
1690
  // The (string & {}) intersection keeps the union from collapsing to plain
1666
1691
  // string (which would silently kill the suggestions).
1667
1692
  //
1668
- // The service binds three types: the read model, the write-only *CreateData,
1669
- // and the *QueryFields (filter/sort/expand/select keys). QueryFields is what
1670
- // lets hidden relation fields \u2014 excluded from the read model but still
1671
- // expandable/filterable at runtime \u2014 typecheck:
1693
+ // The service binds four types: the read model, the write-only *CreateData,
1694
+ // the *QueryFields (filter/sort/expand/select keys), and the *ExpandMap
1695
+ // (relation field \u2192 target record type). QueryFields is what lets hidden
1696
+ // relation fields \u2014 excluded from the read model but still expandable/
1697
+ // filterable at runtime \u2014 typecheck:
1672
1698
  // client.collection("project_members").getFullList({ expand: "user" }) // \u2713
1699
+ // ExpandMap is what types the expanded data on the result:
1700
+ // const [rec] = await client.collection("posts").getFullList({ expand: "user" });
1701
+ // rec.expand?.user // \u2713 UsersRecord, not unknown
1673
1702
  override collection<K extends keyof LazypockCollections | (string & {})>(
1674
1703
  name: K,
1675
1704
  ): K extends keyof LazypockCollections
1676
1705
  ? CollectionService<
1677
1706
  LazypockCollections[K],
1678
1707
  LazypockCreateData[K],
1679
- LazypockQueryFields[K]
1708
+ LazypockQueryFields[K],
1709
+ LazypockExpandMaps[K]
1680
1710
  >
1681
1711
  : CollectionService<unknown> {
1682
1712
  return super.collection(name) as K extends keyof LazypockCollections
1683
1713
  ? CollectionService<
1684
1714
  LazypockCollections[K],
1685
1715
  LazypockCreateData[K],
1686
- LazypockQueryFields[K]
1716
+ LazypockQueryFields[K],
1717
+ LazypockExpandMaps[K]
1687
1718
  >
1688
1719
  : CollectionService<unknown>;
1689
1720
  }