lazypock 0.10.2 → 0.12.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
@@ -101,8 +101,14 @@ declare class ApiError extends Error {
101
101
  * untyped services accept any field name.
102
102
  */
103
103
  type FieldKey<T> = Extract<keyof T, string> extends never ? string : Extract<keyof T, string>;
104
- /** Valid filter operators, matching the backend FilterCompiler. */
105
- type FilterOp = "=" | "!=" | "~" | "!~" | ">" | ">=" | "<" | "<=";
104
+ /**
105
+ * Valid filter operators, matching the backend FilterCompiler.
106
+ *
107
+ * The `?`-prefixed operators are PocketBase's "any/at least one of"
108
+ * conditions over array-valued fields (multi-select, multiple relation,
109
+ * multiple file). They compile to `= ANY` / `ILIKE ANY` SQL in the backend.
110
+ */
111
+ type FilterOp = "=" | "!=" | "~" | "!~" | ">" | ">=" | "<" | "<=" | "?=" | "?!=" | "?~" | "?!~" | "?>" | "?>=" | "?<" | "?<=";
106
112
  /** Optional whitespace around a filter operator (`project=x` and `project = x` both compile). */
107
113
  type FilterWs = "" | " ";
108
114
  /** Trim one-or-more leading/trailing spaces off a string literal. */
@@ -231,18 +237,29 @@ type FilterString<T, F extends string = never> = F & (F extends ValidFilter<T, F
231
237
  type TopExpandKey<S extends string> = S extends `${infer A}.${string}` ? A : S;
232
238
  /** Top-level keys of a comma-separated expand literal (`"a,b"` → `"a" | "b"`). */
233
239
  type ExpandKeys<S extends string> = S extends "" ? never : S extends `${infer H},${infer T}` ? TopExpandKey<H> | ExpandKeys<T> : TopExpandKey<S>;
240
+ /**
241
+ * Value type of one expanded relation: the target record from the expand
242
+ * map (`TExpand`, e.g. the codegen-generated `PostsExpandMap`) when the key
243
+ * is a known relation, `unknown` otherwise.
244
+ */
245
+ type ExpandValue<K extends string, TExpand> = K extends keyof TExpand ? TExpand[K] : unknown;
234
246
  /**
235
247
  * The `expand` property attached to records when `expand` is used. Keys are
236
248
  * 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.
249
+ * segment) and each value is typed from `TExpand` — a per-collection map of
250
+ * relation field → target record type (the codegen CLI emits these), so
251
+ * `record.expand.user` is `UsersRecord`, not `unknown`.
252
+ *
253
+ * Falls back to `Record<string, unknown>` when the expand value is dynamic
254
+ * or absent, or when no expand map is bound (hand-written services).
240
255
  */
241
- type ExpandObj<E extends string> = [
256
+ type ExpandObj<E extends string, TExpand = Record<string, unknown>> = [
242
257
  E
243
258
  ] extends [never] ? Record<string, unknown> : [
244
259
  string
245
- ] extends [E] ? Record<string, unknown> : Partial<Record<ExpandKeys<E>, unknown>>;
260
+ ] extends [E] ? Record<string, unknown> : Partial<{
261
+ [K in ExpandKeys<E>]: ExpandValue<K, TExpand>;
262
+ }>;
246
263
  /**
247
264
  * Query options for list/read operations, typed against a record shape `T`.
248
265
  *
@@ -640,8 +657,12 @@ interface RealtimeSubscribeOptions {
640
657
  * Defaults to `T`. The codegen client binds a *QueryFields type that includes
641
658
  * hidden fields — excluded from the read model but still expandable/filterable
642
659
  * at runtime — so `expand: "hiddenRelation"` typechecks.
660
+ * @typeParam TExpand — Map of relation field → target record type. Defaults to
661
+ * `Record<string, unknown>`. The codegen client binds a per-collection
662
+ * *ExpandMap, so `record.expand.user` is typed as the target `UsersRecord`
663
+ * instead of `unknown`.
643
664
  */
644
- declare class CollectionService<T = ApiRecord, TData = never, TFields = T> {
665
+ declare class CollectionService<T = ApiRecord, TData = never, TFields = T, TExpand = Record<string, unknown>> {
645
666
  private http;
646
667
  private collectionName;
647
668
  private authStore?;
@@ -680,7 +701,7 @@ declare class CollectionService<T = ApiRecord, TData = never, TFields = T> {
680
701
  * `fields` param — the server returns every non-password field.
681
702
  * Pass `select("*")` to restore the default after a projection.
682
703
  */
683
- select<K extends FieldKey<TFields> | "*">(...fields: K[]): CollectionService<T, TData, TFields>;
704
+ select<K extends FieldKey<TFields> | "*">(...fields: K[]): CollectionService<T, TData, TFields, TExpand>;
684
705
  /**
685
706
  * Fields to send with reads:
686
707
  * 1. explicit `options.fields` (caller wins)
@@ -714,7 +735,7 @@ declare class CollectionService<T = ApiRecord, TData = never, TFields = T> {
714
735
  * @param options Query params (`filter`, `sort`, `expand`, `fields`) + request options.
715
736
  */
716
737
  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>;
738
+ expand?: ExpandObj<E, TExpand>;
718
739
  }> | null>;
719
740
  /**
720
741
  * Fetch all records at once (auto-paginates). Mirrors PocketBase's
@@ -723,7 +744,7 @@ declare class CollectionService<T = ApiRecord, TData = never, TFields = T> {
723
744
  * @param options Query params (`sort`, `filter`, `batch`, etc.) + request options.
724
745
  */
725
746
  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>;
747
+ expand?: ExpandObj<E, TExpand>;
727
748
  }>>;
728
749
  /**
729
750
  * Fetch the first record matching a filter (PocketBase `getFirstListItem`).
@@ -732,7 +753,7 @@ declare class CollectionService<T = ApiRecord, TData = never, TFields = T> {
732
753
  * @param options Optional request options.
733
754
  */
734
755
  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>;
756
+ expand?: ExpandObj<E, TExpand>;
736
757
  }) | null>;
737
758
  /**
738
759
  * Get a single record by ID.
@@ -740,7 +761,7 @@ declare class CollectionService<T = ApiRecord, TData = never, TFields = T> {
740
761
  * @param options Optional request options.
741
762
  */
742
763
  getOne<E extends string = never>(id: string, options?: ReadOptions<TFields, E> & RequestOptions): Promise<(T & {
743
- expand?: ExpandObj<E>;
764
+ expand?: ExpandObj<E, TExpand>;
744
765
  }) | null>;
745
766
  /**
746
767
  * Create a new record.
@@ -794,7 +815,7 @@ declare class CollectionService<T = ApiRecord, TData = never, TFields = T> {
794
815
  * `types.schemas`). Enables schema-aware defaults: hidden fields are
795
816
  * excluded from responses, select/expand are validated.
796
817
  */
797
- withSchema(schema: CollectionSchema): CollectionService<T, TData, TFields>;
818
+ withSchema(schema: CollectionSchema): CollectionService<T, TData, TFields, TExpand>;
798
819
  /**
799
820
  * Subscribe to realtime changes for this collection.
800
821
  * The event's `action` is one of `"create" | "update" | "delete"` and the
package/dist/index.d.ts CHANGED
@@ -101,8 +101,14 @@ declare class ApiError extends Error {
101
101
  * untyped services accept any field name.
102
102
  */
103
103
  type FieldKey<T> = Extract<keyof T, string> extends never ? string : Extract<keyof T, string>;
104
- /** Valid filter operators, matching the backend FilterCompiler. */
105
- type FilterOp = "=" | "!=" | "~" | "!~" | ">" | ">=" | "<" | "<=";
104
+ /**
105
+ * Valid filter operators, matching the backend FilterCompiler.
106
+ *
107
+ * The `?`-prefixed operators are PocketBase's "any/at least one of"
108
+ * conditions over array-valued fields (multi-select, multiple relation,
109
+ * multiple file). They compile to `= ANY` / `ILIKE ANY` SQL in the backend.
110
+ */
111
+ type FilterOp = "=" | "!=" | "~" | "!~" | ">" | ">=" | "<" | "<=" | "?=" | "?!=" | "?~" | "?!~" | "?>" | "?>=" | "?<" | "?<=";
106
112
  /** Optional whitespace around a filter operator (`project=x` and `project = x` both compile). */
107
113
  type FilterWs = "" | " ";
108
114
  /** Trim one-or-more leading/trailing spaces off a string literal. */
@@ -231,18 +237,29 @@ type FilterString<T, F extends string = never> = F & (F extends ValidFilter<T, F
231
237
  type TopExpandKey<S extends string> = S extends `${infer A}.${string}` ? A : S;
232
238
  /** Top-level keys of a comma-separated expand literal (`"a,b"` → `"a" | "b"`). */
233
239
  type ExpandKeys<S extends string> = S extends "" ? never : S extends `${infer H},${infer T}` ? TopExpandKey<H> | ExpandKeys<T> : TopExpandKey<S>;
240
+ /**
241
+ * Value type of one expanded relation: the target record from the expand
242
+ * map (`TExpand`, e.g. the codegen-generated `PostsExpandMap`) when the key
243
+ * is a known relation, `unknown` otherwise.
244
+ */
245
+ type ExpandValue<K extends string, TExpand> = K extends keyof TExpand ? TExpand[K] : unknown;
234
246
  /**
235
247
  * The `expand` property attached to records when `expand` is used. Keys are
236
248
  * 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.
249
+ * segment) and each value is typed from `TExpand` — a per-collection map of
250
+ * relation field → target record type (the codegen CLI emits these), so
251
+ * `record.expand.user` is `UsersRecord`, not `unknown`.
252
+ *
253
+ * Falls back to `Record<string, unknown>` when the expand value is dynamic
254
+ * or absent, or when no expand map is bound (hand-written services).
240
255
  */
241
- type ExpandObj<E extends string> = [
256
+ type ExpandObj<E extends string, TExpand = Record<string, unknown>> = [
242
257
  E
243
258
  ] extends [never] ? Record<string, unknown> : [
244
259
  string
245
- ] extends [E] ? Record<string, unknown> : Partial<Record<ExpandKeys<E>, unknown>>;
260
+ ] extends [E] ? Record<string, unknown> : Partial<{
261
+ [K in ExpandKeys<E>]: ExpandValue<K, TExpand>;
262
+ }>;
246
263
  /**
247
264
  * Query options for list/read operations, typed against a record shape `T`.
248
265
  *
@@ -640,8 +657,12 @@ interface RealtimeSubscribeOptions {
640
657
  * Defaults to `T`. The codegen client binds a *QueryFields type that includes
641
658
  * hidden fields — excluded from the read model but still expandable/filterable
642
659
  * at runtime — so `expand: "hiddenRelation"` typechecks.
660
+ * @typeParam TExpand — Map of relation field → target record type. Defaults to
661
+ * `Record<string, unknown>`. The codegen client binds a per-collection
662
+ * *ExpandMap, so `record.expand.user` is typed as the target `UsersRecord`
663
+ * instead of `unknown`.
643
664
  */
644
- declare class CollectionService<T = ApiRecord, TData = never, TFields = T> {
665
+ declare class CollectionService<T = ApiRecord, TData = never, TFields = T, TExpand = Record<string, unknown>> {
645
666
  private http;
646
667
  private collectionName;
647
668
  private authStore?;
@@ -680,7 +701,7 @@ declare class CollectionService<T = ApiRecord, TData = never, TFields = T> {
680
701
  * `fields` param — the server returns every non-password field.
681
702
  * Pass `select("*")` to restore the default after a projection.
682
703
  */
683
- select<K extends FieldKey<TFields> | "*">(...fields: K[]): CollectionService<T, TData, TFields>;
704
+ select<K extends FieldKey<TFields> | "*">(...fields: K[]): CollectionService<T, TData, TFields, TExpand>;
684
705
  /**
685
706
  * Fields to send with reads:
686
707
  * 1. explicit `options.fields` (caller wins)
@@ -714,7 +735,7 @@ declare class CollectionService<T = ApiRecord, TData = never, TFields = T> {
714
735
  * @param options Query params (`filter`, `sort`, `expand`, `fields`) + request options.
715
736
  */
716
737
  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>;
738
+ expand?: ExpandObj<E, TExpand>;
718
739
  }> | null>;
719
740
  /**
720
741
  * Fetch all records at once (auto-paginates). Mirrors PocketBase's
@@ -723,7 +744,7 @@ declare class CollectionService<T = ApiRecord, TData = never, TFields = T> {
723
744
  * @param options Query params (`sort`, `filter`, `batch`, etc.) + request options.
724
745
  */
725
746
  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>;
747
+ expand?: ExpandObj<E, TExpand>;
727
748
  }>>;
728
749
  /**
729
750
  * Fetch the first record matching a filter (PocketBase `getFirstListItem`).
@@ -732,7 +753,7 @@ declare class CollectionService<T = ApiRecord, TData = never, TFields = T> {
732
753
  * @param options Optional request options.
733
754
  */
734
755
  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>;
756
+ expand?: ExpandObj<E, TExpand>;
736
757
  }) | null>;
737
758
  /**
738
759
  * Get a single record by ID.
@@ -740,7 +761,7 @@ declare class CollectionService<T = ApiRecord, TData = never, TFields = T> {
740
761
  * @param options Optional request options.
741
762
  */
742
763
  getOne<E extends string = never>(id: string, options?: ReadOptions<TFields, E> & RequestOptions): Promise<(T & {
743
- expand?: ExpandObj<E>;
764
+ expand?: ExpandObj<E, TExpand>;
744
765
  }) | null>;
745
766
  /**
746
767
  * Create a new record.
@@ -794,7 +815,7 @@ declare class CollectionService<T = ApiRecord, TData = never, TFields = T> {
794
815
  * `types.schemas`). Enables schema-aware defaults: hidden fields are
795
816
  * excluded from responses, select/expand are validated.
796
817
  */
797
- withSchema(schema: CollectionSchema): CollectionService<T, TData, TFields>;
818
+ withSchema(schema: CollectionSchema): CollectionService<T, TData, TFields, TExpand>;
798
819
  /**
799
820
  * Subscribe to realtime changes for this collection.
800
821
  * The event's `action` is one of `"create" | "update" | "delete"` and the
@@ -1437,6 +1437,7 @@ var Lazypock = (() => {
1437
1437
  case "editor":
1438
1438
  case "date":
1439
1439
  case "datetime":
1440
+ case "autodate":
1440
1441
  return "string";
1441
1442
  case "number":
1442
1443
  return "number";
@@ -1480,6 +1481,7 @@ var Lazypock = (() => {
1480
1481
  case "editor":
1481
1482
  case "date":
1482
1483
  case "datetime":
1484
+ case "autodate":
1483
1485
  case "select":
1484
1486
  case "file":
1485
1487
  return "string";
@@ -1525,6 +1527,13 @@ var Lazypock = (() => {
1525
1527
  }
1526
1528
 
1527
1529
  // src/codegen.ts
1530
+ var BASE_RECORD_KEYS = /* @__PURE__ */ new Set([
1531
+ "id",
1532
+ "collectionId",
1533
+ "collectionName",
1534
+ "created",
1535
+ "updated"
1536
+ ]);
1528
1537
  function collectionTypeName(name) {
1529
1538
  return name.split(/[^a-zA-Z0-9]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("").replace(/^[0-9]/, "_$&");
1530
1539
  }
@@ -1553,10 +1562,13 @@ var Lazypock = (() => {
1553
1562
  updated: string;
1554
1563
  }`);
1555
1564
  }
1565
+ const collById = new Map(
1566
+ filtered.filter((c) => c.id !== void 0 && c.id !== "").map((c) => [String(c.id), c])
1567
+ );
1556
1568
  for (const coll of filtered) {
1557
1569
  const typeName = collectionTypeName(coll.name);
1558
1570
  const fields = coll.fields ?? [];
1559
- const lines = fields.map((f) => memberLine(f)).filter((l) => l !== "");
1571
+ const lines = fields.map((f) => memberLine(f, includeBaseFields)).filter((l) => l !== "");
1560
1572
  const body = lines.join("\n");
1561
1573
  sections.push(
1562
1574
  `export interface ${typeName}Record${renderInterface({
@@ -1582,6 +1594,7 @@ var Lazypock = (() => {
1582
1594
  for (const f of fields) {
1583
1595
  if (f.type === "password") continue;
1584
1596
  const key = fieldKey(f.name);
1597
+ if (includeBaseFields && BASE_RECORD_KEYS.has(key)) continue;
1585
1598
  const type = fieldTypeScriptType(f);
1586
1599
  if (type === "never") continue;
1587
1600
  queryLines.push(` ${JSON.stringify(key)}?: ${type};`);
@@ -1592,6 +1605,24 @@ var Lazypock = (() => {
1592
1605
  body: queryLines.join("\n")
1593
1606
  })}`
1594
1607
  );
1608
+ const expandLines = [];
1609
+ for (const f of fields) {
1610
+ if (f.type !== "relation") continue;
1611
+ const target = collById.get(String(f.options?.collectionId ?? "")) ?? filtered.find(
1612
+ (c) => c.name === String(f.options?.collection ?? "")
1613
+ );
1614
+ if (!target) continue;
1615
+ const targetType = `${collectionTypeName(target.name)}Record${target.type === "auth" ? " & AuthRecord" : ""}`;
1616
+ const many = (f.options?.maxSelect ?? 1) > 1;
1617
+ expandLines.push(
1618
+ ` ${JSON.stringify(fieldKey(f.name))}: ${many ? `Array<${targetType}>` : targetType};`
1619
+ );
1620
+ }
1621
+ sections.push(
1622
+ `export interface ${typeName}ExpandMap${renderInterface({
1623
+ body: expandLines.join("\n")
1624
+ })}`
1625
+ );
1595
1626
  }
1596
1627
  sections.push(`export interface AuthRecord extends BaseRecord {
1597
1628
  email: string;
@@ -1612,6 +1643,10 @@ ${createMapEntries}
1612
1643
  ).join("\n");
1613
1644
  sections.push(`export interface LazypockQueryFields {
1614
1645
  ${queryMapEntries}
1646
+ }`);
1647
+ const expandMapEntries = filtered.map((c) => ` "${c.name}": ${collectionTypeName(c.name)}ExpandMap;`).join("\n");
1648
+ sections.push(`export interface LazypockExpandMaps {
1649
+ ${expandMapEntries}
1615
1650
  }`);
1616
1651
  const schemaEntries = collections.map(
1617
1652
  (c) => ` {
@@ -1665,25 +1700,31 @@ export class TypedClient extends LazypockClient {
1665
1700
  // The (string & {}) intersection keeps the union from collapsing to plain
1666
1701
  // string (which would silently kill the suggestions).
1667
1702
  //
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:
1703
+ // The service binds four types: the read model, the write-only *CreateData,
1704
+ // the *QueryFields (filter/sort/expand/select keys), and the *ExpandMap
1705
+ // (relation field \u2192 target record type). QueryFields is what lets hidden
1706
+ // relation fields \u2014 excluded from the read model but still expandable/
1707
+ // filterable at runtime \u2014 typecheck:
1672
1708
  // client.collection("project_members").getFullList({ expand: "user" }) // \u2713
1709
+ // ExpandMap is what types the expanded data on the result:
1710
+ // const [rec] = await client.collection("posts").getFullList({ expand: "user" });
1711
+ // rec.expand?.user // \u2713 UsersRecord, not unknown
1673
1712
  override collection<K extends keyof LazypockCollections | (string & {})>(
1674
1713
  name: K,
1675
1714
  ): K extends keyof LazypockCollections
1676
1715
  ? CollectionService<
1677
1716
  LazypockCollections[K],
1678
1717
  LazypockCreateData[K],
1679
- LazypockQueryFields[K]
1718
+ LazypockQueryFields[K],
1719
+ LazypockExpandMaps[K]
1680
1720
  >
1681
1721
  : CollectionService<unknown> {
1682
1722
  return super.collection(name) as K extends keyof LazypockCollections
1683
1723
  ? CollectionService<
1684
1724
  LazypockCollections[K],
1685
1725
  LazypockCreateData[K],
1686
- LazypockQueryFields[K]
1726
+ LazypockQueryFields[K],
1727
+ LazypockExpandMaps[K]
1687
1728
  >
1688
1729
  : CollectionService<unknown>;
1689
1730
  }
@@ -1698,9 +1739,10 @@ export class TypedClient extends LazypockClient {
1698
1739
  ${opts.body}
1699
1740
  }`;
1700
1741
  }
1701
- function memberLine(f) {
1742
+ function memberLine(f, skipBaseKeys = false) {
1702
1743
  if (f.hidden) return "";
1703
1744
  const key = fieldKey(f.name);
1745
+ if (skipBaseKeys && BASE_RECORD_KEYS.has(key)) return "";
1704
1746
  const req = f.required || f.type === "password" ? "" : "?";
1705
1747
  const type = fieldTypeScriptType(f);
1706
1748
  if (type === "never") return "";