@pylonsync/sdk 0.3.346 → 0.3.348

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.ts CHANGED
@@ -282,8 +282,44 @@ export interface EntityDefinition {
282
282
  * `db.useSearch` + by-id fetch instead of holding the whole table locally:
283
283
  * `sync: false` keeps it out of the replica entirely. Direct reads
284
284
  * (`/api/entities/X`, `/api/search/X`) and policies are unchanged.
285
+ *
286
+ * Pass an object to keep the entity live but bound WHICH rows replicate —
287
+ * see {@link SyncScope}. That's the middle setting between "every row you
288
+ * can read" and "nothing".
285
289
  */
286
- sync?: boolean;
290
+ sync?: boolean | SyncScope;
291
+ }
292
+ /**
293
+ * Bounded replication for an entity that should stay live but shouldn't ship
294
+ * its whole history to every client.
295
+ *
296
+ * ```ts
297
+ * entity("Message", { … }, {
298
+ * sync: { where: "data.roomId == auth.tenantId", limit: 5_000 },
299
+ * })
300
+ * ```
301
+ *
302
+ * `where` is the SAME expression language as policies (including
303
+ * `exists(...)`), evaluated per row per caller. It is applied ON TOP of the
304
+ * read policy, never instead of it — a scope can only remove rows from a
305
+ * replica, so getting one wrong is a missing-data bug, never a leak.
306
+ *
307
+ * Applies to REPLICATION only: the snapshot, the sync engine's bootstrap, and
308
+ * the change-log delta. Direct reads (`/api/entities/X`, `/api/search/X`) are
309
+ * unchanged, exactly as with `sync: false`.
310
+ *
311
+ * A row that leaves scope is pushed to clients as a DELETE, so replicas evict
312
+ * it rather than holding a copy that never updates again.
313
+ */
314
+ export interface SyncScope {
315
+ /** Policy-DSL predicate. Omit to bound by `limit` alone. */
316
+ where?: string;
317
+ /**
318
+ * Hard ceiling on rows replicated per client for this entity. Belt to
319
+ * `where`'s braces — a predicate that turns out not to bound growth still
320
+ * can't flood a replica. Survives snapshot pagination.
321
+ */
322
+ limit?: number;
287
323
  }
288
324
  export declare function entity(name: string, fields: Record<string, FieldBuilder>, options?: {
289
325
  indexes?: IndexDefinition[];
@@ -465,6 +501,15 @@ export interface ManifestEntity {
465
501
  /** Client replication; omitted when true (the default). `false` keeps the
466
502
  * entity out of the client replica (snapshot + delta) — server-queried only. */
467
503
  sync?: boolean;
504
+ /**
505
+ * Replication scope, flattened out of {@link SyncScope}. Sibling keys
506
+ * rather than a nested object so `sync` keeps its boolean shape on the
507
+ * wire — an older binary reading this manifest still sees `sync: true`
508
+ * and replicates everything, which is the right way for a version skew
509
+ * to degrade.
510
+ */
511
+ sync_scope?: string;
512
+ sync_limit?: number;
468
513
  }
469
514
  export interface ManifestRoute {
470
515
  path: string;
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.3.346",
6
+ "version": "0.3.348",
7
7
  "type": "module",
8
8
  "main": "./src/index.ts",
9
9
  "types": "./dist/index.d.ts",
package/src/index.ts CHANGED
@@ -367,8 +367,45 @@ export interface EntityDefinition {
367
367
  * `db.useSearch` + by-id fetch instead of holding the whole table locally:
368
368
  * `sync: false` keeps it out of the replica entirely. Direct reads
369
369
  * (`/api/entities/X`, `/api/search/X`) and policies are unchanged.
370
+ *
371
+ * Pass an object to keep the entity live but bound WHICH rows replicate —
372
+ * see {@link SyncScope}. That's the middle setting between "every row you
373
+ * can read" and "nothing".
370
374
  */
371
- sync?: boolean;
375
+ sync?: boolean | SyncScope;
376
+ }
377
+
378
+ /**
379
+ * Bounded replication for an entity that should stay live but shouldn't ship
380
+ * its whole history to every client.
381
+ *
382
+ * ```ts
383
+ * entity("Message", { … }, {
384
+ * sync: { where: "data.roomId == auth.tenantId", limit: 5_000 },
385
+ * })
386
+ * ```
387
+ *
388
+ * `where` is the SAME expression language as policies (including
389
+ * `exists(...)`), evaluated per row per caller. It is applied ON TOP of the
390
+ * read policy, never instead of it — a scope can only remove rows from a
391
+ * replica, so getting one wrong is a missing-data bug, never a leak.
392
+ *
393
+ * Applies to REPLICATION only: the snapshot, the sync engine's bootstrap, and
394
+ * the change-log delta. Direct reads (`/api/entities/X`, `/api/search/X`) are
395
+ * unchanged, exactly as with `sync: false`.
396
+ *
397
+ * A row that leaves scope is pushed to clients as a DELETE, so replicas evict
398
+ * it rather than holding a copy that never updates again.
399
+ */
400
+ export interface SyncScope {
401
+ /** Policy-DSL predicate. Omit to bound by `limit` alone. */
402
+ where?: string;
403
+ /**
404
+ * Hard ceiling on rows replicated per client for this entity. Belt to
405
+ * `where`'s braces — a predicate that turns out not to bound growth still
406
+ * can't flood a replica. Survives snapshot pagination.
407
+ */
408
+ limit?: number;
372
409
  }
373
410
 
374
411
  export function entity(
@@ -627,6 +664,15 @@ export interface ManifestEntity {
627
664
  /** Client replication; omitted when true (the default). `false` keeps the
628
665
  * entity out of the client replica (snapshot + delta) — server-queried only. */
629
666
  sync?: boolean;
667
+ /**
668
+ * Replication scope, flattened out of {@link SyncScope}. Sibling keys
669
+ * rather than a nested object so `sync` keeps its boolean shape on the
670
+ * wire — an older binary reading this manifest still sees `sync: true`
671
+ * and replicates everything, which is the right way for a version skew
672
+ * to degrade.
673
+ */
674
+ sync_scope?: string;
675
+ sync_limit?: number;
630
676
  }
631
677
 
632
678
  export interface ManifestRoute {
@@ -916,6 +962,16 @@ export function entitiesToManifest(
916
962
  // Emit only when opted OUT — the runtime defaults sync to true.
917
963
  if (e.sync === false) {
918
964
  result.sync = false;
965
+ } else if (e.sync && typeof e.sync === "object") {
966
+ // A SCOPE. Flattened into sibling keys rather than nested under `sync`
967
+ // so the field keeps its boolean shape on the wire: an older binary
968
+ // reading this manifest still sees `sync: true` and replicates
969
+ // everything — more data than intended, which is the right way for
970
+ // this to degrade. Nesting would make `sync` fail to deserialize as a
971
+ // bool and take the whole app down on a version skew.
972
+ const scope = e.sync;
973
+ if (scope.where !== undefined) result.sync_scope = scope.where;
974
+ if (scope.limit !== undefined) result.sync_limit = scope.limit;
919
975
  }
920
976
  return result;
921
977
  });