@zveltio/sdk 3.0.0-beta.5 → 3.0.0-beta.68

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 (101) hide show
  1. package/dist/client/Auth.d.ts.map +1 -1
  2. package/dist/client/Auth.js +3 -0
  3. package/dist/client/QueryBuilder.d.ts.map +1 -1
  4. package/dist/client/QueryBuilder.js +4 -0
  5. package/dist/client/ZveltioClient.d.ts.map +1 -1
  6. package/dist/client/ZveltioClient.js +5 -0
  7. package/dist/client.d.ts +4 -1
  8. package/dist/client.d.ts.map +1 -1
  9. package/dist/client.js +30 -13
  10. package/dist/core.d.ts.map +1 -1
  11. package/dist/core.js +21 -3
  12. package/dist/crdt.d.ts.map +1 -1
  13. package/dist/crdt.js +11 -2
  14. package/dist/ddl/index.d.ts.map +1 -1
  15. package/dist/errors.d.ts +58 -0
  16. package/dist/errors.d.ts.map +1 -0
  17. package/dist/errors.js +124 -0
  18. package/dist/extension/index.d.ts +419 -19
  19. package/dist/extension/index.d.ts.map +1 -1
  20. package/dist/extension/index.js +3 -0
  21. package/dist/extension/jsonb.d.ts +62 -0
  22. package/dist/extension/jsonb.d.ts.map +1 -0
  23. package/dist/extension/jsonb.js +64 -0
  24. package/dist/extension/multipart.d.ts +58 -0
  25. package/dist/extension/multipart.d.ts.map +1 -0
  26. package/dist/extension/multipart.js +61 -0
  27. package/dist/extension/numeric.d.ts +94 -0
  28. package/dist/extension/numeric.d.ts.map +1 -0
  29. package/dist/extension/numeric.js +162 -0
  30. package/dist/extension/permission-gate.d.ts +15 -0
  31. package/dist/extension/permission-gate.d.ts.map +1 -1
  32. package/dist/extension/permission-gate.js +33 -1
  33. package/dist/index.d.ts +2 -0
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +1 -0
  36. package/dist/local-store.d.ts +5 -0
  37. package/dist/local-store.d.ts.map +1 -1
  38. package/dist/local-store.js +62 -10
  39. package/dist/offline/index.d.ts +3 -3
  40. package/dist/offline/index.js +51 -15
  41. package/dist/publish/index.d.ts.map +1 -1
  42. package/dist/publish/index.js +6 -1
  43. package/dist/realtime.d.ts +9 -1
  44. package/dist/realtime.d.ts.map +1 -1
  45. package/dist/realtime.js +15 -2
  46. package/dist/rpc/index.d.ts.map +1 -1
  47. package/dist/rpc/index.js +2 -0
  48. package/dist/schema-codegen.d.ts +3 -1
  49. package/dist/schema-codegen.d.ts.map +1 -1
  50. package/dist/schema-codegen.js +41 -7
  51. package/dist/schema-watcher.d.ts +1 -1
  52. package/dist/schema-watcher.js +1 -1
  53. package/dist/studio/index.d.ts +1 -0
  54. package/dist/studio/index.d.ts.map +1 -1
  55. package/dist/studio/index.js +3 -0
  56. package/dist/studio/slot-owner-registry.d.ts +13 -0
  57. package/dist/studio/slot-owner-registry.d.ts.map +1 -0
  58. package/dist/studio/slot-owner-registry.js +21 -0
  59. package/dist/svelte.d.ts +1 -1
  60. package/dist/svelte.d.ts.map +1 -1
  61. package/dist/svelte.js +4 -2
  62. package/dist/sync-manager.d.ts +30 -4
  63. package/dist/sync-manager.d.ts.map +1 -1
  64. package/dist/sync-manager.js +101 -15
  65. package/dist/testing/index.d.ts.map +1 -1
  66. package/dist/testing/index.js +23 -1
  67. package/dist/testing/with-test-db.d.ts.map +1 -1
  68. package/dist/testing/with-test-db.js +21 -14
  69. package/dist/tests/client.test.d.ts +2 -0
  70. package/dist/tests/client.test.d.ts.map +1 -0
  71. package/dist/tests/client.test.js +55 -0
  72. package/dist/tests/crdt-orset.test.d.ts +2 -0
  73. package/dist/tests/crdt-orset.test.d.ts.map +1 -0
  74. package/dist/tests/crdt-orset.test.js +23 -0
  75. package/dist/tests/local-store.test.js +44 -0
  76. package/dist/tests/multipart.test.d.ts +2 -0
  77. package/dist/tests/multipart.test.d.ts.map +1 -0
  78. package/dist/tests/multipart.test.js +37 -0
  79. package/dist/tests/offline-provider-crdt.test.d.ts +2 -0
  80. package/dist/tests/offline-provider-crdt.test.d.ts.map +1 -0
  81. package/dist/tests/offline-provider-crdt.test.js +119 -0
  82. package/dist/tests/realtime.test.d.ts +2 -0
  83. package/dist/tests/realtime.test.d.ts.map +1 -0
  84. package/dist/tests/realtime.test.js +39 -0
  85. package/dist/tests/sync-manager.test.d.ts +2 -0
  86. package/dist/tests/sync-manager.test.d.ts.map +1 -0
  87. package/dist/tests/sync-manager.test.js +65 -0
  88. package/dist/tests/with-test-db.test.d.ts +2 -0
  89. package/dist/tests/with-test-db.test.d.ts.map +1 -0
  90. package/dist/tests/with-test-db.test.js +57 -0
  91. package/dist/types/index.d.ts.map +1 -1
  92. package/dist/validate/index.d.ts +49 -0
  93. package/dist/validate/index.d.ts.map +1 -1
  94. package/dist/validate/index.js +494 -7
  95. package/dist/validate/shared-message-keys.d.ts +16 -0
  96. package/dist/validate/shared-message-keys.d.ts.map +1 -0
  97. package/dist/validate/shared-message-keys.js +227 -0
  98. package/package.json +7 -7
  99. package/dist/generate-types.d.ts +0 -37
  100. package/dist/generate-types.d.ts.map +0 -1
  101. package/dist/generate-types.js +0 -157
@@ -46,6 +46,7 @@ export class LocalStore {
46
46
  await this.db.put('meta', { key: 'crdt_lamport', value: this.clock.current });
47
47
  }
48
48
  /** Write a local record and add to sync queue */
49
+ // biome-ignore lint/suspicious/noExplicitAny: legacy any; tracked in hardening plan item H-01
49
50
  async put(collection, id, data) {
50
51
  if (!this.db)
51
52
  throw new Error('LocalStore not opened');
@@ -155,14 +156,33 @@ export class LocalStore {
155
156
  }
156
157
  }
157
158
  /** Apply data from server (via WebSocket or pull) */
158
- async applyServerUpdate(collection, id, data, serverVersion) {
159
+ async applyServerUpdate(collection, id,
160
+ // biome-ignore lint/suspicious/noExplicitAny: legacy any; tracked in hardening plan item H-01
161
+ data, serverVersion) {
159
162
  if (!this.db)
160
163
  throw new Error('LocalStore not opened');
161
164
  const existing = (await this.db.get('records', [collection, id]));
162
- // Conflict detection: local has changes not yet confirmed by server
163
- if (existing && existing._localVersion > existing._serverVersion) {
165
+ // Conflict detection: local has changes not yet confirmed by server.
166
+ //
167
+ // `_syncStatus` is the signal that survives contact with the SyncManager.
168
+ // The version comparison alone does not: `syncNow()` and `pull()` both pass
169
+ // `Date.now()` as the server version, because the collections API returns no
170
+ // version column — so after the first ACK `_serverVersion` is an epoch
171
+ // millisecond count while `_localVersion` is still a small counter, and
172
+ // `2 > 1790144009198` is false forever. Every local edit made after that
173
+ // first sync was overwritten in silence: status `synced`, no `_conflictData`,
174
+ // nothing in `getConflicts()`. The unit tests missed it because they hand
175
+ // this method versions on the same scale as the counter (1, 2, 3, 5).
176
+ //
177
+ // The version comparison is kept as a second signal: it still catches the
178
+ // race where an ACK for an earlier operation marks the record synced while a
179
+ // later one is queued.
180
+ if (existing &&
181
+ (existing._syncStatus !== 'synced' || existing._localVersion > existing._serverVersion)) {
164
182
  // CRDT field-level merge if both sides have CRDT docs
183
+ // biome-ignore lint/suspicious/noExplicitAny: legacy any; tracked in hardening plan item H-01
165
184
  if (existing._crdtDoc && data.__crdt) {
185
+ // biome-ignore lint/suspicious/noExplicitAny: legacy any; tracked in hardening plan item H-01
166
186
  const remoteCrdtDoc = data.__crdt;
167
187
  const merged = mergeLWW(existing._crdtDoc, remoteCrdtDoc);
168
188
  const mergedData = fromDocument(merged);
@@ -192,9 +212,22 @@ export class LocalStore {
192
212
  _serverVersion: serverVersion,
193
213
  _syncStatus: 'synced',
194
214
  _updatedAt: Date.now(),
215
+ // Carried over: rebuilding the record from scratch dropped the CRDT
216
+ // document, so the next merge had nothing on the local side to merge with
217
+ // and fell through to the manual-conflict branch.
218
+ ...(existing?._crdtDoc ? { _crdtDoc: existing._crdtDoc } : {}),
195
219
  };
196
220
  await this.db.put('records', record);
197
221
  }
222
+ /**
223
+ * The server deleted a record: drop the local copy WITHOUT queueing a delete.
224
+ * `delete()` would queue one, echoing the server's own deletion back at it.
225
+ */
226
+ async applyServerDelete(collection, id) {
227
+ if (!this.db)
228
+ throw new Error('LocalStore not opened');
229
+ await this.db.delete('records', [collection, id]);
230
+ }
198
231
  /** Get records with conflicts for UI resolution */
199
232
  async getConflicts(collection) {
200
233
  if (!this.db)
@@ -205,16 +238,35 @@ export class LocalStore {
205
238
  return all;
206
239
  }
207
240
  /** Resolve a conflict (user decides which version wins) */
208
- async resolveConflict(collection, id, resolvedData) {
241
+ async resolveConflict(collection, id,
242
+ // biome-ignore lint/suspicious/noExplicitAny: legacy any; tracked in hardening plan item H-01
243
+ resolvedData) {
209
244
  if (!this.db)
210
245
  throw new Error('LocalStore not opened');
211
246
  const record = (await this.db.get('records', [collection, id]));
212
- if (record) {
213
- record.data = resolvedData;
214
- record._syncStatus = 'pending'; // Re-sync with server
215
- record._localVersion += 1;
216
- await this.db.put('records', record);
217
- }
247
+ if (!record)
248
+ return;
249
+ record.data = resolvedData;
250
+ record._syncStatus = 'pending'; // Re-sync with server
251
+ record._localVersion += 1;
252
+ record._conflictData = undefined;
253
+ // Nothing re-synced it. The status said `pending` and the queue held no
254
+ // operation carrying the resolved data, so a resolution — whether a person
255
+ // chose it in the UI or the CRDT merge produced it — stayed on the device.
256
+ // The only operation for this record was the one the server had already
257
+ // rejected.
258
+ const tx = this.db.transaction(['records', 'sync_queue'], 'readwrite');
259
+ await tx.objectStore('records').put(record);
260
+ await tx.objectStore('sync_queue').add({
261
+ id: `${Date.now()}-${Math.random().toString(36).slice(2)}`,
262
+ collection,
263
+ recordId: id,
264
+ operation: 'update',
265
+ payload: resolvedData,
266
+ attempts: 0,
267
+ createdAt: Date.now(),
268
+ });
269
+ await tx.done;
218
270
  }
219
271
  /** Save an offline blob — returns a temporary ID 'local_blob_<UUID>' */
220
272
  async saveBlob(blob, collection, recordId, field) {
@@ -69,9 +69,9 @@ export interface OfflineProviderConfig {
69
69
  websocket?: typeof WebSocket;
70
70
  }
71
71
  /**
72
- * Public interface every provider implements. Today the CRDT provider
73
- * wraps `SyncManager`; the Electric provider wraps a websocket. Both
74
- * expose the same surface.
72
+ * Public interface every provider implements. The CRDT provider wraps
73
+ * `SyncManager`; the Electric provider wraps a websocket. Both expose the same
74
+ * surface.
75
75
  */
76
76
  export interface OfflineProvider {
77
77
  readonly kind: OfflineProviderKind;
@@ -45,7 +45,7 @@ export class ElectricNotConfigured extends Error {
45
45
  constructor(reason) {
46
46
  super(`Electric SQL provider is not configured: ${reason}. ` +
47
47
  `Set provider: 'crdt' for the default sync path, or stand up an Electric ` +
48
- `service and pass electricUrl. See docs/OFFLINE-SYNC.md.`);
48
+ `service and pass electricUrl. See docs/engine/offline-sync.md.`);
49
49
  this.name = 'ElectricNotConfigured';
50
50
  }
51
51
  }
@@ -76,27 +76,63 @@ export async function createOfflineProvider(config) {
76
76
  return makeCrdtAdapter(config);
77
77
  }
78
78
  // ── CRDT adapter (today's working path) ─────────────────────────────────────
79
- async function makeCrdtAdapter(_config) {
80
- // The existing SyncManager lives in `sync-manager.ts`. We don't import
81
- // it at the top of this file to keep the bundle tree-shakeable for
82
- // consumers who only want the type definitions. Lazy import here.
83
- const { SyncManager } = await import('../sync-manager.js');
84
- void SyncManager;
79
+ /**
80
+ * The CRDT provider, wrapping `SyncManager`.
81
+ *
82
+ * It did not wrap anything. `pull()` was an empty body, `subscribe()` returned
83
+ * an unsubscribe that unsubscribed from nothing, and `push()` returned `0` —
84
+ * which is the worst of the four, because `0` reads as "there was nothing to
85
+ * send" rather than "I did not look". `SyncManager` was imported and then
86
+ * discarded with `void SyncManager`, and the interface docs a few lines up said
87
+ * "Today the CRDT provider wraps SyncManager" while it did not.
88
+ *
89
+ * This is the DEFAULT provider (`config.provider ?? 'crdt'`), so an application
90
+ * that called `createOfflineProvider({ engineUrl })` and pushed on a timer got a
91
+ * clean run and an empty server, forever, with nothing in any log.
92
+ *
93
+ * The store is opened but background sync is NOT started: this interface is
94
+ * explicitly pull/push, and a timer firing `syncNow()` underneath would make the
95
+ * number `push()` returns meaningless.
96
+ */
97
+ async function makeCrdtAdapter(config) {
98
+ // Lazy imports keep the bundle tree-shakeable for consumers who only want the
99
+ // type definitions.
100
+ const [{ SyncManager }, { ZveltioClient }] = await Promise.all([
101
+ import('../sync-manager.js'),
102
+ import('../client.js'),
103
+ ]);
104
+ const client = new ZveltioClient({ baseUrl: config.engineUrl });
105
+ const sync = new SyncManager(client);
106
+ await sync.open();
107
+ const tables = config.tables ?? [];
85
108
  return {
86
109
  kind: 'crdt',
87
110
  async pull() {
88
- /* delegated to SyncManager in the full impl */
111
+ // Silence here is what the old implementation was. A provider asked to
112
+ // pull with nothing to pull from is a configuration mistake, and saying so
113
+ // costs one throw; not saying so costs an empty local database that looks
114
+ // like an empty server.
115
+ if (tables.length === 0) {
116
+ throw new Error('createOfflineProvider: `tables` is empty, so there is nothing to pull. ' +
117
+ 'List the collections to replicate, e.g. { tables: ["orders", "customers"] }.');
118
+ }
119
+ await sync.pull(tables);
89
120
  },
90
121
  async push() {
91
- return 0;
122
+ // The count is the drop in pending operations across the sync. Operations
123
+ // that failed stay pending and are therefore not counted, which is what
124
+ // "the number of ops sent" has to mean if the number is to be worth
125
+ // anything.
126
+ const before = await sync.getStatus();
127
+ await sync.syncNow();
128
+ const after = await sync.getStatus();
129
+ return Math.max(0, before.pending - after.pending);
92
130
  },
93
- subscribe(_table, _cb) {
94
- return () => {
95
- /* */
96
- };
131
+ subscribe(table, cb) {
132
+ return sync.collection(table).subscribe((rows) => cb(rows));
97
133
  },
98
134
  async close() {
99
- /* */
135
+ await sync.stop();
100
136
  },
101
137
  };
102
138
  }
@@ -131,7 +167,7 @@ async function makeElectricProvider(config) {
131
167
  return (await res.json());
132
168
  }
133
169
  let auth = await mintToken();
134
- let electricUrl = config.electricUrl ?? auth.electricUrl;
170
+ const electricUrl = config.electricUrl ?? auth.electricUrl;
135
171
  // Per-table subscription registry.
136
172
  const subscribers = new Map();
137
173
  // Token refresh — fire-and-forget background timer that mints a fresh
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/publish/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,+EAA+E;AAC/E,MAAM,WAAW,cAAc;IAC7B,+DAA+D;IAC/D,KAAK,EAAE,MAAM,CAAC;IACd,2CAA2C;IAC3C,SAAS,EAAE,MAAM,CAAC;IAClB,mCAAmC;IACnC,SAAS,EAAE,UAAU,CAAC;IACtB,oDAAoD;IACpD,UAAU,EAAE,UAAU,CAAC;CACxB;AAED,qFAAqF;AACrF,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,SAAS,CAAC;IACrB,+CAA+C;IAC/C,SAAS,EAAE,MAAM,CAAC;IAClB,oCAAoC;IACpC,YAAY,EAAE,MAAM,CAAC;IACrB,+BAA+B;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,sEAAsE;IACtE,KAAK,EAAE,MAAM,CAAC;CACf;AAED,iBAAS,UAAU,CAAC,KAAK,EAAE,WAAW,GAAG,UAAU,GAAG,MAAM,CAK3D;AAED,iBAAS,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAKhD;AAED,iBAAS,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CAO9C;AAED,6CAA6C;AAC7C,wBAAsB,SAAS,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAGlE;AAED;;;;;;GAMG;AACH,wBAAsB,eAAe,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAY7E;AAQD,qEAAqE;AACrE,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CAE1E;AAED,yEAAyE;AACzE,wBAAsB,eAAe,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CAEzE;AAED;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAC9B,OAAO,EAAE,UAAU,EACnB,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,kBAAkB,CAAC,CAY7B;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAChC,OAAO,EAAE,UAAU,EACnB,SAAS,EAAE,kBAAkB,EAC7B,SAAS,EAAE,UAAU,GACpB,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CA6BvD;AAED;;;;;;;GAOG;AACH,wBAAsB,qBAAqB,CACzC,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,UAAU,GACpB,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAAC,CASlD;AAGD,eAAO,MAAM,MAAM;;;;CAA+C,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/publish/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,+EAA+E;AAC/E,MAAM,WAAW,cAAc;IAC7B,+DAA+D;IAC/D,KAAK,EAAE,MAAM,CAAC;IACd,2CAA2C;IAC3C,SAAS,EAAE,MAAM,CAAC;IAClB,mCAAmC;IACnC,SAAS,EAAE,UAAU,CAAC;IACtB,oDAAoD;IACpD,UAAU,EAAE,UAAU,CAAC;CACxB;AAED,qFAAqF;AACrF,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,SAAS,CAAC;IACrB,+CAA+C;IAC/C,SAAS,EAAE,MAAM,CAAC;IAClB,oCAAoC;IACpC,YAAY,EAAE,MAAM,CAAC;IACrB,+BAA+B;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,sEAAsE;IACtE,KAAK,EAAE,MAAM,CAAC;CACf;AAED,iBAAS,UAAU,CAAC,KAAK,EAAE,WAAW,GAAG,UAAU,GAAG,MAAM,CAK3D;AAED,iBAAS,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAKhD;AAED,iBAAS,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CAO9C;AAED,6CAA6C;AAC7C,wBAAsB,SAAS,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAGlE;AAED;;;;;;GAMG;AACH,wBAAsB,eAAe,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAY7E;AAQD,qEAAqE;AACrE,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CAE1E;AAED,yEAAyE;AACzE,wBAAsB,eAAe,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CAEzE;AAED;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAC9B,OAAO,EAAE,UAAU,EACnB,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,kBAAkB,CAAC,CAa7B;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAChC,OAAO,EAAE,UAAU,EACnB,SAAS,EAAE,kBAAkB,EAC7B,SAAS,EAAE,UAAU,GACpB,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CA+BvD;AAED;;;;;;;GAOG;AACH,wBAAsB,qBAAqB,CACzC,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,UAAU,GACpB,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAAC,CASlD;AAGD,eAAO,MAAM,MAAM;;;;CAA+C,CAAC"}
@@ -86,6 +86,7 @@ export async function signBundle(archive, keypair) {
86
86
  const bundleSha256 = await sha256Hex(archive);
87
87
  const key = await importPrivateKey(keypair.privateJwk);
88
88
  const dataBytes = new TextEncoder().encode(bundleSha256.toLowerCase());
89
+ // biome-ignore lint/suspicious/noExplicitAny: legacy any; tracked in hardening plan item H-01
89
90
  const sigBuf = await crypto.subtle.sign({ name: 'Ed25519' }, key, dataBytes);
90
91
  return {
91
92
  algorithm: 'ed25519',
@@ -123,7 +124,11 @@ export async function verifyBundle(archive, signature, publicJwk) {
123
124
  }
124
125
  const key = await importPublicKey(publicJwk);
125
126
  const dataBytes = new TextEncoder().encode(signature.bundleSha256.toLowerCase());
126
- const ok = await crypto.subtle.verify({ name: 'Ed25519' }, key, sigBytes, dataBytes);
127
+ const ok = await crypto.subtle.verify({ name: 'Ed25519' }, key,
128
+ // biome-ignore lint/suspicious/noExplicitAny: legacy any; tracked in hardening plan item H-01
129
+ sigBytes,
130
+ // biome-ignore lint/suspicious/noExplicitAny: legacy any; tracked in hardening plan item H-01
131
+ dataBytes);
127
132
  return ok ? { ok: true } : { ok: false, reason: 'Ed25519 verification failed' };
128
133
  }
129
134
  /**
@@ -5,7 +5,15 @@ export declare class ZveltioRealtime {
5
5
  private reconnectAttempts;
6
6
  private maxReconnectAttempts;
7
7
  private reconnectTimer;
8
- constructor(baseUrl: string);
8
+ private headers;
9
+ /**
10
+ * @param options.headers Sent with the upgrade — e.g. `{ 'X-API-Key': key }`
11
+ * for a server-side client. Bun and Node (undici) accept them; a browser
12
+ * cannot set headers on a WebSocket and authenticates by session cookie.
13
+ */
14
+ constructor(baseUrl: string, options?: {
15
+ headers?: Record<string, string>;
16
+ });
9
17
  connect(): void;
10
18
  private attemptReconnect;
11
19
  subscribe(collection: string, callback: (data: any) => void): () => void;
@@ -1 +1 @@
1
- {"version":3,"file":"realtime.d.ts","sourceRoot":"","sources":["../src/realtime.ts"],"names":[],"mappings":"AAAA,qBAAa,eAAe;IAC1B,OAAO,CAAC,EAAE,CAA0B;IACpC,OAAO,CAAC,SAAS,CAAoD;IACrE,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,iBAAiB,CAAK;IAC9B,OAAO,CAAC,oBAAoB,CAAM;IAClC,OAAO,CAAC,cAAc,CAA8C;gBAExD,OAAO,EAAE,MAAM;IAK3B,OAAO,IAAI,IAAI;IAyCf,OAAO,CAAC,gBAAgB;IAQxB,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,GAAG,MAAM,IAAI;IAqBxE,UAAU,IAAI,IAAI;CAQnB"}
1
+ {"version":3,"file":"realtime.d.ts","sourceRoot":"","sources":["../src/realtime.ts"],"names":[],"mappings":"AAAA,qBAAa,eAAe;IAC1B,OAAO,CAAC,EAAE,CAA0B;IAEpC,OAAO,CAAC,SAAS,CAAoD;IACrE,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,iBAAiB,CAAK;IAC9B,OAAO,CAAC,oBAAoB,CAAM;IAClC,OAAO,CAAC,cAAc,CAA8C;IACpE,OAAO,CAAC,OAAO,CAAqC;IAEpD;;;;OAIG;gBACS,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAO;IAM/E,OAAO,IAAI,IAAI;IA6Cf,OAAO,CAAC,gBAAgB;IASxB,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,GAAG,MAAM,IAAI;IAqBxE,UAAU,IAAI,IAAI;CAQnB"}
package/dist/realtime.js CHANGED
@@ -1,17 +1,29 @@
1
1
  export class ZveltioRealtime {
2
2
  ws = null;
3
+ // biome-ignore lint/suspicious/noExplicitAny: legacy any; tracked in hardening plan item H-01
3
4
  listeners = new Map();
4
5
  baseUrl;
5
6
  reconnectAttempts = 0;
6
7
  maxReconnectAttempts = 10;
7
8
  reconnectTimer = null;
8
- constructor(baseUrl) {
9
+ headers;
10
+ /**
11
+ * @param options.headers Sent with the upgrade — e.g. `{ 'X-API-Key': key }`
12
+ * for a server-side client. Bun and Node (undici) accept them; a browser
13
+ * cannot set headers on a WebSocket and authenticates by session cookie.
14
+ */
15
+ constructor(baseUrl, options = {}) {
9
16
  // Convert http(s) to ws(s)
10
17
  this.baseUrl = baseUrl.replace(/^http/, 'ws');
18
+ this.headers = options.headers;
11
19
  }
12
20
  connect() {
13
21
  const wsUrl = `${this.baseUrl}/api/ws`;
14
- this.ws = new WebSocket(wsUrl);
22
+ // The two-argument form with `{ headers }` is the Bun/undici extension; the
23
+ // standard constructor stays in use when there is nothing to send.
24
+ this.ws = this.headers
25
+ ? new WebSocket(wsUrl, { headers: this.headers })
26
+ : new WebSocket(wsUrl);
15
27
  this.ws.onopen = () => {
16
28
  this.reconnectAttempts = 0;
17
29
  // Re-subscribe to all existing collections using the server's expected protocol
@@ -55,6 +67,7 @@ export class ZveltioRealtime {
55
67
  this.reconnectAttempts++;
56
68
  this.reconnectTimer = setTimeout(() => this.connect(), delay);
57
69
  }
70
+ // biome-ignore lint/suspicious/noExplicitAny: legacy any; tracked in hardening plan item H-01
58
71
  subscribe(collection, callback) {
59
72
  if (!this.listeners.has(collection))
60
73
  this.listeners.set(collection, new Set());
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/rpc/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AACjC,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAEjC,MAAM,WAAW,gBAAgB;IAC/B,mFAAmF;IACnF,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,kFAAkF;IAClF,UAAU,CAAC,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC5E;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B;;;;OAIG;IACH,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;CACtB;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,EAC3D,IAAI,EAAE,gBAAgB,GACrB,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAoB1B;AAID,YAAY,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/rpc/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AACjC,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAEjC,MAAM,WAAW,gBAAgB;IAC/B,mFAAmF;IACnF,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,kFAAkF;IAClF,UAAU,CAAC,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC5E;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B;;;;OAIG;IACH,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;CACtB;AAED;;;;;;GAMG;AAGH,wBAAgB,eAAe,CAAC,CAAC,SAAS,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,EAC3D,IAAI,EAAE,gBAAgB,GACrB,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAqB1B;AAID,YAAY,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC"}
package/dist/rpc/index.js CHANGED
@@ -38,9 +38,11 @@ import { hc } from 'hono/client';
38
38
  * `ZveltioApi` from `@zveltio/engine/api-types`. For an
39
39
  * extension's own routes, pass `typeof yourApp`.
40
40
  */
41
+ // biome-ignore lint/suspicious/noExplicitAny: legacy any; tracked in hardening plan item H-01
41
42
  export function createRpcClient(opts) {
42
43
  const baseUrl = opts.baseUrl.replace(/\/$/, '');
43
44
  const includeCredentials = opts.includeCredentials !== false;
45
+ // biome-ignore lint/suspicious/noExplicitAny: legacy any; tracked in hardening plan item H-01
44
46
  const customFetch = (async (input, init) => {
45
47
  const dynHeaders = opts.getHeaders ? await opts.getHeaders() : {};
46
48
  const merged = new Headers(init?.headers ?? {});
@@ -8,7 +8,9 @@
8
8
  * column names in editors and `tsc` catches typos.
9
9
  *
10
10
  * Scope intentionally narrow:
11
- * - Handles `CREATE TABLE [IF NOT EXISTS]` and `ALTER TABLE … ADD COLUMN`.
11
+ * - Reads only the UP half of each file (everything before `-- DOWN`).
12
+ * - Handles `CREATE TABLE [IF NOT EXISTS]`, `ALTER TABLE … ADD COLUMN`
13
+ * and `ALTER TABLE … DROP COLUMN`.
12
14
  * - Maps the common Postgres types we see in extension migrations to TS.
13
15
  * - Ignores constraint-only ALTER TABLE statements (PRIMARY KEY, FK).
14
16
  * - Ignores CREATE INDEX, CREATE TYPE, CREATE FUNCTION, etc.
@@ -1 +1 @@
1
- {"version":3,"file":"schema-codegen.d.ts","sourceRoot":"","sources":["../src/schema-codegen.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,mFAAmF;IACnF,MAAM,EAAE,MAAM,CAAC;IACf,qEAAqE;IACrE,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,KAAK,EAAE,CAAC;CACjB;AAsMD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CA+BtD;AA6BD;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,YAAY,CA+C7D;AAID,MAAM,WAAW,cAAc;IAC7B,6DAA6D;IAC7D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,0EAA0E;IAC1E,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,GAAE,cAAmB,GAAG,MAAM,CA0BtF"}
1
+ {"version":3,"file":"schema-codegen.d.ts","sourceRoot":"","sources":["../src/schema-codegen.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,mFAAmF;IACnF,MAAM,EAAE,MAAM,CAAC;IACf,qEAAqE;IACrE,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,KAAK,EAAE,CAAC;CACjB;AAoND;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CA+BtD;AA6BD;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,YAAY,CAoE7D;AAID,MAAM,WAAW,cAAc;IAC7B,6DAA6D;IAC7D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,0EAA0E;IAC1E,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,GAAE,cAAmB,GAAG,MAAM,CA0BtF"}
@@ -8,7 +8,9 @@
8
8
  * column names in editors and `tsc` catches typos.
9
9
  *
10
10
  * Scope intentionally narrow:
11
- * - Handles `CREATE TABLE [IF NOT EXISTS]` and `ALTER TABLE … ADD COLUMN`.
11
+ * - Reads only the UP half of each file (everything before `-- DOWN`).
12
+ * - Handles `CREATE TABLE [IF NOT EXISTS]`, `ALTER TABLE … ADD COLUMN`
13
+ * and `ALTER TABLE … DROP COLUMN`.
12
14
  * - Maps the common Postgres types we see in extension migrations to TS.
13
15
  * - Ignores constraint-only ALTER TABLE statements (PRIMARY KEY, FK).
14
16
  * - Ignores CREATE INDEX, CREATE TYPE, CREATE FUNCTION, etc.
@@ -90,6 +92,19 @@ function mapPgTypeToTs(pgType) {
90
92
  return isArray ? `${tsBase}[]` : tsBase;
91
93
  }
92
94
  // ─── SQL parsing ───────────────────────────────────────────────────────────
95
+ /**
96
+ * Keep only the UP half of a migration file. Extension migrations carry their
97
+ * rollback in the same file, after a line that is exactly `-- DOWN`; the
98
+ * engine's runner (`parseMigrationSql`) splits on that marker and only ever
99
+ * executes the UP half. Codegen read both halves, so a `DROP TABLE` /
100
+ * `ADD COLUMN` written for rollback described the generated types as much as
101
+ * the real schema did. Same anchored regex as the runner — a loose `-- DOWN`
102
+ * prefix match once cost the engine half a schema.
103
+ */
104
+ function upSection(sql) {
105
+ const i = sql.search(/^--\s*DOWN\s*$/im);
106
+ return i < 0 ? sql : sql.slice(0, i);
107
+ }
93
108
  /**
94
109
  * Strip line comments (`-- …`) and block comments (`/* … * /`) so they don't
95
110
  * confuse the table/column regexes. Dollar-quoted blocks are preserved
@@ -301,13 +316,18 @@ export function parseSchema(sqlChunks) {
301
316
  }
302
317
  return tables.get(name);
303
318
  };
304
- const allSql = sqlChunks.map(stripComments).join('\n');
319
+ const allSql = sqlChunks.map(upSection).map(stripComments).join('\n');
305
320
  // CREATE TABLE [IF NOT EXISTS] <name> ( <body> )
306
321
  const createRe = /CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?(?:"([^"]+)"|([a-zA-Z_][a-zA-Z0-9_]*))\s*\(/gi;
307
- let m;
308
- while ((m = createRe.exec(allSql)) !== null) {
322
+ for (const m of allSql.matchAll(createRe)) {
309
323
  const tableName = m[1] ?? m[2];
310
- const openIdx = createRe.lastIndex - 1; // position of '('
324
+ // From the MATCH, not from the regex's `lastIndex`.
325
+ //
326
+ // `matchAll` clones the regex internally, so `createRe.lastIndex` stays 0
327
+ // here — it is only advanced by `exec`. Converting this loop from
328
+ // `while ((m = createRe.exec(...)))` and keeping `lastIndex` silently made
329
+ // every table body start at the top of the file; five tests caught it.
330
+ const openIdx = m.index + m[0].length - 1; // position of '('
311
331
  const closeIdx = findMatchingParen(allSql, openIdx);
312
332
  if (closeIdx < 0)
313
333
  continue; // unbalanced — skip
@@ -316,11 +336,10 @@ export function parseSchema(sqlChunks) {
316
336
  const tableCols = ensureTable(tableName);
317
337
  for (const c of columns)
318
338
  tableCols.set(c.name, c);
319
- createRe.lastIndex = closeIdx + 1;
320
339
  }
321
340
  // ALTER TABLE <name> ADD COLUMN [IF NOT EXISTS] <col> <type> <rest>
322
341
  const alterRe = /ALTER\s+TABLE\s+(?:"([^"]+)"|([a-zA-Z_][a-zA-Z0-9_]*))\s+ADD\s+COLUMN\s+(?:IF\s+NOT\s+EXISTS\s+)?(?:"([^"]+)"|([a-zA-Z_][a-zA-Z0-9_]*))\s+([^;]+?)(?=;|$)/gi;
323
- while ((m = alterRe.exec(allSql)) !== null) {
342
+ for (const m of allSql.matchAll(alterRe)) {
324
343
  const tableName = m[1] ?? m[2];
325
344
  const colName = m[3] ?? m[4];
326
345
  const tail = m[5];
@@ -330,6 +349,21 @@ export function parseSchema(sqlChunks) {
330
349
  tableCols.set(col.name, col);
331
350
  }
332
351
  }
352
+ // ALTER TABLE <name> DROP COLUMN [IF EXISTS] <col>
353
+ //
354
+ // Run last, after every CREATE and ADD COLUMN: a column the migrations
355
+ // removed used to stay in the generated types, so `ctx.db` promised a
356
+ // column Postgres no longer has — the silent runtime failure this module
357
+ // exists to prevent.
358
+ //
359
+ // ponytail: a third pass, not an ordered walk of the statements. A column
360
+ // dropped and then re-added by a later migration would be lost; no
361
+ // extension does that today. Make the parser statement-ordered if one does.
362
+ const dropRe = /ALTER\s+TABLE\s+(?:IF\s+EXISTS\s+)?(?:"([^"]+)"|([a-zA-Z_][a-zA-Z0-9_]*))\s+DROP\s+COLUMN\s+(?:IF\s+EXISTS\s+)?(?:"([^"]+)"|([a-zA-Z_][a-zA-Z0-9_]*))/gi;
363
+ for (const m of allSql.matchAll(dropRe)) {
364
+ // Never `ensureTable` here — a DROP alone must not invent a table.
365
+ tables.get(m[1] ?? m[2])?.delete(m[3] ?? m[4]);
366
+ }
333
367
  return {
334
368
  tables: order.map((name) => ({ name, columns: [...tables.get(name).values()] })),
335
369
  };
@@ -6,7 +6,7 @@
6
6
  * updated, or deleted.
7
7
  *
8
8
  * Usage (in `zveltio dev --watch`):
9
- * import { watchSchema } from '@zveltio/sdk/schema-watcher';
9
+ * import { watchSchema } from '@zveltio/sdk';
10
10
  * await watchSchema('http://localhost:3000', './src/zveltio-types.d.ts');
11
11
  */
12
12
  export interface CollectionField {
@@ -6,7 +6,7 @@
6
6
  * updated, or deleted.
7
7
  *
8
8
  * Usage (in `zveltio dev --watch`):
9
- * import { watchSchema } from '@zveltio/sdk/schema-watcher';
9
+ * import { watchSchema } from '@zveltio/sdk';
10
10
  * await watchSchema('http://localhost:3000', './src/zveltio-types.d.ts');
11
11
  */
12
12
  const TYPE_MAP = {
@@ -35,6 +35,7 @@ export declare function applyFormAlterHooks(hooks: FormAlterHook[], schema: Form
35
35
  export declare function sortSlotContributions<T extends {
36
36
  priority?: number;
37
37
  }>(items: T[]): T[];
38
+ export { removeOwnerFromSlots, registerOwnedOnSlot, type OwnedSlotContribution, } from './slot-owner-registry.js';
38
39
  /** Register a top-level Studio page contributed by this extension. */
39
40
  export declare function registerRoute(route: StudioRoute): void;
40
41
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/studio/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EACV,YAAY,EACZ,aAAa,EACb,UAAU,EAEV,gBAAgB,EAChB,WAAW,EACX,eAAe,EACf,mBAAmB,EACpB,MAAM,uBAAuB,CAAC;AAE/B,UAAU,SAAU,SAAQ,YAAY;IACtC,qDAAqD;IACrD,MAAM,IAAI,UAAU,CAAC;CACtB;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,UAAU,GAAG,SAAS,CAoE5D;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,aAAa,EAAE,EACtB,MAAM,EAAE,UAAU,EAClB,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAChC,UAAU,CAWZ;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,SAAS;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,CAGtF;AAoCD,sEAAsE;AACtE,wBAAgB,aAAa,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,CAEtD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,gBAAgB,GAAG,IAAI,CAE/E;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,GAAG,IAAI,CAE3E;AAED,yEAAyE;AACzE,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,eAAe,GAAG,IAAI,CAE3D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,mBAAmB,GAAG,IAAI,CAEvE;AAED,0EAA0E;AAC1E,wBAAgB,YAAY,IAAI,MAAM,CAGrC;AAID,YAAY,EACV,kBAAkB,EAClB,WAAW,EACX,eAAe,EACf,mBAAmB,EACnB,gBAAgB,EAChB,aAAa,EACb,YAAY,EACZ,UAAU,EACV,SAAS,GACV,MAAM,uBAAuB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/studio/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EACV,YAAY,EACZ,aAAa,EACb,UAAU,EAEV,gBAAgB,EAChB,WAAW,EACX,eAAe,EACf,mBAAmB,EACpB,MAAM,uBAAuB,CAAC;AAE/B,UAAU,SAAU,SAAQ,YAAY;IACtC,qDAAqD;IACrD,MAAM,IAAI,UAAU,CAAC;CACtB;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,UAAU,GAAG,SAAS,CAoE5D;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,aAAa,EAAE,EACtB,MAAM,EAAE,UAAU,EAClB,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAChC,UAAU,CAWZ;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,SAAS;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,CAGtF;AAED,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,KAAK,qBAAqB,GAC3B,MAAM,0BAA0B,CAAC;AAqClC,sEAAsE;AACtE,wBAAgB,aAAa,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,CAEtD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,gBAAgB,GAAG,IAAI,CAE/E;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,GAAG,IAAI,CAE3E;AAED,yEAAyE;AACzE,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,eAAe,GAAG,IAAI,CAE3D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,mBAAmB,GAAG,IAAI,CAEvE;AAED,0EAA0E;AAC1E,wBAAgB,YAAY,IAAI,MAAM,CAIrC;AAID,YAAY,EACV,kBAAkB,EAClB,WAAW,EACX,eAAe,EACf,mBAAmB,EACnB,gBAAgB,EAChB,aAAa,EACb,YAAY,EACZ,UAAU,EACV,SAAS,GACV,MAAM,uBAAuB,CAAC"}
@@ -109,9 +109,11 @@ export function sortSlotContributions(items) {
109
109
  // We rely on Array.prototype.sort being stable (TC39 since 2019).
110
110
  return items.slice().sort((a, b) => (a.priority ?? 100) - (b.priority ?? 100));
111
111
  }
112
+ export { removeOwnerFromSlots, registerOwnedOnSlot, } from './slot-owner-registry.js';
112
113
  function getStudioGlobal() {
113
114
  if (typeof window === 'undefined')
114
115
  return null;
116
+ // biome-ignore lint/suspicious/noExplicitAny: legacy any; tracked in hardening plan item H-01
115
117
  const g = window.__zveltio;
116
118
  if (!g) {
117
119
  console.warn('[zveltio/sdk/studio] window.__zveltio is not installed. Is the bundle running inside Studio?');
@@ -176,5 +178,6 @@ export function registerAssetPreview(handler) {
176
178
  export function getEngineUrl() {
177
179
  if (typeof window === 'undefined')
178
180
  return '';
181
+ // biome-ignore lint/suspicious/noExplicitAny: legacy any; tracked in hardening plan item H-01
179
182
  return window.__ZVELTIO_ENGINE_URL__ || getStudioGlobal()?.engineUrl || '';
180
183
  }
@@ -0,0 +1,13 @@
1
+ import type { SlotContribution } from '../extension/index.js';
2
+ /** Owner tag set by compile-time `contribute.ts` modules; absent on legacy window.__zveltio calls. */
3
+ export type OwnedSlotContribution = SlotContribution & {
4
+ owner?: string;
5
+ };
6
+ /** Remove every contribution registered by `owner` across all slot names. */
7
+ export declare function removeOwnerFromSlots(slots: Record<string, OwnedSlotContribution[]>, owner: string): Record<string, OwnedSlotContribution[]>;
8
+ /**
9
+ * Register (or replace) a single owner's contribution on one slot.
10
+ * Prior entries from the same owner on that slot are dropped first.
11
+ */
12
+ export declare function registerOwnedOnSlot(slots: Record<string, OwnedSlotContribution[]>, owner: string, slotName: string, contribution: SlotContribution): Record<string, OwnedSlotContribution[]>;
13
+ //# sourceMappingURL=slot-owner-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slot-owner-registry.d.ts","sourceRoot":"","sources":["../../src/studio/slot-owner-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAE9D,sGAAsG;AACtG,MAAM,MAAM,qBAAqB,GAAG,gBAAgB,GAAG;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAE1E,6EAA6E;AAC7E,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,qBAAqB,EAAE,CAAC,EAC9C,KAAK,EAAE,MAAM,GACZ,MAAM,CAAC,MAAM,EAAE,qBAAqB,EAAE,CAAC,CAOzC;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,qBAAqB,EAAE,CAAC,EAC9C,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,gBAAgB,GAC7B,MAAM,CAAC,MAAM,EAAE,qBAAqB,EAAE,CAAC,CAMzC"}
@@ -0,0 +1,21 @@
1
+ /** Remove every contribution registered by `owner` across all slot names. */
2
+ export function removeOwnerFromSlots(slots, owner) {
3
+ const out = {};
4
+ for (const [name, list] of Object.entries(slots)) {
5
+ const kept = list.filter((c) => c.owner !== owner);
6
+ if (kept.length > 0)
7
+ out[name] = kept;
8
+ }
9
+ return out;
10
+ }
11
+ /**
12
+ * Register (or replace) a single owner's contribution on one slot.
13
+ * Prior entries from the same owner on that slot are dropped first.
14
+ */
15
+ export function registerOwnedOnSlot(slots, owner, slotName, contribution) {
16
+ const out = { ...slots };
17
+ const prev = out[slotName] ?? [];
18
+ const kept = prev.filter((c) => c.owner !== owner);
19
+ out[slotName] = [...kept, { ...contribution, owner }];
20
+ return out;
21
+ }
package/dist/svelte.d.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  * ```svelte
7
7
  * <script lang="ts">
8
8
  * import { SyncManager } from '@zveltio/sdk';
9
- * import { useSyncCollection, useSyncStatus } from '@zveltio/sdk/svelte';
9
+ * import { useSyncCollection, useSyncStatus } from '@zveltio/sdk';
10
10
  *
11
11
  * let todos = $state<any[]>([]);
12
12
  * const unsub = useSyncCollection(sync, 'todos', (records) => { todos = records; });
@@ -1 +1 @@
1
- {"version":3,"file":"svelte.d.ts","sourceRoot":"","sources":["../src/svelte.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,WAAW,EACjB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,KAAK,IAAI,GAC/B,MAAM,IAAI,CAGZ;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,WAAW,EACjB,MAAM,EAAE,CAAC,MAAM,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,KAAK,IAAI,EACnF,UAAU,SAAO,GAChB,MAAM,IAAI,CAeZ"}
1
+ {"version":3,"file":"svelte.d.ts","sourceRoot":"","sources":["../src/svelte.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,WAAW,EACjB,UAAU,EAAE,MAAM,EAElB,MAAM,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,KAAK,IAAI,GAC/B,MAAM,IAAI,CAGZ;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,WAAW,EACjB,MAAM,EAAE,CAAC,MAAM,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,KAAK,IAAI,EACnF,UAAU,SAAO,GAChB,MAAM,IAAI,CAeZ"}
package/dist/svelte.js CHANGED
@@ -6,7 +6,7 @@
6
6
  * ```svelte
7
7
  * <script lang="ts">
8
8
  * import { SyncManager } from '@zveltio/sdk';
9
- * import { useSyncCollection, useSyncStatus } from '@zveltio/sdk/svelte';
9
+ * import { useSyncCollection, useSyncStatus } from '@zveltio/sdk';
10
10
  *
11
11
  * let todos = $state<any[]>([]);
12
12
  * const unsub = useSyncCollection(sync, 'todos', (records) => { todos = records; });
@@ -23,7 +23,9 @@
23
23
  * Calls `setter` immediately with current state and on each update.
24
24
  * Returns the unsubscribe function.
25
25
  */
26
- export function useSyncCollection(sync, collection, setter) {
26
+ export function useSyncCollection(sync, collection,
27
+ // biome-ignore lint/suspicious/noExplicitAny: legacy any; tracked in hardening plan item H-01
28
+ setter) {
27
29
  const col = sync.collection(collection);
28
30
  return col.subscribe(setter);
29
31
  }