@arcote.tech/arc-cli 0.7.16 → 0.7.18

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.js CHANGED
@@ -14004,6 +14004,38 @@ class AuthAdapter {
14004
14004
  this.scopes.clear();
14005
14005
  }
14006
14006
  }
14007
+ function triggerModuleSync(scope, timeoutMs = DEFAULT_TIMEOUT_MS) {
14008
+ if (!provider) {
14009
+ latestSync = Promise.resolve();
14010
+ return latestSync;
14011
+ }
14012
+ const seq = ++syncSeq;
14013
+ const run = (async () => {
14014
+ try {
14015
+ await provider(scope);
14016
+ } catch (err3) {
14017
+ console.warn("[arc] module sync failed during setToken:", err3);
14018
+ }
14019
+ })();
14020
+ const guarded = new Promise((resolve) => {
14021
+ let settled = false;
14022
+ const done = () => {
14023
+ if (settled)
14024
+ return;
14025
+ settled = true;
14026
+ resolve();
14027
+ };
14028
+ const timer = setTimeout(() => {
14029
+ console.warn(`[arc] module sync did not complete within ${timeoutMs}ms; proceeding anyway.`);
14030
+ done();
14031
+ }, timeoutMs);
14032
+ timer?.unref?.();
14033
+ run.then(done, done).finally(() => clearTimeout(timer));
14034
+ });
14035
+ if (seq === syncSeq)
14036
+ latestSync = guarded;
14037
+ return guarded;
14038
+ }
14007
14039
 
14008
14040
  class EventWire {
14009
14041
  baseUrl;
@@ -14017,7 +14049,8 @@ class EventWire {
14017
14049
  onSyncedCallback;
14018
14050
  reconnectTimeout;
14019
14051
  syncRequested = false;
14020
- viewSubscriptions = new Map;
14052
+ querySubscriptions = new Map;
14053
+ querySubCounter = 0;
14021
14054
  enableEventSync;
14022
14055
  constructor(baseUrl, options) {
14023
14056
  this.baseUrl = baseUrl;
@@ -14072,7 +14105,7 @@ class EventWire {
14072
14105
  this.requestSync();
14073
14106
  }
14074
14107
  this.flushPendingEvents();
14075
- this.sendAllViewSubscriptions();
14108
+ this.sendAllQuerySubscriptions();
14076
14109
  } else {
14077
14110
  console.log(`[EventWire] onopen called but ws is not OPEN, readyState:`, this.ws?.readyState);
14078
14111
  }
@@ -14148,24 +14181,29 @@ class EventWire {
14148
14181
  onSynced(callback) {
14149
14182
  this.onSyncedCallback = callback;
14150
14183
  }
14151
- subscribeView(element, scope, callbacks) {
14152
- const key = `${scope}:${element}`;
14153
- this.viewSubscriptions.set(key, callbacks);
14184
+ subscribeQuery(descriptor, scope, callbacks) {
14185
+ const subscriptionId = `qs_${this.instanceId}_${++this.querySubCounter}`;
14186
+ this.querySubscriptions.set(subscriptionId, {
14187
+ descriptor,
14188
+ scope,
14189
+ callbacks
14190
+ });
14154
14191
  if (this.state === "connected" && this.ws) {
14155
14192
  this.ws.send(JSON.stringify({
14156
- type: "subscribe-view",
14157
- element,
14193
+ type: "subscribe-query",
14194
+ subscriptionId,
14195
+ descriptor,
14158
14196
  scope
14159
14197
  }));
14160
14198
  }
14199
+ return subscriptionId;
14161
14200
  }
14162
- unsubscribeView(element, scope) {
14163
- this.viewSubscriptions.delete(`${scope}:${element}`);
14201
+ unsubscribeQuery(subscriptionId) {
14202
+ this.querySubscriptions.delete(subscriptionId);
14164
14203
  if (this.state === "connected" && this.ws) {
14165
14204
  this.ws.send(JSON.stringify({
14166
- type: "unsubscribe-view",
14167
- element,
14168
- scope
14205
+ type: "unsubscribe-query",
14206
+ subscriptionId
14169
14207
  }));
14170
14208
  }
14171
14209
  }
@@ -14200,17 +14238,17 @@ class EventWire {
14200
14238
  this.lastHostEventId = message.lastHostEventId;
14201
14239
  }
14202
14240
  break;
14203
- case "view-snapshot": {
14204
- const sub = this.viewSubscriptions.get(`${message.scope}:${message.element}`);
14241
+ case "query-snapshot": {
14242
+ const sub = this.querySubscriptions.get(message.subscriptionId);
14205
14243
  if (sub) {
14206
- sub.onSnapshot(message.items ?? []);
14244
+ sub.callbacks.onSnapshot(message.result ?? null);
14207
14245
  }
14208
14246
  break;
14209
14247
  }
14210
- case "view-changes": {
14211
- const sub = this.viewSubscriptions.get(`${message.scope}:${message.element}`);
14248
+ case "query-changes": {
14249
+ const sub = this.querySubscriptions.get(message.subscriptionId);
14212
14250
  if (sub && Array.isArray(message.changes)) {
14213
- sub.onChanges(message.changes);
14251
+ sub.callbacks.onChanges(message.changes);
14214
14252
  }
14215
14253
  break;
14216
14254
  }
@@ -14238,17 +14276,15 @@ class EventWire {
14238
14276
  this.pendingEvents = [];
14239
14277
  }
14240
14278
  }
14241
- sendAllViewSubscriptions() {
14279
+ sendAllQuerySubscriptions() {
14242
14280
  if (!this.ws || this.state !== "connected")
14243
14281
  return;
14244
- for (const key of this.viewSubscriptions.keys()) {
14245
- const sepIdx = key.indexOf(":");
14246
- const scope = key.slice(0, sepIdx);
14247
- const element = key.slice(sepIdx + 1);
14282
+ for (const [subscriptionId, sub] of this.querySubscriptions) {
14248
14283
  this.ws.send(JSON.stringify({
14249
- type: "subscribe-view",
14250
- element,
14251
- scope
14284
+ type: "subscribe-query",
14285
+ subscriptionId,
14286
+ descriptor: sub.descriptor,
14287
+ scope: sub.scope
14252
14288
  }));
14253
14289
  }
14254
14290
  }
@@ -14267,19 +14303,12 @@ class LocalEventPublisher2 {
14267
14303
  views = [];
14268
14304
  syncCallback;
14269
14305
  subscribers = new Map;
14270
- viewChangesCallbacks = new Set;
14271
14306
  constructor(dataStorage) {
14272
14307
  this.dataStorage = dataStorage;
14273
14308
  }
14274
14309
  onPublish(callback) {
14275
14310
  this.syncCallback = callback;
14276
14311
  }
14277
- onViewChanges(callback) {
14278
- this.viewChangesCallbacks.add(callback);
14279
- return () => {
14280
- this.viewChangesCallbacks.delete(callback);
14281
- };
14282
- }
14283
14312
  registerViews(views) {
14284
14313
  this.views = views;
14285
14314
  }
@@ -14347,19 +14376,7 @@ class LocalEventPublisher2 {
14347
14376
  });
14348
14377
  const viewChanges = await this.collectViewChanges(event);
14349
14378
  allChanges.push(...viewChanges);
14350
- const viewStoreNames = new Set(viewChanges.map((c2) => c2.store));
14351
- const committed = await this.dataStorage.commitChanges(allChanges, {
14352
- captureRowsFor: viewStoreNames
14353
- });
14354
- if (committed.length > 0 && this.viewChangesCallbacks.size > 0) {
14355
- for (const callback of this.viewChangesCallbacks) {
14356
- try {
14357
- callback(committed);
14358
- } catch (error) {
14359
- console.error(`[EventPublisher] onViewChanges callback error:`, error);
14360
- }
14361
- }
14362
- }
14379
+ await this.dataStorage.commitChanges(allChanges);
14363
14380
  await this.notifySubscribers(event);
14364
14381
  if (this.syncCallback) {
14365
14382
  this.syncCallback(event);
@@ -14740,9 +14757,8 @@ function typeValidatorBuilder(typeName, comparatorStrategy) {
14740
14757
  }
14741
14758
 
14742
14759
  class DataStorage {
14743
- async commitChanges(changes, _options) {
14760
+ async commitChanges(changes) {
14744
14761
  await Promise.all(changes.map(({ store, changes: changes2 }) => this.getStore(store).applyChanges(changes2)));
14745
- return [];
14746
14762
  }
14747
14763
  }
14748
14764
 
@@ -15099,6 +15115,47 @@ function deepMerge(target, source) {
15099
15115
  function isPlainObject(item) {
15100
15116
  return item && typeof item === "object" && !Array.isArray(item) && !(item instanceof Date) && Object.prototype.toString.call(item) === "[object Object]";
15101
15117
  }
15118
+ function murmurHash(key, seed = 0) {
15119
+ let remainder, bytes, h1, h1b, c1, c2, k1, i;
15120
+ remainder = key.length & 3;
15121
+ bytes = key.length - remainder;
15122
+ h1 = seed;
15123
+ c1 = 3432918353;
15124
+ c2 = 461845907;
15125
+ i = 0;
15126
+ while (i < bytes) {
15127
+ k1 = key.charCodeAt(i) & 255 | (key.charCodeAt(++i) & 255) << 8 | (key.charCodeAt(++i) & 255) << 16 | (key.charCodeAt(++i) & 255) << 24;
15128
+ ++i;
15129
+ k1 = (k1 & 65535) * c1 + (((k1 >>> 16) * c1 & 65535) << 16) & 4294967295;
15130
+ k1 = k1 << 15 | k1 >>> 17;
15131
+ k1 = (k1 & 65535) * c2 + (((k1 >>> 16) * c2 & 65535) << 16) & 4294967295;
15132
+ h1 ^= k1;
15133
+ h1 = h1 << 13 | h1 >>> 19;
15134
+ h1b = (h1 & 65535) * 5 + (((h1 >>> 16) * 5 & 65535) << 16) & 4294967295;
15135
+ h1 = (h1b & 65535) + 27492 + (((h1b >>> 16) + 58964 & 65535) << 16);
15136
+ }
15137
+ k1 = 0;
15138
+ if (remainder >= 3) {
15139
+ k1 ^= (key.charCodeAt(i + 2) & 255) << 16;
15140
+ }
15141
+ if (remainder >= 2) {
15142
+ k1 ^= (key.charCodeAt(i + 1) & 255) << 8;
15143
+ }
15144
+ if (remainder >= 1) {
15145
+ k1 ^= key.charCodeAt(i) & 255;
15146
+ k1 = (k1 & 65535) * c1 + (((k1 >>> 16) * c1 & 65535) << 16) & 4294967295;
15147
+ k1 = k1 << 15 | k1 >>> 17;
15148
+ k1 = (k1 & 65535) * c2 + (((k1 >>> 16) * c2 & 65535) << 16) & 4294967295;
15149
+ h1 ^= k1;
15150
+ }
15151
+ h1 ^= key.length;
15152
+ h1 ^= h1 >>> 16;
15153
+ h1 = (h1 & 65535) * 2246822507 + (((h1 >>> 16) * 2246822507 & 65535) << 16) & 4294967295;
15154
+ h1 ^= h1 >>> 13;
15155
+ h1 = (h1 & 65535) * 3266489909 + (((h1 >>> 16) * 3266489909 & 65535) << 16) & 4294967295;
15156
+ h1 ^= h1 >>> 16;
15157
+ return h1 >>> 0;
15158
+ }
15102
15159
  function resolveQueryChange(currentResult, event3, options) {
15103
15160
  const index = currentResult.findIndex((e2) => e2._id === event3.id);
15104
15161
  const isInCurrentResult = index !== -1;
@@ -15200,8 +15257,8 @@ class ObservableDataStorage {
15200
15257
  getReadWriteTransaction() {
15201
15258
  return this.source.getReadWriteTransaction();
15202
15259
  }
15203
- commitChanges(changes, options) {
15204
- return this.source.commitChanges(changes, options);
15260
+ commitChanges(changes) {
15261
+ return this.source.commitChanges(changes);
15205
15262
  }
15206
15263
  trackQuery(storeName, options, result, listener4) {
15207
15264
  const key = this.getQueryKey(storeName, options);
@@ -15214,7 +15271,8 @@ class ObservableDataStorage {
15214
15271
  }
15215
15272
  handleStoreChange(storeName, events) {
15216
15273
  let hasChanges = false;
15217
- for (const query of this.trackedQueries.values()) {
15274
+ const staleKeys = [];
15275
+ for (const [key, query] of this.trackedQueries) {
15218
15276
  if (query.storeName !== storeName)
15219
15277
  continue;
15220
15278
  let currentResult = query.result;
@@ -15226,10 +15284,20 @@ class ObservableDataStorage {
15226
15284
  queryChanged = true;
15227
15285
  }
15228
15286
  }
15229
- if (queryChanged) {
15230
- query.result = currentResult;
15287
+ if (!queryChanged)
15288
+ continue;
15289
+ if (query.options.limit !== undefined && query.result.length === query.options.limit && currentResult.length < query.options.limit) {
15290
+ staleKeys.push(key);
15231
15291
  hasChanges = true;
15292
+ continue;
15232
15293
  }
15294
+ query.result = currentResult;
15295
+ hasChanges = true;
15296
+ }
15297
+ for (const key of staleKeys) {
15298
+ const query = this.trackedQueries.get(key);
15299
+ this.source.getStore(query.storeName).unsubscribe(query.listener);
15300
+ this.trackedQueries.delete(key);
15233
15301
  }
15234
15302
  if (hasChanges) {
15235
15303
  this.onChange();
@@ -15402,6 +15470,7 @@ class ScopedModel3 {
15402
15470
  for (const listener4 of this.tokenListeners) {
15403
15471
  listener4();
15404
15472
  }
15473
+ return triggerModuleSync(this.scopeName);
15405
15474
  }
15406
15475
  getToken() {
15407
15476
  return this.authAdapter.getToken();
@@ -15499,243 +15568,136 @@ class Model2 {
15499
15568
  return s;
15500
15569
  }
15501
15570
  }
15502
-
15503
- class StreamingQueryCache {
15504
- stores = new Map;
15505
- views = [];
15506
- activeStreams = new Map;
15507
- pendingUnsubscribes = new Map;
15508
- static UNSUBSCRIBE_DELAY_MS = 5000;
15509
- storeKey(viewName, scope) {
15510
- return `${scope ?? DEFAULT_SCOPE}:${viewName}`;
15511
- }
15512
- registerViews(views) {
15513
- this.views = views;
15514
- }
15515
- getStore(viewName, scope) {
15516
- const key = this.storeKey(viewName, scope);
15517
- if (!this.stores.has(key)) {
15518
- this.stores.set(key, new StreamingStore);
15571
+ function applyQueryChanges(result, changes) {
15572
+ const next = [...result];
15573
+ for (const change of changes) {
15574
+ if (change.type === "delete") {
15575
+ const idx = next.findIndex((it) => it._id === change.id);
15576
+ if (idx !== -1)
15577
+ next.splice(idx, 1);
15519
15578
  }
15520
- return this.stores.get(key);
15521
- }
15522
- hasData(viewName, scope) {
15523
- const store = this.stores.get(this.storeKey(viewName, scope));
15524
- return store ? store.hasData() : false;
15525
15579
  }
15526
- registerStream(key, createStream) {
15527
- const pending = this.pendingUnsubscribes.get(key);
15528
- if (pending) {
15529
- clearTimeout(pending);
15530
- this.pendingUnsubscribes.delete(key);
15580
+ for (const change of changes) {
15581
+ if (change.type === "set") {
15582
+ const idx = next.findIndex((it) => it._id === change.id);
15583
+ if (idx !== -1)
15584
+ next.splice(idx, 1);
15585
+ next.splice(change.index, 0, change.item);
15531
15586
  }
15532
- const existing = this.activeStreams.get(key);
15533
- if (existing) {
15534
- existing.refCount++;
15535
- return {
15536
- unsubscribe: () => this.unregisterStream(key),
15537
- wasReused: true
15587
+ }
15588
+ return next;
15589
+ }
15590
+
15591
+ class StreamingQueryCache {
15592
+ entries = new Map;
15593
+ static UNSUBSCRIBE_DELAY_MS = 5000;
15594
+ entryKey(descriptor, scope) {
15595
+ return `${scope ?? "default"}:${murmurHash(JSON.stringify(descriptor))}`;
15596
+ }
15597
+ subscribe(descriptor, scope, eventWire, onChange) {
15598
+ const key = this.entryKey(descriptor, scope);
15599
+ let entry = this.entries.get(key);
15600
+ if (entry) {
15601
+ if (entry.pendingUnsub) {
15602
+ clearTimeout(entry.pendingUnsub);
15603
+ entry.pendingUnsub = undefined;
15604
+ }
15605
+ entry.refCount++;
15606
+ } else {
15607
+ const newEntry = {
15608
+ result: undefined,
15609
+ hasResult: false,
15610
+ listeners: new Set,
15611
+ refCount: 1,
15612
+ subscriptionId: ""
15538
15613
  };
15614
+ newEntry.subscriptionId = eventWire.subscribeQuery(descriptor, scope, {
15615
+ onSnapshot: (result) => {
15616
+ newEntry.result = result;
15617
+ newEntry.hasResult = true;
15618
+ this.notify(newEntry);
15619
+ },
15620
+ onChanges: (changes) => {
15621
+ if (!newEntry.hasResult || !Array.isArray(newEntry.result)) {
15622
+ return;
15623
+ }
15624
+ newEntry.result = applyQueryChanges(newEntry.result, changes);
15625
+ this.notify(newEntry);
15626
+ }
15627
+ });
15628
+ this.entries.set(key, newEntry);
15629
+ entry = newEntry;
15539
15630
  }
15540
- const streamConn = createStream();
15541
- this.activeStreams.set(key, {
15542
- unsubscribe: streamConn.unsubscribe,
15543
- refCount: 1
15544
- });
15631
+ const subscribed = entry;
15632
+ subscribed.listeners.add(onChange);
15633
+ let active = true;
15545
15634
  return {
15546
- unsubscribe: () => this.unregisterStream(key),
15547
- wasReused: false
15635
+ read: () => ({
15636
+ result: subscribed.result,
15637
+ loading: !subscribed.hasResult
15638
+ }),
15639
+ unsubscribe: () => {
15640
+ if (!active)
15641
+ return;
15642
+ active = false;
15643
+ subscribed.listeners.delete(onChange);
15644
+ subscribed.refCount--;
15645
+ if (subscribed.refCount > 0)
15646
+ return;
15647
+ subscribed.pendingUnsub = setTimeout(() => {
15648
+ subscribed.pendingUnsub = undefined;
15649
+ if (subscribed.refCount > 0)
15650
+ return;
15651
+ eventWire.unsubscribeQuery(subscribed.subscriptionId);
15652
+ this.entries.delete(key);
15653
+ }, StreamingQueryCache.UNSUBSCRIBE_DELAY_MS);
15654
+ }
15548
15655
  };
15549
15656
  }
15550
- unregisterStream(key) {
15551
- const stream2 = this.activeStreams.get(key);
15552
- if (!stream2)
15553
- return;
15554
- stream2.refCount--;
15555
- if (stream2.refCount <= 0) {
15556
- const timeout = setTimeout(() => {
15557
- this.pendingUnsubscribes.delete(key);
15558
- const current2 = this.activeStreams.get(key);
15559
- if (current2 && current2.refCount <= 0) {
15560
- current2.unsubscribe();
15561
- this.activeStreams.delete(key);
15562
- }
15563
- }, StreamingQueryCache.UNSUBSCRIBE_DELAY_MS);
15564
- this.pendingUnsubscribes.set(key, timeout);
15565
- }
15566
- }
15567
- subscribeView(viewName, eventWire, scope) {
15568
- const key = this.storeKey(viewName, scope);
15569
- const { unsubscribe } = this.registerStream(key, () => {
15570
- const store = this.stores.get(key) ?? new StreamingStore;
15571
- this.stores.set(key, store);
15572
- eventWire.subscribeView(viewName, scope ?? DEFAULT_SCOPE, {
15573
- onSnapshot: (items) => store.setAll(items),
15574
- onChanges: (changes) => store.applyChanges(changes)
15575
- });
15576
- return {
15577
- unsubscribe: () => eventWire.unsubscribeView(viewName, scope ?? DEFAULT_SCOPE)
15578
- };
15579
- });
15580
- return unsubscribe;
15581
- }
15582
- invalidateScope(scope) {
15657
+ invalidateScope(scope, eventWire) {
15583
15658
  const prefix = `${scope}:`;
15584
- for (const [key, timeout] of this.pendingUnsubscribes) {
15659
+ for (const [key, entry] of this.entries) {
15585
15660
  if (!key.startsWith(prefix))
15586
15661
  continue;
15587
- clearTimeout(timeout);
15588
- this.pendingUnsubscribes.delete(key);
15589
- }
15590
- for (const [key, stream2] of this.activeStreams) {
15591
- if (!key.startsWith(prefix))
15592
- continue;
15593
- try {
15594
- stream2.unsubscribe();
15595
- } catch {}
15596
- this.activeStreams.delete(key);
15597
- }
15598
- for (const [key, store] of this.stores) {
15599
- if (!key.startsWith(prefix))
15600
- continue;
15601
- store.clear();
15602
- }
15603
- }
15604
- async applyEvent(event3) {
15605
- for (const view3 of this.views) {
15606
- const handlers = view3.getHandlers();
15607
- const handler = handlers[event3.type];
15608
- if (!handler)
15609
- continue;
15610
- const suffix = `:${view3.name}`;
15611
- for (const [key, store] of this.stores) {
15612
- if (!key.endsWith(suffix))
15613
- continue;
15614
- const ctx = {
15615
- set: async (id3, data) => {
15616
- store.set(String(id3), { _id: String(id3), ...data });
15617
- },
15618
- modify: async (id3, data) => {
15619
- store.modify(String(id3), data);
15620
- },
15621
- remove: async (id3) => {
15622
- store.remove(String(id3));
15623
- },
15624
- find: async (options) => {
15625
- return store.find(options);
15626
- },
15627
- findOne: async (where) => {
15628
- return store.findOne(where);
15629
- },
15630
- $auth: {}
15631
- };
15632
- await handler(ctx, event3);
15633
- }
15662
+ if (entry.pendingUnsub)
15663
+ clearTimeout(entry.pendingUnsub);
15664
+ eventWire?.unsubscribeQuery(entry.subscriptionId);
15665
+ this.entries.delete(key);
15666
+ entry.result = undefined;
15667
+ entry.hasResult = false;
15668
+ this.notify(entry);
15634
15669
  }
15635
15670
  }
15636
- clear() {
15637
- for (const stream2 of this.activeStreams.values()) {
15638
- stream2.unsubscribe();
15639
- }
15640
- this.activeStreams.clear();
15641
- for (const timeout of this.pendingUnsubscribes.values()) {
15642
- clearTimeout(timeout);
15643
- }
15644
- this.pendingUnsubscribes.clear();
15645
- for (const store of this.stores.values()) {
15646
- store.clear();
15671
+ clear(eventWire) {
15672
+ for (const entry of this.entries.values()) {
15673
+ if (entry.pendingUnsub)
15674
+ clearTimeout(entry.pendingUnsub);
15675
+ eventWire?.unsubscribeQuery(entry.subscriptionId);
15647
15676
  }
15677
+ this.entries.clear();
15648
15678
  }
15649
- }
15650
-
15651
- class StreamingStore {
15652
- data = new Map;
15653
- listeners = new Set;
15654
- initialized = false;
15655
- hasData() {
15656
- return this.initialized;
15657
- }
15658
- setAll(items) {
15659
- this.initialized = true;
15660
- this.data.clear();
15661
- for (const item of items) {
15662
- this.data.set(item._id, item);
15663
- }
15664
- this.notifyListeners(null);
15665
- }
15666
- applyChanges(events) {
15667
- if (events.length === 0)
15668
- return;
15669
- for (const event3 of events) {
15670
- if (event3.type === "set" && event3.item) {
15671
- this.data.set(event3.id, event3.item);
15672
- } else if (event3.type === "delete") {
15673
- this.data.delete(event3.id);
15679
+ notify(entry) {
15680
+ for (const listener4 of entry.listeners) {
15681
+ try {
15682
+ listener4();
15683
+ } catch (err3) {
15684
+ console.error(`[Arc] Query cache listener error:`, err3);
15674
15685
  }
15675
15686
  }
15676
- this.notifyListeners(events);
15677
- }
15678
- set(id3, item) {
15679
- this.data.set(id3, item);
15680
- this.notifyListeners([{ type: "set", id: id3, item }]);
15681
- }
15682
- modify(id3, updates) {
15683
- const existing = this.data.get(id3);
15684
- if (existing) {
15685
- const updated = { ...existing, ...updates };
15686
- this.data.set(id3, updated);
15687
- this.notifyListeners([{ type: "set", id: id3, item: updated }]);
15688
- }
15689
- }
15690
- remove(id3) {
15691
- if (this.data.delete(id3)) {
15692
- this.notifyListeners([{ type: "delete", id: id3, item: null }]);
15693
- }
15694
- }
15695
- clear() {
15696
- this.initialized = false;
15697
- this.data.clear();
15698
- this.notifyListeners(null);
15699
- }
15700
- find(options = {}) {
15701
- let results = Array.from(this.data.values());
15702
- if (options.where) {
15703
- results = results.filter((item) => checkItemMatchesWhere(item, options.where));
15704
- }
15705
- return applyOrderByAndLimit(results, options);
15706
- }
15707
- findOne(where) {
15708
- const results = this.find({ where });
15709
- return results[0];
15710
- }
15711
- subscribe(listener4) {
15712
- this.listeners.add(listener4);
15713
- return () => {
15714
- this.listeners.delete(listener4);
15715
- };
15716
- }
15717
- notifyListeners(events) {
15718
- for (const listener4 of this.listeners) {
15719
- listener4(events);
15720
- }
15721
15687
  }
15722
15688
  }
15723
15689
 
15724
15690
  class StreamingEventPublisher {
15725
- cache;
15726
15691
  eventWire;
15727
15692
  views = [];
15728
15693
  subscribers = new Map;
15729
- constructor(cache, eventWire) {
15730
- this.cache = cache;
15694
+ constructor(eventWire) {
15731
15695
  this.eventWire = eventWire;
15732
15696
  }
15733
15697
  registerViews(views) {
15734
15698
  this.views = views;
15735
- this.cache.registerViews(views);
15736
15699
  }
15737
15700
  async publish(event3) {
15738
- await this.cache.applyEvent(event3);
15739
15701
  await this.notifySubscribers(event3);
15740
15702
  this.eventWire.syncEvents([
15741
15703
  {
@@ -18142,7 +18104,7 @@ var Operation, PROXY_DRAFT, RAW_RETURN_SYMBOL, iteratorSymbol, dataTypes, intern
18142
18104
  }
18143
18105
  return returnValue(result);
18144
18106
  };
18145
- }, create, constructorString, TOKEN_PREFIX = "arc:token:", eventWireInstanceCounter = 0, EVENT_TABLES, arrayValidator, ArcArray, objectValidator, ArcObject, ScopedDataStorage, ArcPrimitive, stringValidator, ArcString, ArcContextElement, ArcBoolean, ArcId, numberValidator, ArcNumber, ArcEvent, ArcCommand, ArcListener, ArcRoute, ForkedStoreState, ForkedDataStorage, MasterStoreState, MasterDataStorage2, dateValidator, DEFAULT_SCOPE = "default", originCache, originStackCache, originError, CLOSE, Query, PostgresError, Errors, types2, Identifier, Parameter, Builder, defaultHandlers, builders, serializers, parsers, mergeUserTypes = function(types22) {
18107
+ }, create, constructorString, TOKEN_PREFIX = "arc:token:", DEFAULT_TIMEOUT_MS = 8000, provider = null, latestSync = null, syncSeq = 0, eventWireInstanceCounter = 0, EVENT_TABLES, arrayValidator, ArcArray, objectValidator, ArcObject, ScopedDataStorage, ArcPrimitive, stringValidator, ArcString, ArcContextElement, ArcBoolean, ArcId, numberValidator, ArcNumber, ArcEvent, ArcCommand, ArcListener, ArcRoute, ForkedStoreState, ForkedDataStorage, MasterStoreState, MasterDataStorage2, dateValidator, originCache, originStackCache, originError, CLOSE, Query, PostgresError, Errors, types2, Identifier, Parameter, Builder, defaultHandlers, builders, serializers, parsers, mergeUserTypes = function(types22) {
18146
18108
  const user = typeHandlers(types22 || {});
18147
18109
  return {
18148
18110
  serializers: Object.assign({}, serializers, user.serializers),
@@ -19748,12 +19710,8 @@ var init_dist = __esm(() => {
19748
19710
  }
19749
19711
  return transaction.find(this.storeName, { where: { _id: id2 } }).then((results) => results[0]);
19750
19712
  }
19751
- async applyChangeAndReturnEvent(transaction, change, transactionCache, options) {
19713
+ async applyChangeAndReturnEvent(transaction, change, transactionCache) {
19752
19714
  if (change.type === "set") {
19753
- let existing;
19754
- if (options?.captureRows) {
19755
- existing = await this.readExisting(transaction, change.data._id, transactionCache);
19756
- }
19757
19715
  await transaction.set(this.storeName, change.data);
19758
19716
  const item = this.deserialize ? this.deserialize(change.data) : change.data;
19759
19717
  if (transactionCache) {
@@ -19766,16 +19724,10 @@ var init_dist = __esm(() => {
19766
19724
  type: "set",
19767
19725
  item: change.data,
19768
19726
  id: change.data._id
19769
- },
19770
- oldRow: existing ?? null,
19771
- newRow: change.data
19727
+ }
19772
19728
  };
19773
19729
  }
19774
19730
  if (change.type === "delete") {
19775
- let existing;
19776
- if (options?.captureRows) {
19777
- existing = await this.readExisting(transaction, change.id, transactionCache);
19778
- }
19779
19731
  await transaction.remove(this.storeName, change.id);
19780
19732
  if (transactionCache) {
19781
19733
  transactionCache.delete(`${this.storeName}:${change.id}`);
@@ -19787,9 +19739,7 @@ var init_dist = __esm(() => {
19787
19739
  type: "delete",
19788
19740
  item: null,
19789
19741
  id: change.id
19790
- },
19791
- oldRow: existing ?? null,
19792
- newRow: null
19742
+ }
19793
19743
  };
19794
19744
  }
19795
19745
  if (change.type === "modify") {
@@ -19807,9 +19757,7 @@ var init_dist = __esm(() => {
19807
19757
  type: "set",
19808
19758
  item,
19809
19759
  id: change.id
19810
- },
19811
- oldRow: existing ?? null,
19812
- newRow: updated
19760
+ }
19813
19761
  };
19814
19762
  }
19815
19763
  if (change.type === "mutate") {
@@ -19827,9 +19775,7 @@ var init_dist = __esm(() => {
19827
19775
  type: "set",
19828
19776
  item,
19829
19777
  id: change.id
19830
- },
19831
- oldRow: existing ?? null,
19832
- newRow: updated
19778
+ }
19833
19779
  };
19834
19780
  }
19835
19781
  throw new Error("Unknown change type");
@@ -19906,22 +19852,17 @@ var init_dist = __esm(() => {
19906
19852
  applySerializedChanges(changes) {
19907
19853
  return Promise.all(changes.map(({ store, changes: changes2 }) => this.getStore(store).applySerializedChanges(changes2)));
19908
19854
  }
19909
- async commitChanges(changes, options) {
19855
+ async commitChanges(changes) {
19910
19856
  const transaction = await this.getReadWriteTransaction();
19911
19857
  const transactionCache = new Map;
19912
19858
  const eventsByStore = new Map;
19913
- const committed = [];
19914
19859
  for (const { store, changes: storeChanges } of changes) {
19915
19860
  const storeState = this.getStore(store);
19916
19861
  const storeEvents = [];
19917
- const capture = options?.captureRowsFor?.has(store) ?? false;
19918
19862
  for (const change of storeChanges) {
19919
- const { event: event3, oldRow, newRow } = await storeState.applyChangeAndReturnEvent(transaction, change, transactionCache, { captureRows: capture });
19863
+ const { event: event3 } = await storeState.applyChangeAndReturnEvent(transaction, change, transactionCache);
19920
19864
  if (event3)
19921
19865
  storeEvents.push(event3);
19922
- if (capture) {
19923
- committed.push({ store, id: event3.id, oldRow, newRow });
19924
- }
19925
19866
  }
19926
19867
  if (storeEvents.length > 0) {
19927
19868
  eventsByStore.set(store, storeEvents);
@@ -19932,7 +19873,6 @@ var init_dist = __esm(() => {
19932
19873
  const storeState = this.getStore(store);
19933
19874
  storeState.notifyListenersPublic(events);
19934
19875
  }
19935
- return committed;
19936
19876
  }
19937
19877
  fork() {
19938
19878
  return new ForkedDataStorage(this);
@@ -21435,6 +21375,38 @@ class AuthAdapter2 {
21435
21375
  this.scopes.clear();
21436
21376
  }
21437
21377
  }
21378
+ function triggerModuleSync2(scope, timeoutMs = DEFAULT_TIMEOUT_MS2) {
21379
+ if (!provider2) {
21380
+ latestSync2 = Promise.resolve();
21381
+ return latestSync2;
21382
+ }
21383
+ const seq = ++syncSeq2;
21384
+ const run = (async () => {
21385
+ try {
21386
+ await provider2(scope);
21387
+ } catch (err3) {
21388
+ console.warn("[arc] module sync failed during setToken:", err3);
21389
+ }
21390
+ })();
21391
+ const guarded = new Promise((resolve) => {
21392
+ let settled = false;
21393
+ const done = () => {
21394
+ if (settled)
21395
+ return;
21396
+ settled = true;
21397
+ resolve();
21398
+ };
21399
+ const timer2 = setTimeout(() => {
21400
+ console.warn(`[arc] module sync did not complete within ${timeoutMs}ms; proceeding anyway.`);
21401
+ done();
21402
+ }, timeoutMs);
21403
+ timer2?.unref?.();
21404
+ run.then(done, done).finally(() => clearTimeout(timer2));
21405
+ });
21406
+ if (seq === syncSeq2)
21407
+ latestSync2 = guarded;
21408
+ return guarded;
21409
+ }
21438
21410
 
21439
21411
  class EventWire2 {
21440
21412
  baseUrl;
@@ -21448,7 +21420,8 @@ class EventWire2 {
21448
21420
  onSyncedCallback;
21449
21421
  reconnectTimeout;
21450
21422
  syncRequested = false;
21451
- viewSubscriptions = new Map;
21423
+ querySubscriptions = new Map;
21424
+ querySubCounter = 0;
21452
21425
  enableEventSync;
21453
21426
  constructor(baseUrl, options) {
21454
21427
  this.baseUrl = baseUrl;
@@ -21503,7 +21476,7 @@ class EventWire2 {
21503
21476
  this.requestSync();
21504
21477
  }
21505
21478
  this.flushPendingEvents();
21506
- this.sendAllViewSubscriptions();
21479
+ this.sendAllQuerySubscriptions();
21507
21480
  } else {
21508
21481
  console.log(`[EventWire] onopen called but ws is not OPEN, readyState:`, this.ws?.readyState);
21509
21482
  }
@@ -21579,24 +21552,29 @@ class EventWire2 {
21579
21552
  onSynced(callback) {
21580
21553
  this.onSyncedCallback = callback;
21581
21554
  }
21582
- subscribeView(element, scope, callbacks) {
21583
- const key = `${scope}:${element}`;
21584
- this.viewSubscriptions.set(key, callbacks);
21555
+ subscribeQuery(descriptor, scope, callbacks) {
21556
+ const subscriptionId = `qs_${this.instanceId}_${++this.querySubCounter}`;
21557
+ this.querySubscriptions.set(subscriptionId, {
21558
+ descriptor,
21559
+ scope,
21560
+ callbacks
21561
+ });
21585
21562
  if (this.state === "connected" && this.ws) {
21586
21563
  this.ws.send(JSON.stringify({
21587
- type: "subscribe-view",
21588
- element,
21564
+ type: "subscribe-query",
21565
+ subscriptionId,
21566
+ descriptor,
21589
21567
  scope
21590
21568
  }));
21591
21569
  }
21570
+ return subscriptionId;
21592
21571
  }
21593
- unsubscribeView(element, scope) {
21594
- this.viewSubscriptions.delete(`${scope}:${element}`);
21572
+ unsubscribeQuery(subscriptionId) {
21573
+ this.querySubscriptions.delete(subscriptionId);
21595
21574
  if (this.state === "connected" && this.ws) {
21596
21575
  this.ws.send(JSON.stringify({
21597
- type: "unsubscribe-view",
21598
- element,
21599
- scope
21576
+ type: "unsubscribe-query",
21577
+ subscriptionId
21600
21578
  }));
21601
21579
  }
21602
21580
  }
@@ -21631,17 +21609,17 @@ class EventWire2 {
21631
21609
  this.lastHostEventId = message.lastHostEventId;
21632
21610
  }
21633
21611
  break;
21634
- case "view-snapshot": {
21635
- const sub = this.viewSubscriptions.get(`${message.scope}:${message.element}`);
21612
+ case "query-snapshot": {
21613
+ const sub = this.querySubscriptions.get(message.subscriptionId);
21636
21614
  if (sub) {
21637
- sub.onSnapshot(message.items ?? []);
21615
+ sub.callbacks.onSnapshot(message.result ?? null);
21638
21616
  }
21639
21617
  break;
21640
21618
  }
21641
- case "view-changes": {
21642
- const sub = this.viewSubscriptions.get(`${message.scope}:${message.element}`);
21619
+ case "query-changes": {
21620
+ const sub = this.querySubscriptions.get(message.subscriptionId);
21643
21621
  if (sub && Array.isArray(message.changes)) {
21644
- sub.onChanges(message.changes);
21622
+ sub.callbacks.onChanges(message.changes);
21645
21623
  }
21646
21624
  break;
21647
21625
  }
@@ -21669,17 +21647,15 @@ class EventWire2 {
21669
21647
  this.pendingEvents = [];
21670
21648
  }
21671
21649
  }
21672
- sendAllViewSubscriptions() {
21650
+ sendAllQuerySubscriptions() {
21673
21651
  if (!this.ws || this.state !== "connected")
21674
21652
  return;
21675
- for (const key of this.viewSubscriptions.keys()) {
21676
- const sepIdx = key.indexOf(":");
21677
- const scope = key.slice(0, sepIdx);
21678
- const element = key.slice(sepIdx + 1);
21653
+ for (const [subscriptionId, sub] of this.querySubscriptions) {
21679
21654
  this.ws.send(JSON.stringify({
21680
- type: "subscribe-view",
21681
- element,
21682
- scope
21655
+ type: "subscribe-query",
21656
+ subscriptionId,
21657
+ descriptor: sub.descriptor,
21658
+ scope: sub.scope
21683
21659
  }));
21684
21660
  }
21685
21661
  }
@@ -21698,19 +21674,12 @@ class LocalEventPublisher3 {
21698
21674
  views = [];
21699
21675
  syncCallback;
21700
21676
  subscribers = new Map;
21701
- viewChangesCallbacks = new Set;
21702
21677
  constructor(dataStorage) {
21703
21678
  this.dataStorage = dataStorage;
21704
21679
  }
21705
21680
  onPublish(callback) {
21706
21681
  this.syncCallback = callback;
21707
21682
  }
21708
- onViewChanges(callback) {
21709
- this.viewChangesCallbacks.add(callback);
21710
- return () => {
21711
- this.viewChangesCallbacks.delete(callback);
21712
- };
21713
- }
21714
21683
  registerViews(views) {
21715
21684
  this.views = views;
21716
21685
  }
@@ -21778,19 +21747,7 @@ class LocalEventPublisher3 {
21778
21747
  });
21779
21748
  const viewChanges = await this.collectViewChanges(event);
21780
21749
  allChanges.push(...viewChanges);
21781
- const viewStoreNames = new Set(viewChanges.map((c2) => c2.store));
21782
- const committed = await this.dataStorage.commitChanges(allChanges, {
21783
- captureRowsFor: viewStoreNames
21784
- });
21785
- if (committed.length > 0 && this.viewChangesCallbacks.size > 0) {
21786
- for (const callback of this.viewChangesCallbacks) {
21787
- try {
21788
- callback(committed);
21789
- } catch (error) {
21790
- console.error(`[EventPublisher] onViewChanges callback error:`, error);
21791
- }
21792
- }
21793
- }
21750
+ await this.dataStorage.commitChanges(allChanges);
21794
21751
  await this.notifySubscribers(event);
21795
21752
  if (this.syncCallback) {
21796
21753
  this.syncCallback(event);
@@ -22171,9 +22128,8 @@ function typeValidatorBuilder2(typeName, comparatorStrategy) {
22171
22128
  }
22172
22129
 
22173
22130
  class DataStorage2 {
22174
- async commitChanges(changes, _options) {
22131
+ async commitChanges(changes) {
22175
22132
  await Promise.all(changes.map(({ store, changes: changes2 }) => this.getStore(store).applyChanges(changes2)));
22176
- return [];
22177
22133
  }
22178
22134
  }
22179
22135
 
@@ -22530,6 +22486,47 @@ function deepMerge2(target, source) {
22530
22486
  function isPlainObject2(item) {
22531
22487
  return item && typeof item === "object" && !Array.isArray(item) && !(item instanceof Date) && Object.prototype.toString.call(item) === "[object Object]";
22532
22488
  }
22489
+ function murmurHash2(key, seed = 0) {
22490
+ let remainder, bytes, h1, h1b, c1, c2, k1, i;
22491
+ remainder = key.length & 3;
22492
+ bytes = key.length - remainder;
22493
+ h1 = seed;
22494
+ c1 = 3432918353;
22495
+ c2 = 461845907;
22496
+ i = 0;
22497
+ while (i < bytes) {
22498
+ k1 = key.charCodeAt(i) & 255 | (key.charCodeAt(++i) & 255) << 8 | (key.charCodeAt(++i) & 255) << 16 | (key.charCodeAt(++i) & 255) << 24;
22499
+ ++i;
22500
+ k1 = (k1 & 65535) * c1 + (((k1 >>> 16) * c1 & 65535) << 16) & 4294967295;
22501
+ k1 = k1 << 15 | k1 >>> 17;
22502
+ k1 = (k1 & 65535) * c2 + (((k1 >>> 16) * c2 & 65535) << 16) & 4294967295;
22503
+ h1 ^= k1;
22504
+ h1 = h1 << 13 | h1 >>> 19;
22505
+ h1b = (h1 & 65535) * 5 + (((h1 >>> 16) * 5 & 65535) << 16) & 4294967295;
22506
+ h1 = (h1b & 65535) + 27492 + (((h1b >>> 16) + 58964 & 65535) << 16);
22507
+ }
22508
+ k1 = 0;
22509
+ if (remainder >= 3) {
22510
+ k1 ^= (key.charCodeAt(i + 2) & 255) << 16;
22511
+ }
22512
+ if (remainder >= 2) {
22513
+ k1 ^= (key.charCodeAt(i + 1) & 255) << 8;
22514
+ }
22515
+ if (remainder >= 1) {
22516
+ k1 ^= key.charCodeAt(i) & 255;
22517
+ k1 = (k1 & 65535) * c1 + (((k1 >>> 16) * c1 & 65535) << 16) & 4294967295;
22518
+ k1 = k1 << 15 | k1 >>> 17;
22519
+ k1 = (k1 & 65535) * c2 + (((k1 >>> 16) * c2 & 65535) << 16) & 4294967295;
22520
+ h1 ^= k1;
22521
+ }
22522
+ h1 ^= key.length;
22523
+ h1 ^= h1 >>> 16;
22524
+ h1 = (h1 & 65535) * 2246822507 + (((h1 >>> 16) * 2246822507 & 65535) << 16) & 4294967295;
22525
+ h1 ^= h1 >>> 13;
22526
+ h1 = (h1 & 65535) * 3266489909 + (((h1 >>> 16) * 3266489909 & 65535) << 16) & 4294967295;
22527
+ h1 ^= h1 >>> 16;
22528
+ return h1 >>> 0;
22529
+ }
22533
22530
  function resolveQueryChange2(currentResult, event3, options) {
22534
22531
  const index = currentResult.findIndex((e2) => e2._id === event3.id);
22535
22532
  const isInCurrentResult = index !== -1;
@@ -22631,8 +22628,8 @@ class ObservableDataStorage2 {
22631
22628
  getReadWriteTransaction() {
22632
22629
  return this.source.getReadWriteTransaction();
22633
22630
  }
22634
- commitChanges(changes, options) {
22635
- return this.source.commitChanges(changes, options);
22631
+ commitChanges(changes) {
22632
+ return this.source.commitChanges(changes);
22636
22633
  }
22637
22634
  trackQuery(storeName, options, result, listener4) {
22638
22635
  const key = this.getQueryKey(storeName, options);
@@ -22645,7 +22642,8 @@ class ObservableDataStorage2 {
22645
22642
  }
22646
22643
  handleStoreChange(storeName, events) {
22647
22644
  let hasChanges = false;
22648
- for (const query of this.trackedQueries.values()) {
22645
+ const staleKeys = [];
22646
+ for (const [key, query] of this.trackedQueries) {
22649
22647
  if (query.storeName !== storeName)
22650
22648
  continue;
22651
22649
  let currentResult = query.result;
@@ -22657,10 +22655,20 @@ class ObservableDataStorage2 {
22657
22655
  queryChanged = true;
22658
22656
  }
22659
22657
  }
22660
- if (queryChanged) {
22661
- query.result = currentResult;
22658
+ if (!queryChanged)
22659
+ continue;
22660
+ if (query.options.limit !== undefined && query.result.length === query.options.limit && currentResult.length < query.options.limit) {
22661
+ staleKeys.push(key);
22662
22662
  hasChanges = true;
22663
+ continue;
22663
22664
  }
22665
+ query.result = currentResult;
22666
+ hasChanges = true;
22667
+ }
22668
+ for (const key of staleKeys) {
22669
+ const query = this.trackedQueries.get(key);
22670
+ this.source.getStore(query.storeName).unsubscribe(query.listener);
22671
+ this.trackedQueries.delete(key);
22664
22672
  }
22665
22673
  if (hasChanges) {
22666
22674
  this.onChange();
@@ -22833,6 +22841,7 @@ class ScopedModel4 {
22833
22841
  for (const listener4 of this.tokenListeners) {
22834
22842
  listener4();
22835
22843
  }
22844
+ return triggerModuleSync2(this.scopeName);
22836
22845
  }
22837
22846
  getToken() {
22838
22847
  return this.authAdapter.getToken();
@@ -22930,243 +22939,136 @@ class Model3 {
22930
22939
  return s;
22931
22940
  }
22932
22941
  }
22933
-
22934
- class StreamingQueryCache2 {
22935
- stores = new Map;
22936
- views = [];
22937
- activeStreams = new Map;
22938
- pendingUnsubscribes = new Map;
22939
- static UNSUBSCRIBE_DELAY_MS = 5000;
22940
- storeKey(viewName, scope) {
22941
- return `${scope ?? DEFAULT_SCOPE2}:${viewName}`;
22942
- }
22943
- registerViews(views) {
22944
- this.views = views;
22945
- }
22946
- getStore(viewName, scope) {
22947
- const key = this.storeKey(viewName, scope);
22948
- if (!this.stores.has(key)) {
22949
- this.stores.set(key, new StreamingStore2);
22942
+ function applyQueryChanges2(result, changes) {
22943
+ const next = [...result];
22944
+ for (const change of changes) {
22945
+ if (change.type === "delete") {
22946
+ const idx = next.findIndex((it) => it._id === change.id);
22947
+ if (idx !== -1)
22948
+ next.splice(idx, 1);
22950
22949
  }
22951
- return this.stores.get(key);
22952
- }
22953
- hasData(viewName, scope) {
22954
- const store = this.stores.get(this.storeKey(viewName, scope));
22955
- return store ? store.hasData() : false;
22956
22950
  }
22957
- registerStream(key, createStream) {
22958
- const pending = this.pendingUnsubscribes.get(key);
22959
- if (pending) {
22960
- clearTimeout(pending);
22961
- this.pendingUnsubscribes.delete(key);
22951
+ for (const change of changes) {
22952
+ if (change.type === "set") {
22953
+ const idx = next.findIndex((it) => it._id === change.id);
22954
+ if (idx !== -1)
22955
+ next.splice(idx, 1);
22956
+ next.splice(change.index, 0, change.item);
22962
22957
  }
22963
- const existing = this.activeStreams.get(key);
22964
- if (existing) {
22965
- existing.refCount++;
22966
- return {
22967
- unsubscribe: () => this.unregisterStream(key),
22968
- wasReused: true
22958
+ }
22959
+ return next;
22960
+ }
22961
+
22962
+ class StreamingQueryCache2 {
22963
+ entries = new Map;
22964
+ static UNSUBSCRIBE_DELAY_MS = 5000;
22965
+ entryKey(descriptor, scope) {
22966
+ return `${scope ?? "default"}:${murmurHash2(JSON.stringify(descriptor))}`;
22967
+ }
22968
+ subscribe(descriptor, scope, eventWire, onChange) {
22969
+ const key = this.entryKey(descriptor, scope);
22970
+ let entry = this.entries.get(key);
22971
+ if (entry) {
22972
+ if (entry.pendingUnsub) {
22973
+ clearTimeout(entry.pendingUnsub);
22974
+ entry.pendingUnsub = undefined;
22975
+ }
22976
+ entry.refCount++;
22977
+ } else {
22978
+ const newEntry = {
22979
+ result: undefined,
22980
+ hasResult: false,
22981
+ listeners: new Set,
22982
+ refCount: 1,
22983
+ subscriptionId: ""
22969
22984
  };
22985
+ newEntry.subscriptionId = eventWire.subscribeQuery(descriptor, scope, {
22986
+ onSnapshot: (result) => {
22987
+ newEntry.result = result;
22988
+ newEntry.hasResult = true;
22989
+ this.notify(newEntry);
22990
+ },
22991
+ onChanges: (changes) => {
22992
+ if (!newEntry.hasResult || !Array.isArray(newEntry.result)) {
22993
+ return;
22994
+ }
22995
+ newEntry.result = applyQueryChanges2(newEntry.result, changes);
22996
+ this.notify(newEntry);
22997
+ }
22998
+ });
22999
+ this.entries.set(key, newEntry);
23000
+ entry = newEntry;
22970
23001
  }
22971
- const streamConn = createStream();
22972
- this.activeStreams.set(key, {
22973
- unsubscribe: streamConn.unsubscribe,
22974
- refCount: 1
22975
- });
23002
+ const subscribed = entry;
23003
+ subscribed.listeners.add(onChange);
23004
+ let active = true;
22976
23005
  return {
22977
- unsubscribe: () => this.unregisterStream(key),
22978
- wasReused: false
23006
+ read: () => ({
23007
+ result: subscribed.result,
23008
+ loading: !subscribed.hasResult
23009
+ }),
23010
+ unsubscribe: () => {
23011
+ if (!active)
23012
+ return;
23013
+ active = false;
23014
+ subscribed.listeners.delete(onChange);
23015
+ subscribed.refCount--;
23016
+ if (subscribed.refCount > 0)
23017
+ return;
23018
+ subscribed.pendingUnsub = setTimeout(() => {
23019
+ subscribed.pendingUnsub = undefined;
23020
+ if (subscribed.refCount > 0)
23021
+ return;
23022
+ eventWire.unsubscribeQuery(subscribed.subscriptionId);
23023
+ this.entries.delete(key);
23024
+ }, StreamingQueryCache2.UNSUBSCRIBE_DELAY_MS);
23025
+ }
22979
23026
  };
22980
23027
  }
22981
- unregisterStream(key) {
22982
- const stream2 = this.activeStreams.get(key);
22983
- if (!stream2)
22984
- return;
22985
- stream2.refCount--;
22986
- if (stream2.refCount <= 0) {
22987
- const timeout = setTimeout(() => {
22988
- this.pendingUnsubscribes.delete(key);
22989
- const current22 = this.activeStreams.get(key);
22990
- if (current22 && current22.refCount <= 0) {
22991
- current22.unsubscribe();
22992
- this.activeStreams.delete(key);
22993
- }
22994
- }, StreamingQueryCache2.UNSUBSCRIBE_DELAY_MS);
22995
- this.pendingUnsubscribes.set(key, timeout);
22996
- }
22997
- }
22998
- subscribeView(viewName, eventWire, scope) {
22999
- const key = this.storeKey(viewName, scope);
23000
- const { unsubscribe } = this.registerStream(key, () => {
23001
- const store = this.stores.get(key) ?? new StreamingStore2;
23002
- this.stores.set(key, store);
23003
- eventWire.subscribeView(viewName, scope ?? DEFAULT_SCOPE2, {
23004
- onSnapshot: (items) => store.setAll(items),
23005
- onChanges: (changes) => store.applyChanges(changes)
23006
- });
23007
- return {
23008
- unsubscribe: () => eventWire.unsubscribeView(viewName, scope ?? DEFAULT_SCOPE2)
23009
- };
23010
- });
23011
- return unsubscribe;
23012
- }
23013
- invalidateScope(scope) {
23028
+ invalidateScope(scope, eventWire) {
23014
23029
  const prefix = `${scope}:`;
23015
- for (const [key, timeout] of this.pendingUnsubscribes) {
23030
+ for (const [key, entry] of this.entries) {
23016
23031
  if (!key.startsWith(prefix))
23017
23032
  continue;
23018
- clearTimeout(timeout);
23019
- this.pendingUnsubscribes.delete(key);
23020
- }
23021
- for (const [key, stream2] of this.activeStreams) {
23022
- if (!key.startsWith(prefix))
23023
- continue;
23024
- try {
23025
- stream2.unsubscribe();
23026
- } catch {}
23027
- this.activeStreams.delete(key);
23028
- }
23029
- for (const [key, store] of this.stores) {
23030
- if (!key.startsWith(prefix))
23031
- continue;
23032
- store.clear();
23033
- }
23034
- }
23035
- async applyEvent(event3) {
23036
- for (const view3 of this.views) {
23037
- const handlers = view3.getHandlers();
23038
- const handler = handlers[event3.type];
23039
- if (!handler)
23040
- continue;
23041
- const suffix = `:${view3.name}`;
23042
- for (const [key, store] of this.stores) {
23043
- if (!key.endsWith(suffix))
23044
- continue;
23045
- const ctx = {
23046
- set: async (id3, data) => {
23047
- store.set(String(id3), { _id: String(id3), ...data });
23048
- },
23049
- modify: async (id3, data) => {
23050
- store.modify(String(id3), data);
23051
- },
23052
- remove: async (id3) => {
23053
- store.remove(String(id3));
23054
- },
23055
- find: async (options) => {
23056
- return store.find(options);
23057
- },
23058
- findOne: async (where) => {
23059
- return store.findOne(where);
23060
- },
23061
- $auth: {}
23062
- };
23063
- await handler(ctx, event3);
23064
- }
23065
- }
23066
- }
23067
- clear() {
23068
- for (const stream2 of this.activeStreams.values()) {
23069
- stream2.unsubscribe();
23070
- }
23071
- this.activeStreams.clear();
23072
- for (const timeout of this.pendingUnsubscribes.values()) {
23073
- clearTimeout(timeout);
23074
- }
23075
- this.pendingUnsubscribes.clear();
23076
- for (const store of this.stores.values()) {
23077
- store.clear();
23033
+ if (entry.pendingUnsub)
23034
+ clearTimeout(entry.pendingUnsub);
23035
+ eventWire?.unsubscribeQuery(entry.subscriptionId);
23036
+ this.entries.delete(key);
23037
+ entry.result = undefined;
23038
+ entry.hasResult = false;
23039
+ this.notify(entry);
23078
23040
  }
23079
23041
  }
23080
- }
23081
-
23082
- class StreamingStore2 {
23083
- data = new Map;
23084
- listeners = new Set;
23085
- initialized = false;
23086
- hasData() {
23087
- return this.initialized;
23088
- }
23089
- setAll(items) {
23090
- this.initialized = true;
23091
- this.data.clear();
23092
- for (const item of items) {
23093
- this.data.set(item._id, item);
23042
+ clear(eventWire) {
23043
+ for (const entry of this.entries.values()) {
23044
+ if (entry.pendingUnsub)
23045
+ clearTimeout(entry.pendingUnsub);
23046
+ eventWire?.unsubscribeQuery(entry.subscriptionId);
23094
23047
  }
23095
- this.notifyListeners(null);
23048
+ this.entries.clear();
23096
23049
  }
23097
- applyChanges(events) {
23098
- if (events.length === 0)
23099
- return;
23100
- for (const event3 of events) {
23101
- if (event3.type === "set" && event3.item) {
23102
- this.data.set(event3.id, event3.item);
23103
- } else if (event3.type === "delete") {
23104
- this.data.delete(event3.id);
23050
+ notify(entry) {
23051
+ for (const listener4 of entry.listeners) {
23052
+ try {
23053
+ listener4();
23054
+ } catch (err3) {
23055
+ console.error(`[Arc] Query cache listener error:`, err3);
23105
23056
  }
23106
23057
  }
23107
- this.notifyListeners(events);
23108
- }
23109
- set(id3, item) {
23110
- this.data.set(id3, item);
23111
- this.notifyListeners([{ type: "set", id: id3, item }]);
23112
- }
23113
- modify(id3, updates) {
23114
- const existing = this.data.get(id3);
23115
- if (existing) {
23116
- const updated = { ...existing, ...updates };
23117
- this.data.set(id3, updated);
23118
- this.notifyListeners([{ type: "set", id: id3, item: updated }]);
23119
- }
23120
- }
23121
- remove(id3) {
23122
- if (this.data.delete(id3)) {
23123
- this.notifyListeners([{ type: "delete", id: id3, item: null }]);
23124
- }
23125
- }
23126
- clear() {
23127
- this.initialized = false;
23128
- this.data.clear();
23129
- this.notifyListeners(null);
23130
- }
23131
- find(options = {}) {
23132
- let results = Array.from(this.data.values());
23133
- if (options.where) {
23134
- results = results.filter((item) => checkItemMatchesWhere2(item, options.where));
23135
- }
23136
- return applyOrderByAndLimit2(results, options);
23137
- }
23138
- findOne(where) {
23139
- const results = this.find({ where });
23140
- return results[0];
23141
- }
23142
- subscribe(listener4) {
23143
- this.listeners.add(listener4);
23144
- return () => {
23145
- this.listeners.delete(listener4);
23146
- };
23147
- }
23148
- notifyListeners(events) {
23149
- for (const listener4 of this.listeners) {
23150
- listener4(events);
23151
- }
23152
23058
  }
23153
23059
  }
23154
23060
 
23155
23061
  class StreamingEventPublisher2 {
23156
- cache;
23157
23062
  eventWire;
23158
23063
  views = [];
23159
23064
  subscribers = new Map;
23160
- constructor(cache, eventWire) {
23161
- this.cache = cache;
23065
+ constructor(eventWire) {
23162
23066
  this.eventWire = eventWire;
23163
23067
  }
23164
23068
  registerViews(views) {
23165
23069
  this.views = views;
23166
- this.cache.registerViews(views);
23167
23070
  }
23168
23071
  async publish(event3) {
23169
- await this.cache.applyEvent(event3);
23170
23072
  await this.notifySubscribers(event3);
23171
23073
  this.eventWire.syncEvents([
23172
23074
  {
@@ -24049,7 +23951,7 @@ var Operation2, PROXY_DRAFT2, RAW_RETURN_SYMBOL2, iteratorSymbol2, dataTypes2, i
24049
23951
  }
24050
23952
  return returnValue(result);
24051
23953
  };
24052
- }, create2, constructorString2, TOKEN_PREFIX2 = "arc:token:", eventWireInstanceCounter2 = 0, EVENT_TABLES2, arrayValidator2, ArcArray2, objectValidator2, ArcObject2, ScopedDataStorage2, ArcPrimitive2, stringValidator2, ArcString2, ArcContextElement2, ArcBoolean2, ArcId2, numberValidator2, ArcNumber2, ArcEvent2, ArcCommand2, ArcListener2, ArcRoute2, ForkedStoreState2, ForkedDataStorage2, MasterStoreState2, MasterDataStorage3, dateValidator2, DEFAULT_SCOPE2 = "default", SQLiteReadWriteTransaction, createSQLiteAdapterFactory = (db) => {
23954
+ }, create2, constructorString2, TOKEN_PREFIX2 = "arc:token:", DEFAULT_TIMEOUT_MS2 = 8000, provider2 = null, latestSync2 = null, syncSeq2 = 0, eventWireInstanceCounter2 = 0, EVENT_TABLES2, arrayValidator2, ArcArray2, objectValidator2, ArcObject2, ScopedDataStorage2, ArcPrimitive2, stringValidator2, ArcString2, ArcContextElement2, ArcBoolean2, ArcId2, numberValidator2, ArcNumber2, ArcEvent2, ArcCommand2, ArcListener2, ArcRoute2, ForkedStoreState2, ForkedDataStorage2, MasterStoreState2, MasterDataStorage3, dateValidator2, SQLiteReadWriteTransaction, createSQLiteAdapterFactory = (db) => {
24053
23955
  return async (context) => {
24054
23956
  const adapter = new SQLiteAdapter(db, context);
24055
23957
  await adapter.initialize();
@@ -25614,12 +25516,8 @@ var init_dist2 = __esm(() => {
25614
25516
  }
25615
25517
  return transaction.find(this.storeName, { where: { _id: id22 } }).then((results) => results[0]);
25616
25518
  }
25617
- async applyChangeAndReturnEvent(transaction, change, transactionCache, options) {
25519
+ async applyChangeAndReturnEvent(transaction, change, transactionCache) {
25618
25520
  if (change.type === "set") {
25619
- let existing;
25620
- if (options?.captureRows) {
25621
- existing = await this.readExisting(transaction, change.data._id, transactionCache);
25622
- }
25623
25521
  await transaction.set(this.storeName, change.data);
25624
25522
  const item = this.deserialize ? this.deserialize(change.data) : change.data;
25625
25523
  if (transactionCache) {
@@ -25632,16 +25530,10 @@ var init_dist2 = __esm(() => {
25632
25530
  type: "set",
25633
25531
  item: change.data,
25634
25532
  id: change.data._id
25635
- },
25636
- oldRow: existing ?? null,
25637
- newRow: change.data
25533
+ }
25638
25534
  };
25639
25535
  }
25640
25536
  if (change.type === "delete") {
25641
- let existing;
25642
- if (options?.captureRows) {
25643
- existing = await this.readExisting(transaction, change.id, transactionCache);
25644
- }
25645
25537
  await transaction.remove(this.storeName, change.id);
25646
25538
  if (transactionCache) {
25647
25539
  transactionCache.delete(`${this.storeName}:${change.id}`);
@@ -25653,9 +25545,7 @@ var init_dist2 = __esm(() => {
25653
25545
  type: "delete",
25654
25546
  item: null,
25655
25547
  id: change.id
25656
- },
25657
- oldRow: existing ?? null,
25658
- newRow: null
25548
+ }
25659
25549
  };
25660
25550
  }
25661
25551
  if (change.type === "modify") {
@@ -25673,9 +25563,7 @@ var init_dist2 = __esm(() => {
25673
25563
  type: "set",
25674
25564
  item,
25675
25565
  id: change.id
25676
- },
25677
- oldRow: existing ?? null,
25678
- newRow: updated
25566
+ }
25679
25567
  };
25680
25568
  }
25681
25569
  if (change.type === "mutate") {
@@ -25693,9 +25581,7 @@ var init_dist2 = __esm(() => {
25693
25581
  type: "set",
25694
25582
  item,
25695
25583
  id: change.id
25696
- },
25697
- oldRow: existing ?? null,
25698
- newRow: updated
25584
+ }
25699
25585
  };
25700
25586
  }
25701
25587
  throw new Error("Unknown change type");
@@ -25772,22 +25658,17 @@ var init_dist2 = __esm(() => {
25772
25658
  applySerializedChanges(changes) {
25773
25659
  return Promise.all(changes.map(({ store, changes: changes2 }) => this.getStore(store).applySerializedChanges(changes2)));
25774
25660
  }
25775
- async commitChanges(changes, options) {
25661
+ async commitChanges(changes) {
25776
25662
  const transaction = await this.getReadWriteTransaction();
25777
25663
  const transactionCache = new Map;
25778
25664
  const eventsByStore = new Map;
25779
- const committed = [];
25780
25665
  for (const { store, changes: storeChanges } of changes) {
25781
25666
  const storeState = this.getStore(store);
25782
25667
  const storeEvents = [];
25783
- const capture = options?.captureRowsFor?.has(store) ?? false;
25784
25668
  for (const change of storeChanges) {
25785
- const { event: event3, oldRow, newRow } = await storeState.applyChangeAndReturnEvent(transaction, change, transactionCache, { captureRows: capture });
25669
+ const { event: event3 } = await storeState.applyChangeAndReturnEvent(transaction, change, transactionCache);
25786
25670
  if (event3)
25787
25671
  storeEvents.push(event3);
25788
- if (capture) {
25789
- committed.push({ store, id: event3.id, oldRow, newRow });
25790
- }
25791
25672
  }
25792
25673
  if (storeEvents.length > 0) {
25793
25674
  eventsByStore.set(store, storeEvents);
@@ -25798,7 +25679,6 @@ var init_dist2 = __esm(() => {
25798
25679
  const storeState = this.getStore(store);
25799
25680
  storeState.notifyListenersPublic(events);
25800
25681
  }
25801
- return committed;
25802
25682
  }
25803
25683
  fork() {
25804
25684
  return new ForkedDataStorage2(this);
@@ -34582,8 +34462,8 @@ ${colors3.yellow}Type declaration errors:${colors3.reset}`);
34582
34462
  }
34583
34463
 
34584
34464
  // src/platform/shared.ts
34585
- import { copyFileSync, existsSync as existsSync10, mkdirSync as mkdirSync9, readdirSync as readdirSync5, readFileSync as readFileSync10, writeFileSync as writeFileSync9 } from "fs";
34586
- import { dirname as dirname8, join as join11 } from "path";
34465
+ import { copyFileSync, existsSync as existsSync10, mkdirSync as mkdirSync9, readdirSync as readdirSync6, readFileSync as readFileSync11, writeFileSync as writeFileSync9 } from "fs";
34466
+ import { dirname as dirname8, join as join12 } from "path";
34587
34467
 
34588
34468
  // src/builder/module-builder.ts
34589
34469
  import { execSync } from "child_process";
@@ -35378,7 +35258,8 @@ writeFileSync(out, JSON.stringify(result, null, 2) + "\\n");
35378
35258
  `.trim();
35379
35259
 
35380
35260
  // src/builder/chunk-planner.ts
35381
- import { basename as basename3 } from "path";
35261
+ import { readFileSync as readFileSync9, readdirSync as readdirSync5, statSync as statSync2 } from "fs";
35262
+ import { basename as basename3, join as join10 } from "path";
35382
35263
  var PUBLIC_CHUNK = "public";
35383
35264
  function planChunks(packages, accessMap) {
35384
35265
  const assignments = new Map;
@@ -35410,6 +35291,53 @@ function planChunks(packages, accessMap) {
35410
35291
  });
35411
35292
  return { assignments, groups, chunks };
35412
35293
  }
35294
+ var MODULE_CALL = /\bmodule\(\s*["'`]([a-zA-Z0-9_-]+)["'`]/g;
35295
+ function collectModuleNames(dir, acc) {
35296
+ let entries;
35297
+ try {
35298
+ entries = readdirSync5(dir);
35299
+ } catch {
35300
+ return;
35301
+ }
35302
+ for (const e of entries) {
35303
+ if (e === "node_modules" || e === "dist")
35304
+ continue;
35305
+ const full = join10(dir, e);
35306
+ let isDir = false;
35307
+ try {
35308
+ isDir = statSync2(full).isDirectory();
35309
+ } catch {
35310
+ continue;
35311
+ }
35312
+ if (isDir) {
35313
+ collectModuleNames(full, acc);
35314
+ } else if (e.endsWith(".ts") || e.endsWith(".tsx")) {
35315
+ const src = readFileSync9(full, "utf-8");
35316
+ MODULE_CALL.lastIndex = 0;
35317
+ let m;
35318
+ while (m = MODULE_CALL.exec(src))
35319
+ acc.add(m[1]);
35320
+ }
35321
+ }
35322
+ }
35323
+ function assertOneModulePerPackage(packages) {
35324
+ const offenders = [];
35325
+ for (const pkg of packages) {
35326
+ const names = new Set;
35327
+ collectModuleNames(join10(pkg.path, "src"), names);
35328
+ if (names.size > 1) {
35329
+ offenders.push({ pkg: pkg.name, modules: [...names].sort() });
35330
+ }
35331
+ }
35332
+ if (offenders.length === 0)
35333
+ return;
35334
+ const detail = offenders.map((o) => ` \u2022 ${o.pkg} declares ${o.modules.length}: ${o.modules.join(", ")}`).join(`
35335
+ `);
35336
+ throw new Error(`Arc build: a workspace package must declare at most ONE module().
35337
+ ` + `Chunk grouping is per-package \u2014 a second module() silently inherits the ` + `first module's token protection and chunk, so e.g. a public onboarding ` + `page sharing a package with a workspaceToken-gated module becomes ` + `unreachable for users without that token.
35338
+ ${detail}
35339
+ ` + `Fix: move each extra module() into its own workspace package ` + `(declare its own protectedBy there).`);
35340
+ }
35413
35341
  function moduleNameOf(pkgName) {
35414
35342
  return pkgName.includes("/") ? pkgName.split("/").pop() : pkgName;
35415
35343
  }
@@ -35417,17 +35345,19 @@ function resolveChunk(moduleName, access) {
35417
35345
  if (!access || access.rules.length === 0)
35418
35346
  return PUBLIC_CHUNK;
35419
35347
  const tokenNames = new Set(access.rules.map((r) => r.token.name));
35420
- if (tokenNames.size === 1) {
35421
- return [...tokenNames][0];
35348
+ if (tokenNames.size > 1) {
35349
+ const list = [...tokenNames].sort().join(", ");
35350
+ throw new Error(`Module "${moduleName}" has access rules for multiple tokens [${list}]. ` + `Multi-token modules are not supported \u2014 split the module or unify on a single token type.`);
35422
35351
  }
35423
- const list = [...tokenNames].sort().join(", ");
35424
- throw new Error(`Module "${moduleName}" has access rules for multiple tokens [${list}]. ` + `Multi-token modules are not supported \u2014 split the module or unify on a single token type.`);
35352
+ const tokenName = [...tokenNames][0];
35353
+ const hasCheck = access.rules.some((r) => r.hasCheck);
35354
+ return hasCheck ? `${tokenName}__${moduleName}` : tokenName;
35425
35355
  }
35426
35356
 
35427
35357
  // src/builder/dependency-collector.ts
35428
35358
  import { createHash } from "crypto";
35429
- import { existsSync as existsSync9, mkdirSync as mkdirSync8, readFileSync as readFileSync9, writeFileSync as writeFileSync8 } from "fs";
35430
- import { basename as basename4, dirname as dirname7, join as join10 } from "path";
35359
+ import { existsSync as existsSync9, mkdirSync as mkdirSync8, readFileSync as readFileSync10, writeFileSync as writeFileSync8 } from "fs";
35360
+ import { basename as basename4, dirname as dirname7, join as join11 } from "path";
35431
35361
  import { fileURLToPath as fileURLToPath6 } from "url";
35432
35362
  function collectFrameworkDeps(arcDir, rootDir, packages, sharedDeps = []) {
35433
35363
  mkdirSync8(arcDir, { recursive: true });
@@ -35438,7 +35368,7 @@ function collectFrameworkDeps(arcDir, rootDir, packages, sharedDeps = []) {
35438
35368
  try {
35439
35369
  const cliDir = dirname7(fileURLToPath6(import.meta.url));
35440
35370
  const arcOtelPkgPath = Bun.resolveSync("@arcote.tech/arc-otel/package.json", cliDir);
35441
- const arcOtelPkg = JSON.parse(readFileSync9(arcOtelPkgPath, "utf-8"));
35371
+ const arcOtelPkg = JSON.parse(readFileSync10(arcOtelPkgPath, "utf-8"));
35442
35372
  for (const [name, spec] of Object.entries(arcOtelPkg.dependencies ?? {})) {
35443
35373
  if (name.startsWith("@opentelemetry/")) {
35444
35374
  versions[name] = spec;
@@ -35449,10 +35379,10 @@ function collectFrameworkDeps(arcDir, rootDir, packages, sharedDeps = []) {
35449
35379
  console.warn(`[arc-otel] could not resolve @arcote.tech/arc-otel \u2014 image will run without telemetry deps: ${e.message}`);
35450
35380
  }
35451
35381
  let rootArc;
35452
- const rootPkgPath = join10(rootDir, "package.json");
35382
+ const rootPkgPath = join11(rootDir, "package.json");
35453
35383
  if (existsSync9(rootPkgPath)) {
35454
35384
  try {
35455
- const rootPkg = JSON.parse(readFileSync9(rootPkgPath, "utf-8"));
35385
+ const rootPkg = JSON.parse(readFileSync10(rootPkgPath, "utf-8"));
35456
35386
  if (rootPkg.arc && typeof rootPkg.arc === "object")
35457
35387
  rootArc = rootPkg.arc;
35458
35388
  } catch {}
@@ -35464,11 +35394,11 @@ function collectFrameworkDeps(arcDir, rootDir, packages, sharedDeps = []) {
35464
35394
  dependencies: versions,
35465
35395
  ...rootArc ? { arc: rootArc } : {}
35466
35396
  };
35467
- const manifestPath = join10(arcDir, "package.json");
35397
+ const manifestPath = join11(arcDir, "package.json");
35468
35398
  writeFileSync8(manifestPath, JSON.stringify(manifest, null, 2) + `
35469
35399
  `);
35470
- const hash = sha256Hex2(readFileSync9(manifestPath));
35471
- writeFileSync8(join10(arcDir, ".deps-hash"), hash + `
35400
+ const hash = sha256Hex2(readFileSync10(manifestPath));
35401
+ writeFileSync8(join11(arcDir, ".deps-hash"), hash + `
35472
35402
  `);
35473
35403
  return { hash, manifestPath };
35474
35404
  }
@@ -35476,7 +35406,7 @@ function sha256Hex2(bytes) {
35476
35406
  return createHash("sha256").update(bytes).digest("hex");
35477
35407
  }
35478
35408
  function resolveFrameworkVersions(rootDir, packages) {
35479
- const rootPkg = JSON.parse(readFileSync9(join10(rootDir, "package.json"), "utf-8"));
35409
+ const rootPkg = JSON.parse(readFileSync10(join11(rootDir, "package.json"), "utf-8"));
35480
35410
  const rootDeps = rootPkg.dependencies ?? {};
35481
35411
  const rootDevDeps = rootPkg.devDependencies ?? {};
35482
35412
  const required = new Set(FRAMEWORK_PEERS);
@@ -35535,9 +35465,9 @@ function resolveWorkspace() {
35535
35465
  process.exit(1);
35536
35466
  }
35537
35467
  const rootDir = dirname8(packageJsonPath);
35538
- const rootPkg = JSON.parse(readFileSync10(packageJsonPath, "utf-8"));
35468
+ const rootPkg = JSON.parse(readFileSync11(packageJsonPath, "utf-8"));
35539
35469
  const appName = rootPkg.name ?? "Arc App";
35540
- const arcDir = join11(rootDir, ".arc", "platform");
35470
+ const arcDir = join12(rootDir, ".arc", "platform");
35541
35471
  log2("Scanning workspaces...");
35542
35472
  const packages = discoverPackages(rootDir);
35543
35473
  if (packages.length > 0) {
@@ -35547,10 +35477,10 @@ function resolveWorkspace() {
35547
35477
  }
35548
35478
  let manifest;
35549
35479
  for (const name of ["manifest.json", "manifest.webmanifest"]) {
35550
- const manifestPath = join11(rootDir, name);
35480
+ const manifestPath = join12(rootDir, name);
35551
35481
  if (existsSync10(manifestPath)) {
35552
35482
  try {
35553
- const data = JSON.parse(readFileSync10(manifestPath, "utf-8"));
35483
+ const data = JSON.parse(readFileSync11(manifestPath, "utf-8"));
35554
35484
  const icons = data.icons;
35555
35485
  manifest = {
35556
35486
  path: manifestPath,
@@ -35569,9 +35499,9 @@ function resolveWorkspace() {
35569
35499
  rootPkg,
35570
35500
  appName,
35571
35501
  arcDir,
35572
- browserDir: join11(arcDir, "browser"),
35573
- assetsDir: join11(arcDir, "assets"),
35574
- publicDir: join11(rootDir, "public"),
35502
+ browserDir: join12(arcDir, "browser"),
35503
+ assetsDir: join12(arcDir, "assets"),
35504
+ publicDir: join12(rootDir, "public"),
35575
35505
  packages,
35576
35506
  manifest
35577
35507
  };
@@ -35581,11 +35511,12 @@ async function buildAll(ws, opts = {}) {
35581
35511
  const noCache = opts.noCache ?? false;
35582
35512
  const themePath = ws.rootPkg.arc?.theme;
35583
35513
  log2(`Building (concurrency parallel${noCache ? ", no-cache" : ""})...`);
35514
+ assertOneModulePerPackage(ws.packages);
35584
35515
  await buildContextPackages(ws.rootDir, ws.packages, cache, noCache);
35585
35516
  copyContextServerBundles(ws);
35586
35517
  const accessMap = await extractAccessMap(ws.rootDir, ws.packages);
35587
35518
  mkdirSync9(ws.arcDir, { recursive: true });
35588
- writeFileSync9(join11(ws.arcDir, "access.json"), JSON.stringify(accessMap, null, 2) + `
35519
+ writeFileSync9(join12(ws.arcDir, "access.json"), JSON.stringify(accessMap, null, 2) + `
35589
35520
  `);
35590
35521
  const plan = planChunks(ws.packages, accessMap);
35591
35522
  ok(`Chunks: ${plan.chunks.map((c) => `${c}(${plan.groups.get(c)?.length ?? 0})`).join(", ")}`);
@@ -35601,7 +35532,7 @@ async function buildAll(ws, opts = {}) {
35601
35532
  collectFrameworkDeps(ws.arcDir, ws.rootDir, ws.packages);
35602
35533
  saveBuildCache(ws.arcDir, cache);
35603
35534
  const finalManifest = assembleManifest(ws, browserResult, cache);
35604
- writeFileSync9(join11(ws.arcDir, "manifest.json"), JSON.stringify(finalManifest, null, 2));
35535
+ writeFileSync9(join12(ws.arcDir, "manifest.json"), JSON.stringify(finalManifest, null, 2));
35605
35536
  return finalManifest;
35606
35537
  }
35607
35538
  function assembleManifest(ws, browser, cache) {
@@ -35615,40 +35546,40 @@ function assembleManifest(ws, browser, cache) {
35615
35546
  };
35616
35547
  }
35617
35548
  function copyContextServerBundles(ws) {
35618
- const outDir = join11(ws.arcDir, "server");
35549
+ const outDir = join12(ws.arcDir, "server");
35619
35550
  mkdirSync9(outDir, { recursive: true });
35620
35551
  for (const pkg of ws.packages) {
35621
35552
  if (!isContextPackage(pkg.packageJson))
35622
35553
  continue;
35623
- const src = join11(pkg.path, "dist", "server", "main", "index.js");
35554
+ const src = join12(pkg.path, "dist", "server", "main", "index.js");
35624
35555
  if (!existsSync10(src)) {
35625
35556
  err(`Server bundle missing for ${pkg.name}: ${src}`);
35626
35557
  continue;
35627
35558
  }
35628
35559
  const safeName = pkg.path.split("/").pop();
35629
- const dst = join11(outDir, `${safeName}.js`);
35560
+ const dst = join12(outDir, `${safeName}.js`);
35630
35561
  copyFileSync(src, dst);
35631
35562
  }
35632
35563
  }
35633
35564
  function resolveAssetSource(from, pkgDir, rootDir) {
35634
35565
  if (from.startsWith("./") || from.startsWith("../")) {
35635
- const resolved = join11(pkgDir, from);
35566
+ const resolved = join12(pkgDir, from);
35636
35567
  return existsSync10(resolved) ? resolved : null;
35637
35568
  }
35638
35569
  const candidates = [
35639
- join11(rootDir, "node_modules", from),
35640
- join11(pkgDir, "node_modules", from)
35570
+ join12(rootDir, "node_modules", from),
35571
+ join12(pkgDir, "node_modules", from)
35641
35572
  ];
35642
35573
  for (const c of candidates) {
35643
35574
  if (existsSync10(c))
35644
35575
  return c;
35645
35576
  }
35646
- const bunCacheDir = join11(rootDir, "node_modules", ".bun");
35577
+ const bunCacheDir = join12(rootDir, "node_modules", ".bun");
35647
35578
  if (existsSync10(bunCacheDir)) {
35648
- for (const entry of readdirSync5(bunCacheDir, { withFileTypes: true })) {
35579
+ for (const entry of readdirSync6(bunCacheDir, { withFileTypes: true })) {
35649
35580
  if (!entry.isDirectory())
35650
35581
  continue;
35651
- const candidate = join11(bunCacheDir, entry.name, "node_modules", from);
35582
+ const candidate = join12(bunCacheDir, entry.name, "node_modules", from);
35652
35583
  if (existsSync10(candidate))
35653
35584
  return candidate;
35654
35585
  }
@@ -35656,11 +35587,11 @@ function resolveAssetSource(from, pkgDir, rootDir) {
35656
35587
  return null;
35657
35588
  }
35658
35589
  function readBrowserAssets(pkgDir) {
35659
- const pkgJsonPath = join11(pkgDir, "package.json");
35590
+ const pkgJsonPath = join12(pkgDir, "package.json");
35660
35591
  if (!existsSync10(pkgJsonPath))
35661
35592
  return [];
35662
35593
  try {
35663
- const pkg = JSON.parse(readFileSync10(pkgJsonPath, "utf-8"));
35594
+ const pkg = JSON.parse(readFileSync11(pkgJsonPath, "utf-8"));
35664
35595
  const assets = pkg.arc?.browserAssets;
35665
35596
  if (!Array.isArray(assets))
35666
35597
  return [];
@@ -35670,14 +35601,14 @@ function readBrowserAssets(pkgDir) {
35670
35601
  }
35671
35602
  }
35672
35603
  function discoverBrowserAssets(ws) {
35673
- const arcDir = join11(ws.rootDir, "node_modules", "@arcote.tech");
35604
+ const arcDir = join12(ws.rootDir, "node_modules", "@arcote.tech");
35674
35605
  if (!existsSync10(arcDir))
35675
35606
  return [];
35676
35607
  const out = [];
35677
- for (const entry of readdirSync5(arcDir, { withFileTypes: true })) {
35608
+ for (const entry of readdirSync6(arcDir, { withFileTypes: true })) {
35678
35609
  if (!entry.isDirectory() && !entry.isSymbolicLink())
35679
35610
  continue;
35680
- const pkgDir = join11(arcDir, entry.name);
35611
+ const pkgDir = join12(arcDir, entry.name);
35681
35612
  const assets = readBrowserAssets(pkgDir);
35682
35613
  for (const asset of assets) {
35683
35614
  const src = resolveAssetSource(asset.from, pkgDir, ws.rootDir);
@@ -35702,7 +35633,7 @@ async function copyBrowserAssets(ws, cache, noCache) {
35702
35633
  to: a.to,
35703
35634
  mtime: mtimeOf(a.src)
35704
35635
  })));
35705
- const requiredOutputs = assets.map((a) => join11(ws.assetsDir, a.to));
35636
+ const requiredOutputs = assets.map((a) => join12(ws.assetsDir, a.to));
35706
35637
  if (!noCache && isCacheHit(cache, unitId, inputHash, requiredOutputs)) {
35707
35638
  console.log(` \u2713 cached: browser-assets (${assets.length})`);
35708
35639
  return;
@@ -35710,10 +35641,10 @@ async function copyBrowserAssets(ws, cache, noCache) {
35710
35641
  console.log(` building: browser-assets (${assets.length})`);
35711
35642
  const outputHashes = {};
35712
35643
  for (const asset of assets) {
35713
- const dest = join11(ws.assetsDir, asset.to);
35644
+ const dest = join12(ws.assetsDir, asset.to);
35714
35645
  mkdirSync9(dirname8(dest), { recursive: true });
35715
35646
  copyFileSync(asset.src, dest);
35716
- outputHashes[asset.to] = sha256Hex(readFileSync10(dest));
35647
+ outputHashes[asset.to] = sha256Hex(readFileSync11(dest));
35717
35648
  }
35718
35649
  updateCache(cache, unitId, inputHash, { outputHashes });
35719
35650
  }
@@ -35721,15 +35652,15 @@ async function loadServerContext(ws) {
35721
35652
  globalThis.ONLY_SERVER = true;
35722
35653
  globalThis.ONLY_BROWSER = false;
35723
35654
  globalThis.ONLY_CLIENT = false;
35724
- const platformDir = join11(process.cwd(), "node_modules", "@arcote.tech", "platform");
35725
- const platformPkg = JSON.parse(readFileSync10(join11(platformDir, "package.json"), "utf-8"));
35726
- const platformEntry = join11(platformDir, platformPkg.main ?? "src/index.ts");
35655
+ const platformDir = join12(process.cwd(), "node_modules", "@arcote.tech", "platform");
35656
+ const platformPkg = JSON.parse(readFileSync11(join12(platformDir, "package.json"), "utf-8"));
35657
+ const platformEntry = join12(platformDir, platformPkg.main ?? "src/index.ts");
35727
35658
  await import(platformEntry);
35728
- const serverDir = join11(ws.arcDir, "server");
35729
- const bundles = existsSync10(serverDir) ? readdirSync5(serverDir).filter((f) => f.endsWith(".js")) : [];
35659
+ const serverDir = join12(ws.arcDir, "server");
35660
+ const bundles = existsSync10(serverDir) ? readdirSync6(serverDir).filter((f) => f.endsWith(".js")) : [];
35730
35661
  if (bundles.length > 0) {
35731
35662
  for (const file of bundles) {
35732
- const bundlePath = join11(serverDir, file);
35663
+ const bundlePath = join12(serverDir, file);
35733
35664
  try {
35734
35665
  await import(bundlePath);
35735
35666
  } catch (e) {
@@ -35739,7 +35670,7 @@ async function loadServerContext(ws) {
35739
35670
  } else if (ws.packages.length > 0) {
35740
35671
  const ctxPackages = ws.packages.filter((p) => isContextPackage(p.packageJson));
35741
35672
  for (const ctx of ctxPackages) {
35742
- const serverDist = join11(ctx.path, "dist", "server", "main", "index.js");
35673
+ const serverDist = join12(ctx.path, "dist", "server", "main", "index.js");
35743
35674
  if (!existsSync10(serverDist)) {
35744
35675
  err(`Context server dist not found: ${serverDist}`);
35745
35676
  continue;
@@ -35769,21 +35700,21 @@ async function platformBuild(opts = {}) {
35769
35700
  }
35770
35701
 
35771
35702
  // src/commands/platform-deploy.ts
35772
- import { existsSync as existsSync17, readFileSync as readFileSync14 } from "fs";
35773
- import { dirname as dirname11, join as join19 } from "path";
35703
+ import { existsSync as existsSync17, readFileSync as readFileSync15 } from "fs";
35704
+ import { dirname as dirname11, join as join20 } from "path";
35774
35705
  import { fileURLToPath as fileURLToPath8 } from "url";
35775
35706
 
35776
35707
  // src/deploy/bootstrap.ts
35777
35708
  var {spawn: spawn4 } = globalThis.Bun;
35778
35709
  import { mkdirSync as mkdirSync12, writeFileSync as writeFileSync13 } from "fs";
35779
35710
  import { tmpdir as tmpdir2 } from "os";
35780
- import { dirname as dirname9, join as join17 } from "path";
35711
+ import { dirname as dirname9, join as join18 } from "path";
35781
35712
 
35782
35713
  // src/deploy/ansible.ts
35783
35714
  import { spawn as nodeSpawn } from "child_process";
35784
35715
  import { existsSync as existsSync11, mkdirSync as mkdirSync10, writeFileSync as writeFileSync10 } from "fs";
35785
35716
  import { homedir, tmpdir } from "os";
35786
- import { join as join12 } from "path";
35717
+ import { join as join13 } from "path";
35787
35718
 
35788
35719
  // src/deploy/assets.ts
35789
35720
  var TERRAFORM_MAIN_TF = `terraform {
@@ -36057,28 +35988,28 @@ var ASSETS = {
36057
35988
  };
36058
35989
  async function materializeAssets(targetDir, files) {
36059
35990
  const { mkdirSync: mkdirSync10, writeFileSync: writeFileSync10 } = await import("fs");
36060
- const { join: join12 } = await import("path");
35991
+ const { join: join13 } = await import("path");
36061
35992
  mkdirSync10(targetDir, { recursive: true });
36062
35993
  for (const [name, content] of Object.entries(files)) {
36063
- writeFileSync10(join12(targetDir, name), content);
35994
+ writeFileSync10(join13(targetDir, name), content);
36064
35995
  }
36065
35996
  }
36066
35997
 
36067
35998
  // src/deploy/ansible.ts
36068
35999
  function pickSshKeyForAnsible(configured) {
36069
36000
  if (configured) {
36070
- const expanded = configured.startsWith("~") ? join12(homedir(), configured.slice(1)) : configured;
36001
+ const expanded = configured.startsWith("~") ? join13(homedir(), configured.slice(1)) : configured;
36071
36002
  return existsSync11(expanded) ? expanded : null;
36072
36003
  }
36073
36004
  for (const name of ["id_ed25519", "id_ecdsa", "id_rsa"]) {
36074
- const path4 = join12(homedir(), ".ssh", name);
36005
+ const path4 = join13(homedir(), ".ssh", name);
36075
36006
  if (existsSync11(path4))
36076
36007
  return path4;
36077
36008
  }
36078
36009
  return null;
36079
36010
  }
36080
36011
  async function runAnsible(inputs) {
36081
- const workDir = join12(tmpdir(), "arc-deploy", `ansible-${Date.now()}`);
36012
+ const workDir = join13(tmpdir(), "arc-deploy", `ansible-${Date.now()}`);
36082
36013
  mkdirSync10(workDir, { recursive: true });
36083
36014
  await materializeAssets(workDir, ASSETS.ansible);
36084
36015
  const user = inputs.asRoot ? "root" : inputs.target.user;
@@ -36095,7 +36026,7 @@ async function runAnsible(inputs) {
36095
36026
  ""
36096
36027
  ].join(`
36097
36028
  `);
36098
- writeFileSync10(join12(workDir, "inventory.ini"), inventory);
36029
+ writeFileSync10(join13(workDir, "inventory.ini"), inventory);
36099
36030
  const extraVarsJson = JSON.stringify({
36100
36031
  username: inputs.target.user,
36101
36032
  ssh_port: port,
@@ -37143,10 +37074,10 @@ import { spawn as nodeSpawn2 } from "child_process";
37143
37074
  import { createHash as createHash2 } from "crypto";
37144
37075
  import { existsSync as existsSync12, mkdirSync as mkdirSync11, writeFileSync as writeFileSync11 } from "fs";
37145
37076
  import { homedir as homedir2 } from "os";
37146
- import { join as join13 } from "path";
37077
+ import { join as join14 } from "path";
37147
37078
  async function runTerraform(inputs) {
37148
37079
  const wsHash = createHash2("sha256").update(inputs.workspaceDir).digest("hex").slice(0, 16);
37149
- const workDir = join13(homedir2(), ".arc-deploy", wsHash, "tf");
37080
+ const workDir = join14(homedir2(), ".arc-deploy", wsHash, "tf");
37150
37081
  mkdirSync11(workDir, { recursive: true });
37151
37082
  await materializeAssets(workDir, ASSETS.terraform);
37152
37083
  const sshPubKey = inputs.tf.sshPublicKey ?? expandHome("~/.ssh/id_ed25519.pub");
@@ -37163,7 +37094,7 @@ async function runTerraform(inputs) {
37163
37094
  ].join(`
37164
37095
  `) + `
37165
37096
  `;
37166
- writeFileSync11(join13(workDir, "terraform.tfvars"), tfvars);
37097
+ writeFileSync11(join14(workDir, "terraform.tfvars"), tfvars);
37167
37098
  await runTf(workDir, ["init", "-input=false", "-no-color"]);
37168
37099
  await runTf(workDir, [
37169
37100
  "apply",
@@ -37221,20 +37152,20 @@ function expandHome(p) {
37221
37152
  }
37222
37153
 
37223
37154
  // src/deploy/config.ts
37224
- import { existsSync as existsSync14, readFileSync as readFileSync12, writeFileSync as writeFileSync12 } from "fs";
37225
- import { join as join15 } from "path";
37155
+ import { existsSync as existsSync14, readFileSync as readFileSync13, writeFileSync as writeFileSync12 } from "fs";
37156
+ import { join as join16 } from "path";
37226
37157
 
37227
37158
  // src/deploy/env-file.ts
37228
- import { appendFileSync, existsSync as existsSync13, readFileSync as readFileSync11 } from "fs";
37229
- import { join as join14 } from "path";
37159
+ import { appendFileSync, existsSync as existsSync13, readFileSync as readFileSync12 } from "fs";
37160
+ import { join as join15 } from "path";
37230
37161
  function loadDeployEnvFiles(rootDir, envNames) {
37231
- const globalsPath = join14(rootDir, "deploy.arc.env");
37232
- const globals = existsSync13(globalsPath) ? parseEnvFile(readFileSync11(globalsPath, "utf-8"), globalsPath) : {};
37162
+ const globalsPath = join15(rootDir, "deploy.arc.env");
37163
+ const globals = existsSync13(globalsPath) ? parseEnvFile(readFileSync12(globalsPath, "utf-8"), globalsPath) : {};
37233
37164
  const perEnv = {};
37234
37165
  for (const name of envNames) {
37235
- const envPath = join14(rootDir, `deploy.arc.${name}.env`);
37166
+ const envPath = join15(rootDir, `deploy.arc.${name}.env`);
37236
37167
  if (existsSync13(envPath)) {
37237
- perEnv[name] = parseEnvFile(readFileSync11(envPath, "utf-8"), envPath);
37168
+ perEnv[name] = parseEnvFile(readFileSync12(envPath, "utf-8"), envPath);
37238
37169
  }
37239
37170
  }
37240
37171
  return { globals, perEnv };
@@ -37250,16 +37181,16 @@ function ensurePersistedSecret(rootDir, scope, key, generate) {
37250
37181
  if (process.env[key])
37251
37182
  return process.env[key];
37252
37183
  const fileName = scope === "globals" ? "deploy.arc.env" : `deploy.arc.${scope}.env`;
37253
- const path4 = join14(rootDir, fileName);
37184
+ const path4 = join15(rootDir, fileName);
37254
37185
  if (existsSync13(path4)) {
37255
- const existing = parseEnvFile(readFileSync11(path4, "utf-8"), path4);
37186
+ const existing = parseEnvFile(readFileSync12(path4, "utf-8"), path4);
37256
37187
  if (existing[key]) {
37257
37188
  process.env[key] = existing[key];
37258
37189
  return existing[key];
37259
37190
  }
37260
37191
  }
37261
37192
  const value = generate();
37262
- const prefix = existsSync13(path4) && !readFileSync11(path4, "utf-8").endsWith(`
37193
+ const prefix = existsSync13(path4) && !readFileSync12(path4, "utf-8").endsWith(`
37263
37194
  `) ? `
37264
37195
  ` : "";
37265
37196
  appendFileSync(path4, `${prefix}${key}=${value}
@@ -37295,7 +37226,7 @@ function parseEnvFile(content, pathForErrors) {
37295
37226
  // src/deploy/config.ts
37296
37227
  var DEPLOY_CONFIG_FILE = "deploy.arc.json";
37297
37228
  function deployConfigPath(rootDir) {
37298
- return join15(rootDir, DEPLOY_CONFIG_FILE);
37229
+ return join16(rootDir, DEPLOY_CONFIG_FILE);
37299
37230
  }
37300
37231
  function deployConfigExists(rootDir) {
37301
37232
  return existsSync14(deployConfigPath(rootDir));
@@ -37305,7 +37236,7 @@ function loadDeployConfig(rootDir) {
37305
37236
  if (!existsSync14(path4)) {
37306
37237
  throw new Error(`Missing ${DEPLOY_CONFIG_FILE} at ${path4}`);
37307
37238
  }
37308
- const raw = readFileSync12(path4, "utf-8");
37239
+ const raw = readFileSync13(path4, "utf-8");
37309
37240
  let parsed;
37310
37241
  try {
37311
37242
  parsed = JSON.parse(raw);
@@ -37328,7 +37259,7 @@ function loadDeployConfig(rootDir) {
37328
37259
  }
37329
37260
  function saveDeployConfig(rootDir, cfg) {
37330
37261
  const path4 = deployConfigPath(rootDir);
37331
- const raw = existsSync14(path4) ? JSON.parse(readFileSync12(path4, "utf-8")) : {};
37262
+ const raw = existsSync14(path4) ? JSON.parse(readFileSync13(path4, "utf-8")) : {};
37332
37263
  raw.target = { ...raw.target, ...cfg.target };
37333
37264
  writeFileSync12(path4, JSON.stringify(raw, null, 2) + `
37334
37265
  `);
@@ -37541,14 +37472,14 @@ function cfgErr(path4, expected) {
37541
37472
  var {spawn: spawn3 } = globalThis.Bun;
37542
37473
  import { existsSync as existsSync15 } from "fs";
37543
37474
  import { homedir as homedir3 } from "os";
37544
- import { join as join16 } from "path";
37475
+ import { join as join17 } from "path";
37545
37476
  function pickSshKey(target) {
37546
37477
  if (target.sshKey) {
37547
- const expanded = target.sshKey.startsWith("~") ? join16(homedir3(), target.sshKey.slice(1)) : target.sshKey;
37478
+ const expanded = target.sshKey.startsWith("~") ? join17(homedir3(), target.sshKey.slice(1)) : target.sshKey;
37548
37479
  return existsSync15(expanded) ? expanded : null;
37549
37480
  }
37550
37481
  for (const name of ["id_ed25519", "id_ecdsa", "id_rsa"]) {
37551
- const path4 = join16(homedir3(), ".ssh", name);
37482
+ const path4 = join17(homedir3(), ".ssh", name);
37552
37483
  if (existsSync15(path4))
37553
37484
  return path4;
37554
37485
  }
@@ -37784,7 +37715,7 @@ async function isRegistryRunning(cfg) {
37784
37715
  }
37785
37716
  async function upStack(inputs) {
37786
37717
  const { cfg } = inputs;
37787
- const workDir = join17(tmpdir2(), "arc-deploy", `stack-${Date.now()}`);
37718
+ const workDir = join18(tmpdir2(), "arc-deploy", `stack-${Date.now()}`);
37788
37719
  mkdirSync12(workDir, { recursive: true });
37789
37720
  await assertRegistryDnsResolves(cfg);
37790
37721
  const password = process.env[cfg.registry.passwordEnv];
@@ -37792,9 +37723,9 @@ async function upStack(inputs) {
37792
37723
  throw new Error(`Registry password env var ${cfg.registry.passwordEnv} is not set. ` + `Set it (e.g. \`export ${cfg.registry.passwordEnv}=...\`) before bootstrap.`);
37793
37724
  }
37794
37725
  const htpasswdLine = await generateHtpasswd(cfg.registry.username, password);
37795
- writeFileSync13(join17(workDir, "htpasswd"), htpasswdLine);
37796
- writeFileSync13(join17(workDir, "Caddyfile"), generateCaddyfile(cfg));
37797
- writeFileSync13(join17(workDir, "docker-compose.yml"), generateCompose({ cfg }));
37726
+ writeFileSync13(join18(workDir, "htpasswd"), htpasswdLine);
37727
+ writeFileSync13(join18(workDir, "Caddyfile"), generateCaddyfile(cfg));
37728
+ writeFileSync13(join18(workDir, "docker-compose.yml"), generateCompose({ cfg }));
37798
37729
  let observabilityFiles = null;
37799
37730
  let observabilityHtpasswd = null;
37800
37731
  if (cfg.observability?.enabled) {
@@ -37806,30 +37737,30 @@ async function upStack(inputs) {
37806
37737
  }
37807
37738
  observabilityHtpasswd = await generateCaddyBasicAuthLine("admin", adminPassword);
37808
37739
  for (const [relPath, contents] of Object.entries(observabilityFiles)) {
37809
- const fullPath = join17(workDir, relPath);
37740
+ const fullPath = join18(workDir, relPath);
37810
37741
  mkdirSync12(dirname9(fullPath), { recursive: true });
37811
37742
  writeFileSync13(fullPath, contents);
37812
37743
  }
37813
- writeFileSync13(join17(workDir, "observability-htpasswd"), observabilityHtpasswd);
37744
+ writeFileSync13(join18(workDir, "observability-htpasswd"), observabilityHtpasswd);
37814
37745
  }
37815
37746
  await assertExec(cfg.target, `sudo mkdir -p ${cfg.target.remoteDir} && sudo chown ${cfg.target.user}:${cfg.target.user} ${cfg.target.remoteDir}`);
37816
37747
  for (const name of Object.keys(cfg.envs)) {
37817
37748
  await assertExec(cfg.target, `mkdir -p ${cfg.target.remoteDir}/${name}`);
37818
37749
  }
37819
37750
  await assertExec(cfg.target, `mkdir -p ${cfg.target.remoteDir}/registry-auth`);
37820
- await scpUpload(cfg.target, join17(workDir, "Caddyfile"), `${cfg.target.remoteDir}/Caddyfile`);
37821
- await scpUpload(cfg.target, join17(workDir, "docker-compose.yml"), `${cfg.target.remoteDir}/docker-compose.yml`);
37822
- await scpUpload(cfg.target, join17(workDir, "htpasswd"), `${cfg.target.remoteDir}/registry-auth/htpasswd`);
37751
+ await scpUpload(cfg.target, join18(workDir, "Caddyfile"), `${cfg.target.remoteDir}/Caddyfile`);
37752
+ await scpUpload(cfg.target, join18(workDir, "docker-compose.yml"), `${cfg.target.remoteDir}/docker-compose.yml`);
37753
+ await scpUpload(cfg.target, join18(workDir, "htpasswd"), `${cfg.target.remoteDir}/registry-auth/htpasswd`);
37823
37754
  if (observabilityFiles && observabilityHtpasswd) {
37824
37755
  await assertExec(cfg.target, `mkdir -p ${cfg.target.remoteDir}/observability/grafana-dashboards`);
37825
37756
  for (const relPath of Object.keys(observabilityFiles)) {
37826
- const localDir = dirname9(join17(workDir, relPath));
37757
+ const localDir = dirname9(join18(workDir, relPath));
37827
37758
  mkdirSync12(localDir, { recursive: true });
37828
37759
  }
37829
37760
  for (const relPath of Object.keys(observabilityFiles)) {
37830
- await scpUpload(cfg.target, join17(workDir, relPath), `${cfg.target.remoteDir}/${relPath}`);
37761
+ await scpUpload(cfg.target, join18(workDir, relPath), `${cfg.target.remoteDir}/${relPath}`);
37831
37762
  }
37832
- await scpUpload(cfg.target, join17(workDir, "observability-htpasswd"), `${cfg.target.remoteDir}/observability-htpasswd`);
37763
+ await scpUpload(cfg.target, join18(workDir, "observability-htpasswd"), `${cfg.target.remoteDir}/observability-htpasswd`);
37833
37764
  }
37834
37765
  await assertExec(cfg.target, `touch ${cfg.target.remoteDir}/.env`);
37835
37766
  if (cfg.observability?.enabled) {
@@ -37997,12 +37928,12 @@ import {
37997
37928
  copyFileSync as copyFileSync2,
37998
37929
  existsSync as existsSync16,
37999
37930
  mkdirSync as mkdirSync13,
38000
- readFileSync as readFileSync13,
37931
+ readFileSync as readFileSync14,
38001
37932
  realpathSync as realpathSync2,
38002
37933
  writeFileSync as writeFileSync14
38003
37934
  } from "fs";
38004
37935
  import { tmpdir as tmpdir3 } from "os";
38005
- import { dirname as dirname10, join as join18 } from "path";
37936
+ import { dirname as dirname10, join as join19 } from "path";
38006
37937
  import { fileURLToPath as fileURLToPath7 } from "url";
38007
37938
 
38008
37939
  // src/deploy/image-template.ts
@@ -38052,7 +37983,7 @@ function generateDockerfile(inputs) {
38052
37983
  // src/deploy/image.ts
38053
37984
  async function buildImage(ws, opts) {
38054
37985
  await ensureDocker();
38055
- const manifestPath = join18(ws.arcDir, "manifest.json");
37986
+ const manifestPath = join19(ws.arcDir, "manifest.json");
38056
37987
  if (!existsSync16(manifestPath)) {
38057
37988
  throw new Error(`No build manifest at ${manifestPath}. Run \`arc platform build\` first or omit --skip-build.`);
38058
37989
  }
@@ -38063,9 +37994,9 @@ async function buildImage(ws, opts) {
38063
37994
  const dockerfileInputs = collectDockerfileInputs(ws);
38064
37995
  const dockerfile = generateDockerfile(dockerfileInputs);
38065
37996
  const buildContextDir = ws.rootDir;
38066
- const dockerfileDir = join18(tmpdir3(), `arc-image-${Date.now()}`);
37997
+ const dockerfileDir = join19(tmpdir3(), `arc-image-${Date.now()}`);
38067
37998
  mkdirSync13(dockerfileDir, { recursive: true });
38068
- const dockerfilePath = join18(dockerfileDir, "Dockerfile");
37999
+ const dockerfilePath = join19(dockerfileDir, "Dockerfile");
38069
38000
  writeFileSync14(dockerfilePath, dockerfile);
38070
38001
  const buildArgs = [
38071
38002
  "build",
@@ -38091,19 +38022,19 @@ async function buildImage(ws, opts) {
38091
38022
  }
38092
38023
  function embedCliBundle(ws) {
38093
38024
  const source = locateCliBundle();
38094
- const target = join18(ws.arcDir, "host.js");
38025
+ const target = join19(ws.arcDir, "host.js");
38095
38026
  copyFileSync2(source, target);
38096
38027
  }
38097
38028
  function locateCliBundle() {
38098
38029
  const here = fileURLToPath7(import.meta.url);
38099
38030
  let cur = dirname10(here);
38100
38031
  while (cur !== "/" && cur !== "") {
38101
- const candidate = join18(cur, "package.json");
38032
+ const candidate = join19(cur, "package.json");
38102
38033
  if (existsSync16(candidate)) {
38103
38034
  try {
38104
- const pkg = JSON.parse(readFileSync13(candidate, "utf-8"));
38035
+ const pkg = JSON.parse(readFileSync14(candidate, "utf-8"));
38105
38036
  if (pkg.name === "@arcote.tech/arc-cli") {
38106
- const distIndex = join18(realpathSync2(cur), "dist", "index.js");
38037
+ const distIndex = join19(realpathSync2(cur), "dist", "index.js");
38107
38038
  if (!existsSync16(distIndex)) {
38108
38039
  throw new Error(`arc-cli bundle missing at ${distIndex}. Run \`bun run build\` in packages/cli/.`);
38109
38040
  }
@@ -38136,17 +38067,17 @@ async function ensureDocker() {
38136
38067
  }
38137
38068
  }
38138
38069
  function computeContentHash(manifestPath) {
38139
- const raw = JSON.parse(readFileSync13(manifestPath, "utf-8"));
38070
+ const raw = JSON.parse(readFileSync14(manifestPath, "utf-8"));
38140
38071
  delete raw.buildTime;
38141
38072
  const canonical = JSON.stringify(raw);
38142
38073
  return createHash3("sha256").update(canonical).digest("hex").slice(0, 12);
38143
38074
  }
38144
38075
  function collectDockerfileInputs(ws) {
38145
- const hasPublicDir = existsSync16(join18(ws.rootDir, "public"));
38146
- const hasLocales = existsSync16(join18(ws.rootDir, "locales"));
38076
+ const hasPublicDir = existsSync16(join19(ws.rootDir, "public"));
38077
+ const hasLocales = existsSync16(join19(ws.rootDir, "locales"));
38147
38078
  let manifestPath;
38148
38079
  for (const name of ["manifest.webmanifest", "manifest.json"]) {
38149
- if (existsSync16(join18(ws.rootDir, name))) {
38080
+ if (existsSync16(join19(ws.rootDir, name))) {
38150
38081
  manifestPath = name;
38151
38082
  break;
38152
38083
  }
@@ -38975,7 +38906,7 @@ async function platformDeploy(envArg, options = {}) {
38975
38906
  err(`Unknown env "${envArg}". Known: ${Object.keys(cfg.envs).join(", ")}`);
38976
38907
  process.exit(1);
38977
38908
  })() : Object.keys(cfg.envs);
38978
- const manifestPath = join19(ws.arcDir, "manifest.json");
38909
+ const manifestPath = join20(ws.arcDir, "manifest.json");
38979
38910
  if (!options.imageTag) {
38980
38911
  const needBuild = options.rebuild || !existsSync17(manifestPath);
38981
38912
  if (needBuild && !options.skipBuild) {
@@ -39048,9 +38979,9 @@ function readCliVersion2() {
39048
38979
  let cur = dirname11(fileURLToPath8(import.meta.url));
39049
38980
  const root = dirname11(cur).startsWith("/") ? "/" : ".";
39050
38981
  while (cur !== root && cur !== "") {
39051
- const candidate = join19(cur, "package.json");
38982
+ const candidate = join20(cur, "package.json");
39052
38983
  if (existsSync17(candidate)) {
39053
- const pkg = JSON.parse(readFileSync14(candidate, "utf-8"));
38984
+ const pkg = JSON.parse(readFileSync15(candidate, "utf-8"));
39054
38985
  if (pkg.name === "@arcote.tech/arc-cli") {
39055
38986
  return pkg.version ?? "unknown";
39056
38987
  }
@@ -39066,8 +38997,8 @@ function readCliVersion2() {
39066
38997
  }
39067
38998
  }
39068
38999
  async function hashDeployConfig(rootDir) {
39069
- const p2 = join19(rootDir, "deploy.arc.json");
39070
- const content = readFileSync14(p2);
39000
+ const p2 = join20(rootDir, "deploy.arc.json");
39001
+ const content = readFileSync15(p2);
39071
39002
  const hasher = new Bun.CryptoHasher("sha256");
39072
39003
  hasher.update(content);
39073
39004
  hasher.update(readCliVersion2());
@@ -39075,8 +39006,8 @@ async function hashDeployConfig(rootDir) {
39075
39006
  }
39076
39007
 
39077
39008
  // src/platform/startup.ts
39078
- import { existsSync as existsSync19, readFileSync as readFileSync15, watch } from "fs";
39079
- import { join as join21 } from "path";
39009
+ import { existsSync as existsSync19, readFileSync as readFileSync16, watch } from "fs";
39010
+ import { join as join22 } from "path";
39080
39011
 
39081
39012
  // ../host/src/create-server.ts
39082
39013
  var import_jsonwebtoken = __toESM(require_jsonwebtoken(), 1);
@@ -40496,7 +40427,7 @@ async function createArcServer(config) {
40496
40427
  // src/platform/server.ts
40497
40428
  init_i18n();
40498
40429
  import { existsSync as existsSync18, mkdirSync as mkdirSync14 } from "fs";
40499
- import { join as join20 } from "path";
40430
+ import { join as join21 } from "path";
40500
40431
  async function resolveDbAdapterFactory(dbPath) {
40501
40432
  const databaseUrl = process.env.DATABASE_URL;
40502
40433
  if (databaseUrl && databaseUrl.length > 0) {
@@ -40628,15 +40559,13 @@ function parseArcTokensHeader(header) {
40628
40559
  return payloads;
40629
40560
  }
40630
40561
  async function filterManifestForTokens(manifest, moduleAccessMap, tokenPayloads) {
40631
- const allowedGroups = new Set;
40562
+ const tokensByName = new Map;
40632
40563
  for (const t of tokenPayloads) {
40633
40564
  if (t?.tokenType)
40634
- allowedGroups.add(t.tokenType);
40565
+ tokensByName.set(t.tokenType, t);
40635
40566
  }
40636
40567
  const filteredGroups = {};
40637
40568
  for (const [name, group] of Object.entries(manifest.groups)) {
40638
- if (!allowedGroups.has(name))
40639
- continue;
40640
40569
  let allGranted = true;
40641
40570
  for (const moduleName of group.modules) {
40642
40571
  const access = moduleAccessMap.get(moduleName);
@@ -40644,9 +40573,7 @@ async function filterManifestForTokens(manifest, moduleAccessMap, tokenPayloads)
40644
40573
  continue;
40645
40574
  let granted = false;
40646
40575
  for (const rule of access.rules) {
40647
- if (rule.token.name !== name)
40648
- continue;
40649
- const matching = tokenPayloads.find((t) => t.tokenType === name);
40576
+ const matching = tokensByName.get(rule.token.name);
40650
40577
  if (!matching)
40651
40578
  continue;
40652
40579
  granted = rule.check ? await rule.check(matching) : true;
@@ -40687,28 +40614,28 @@ function staticFilesHandler(ws, devMode, getManifest) {
40687
40614
  return new Response("Forbidden", { status: 403, headers: ctx.corsHeaders });
40688
40615
  }
40689
40616
  }
40690
- return serveFile(join20(ws.browserDir, file), {
40617
+ return serveFile(join21(ws.browserDir, file), {
40691
40618
  ...ctx.corsHeaders,
40692
40619
  "Cache-Control": devMode ? "no-cache" : "max-age=31536000,immutable"
40693
40620
  });
40694
40621
  }
40695
40622
  if (path4.startsWith("/locales/"))
40696
- return serveFile(join20(ws.arcDir, path4.slice(1)), {
40623
+ return serveFile(join21(ws.arcDir, path4.slice(1)), {
40697
40624
  ...ctx.corsHeaders,
40698
40625
  "Cache-Control": devMode ? "no-cache" : "max-age=300,stale-while-revalidate=3600"
40699
40626
  });
40700
40627
  if (path4.startsWith("/assets/"))
40701
- return serveFile(join20(ws.assetsDir, path4.slice(8)), {
40628
+ return serveFile(join21(ws.assetsDir, path4.slice(8)), {
40702
40629
  ...ctx.corsHeaders,
40703
40630
  "Cache-Control": devMode ? "no-cache" : "max-age=31536000,immutable"
40704
40631
  });
40705
40632
  if (path4 === "/styles.css")
40706
- return serveFile(join20(ws.arcDir, "styles.css"), {
40633
+ return serveFile(join21(ws.arcDir, "styles.css"), {
40707
40634
  ...ctx.corsHeaders,
40708
40635
  "Cache-Control": devMode ? "no-cache" : "max-age=31536000,immutable"
40709
40636
  });
40710
40637
  if (path4 === "/theme.css")
40711
- return serveFile(join20(ws.arcDir, "theme.css"), {
40638
+ return serveFile(join21(ws.arcDir, "theme.css"), {
40712
40639
  ...ctx.corsHeaders,
40713
40640
  "Cache-Control": devMode ? "no-cache" : "max-age=31536000,immutable"
40714
40641
  });
@@ -40716,7 +40643,7 @@ function staticFilesHandler(ws, devMode, getManifest) {
40716
40643
  return serveFile(ws.manifest.path, ctx.corsHeaders);
40717
40644
  }
40718
40645
  if (path4.lastIndexOf(".") > path4.lastIndexOf("/")) {
40719
- const publicFile = join20(ws.publicDir, path4.slice(1));
40646
+ const publicFile = join21(ws.publicDir, path4.slice(1));
40720
40647
  if (existsSync18(publicFile))
40721
40648
  return serveFile(publicFile, ctx.corsHeaders);
40722
40649
  }
@@ -40871,7 +40798,7 @@ async function startPlatformServer(opts) {
40871
40798
  stop: () => server.stop()
40872
40799
  };
40873
40800
  }
40874
- const dbPath = opts.dbPath || join20(ws.arcDir, "data", "arc.db");
40801
+ const dbPath = opts.dbPath || join21(ws.arcDir, "data", "arc.db");
40875
40802
  const baseDbFactory = await resolveDbAdapterFactory(dbPath);
40876
40803
  let dbAdapterFactory = baseDbFactory;
40877
40804
  if (telemetry) {
@@ -40912,17 +40839,17 @@ async function startPlatformServer(opts) {
40912
40839
  async function startPlatform(opts) {
40913
40840
  const { ws, devMode } = opts;
40914
40841
  const port = opts.port ?? parseInt(process.env.PORT || "5005", 10);
40915
- const dbPath = opts.dbPath ?? join21(ws.rootDir, ".arc", "data", devMode ? "dev.db" : "prod.db");
40842
+ const dbPath = opts.dbPath ?? join22(ws.rootDir, ".arc", "data", devMode ? "dev.db" : "prod.db");
40916
40843
  let manifest;
40917
40844
  if (devMode) {
40918
40845
  manifest = await buildAll(ws);
40919
40846
  } else {
40920
- const manifestPath = join21(ws.arcDir, "manifest.json");
40847
+ const manifestPath = join22(ws.arcDir, "manifest.json");
40921
40848
  if (!existsSync19(manifestPath)) {
40922
40849
  err("No build found. Run `arc platform build` first.");
40923
40850
  process.exit(1);
40924
40851
  }
40925
- manifest = JSON.parse(readFileSync15(manifestPath, "utf-8"));
40852
+ manifest = JSON.parse(readFileSync16(manifestPath, "utf-8"));
40926
40853
  }
40927
40854
  log2("Loading server context...");
40928
40855
  const { context: context2, moduleAccess } = await loadServerContext(ws);
@@ -40998,7 +40925,7 @@ function attachDevWatcher(ws, platform3) {
40998
40925
  }, 300);
40999
40926
  };
41000
40927
  for (const pkg of ws.packages) {
41001
- const srcDir = join21(pkg.path, "src");
40928
+ const srcDir = join22(pkg.path, "src");
41002
40929
  if (!existsSync19(srcDir))
41003
40930
  continue;
41004
40931
  watch(srcDir, { recursive: true }, (_event, filename) => {
@@ -41009,7 +40936,7 @@ function attachDevWatcher(ws, platform3) {
41009
40936
  triggerRebuild();
41010
40937
  });
41011
40938
  }
41012
- const localesDir = join21(ws.rootDir, "locales");
40939
+ const localesDir = join22(ws.rootDir, "locales");
41013
40940
  if (existsSync19(localesDir)) {
41014
40941
  watch(localesDir, { recursive: false }, (_event, filename) => {
41015
40942
  if (!filename?.endsWith(".po"))