@arcote.tech/arc-cli 0.7.16 → 0.7.17

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
@@ -14017,7 +14017,8 @@ class EventWire {
14017
14017
  onSyncedCallback;
14018
14018
  reconnectTimeout;
14019
14019
  syncRequested = false;
14020
- viewSubscriptions = new Map;
14020
+ querySubscriptions = new Map;
14021
+ querySubCounter = 0;
14021
14022
  enableEventSync;
14022
14023
  constructor(baseUrl, options) {
14023
14024
  this.baseUrl = baseUrl;
@@ -14072,7 +14073,7 @@ class EventWire {
14072
14073
  this.requestSync();
14073
14074
  }
14074
14075
  this.flushPendingEvents();
14075
- this.sendAllViewSubscriptions();
14076
+ this.sendAllQuerySubscriptions();
14076
14077
  } else {
14077
14078
  console.log(`[EventWire] onopen called but ws is not OPEN, readyState:`, this.ws?.readyState);
14078
14079
  }
@@ -14148,24 +14149,29 @@ class EventWire {
14148
14149
  onSynced(callback) {
14149
14150
  this.onSyncedCallback = callback;
14150
14151
  }
14151
- subscribeView(element, scope, callbacks) {
14152
- const key = `${scope}:${element}`;
14153
- this.viewSubscriptions.set(key, callbacks);
14152
+ subscribeQuery(descriptor, scope, callbacks) {
14153
+ const subscriptionId = `qs_${this.instanceId}_${++this.querySubCounter}`;
14154
+ this.querySubscriptions.set(subscriptionId, {
14155
+ descriptor,
14156
+ scope,
14157
+ callbacks
14158
+ });
14154
14159
  if (this.state === "connected" && this.ws) {
14155
14160
  this.ws.send(JSON.stringify({
14156
- type: "subscribe-view",
14157
- element,
14161
+ type: "subscribe-query",
14162
+ subscriptionId,
14163
+ descriptor,
14158
14164
  scope
14159
14165
  }));
14160
14166
  }
14167
+ return subscriptionId;
14161
14168
  }
14162
- unsubscribeView(element, scope) {
14163
- this.viewSubscriptions.delete(`${scope}:${element}`);
14169
+ unsubscribeQuery(subscriptionId) {
14170
+ this.querySubscriptions.delete(subscriptionId);
14164
14171
  if (this.state === "connected" && this.ws) {
14165
14172
  this.ws.send(JSON.stringify({
14166
- type: "unsubscribe-view",
14167
- element,
14168
- scope
14173
+ type: "unsubscribe-query",
14174
+ subscriptionId
14169
14175
  }));
14170
14176
  }
14171
14177
  }
@@ -14200,17 +14206,17 @@ class EventWire {
14200
14206
  this.lastHostEventId = message.lastHostEventId;
14201
14207
  }
14202
14208
  break;
14203
- case "view-snapshot": {
14204
- const sub = this.viewSubscriptions.get(`${message.scope}:${message.element}`);
14209
+ case "query-snapshot": {
14210
+ const sub = this.querySubscriptions.get(message.subscriptionId);
14205
14211
  if (sub) {
14206
- sub.onSnapshot(message.items ?? []);
14212
+ sub.callbacks.onSnapshot(message.result ?? null);
14207
14213
  }
14208
14214
  break;
14209
14215
  }
14210
- case "view-changes": {
14211
- const sub = this.viewSubscriptions.get(`${message.scope}:${message.element}`);
14216
+ case "query-changes": {
14217
+ const sub = this.querySubscriptions.get(message.subscriptionId);
14212
14218
  if (sub && Array.isArray(message.changes)) {
14213
- sub.onChanges(message.changes);
14219
+ sub.callbacks.onChanges(message.changes);
14214
14220
  }
14215
14221
  break;
14216
14222
  }
@@ -14238,17 +14244,15 @@ class EventWire {
14238
14244
  this.pendingEvents = [];
14239
14245
  }
14240
14246
  }
14241
- sendAllViewSubscriptions() {
14247
+ sendAllQuerySubscriptions() {
14242
14248
  if (!this.ws || this.state !== "connected")
14243
14249
  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);
14250
+ for (const [subscriptionId, sub] of this.querySubscriptions) {
14248
14251
  this.ws.send(JSON.stringify({
14249
- type: "subscribe-view",
14250
- element,
14251
- scope
14252
+ type: "subscribe-query",
14253
+ subscriptionId,
14254
+ descriptor: sub.descriptor,
14255
+ scope: sub.scope
14252
14256
  }));
14253
14257
  }
14254
14258
  }
@@ -14267,19 +14271,12 @@ class LocalEventPublisher2 {
14267
14271
  views = [];
14268
14272
  syncCallback;
14269
14273
  subscribers = new Map;
14270
- viewChangesCallbacks = new Set;
14271
14274
  constructor(dataStorage) {
14272
14275
  this.dataStorage = dataStorage;
14273
14276
  }
14274
14277
  onPublish(callback) {
14275
14278
  this.syncCallback = callback;
14276
14279
  }
14277
- onViewChanges(callback) {
14278
- this.viewChangesCallbacks.add(callback);
14279
- return () => {
14280
- this.viewChangesCallbacks.delete(callback);
14281
- };
14282
- }
14283
14280
  registerViews(views) {
14284
14281
  this.views = views;
14285
14282
  }
@@ -14347,19 +14344,7 @@ class LocalEventPublisher2 {
14347
14344
  });
14348
14345
  const viewChanges = await this.collectViewChanges(event);
14349
14346
  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
- }
14347
+ await this.dataStorage.commitChanges(allChanges);
14363
14348
  await this.notifySubscribers(event);
14364
14349
  if (this.syncCallback) {
14365
14350
  this.syncCallback(event);
@@ -14740,9 +14725,8 @@ function typeValidatorBuilder(typeName, comparatorStrategy) {
14740
14725
  }
14741
14726
 
14742
14727
  class DataStorage {
14743
- async commitChanges(changes, _options) {
14728
+ async commitChanges(changes) {
14744
14729
  await Promise.all(changes.map(({ store, changes: changes2 }) => this.getStore(store).applyChanges(changes2)));
14745
- return [];
14746
14730
  }
14747
14731
  }
14748
14732
 
@@ -15099,6 +15083,47 @@ function deepMerge(target, source) {
15099
15083
  function isPlainObject(item) {
15100
15084
  return item && typeof item === "object" && !Array.isArray(item) && !(item instanceof Date) && Object.prototype.toString.call(item) === "[object Object]";
15101
15085
  }
15086
+ function murmurHash(key, seed = 0) {
15087
+ let remainder, bytes, h1, h1b, c1, c2, k1, i;
15088
+ remainder = key.length & 3;
15089
+ bytes = key.length - remainder;
15090
+ h1 = seed;
15091
+ c1 = 3432918353;
15092
+ c2 = 461845907;
15093
+ i = 0;
15094
+ while (i < bytes) {
15095
+ k1 = key.charCodeAt(i) & 255 | (key.charCodeAt(++i) & 255) << 8 | (key.charCodeAt(++i) & 255) << 16 | (key.charCodeAt(++i) & 255) << 24;
15096
+ ++i;
15097
+ k1 = (k1 & 65535) * c1 + (((k1 >>> 16) * c1 & 65535) << 16) & 4294967295;
15098
+ k1 = k1 << 15 | k1 >>> 17;
15099
+ k1 = (k1 & 65535) * c2 + (((k1 >>> 16) * c2 & 65535) << 16) & 4294967295;
15100
+ h1 ^= k1;
15101
+ h1 = h1 << 13 | h1 >>> 19;
15102
+ h1b = (h1 & 65535) * 5 + (((h1 >>> 16) * 5 & 65535) << 16) & 4294967295;
15103
+ h1 = (h1b & 65535) + 27492 + (((h1b >>> 16) + 58964 & 65535) << 16);
15104
+ }
15105
+ k1 = 0;
15106
+ if (remainder >= 3) {
15107
+ k1 ^= (key.charCodeAt(i + 2) & 255) << 16;
15108
+ }
15109
+ if (remainder >= 2) {
15110
+ k1 ^= (key.charCodeAt(i + 1) & 255) << 8;
15111
+ }
15112
+ if (remainder >= 1) {
15113
+ k1 ^= key.charCodeAt(i) & 255;
15114
+ k1 = (k1 & 65535) * c1 + (((k1 >>> 16) * c1 & 65535) << 16) & 4294967295;
15115
+ k1 = k1 << 15 | k1 >>> 17;
15116
+ k1 = (k1 & 65535) * c2 + (((k1 >>> 16) * c2 & 65535) << 16) & 4294967295;
15117
+ h1 ^= k1;
15118
+ }
15119
+ h1 ^= key.length;
15120
+ h1 ^= h1 >>> 16;
15121
+ h1 = (h1 & 65535) * 2246822507 + (((h1 >>> 16) * 2246822507 & 65535) << 16) & 4294967295;
15122
+ h1 ^= h1 >>> 13;
15123
+ h1 = (h1 & 65535) * 3266489909 + (((h1 >>> 16) * 3266489909 & 65535) << 16) & 4294967295;
15124
+ h1 ^= h1 >>> 16;
15125
+ return h1 >>> 0;
15126
+ }
15102
15127
  function resolveQueryChange(currentResult, event3, options) {
15103
15128
  const index = currentResult.findIndex((e2) => e2._id === event3.id);
15104
15129
  const isInCurrentResult = index !== -1;
@@ -15200,8 +15225,8 @@ class ObservableDataStorage {
15200
15225
  getReadWriteTransaction() {
15201
15226
  return this.source.getReadWriteTransaction();
15202
15227
  }
15203
- commitChanges(changes, options) {
15204
- return this.source.commitChanges(changes, options);
15228
+ commitChanges(changes) {
15229
+ return this.source.commitChanges(changes);
15205
15230
  }
15206
15231
  trackQuery(storeName, options, result, listener4) {
15207
15232
  const key = this.getQueryKey(storeName, options);
@@ -15214,7 +15239,8 @@ class ObservableDataStorage {
15214
15239
  }
15215
15240
  handleStoreChange(storeName, events) {
15216
15241
  let hasChanges = false;
15217
- for (const query of this.trackedQueries.values()) {
15242
+ const staleKeys = [];
15243
+ for (const [key, query] of this.trackedQueries) {
15218
15244
  if (query.storeName !== storeName)
15219
15245
  continue;
15220
15246
  let currentResult = query.result;
@@ -15226,10 +15252,20 @@ class ObservableDataStorage {
15226
15252
  queryChanged = true;
15227
15253
  }
15228
15254
  }
15229
- if (queryChanged) {
15230
- query.result = currentResult;
15255
+ if (!queryChanged)
15256
+ continue;
15257
+ if (query.options.limit !== undefined && query.result.length === query.options.limit && currentResult.length < query.options.limit) {
15258
+ staleKeys.push(key);
15231
15259
  hasChanges = true;
15260
+ continue;
15232
15261
  }
15262
+ query.result = currentResult;
15263
+ hasChanges = true;
15264
+ }
15265
+ for (const key of staleKeys) {
15266
+ const query = this.trackedQueries.get(key);
15267
+ this.source.getStore(query.storeName).unsubscribe(query.listener);
15268
+ this.trackedQueries.delete(key);
15233
15269
  }
15234
15270
  if (hasChanges) {
15235
15271
  this.onChange();
@@ -15499,243 +15535,136 @@ class Model2 {
15499
15535
  return s;
15500
15536
  }
15501
15537
  }
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);
15538
+ function applyQueryChanges(result, changes) {
15539
+ const next = [...result];
15540
+ for (const change of changes) {
15541
+ if (change.type === "delete") {
15542
+ const idx = next.findIndex((it) => it._id === change.id);
15543
+ if (idx !== -1)
15544
+ next.splice(idx, 1);
15519
15545
  }
15520
- return this.stores.get(key);
15521
15546
  }
15522
- hasData(viewName, scope) {
15523
- const store = this.stores.get(this.storeKey(viewName, scope));
15524
- return store ? store.hasData() : false;
15525
- }
15526
- registerStream(key, createStream) {
15527
- const pending = this.pendingUnsubscribes.get(key);
15528
- if (pending) {
15529
- clearTimeout(pending);
15530
- this.pendingUnsubscribes.delete(key);
15547
+ for (const change of changes) {
15548
+ if (change.type === "set") {
15549
+ const idx = next.findIndex((it) => it._id === change.id);
15550
+ if (idx !== -1)
15551
+ next.splice(idx, 1);
15552
+ next.splice(change.index, 0, change.item);
15531
15553
  }
15532
- const existing = this.activeStreams.get(key);
15533
- if (existing) {
15534
- existing.refCount++;
15535
- return {
15536
- unsubscribe: () => this.unregisterStream(key),
15537
- wasReused: true
15554
+ }
15555
+ return next;
15556
+ }
15557
+
15558
+ class StreamingQueryCache {
15559
+ entries = new Map;
15560
+ static UNSUBSCRIBE_DELAY_MS = 5000;
15561
+ entryKey(descriptor, scope) {
15562
+ return `${scope ?? "default"}:${murmurHash(JSON.stringify(descriptor))}`;
15563
+ }
15564
+ subscribe(descriptor, scope, eventWire, onChange) {
15565
+ const key = this.entryKey(descriptor, scope);
15566
+ let entry = this.entries.get(key);
15567
+ if (entry) {
15568
+ if (entry.pendingUnsub) {
15569
+ clearTimeout(entry.pendingUnsub);
15570
+ entry.pendingUnsub = undefined;
15571
+ }
15572
+ entry.refCount++;
15573
+ } else {
15574
+ const newEntry = {
15575
+ result: undefined,
15576
+ hasResult: false,
15577
+ listeners: new Set,
15578
+ refCount: 1,
15579
+ subscriptionId: ""
15538
15580
  };
15581
+ newEntry.subscriptionId = eventWire.subscribeQuery(descriptor, scope, {
15582
+ onSnapshot: (result) => {
15583
+ newEntry.result = result;
15584
+ newEntry.hasResult = true;
15585
+ this.notify(newEntry);
15586
+ },
15587
+ onChanges: (changes) => {
15588
+ if (!newEntry.hasResult || !Array.isArray(newEntry.result)) {
15589
+ return;
15590
+ }
15591
+ newEntry.result = applyQueryChanges(newEntry.result, changes);
15592
+ this.notify(newEntry);
15593
+ }
15594
+ });
15595
+ this.entries.set(key, newEntry);
15596
+ entry = newEntry;
15539
15597
  }
15540
- const streamConn = createStream();
15541
- this.activeStreams.set(key, {
15542
- unsubscribe: streamConn.unsubscribe,
15543
- refCount: 1
15544
- });
15598
+ const subscribed = entry;
15599
+ subscribed.listeners.add(onChange);
15600
+ let active = true;
15545
15601
  return {
15546
- unsubscribe: () => this.unregisterStream(key),
15547
- wasReused: false
15602
+ read: () => ({
15603
+ result: subscribed.result,
15604
+ loading: !subscribed.hasResult
15605
+ }),
15606
+ unsubscribe: () => {
15607
+ if (!active)
15608
+ return;
15609
+ active = false;
15610
+ subscribed.listeners.delete(onChange);
15611
+ subscribed.refCount--;
15612
+ if (subscribed.refCount > 0)
15613
+ return;
15614
+ subscribed.pendingUnsub = setTimeout(() => {
15615
+ subscribed.pendingUnsub = undefined;
15616
+ if (subscribed.refCount > 0)
15617
+ return;
15618
+ eventWire.unsubscribeQuery(subscribed.subscriptionId);
15619
+ this.entries.delete(key);
15620
+ }, StreamingQueryCache.UNSUBSCRIBE_DELAY_MS);
15621
+ }
15548
15622
  };
15549
15623
  }
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) {
15624
+ invalidateScope(scope, eventWire) {
15583
15625
  const prefix = `${scope}:`;
15584
- for (const [key, timeout] of this.pendingUnsubscribes) {
15585
- if (!key.startsWith(prefix))
15586
- 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) {
15626
+ for (const [key, entry] of this.entries) {
15599
15627
  if (!key.startsWith(prefix))
15600
15628
  continue;
15601
- store.clear();
15629
+ if (entry.pendingUnsub)
15630
+ clearTimeout(entry.pendingUnsub);
15631
+ eventWire?.unsubscribeQuery(entry.subscriptionId);
15632
+ this.entries.delete(key);
15633
+ entry.result = undefined;
15634
+ entry.hasResult = false;
15635
+ this.notify(entry);
15602
15636
  }
15603
15637
  }
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
- }
15634
- }
15635
- }
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();
15647
- }
15648
- }
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);
15638
+ clear(eventWire) {
15639
+ for (const entry of this.entries.values()) {
15640
+ if (entry.pendingUnsub)
15641
+ clearTimeout(entry.pendingUnsub);
15642
+ eventWire?.unsubscribeQuery(entry.subscriptionId);
15663
15643
  }
15664
- this.notifyListeners(null);
15644
+ this.entries.clear();
15665
15645
  }
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);
15646
+ notify(entry) {
15647
+ for (const listener4 of entry.listeners) {
15648
+ try {
15649
+ listener4();
15650
+ } catch (err3) {
15651
+ console.error(`[Arc] Query cache listener error:`, err3);
15674
15652
  }
15675
15653
  }
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
15654
  }
15722
15655
  }
15723
15656
 
15724
15657
  class StreamingEventPublisher {
15725
- cache;
15726
15658
  eventWire;
15727
15659
  views = [];
15728
15660
  subscribers = new Map;
15729
- constructor(cache, eventWire) {
15730
- this.cache = cache;
15661
+ constructor(eventWire) {
15731
15662
  this.eventWire = eventWire;
15732
15663
  }
15733
15664
  registerViews(views) {
15734
15665
  this.views = views;
15735
- this.cache.registerViews(views);
15736
15666
  }
15737
15667
  async publish(event3) {
15738
- await this.cache.applyEvent(event3);
15739
15668
  await this.notifySubscribers(event3);
15740
15669
  this.eventWire.syncEvents([
15741
15670
  {
@@ -18142,7 +18071,7 @@ var Operation, PROXY_DRAFT, RAW_RETURN_SYMBOL, iteratorSymbol, dataTypes, intern
18142
18071
  }
18143
18072
  return returnValue(result);
18144
18073
  };
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) {
18074
+ }, 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, originCache, originStackCache, originError, CLOSE, Query, PostgresError, Errors, types2, Identifier, Parameter, Builder, defaultHandlers, builders, serializers, parsers, mergeUserTypes = function(types22) {
18146
18075
  const user = typeHandlers(types22 || {});
18147
18076
  return {
18148
18077
  serializers: Object.assign({}, serializers, user.serializers),
@@ -19748,12 +19677,8 @@ var init_dist = __esm(() => {
19748
19677
  }
19749
19678
  return transaction.find(this.storeName, { where: { _id: id2 } }).then((results) => results[0]);
19750
19679
  }
19751
- async applyChangeAndReturnEvent(transaction, change, transactionCache, options) {
19680
+ async applyChangeAndReturnEvent(transaction, change, transactionCache) {
19752
19681
  if (change.type === "set") {
19753
- let existing;
19754
- if (options?.captureRows) {
19755
- existing = await this.readExisting(transaction, change.data._id, transactionCache);
19756
- }
19757
19682
  await transaction.set(this.storeName, change.data);
19758
19683
  const item = this.deserialize ? this.deserialize(change.data) : change.data;
19759
19684
  if (transactionCache) {
@@ -19766,16 +19691,10 @@ var init_dist = __esm(() => {
19766
19691
  type: "set",
19767
19692
  item: change.data,
19768
19693
  id: change.data._id
19769
- },
19770
- oldRow: existing ?? null,
19771
- newRow: change.data
19694
+ }
19772
19695
  };
19773
19696
  }
19774
19697
  if (change.type === "delete") {
19775
- let existing;
19776
- if (options?.captureRows) {
19777
- existing = await this.readExisting(transaction, change.id, transactionCache);
19778
- }
19779
19698
  await transaction.remove(this.storeName, change.id);
19780
19699
  if (transactionCache) {
19781
19700
  transactionCache.delete(`${this.storeName}:${change.id}`);
@@ -19787,9 +19706,7 @@ var init_dist = __esm(() => {
19787
19706
  type: "delete",
19788
19707
  item: null,
19789
19708
  id: change.id
19790
- },
19791
- oldRow: existing ?? null,
19792
- newRow: null
19709
+ }
19793
19710
  };
19794
19711
  }
19795
19712
  if (change.type === "modify") {
@@ -19807,9 +19724,7 @@ var init_dist = __esm(() => {
19807
19724
  type: "set",
19808
19725
  item,
19809
19726
  id: change.id
19810
- },
19811
- oldRow: existing ?? null,
19812
- newRow: updated
19727
+ }
19813
19728
  };
19814
19729
  }
19815
19730
  if (change.type === "mutate") {
@@ -19827,9 +19742,7 @@ var init_dist = __esm(() => {
19827
19742
  type: "set",
19828
19743
  item,
19829
19744
  id: change.id
19830
- },
19831
- oldRow: existing ?? null,
19832
- newRow: updated
19745
+ }
19833
19746
  };
19834
19747
  }
19835
19748
  throw new Error("Unknown change type");
@@ -19906,22 +19819,17 @@ var init_dist = __esm(() => {
19906
19819
  applySerializedChanges(changes) {
19907
19820
  return Promise.all(changes.map(({ store, changes: changes2 }) => this.getStore(store).applySerializedChanges(changes2)));
19908
19821
  }
19909
- async commitChanges(changes, options) {
19822
+ async commitChanges(changes) {
19910
19823
  const transaction = await this.getReadWriteTransaction();
19911
19824
  const transactionCache = new Map;
19912
19825
  const eventsByStore = new Map;
19913
- const committed = [];
19914
19826
  for (const { store, changes: storeChanges } of changes) {
19915
19827
  const storeState = this.getStore(store);
19916
19828
  const storeEvents = [];
19917
- const capture = options?.captureRowsFor?.has(store) ?? false;
19918
19829
  for (const change of storeChanges) {
19919
- const { event: event3, oldRow, newRow } = await storeState.applyChangeAndReturnEvent(transaction, change, transactionCache, { captureRows: capture });
19830
+ const { event: event3 } = await storeState.applyChangeAndReturnEvent(transaction, change, transactionCache);
19920
19831
  if (event3)
19921
19832
  storeEvents.push(event3);
19922
- if (capture) {
19923
- committed.push({ store, id: event3.id, oldRow, newRow });
19924
- }
19925
19833
  }
19926
19834
  if (storeEvents.length > 0) {
19927
19835
  eventsByStore.set(store, storeEvents);
@@ -19932,7 +19840,6 @@ var init_dist = __esm(() => {
19932
19840
  const storeState = this.getStore(store);
19933
19841
  storeState.notifyListenersPublic(events);
19934
19842
  }
19935
- return committed;
19936
19843
  }
19937
19844
  fork() {
19938
19845
  return new ForkedDataStorage(this);
@@ -21448,7 +21355,8 @@ class EventWire2 {
21448
21355
  onSyncedCallback;
21449
21356
  reconnectTimeout;
21450
21357
  syncRequested = false;
21451
- viewSubscriptions = new Map;
21358
+ querySubscriptions = new Map;
21359
+ querySubCounter = 0;
21452
21360
  enableEventSync;
21453
21361
  constructor(baseUrl, options) {
21454
21362
  this.baseUrl = baseUrl;
@@ -21503,7 +21411,7 @@ class EventWire2 {
21503
21411
  this.requestSync();
21504
21412
  }
21505
21413
  this.flushPendingEvents();
21506
- this.sendAllViewSubscriptions();
21414
+ this.sendAllQuerySubscriptions();
21507
21415
  } else {
21508
21416
  console.log(`[EventWire] onopen called but ws is not OPEN, readyState:`, this.ws?.readyState);
21509
21417
  }
@@ -21579,24 +21487,29 @@ class EventWire2 {
21579
21487
  onSynced(callback) {
21580
21488
  this.onSyncedCallback = callback;
21581
21489
  }
21582
- subscribeView(element, scope, callbacks) {
21583
- const key = `${scope}:${element}`;
21584
- this.viewSubscriptions.set(key, callbacks);
21490
+ subscribeQuery(descriptor, scope, callbacks) {
21491
+ const subscriptionId = `qs_${this.instanceId}_${++this.querySubCounter}`;
21492
+ this.querySubscriptions.set(subscriptionId, {
21493
+ descriptor,
21494
+ scope,
21495
+ callbacks
21496
+ });
21585
21497
  if (this.state === "connected" && this.ws) {
21586
21498
  this.ws.send(JSON.stringify({
21587
- type: "subscribe-view",
21588
- element,
21499
+ type: "subscribe-query",
21500
+ subscriptionId,
21501
+ descriptor,
21589
21502
  scope
21590
21503
  }));
21591
21504
  }
21505
+ return subscriptionId;
21592
21506
  }
21593
- unsubscribeView(element, scope) {
21594
- this.viewSubscriptions.delete(`${scope}:${element}`);
21507
+ unsubscribeQuery(subscriptionId) {
21508
+ this.querySubscriptions.delete(subscriptionId);
21595
21509
  if (this.state === "connected" && this.ws) {
21596
21510
  this.ws.send(JSON.stringify({
21597
- type: "unsubscribe-view",
21598
- element,
21599
- scope
21511
+ type: "unsubscribe-query",
21512
+ subscriptionId
21600
21513
  }));
21601
21514
  }
21602
21515
  }
@@ -21631,17 +21544,17 @@ class EventWire2 {
21631
21544
  this.lastHostEventId = message.lastHostEventId;
21632
21545
  }
21633
21546
  break;
21634
- case "view-snapshot": {
21635
- const sub = this.viewSubscriptions.get(`${message.scope}:${message.element}`);
21547
+ case "query-snapshot": {
21548
+ const sub = this.querySubscriptions.get(message.subscriptionId);
21636
21549
  if (sub) {
21637
- sub.onSnapshot(message.items ?? []);
21550
+ sub.callbacks.onSnapshot(message.result ?? null);
21638
21551
  }
21639
21552
  break;
21640
21553
  }
21641
- case "view-changes": {
21642
- const sub = this.viewSubscriptions.get(`${message.scope}:${message.element}`);
21554
+ case "query-changes": {
21555
+ const sub = this.querySubscriptions.get(message.subscriptionId);
21643
21556
  if (sub && Array.isArray(message.changes)) {
21644
- sub.onChanges(message.changes);
21557
+ sub.callbacks.onChanges(message.changes);
21645
21558
  }
21646
21559
  break;
21647
21560
  }
@@ -21669,17 +21582,15 @@ class EventWire2 {
21669
21582
  this.pendingEvents = [];
21670
21583
  }
21671
21584
  }
21672
- sendAllViewSubscriptions() {
21585
+ sendAllQuerySubscriptions() {
21673
21586
  if (!this.ws || this.state !== "connected")
21674
21587
  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);
21588
+ for (const [subscriptionId, sub] of this.querySubscriptions) {
21679
21589
  this.ws.send(JSON.stringify({
21680
- type: "subscribe-view",
21681
- element,
21682
- scope
21590
+ type: "subscribe-query",
21591
+ subscriptionId,
21592
+ descriptor: sub.descriptor,
21593
+ scope: sub.scope
21683
21594
  }));
21684
21595
  }
21685
21596
  }
@@ -21698,19 +21609,12 @@ class LocalEventPublisher3 {
21698
21609
  views = [];
21699
21610
  syncCallback;
21700
21611
  subscribers = new Map;
21701
- viewChangesCallbacks = new Set;
21702
21612
  constructor(dataStorage) {
21703
21613
  this.dataStorage = dataStorage;
21704
21614
  }
21705
21615
  onPublish(callback) {
21706
21616
  this.syncCallback = callback;
21707
21617
  }
21708
- onViewChanges(callback) {
21709
- this.viewChangesCallbacks.add(callback);
21710
- return () => {
21711
- this.viewChangesCallbacks.delete(callback);
21712
- };
21713
- }
21714
21618
  registerViews(views) {
21715
21619
  this.views = views;
21716
21620
  }
@@ -21778,19 +21682,7 @@ class LocalEventPublisher3 {
21778
21682
  });
21779
21683
  const viewChanges = await this.collectViewChanges(event);
21780
21684
  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
- }
21685
+ await this.dataStorage.commitChanges(allChanges);
21794
21686
  await this.notifySubscribers(event);
21795
21687
  if (this.syncCallback) {
21796
21688
  this.syncCallback(event);
@@ -22171,9 +22063,8 @@ function typeValidatorBuilder2(typeName, comparatorStrategy) {
22171
22063
  }
22172
22064
 
22173
22065
  class DataStorage2 {
22174
- async commitChanges(changes, _options) {
22066
+ async commitChanges(changes) {
22175
22067
  await Promise.all(changes.map(({ store, changes: changes2 }) => this.getStore(store).applyChanges(changes2)));
22176
- return [];
22177
22068
  }
22178
22069
  }
22179
22070
 
@@ -22530,6 +22421,47 @@ function deepMerge2(target, source) {
22530
22421
  function isPlainObject2(item) {
22531
22422
  return item && typeof item === "object" && !Array.isArray(item) && !(item instanceof Date) && Object.prototype.toString.call(item) === "[object Object]";
22532
22423
  }
22424
+ function murmurHash2(key, seed = 0) {
22425
+ let remainder, bytes, h1, h1b, c1, c2, k1, i;
22426
+ remainder = key.length & 3;
22427
+ bytes = key.length - remainder;
22428
+ h1 = seed;
22429
+ c1 = 3432918353;
22430
+ c2 = 461845907;
22431
+ i = 0;
22432
+ while (i < bytes) {
22433
+ k1 = key.charCodeAt(i) & 255 | (key.charCodeAt(++i) & 255) << 8 | (key.charCodeAt(++i) & 255) << 16 | (key.charCodeAt(++i) & 255) << 24;
22434
+ ++i;
22435
+ k1 = (k1 & 65535) * c1 + (((k1 >>> 16) * c1 & 65535) << 16) & 4294967295;
22436
+ k1 = k1 << 15 | k1 >>> 17;
22437
+ k1 = (k1 & 65535) * c2 + (((k1 >>> 16) * c2 & 65535) << 16) & 4294967295;
22438
+ h1 ^= k1;
22439
+ h1 = h1 << 13 | h1 >>> 19;
22440
+ h1b = (h1 & 65535) * 5 + (((h1 >>> 16) * 5 & 65535) << 16) & 4294967295;
22441
+ h1 = (h1b & 65535) + 27492 + (((h1b >>> 16) + 58964 & 65535) << 16);
22442
+ }
22443
+ k1 = 0;
22444
+ if (remainder >= 3) {
22445
+ k1 ^= (key.charCodeAt(i + 2) & 255) << 16;
22446
+ }
22447
+ if (remainder >= 2) {
22448
+ k1 ^= (key.charCodeAt(i + 1) & 255) << 8;
22449
+ }
22450
+ if (remainder >= 1) {
22451
+ k1 ^= key.charCodeAt(i) & 255;
22452
+ k1 = (k1 & 65535) * c1 + (((k1 >>> 16) * c1 & 65535) << 16) & 4294967295;
22453
+ k1 = k1 << 15 | k1 >>> 17;
22454
+ k1 = (k1 & 65535) * c2 + (((k1 >>> 16) * c2 & 65535) << 16) & 4294967295;
22455
+ h1 ^= k1;
22456
+ }
22457
+ h1 ^= key.length;
22458
+ h1 ^= h1 >>> 16;
22459
+ h1 = (h1 & 65535) * 2246822507 + (((h1 >>> 16) * 2246822507 & 65535) << 16) & 4294967295;
22460
+ h1 ^= h1 >>> 13;
22461
+ h1 = (h1 & 65535) * 3266489909 + (((h1 >>> 16) * 3266489909 & 65535) << 16) & 4294967295;
22462
+ h1 ^= h1 >>> 16;
22463
+ return h1 >>> 0;
22464
+ }
22533
22465
  function resolveQueryChange2(currentResult, event3, options) {
22534
22466
  const index = currentResult.findIndex((e2) => e2._id === event3.id);
22535
22467
  const isInCurrentResult = index !== -1;
@@ -22631,8 +22563,8 @@ class ObservableDataStorage2 {
22631
22563
  getReadWriteTransaction() {
22632
22564
  return this.source.getReadWriteTransaction();
22633
22565
  }
22634
- commitChanges(changes, options) {
22635
- return this.source.commitChanges(changes, options);
22566
+ commitChanges(changes) {
22567
+ return this.source.commitChanges(changes);
22636
22568
  }
22637
22569
  trackQuery(storeName, options, result, listener4) {
22638
22570
  const key = this.getQueryKey(storeName, options);
@@ -22645,7 +22577,8 @@ class ObservableDataStorage2 {
22645
22577
  }
22646
22578
  handleStoreChange(storeName, events) {
22647
22579
  let hasChanges = false;
22648
- for (const query of this.trackedQueries.values()) {
22580
+ const staleKeys = [];
22581
+ for (const [key, query] of this.trackedQueries) {
22649
22582
  if (query.storeName !== storeName)
22650
22583
  continue;
22651
22584
  let currentResult = query.result;
@@ -22657,10 +22590,20 @@ class ObservableDataStorage2 {
22657
22590
  queryChanged = true;
22658
22591
  }
22659
22592
  }
22660
- if (queryChanged) {
22661
- query.result = currentResult;
22593
+ if (!queryChanged)
22594
+ continue;
22595
+ if (query.options.limit !== undefined && query.result.length === query.options.limit && currentResult.length < query.options.limit) {
22596
+ staleKeys.push(key);
22662
22597
  hasChanges = true;
22598
+ continue;
22663
22599
  }
22600
+ query.result = currentResult;
22601
+ hasChanges = true;
22602
+ }
22603
+ for (const key of staleKeys) {
22604
+ const query = this.trackedQueries.get(key);
22605
+ this.source.getStore(query.storeName).unsubscribe(query.listener);
22606
+ this.trackedQueries.delete(key);
22664
22607
  }
22665
22608
  if (hasChanges) {
22666
22609
  this.onChange();
@@ -22930,243 +22873,136 @@ class Model3 {
22930
22873
  return s;
22931
22874
  }
22932
22875
  }
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);
22876
+ function applyQueryChanges2(result, changes) {
22877
+ const next = [...result];
22878
+ for (const change of changes) {
22879
+ if (change.type === "delete") {
22880
+ const idx = next.findIndex((it) => it._id === change.id);
22881
+ if (idx !== -1)
22882
+ next.splice(idx, 1);
22950
22883
  }
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
22884
  }
22957
- registerStream(key, createStream) {
22958
- const pending = this.pendingUnsubscribes.get(key);
22959
- if (pending) {
22960
- clearTimeout(pending);
22961
- this.pendingUnsubscribes.delete(key);
22885
+ for (const change of changes) {
22886
+ if (change.type === "set") {
22887
+ const idx = next.findIndex((it) => it._id === change.id);
22888
+ if (idx !== -1)
22889
+ next.splice(idx, 1);
22890
+ next.splice(change.index, 0, change.item);
22962
22891
  }
22963
- const existing = this.activeStreams.get(key);
22964
- if (existing) {
22965
- existing.refCount++;
22966
- return {
22967
- unsubscribe: () => this.unregisterStream(key),
22968
- wasReused: true
22892
+ }
22893
+ return next;
22894
+ }
22895
+
22896
+ class StreamingQueryCache2 {
22897
+ entries = new Map;
22898
+ static UNSUBSCRIBE_DELAY_MS = 5000;
22899
+ entryKey(descriptor, scope) {
22900
+ return `${scope ?? "default"}:${murmurHash2(JSON.stringify(descriptor))}`;
22901
+ }
22902
+ subscribe(descriptor, scope, eventWire, onChange) {
22903
+ const key = this.entryKey(descriptor, scope);
22904
+ let entry = this.entries.get(key);
22905
+ if (entry) {
22906
+ if (entry.pendingUnsub) {
22907
+ clearTimeout(entry.pendingUnsub);
22908
+ entry.pendingUnsub = undefined;
22909
+ }
22910
+ entry.refCount++;
22911
+ } else {
22912
+ const newEntry = {
22913
+ result: undefined,
22914
+ hasResult: false,
22915
+ listeners: new Set,
22916
+ refCount: 1,
22917
+ subscriptionId: ""
22969
22918
  };
22919
+ newEntry.subscriptionId = eventWire.subscribeQuery(descriptor, scope, {
22920
+ onSnapshot: (result) => {
22921
+ newEntry.result = result;
22922
+ newEntry.hasResult = true;
22923
+ this.notify(newEntry);
22924
+ },
22925
+ onChanges: (changes) => {
22926
+ if (!newEntry.hasResult || !Array.isArray(newEntry.result)) {
22927
+ return;
22928
+ }
22929
+ newEntry.result = applyQueryChanges2(newEntry.result, changes);
22930
+ this.notify(newEntry);
22931
+ }
22932
+ });
22933
+ this.entries.set(key, newEntry);
22934
+ entry = newEntry;
22970
22935
  }
22971
- const streamConn = createStream();
22972
- this.activeStreams.set(key, {
22973
- unsubscribe: streamConn.unsubscribe,
22974
- refCount: 1
22975
- });
22936
+ const subscribed = entry;
22937
+ subscribed.listeners.add(onChange);
22938
+ let active = true;
22976
22939
  return {
22977
- unsubscribe: () => this.unregisterStream(key),
22978
- wasReused: false
22940
+ read: () => ({
22941
+ result: subscribed.result,
22942
+ loading: !subscribed.hasResult
22943
+ }),
22944
+ unsubscribe: () => {
22945
+ if (!active)
22946
+ return;
22947
+ active = false;
22948
+ subscribed.listeners.delete(onChange);
22949
+ subscribed.refCount--;
22950
+ if (subscribed.refCount > 0)
22951
+ return;
22952
+ subscribed.pendingUnsub = setTimeout(() => {
22953
+ subscribed.pendingUnsub = undefined;
22954
+ if (subscribed.refCount > 0)
22955
+ return;
22956
+ eventWire.unsubscribeQuery(subscribed.subscriptionId);
22957
+ this.entries.delete(key);
22958
+ }, StreamingQueryCache2.UNSUBSCRIBE_DELAY_MS);
22959
+ }
22979
22960
  };
22980
22961
  }
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) {
22962
+ invalidateScope(scope, eventWire) {
23014
22963
  const prefix = `${scope}:`;
23015
- for (const [key, timeout] of this.pendingUnsubscribes) {
23016
- if (!key.startsWith(prefix))
23017
- 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) {
22964
+ for (const [key, entry] of this.entries) {
23030
22965
  if (!key.startsWith(prefix))
23031
22966
  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);
22967
+ if (entry.pendingUnsub)
22968
+ clearTimeout(entry.pendingUnsub);
22969
+ eventWire?.unsubscribeQuery(entry.subscriptionId);
22970
+ this.entries.delete(key);
22971
+ entry.result = undefined;
22972
+ entry.hasResult = false;
22973
+ this.notify(entry);
23074
22974
  }
23075
- this.pendingUnsubscribes.clear();
23076
- for (const store of this.stores.values()) {
23077
- store.clear();
23078
- }
23079
- }
23080
- }
23081
-
23082
- class StreamingStore2 {
23083
- data = new Map;
23084
- listeners = new Set;
23085
- initialized = false;
23086
- hasData() {
23087
- return this.initialized;
23088
22975
  }
23089
- setAll(items) {
23090
- this.initialized = true;
23091
- this.data.clear();
23092
- for (const item of items) {
23093
- this.data.set(item._id, item);
22976
+ clear(eventWire) {
22977
+ for (const entry of this.entries.values()) {
22978
+ if (entry.pendingUnsub)
22979
+ clearTimeout(entry.pendingUnsub);
22980
+ eventWire?.unsubscribeQuery(entry.subscriptionId);
23094
22981
  }
23095
- this.notifyListeners(null);
22982
+ this.entries.clear();
23096
22983
  }
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);
22984
+ notify(entry) {
22985
+ for (const listener4 of entry.listeners) {
22986
+ try {
22987
+ listener4();
22988
+ } catch (err3) {
22989
+ console.error(`[Arc] Query cache listener error:`, err3);
23105
22990
  }
23106
22991
  }
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
22992
  }
23153
22993
  }
23154
22994
 
23155
22995
  class StreamingEventPublisher2 {
23156
- cache;
23157
22996
  eventWire;
23158
22997
  views = [];
23159
22998
  subscribers = new Map;
23160
- constructor(cache, eventWire) {
23161
- this.cache = cache;
22999
+ constructor(eventWire) {
23162
23000
  this.eventWire = eventWire;
23163
23001
  }
23164
23002
  registerViews(views) {
23165
23003
  this.views = views;
23166
- this.cache.registerViews(views);
23167
23004
  }
23168
23005
  async publish(event3) {
23169
- await this.cache.applyEvent(event3);
23170
23006
  await this.notifySubscribers(event3);
23171
23007
  this.eventWire.syncEvents([
23172
23008
  {
@@ -24049,7 +23885,7 @@ var Operation2, PROXY_DRAFT2, RAW_RETURN_SYMBOL2, iteratorSymbol2, dataTypes2, i
24049
23885
  }
24050
23886
  return returnValue(result);
24051
23887
  };
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) => {
23888
+ }, 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, SQLiteReadWriteTransaction, createSQLiteAdapterFactory = (db) => {
24053
23889
  return async (context) => {
24054
23890
  const adapter = new SQLiteAdapter(db, context);
24055
23891
  await adapter.initialize();
@@ -25614,12 +25450,8 @@ var init_dist2 = __esm(() => {
25614
25450
  }
25615
25451
  return transaction.find(this.storeName, { where: { _id: id22 } }).then((results) => results[0]);
25616
25452
  }
25617
- async applyChangeAndReturnEvent(transaction, change, transactionCache, options) {
25453
+ async applyChangeAndReturnEvent(transaction, change, transactionCache) {
25618
25454
  if (change.type === "set") {
25619
- let existing;
25620
- if (options?.captureRows) {
25621
- existing = await this.readExisting(transaction, change.data._id, transactionCache);
25622
- }
25623
25455
  await transaction.set(this.storeName, change.data);
25624
25456
  const item = this.deserialize ? this.deserialize(change.data) : change.data;
25625
25457
  if (transactionCache) {
@@ -25632,16 +25464,10 @@ var init_dist2 = __esm(() => {
25632
25464
  type: "set",
25633
25465
  item: change.data,
25634
25466
  id: change.data._id
25635
- },
25636
- oldRow: existing ?? null,
25637
- newRow: change.data
25467
+ }
25638
25468
  };
25639
25469
  }
25640
25470
  if (change.type === "delete") {
25641
- let existing;
25642
- if (options?.captureRows) {
25643
- existing = await this.readExisting(transaction, change.id, transactionCache);
25644
- }
25645
25471
  await transaction.remove(this.storeName, change.id);
25646
25472
  if (transactionCache) {
25647
25473
  transactionCache.delete(`${this.storeName}:${change.id}`);
@@ -25653,9 +25479,7 @@ var init_dist2 = __esm(() => {
25653
25479
  type: "delete",
25654
25480
  item: null,
25655
25481
  id: change.id
25656
- },
25657
- oldRow: existing ?? null,
25658
- newRow: null
25482
+ }
25659
25483
  };
25660
25484
  }
25661
25485
  if (change.type === "modify") {
@@ -25673,9 +25497,7 @@ var init_dist2 = __esm(() => {
25673
25497
  type: "set",
25674
25498
  item,
25675
25499
  id: change.id
25676
- },
25677
- oldRow: existing ?? null,
25678
- newRow: updated
25500
+ }
25679
25501
  };
25680
25502
  }
25681
25503
  if (change.type === "mutate") {
@@ -25693,9 +25515,7 @@ var init_dist2 = __esm(() => {
25693
25515
  type: "set",
25694
25516
  item,
25695
25517
  id: change.id
25696
- },
25697
- oldRow: existing ?? null,
25698
- newRow: updated
25518
+ }
25699
25519
  };
25700
25520
  }
25701
25521
  throw new Error("Unknown change type");
@@ -25772,22 +25592,17 @@ var init_dist2 = __esm(() => {
25772
25592
  applySerializedChanges(changes) {
25773
25593
  return Promise.all(changes.map(({ store, changes: changes2 }) => this.getStore(store).applySerializedChanges(changes2)));
25774
25594
  }
25775
- async commitChanges(changes, options) {
25595
+ async commitChanges(changes) {
25776
25596
  const transaction = await this.getReadWriteTransaction();
25777
25597
  const transactionCache = new Map;
25778
25598
  const eventsByStore = new Map;
25779
- const committed = [];
25780
25599
  for (const { store, changes: storeChanges } of changes) {
25781
25600
  const storeState = this.getStore(store);
25782
25601
  const storeEvents = [];
25783
- const capture = options?.captureRowsFor?.has(store) ?? false;
25784
25602
  for (const change of storeChanges) {
25785
- const { event: event3, oldRow, newRow } = await storeState.applyChangeAndReturnEvent(transaction, change, transactionCache, { captureRows: capture });
25603
+ const { event: event3 } = await storeState.applyChangeAndReturnEvent(transaction, change, transactionCache);
25786
25604
  if (event3)
25787
25605
  storeEvents.push(event3);
25788
- if (capture) {
25789
- committed.push({ store, id: event3.id, oldRow, newRow });
25790
- }
25791
25606
  }
25792
25607
  if (storeEvents.length > 0) {
25793
25608
  eventsByStore.set(store, storeEvents);
@@ -25798,7 +25613,6 @@ var init_dist2 = __esm(() => {
25798
25613
  const storeState = this.getStore(store);
25799
25614
  storeState.notifyListenersPublic(events);
25800
25615
  }
25801
- return committed;
25802
25616
  }
25803
25617
  fork() {
25804
25618
  return new ForkedDataStorage2(this);
@@ -34582,8 +34396,8 @@ ${colors3.yellow}Type declaration errors:${colors3.reset}`);
34582
34396
  }
34583
34397
 
34584
34398
  // 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";
34399
+ import { copyFileSync, existsSync as existsSync10, mkdirSync as mkdirSync9, readdirSync as readdirSync6, readFileSync as readFileSync11, writeFileSync as writeFileSync9 } from "fs";
34400
+ import { dirname as dirname8, join as join12 } from "path";
34587
34401
 
34588
34402
  // src/builder/module-builder.ts
34589
34403
  import { execSync } from "child_process";
@@ -35378,7 +35192,8 @@ writeFileSync(out, JSON.stringify(result, null, 2) + "\\n");
35378
35192
  `.trim();
35379
35193
 
35380
35194
  // src/builder/chunk-planner.ts
35381
- import { basename as basename3 } from "path";
35195
+ import { readFileSync as readFileSync9, readdirSync as readdirSync5, statSync as statSync2 } from "fs";
35196
+ import { basename as basename3, join as join10 } from "path";
35382
35197
  var PUBLIC_CHUNK = "public";
35383
35198
  function planChunks(packages, accessMap) {
35384
35199
  const assignments = new Map;
@@ -35410,6 +35225,53 @@ function planChunks(packages, accessMap) {
35410
35225
  });
35411
35226
  return { assignments, groups, chunks };
35412
35227
  }
35228
+ var MODULE_CALL = /\bmodule\(\s*["'`]([a-zA-Z0-9_-]+)["'`]/g;
35229
+ function collectModuleNames(dir, acc) {
35230
+ let entries;
35231
+ try {
35232
+ entries = readdirSync5(dir);
35233
+ } catch {
35234
+ return;
35235
+ }
35236
+ for (const e of entries) {
35237
+ if (e === "node_modules" || e === "dist")
35238
+ continue;
35239
+ const full = join10(dir, e);
35240
+ let isDir = false;
35241
+ try {
35242
+ isDir = statSync2(full).isDirectory();
35243
+ } catch {
35244
+ continue;
35245
+ }
35246
+ if (isDir) {
35247
+ collectModuleNames(full, acc);
35248
+ } else if (e.endsWith(".ts") || e.endsWith(".tsx")) {
35249
+ const src = readFileSync9(full, "utf-8");
35250
+ MODULE_CALL.lastIndex = 0;
35251
+ let m;
35252
+ while (m = MODULE_CALL.exec(src))
35253
+ acc.add(m[1]);
35254
+ }
35255
+ }
35256
+ }
35257
+ function assertOneModulePerPackage(packages) {
35258
+ const offenders = [];
35259
+ for (const pkg of packages) {
35260
+ const names = new Set;
35261
+ collectModuleNames(join10(pkg.path, "src"), names);
35262
+ if (names.size > 1) {
35263
+ offenders.push({ pkg: pkg.name, modules: [...names].sort() });
35264
+ }
35265
+ }
35266
+ if (offenders.length === 0)
35267
+ return;
35268
+ const detail = offenders.map((o) => ` \u2022 ${o.pkg} declares ${o.modules.length}: ${o.modules.join(", ")}`).join(`
35269
+ `);
35270
+ throw new Error(`Arc build: a workspace package must declare at most ONE module().
35271
+ ` + `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.
35272
+ ${detail}
35273
+ ` + `Fix: move each extra module() into its own workspace package ` + `(declare its own protectedBy there).`);
35274
+ }
35413
35275
  function moduleNameOf(pkgName) {
35414
35276
  return pkgName.includes("/") ? pkgName.split("/").pop() : pkgName;
35415
35277
  }
@@ -35417,17 +35279,19 @@ function resolveChunk(moduleName, access) {
35417
35279
  if (!access || access.rules.length === 0)
35418
35280
  return PUBLIC_CHUNK;
35419
35281
  const tokenNames = new Set(access.rules.map((r) => r.token.name));
35420
- if (tokenNames.size === 1) {
35421
- return [...tokenNames][0];
35282
+ if (tokenNames.size > 1) {
35283
+ const list = [...tokenNames].sort().join(", ");
35284
+ 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
35285
  }
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.`);
35286
+ const tokenName = [...tokenNames][0];
35287
+ const hasCheck = access.rules.some((r) => r.hasCheck);
35288
+ return hasCheck ? `${tokenName}__${moduleName}` : tokenName;
35425
35289
  }
35426
35290
 
35427
35291
  // src/builder/dependency-collector.ts
35428
35292
  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";
35293
+ import { existsSync as existsSync9, mkdirSync as mkdirSync8, readFileSync as readFileSync10, writeFileSync as writeFileSync8 } from "fs";
35294
+ import { basename as basename4, dirname as dirname7, join as join11 } from "path";
35431
35295
  import { fileURLToPath as fileURLToPath6 } from "url";
35432
35296
  function collectFrameworkDeps(arcDir, rootDir, packages, sharedDeps = []) {
35433
35297
  mkdirSync8(arcDir, { recursive: true });
@@ -35438,7 +35302,7 @@ function collectFrameworkDeps(arcDir, rootDir, packages, sharedDeps = []) {
35438
35302
  try {
35439
35303
  const cliDir = dirname7(fileURLToPath6(import.meta.url));
35440
35304
  const arcOtelPkgPath = Bun.resolveSync("@arcote.tech/arc-otel/package.json", cliDir);
35441
- const arcOtelPkg = JSON.parse(readFileSync9(arcOtelPkgPath, "utf-8"));
35305
+ const arcOtelPkg = JSON.parse(readFileSync10(arcOtelPkgPath, "utf-8"));
35442
35306
  for (const [name, spec] of Object.entries(arcOtelPkg.dependencies ?? {})) {
35443
35307
  if (name.startsWith("@opentelemetry/")) {
35444
35308
  versions[name] = spec;
@@ -35449,10 +35313,10 @@ function collectFrameworkDeps(arcDir, rootDir, packages, sharedDeps = []) {
35449
35313
  console.warn(`[arc-otel] could not resolve @arcote.tech/arc-otel \u2014 image will run without telemetry deps: ${e.message}`);
35450
35314
  }
35451
35315
  let rootArc;
35452
- const rootPkgPath = join10(rootDir, "package.json");
35316
+ const rootPkgPath = join11(rootDir, "package.json");
35453
35317
  if (existsSync9(rootPkgPath)) {
35454
35318
  try {
35455
- const rootPkg = JSON.parse(readFileSync9(rootPkgPath, "utf-8"));
35319
+ const rootPkg = JSON.parse(readFileSync10(rootPkgPath, "utf-8"));
35456
35320
  if (rootPkg.arc && typeof rootPkg.arc === "object")
35457
35321
  rootArc = rootPkg.arc;
35458
35322
  } catch {}
@@ -35464,11 +35328,11 @@ function collectFrameworkDeps(arcDir, rootDir, packages, sharedDeps = []) {
35464
35328
  dependencies: versions,
35465
35329
  ...rootArc ? { arc: rootArc } : {}
35466
35330
  };
35467
- const manifestPath = join10(arcDir, "package.json");
35331
+ const manifestPath = join11(arcDir, "package.json");
35468
35332
  writeFileSync8(manifestPath, JSON.stringify(manifest, null, 2) + `
35469
35333
  `);
35470
- const hash = sha256Hex2(readFileSync9(manifestPath));
35471
- writeFileSync8(join10(arcDir, ".deps-hash"), hash + `
35334
+ const hash = sha256Hex2(readFileSync10(manifestPath));
35335
+ writeFileSync8(join11(arcDir, ".deps-hash"), hash + `
35472
35336
  `);
35473
35337
  return { hash, manifestPath };
35474
35338
  }
@@ -35476,7 +35340,7 @@ function sha256Hex2(bytes) {
35476
35340
  return createHash("sha256").update(bytes).digest("hex");
35477
35341
  }
35478
35342
  function resolveFrameworkVersions(rootDir, packages) {
35479
- const rootPkg = JSON.parse(readFileSync9(join10(rootDir, "package.json"), "utf-8"));
35343
+ const rootPkg = JSON.parse(readFileSync10(join11(rootDir, "package.json"), "utf-8"));
35480
35344
  const rootDeps = rootPkg.dependencies ?? {};
35481
35345
  const rootDevDeps = rootPkg.devDependencies ?? {};
35482
35346
  const required = new Set(FRAMEWORK_PEERS);
@@ -35535,9 +35399,9 @@ function resolveWorkspace() {
35535
35399
  process.exit(1);
35536
35400
  }
35537
35401
  const rootDir = dirname8(packageJsonPath);
35538
- const rootPkg = JSON.parse(readFileSync10(packageJsonPath, "utf-8"));
35402
+ const rootPkg = JSON.parse(readFileSync11(packageJsonPath, "utf-8"));
35539
35403
  const appName = rootPkg.name ?? "Arc App";
35540
- const arcDir = join11(rootDir, ".arc", "platform");
35404
+ const arcDir = join12(rootDir, ".arc", "platform");
35541
35405
  log2("Scanning workspaces...");
35542
35406
  const packages = discoverPackages(rootDir);
35543
35407
  if (packages.length > 0) {
@@ -35547,10 +35411,10 @@ function resolveWorkspace() {
35547
35411
  }
35548
35412
  let manifest;
35549
35413
  for (const name of ["manifest.json", "manifest.webmanifest"]) {
35550
- const manifestPath = join11(rootDir, name);
35414
+ const manifestPath = join12(rootDir, name);
35551
35415
  if (existsSync10(manifestPath)) {
35552
35416
  try {
35553
- const data = JSON.parse(readFileSync10(manifestPath, "utf-8"));
35417
+ const data = JSON.parse(readFileSync11(manifestPath, "utf-8"));
35554
35418
  const icons = data.icons;
35555
35419
  manifest = {
35556
35420
  path: manifestPath,
@@ -35569,9 +35433,9 @@ function resolveWorkspace() {
35569
35433
  rootPkg,
35570
35434
  appName,
35571
35435
  arcDir,
35572
- browserDir: join11(arcDir, "browser"),
35573
- assetsDir: join11(arcDir, "assets"),
35574
- publicDir: join11(rootDir, "public"),
35436
+ browserDir: join12(arcDir, "browser"),
35437
+ assetsDir: join12(arcDir, "assets"),
35438
+ publicDir: join12(rootDir, "public"),
35575
35439
  packages,
35576
35440
  manifest
35577
35441
  };
@@ -35581,11 +35445,12 @@ async function buildAll(ws, opts = {}) {
35581
35445
  const noCache = opts.noCache ?? false;
35582
35446
  const themePath = ws.rootPkg.arc?.theme;
35583
35447
  log2(`Building (concurrency parallel${noCache ? ", no-cache" : ""})...`);
35448
+ assertOneModulePerPackage(ws.packages);
35584
35449
  await buildContextPackages(ws.rootDir, ws.packages, cache, noCache);
35585
35450
  copyContextServerBundles(ws);
35586
35451
  const accessMap = await extractAccessMap(ws.rootDir, ws.packages);
35587
35452
  mkdirSync9(ws.arcDir, { recursive: true });
35588
- writeFileSync9(join11(ws.arcDir, "access.json"), JSON.stringify(accessMap, null, 2) + `
35453
+ writeFileSync9(join12(ws.arcDir, "access.json"), JSON.stringify(accessMap, null, 2) + `
35589
35454
  `);
35590
35455
  const plan = planChunks(ws.packages, accessMap);
35591
35456
  ok(`Chunks: ${plan.chunks.map((c) => `${c}(${plan.groups.get(c)?.length ?? 0})`).join(", ")}`);
@@ -35601,7 +35466,7 @@ async function buildAll(ws, opts = {}) {
35601
35466
  collectFrameworkDeps(ws.arcDir, ws.rootDir, ws.packages);
35602
35467
  saveBuildCache(ws.arcDir, cache);
35603
35468
  const finalManifest = assembleManifest(ws, browserResult, cache);
35604
- writeFileSync9(join11(ws.arcDir, "manifest.json"), JSON.stringify(finalManifest, null, 2));
35469
+ writeFileSync9(join12(ws.arcDir, "manifest.json"), JSON.stringify(finalManifest, null, 2));
35605
35470
  return finalManifest;
35606
35471
  }
35607
35472
  function assembleManifest(ws, browser, cache) {
@@ -35615,40 +35480,40 @@ function assembleManifest(ws, browser, cache) {
35615
35480
  };
35616
35481
  }
35617
35482
  function copyContextServerBundles(ws) {
35618
- const outDir = join11(ws.arcDir, "server");
35483
+ const outDir = join12(ws.arcDir, "server");
35619
35484
  mkdirSync9(outDir, { recursive: true });
35620
35485
  for (const pkg of ws.packages) {
35621
35486
  if (!isContextPackage(pkg.packageJson))
35622
35487
  continue;
35623
- const src = join11(pkg.path, "dist", "server", "main", "index.js");
35488
+ const src = join12(pkg.path, "dist", "server", "main", "index.js");
35624
35489
  if (!existsSync10(src)) {
35625
35490
  err(`Server bundle missing for ${pkg.name}: ${src}`);
35626
35491
  continue;
35627
35492
  }
35628
35493
  const safeName = pkg.path.split("/").pop();
35629
- const dst = join11(outDir, `${safeName}.js`);
35494
+ const dst = join12(outDir, `${safeName}.js`);
35630
35495
  copyFileSync(src, dst);
35631
35496
  }
35632
35497
  }
35633
35498
  function resolveAssetSource(from, pkgDir, rootDir) {
35634
35499
  if (from.startsWith("./") || from.startsWith("../")) {
35635
- const resolved = join11(pkgDir, from);
35500
+ const resolved = join12(pkgDir, from);
35636
35501
  return existsSync10(resolved) ? resolved : null;
35637
35502
  }
35638
35503
  const candidates = [
35639
- join11(rootDir, "node_modules", from),
35640
- join11(pkgDir, "node_modules", from)
35504
+ join12(rootDir, "node_modules", from),
35505
+ join12(pkgDir, "node_modules", from)
35641
35506
  ];
35642
35507
  for (const c of candidates) {
35643
35508
  if (existsSync10(c))
35644
35509
  return c;
35645
35510
  }
35646
- const bunCacheDir = join11(rootDir, "node_modules", ".bun");
35511
+ const bunCacheDir = join12(rootDir, "node_modules", ".bun");
35647
35512
  if (existsSync10(bunCacheDir)) {
35648
- for (const entry of readdirSync5(bunCacheDir, { withFileTypes: true })) {
35513
+ for (const entry of readdirSync6(bunCacheDir, { withFileTypes: true })) {
35649
35514
  if (!entry.isDirectory())
35650
35515
  continue;
35651
- const candidate = join11(bunCacheDir, entry.name, "node_modules", from);
35516
+ const candidate = join12(bunCacheDir, entry.name, "node_modules", from);
35652
35517
  if (existsSync10(candidate))
35653
35518
  return candidate;
35654
35519
  }
@@ -35656,11 +35521,11 @@ function resolveAssetSource(from, pkgDir, rootDir) {
35656
35521
  return null;
35657
35522
  }
35658
35523
  function readBrowserAssets(pkgDir) {
35659
- const pkgJsonPath = join11(pkgDir, "package.json");
35524
+ const pkgJsonPath = join12(pkgDir, "package.json");
35660
35525
  if (!existsSync10(pkgJsonPath))
35661
35526
  return [];
35662
35527
  try {
35663
- const pkg = JSON.parse(readFileSync10(pkgJsonPath, "utf-8"));
35528
+ const pkg = JSON.parse(readFileSync11(pkgJsonPath, "utf-8"));
35664
35529
  const assets = pkg.arc?.browserAssets;
35665
35530
  if (!Array.isArray(assets))
35666
35531
  return [];
@@ -35670,14 +35535,14 @@ function readBrowserAssets(pkgDir) {
35670
35535
  }
35671
35536
  }
35672
35537
  function discoverBrowserAssets(ws) {
35673
- const arcDir = join11(ws.rootDir, "node_modules", "@arcote.tech");
35538
+ const arcDir = join12(ws.rootDir, "node_modules", "@arcote.tech");
35674
35539
  if (!existsSync10(arcDir))
35675
35540
  return [];
35676
35541
  const out = [];
35677
- for (const entry of readdirSync5(arcDir, { withFileTypes: true })) {
35542
+ for (const entry of readdirSync6(arcDir, { withFileTypes: true })) {
35678
35543
  if (!entry.isDirectory() && !entry.isSymbolicLink())
35679
35544
  continue;
35680
- const pkgDir = join11(arcDir, entry.name);
35545
+ const pkgDir = join12(arcDir, entry.name);
35681
35546
  const assets = readBrowserAssets(pkgDir);
35682
35547
  for (const asset of assets) {
35683
35548
  const src = resolveAssetSource(asset.from, pkgDir, ws.rootDir);
@@ -35702,7 +35567,7 @@ async function copyBrowserAssets(ws, cache, noCache) {
35702
35567
  to: a.to,
35703
35568
  mtime: mtimeOf(a.src)
35704
35569
  })));
35705
- const requiredOutputs = assets.map((a) => join11(ws.assetsDir, a.to));
35570
+ const requiredOutputs = assets.map((a) => join12(ws.assetsDir, a.to));
35706
35571
  if (!noCache && isCacheHit(cache, unitId, inputHash, requiredOutputs)) {
35707
35572
  console.log(` \u2713 cached: browser-assets (${assets.length})`);
35708
35573
  return;
@@ -35710,10 +35575,10 @@ async function copyBrowserAssets(ws, cache, noCache) {
35710
35575
  console.log(` building: browser-assets (${assets.length})`);
35711
35576
  const outputHashes = {};
35712
35577
  for (const asset of assets) {
35713
- const dest = join11(ws.assetsDir, asset.to);
35578
+ const dest = join12(ws.assetsDir, asset.to);
35714
35579
  mkdirSync9(dirname8(dest), { recursive: true });
35715
35580
  copyFileSync(asset.src, dest);
35716
- outputHashes[asset.to] = sha256Hex(readFileSync10(dest));
35581
+ outputHashes[asset.to] = sha256Hex(readFileSync11(dest));
35717
35582
  }
35718
35583
  updateCache(cache, unitId, inputHash, { outputHashes });
35719
35584
  }
@@ -35721,15 +35586,15 @@ async function loadServerContext(ws) {
35721
35586
  globalThis.ONLY_SERVER = true;
35722
35587
  globalThis.ONLY_BROWSER = false;
35723
35588
  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");
35589
+ const platformDir = join12(process.cwd(), "node_modules", "@arcote.tech", "platform");
35590
+ const platformPkg = JSON.parse(readFileSync11(join12(platformDir, "package.json"), "utf-8"));
35591
+ const platformEntry = join12(platformDir, platformPkg.main ?? "src/index.ts");
35727
35592
  await import(platformEntry);
35728
- const serverDir = join11(ws.arcDir, "server");
35729
- const bundles = existsSync10(serverDir) ? readdirSync5(serverDir).filter((f) => f.endsWith(".js")) : [];
35593
+ const serverDir = join12(ws.arcDir, "server");
35594
+ const bundles = existsSync10(serverDir) ? readdirSync6(serverDir).filter((f) => f.endsWith(".js")) : [];
35730
35595
  if (bundles.length > 0) {
35731
35596
  for (const file of bundles) {
35732
- const bundlePath = join11(serverDir, file);
35597
+ const bundlePath = join12(serverDir, file);
35733
35598
  try {
35734
35599
  await import(bundlePath);
35735
35600
  } catch (e) {
@@ -35739,7 +35604,7 @@ async function loadServerContext(ws) {
35739
35604
  } else if (ws.packages.length > 0) {
35740
35605
  const ctxPackages = ws.packages.filter((p) => isContextPackage(p.packageJson));
35741
35606
  for (const ctx of ctxPackages) {
35742
- const serverDist = join11(ctx.path, "dist", "server", "main", "index.js");
35607
+ const serverDist = join12(ctx.path, "dist", "server", "main", "index.js");
35743
35608
  if (!existsSync10(serverDist)) {
35744
35609
  err(`Context server dist not found: ${serverDist}`);
35745
35610
  continue;
@@ -35769,21 +35634,21 @@ async function platformBuild(opts = {}) {
35769
35634
  }
35770
35635
 
35771
35636
  // 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";
35637
+ import { existsSync as existsSync17, readFileSync as readFileSync15 } from "fs";
35638
+ import { dirname as dirname11, join as join20 } from "path";
35774
35639
  import { fileURLToPath as fileURLToPath8 } from "url";
35775
35640
 
35776
35641
  // src/deploy/bootstrap.ts
35777
35642
  var {spawn: spawn4 } = globalThis.Bun;
35778
35643
  import { mkdirSync as mkdirSync12, writeFileSync as writeFileSync13 } from "fs";
35779
35644
  import { tmpdir as tmpdir2 } from "os";
35780
- import { dirname as dirname9, join as join17 } from "path";
35645
+ import { dirname as dirname9, join as join18 } from "path";
35781
35646
 
35782
35647
  // src/deploy/ansible.ts
35783
35648
  import { spawn as nodeSpawn } from "child_process";
35784
35649
  import { existsSync as existsSync11, mkdirSync as mkdirSync10, writeFileSync as writeFileSync10 } from "fs";
35785
35650
  import { homedir, tmpdir } from "os";
35786
- import { join as join12 } from "path";
35651
+ import { join as join13 } from "path";
35787
35652
 
35788
35653
  // src/deploy/assets.ts
35789
35654
  var TERRAFORM_MAIN_TF = `terraform {
@@ -36057,28 +35922,28 @@ var ASSETS = {
36057
35922
  };
36058
35923
  async function materializeAssets(targetDir, files) {
36059
35924
  const { mkdirSync: mkdirSync10, writeFileSync: writeFileSync10 } = await import("fs");
36060
- const { join: join12 } = await import("path");
35925
+ const { join: join13 } = await import("path");
36061
35926
  mkdirSync10(targetDir, { recursive: true });
36062
35927
  for (const [name, content] of Object.entries(files)) {
36063
- writeFileSync10(join12(targetDir, name), content);
35928
+ writeFileSync10(join13(targetDir, name), content);
36064
35929
  }
36065
35930
  }
36066
35931
 
36067
35932
  // src/deploy/ansible.ts
36068
35933
  function pickSshKeyForAnsible(configured) {
36069
35934
  if (configured) {
36070
- const expanded = configured.startsWith("~") ? join12(homedir(), configured.slice(1)) : configured;
35935
+ const expanded = configured.startsWith("~") ? join13(homedir(), configured.slice(1)) : configured;
36071
35936
  return existsSync11(expanded) ? expanded : null;
36072
35937
  }
36073
35938
  for (const name of ["id_ed25519", "id_ecdsa", "id_rsa"]) {
36074
- const path4 = join12(homedir(), ".ssh", name);
35939
+ const path4 = join13(homedir(), ".ssh", name);
36075
35940
  if (existsSync11(path4))
36076
35941
  return path4;
36077
35942
  }
36078
35943
  return null;
36079
35944
  }
36080
35945
  async function runAnsible(inputs) {
36081
- const workDir = join12(tmpdir(), "arc-deploy", `ansible-${Date.now()}`);
35946
+ const workDir = join13(tmpdir(), "arc-deploy", `ansible-${Date.now()}`);
36082
35947
  mkdirSync10(workDir, { recursive: true });
36083
35948
  await materializeAssets(workDir, ASSETS.ansible);
36084
35949
  const user = inputs.asRoot ? "root" : inputs.target.user;
@@ -36095,7 +35960,7 @@ async function runAnsible(inputs) {
36095
35960
  ""
36096
35961
  ].join(`
36097
35962
  `);
36098
- writeFileSync10(join12(workDir, "inventory.ini"), inventory);
35963
+ writeFileSync10(join13(workDir, "inventory.ini"), inventory);
36099
35964
  const extraVarsJson = JSON.stringify({
36100
35965
  username: inputs.target.user,
36101
35966
  ssh_port: port,
@@ -37143,10 +37008,10 @@ import { spawn as nodeSpawn2 } from "child_process";
37143
37008
  import { createHash as createHash2 } from "crypto";
37144
37009
  import { existsSync as existsSync12, mkdirSync as mkdirSync11, writeFileSync as writeFileSync11 } from "fs";
37145
37010
  import { homedir as homedir2 } from "os";
37146
- import { join as join13 } from "path";
37011
+ import { join as join14 } from "path";
37147
37012
  async function runTerraform(inputs) {
37148
37013
  const wsHash = createHash2("sha256").update(inputs.workspaceDir).digest("hex").slice(0, 16);
37149
- const workDir = join13(homedir2(), ".arc-deploy", wsHash, "tf");
37014
+ const workDir = join14(homedir2(), ".arc-deploy", wsHash, "tf");
37150
37015
  mkdirSync11(workDir, { recursive: true });
37151
37016
  await materializeAssets(workDir, ASSETS.terraform);
37152
37017
  const sshPubKey = inputs.tf.sshPublicKey ?? expandHome("~/.ssh/id_ed25519.pub");
@@ -37163,7 +37028,7 @@ async function runTerraform(inputs) {
37163
37028
  ].join(`
37164
37029
  `) + `
37165
37030
  `;
37166
- writeFileSync11(join13(workDir, "terraform.tfvars"), tfvars);
37031
+ writeFileSync11(join14(workDir, "terraform.tfvars"), tfvars);
37167
37032
  await runTf(workDir, ["init", "-input=false", "-no-color"]);
37168
37033
  await runTf(workDir, [
37169
37034
  "apply",
@@ -37221,20 +37086,20 @@ function expandHome(p) {
37221
37086
  }
37222
37087
 
37223
37088
  // src/deploy/config.ts
37224
- import { existsSync as existsSync14, readFileSync as readFileSync12, writeFileSync as writeFileSync12 } from "fs";
37225
- import { join as join15 } from "path";
37089
+ import { existsSync as existsSync14, readFileSync as readFileSync13, writeFileSync as writeFileSync12 } from "fs";
37090
+ import { join as join16 } from "path";
37226
37091
 
37227
37092
  // src/deploy/env-file.ts
37228
- import { appendFileSync, existsSync as existsSync13, readFileSync as readFileSync11 } from "fs";
37229
- import { join as join14 } from "path";
37093
+ import { appendFileSync, existsSync as existsSync13, readFileSync as readFileSync12 } from "fs";
37094
+ import { join as join15 } from "path";
37230
37095
  function loadDeployEnvFiles(rootDir, envNames) {
37231
- const globalsPath = join14(rootDir, "deploy.arc.env");
37232
- const globals = existsSync13(globalsPath) ? parseEnvFile(readFileSync11(globalsPath, "utf-8"), globalsPath) : {};
37096
+ const globalsPath = join15(rootDir, "deploy.arc.env");
37097
+ const globals = existsSync13(globalsPath) ? parseEnvFile(readFileSync12(globalsPath, "utf-8"), globalsPath) : {};
37233
37098
  const perEnv = {};
37234
37099
  for (const name of envNames) {
37235
- const envPath = join14(rootDir, `deploy.arc.${name}.env`);
37100
+ const envPath = join15(rootDir, `deploy.arc.${name}.env`);
37236
37101
  if (existsSync13(envPath)) {
37237
- perEnv[name] = parseEnvFile(readFileSync11(envPath, "utf-8"), envPath);
37102
+ perEnv[name] = parseEnvFile(readFileSync12(envPath, "utf-8"), envPath);
37238
37103
  }
37239
37104
  }
37240
37105
  return { globals, perEnv };
@@ -37250,16 +37115,16 @@ function ensurePersistedSecret(rootDir, scope, key, generate) {
37250
37115
  if (process.env[key])
37251
37116
  return process.env[key];
37252
37117
  const fileName = scope === "globals" ? "deploy.arc.env" : `deploy.arc.${scope}.env`;
37253
- const path4 = join14(rootDir, fileName);
37118
+ const path4 = join15(rootDir, fileName);
37254
37119
  if (existsSync13(path4)) {
37255
- const existing = parseEnvFile(readFileSync11(path4, "utf-8"), path4);
37120
+ const existing = parseEnvFile(readFileSync12(path4, "utf-8"), path4);
37256
37121
  if (existing[key]) {
37257
37122
  process.env[key] = existing[key];
37258
37123
  return existing[key];
37259
37124
  }
37260
37125
  }
37261
37126
  const value = generate();
37262
- const prefix = existsSync13(path4) && !readFileSync11(path4, "utf-8").endsWith(`
37127
+ const prefix = existsSync13(path4) && !readFileSync12(path4, "utf-8").endsWith(`
37263
37128
  `) ? `
37264
37129
  ` : "";
37265
37130
  appendFileSync(path4, `${prefix}${key}=${value}
@@ -37295,7 +37160,7 @@ function parseEnvFile(content, pathForErrors) {
37295
37160
  // src/deploy/config.ts
37296
37161
  var DEPLOY_CONFIG_FILE = "deploy.arc.json";
37297
37162
  function deployConfigPath(rootDir) {
37298
- return join15(rootDir, DEPLOY_CONFIG_FILE);
37163
+ return join16(rootDir, DEPLOY_CONFIG_FILE);
37299
37164
  }
37300
37165
  function deployConfigExists(rootDir) {
37301
37166
  return existsSync14(deployConfigPath(rootDir));
@@ -37305,7 +37170,7 @@ function loadDeployConfig(rootDir) {
37305
37170
  if (!existsSync14(path4)) {
37306
37171
  throw new Error(`Missing ${DEPLOY_CONFIG_FILE} at ${path4}`);
37307
37172
  }
37308
- const raw = readFileSync12(path4, "utf-8");
37173
+ const raw = readFileSync13(path4, "utf-8");
37309
37174
  let parsed;
37310
37175
  try {
37311
37176
  parsed = JSON.parse(raw);
@@ -37328,7 +37193,7 @@ function loadDeployConfig(rootDir) {
37328
37193
  }
37329
37194
  function saveDeployConfig(rootDir, cfg) {
37330
37195
  const path4 = deployConfigPath(rootDir);
37331
- const raw = existsSync14(path4) ? JSON.parse(readFileSync12(path4, "utf-8")) : {};
37196
+ const raw = existsSync14(path4) ? JSON.parse(readFileSync13(path4, "utf-8")) : {};
37332
37197
  raw.target = { ...raw.target, ...cfg.target };
37333
37198
  writeFileSync12(path4, JSON.stringify(raw, null, 2) + `
37334
37199
  `);
@@ -37541,14 +37406,14 @@ function cfgErr(path4, expected) {
37541
37406
  var {spawn: spawn3 } = globalThis.Bun;
37542
37407
  import { existsSync as existsSync15 } from "fs";
37543
37408
  import { homedir as homedir3 } from "os";
37544
- import { join as join16 } from "path";
37409
+ import { join as join17 } from "path";
37545
37410
  function pickSshKey(target) {
37546
37411
  if (target.sshKey) {
37547
- const expanded = target.sshKey.startsWith("~") ? join16(homedir3(), target.sshKey.slice(1)) : target.sshKey;
37412
+ const expanded = target.sshKey.startsWith("~") ? join17(homedir3(), target.sshKey.slice(1)) : target.sshKey;
37548
37413
  return existsSync15(expanded) ? expanded : null;
37549
37414
  }
37550
37415
  for (const name of ["id_ed25519", "id_ecdsa", "id_rsa"]) {
37551
- const path4 = join16(homedir3(), ".ssh", name);
37416
+ const path4 = join17(homedir3(), ".ssh", name);
37552
37417
  if (existsSync15(path4))
37553
37418
  return path4;
37554
37419
  }
@@ -37784,7 +37649,7 @@ async function isRegistryRunning(cfg) {
37784
37649
  }
37785
37650
  async function upStack(inputs) {
37786
37651
  const { cfg } = inputs;
37787
- const workDir = join17(tmpdir2(), "arc-deploy", `stack-${Date.now()}`);
37652
+ const workDir = join18(tmpdir2(), "arc-deploy", `stack-${Date.now()}`);
37788
37653
  mkdirSync12(workDir, { recursive: true });
37789
37654
  await assertRegistryDnsResolves(cfg);
37790
37655
  const password = process.env[cfg.registry.passwordEnv];
@@ -37792,9 +37657,9 @@ async function upStack(inputs) {
37792
37657
  throw new Error(`Registry password env var ${cfg.registry.passwordEnv} is not set. ` + `Set it (e.g. \`export ${cfg.registry.passwordEnv}=...\`) before bootstrap.`);
37793
37658
  }
37794
37659
  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 }));
37660
+ writeFileSync13(join18(workDir, "htpasswd"), htpasswdLine);
37661
+ writeFileSync13(join18(workDir, "Caddyfile"), generateCaddyfile(cfg));
37662
+ writeFileSync13(join18(workDir, "docker-compose.yml"), generateCompose({ cfg }));
37798
37663
  let observabilityFiles = null;
37799
37664
  let observabilityHtpasswd = null;
37800
37665
  if (cfg.observability?.enabled) {
@@ -37806,30 +37671,30 @@ async function upStack(inputs) {
37806
37671
  }
37807
37672
  observabilityHtpasswd = await generateCaddyBasicAuthLine("admin", adminPassword);
37808
37673
  for (const [relPath, contents] of Object.entries(observabilityFiles)) {
37809
- const fullPath = join17(workDir, relPath);
37674
+ const fullPath = join18(workDir, relPath);
37810
37675
  mkdirSync12(dirname9(fullPath), { recursive: true });
37811
37676
  writeFileSync13(fullPath, contents);
37812
37677
  }
37813
- writeFileSync13(join17(workDir, "observability-htpasswd"), observabilityHtpasswd);
37678
+ writeFileSync13(join18(workDir, "observability-htpasswd"), observabilityHtpasswd);
37814
37679
  }
37815
37680
  await assertExec(cfg.target, `sudo mkdir -p ${cfg.target.remoteDir} && sudo chown ${cfg.target.user}:${cfg.target.user} ${cfg.target.remoteDir}`);
37816
37681
  for (const name of Object.keys(cfg.envs)) {
37817
37682
  await assertExec(cfg.target, `mkdir -p ${cfg.target.remoteDir}/${name}`);
37818
37683
  }
37819
37684
  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`);
37685
+ await scpUpload(cfg.target, join18(workDir, "Caddyfile"), `${cfg.target.remoteDir}/Caddyfile`);
37686
+ await scpUpload(cfg.target, join18(workDir, "docker-compose.yml"), `${cfg.target.remoteDir}/docker-compose.yml`);
37687
+ await scpUpload(cfg.target, join18(workDir, "htpasswd"), `${cfg.target.remoteDir}/registry-auth/htpasswd`);
37823
37688
  if (observabilityFiles && observabilityHtpasswd) {
37824
37689
  await assertExec(cfg.target, `mkdir -p ${cfg.target.remoteDir}/observability/grafana-dashboards`);
37825
37690
  for (const relPath of Object.keys(observabilityFiles)) {
37826
- const localDir = dirname9(join17(workDir, relPath));
37691
+ const localDir = dirname9(join18(workDir, relPath));
37827
37692
  mkdirSync12(localDir, { recursive: true });
37828
37693
  }
37829
37694
  for (const relPath of Object.keys(observabilityFiles)) {
37830
- await scpUpload(cfg.target, join17(workDir, relPath), `${cfg.target.remoteDir}/${relPath}`);
37695
+ await scpUpload(cfg.target, join18(workDir, relPath), `${cfg.target.remoteDir}/${relPath}`);
37831
37696
  }
37832
- await scpUpload(cfg.target, join17(workDir, "observability-htpasswd"), `${cfg.target.remoteDir}/observability-htpasswd`);
37697
+ await scpUpload(cfg.target, join18(workDir, "observability-htpasswd"), `${cfg.target.remoteDir}/observability-htpasswd`);
37833
37698
  }
37834
37699
  await assertExec(cfg.target, `touch ${cfg.target.remoteDir}/.env`);
37835
37700
  if (cfg.observability?.enabled) {
@@ -37997,12 +37862,12 @@ import {
37997
37862
  copyFileSync as copyFileSync2,
37998
37863
  existsSync as existsSync16,
37999
37864
  mkdirSync as mkdirSync13,
38000
- readFileSync as readFileSync13,
37865
+ readFileSync as readFileSync14,
38001
37866
  realpathSync as realpathSync2,
38002
37867
  writeFileSync as writeFileSync14
38003
37868
  } from "fs";
38004
37869
  import { tmpdir as tmpdir3 } from "os";
38005
- import { dirname as dirname10, join as join18 } from "path";
37870
+ import { dirname as dirname10, join as join19 } from "path";
38006
37871
  import { fileURLToPath as fileURLToPath7 } from "url";
38007
37872
 
38008
37873
  // src/deploy/image-template.ts
@@ -38052,7 +37917,7 @@ function generateDockerfile(inputs) {
38052
37917
  // src/deploy/image.ts
38053
37918
  async function buildImage(ws, opts) {
38054
37919
  await ensureDocker();
38055
- const manifestPath = join18(ws.arcDir, "manifest.json");
37920
+ const manifestPath = join19(ws.arcDir, "manifest.json");
38056
37921
  if (!existsSync16(manifestPath)) {
38057
37922
  throw new Error(`No build manifest at ${manifestPath}. Run \`arc platform build\` first or omit --skip-build.`);
38058
37923
  }
@@ -38063,9 +37928,9 @@ async function buildImage(ws, opts) {
38063
37928
  const dockerfileInputs = collectDockerfileInputs(ws);
38064
37929
  const dockerfile = generateDockerfile(dockerfileInputs);
38065
37930
  const buildContextDir = ws.rootDir;
38066
- const dockerfileDir = join18(tmpdir3(), `arc-image-${Date.now()}`);
37931
+ const dockerfileDir = join19(tmpdir3(), `arc-image-${Date.now()}`);
38067
37932
  mkdirSync13(dockerfileDir, { recursive: true });
38068
- const dockerfilePath = join18(dockerfileDir, "Dockerfile");
37933
+ const dockerfilePath = join19(dockerfileDir, "Dockerfile");
38069
37934
  writeFileSync14(dockerfilePath, dockerfile);
38070
37935
  const buildArgs = [
38071
37936
  "build",
@@ -38091,19 +37956,19 @@ async function buildImage(ws, opts) {
38091
37956
  }
38092
37957
  function embedCliBundle(ws) {
38093
37958
  const source = locateCliBundle();
38094
- const target = join18(ws.arcDir, "host.js");
37959
+ const target = join19(ws.arcDir, "host.js");
38095
37960
  copyFileSync2(source, target);
38096
37961
  }
38097
37962
  function locateCliBundle() {
38098
37963
  const here = fileURLToPath7(import.meta.url);
38099
37964
  let cur = dirname10(here);
38100
37965
  while (cur !== "/" && cur !== "") {
38101
- const candidate = join18(cur, "package.json");
37966
+ const candidate = join19(cur, "package.json");
38102
37967
  if (existsSync16(candidate)) {
38103
37968
  try {
38104
- const pkg = JSON.parse(readFileSync13(candidate, "utf-8"));
37969
+ const pkg = JSON.parse(readFileSync14(candidate, "utf-8"));
38105
37970
  if (pkg.name === "@arcote.tech/arc-cli") {
38106
- const distIndex = join18(realpathSync2(cur), "dist", "index.js");
37971
+ const distIndex = join19(realpathSync2(cur), "dist", "index.js");
38107
37972
  if (!existsSync16(distIndex)) {
38108
37973
  throw new Error(`arc-cli bundle missing at ${distIndex}. Run \`bun run build\` in packages/cli/.`);
38109
37974
  }
@@ -38136,17 +38001,17 @@ async function ensureDocker() {
38136
38001
  }
38137
38002
  }
38138
38003
  function computeContentHash(manifestPath) {
38139
- const raw = JSON.parse(readFileSync13(manifestPath, "utf-8"));
38004
+ const raw = JSON.parse(readFileSync14(manifestPath, "utf-8"));
38140
38005
  delete raw.buildTime;
38141
38006
  const canonical = JSON.stringify(raw);
38142
38007
  return createHash3("sha256").update(canonical).digest("hex").slice(0, 12);
38143
38008
  }
38144
38009
  function collectDockerfileInputs(ws) {
38145
- const hasPublicDir = existsSync16(join18(ws.rootDir, "public"));
38146
- const hasLocales = existsSync16(join18(ws.rootDir, "locales"));
38010
+ const hasPublicDir = existsSync16(join19(ws.rootDir, "public"));
38011
+ const hasLocales = existsSync16(join19(ws.rootDir, "locales"));
38147
38012
  let manifestPath;
38148
38013
  for (const name of ["manifest.webmanifest", "manifest.json"]) {
38149
- if (existsSync16(join18(ws.rootDir, name))) {
38014
+ if (existsSync16(join19(ws.rootDir, name))) {
38150
38015
  manifestPath = name;
38151
38016
  break;
38152
38017
  }
@@ -38975,7 +38840,7 @@ async function platformDeploy(envArg, options = {}) {
38975
38840
  err(`Unknown env "${envArg}". Known: ${Object.keys(cfg.envs).join(", ")}`);
38976
38841
  process.exit(1);
38977
38842
  })() : Object.keys(cfg.envs);
38978
- const manifestPath = join19(ws.arcDir, "manifest.json");
38843
+ const manifestPath = join20(ws.arcDir, "manifest.json");
38979
38844
  if (!options.imageTag) {
38980
38845
  const needBuild = options.rebuild || !existsSync17(manifestPath);
38981
38846
  if (needBuild && !options.skipBuild) {
@@ -39048,9 +38913,9 @@ function readCliVersion2() {
39048
38913
  let cur = dirname11(fileURLToPath8(import.meta.url));
39049
38914
  const root = dirname11(cur).startsWith("/") ? "/" : ".";
39050
38915
  while (cur !== root && cur !== "") {
39051
- const candidate = join19(cur, "package.json");
38916
+ const candidate = join20(cur, "package.json");
39052
38917
  if (existsSync17(candidate)) {
39053
- const pkg = JSON.parse(readFileSync14(candidate, "utf-8"));
38918
+ const pkg = JSON.parse(readFileSync15(candidate, "utf-8"));
39054
38919
  if (pkg.name === "@arcote.tech/arc-cli") {
39055
38920
  return pkg.version ?? "unknown";
39056
38921
  }
@@ -39066,8 +38931,8 @@ function readCliVersion2() {
39066
38931
  }
39067
38932
  }
39068
38933
  async function hashDeployConfig(rootDir) {
39069
- const p2 = join19(rootDir, "deploy.arc.json");
39070
- const content = readFileSync14(p2);
38934
+ const p2 = join20(rootDir, "deploy.arc.json");
38935
+ const content = readFileSync15(p2);
39071
38936
  const hasher = new Bun.CryptoHasher("sha256");
39072
38937
  hasher.update(content);
39073
38938
  hasher.update(readCliVersion2());
@@ -39075,8 +38940,8 @@ async function hashDeployConfig(rootDir) {
39075
38940
  }
39076
38941
 
39077
38942
  // src/platform/startup.ts
39078
- import { existsSync as existsSync19, readFileSync as readFileSync15, watch } from "fs";
39079
- import { join as join21 } from "path";
38943
+ import { existsSync as existsSync19, readFileSync as readFileSync16, watch } from "fs";
38944
+ import { join as join22 } from "path";
39080
38945
 
39081
38946
  // ../host/src/create-server.ts
39082
38947
  var import_jsonwebtoken = __toESM(require_jsonwebtoken(), 1);
@@ -40496,7 +40361,7 @@ async function createArcServer(config) {
40496
40361
  // src/platform/server.ts
40497
40362
  init_i18n();
40498
40363
  import { existsSync as existsSync18, mkdirSync as mkdirSync14 } from "fs";
40499
- import { join as join20 } from "path";
40364
+ import { join as join21 } from "path";
40500
40365
  async function resolveDbAdapterFactory(dbPath) {
40501
40366
  const databaseUrl = process.env.DATABASE_URL;
40502
40367
  if (databaseUrl && databaseUrl.length > 0) {
@@ -40628,15 +40493,13 @@ function parseArcTokensHeader(header) {
40628
40493
  return payloads;
40629
40494
  }
40630
40495
  async function filterManifestForTokens(manifest, moduleAccessMap, tokenPayloads) {
40631
- const allowedGroups = new Set;
40496
+ const tokensByName = new Map;
40632
40497
  for (const t of tokenPayloads) {
40633
40498
  if (t?.tokenType)
40634
- allowedGroups.add(t.tokenType);
40499
+ tokensByName.set(t.tokenType, t);
40635
40500
  }
40636
40501
  const filteredGroups = {};
40637
40502
  for (const [name, group] of Object.entries(manifest.groups)) {
40638
- if (!allowedGroups.has(name))
40639
- continue;
40640
40503
  let allGranted = true;
40641
40504
  for (const moduleName of group.modules) {
40642
40505
  const access = moduleAccessMap.get(moduleName);
@@ -40644,9 +40507,7 @@ async function filterManifestForTokens(manifest, moduleAccessMap, tokenPayloads)
40644
40507
  continue;
40645
40508
  let granted = false;
40646
40509
  for (const rule of access.rules) {
40647
- if (rule.token.name !== name)
40648
- continue;
40649
- const matching = tokenPayloads.find((t) => t.tokenType === name);
40510
+ const matching = tokensByName.get(rule.token.name);
40650
40511
  if (!matching)
40651
40512
  continue;
40652
40513
  granted = rule.check ? await rule.check(matching) : true;
@@ -40687,28 +40548,28 @@ function staticFilesHandler(ws, devMode, getManifest) {
40687
40548
  return new Response("Forbidden", { status: 403, headers: ctx.corsHeaders });
40688
40549
  }
40689
40550
  }
40690
- return serveFile(join20(ws.browserDir, file), {
40551
+ return serveFile(join21(ws.browserDir, file), {
40691
40552
  ...ctx.corsHeaders,
40692
40553
  "Cache-Control": devMode ? "no-cache" : "max-age=31536000,immutable"
40693
40554
  });
40694
40555
  }
40695
40556
  if (path4.startsWith("/locales/"))
40696
- return serveFile(join20(ws.arcDir, path4.slice(1)), {
40557
+ return serveFile(join21(ws.arcDir, path4.slice(1)), {
40697
40558
  ...ctx.corsHeaders,
40698
40559
  "Cache-Control": devMode ? "no-cache" : "max-age=300,stale-while-revalidate=3600"
40699
40560
  });
40700
40561
  if (path4.startsWith("/assets/"))
40701
- return serveFile(join20(ws.assetsDir, path4.slice(8)), {
40562
+ return serveFile(join21(ws.assetsDir, path4.slice(8)), {
40702
40563
  ...ctx.corsHeaders,
40703
40564
  "Cache-Control": devMode ? "no-cache" : "max-age=31536000,immutable"
40704
40565
  });
40705
40566
  if (path4 === "/styles.css")
40706
- return serveFile(join20(ws.arcDir, "styles.css"), {
40567
+ return serveFile(join21(ws.arcDir, "styles.css"), {
40707
40568
  ...ctx.corsHeaders,
40708
40569
  "Cache-Control": devMode ? "no-cache" : "max-age=31536000,immutable"
40709
40570
  });
40710
40571
  if (path4 === "/theme.css")
40711
- return serveFile(join20(ws.arcDir, "theme.css"), {
40572
+ return serveFile(join21(ws.arcDir, "theme.css"), {
40712
40573
  ...ctx.corsHeaders,
40713
40574
  "Cache-Control": devMode ? "no-cache" : "max-age=31536000,immutable"
40714
40575
  });
@@ -40716,7 +40577,7 @@ function staticFilesHandler(ws, devMode, getManifest) {
40716
40577
  return serveFile(ws.manifest.path, ctx.corsHeaders);
40717
40578
  }
40718
40579
  if (path4.lastIndexOf(".") > path4.lastIndexOf("/")) {
40719
- const publicFile = join20(ws.publicDir, path4.slice(1));
40580
+ const publicFile = join21(ws.publicDir, path4.slice(1));
40720
40581
  if (existsSync18(publicFile))
40721
40582
  return serveFile(publicFile, ctx.corsHeaders);
40722
40583
  }
@@ -40871,7 +40732,7 @@ async function startPlatformServer(opts) {
40871
40732
  stop: () => server.stop()
40872
40733
  };
40873
40734
  }
40874
- const dbPath = opts.dbPath || join20(ws.arcDir, "data", "arc.db");
40735
+ const dbPath = opts.dbPath || join21(ws.arcDir, "data", "arc.db");
40875
40736
  const baseDbFactory = await resolveDbAdapterFactory(dbPath);
40876
40737
  let dbAdapterFactory = baseDbFactory;
40877
40738
  if (telemetry) {
@@ -40912,17 +40773,17 @@ async function startPlatformServer(opts) {
40912
40773
  async function startPlatform(opts) {
40913
40774
  const { ws, devMode } = opts;
40914
40775
  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");
40776
+ const dbPath = opts.dbPath ?? join22(ws.rootDir, ".arc", "data", devMode ? "dev.db" : "prod.db");
40916
40777
  let manifest;
40917
40778
  if (devMode) {
40918
40779
  manifest = await buildAll(ws);
40919
40780
  } else {
40920
- const manifestPath = join21(ws.arcDir, "manifest.json");
40781
+ const manifestPath = join22(ws.arcDir, "manifest.json");
40921
40782
  if (!existsSync19(manifestPath)) {
40922
40783
  err("No build found. Run `arc platform build` first.");
40923
40784
  process.exit(1);
40924
40785
  }
40925
- manifest = JSON.parse(readFileSync15(manifestPath, "utf-8"));
40786
+ manifest = JSON.parse(readFileSync16(manifestPath, "utf-8"));
40926
40787
  }
40927
40788
  log2("Loading server context...");
40928
40789
  const { context: context2, moduleAccess } = await loadServerContext(ws);
@@ -40998,7 +40859,7 @@ function attachDevWatcher(ws, platform3) {
40998
40859
  }, 300);
40999
40860
  };
41000
40861
  for (const pkg of ws.packages) {
41001
- const srcDir = join21(pkg.path, "src");
40862
+ const srcDir = join22(pkg.path, "src");
41002
40863
  if (!existsSync19(srcDir))
41003
40864
  continue;
41004
40865
  watch(srcDir, { recursive: true }, (_event, filename) => {
@@ -41009,7 +40870,7 @@ function attachDevWatcher(ws, platform3) {
41009
40870
  triggerRebuild();
41010
40871
  });
41011
40872
  }
41012
- const localesDir = join21(ws.rootDir, "locales");
40873
+ const localesDir = join22(ws.rootDir, "locales");
41013
40874
  if (existsSync19(localesDir)) {
41014
40875
  watch(localesDir, { recursive: false }, (_event, filename) => {
41015
40876
  if (!filename?.endsWith(".po"))