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/dist/index.d.cts CHANGED
@@ -413,6 +413,8 @@ interface SchemaField {
413
413
  id?: string;
414
414
  name: string;
415
415
  type: string;
416
+ /** For relation fields: the target collection id (server-resolved). */
417
+ collectionId?: string | null;
416
418
  required?: boolean;
417
419
  unique?: boolean;
418
420
  options?: {
@@ -456,7 +458,7 @@ type RealtimeCallback = (e: RealtimeMessage) => void;
456
458
  *
457
459
  * @typeParam T — The record shape for this collection. Defaults to {@link ApiRecord}.
458
460
  */
459
- declare class CollectionService<T = ApiRecord> {
461
+ declare class CollectionService<T = ApiRecord, TData = never> {
460
462
  private http;
461
463
  private collectionName;
462
464
  private authStore?;
@@ -465,8 +467,9 @@ declare class CollectionService<T = ApiRecord> {
465
467
  private schema?;
466
468
  /**
467
469
  * Active field projection from {@link select}. `"*"` (or unset) means
468
- * "all visible (non-hidden) fields" — resolved against the schema when
469
- * one is available, otherwise left to the server.
470
+ * "all visible (non-hidden) fields plus the implicit system keys" —
471
+ * resolved against the schema when one is available, otherwise left to
472
+ * the server.
470
473
  */
471
474
  private fieldsPreset?;
472
475
  /** @internal */
@@ -484,26 +487,38 @@ declare class CollectionService<T = ApiRecord> {
484
487
  * const t = await client.collection("posts").select("id", "title").getList();
485
488
  * // GET /api/posts?fields=id,title
486
489
  *
487
- * // All visible fields (hidden fields are excluded server-side)
490
+ * // All visible fields (implicit system keys id/created/updated/… are
491
+ * // kept; hidden fields are excluded via the projection)
488
492
  * const all = await client.collection("posts").select("*").getList();
489
- * // GET /api/posts?fields=id,title,published,… (schema known)
493
+ * // GET /api/posts?fields=id,created,updated,collectionId,collectionName,title,published,… (schema known)
490
494
  * ```
491
495
  *
492
496
  * When no schema is known, the default (no `select()` call) sends no
493
497
  * `fields` param — the server returns every non-password field.
494
498
  * Pass `select("*")` to restore the default after a projection.
495
499
  */
496
- select<K extends FieldKey<T> | "*">(...fields: K[]): CollectionService<T>;
500
+ select<K extends FieldKey<T> | "*">(...fields: K[]): CollectionService<T, TData>;
497
501
  /**
498
502
  * Fields to send with reads:
499
503
  * 1. explicit `options.fields` (caller wins)
500
504
  * 2. `select()` preset
501
- * 3. schema default — all visible (non-hidden, non-system) fields
505
+ * 3. schema default — all visible (non-hidden) fields plus the implicit
506
+ * system keys (`id`, `created`, `updated`, `collectionId`, `collectionName`)
502
507
  */
503
508
  private effectiveFields;
504
509
  /**
505
- * All non-hidden, non-system, non-password field names from the schema.
506
- * `undefined` when no schema is available (server decides).
510
+ * All non-hidden, non-password, non-autodate field names from the schema,
511
+ * prefixed with the implicit system keys that are part of every record
512
+ * (`id`, `created`, `updated`, `collectionId`, `collectionName`).
513
+ *
514
+ * The system keys never appear in the schema (they are implied for every
515
+ * collection) but MUST be listed explicitly when projecting — the server
516
+ * applies strict projection, so unrequested fields are dropped. Without
517
+ * them, `getList`/`getOne` responses lose `id` (and `created`/`updated`),
518
+ * breaking selection, edits, and relation references.
519
+ *
520
+ * `undefined` when no schema fields are available (server decides — its
521
+ * default returns every non-password field, including the system keys).
507
522
  */
508
523
  visibleFields(): string | undefined;
509
524
  /** Warn once when an expand field is not a relation (schema known). */
@@ -542,7 +557,7 @@ declare class CollectionService<T = ApiRecord> {
542
557
  * record type), excess/unknown fields are rejected at compile time.
543
558
  * @param options Optional request options.
544
559
  */
545
- create(data: T extends ApiRecord ? Record<string, unknown> : CreateData<T>, options?: RequestOptions): Promise<T | null>;
560
+ create(data: [TData] extends [never] ? T extends ApiRecord ? Record<string, unknown> : CreateData<T> : TData, options?: RequestOptions): Promise<T | null>;
546
561
  /**
547
562
  * Update a record by ID.
548
563
  * @param id Record ID.
@@ -550,7 +565,7 @@ declare class CollectionService<T = ApiRecord> {
550
565
  * must be a partial of `T` — unknown fields are rejected.
551
566
  * @param options Optional request options.
552
567
  */
553
- update(id: string, data: T extends ApiRecord ? Record<string, unknown> : UpdateData<T>, options?: RequestOptions): Promise<T | null>;
568
+ update(id: string, data: [TData] extends [never] ? T extends ApiRecord ? Record<string, unknown> : UpdateData<T> : Partial<TData>, options?: RequestOptions): Promise<T | null>;
554
569
  /**
555
570
  * Delete a record by ID.
556
571
  * @param id Record ID.
@@ -588,7 +603,7 @@ declare class CollectionService<T = ApiRecord> {
588
603
  * `types.schemas`). Enables schema-aware defaults: hidden fields are
589
604
  * excluded from responses, select/expand are validated.
590
605
  */
591
- withSchema(schema: CollectionSchema): CollectionService<T>;
606
+ withSchema(schema: CollectionSchema): CollectionService<T, TData>;
592
607
  /**
593
608
  * Subscribe to realtime changes for this collection.
594
609
  * The event's `action` is one of `"create" | "update" | "delete"`.
@@ -865,6 +880,14 @@ interface LazypockClientOptions {
865
880
  declare class LazypockClient {
866
881
  readonly http: HttpClient;
867
882
  readonly authStore: AuthStore;
883
+ /**
884
+ * Resolves once persisted auth state has been loaded from storage.
885
+ * The client auto-initializes its auth store on construction, so a
886
+ * previously persisted token is ready immediately (no manual
887
+ * `authStore.init()` needed). Await this only if you must gate on
888
+ * the restored session.
889
+ */
890
+ readonly authReady: Promise<void>;
868
891
  readonly realtime: RealtimeService;
869
892
  readonly collections: CollectionsService;
870
893
  readonly files: FilesService;
package/dist/index.d.ts CHANGED
@@ -413,6 +413,8 @@ interface SchemaField {
413
413
  id?: string;
414
414
  name: string;
415
415
  type: string;
416
+ /** For relation fields: the target collection id (server-resolved). */
417
+ collectionId?: string | null;
416
418
  required?: boolean;
417
419
  unique?: boolean;
418
420
  options?: {
@@ -456,7 +458,7 @@ type RealtimeCallback = (e: RealtimeMessage) => void;
456
458
  *
457
459
  * @typeParam T — The record shape for this collection. Defaults to {@link ApiRecord}.
458
460
  */
459
- declare class CollectionService<T = ApiRecord> {
461
+ declare class CollectionService<T = ApiRecord, TData = never> {
460
462
  private http;
461
463
  private collectionName;
462
464
  private authStore?;
@@ -465,8 +467,9 @@ declare class CollectionService<T = ApiRecord> {
465
467
  private schema?;
466
468
  /**
467
469
  * Active field projection from {@link select}. `"*"` (or unset) means
468
- * "all visible (non-hidden) fields" — resolved against the schema when
469
- * one is available, otherwise left to the server.
470
+ * "all visible (non-hidden) fields plus the implicit system keys" —
471
+ * resolved against the schema when one is available, otherwise left to
472
+ * the server.
470
473
  */
471
474
  private fieldsPreset?;
472
475
  /** @internal */
@@ -484,26 +487,38 @@ declare class CollectionService<T = ApiRecord> {
484
487
  * const t = await client.collection("posts").select("id", "title").getList();
485
488
  * // GET /api/posts?fields=id,title
486
489
  *
487
- * // All visible fields (hidden fields are excluded server-side)
490
+ * // All visible fields (implicit system keys id/created/updated/… are
491
+ * // kept; hidden fields are excluded via the projection)
488
492
  * const all = await client.collection("posts").select("*").getList();
489
- * // GET /api/posts?fields=id,title,published,… (schema known)
493
+ * // GET /api/posts?fields=id,created,updated,collectionId,collectionName,title,published,… (schema known)
490
494
  * ```
491
495
  *
492
496
  * When no schema is known, the default (no `select()` call) sends no
493
497
  * `fields` param — the server returns every non-password field.
494
498
  * Pass `select("*")` to restore the default after a projection.
495
499
  */
496
- select<K extends FieldKey<T> | "*">(...fields: K[]): CollectionService<T>;
500
+ select<K extends FieldKey<T> | "*">(...fields: K[]): CollectionService<T, TData>;
497
501
  /**
498
502
  * Fields to send with reads:
499
503
  * 1. explicit `options.fields` (caller wins)
500
504
  * 2. `select()` preset
501
- * 3. schema default — all visible (non-hidden, non-system) fields
505
+ * 3. schema default — all visible (non-hidden) fields plus the implicit
506
+ * system keys (`id`, `created`, `updated`, `collectionId`, `collectionName`)
502
507
  */
503
508
  private effectiveFields;
504
509
  /**
505
- * All non-hidden, non-system, non-password field names from the schema.
506
- * `undefined` when no schema is available (server decides).
510
+ * All non-hidden, non-password, non-autodate field names from the schema,
511
+ * prefixed with the implicit system keys that are part of every record
512
+ * (`id`, `created`, `updated`, `collectionId`, `collectionName`).
513
+ *
514
+ * The system keys never appear in the schema (they are implied for every
515
+ * collection) but MUST be listed explicitly when projecting — the server
516
+ * applies strict projection, so unrequested fields are dropped. Without
517
+ * them, `getList`/`getOne` responses lose `id` (and `created`/`updated`),
518
+ * breaking selection, edits, and relation references.
519
+ *
520
+ * `undefined` when no schema fields are available (server decides — its
521
+ * default returns every non-password field, including the system keys).
507
522
  */
508
523
  visibleFields(): string | undefined;
509
524
  /** Warn once when an expand field is not a relation (schema known). */
@@ -542,7 +557,7 @@ declare class CollectionService<T = ApiRecord> {
542
557
  * record type), excess/unknown fields are rejected at compile time.
543
558
  * @param options Optional request options.
544
559
  */
545
- create(data: T extends ApiRecord ? Record<string, unknown> : CreateData<T>, options?: RequestOptions): Promise<T | null>;
560
+ create(data: [TData] extends [never] ? T extends ApiRecord ? Record<string, unknown> : CreateData<T> : TData, options?: RequestOptions): Promise<T | null>;
546
561
  /**
547
562
  * Update a record by ID.
548
563
  * @param id Record ID.
@@ -550,7 +565,7 @@ declare class CollectionService<T = ApiRecord> {
550
565
  * must be a partial of `T` — unknown fields are rejected.
551
566
  * @param options Optional request options.
552
567
  */
553
- update(id: string, data: T extends ApiRecord ? Record<string, unknown> : UpdateData<T>, options?: RequestOptions): Promise<T | null>;
568
+ update(id: string, data: [TData] extends [never] ? T extends ApiRecord ? Record<string, unknown> : UpdateData<T> : Partial<TData>, options?: RequestOptions): Promise<T | null>;
554
569
  /**
555
570
  * Delete a record by ID.
556
571
  * @param id Record ID.
@@ -588,7 +603,7 @@ declare class CollectionService<T = ApiRecord> {
588
603
  * `types.schemas`). Enables schema-aware defaults: hidden fields are
589
604
  * excluded from responses, select/expand are validated.
590
605
  */
591
- withSchema(schema: CollectionSchema): CollectionService<T>;
606
+ withSchema(schema: CollectionSchema): CollectionService<T, TData>;
592
607
  /**
593
608
  * Subscribe to realtime changes for this collection.
594
609
  * The event's `action` is one of `"create" | "update" | "delete"`.
@@ -865,6 +880,14 @@ interface LazypockClientOptions {
865
880
  declare class LazypockClient {
866
881
  readonly http: HttpClient;
867
882
  readonly authStore: AuthStore;
883
+ /**
884
+ * Resolves once persisted auth state has been loaded from storage.
885
+ * The client auto-initializes its auth store on construction, so a
886
+ * previously persisted token is ready immediately (no manual
887
+ * `authStore.init()` needed). Await this only if you must gate on
888
+ * the restored session.
889
+ */
890
+ readonly authReady: Promise<void>;
868
891
  readonly realtime: RealtimeService;
869
892
  readonly collections: CollectionsService;
870
893
  readonly files: FilesService;
@@ -519,9 +519,10 @@ var Lazypock = (() => {
519
519
  * const t = await client.collection("posts").select("id", "title").getList();
520
520
  * // GET /api/posts?fields=id,title
521
521
  *
522
- * // All visible fields (hidden fields are excluded server-side)
522
+ * // All visible fields (implicit system keys id/created/updated/… are
523
+ * // kept; hidden fields are excluded via the projection)
523
524
  * const all = await client.collection("posts").select("*").getList();
524
- * // GET /api/posts?fields=id,title,published,… (schema known)
525
+ * // GET /api/posts?fields=id,created,updated,collectionId,collectionName,title,published,… (schema known)
525
526
  * ```
526
527
  *
527
528
  * When no schema is known, the default (no `select()` call) sends no
@@ -554,7 +555,8 @@ var Lazypock = (() => {
554
555
  * Fields to send with reads:
555
556
  * 1. explicit `options.fields` (caller wins)
556
557
  * 2. `select()` preset
557
- * 3. schema default — all visible (non-hidden, non-system) fields
558
+ * 3. schema default — all visible (non-hidden) fields plus the implicit
559
+ * system keys (`id`, `created`, `updated`, `collectionId`, `collectionName`)
558
560
  */
559
561
  effectiveFields(optionsFields) {
560
562
  if (optionsFields !== void 0) return optionsFields;
@@ -567,15 +569,35 @@ var Lazypock = (() => {
567
569
  return this.visibleFields();
568
570
  }
569
571
  /**
570
- * All non-hidden, non-system, non-password field names from the schema.
571
- * `undefined` when no schema is available (server decides).
572
+ * All non-hidden, non-password, non-autodate field names from the schema,
573
+ * prefixed with the implicit system keys that are part of every record
574
+ * (`id`, `created`, `updated`, `collectionId`, `collectionName`).
575
+ *
576
+ * The system keys never appear in the schema (they are implied for every
577
+ * collection) but MUST be listed explicitly when projecting — the server
578
+ * applies strict projection, so unrequested fields are dropped. Without
579
+ * them, `getList`/`getOne` responses lose `id` (and `created`/`updated`),
580
+ * breaking selection, edits, and relation references.
581
+ *
582
+ * `undefined` when no schema fields are available (server decides — its
583
+ * default returns every non-password field, including the system keys).
572
584
  */
573
585
  visibleFields() {
574
586
  if (!this.schema?.fields) return void 0;
575
587
  const visible = this.schema.fields.filter(
576
588
  (f) => !f.hidden && f.type !== "password" && f.type !== "autodate"
577
589
  ).map((f) => f.name);
578
- return visible.length > 0 ? visible.join(",") : void 0;
590
+ if (visible.length === 0) return void 0;
591
+ return [
592
+ .../* @__PURE__ */ new Set([
593
+ "id",
594
+ "created",
595
+ "updated",
596
+ "collectionId",
597
+ "collectionName",
598
+ ...visible
599
+ ])
600
+ ].join(",");
579
601
  }
580
602
  /** Warn once when an expand field is not a relation (schema known). */
581
603
  validateExpand(expand) {
@@ -1461,6 +1483,12 @@ var Lazypock = (() => {
1461
1483
  body
1462
1484
  })}`
1463
1485
  );
1486
+ const createLines = fields.map((f) => createDataMemberLine(f)).filter((l) => l !== "");
1487
+ sections.push(
1488
+ `export interface ${typeName}CreateData${renderInterface({
1489
+ body: createLines.join("\n")
1490
+ })}`
1491
+ );
1464
1492
  }
1465
1493
  sections.push(`export interface AuthRecord extends BaseRecord {
1466
1494
  email: string;
@@ -1471,6 +1499,10 @@ var Lazypock = (() => {
1471
1499
  ).join("\n");
1472
1500
  sections.push(`export interface LazypockCollections {
1473
1501
  ${mapEntries}
1502
+ }`);
1503
+ const createMapEntries = filtered.map((c) => ` "${c.name}": ${collectionTypeName(c.name)}CreateData;`).join("\n");
1504
+ sections.push(`export interface LazypockCreateData {
1505
+ ${createMapEntries}
1474
1506
  }`);
1475
1507
  const schemaEntries = collections.map(
1476
1508
  (c) => ` {
@@ -1494,6 +1526,10 @@ ${schemaEntries}
1494
1526
  * Collection access is fully type-checked:
1495
1527
  * client.collection("posts").create({ title: "x" }) // title must exist
1496
1528
  *
1529
+ * For auth collections the generated *CreateData type includes the
1530
+ * write-only password field, so creating a user is type-safe:
1531
+ * client.collection("users").create({ email, password }) // \u2713
1532
+ *
1497
1533
  * The schema snapshot is wired into the client automatically, so hidden
1498
1534
  * fields are excluded from responses and select/expand are validated.
1499
1535
  */
@@ -1508,10 +1544,16 @@ export function createClient(options: LazypockClientOptions): TypedClient {
1508
1544
  }
1509
1545
 
1510
1546
  export class TypedClient extends LazypockClient {
1511
- override collection<T extends string>(name: T): T extends keyof LazypockCollections
1512
- ? CollectionService<LazypockCollections[T]>
1513
- : CollectionService<unknown> {
1514
- return super.collection(name) as T extends keyof LazypockCollections ? CollectionService<LazypockCollections[T]> : CollectionService<unknown>;
1547
+ // K extends keyof LazypockCollections (rather than a generic string with a
1548
+ // conditional return) so the IDE suggests collection names and unknown names
1549
+ // are rejected at compile time:
1550
+ // client.collection("posts") // suggested + typed
1551
+ // client.collection("nope") // TS error
1552
+ //
1553
+ // create()/update() take the collection's *CreateData \u2014 the read model
1554
+ // omits password/hidden fields, but the write model carries them.
1555
+ override collection<K extends keyof LazypockCollections>(name: K): CollectionService<LazypockCollections[K], LazypockCreateData[K]> {
1556
+ return super.collection(name) as CollectionService<LazypockCollections[K], LazypockCreateData[K]>;
1515
1557
  }
1516
1558
  }
1517
1559
  `);
@@ -1532,6 +1574,14 @@ ${opts.body}
1532
1574
  if (type === "never") return "";
1533
1575
  return ` ${JSON.stringify(key)}${req}: ${type};`;
1534
1576
  }
1577
+ function createDataMemberLine(f) {
1578
+ if (f.type === "autodate") return "";
1579
+ const key = fieldKey(f.name);
1580
+ const req = f.required ? "" : "?";
1581
+ const type = f.type === "password" ? "string" : fieldTypeScriptType(f);
1582
+ if (type === "never") return "";
1583
+ return ` ${JSON.stringify(key)}${req}: ${type};`;
1584
+ }
1535
1585
 
1536
1586
  // src/lazypock.ts
1537
1587
  var LazypockClient = class {
@@ -1543,6 +1593,7 @@ ${opts.body}
1543
1593
  this.collectionCache = /* @__PURE__ */ new Map();
1544
1594
  const baseUrl = options.baseUrl.replace(/\/+$/, "");
1545
1595
  this.authStore = options.authStore ?? new AuthStore(options.storage ?? memoryStorage);
1596
+ this.authReady = options.authStore ? Promise.resolve() : this.authStore.init();
1546
1597
  this.http = new HttpClient(baseUrl, this.authStore);
1547
1598
  this.realtime = options.realtime ?? new RealtimeService();
1548
1599
  if (!options.realtime) {