@abloatai/humans 0.59.2 → 0.61.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.
Files changed (96) hide show
  1. package/README.md +1 -1
  2. package/dist/Ablo.d.ts +2 -10
  3. package/dist/Ablo.js +0 -1
  4. package/dist/client.d.ts +1 -48
  5. package/dist/humans.d.ts +1 -1
  6. package/dist/local/BaseSyncedStore.d.ts +5 -5
  7. package/dist/local/BaseSyncedStore.js +7 -7
  8. package/dist/local/Database.d.ts +2 -2
  9. package/dist/local/LazyReferenceCollection.d.ts +1 -1
  10. package/dist/local/Model.js +46 -56
  11. package/dist/local/NetworkMonitor.js +2 -0
  12. package/dist/local/RuntimeContext.js +2 -0
  13. package/dist/local/SyncClient.d.ts +12 -36
  14. package/dist/local/SyncClient.js +50 -108
  15. package/dist/local/client/createModelOperations.d.ts +3 -27
  16. package/dist/local/client/createModelOperations.js +20 -21
  17. package/dist/local/client/options.d.ts +14 -39
  18. package/dist/local/client/reactiveEngine.d.ts +3 -9
  19. package/dist/local/client/reactiveEngine.js +6 -151
  20. package/dist/local/client/storeLifecycle.js +5 -1
  21. package/dist/local/fileUploads.d.ts +27 -0
  22. package/dist/local/fileUploads.js +55 -0
  23. package/dist/local/storeContract.d.ts +5 -5
  24. package/dist/local/stores/syncAction.d.ts +4 -4
  25. package/dist/local/sync/contextOnChange.js +1 -1
  26. package/dist/local/sync/createClaimStream.js +1 -1
  27. package/dist/local/sync/credentialLifecycle.d.ts +4 -5
  28. package/dist/local/sync/credentialLifecycle.js +4 -5
  29. package/dist/local/sync/deltaPipeline.js +12 -6
  30. package/dist/local/sync/schemas.d.ts +10 -10
  31. package/dist/local/sync/scopeGroups.d.ts +11 -0
  32. package/dist/local/sync/scopeGroups.js +75 -0
  33. package/dist/local/sync/wsFrameHandlers.d.ts +1 -1
  34. package/dist/local/transactions/localMutation.js +3 -3
  35. package/dist/local/transactions/mutations/MutationQueue.d.ts +4 -5
  36. package/dist/local/transactions/mutations/MutationQueue.js +25 -51
  37. package/dist/local/transactions/mutations/batchProcessing.js +23 -10
  38. package/dist/local/transactions/mutations/commitPayload.d.ts +9 -2
  39. package/dist/local/transactions/mutations/commitTransport.js +3 -1
  40. package/dist/local/transactions/mutations/executionSelection.d.ts +0 -1
  41. package/dist/local/transactions/mutations/executionSelection.js +9 -17
  42. package/dist/local/transactions/mutations/failureHandling.js +9 -0
  43. package/dist/local/transactions/mutations/localMutation.js +3 -3
  44. package/dist/local/transactions/mutations/queueCoalescing.js +8 -0
  45. package/dist/local/transactions/mutations/replayValidation.d.ts +15 -15
  46. package/dist/react/AbloProvider.d.ts +11 -86
  47. package/dist/react/AbloProvider.js +11 -163
  48. package/dist/react/ClientSideSuspense.d.ts +1 -1
  49. package/dist/react/DefaultFallback.d.ts +1 -1
  50. package/dist/react/createAbloReact.js +1 -1
  51. package/dist/react/useErrorListener.js +1 -1
  52. package/dist/react/useMutationFailureListener.js +1 -1
  53. package/dist/react.d.ts +1 -1
  54. package/dist/react.js +1 -1
  55. package/dist/surface.d.ts +3 -3
  56. package/dist/surface.js +1 -4
  57. package/package.json +3 -3
  58. package/src/Ablo.ts +5 -17
  59. package/src/client.ts +0 -51
  60. package/src/local/BaseSyncedStore.ts +14 -14
  61. package/src/local/Model.ts +45 -55
  62. package/src/local/NetworkMonitor.ts +2 -0
  63. package/src/local/RuntimeContext.ts +2 -0
  64. package/src/local/SyncClient.ts +73 -140
  65. package/src/local/client/createModelOperations.ts +30 -64
  66. package/src/local/client/options.ts +20 -43
  67. package/src/local/client/reactiveEngine.ts +7 -179
  68. package/src/local/client/storeLifecycle.ts +6 -1
  69. package/src/local/fileUploads.ts +97 -0
  70. package/src/local/storeContract.ts +5 -5
  71. package/src/local/sync/contextOnChange.ts +1 -1
  72. package/src/local/sync/createClaimStream.ts +1 -1
  73. package/src/local/sync/credentialLifecycle.ts +4 -5
  74. package/src/local/sync/deltaPipeline.ts +10 -6
  75. package/src/local/sync/scopeGroups.ts +91 -0
  76. package/src/local/sync/wsFrameHandlers.ts +0 -1
  77. package/src/local/transactions/localMutation.ts +3 -3
  78. package/src/local/transactions/mutations/MutationQueue.ts +24 -53
  79. package/src/local/transactions/mutations/batchProcessing.ts +25 -10
  80. package/src/local/transactions/mutations/commitPayload.ts +11 -1
  81. package/src/local/transactions/mutations/commitTransport.ts +2 -2
  82. package/src/local/transactions/mutations/executionSelection.ts +9 -15
  83. package/src/local/transactions/mutations/failureHandling.ts +10 -0
  84. package/src/local/transactions/mutations/localMutation.ts +3 -3
  85. package/src/local/transactions/mutations/queueCoalescing.ts +6 -0
  86. package/src/react/AbloProvider.tsx +17 -249
  87. package/src/react/useErrorListener.ts +1 -1
  88. package/src/react/useMutationFailureListener.ts +1 -1
  89. package/src/react.ts +1 -5
  90. package/src/surface.ts +1 -4
  91. package/dist/local/sync/participants.d.ts +0 -132
  92. package/dist/local/sync/participants.js +0 -342
  93. package/dist/local/transactions/mutations/pendingDrain.d.ts +0 -33
  94. package/dist/local/transactions/mutations/pendingDrain.js +0 -117
  95. package/src/local/sync/participants.ts +0 -564
  96. package/src/local/transactions/mutations/pendingDrain.ts +0 -169
package/README.md CHANGED
@@ -25,7 +25,7 @@ import { Ablo } from '@abloatai/ablo/client';
25
25
 
26
26
  const ablo = Ablo({
27
27
  schema,
28
- authEndpoint: '/api/ablo-session',
28
+ session: { endpoint: '/api/ablo-session' },
29
29
  });
30
30
 
31
31
  await ablo.ready();
package/dist/Ablo.d.ts CHANGED
@@ -50,11 +50,11 @@ export type Ablo<S extends SchemaRecord> = AbloClient<S>;
50
50
  * ```
51
51
  *
52
52
  * In the browser (or any client that shouldn't hold a secret key), point
53
- * `authEndpoint` at your session-mint route instead — the SDK fetches it, keeps the
53
+ * `session.endpoint` at your session-mint route instead — the SDK fetches it, keeps the
54
54
  * short-lived token fresh, and re-mints on expiry:
55
55
  *
56
56
  * ```ts
57
- * const ablo = Ablo({ schema, authEndpoint: '/api/ablo-session' });
57
+ * const ablo = Ablo({ schema, session: { endpoint: '/api/ablo-session' } });
58
58
  * ```
59
59
  *
60
60
  * Server-side agents, workers, and services use `@abloatai/transaction`.
@@ -64,7 +64,6 @@ export declare function Ablo<const S extends SchemaRecord, const P extends reado
64
64
  }): Ablo<S> & MergedSurface<P>;
65
65
  export declare function Ablo<const S extends SchemaRecord>(options: AbloOptions<S>): Ablo<S>;
66
66
  import type * as _Streams from '@abloatai/transaction/types/streams';
67
- import type * as _Participants from './local/sync/participants.js';
68
67
  import type * as _Mutators from './local/mutators/defineMutators.js';
69
68
  import type * as _Tx from './local/mutators/Transaction.js';
70
69
  import type * as _Undo from './local/mutators/UndoManager.js';
@@ -110,13 +109,6 @@ export declare namespace Ablo {
110
109
  namespace Auth {
111
110
  type Actor = _Streams.ParticipantRef;
112
111
  }
113
- namespace Participant {
114
- type Manager = _Participants.ParticipantManager;
115
- type Joined = _Participants.JoinedParticipant;
116
- type Scope = _Participants.ParticipantScope;
117
- type Status = _Participants.ParticipantStatus;
118
- type JoinOptions = _Participants.ParticipantJoinOptions;
119
- }
120
112
  type Schema<S extends _SchemaTypes.SchemaRecord = _SchemaTypes.SchemaRecord> = _SchemaTypes.Schema<S>;
121
113
  /**
122
114
  * The schema this program has registered via `interface Register { Schema }`
package/dist/Ablo.js CHANGED
@@ -115,6 +115,5 @@ export function Ablo(options) {
115
115
  transport,
116
116
  presence: humansSurface.presence,
117
117
  cluster,
118
- createSibling: (siblingOptions) => Ablo(siblingOptions),
119
118
  }), installedPlugins);
120
119
  }
package/dist/client.d.ts CHANGED
@@ -17,7 +17,7 @@ import type { SyncStoreContract } from './react/context.js';
17
17
  import type { SyncWebSocket, CoreSyncEventMap } from './local/sync/SyncWebSocket.js';
18
18
  import type { SyncStatus } from './local/BaseSyncedStore.js';
19
19
  import type { ModelOperations } from './local/client/createModelOperations.js';
20
- import type { ClaimResource, CommitResource, CreateAgentClientParams, SessionResource } from '@abloatai/transaction/client/resources/httpResources';
20
+ import type { ClaimResource, CommitResource } from '@abloatai/transaction/client/resources/httpResources';
21
21
  import type { EffectiveAuthority } from '@abloatai/transaction/auth';
22
22
  import type { ReadDependency } from '@abloatai/transaction/coordination';
23
23
  import type { CapturedRow } from '@abloatai/transaction/transport/http';
@@ -102,53 +102,6 @@ export type AbloClient<S extends SchemaRecord> = {
102
102
  * the watchdog.
103
103
  */
104
104
  nudgeReconnect(): void;
105
- /**
106
- * Mint a short-lived, scoped session token for one end user. Call this on your
107
- * backend, where the `sk_` secret key lives, then hand the returned `token` to
108
- * that user's browser — typically through a token route the browser's `apiKey`
109
- * resolver fetches. The browser presents the token as its bearer, and the
110
- * server verifies it. The browser must never see the `sk_` key, only the
111
- * per-user session token.
112
- *
113
- * Pass `{ user: { id }, can: { items: ['read', 'update'] } }` for an end-user
114
- * session. It mints an `ek_` and attributes writes to a user (recorded as
115
- * `actor_kind` on the delta row). Pass `{ agent: { id }, can: {
116
- * items: ['update'] } }` for a scoped agent session, which mints an `rk_`.
117
- * Both kinds require `can`, typed against your schema's model names. This
118
- * always authenticates with the original `sk_`, never the client's exchanged
119
- * sync credential.
120
- */
121
- sessions: SessionResource<S>;
122
- /**
123
- * Mint a scoped **agent identity** and return a ready-to-use client bound to
124
- * it — the `ablo.<resource>.<verb>` shape for the agent use case. One call
125
- * replaces `sessions.create({ agent, can })` + constructing a second
126
- * `Ablo({ apiKey: token })`:
127
- *
128
- * ```ts
129
- * const agent = await ablo.agents.create({
130
- * name: 'researcher', // readable label (optional)
131
- * can: { records: ['read', 'update'] },
132
- * // id omitted → a fresh uuid: a distinct, independent participant
133
- * });
134
- * await agent.records.update({ id, data, claim });
135
- * await agent.dispose(); // when the agent is done
136
- * ```
137
- *
138
- * Server-side only: it requires the `sk_` secret key (like `sessions.create`)
139
- * and throws `AbloAuthenticationError` in the browser. The returned client
140
- * holds its own auto-refreshing `rk_`, so a long run never hits token expiry,
141
- * and the `sk_` never leaves this process. Each call is a distinct participant
142
- * by default (omit `id` for a fresh uuid), so even two agents sharing a `name`
143
- * queue behind one another on a contended row — `name` is display only and
144
- * never collapses identity. Humans don't get a server-built client; ship them a
145
- * token via `sessions.create({ user, can })`. If you need the raw token for
146
- * revocation, or a stable re-attachable id, use `sessions.create({ agent, can })`
147
- * or pass `id`.
148
- */
149
- agents: {
150
- create(params: CreateAgentClientParams<S>): Promise<AbloClient<S>>;
151
- };
152
105
  /**
153
106
  * The organization this client resolved to — `null` until `ready()`
154
107
  * completes. Use it instead of scraping CLI output or hardcoding env vars:
package/dist/humans.d.ts CHANGED
@@ -13,7 +13,7 @@ export interface HumansSurface {
13
13
  readonly [kStoreCluster]?: StoreCluster;
14
14
  }
15
15
  export declare function humans(): {
16
- readonly id: 'humans';
16
+ readonly id: "humans";
17
17
  readonly requires: {
18
18
  readonly duplex: true;
19
19
  };
@@ -13,7 +13,7 @@
13
13
  import type { RecoveryClass } from '@abloatai/transaction/errorCodes';
14
14
  import { ConnectionManager } from './sync/ConnectionManager.js';
15
15
  import { SubscriptionManager } from './sync/SubscriptionManager.js';
16
- import { type ParticipantScope } from './sync/participants.js';
16
+ import { type GroupScope } from './sync/scopeGroups.js';
17
17
  import type { SyncClient } from './SyncClient.js';
18
18
  import type { Database, BootstrapResult, BootstrapRequirements } from './Database.js';
19
19
  import type { InstanceCache } from './InstanceCache.js';
@@ -254,7 +254,7 @@ export declare class BaseSyncedStore<TCollaboration extends EventMap<TCollaborat
254
254
  * Hydration is best-effort — a failed backfill never rejects `enterScope`,
255
255
  * and the live delta stream keeps flowing regardless.
256
256
  */
257
- enterScope(scope: ParticipantScope, opts?: {
257
+ enterScope(scope: GroupScope, opts?: {
258
258
  hydrate?: boolean;
259
259
  }): Promise<void>;
260
260
  /**
@@ -266,11 +266,11 @@ export declare class BaseSyncedStore<TCollaboration extends EventMap<TCollaborat
266
266
  */
267
267
  protected hydrateGroups(syncGroups: readonly string[]): Promise<void>;
268
268
  /** Leave a scope → its groups go warm (hysteresis), then drop on sweep. */
269
- leaveScope(scope: ParticipantScope): Promise<void>;
269
+ leaveScope(scope: GroupScope): Promise<void>;
270
270
  /** Pin a scope (active claim / prominence) → never warms while pinned. */
271
- pinScope(scope: ParticipantScope): Promise<void>;
271
+ pinScope(scope: GroupScope): Promise<void>;
272
272
  /** Release a pin → the group transitions to warm rather than dropping. */
273
- unpinScope(scope: ParticipantScope): Promise<void>;
273
+ unpinScope(scope: GroupScope): Promise<void>;
274
274
  protected readonly queryProcessor: QueryProcessor;
275
275
  /**
276
276
  * Runtime behavior flags only — the schema/config arrays
@@ -15,7 +15,7 @@ import { AbloConnectionError, AbloValidationError, toAbloError } from '@abloatai
15
15
  import { ConnectionManager } from './sync/ConnectionManager.js';
16
16
  import { contextLogger, contextSocketObservability } from './sync/contextPorts.js';
17
17
  import { SubscriptionManager } from './sync/SubscriptionManager.js';
18
- import { resolveParticipantSyncGroups, } from './sync/participants.js';
18
+ import { resolveScopeGroups, } from './sync/scopeGroups.js';
19
19
  import { ModelRegistry } from './ModelRegistry.js';
20
20
  import { PropertyType } from '@abloatai/transaction/types';
21
21
  import { SyncWebSocket, } from './sync/SyncWebSocket.js';
@@ -180,7 +180,7 @@ export class BaseSyncedStore {
180
180
  // {@link SubscriptionManager.reconcile}); the on-connect `resync` pushes
181
181
  // whatever interest accumulated.
182
182
  scopeToGroups(scope) {
183
- return resolveParticipantSyncGroups(scope, this.schema);
183
+ return resolveScopeGroups(scope, this.schema);
184
184
  }
185
185
  /**
186
186
  * Bring a scope into view and subscribe to its sync groups. With
@@ -1334,10 +1334,10 @@ export class BaseSyncedStore {
1334
1334
  const isCreate = !this.objectPool.get(model.id);
1335
1335
  if (isCreate) {
1336
1336
  model.updatedAt = new Date();
1337
- this.syncClient.add(model);
1337
+ await this.syncClient.add(model);
1338
1338
  }
1339
1339
  else {
1340
- this.syncClient.update(model);
1340
+ await this.syncClient.update(model);
1341
1341
  }
1342
1342
  }
1343
1343
  /** Save with an atomic server mutation (e.g., createSectionWithBlocks) */
@@ -1350,19 +1350,19 @@ export class BaseSyncedStore {
1350
1350
  const model = rowAsModel(entity);
1351
1351
  this.pendingDeletes.add(model.id);
1352
1352
  // SyncClient.delete handles: pool remove, transaction queue
1353
- this.syncClient.delete(model);
1353
+ await this.syncClient.delete(model);
1354
1354
  }
1355
1355
  /** Archive a model. Accepts schema-inferred entity shapes (see `save`). */
1356
1356
  async archive(entity) {
1357
1357
  const model = rowAsModel(entity);
1358
1358
  model.archivedAt = new Date();
1359
- this.syncClient.archive(model);
1359
+ await this.syncClient.archive(model);
1360
1360
  }
1361
1361
  /** Unarchive a model. Accepts schema-inferred entity shapes (see `save`). */
1362
1362
  async unarchive(entity) {
1363
1363
  const model = rowAsModel(entity);
1364
1364
  model.archivedAt = null;
1365
- this.syncClient.update(model);
1365
+ await this.syncClient.update(model);
1366
1366
  }
1367
1367
  // ── Query API ────────────────────────────────────────────────────────────
1368
1368
  // `ablo.<model>.local.get` / `.local.list` is the read surface for
@@ -101,7 +101,7 @@ export declare class Database {
101
101
  * where a missing store points to silent data loss. Callers that
102
102
  * already expect optional behavior (e.g. lazy lookups) can omit it.
103
103
  */
104
- getStore(modelName: string, context?: string): InMemoryObjectStore | import("./stores/ObjectStore.js").ObjectStore | undefined;
104
+ getStore(modelName: string, context?: string): import("./stores/ObjectStore.js").ObjectStore | InMemoryObjectStore | undefined;
105
105
  /** Get store or throw if not found (for operations that require the store). */
106
106
  private getRequiredStore;
107
107
  /** Log preserved fields during partial UPDATE merge (debug helper) */
@@ -274,7 +274,7 @@ export declare class Database {
274
274
  * `getStore(modelName, context?)` is defined near the top of this
275
275
  * class — single accessor for both inMemory and IDB modes.
276
276
  */
277
- getAllStores(): Map<string, InMemoryObjectStore> | Map<string, import("./stores/ObjectStore.js").ObjectStore>;
277
+ getAllStores(): Map<string, import("./stores/ObjectStore.js").ObjectStore> | Map<string, InMemoryObjectStore>;
278
278
  /**
279
279
  * Model persistence tracking
280
280
  */
@@ -65,7 +65,7 @@ export declare class LazyReferenceCollection<T extends Model> {
65
65
  private get database();
66
66
  /** Get objectPool from static dependencies */
67
67
  private get objectPool();
68
- constructor(modelName: string, parent: Model, foreignKey: string, customQuery?: any, options?: LazyCollectionOptions);
68
+ constructor(modelName: string, parent: Model, foreignKey: string, customQuery?: any | undefined, options?: LazyCollectionOptions);
69
69
  /**
70
70
  * Set up MobX observation lifecycle hooks
71
71
  * When React components observe this collection, we prevent GC of the parent model
@@ -311,7 +311,7 @@ export class Model {
311
311
  for (const key of keys) {
312
312
  if (key === 'id')
313
313
  continue;
314
- const mod = modified?.get(key);
314
+ const mod = modified.get(key);
315
315
  if (mod) {
316
316
  out[key] = mod.old;
317
317
  }
@@ -358,25 +358,23 @@ export class Model {
358
358
  const errors = [];
359
359
  const modelName = this.getModelName();
360
360
  const properties = getActiveRegistry().getProperties(modelName);
361
- if (properties) {
362
- const json = this.toJSON();
363
- for (const [propName, metadata] of properties) {
364
- // Check required fields
365
- if (!metadata.nullable && !metadata.optional) {
366
- const value = json[propName];
367
- if (value == null || value === '') {
368
- errors.push(`${propName} is required`);
369
- }
361
+ const json = this.toJSON();
362
+ for (const [propName, metadata] of properties) {
363
+ // Check required fields
364
+ if (!metadata.nullable && !metadata.optional) {
365
+ const value = json[propName];
366
+ if (value == null || value === '') {
367
+ errors.push(`${propName} is required`);
370
368
  }
371
- // Run custom validation rules
372
- const rules = this.validationRules[propName];
373
- if (rules) {
374
- const value = json[propName];
375
- for (const rule of rules) {
376
- const error = rule(value);
377
- if (error)
378
- errors.push(error);
379
- }
369
+ }
370
+ // Run custom validation rules
371
+ const rules = this.validationRules[propName];
372
+ if (rules) {
373
+ const value = json[propName];
374
+ for (const rule of rules) {
375
+ const error = rule(value);
376
+ if (error)
377
+ errors.push(error);
380
378
  }
381
379
  }
382
380
  }
@@ -679,20 +677,18 @@ export class Model {
679
677
  if (this.archivedAt !== undefined) {
680
678
  result.archivedAt = this.archivedAt?.toISOString() ?? null;
681
679
  }
682
- if (properties) {
683
- const self = this;
684
- for (const [propName, metadata] of properties) {
685
- // Skip certain types
686
- if (metadata.type === 'ephemeralProperty')
687
- continue;
688
- if (metadata.type === 'referenceModel')
689
- continue;
690
- if (metadata.type === 'referenceCollection')
691
- continue;
692
- const value = self[propName];
693
- if (value !== undefined) {
694
- result[propName] = value;
695
- }
680
+ const self = this;
681
+ for (const [propName, metadata] of properties) {
682
+ // Skip certain types
683
+ if (metadata.type === 'ephemeralProperty')
684
+ continue;
685
+ if (metadata.type === 'referenceModel')
686
+ continue;
687
+ if (metadata.type === 'referenceCollection')
688
+ continue;
689
+ const value = self[propName];
690
+ if (value !== undefined) {
691
+ result[propName] = value;
696
692
  }
697
693
  }
698
694
  return result;
@@ -793,14 +789,12 @@ export class Model {
793
789
  if (hasActiveRegistry()) {
794
790
  const modelName = this.getModelName();
795
791
  const properties = getActiveRegistry().getProperties(modelName);
796
- if (properties) {
797
- const self = this;
798
- for (const [propName, metadata] of properties) {
799
- if (metadata.type === 'referenceCollection') {
800
- const collection = self[propName];
801
- if (collection?.dispose) {
802
- collection.dispose();
803
- }
792
+ const self = this;
793
+ for (const [propName, metadata] of properties) {
794
+ if (metadata.type === 'referenceCollection') {
795
+ const collection = self[propName];
796
+ if (collection) {
797
+ collection.dispose();
804
798
  }
805
799
  }
806
800
  }
@@ -825,11 +819,9 @@ export class Model {
825
819
  const snapshot = {};
826
820
  const modelName = this.getModelName();
827
821
  const properties = getActiveRegistry().getProperties(modelName);
828
- if (properties) {
829
- const json = this.toJSON();
830
- for (const [propName] of properties) {
831
- snapshot[propName] = json[propName];
832
- }
822
+ const json = this.toJSON();
823
+ for (const [propName] of properties) {
824
+ snapshot[propName] = json[propName];
833
825
  }
834
826
  return snapshot;
835
827
  }
@@ -874,17 +866,15 @@ export class Model {
874
866
  snapshot.archivedAt = this.archivedAt;
875
867
  const properties = getActiveRegistry().getProperties(this.getModelName());
876
868
  const self = this;
877
- if (properties) {
878
- for (const [propName, metadata] of properties) {
879
- if (metadata.type === 'ephemeralProperty' ||
880
- metadata.type === 'referenceModel' ||
881
- metadata.type === 'referenceCollection') {
882
- continue;
883
- }
884
- // Reading through the observable getter is the point: it subscribes the
885
- // enclosing MobX reaction to this field.
886
- snapshot[propName] = self[propName];
869
+ for (const [propName, metadata] of properties) {
870
+ if (metadata.type === 'ephemeralProperty' ||
871
+ metadata.type === 'referenceModel' ||
872
+ metadata.type === 'referenceCollection') {
873
+ continue;
887
874
  }
875
+ // Reading through the observable getter is the point: it subscribes the
876
+ // enclosing MobX reaction to this field.
877
+ snapshot[propName] = self[propName];
888
878
  }
889
879
  for (const name of this.getDerivedGetterNames()) {
890
880
  Object.defineProperty(snapshot, name, {
@@ -12,6 +12,8 @@ export class NetworkMonitor extends EventEmitter {
12
12
  // Only `navigator.onLine === false` means offline. Node 18+ exposes a global
13
13
  // `navigator` with `onLine === undefined`, so the naive `navigator.onLine`
14
14
  // would seed `false` (offline) on every server client — start optimistic.
15
+ // DOM types say `onLine` is boolean, but Node exposes it as undefined.
16
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-boolean-literal-compare
15
17
  isOnline = !(typeof navigator !== 'undefined' && navigator.onLine === false);
16
18
  lastOnlineCheck = new Date();
17
19
  constructor(runtime = globalRuntime) {
@@ -47,6 +47,8 @@ export const browserOnlineStatus = {
47
47
  // signal. Don't use `!navigator.onLine`: Node 18+ exposes a global
48
48
  // `navigator` whose `onLine` is `undefined`, which `!` would read as offline —
49
49
  // wedging every Node/server client (agents, worker, MCP) into a false offline.
50
+ // DOM types say `onLine` is boolean, but Node exposes it as undefined.
51
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-boolean-literal-compare
50
52
  return !(typeof navigator !== 'undefined' && navigator.onLine === false);
51
53
  },
52
54
  };
@@ -21,6 +21,7 @@ import type { WriteOptions } from './interfaces/index.js';
21
21
  import { LogPosition } from './logPosition.js';
22
22
  import { type RehydrationStats, type SyncObserver, type SyncState } from './syncClientTypes.js';
23
23
  import type { BootstrapSnapshot } from './syncClientTypes.js';
24
+ import { type BatchFileUploadOptions, type FileUploadOptions } from './fileUploads.js';
24
25
  export type { BootstrapSnapshot, RehydrationStats } from './syncClientTypes.js';
25
26
  export declare class SyncClient extends EventEmitter {
26
27
  private readonly runtime;
@@ -186,9 +187,9 @@ export declare class SyncClient extends EventEmitter {
186
187
  */
187
188
  private captureModelChanges;
188
189
  /** Add new model (CREATE) - works offline */
189
- add(model: Model, options?: WriteOptions): void;
190
+ add(model: Model, options?: WriteOptions): Promise<void> | undefined;
190
191
  /** Update existing model (UPDATE) - works offline */
191
- update(model: Model, options?: WriteOptions): void;
192
+ update(model: Model, options?: WriteOptions, capturedChanges?: Record<string, unknown>): Promise<void> | undefined;
192
193
  /**
193
194
  * Update existing model with pre-computed changes.
194
195
  * Used by saveManyOptimized when incoming models have empty change-tracking
@@ -200,37 +201,12 @@ export declare class SyncClient extends EventEmitter {
200
201
  * but still need optimistic pool updates at the sync layer. */
201
202
  get gql(): import("./interfaces/index.js").MutationExecutor;
202
203
  /** Delete model (DELETE) - works offline */
203
- delete(model: Model, options?: WriteOptions): void;
204
- /**
205
- * Upload a file and create its attachment record. The upload runs through
206
- * the {@link MutationQueue}, and a model is built from the server's
207
- * response and added to the pool.
208
- */
209
- uploadFile(file: File, options: {
210
- id: string;
211
- attachableType: string;
212
- attachableId: string;
213
- metadata?: Record<string, unknown>;
214
- }): Promise<Model | null>;
215
- /**
216
- * Batch upload files — single GraphQL call + parallel S3 PUTs.
217
- *
218
- * Returns the raw `Model[]` built by the object pool (typename is
219
- * determined by the payload the server returns — currently always
220
- * `Attachment`). The SDK has no knowledge of app-specific model classes,
221
- * so it cannot honestly claim a narrower return type; consumers that
222
- * need an `Attachment[]` project through their own typed accessor
223
- * (e.g. `store.query.attachments.findMany({ where: { id: IN ids } })`)
224
- * after the upload resolves.
225
- */
226
- batchUploadFiles(files: File[], options: {
227
- ids: string[];
228
- attachableType: string;
229
- attachableId: string;
230
- metadata?: Record<string, unknown>;
231
- }): Promise<Model[]>;
204
+ delete(model: Model, options?: WriteOptions): Promise<void> | undefined;
205
+ private fileUploadContext;
206
+ uploadFile(file: File, options: FileUploadOptions): Promise<Model | null>;
207
+ batchUploadFiles(files: File[], options: BatchFileUploadOptions): Promise<Model[]>;
232
208
  /** Archive model (ARCHIVE) - works offline */
233
- archive(model: Model): void;
209
+ archive(model: Model): Promise<void> | undefined;
234
210
  /**
235
211
  * Append a mutation to the pending queue and schedule its sync work.
236
212
  *
@@ -409,14 +385,14 @@ export declare class SyncClient extends EventEmitter {
409
385
  * Get detailed debug info for the sync debug page
410
386
  */
411
387
  getDebugInfo(): {
412
- connectionState: "connected" | "connecting" | "disconnected";
388
+ connectionState: "connected" | "disconnected" | "connecting";
413
389
  pendingMutationsCount: number;
414
390
  mutationQueue: {
415
391
  lastSeenSyncId: number;
416
392
  awaitingDeltaCount: number;
417
393
  awaitingDeltaTransactions: {
418
394
  id: string;
419
- type: "archive" | "create" | "delete" | "unarchive" | "update";
395
+ type: "update" | "create" | "delete" | "archive" | "unarchive";
420
396
  modelName: string;
421
397
  modelId: string;
422
398
  syncIdNeeded: number | undefined;
@@ -425,13 +401,13 @@ export declare class SyncClient extends EventEmitter {
425
401
  }[];
426
402
  pendingTransactions: {
427
403
  id: string;
428
- type: "archive" | "create" | "delete" | "unarchive" | "update";
404
+ type: "update" | "create" | "delete" | "archive" | "unarchive";
429
405
  modelName: string;
430
406
  modelId: string;
431
407
  }[];
432
408
  executingTransactions: {
433
409
  id: string;
434
- type: "archive" | "create" | "delete" | "unarchive" | "update";
410
+ type: "update" | "create" | "delete" | "archive" | "unarchive";
435
411
  modelName: string;
436
412
  modelId: string;
437
413
  }[];