@arcote.tech/arc-cli 0.7.15 → 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,12 +14017,13 @@ class EventWire {
14017
14017
  onSyncedCallback;
14018
14018
  reconnectTimeout;
14019
14019
  syncRequested = false;
14020
- viewSubscriptions = new Map;
14021
- viewSubCounter = 0;
14022
- pendingViewSubs = [];
14023
- constructor(baseUrl) {
14020
+ querySubscriptions = new Map;
14021
+ querySubCounter = 0;
14022
+ enableEventSync;
14023
+ constructor(baseUrl, options) {
14024
14024
  this.baseUrl = baseUrl;
14025
14025
  this.instanceId = ++eventWireInstanceCounter;
14026
+ this.enableEventSync = options?.enableEventSync ?? true;
14026
14027
  }
14027
14028
  setScopeToken(scope, token) {
14028
14029
  if (token === null) {
@@ -14068,9 +14069,11 @@ class EventWire {
14068
14069
  if (this.ws && this.ws.readyState === WebSocket.OPEN) {
14069
14070
  this.state = "connected";
14070
14071
  this.sendAllScopeTokens();
14071
- this.requestSync();
14072
+ if (this.enableEventSync) {
14073
+ this.requestSync();
14074
+ }
14072
14075
  this.flushPendingEvents();
14073
- this.flushPendingViewSubs();
14076
+ this.sendAllQuerySubscriptions();
14074
14077
  } else {
14075
14078
  console.log(`[EventWire] onopen called but ws is not OPEN, readyState:`, this.ws?.readyState);
14076
14079
  }
@@ -14146,9 +14149,13 @@ class EventWire {
14146
14149
  onSynced(callback) {
14147
14150
  this.onSyncedCallback = callback;
14148
14151
  }
14149
- subscribeQuery(descriptor, callback, scope) {
14150
- const subscriptionId = `qs_${++this.viewSubCounter}_${Date.now()}`;
14151
- this.viewSubscriptions.set(subscriptionId, callback);
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
+ });
14152
14159
  if (this.state === "connected" && this.ws) {
14153
14160
  this.ws.send(JSON.stringify({
14154
14161
  type: "subscribe-query",
@@ -14156,20 +14163,17 @@ class EventWire {
14156
14163
  descriptor,
14157
14164
  scope
14158
14165
  }));
14159
- } else {
14160
- this.pendingViewSubs.push({ subscriptionId, descriptor, scope });
14161
14166
  }
14162
14167
  return subscriptionId;
14163
14168
  }
14164
14169
  unsubscribeQuery(subscriptionId) {
14165
- this.viewSubscriptions.delete(subscriptionId);
14170
+ this.querySubscriptions.delete(subscriptionId);
14166
14171
  if (this.state === "connected" && this.ws) {
14167
14172
  this.ws.send(JSON.stringify({
14168
14173
  type: "unsubscribe-query",
14169
14174
  subscriptionId
14170
14175
  }));
14171
14176
  }
14172
- this.pendingViewSubs = this.pendingViewSubs.filter((s) => s.subscriptionId !== subscriptionId);
14173
14177
  }
14174
14178
  getState() {
14175
14179
  return this.state;
@@ -14202,10 +14206,17 @@ class EventWire {
14202
14206
  this.lastHostEventId = message.lastHostEventId;
14203
14207
  }
14204
14208
  break;
14205
- case "query-data": {
14206
- const cb = this.viewSubscriptions.get(message.subscriptionId);
14207
- if (cb) {
14208
- cb(message.data);
14209
+ case "query-snapshot": {
14210
+ const sub = this.querySubscriptions.get(message.subscriptionId);
14211
+ if (sub) {
14212
+ sub.callbacks.onSnapshot(message.result ?? null);
14213
+ }
14214
+ break;
14215
+ }
14216
+ case "query-changes": {
14217
+ const sub = this.querySubscriptions.get(message.subscriptionId);
14218
+ if (sub && Array.isArray(message.changes)) {
14219
+ sub.callbacks.onChanges(message.changes);
14209
14220
  }
14210
14221
  break;
14211
14222
  }
@@ -14233,18 +14244,17 @@ class EventWire {
14233
14244
  this.pendingEvents = [];
14234
14245
  }
14235
14246
  }
14236
- flushPendingViewSubs() {
14247
+ sendAllQuerySubscriptions() {
14237
14248
  if (!this.ws || this.state !== "connected")
14238
14249
  return;
14239
- for (const sub of this.pendingViewSubs) {
14250
+ for (const [subscriptionId, sub] of this.querySubscriptions) {
14240
14251
  this.ws.send(JSON.stringify({
14241
14252
  type: "subscribe-query",
14242
- subscriptionId: sub.subscriptionId,
14253
+ subscriptionId,
14243
14254
  descriptor: sub.descriptor,
14244
14255
  scope: sub.scope
14245
14256
  }));
14246
14257
  }
14247
- this.pendingViewSubs = [];
14248
14258
  }
14249
14259
  scheduleReconnect() {
14250
14260
  if (this.reconnectTimeout)
@@ -14720,7 +14730,7 @@ class DataStorage {
14720
14730
  }
14721
14731
  }
14722
14732
 
14723
- class ScopedStore2 {
14733
+ class ScopedStore {
14724
14734
  #inner;
14725
14735
  #restrictions;
14726
14736
  #canWrite;
@@ -15073,6 +15083,47 @@ function deepMerge(target, source) {
15073
15083
  function isPlainObject(item) {
15074
15084
  return item && typeof item === "object" && !Array.isArray(item) && !(item instanceof Date) && Object.prototype.toString.call(item) === "[object Object]";
15075
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
+ }
15076
15127
  function resolveQueryChange(currentResult, event3, options) {
15077
15128
  const index = currentResult.findIndex((e2) => e2._id === event3.id);
15078
15129
  const isInCurrentResult = index !== -1;
@@ -15082,7 +15133,7 @@ function resolveQueryChange(currentResult, event3, options) {
15082
15133
  }
15083
15134
  return false;
15084
15135
  }
15085
- const shouldBeInResult = checkItemMatchesWhere2(event3.item, options.where);
15136
+ const shouldBeInResult = checkItemMatchesWhere(event3.item, options.where);
15086
15137
  if (isInCurrentResult && shouldBeInResult) {
15087
15138
  const newResult = currentResult.toSpliced(index, 1, event3.item);
15088
15139
  return applyOrderByAndLimit(newResult, options);
@@ -15094,7 +15145,7 @@ function resolveQueryChange(currentResult, event3, options) {
15094
15145
  }
15095
15146
  return false;
15096
15147
  }
15097
- function checkItemMatchesWhere2(item, where) {
15148
+ function checkItemMatchesWhere(item, where) {
15098
15149
  if (!where) {
15099
15150
  return true;
15100
15151
  }
@@ -15188,7 +15239,8 @@ class ObservableDataStorage {
15188
15239
  }
15189
15240
  handleStoreChange(storeName, events) {
15190
15241
  let hasChanges = false;
15191
- for (const query of this.trackedQueries.values()) {
15242
+ const staleKeys = [];
15243
+ for (const [key, query] of this.trackedQueries) {
15192
15244
  if (query.storeName !== storeName)
15193
15245
  continue;
15194
15246
  let currentResult = query.result;
@@ -15200,10 +15252,20 @@ class ObservableDataStorage {
15200
15252
  queryChanged = true;
15201
15253
  }
15202
15254
  }
15203
- if (queryChanged) {
15204
- 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);
15205
15259
  hasChanges = true;
15260
+ continue;
15206
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);
15207
15269
  }
15208
15270
  if (hasChanges) {
15209
15271
  this.onChange();
@@ -15346,7 +15408,7 @@ function executeDescriptor(descriptor, context2, adapters, contextMethod, option
15346
15408
  return method(...descriptor.args);
15347
15409
  }
15348
15410
 
15349
- class ScopedModel4 {
15411
+ class ScopedModel3 {
15350
15412
  context;
15351
15413
  scopeName;
15352
15414
  parent;
@@ -15418,13 +15480,6 @@ class ScopedModel4 {
15418
15480
  }
15419
15481
  return wire.query(viewName, options, this.getAuth());
15420
15482
  }
15421
- subscribeQuery(descriptor, callback) {
15422
- const wire = this.parent.getAdapters().eventWire;
15423
- if (!wire) {
15424
- throw new Error(`Cannot subscribe to query: no eventWire available.`);
15425
- }
15426
- return wire.subscribeQuery(descriptor, callback, this.scopeName);
15427
- }
15428
15483
  get query() {
15429
15484
  return buildContextAccessor(this.context, this.scopedAdapters, "queryContext", (descriptor) => descriptor);
15430
15485
  }
@@ -15474,251 +15529,142 @@ class Model2 {
15474
15529
  scope(name) {
15475
15530
  let s = this.scopes.get(name);
15476
15531
  if (!s) {
15477
- s = new ScopedModel4(this, name);
15532
+ s = new ScopedModel3(this, name);
15478
15533
  this.scopes.set(name, s);
15479
15534
  }
15480
15535
  return s;
15481
15536
  }
15482
15537
  }
15483
-
15484
- class StreamingQueryCache {
15485
- stores = new Map;
15486
- views = [];
15487
- activeStreams = new Map;
15488
- pendingUnsubscribes = new Map;
15489
- streamScopes = new Map;
15490
- static UNSUBSCRIBE_DELAY_MS = 5000;
15491
- registerViews(views) {
15492
- this.views = views;
15493
- for (const view3 of views) {
15494
- if (!this.stores.has(view3.name)) {
15495
- this.stores.set(view3.name, new StreamingStore);
15496
- }
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);
15497
15545
  }
15498
15546
  }
15499
- getStore(viewName) {
15500
- if (!this.stores.has(viewName)) {
15501
- this.stores.set(viewName, new StreamingStore);
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);
15502
15553
  }
15503
- return this.stores.get(viewName);
15504
- }
15505
- hasData(viewName) {
15506
- const store = this.stores.get(viewName);
15507
- return store ? store.hasData() : false;
15508
15554
  }
15509
- hasActiveStream(viewName) {
15510
- return this.activeStreams.has(viewName);
15511
- }
15512
- registerStream(viewName, createStream) {
15513
- const pending = this.pendingUnsubscribes.get(viewName);
15514
- if (pending) {
15515
- clearTimeout(pending);
15516
- this.pendingUnsubscribes.delete(viewName);
15517
- }
15518
- const existing = this.activeStreams.get(viewName);
15519
- if (existing) {
15520
- existing.refCount++;
15521
- return {
15522
- unsubscribe: () => this.unregisterStream(viewName),
15523
- wasReused: true
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: ""
15524
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;
15525
15597
  }
15526
- const streamConn = createStream();
15527
- this.activeStreams.set(viewName, {
15528
- unsubscribe: streamConn.unsubscribe,
15529
- refCount: 1
15530
- });
15598
+ const subscribed = entry;
15599
+ subscribed.listeners.add(onChange);
15600
+ let active = true;
15531
15601
  return {
15532
- unsubscribe: () => this.unregisterStream(viewName),
15533
- wasReused: false
15534
- };
15535
- }
15536
- unregisterStream(viewName) {
15537
- const stream2 = this.activeStreams.get(viewName);
15538
- if (!stream2)
15539
- return;
15540
- stream2.refCount--;
15541
- if (stream2.refCount <= 0) {
15542
- const timeout = setTimeout(() => {
15543
- this.pendingUnsubscribes.delete(viewName);
15544
- const current2 = this.activeStreams.get(viewName);
15545
- if (current2 && current2.refCount <= 0) {
15546
- current2.unsubscribe();
15547
- this.activeStreams.delete(viewName);
15548
- this.streamScopes.delete(viewName);
15549
- }
15550
- }, StreamingQueryCache.UNSUBSCRIBE_DELAY_MS);
15551
- this.pendingUnsubscribes.set(viewName, timeout);
15552
- }
15553
- }
15554
- subscribeQuery(descriptor, eventWire, scope) {
15555
- const key = descriptor.element;
15556
- if (scope)
15557
- this.streamScopes.set(key, scope);
15558
- const { unsubscribe } = this.registerStream(key, () => {
15559
- const subId = eventWire.subscribeQuery(descriptor, (data) => {
15560
- this.setViewData(descriptor.element, data);
15561
- }, scope);
15562
- return { unsubscribe: () => eventWire.unsubscribeQuery(subId) };
15563
- });
15564
- return unsubscribe;
15565
- }
15566
- invalidateScope(scope) {
15567
- for (const [viewName, viewScope] of this.streamScopes) {
15568
- if (viewScope !== scope)
15569
- continue;
15570
- const pending = this.pendingUnsubscribes.get(viewName);
15571
- if (pending) {
15572
- clearTimeout(pending);
15573
- this.pendingUnsubscribes.delete(viewName);
15574
- }
15575
- const stream2 = this.activeStreams.get(viewName);
15576
- if (stream2) {
15577
- try {
15578
- stream2.unsubscribe();
15579
- } catch {}
15580
- this.activeStreams.delete(viewName);
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);
15581
15621
  }
15582
- this.streamScopes.delete(viewName);
15583
- const store = this.stores.get(viewName);
15584
- if (store)
15585
- store.clear();
15586
- }
15587
- }
15588
- setViewData(viewName, data) {
15589
- const store = this.stores.get(viewName);
15590
- if (!store)
15591
- return;
15592
- if (Array.isArray(data)) {
15593
- store.setAll(data);
15594
- } else if (data && typeof data === "object" && "_id" in data) {
15595
- store.setAll([data]);
15596
- } else {
15597
- store.setAll([]);
15598
- }
15622
+ };
15599
15623
  }
15600
- async applyEvent(event3) {
15601
- for (const view3 of this.views) {
15602
- const handlers = view3.getHandlers();
15603
- const handler = handlers[event3.type];
15604
- if (!handler)
15624
+ invalidateScope(scope, eventWire) {
15625
+ const prefix = `${scope}:`;
15626
+ for (const [key, entry] of this.entries) {
15627
+ if (!key.startsWith(prefix))
15605
15628
  continue;
15606
- const store = this.stores.get(view3.name);
15607
- if (!store)
15608
- continue;
15609
- const ctx = {
15610
- set: async (id3, data) => {
15611
- store.set(String(id3), { _id: String(id3), ...data });
15612
- },
15613
- modify: async (id3, data) => {
15614
- store.modify(String(id3), data);
15615
- },
15616
- remove: async (id3) => {
15617
- store.remove(String(id3));
15618
- },
15619
- find: async (options) => {
15620
- return store.find(options);
15621
- },
15622
- findOne: async (where) => {
15623
- return store.findOne(where);
15624
- },
15625
- $auth: {}
15626
- };
15627
- await handler(ctx, event3);
15628
- }
15629
- }
15630
- clear() {
15631
- for (const stream2 of this.activeStreams.values()) {
15632
- stream2.unsubscribe();
15633
- }
15634
- this.activeStreams.clear();
15635
- this.streamScopes.clear();
15636
- for (const timeout of this.pendingUnsubscribes.values()) {
15637
- clearTimeout(timeout);
15638
- }
15639
- this.pendingUnsubscribes.clear();
15640
- for (const store of this.stores.values()) {
15641
- 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);
15642
15636
  }
15643
15637
  }
15644
- }
15645
-
15646
- class StreamingStore {
15647
- data = new Map;
15648
- listeners = new Set;
15649
- initialized = false;
15650
- hasData() {
15651
- return this.initialized;
15652
- }
15653
- setAll(items) {
15654
- this.initialized = true;
15655
- this.data.clear();
15656
- for (const item of items) {
15657
- 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);
15658
15643
  }
15659
- this.notifyListeners(null);
15660
- }
15661
- set(id3, item) {
15662
- this.data.set(id3, item);
15663
- this.notifyListeners([{ type: "set", id: id3, item }]);
15664
- }
15665
- modify(id3, updates) {
15666
- const existing = this.data.get(id3);
15667
- if (existing) {
15668
- const updated = { ...existing, ...updates };
15669
- this.data.set(id3, updated);
15670
- this.notifyListeners([{ type: "set", id: id3, item: updated }]);
15671
- }
15672
- }
15673
- remove(id3) {
15674
- if (this.data.delete(id3)) {
15675
- this.notifyListeners([{ type: "delete", id: id3, item: null }]);
15676
- }
15677
- }
15678
- clear() {
15679
- this.initialized = false;
15680
- this.data.clear();
15681
- this.notifyListeners(null);
15682
- }
15683
- find(options = {}) {
15684
- let results = Array.from(this.data.values());
15685
- if (options.where) {
15686
- results = results.filter((item) => checkItemMatchesWhere2(item, options.where));
15687
- }
15688
- return applyOrderByAndLimit(results, options);
15689
- }
15690
- findOne(where) {
15691
- const results = this.find({ where });
15692
- return results[0];
15693
- }
15694
- subscribe(listener4) {
15695
- this.listeners.add(listener4);
15696
- return () => {
15697
- this.listeners.delete(listener4);
15698
- };
15644
+ this.entries.clear();
15699
15645
  }
15700
- notifyListeners(events) {
15701
- for (const listener4 of this.listeners) {
15702
- listener4(events);
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);
15652
+ }
15703
15653
  }
15704
15654
  }
15705
15655
  }
15706
15656
 
15707
15657
  class StreamingEventPublisher {
15708
- cache;
15709
15658
  eventWire;
15710
15659
  views = [];
15711
15660
  subscribers = new Map;
15712
- constructor(cache, eventWire) {
15713
- this.cache = cache;
15661
+ constructor(eventWire) {
15714
15662
  this.eventWire = eventWire;
15715
15663
  }
15716
15664
  registerViews(views) {
15717
15665
  this.views = views;
15718
- this.cache.registerViews(views);
15719
15666
  }
15720
15667
  async publish(event3) {
15721
- await this.cache.applyEvent(event3);
15722
15668
  await this.notifySubscribers(event3);
15723
15669
  this.eventWire.syncEvents([
15724
15670
  {
@@ -18862,7 +18808,7 @@ var init_dist = __esm(() => {
18862
18808
  throw new Error(`Scope violation: access denied to store "${storeName}" (not declared in query/mutate)`);
18863
18809
  }
18864
18810
  const inner = this.#inner.getStore(storeName);
18865
- return new ScopedStore2(inner, this.#restrictions, permission === "read-write");
18811
+ return new ScopedStore(inner, this.#restrictions, permission === "read-write");
18866
18812
  }
18867
18813
  fork() {
18868
18814
  return this.#inner.fork();
@@ -19724,12 +19670,19 @@ var init_dist = __esm(() => {
19724
19670
  constructor(storeName, dataStorage, deserialize) {
19725
19671
  super(storeName, dataStorage, deserialize);
19726
19672
  }
19673
+ async readExisting(transaction, id2, transactionCache) {
19674
+ const cacheKey = `${this.storeName}:${id2}`;
19675
+ if (transactionCache && transactionCache.has(cacheKey)) {
19676
+ return transactionCache.get(cacheKey);
19677
+ }
19678
+ return transaction.find(this.storeName, { where: { _id: id2 } }).then((results) => results[0]);
19679
+ }
19727
19680
  async applyChangeAndReturnEvent(transaction, change, transactionCache) {
19728
19681
  if (change.type === "set") {
19729
19682
  await transaction.set(this.storeName, change.data);
19730
19683
  const item = this.deserialize ? this.deserialize(change.data) : change.data;
19731
19684
  if (transactionCache) {
19732
- transactionCache.set(change.data._id, item);
19685
+ transactionCache.set(`${this.storeName}:${change.data._id}`, item);
19733
19686
  }
19734
19687
  return {
19735
19688
  from: null,
@@ -19743,6 +19696,9 @@ var init_dist = __esm(() => {
19743
19696
  }
19744
19697
  if (change.type === "delete") {
19745
19698
  await transaction.remove(this.storeName, change.id);
19699
+ if (transactionCache) {
19700
+ transactionCache.delete(`${this.storeName}:${change.id}`);
19701
+ }
19746
19702
  return {
19747
19703
  from: null,
19748
19704
  to: null,
@@ -19754,17 +19710,12 @@ var init_dist = __esm(() => {
19754
19710
  };
19755
19711
  }
19756
19712
  if (change.type === "modify") {
19757
- let existing;
19758
- if (transactionCache && transactionCache.has(change.id)) {
19759
- existing = transactionCache.get(change.id);
19760
- } else {
19761
- existing = await transaction.find(this.storeName, { where: { _id: change.id } }).then((results) => results[0]);
19762
- }
19713
+ const existing = await this.readExisting(transaction, change.id, transactionCache);
19763
19714
  const updated = existing ? deepMerge(existing, change.data) : { _id: change.id, ...change.data };
19764
19715
  await transaction.set(this.storeName, updated);
19765
19716
  const item = this.deserialize ? this.deserialize(updated) : updated;
19766
19717
  if (transactionCache) {
19767
- transactionCache.set(change.id, item);
19718
+ transactionCache.set(`${this.storeName}:${change.id}`, item);
19768
19719
  }
19769
19720
  return {
19770
19721
  from: null,
@@ -19777,17 +19728,12 @@ var init_dist = __esm(() => {
19777
19728
  };
19778
19729
  }
19779
19730
  if (change.type === "mutate") {
19780
- let existing;
19781
- if (transactionCache && transactionCache.has(change.id)) {
19782
- existing = transactionCache.get(change.id);
19783
- } else {
19784
- existing = await transaction.find(this.storeName, { where: { _id: change.id } }).then((results) => results[0]);
19785
- }
19731
+ const existing = await this.readExisting(transaction, change.id, transactionCache);
19786
19732
  const updated = apply(existing || {}, change.patches);
19787
19733
  await transaction.set(this.storeName, updated);
19788
19734
  const item = this.deserialize ? this.deserialize(updated) : updated;
19789
19735
  if (transactionCache) {
19790
- transactionCache.set(change.id, item);
19736
+ transactionCache.set(`${this.storeName}:${change.id}`, item);
19791
19737
  }
19792
19738
  return {
19793
19739
  from: null,
@@ -21409,12 +21355,13 @@ class EventWire2 {
21409
21355
  onSyncedCallback;
21410
21356
  reconnectTimeout;
21411
21357
  syncRequested = false;
21412
- viewSubscriptions = new Map;
21413
- viewSubCounter = 0;
21414
- pendingViewSubs = [];
21415
- constructor(baseUrl) {
21358
+ querySubscriptions = new Map;
21359
+ querySubCounter = 0;
21360
+ enableEventSync;
21361
+ constructor(baseUrl, options) {
21416
21362
  this.baseUrl = baseUrl;
21417
21363
  this.instanceId = ++eventWireInstanceCounter2;
21364
+ this.enableEventSync = options?.enableEventSync ?? true;
21418
21365
  }
21419
21366
  setScopeToken(scope, token) {
21420
21367
  if (token === null) {
@@ -21460,9 +21407,11 @@ class EventWire2 {
21460
21407
  if (this.ws && this.ws.readyState === WebSocket.OPEN) {
21461
21408
  this.state = "connected";
21462
21409
  this.sendAllScopeTokens();
21463
- this.requestSync();
21410
+ if (this.enableEventSync) {
21411
+ this.requestSync();
21412
+ }
21464
21413
  this.flushPendingEvents();
21465
- this.flushPendingViewSubs();
21414
+ this.sendAllQuerySubscriptions();
21466
21415
  } else {
21467
21416
  console.log(`[EventWire] onopen called but ws is not OPEN, readyState:`, this.ws?.readyState);
21468
21417
  }
@@ -21538,9 +21487,13 @@ class EventWire2 {
21538
21487
  onSynced(callback) {
21539
21488
  this.onSyncedCallback = callback;
21540
21489
  }
21541
- subscribeQuery(descriptor, callback, scope) {
21542
- const subscriptionId = `qs_${++this.viewSubCounter}_${Date.now()}`;
21543
- this.viewSubscriptions.set(subscriptionId, callback);
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
+ });
21544
21497
  if (this.state === "connected" && this.ws) {
21545
21498
  this.ws.send(JSON.stringify({
21546
21499
  type: "subscribe-query",
@@ -21548,20 +21501,17 @@ class EventWire2 {
21548
21501
  descriptor,
21549
21502
  scope
21550
21503
  }));
21551
- } else {
21552
- this.pendingViewSubs.push({ subscriptionId, descriptor, scope });
21553
21504
  }
21554
21505
  return subscriptionId;
21555
21506
  }
21556
21507
  unsubscribeQuery(subscriptionId) {
21557
- this.viewSubscriptions.delete(subscriptionId);
21508
+ this.querySubscriptions.delete(subscriptionId);
21558
21509
  if (this.state === "connected" && this.ws) {
21559
21510
  this.ws.send(JSON.stringify({
21560
21511
  type: "unsubscribe-query",
21561
21512
  subscriptionId
21562
21513
  }));
21563
21514
  }
21564
- this.pendingViewSubs = this.pendingViewSubs.filter((s) => s.subscriptionId !== subscriptionId);
21565
21515
  }
21566
21516
  getState() {
21567
21517
  return this.state;
@@ -21594,10 +21544,17 @@ class EventWire2 {
21594
21544
  this.lastHostEventId = message.lastHostEventId;
21595
21545
  }
21596
21546
  break;
21597
- case "query-data": {
21598
- const cb = this.viewSubscriptions.get(message.subscriptionId);
21599
- if (cb) {
21600
- cb(message.data);
21547
+ case "query-snapshot": {
21548
+ const sub = this.querySubscriptions.get(message.subscriptionId);
21549
+ if (sub) {
21550
+ sub.callbacks.onSnapshot(message.result ?? null);
21551
+ }
21552
+ break;
21553
+ }
21554
+ case "query-changes": {
21555
+ const sub = this.querySubscriptions.get(message.subscriptionId);
21556
+ if (sub && Array.isArray(message.changes)) {
21557
+ sub.callbacks.onChanges(message.changes);
21601
21558
  }
21602
21559
  break;
21603
21560
  }
@@ -21625,18 +21582,17 @@ class EventWire2 {
21625
21582
  this.pendingEvents = [];
21626
21583
  }
21627
21584
  }
21628
- flushPendingViewSubs() {
21585
+ sendAllQuerySubscriptions() {
21629
21586
  if (!this.ws || this.state !== "connected")
21630
21587
  return;
21631
- for (const sub of this.pendingViewSubs) {
21588
+ for (const [subscriptionId, sub] of this.querySubscriptions) {
21632
21589
  this.ws.send(JSON.stringify({
21633
21590
  type: "subscribe-query",
21634
- subscriptionId: sub.subscriptionId,
21591
+ subscriptionId,
21635
21592
  descriptor: sub.descriptor,
21636
21593
  scope: sub.scope
21637
21594
  }));
21638
21595
  }
21639
- this.pendingViewSubs = [];
21640
21596
  }
21641
21597
  scheduleReconnect() {
21642
21598
  if (this.reconnectTimeout)
@@ -22112,7 +22068,7 @@ class DataStorage2 {
22112
22068
  }
22113
22069
  }
22114
22070
 
22115
- class ScopedStore3 {
22071
+ class ScopedStore2 {
22116
22072
  #inner;
22117
22073
  #restrictions;
22118
22074
  #canWrite;
@@ -22465,6 +22421,47 @@ function deepMerge2(target, source) {
22465
22421
  function isPlainObject2(item) {
22466
22422
  return item && typeof item === "object" && !Array.isArray(item) && !(item instanceof Date) && Object.prototype.toString.call(item) === "[object Object]";
22467
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
+ }
22468
22465
  function resolveQueryChange2(currentResult, event3, options) {
22469
22466
  const index = currentResult.findIndex((e2) => e2._id === event3.id);
22470
22467
  const isInCurrentResult = index !== -1;
@@ -22474,7 +22471,7 @@ function resolveQueryChange2(currentResult, event3, options) {
22474
22471
  }
22475
22472
  return false;
22476
22473
  }
22477
- const shouldBeInResult = checkItemMatchesWhere3(event3.item, options.where);
22474
+ const shouldBeInResult = checkItemMatchesWhere2(event3.item, options.where);
22478
22475
  if (isInCurrentResult && shouldBeInResult) {
22479
22476
  const newResult = currentResult.toSpliced(index, 1, event3.item);
22480
22477
  return applyOrderByAndLimit2(newResult, options);
@@ -22486,7 +22483,7 @@ function resolveQueryChange2(currentResult, event3, options) {
22486
22483
  }
22487
22484
  return false;
22488
22485
  }
22489
- function checkItemMatchesWhere3(item, where) {
22486
+ function checkItemMatchesWhere2(item, where) {
22490
22487
  if (!where) {
22491
22488
  return true;
22492
22489
  }
@@ -22580,7 +22577,8 @@ class ObservableDataStorage2 {
22580
22577
  }
22581
22578
  handleStoreChange(storeName, events) {
22582
22579
  let hasChanges = false;
22583
- for (const query of this.trackedQueries.values()) {
22580
+ const staleKeys = [];
22581
+ for (const [key, query] of this.trackedQueries) {
22584
22582
  if (query.storeName !== storeName)
22585
22583
  continue;
22586
22584
  let currentResult = query.result;
@@ -22592,10 +22590,20 @@ class ObservableDataStorage2 {
22592
22590
  queryChanged = true;
22593
22591
  }
22594
22592
  }
22595
- if (queryChanged) {
22596
- 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);
22597
22597
  hasChanges = true;
22598
+ continue;
22598
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);
22599
22607
  }
22600
22608
  if (hasChanges) {
22601
22609
  this.onChange();
@@ -22738,7 +22746,7 @@ function executeDescriptor2(descriptor, context2, adapters, contextMethod, optio
22738
22746
  return method(...descriptor.args);
22739
22747
  }
22740
22748
 
22741
- class ScopedModel5 {
22749
+ class ScopedModel4 {
22742
22750
  context;
22743
22751
  scopeName;
22744
22752
  parent;
@@ -22810,13 +22818,6 @@ class ScopedModel5 {
22810
22818
  }
22811
22819
  return wire.query(viewName, options, this.getAuth());
22812
22820
  }
22813
- subscribeQuery(descriptor, callback) {
22814
- const wire = this.parent.getAdapters().eventWire;
22815
- if (!wire) {
22816
- throw new Error(`Cannot subscribe to query: no eventWire available.`);
22817
- }
22818
- return wire.subscribeQuery(descriptor, callback, this.scopeName);
22819
- }
22820
22821
  get query() {
22821
22822
  return buildContextAccessor2(this.context, this.scopedAdapters, "queryContext", (descriptor) => descriptor);
22822
22823
  }
@@ -22866,251 +22867,142 @@ class Model3 {
22866
22867
  scope(name) {
22867
22868
  let s = this.scopes.get(name);
22868
22869
  if (!s) {
22869
- s = new ScopedModel5(this, name);
22870
+ s = new ScopedModel4(this, name);
22870
22871
  this.scopes.set(name, s);
22871
22872
  }
22872
22873
  return s;
22873
22874
  }
22874
22875
  }
22875
-
22876
- class StreamingQueryCache2 {
22877
- stores = new Map;
22878
- views = [];
22879
- activeStreams = new Map;
22880
- pendingUnsubscribes = new Map;
22881
- streamScopes = new Map;
22882
- static UNSUBSCRIBE_DELAY_MS = 5000;
22883
- registerViews(views) {
22884
- this.views = views;
22885
- for (const view3 of views) {
22886
- if (!this.stores.has(view3.name)) {
22887
- this.stores.set(view3.name, new StreamingStore2);
22888
- }
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);
22889
22883
  }
22890
22884
  }
22891
- getStore(viewName) {
22892
- if (!this.stores.has(viewName)) {
22893
- this.stores.set(viewName, new StreamingStore2);
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);
22894
22891
  }
22895
- return this.stores.get(viewName);
22896
22892
  }
22897
- hasData(viewName) {
22898
- const store = this.stores.get(viewName);
22899
- return store ? store.hasData() : false;
22900
- }
22901
- hasActiveStream(viewName) {
22902
- return this.activeStreams.has(viewName);
22903
- }
22904
- registerStream(viewName, createStream) {
22905
- const pending = this.pendingUnsubscribes.get(viewName);
22906
- if (pending) {
22907
- clearTimeout(pending);
22908
- this.pendingUnsubscribes.delete(viewName);
22909
- }
22910
- const existing = this.activeStreams.get(viewName);
22911
- if (existing) {
22912
- existing.refCount++;
22913
- return {
22914
- unsubscribe: () => this.unregisterStream(viewName),
22915
- wasReused: true
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: ""
22916
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;
22917
22935
  }
22918
- const streamConn = createStream();
22919
- this.activeStreams.set(viewName, {
22920
- unsubscribe: streamConn.unsubscribe,
22921
- refCount: 1
22922
- });
22936
+ const subscribed = entry;
22937
+ subscribed.listeners.add(onChange);
22938
+ let active = true;
22923
22939
  return {
22924
- unsubscribe: () => this.unregisterStream(viewName),
22925
- wasReused: false
22926
- };
22927
- }
22928
- unregisterStream(viewName) {
22929
- const stream2 = this.activeStreams.get(viewName);
22930
- if (!stream2)
22931
- return;
22932
- stream2.refCount--;
22933
- if (stream2.refCount <= 0) {
22934
- const timeout = setTimeout(() => {
22935
- this.pendingUnsubscribes.delete(viewName);
22936
- const current22 = this.activeStreams.get(viewName);
22937
- if (current22 && current22.refCount <= 0) {
22938
- current22.unsubscribe();
22939
- this.activeStreams.delete(viewName);
22940
- this.streamScopes.delete(viewName);
22941
- }
22942
- }, StreamingQueryCache2.UNSUBSCRIBE_DELAY_MS);
22943
- this.pendingUnsubscribes.set(viewName, timeout);
22944
- }
22945
- }
22946
- subscribeQuery(descriptor, eventWire, scope) {
22947
- const key = descriptor.element;
22948
- if (scope)
22949
- this.streamScopes.set(key, scope);
22950
- const { unsubscribe } = this.registerStream(key, () => {
22951
- const subId = eventWire.subscribeQuery(descriptor, (data) => {
22952
- this.setViewData(descriptor.element, data);
22953
- }, scope);
22954
- return { unsubscribe: () => eventWire.unsubscribeQuery(subId) };
22955
- });
22956
- return unsubscribe;
22957
- }
22958
- invalidateScope(scope) {
22959
- for (const [viewName, viewScope] of this.streamScopes) {
22960
- if (viewScope !== scope)
22961
- continue;
22962
- const pending = this.pendingUnsubscribes.get(viewName);
22963
- if (pending) {
22964
- clearTimeout(pending);
22965
- this.pendingUnsubscribes.delete(viewName);
22966
- }
22967
- const stream2 = this.activeStreams.get(viewName);
22968
- if (stream2) {
22969
- try {
22970
- stream2.unsubscribe();
22971
- } catch {}
22972
- this.activeStreams.delete(viewName);
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);
22973
22959
  }
22974
- this.streamScopes.delete(viewName);
22975
- const store = this.stores.get(viewName);
22976
- if (store)
22977
- store.clear();
22978
- }
22979
- }
22980
- setViewData(viewName, data) {
22981
- const store = this.stores.get(viewName);
22982
- if (!store)
22983
- return;
22984
- if (Array.isArray(data)) {
22985
- store.setAll(data);
22986
- } else if (data && typeof data === "object" && "_id" in data) {
22987
- store.setAll([data]);
22988
- } else {
22989
- store.setAll([]);
22990
- }
22960
+ };
22991
22961
  }
22992
- async applyEvent(event3) {
22993
- for (const view3 of this.views) {
22994
- const handlers = view3.getHandlers();
22995
- const handler = handlers[event3.type];
22996
- if (!handler)
22997
- continue;
22998
- const store = this.stores.get(view3.name);
22999
- if (!store)
22962
+ invalidateScope(scope, eventWire) {
22963
+ const prefix = `${scope}:`;
22964
+ for (const [key, entry] of this.entries) {
22965
+ if (!key.startsWith(prefix))
23000
22966
  continue;
23001
- const ctx = {
23002
- set: async (id3, data) => {
23003
- store.set(String(id3), { _id: String(id3), ...data });
23004
- },
23005
- modify: async (id3, data) => {
23006
- store.modify(String(id3), data);
23007
- },
23008
- remove: async (id3) => {
23009
- store.remove(String(id3));
23010
- },
23011
- find: async (options) => {
23012
- return store.find(options);
23013
- },
23014
- findOne: async (where) => {
23015
- return store.findOne(where);
23016
- },
23017
- $auth: {}
23018
- };
23019
- await handler(ctx, event3);
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);
23020
22974
  }
23021
22975
  }
23022
- clear() {
23023
- for (const stream2 of this.activeStreams.values()) {
23024
- stream2.unsubscribe();
23025
- }
23026
- this.activeStreams.clear();
23027
- this.streamScopes.clear();
23028
- for (const timeout of this.pendingUnsubscribes.values()) {
23029
- clearTimeout(timeout);
23030
- }
23031
- this.pendingUnsubscribes.clear();
23032
- for (const store of this.stores.values()) {
23033
- store.clear();
22976
+ clear(eventWire) {
22977
+ for (const entry of this.entries.values()) {
22978
+ if (entry.pendingUnsub)
22979
+ clearTimeout(entry.pendingUnsub);
22980
+ eventWire?.unsubscribeQuery(entry.subscriptionId);
23034
22981
  }
22982
+ this.entries.clear();
23035
22983
  }
23036
- }
23037
-
23038
- class StreamingStore2 {
23039
- data = new Map;
23040
- listeners = new Set;
23041
- initialized = false;
23042
- hasData() {
23043
- return this.initialized;
23044
- }
23045
- setAll(items) {
23046
- this.initialized = true;
23047
- this.data.clear();
23048
- for (const item of items) {
23049
- this.data.set(item._id, item);
23050
- }
23051
- this.notifyListeners(null);
23052
- }
23053
- set(id3, item) {
23054
- this.data.set(id3, item);
23055
- this.notifyListeners([{ type: "set", id: id3, item }]);
23056
- }
23057
- modify(id3, updates) {
23058
- const existing = this.data.get(id3);
23059
- if (existing) {
23060
- const updated = { ...existing, ...updates };
23061
- this.data.set(id3, updated);
23062
- this.notifyListeners([{ type: "set", id: id3, item: updated }]);
23063
- }
23064
- }
23065
- remove(id3) {
23066
- if (this.data.delete(id3)) {
23067
- this.notifyListeners([{ type: "delete", id: id3, item: null }]);
23068
- }
23069
- }
23070
- clear() {
23071
- this.initialized = false;
23072
- this.data.clear();
23073
- this.notifyListeners(null);
23074
- }
23075
- find(options = {}) {
23076
- let results = Array.from(this.data.values());
23077
- if (options.where) {
23078
- results = results.filter((item) => checkItemMatchesWhere3(item, options.where));
23079
- }
23080
- return applyOrderByAndLimit2(results, options);
23081
- }
23082
- findOne(where) {
23083
- const results = this.find({ where });
23084
- return results[0];
23085
- }
23086
- subscribe(listener4) {
23087
- this.listeners.add(listener4);
23088
- return () => {
23089
- this.listeners.delete(listener4);
23090
- };
23091
- }
23092
- notifyListeners(events) {
23093
- for (const listener4 of this.listeners) {
23094
- listener4(events);
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);
22990
+ }
23095
22991
  }
23096
22992
  }
23097
22993
  }
23098
22994
 
23099
22995
  class StreamingEventPublisher2 {
23100
- cache;
23101
22996
  eventWire;
23102
22997
  views = [];
23103
22998
  subscribers = new Map;
23104
- constructor(cache, eventWire) {
23105
- this.cache = cache;
22999
+ constructor(eventWire) {
23106
23000
  this.eventWire = eventWire;
23107
23001
  }
23108
23002
  registerViews(views) {
23109
23003
  this.views = views;
23110
- this.cache.registerViews(views);
23111
23004
  }
23112
23005
  async publish(event3) {
23113
- await this.cache.applyEvent(event3);
23114
23006
  await this.notifySubscribers(event3);
23115
23007
  this.eventWire.syncEvents([
23116
23008
  {
@@ -24689,7 +24581,7 @@ var init_dist2 = __esm(() => {
24689
24581
  throw new Error(`Scope violation: access denied to store "${storeName}" (not declared in query/mutate)`);
24690
24582
  }
24691
24583
  const inner = this.#inner.getStore(storeName);
24692
- return new ScopedStore3(inner, this.#restrictions, permission === "read-write");
24584
+ return new ScopedStore2(inner, this.#restrictions, permission === "read-write");
24693
24585
  }
24694
24586
  fork() {
24695
24587
  return this.#inner.fork();
@@ -25551,12 +25443,19 @@ var init_dist2 = __esm(() => {
25551
25443
  constructor(storeName, dataStorage, deserialize) {
25552
25444
  super(storeName, dataStorage, deserialize);
25553
25445
  }
25446
+ async readExisting(transaction, id22, transactionCache) {
25447
+ const cacheKey = `${this.storeName}:${id22}`;
25448
+ if (transactionCache && transactionCache.has(cacheKey)) {
25449
+ return transactionCache.get(cacheKey);
25450
+ }
25451
+ return transaction.find(this.storeName, { where: { _id: id22 } }).then((results) => results[0]);
25452
+ }
25554
25453
  async applyChangeAndReturnEvent(transaction, change, transactionCache) {
25555
25454
  if (change.type === "set") {
25556
25455
  await transaction.set(this.storeName, change.data);
25557
25456
  const item = this.deserialize ? this.deserialize(change.data) : change.data;
25558
25457
  if (transactionCache) {
25559
- transactionCache.set(change.data._id, item);
25458
+ transactionCache.set(`${this.storeName}:${change.data._id}`, item);
25560
25459
  }
25561
25460
  return {
25562
25461
  from: null,
@@ -25570,6 +25469,9 @@ var init_dist2 = __esm(() => {
25570
25469
  }
25571
25470
  if (change.type === "delete") {
25572
25471
  await transaction.remove(this.storeName, change.id);
25472
+ if (transactionCache) {
25473
+ transactionCache.delete(`${this.storeName}:${change.id}`);
25474
+ }
25573
25475
  return {
25574
25476
  from: null,
25575
25477
  to: null,
@@ -25581,17 +25483,12 @@ var init_dist2 = __esm(() => {
25581
25483
  };
25582
25484
  }
25583
25485
  if (change.type === "modify") {
25584
- let existing;
25585
- if (transactionCache && transactionCache.has(change.id)) {
25586
- existing = transactionCache.get(change.id);
25587
- } else {
25588
- existing = await transaction.find(this.storeName, { where: { _id: change.id } }).then((results) => results[0]);
25589
- }
25486
+ const existing = await this.readExisting(transaction, change.id, transactionCache);
25590
25487
  const updated = existing ? deepMerge2(existing, change.data) : { _id: change.id, ...change.data };
25591
25488
  await transaction.set(this.storeName, updated);
25592
25489
  const item = this.deserialize ? this.deserialize(updated) : updated;
25593
25490
  if (transactionCache) {
25594
- transactionCache.set(change.id, item);
25491
+ transactionCache.set(`${this.storeName}:${change.id}`, item);
25595
25492
  }
25596
25493
  return {
25597
25494
  from: null,
@@ -25604,17 +25501,12 @@ var init_dist2 = __esm(() => {
25604
25501
  };
25605
25502
  }
25606
25503
  if (change.type === "mutate") {
25607
- let existing;
25608
- if (transactionCache && transactionCache.has(change.id)) {
25609
- existing = transactionCache.get(change.id);
25610
- } else {
25611
- existing = await transaction.find(this.storeName, { where: { _id: change.id } }).then((results) => results[0]);
25612
- }
25504
+ const existing = await this.readExisting(transaction, change.id, transactionCache);
25613
25505
  const updated = apply2(existing || {}, change.patches);
25614
25506
  await transaction.set(this.storeName, updated);
25615
25507
  const item = this.deserialize ? this.deserialize(updated) : updated;
25616
25508
  if (transactionCache) {
25617
- transactionCache.set(change.id, item);
25509
+ transactionCache.set(`${this.storeName}:${change.id}`, item);
25618
25510
  }
25619
25511
  return {
25620
25512
  from: null,
@@ -34504,8 +34396,8 @@ ${colors3.yellow}Type declaration errors:${colors3.reset}`);
34504
34396
  }
34505
34397
 
34506
34398
  // src/platform/shared.ts
34507
- import { copyFileSync, existsSync as existsSync10, mkdirSync as mkdirSync9, readdirSync as readdirSync5, readFileSync as readFileSync10, writeFileSync as writeFileSync9 } from "fs";
34508
- 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";
34509
34401
 
34510
34402
  // src/builder/module-builder.ts
34511
34403
  import { execSync } from "child_process";
@@ -35300,7 +35192,8 @@ writeFileSync(out, JSON.stringify(result, null, 2) + "\\n");
35300
35192
  `.trim();
35301
35193
 
35302
35194
  // src/builder/chunk-planner.ts
35303
- 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";
35304
35197
  var PUBLIC_CHUNK = "public";
35305
35198
  function planChunks(packages, accessMap) {
35306
35199
  const assignments = new Map;
@@ -35332,6 +35225,53 @@ function planChunks(packages, accessMap) {
35332
35225
  });
35333
35226
  return { assignments, groups, chunks };
35334
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
+ }
35335
35275
  function moduleNameOf(pkgName) {
35336
35276
  return pkgName.includes("/") ? pkgName.split("/").pop() : pkgName;
35337
35277
  }
@@ -35339,17 +35279,19 @@ function resolveChunk(moduleName, access) {
35339
35279
  if (!access || access.rules.length === 0)
35340
35280
  return PUBLIC_CHUNK;
35341
35281
  const tokenNames = new Set(access.rules.map((r) => r.token.name));
35342
- if (tokenNames.size === 1) {
35343
- 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.`);
35344
35285
  }
35345
- const list = [...tokenNames].sort().join(", ");
35346
- 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;
35347
35289
  }
35348
35290
 
35349
35291
  // src/builder/dependency-collector.ts
35350
35292
  import { createHash } from "crypto";
35351
- import { existsSync as existsSync9, mkdirSync as mkdirSync8, readFileSync as readFileSync9, writeFileSync as writeFileSync8 } from "fs";
35352
- 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";
35353
35295
  import { fileURLToPath as fileURLToPath6 } from "url";
35354
35296
  function collectFrameworkDeps(arcDir, rootDir, packages, sharedDeps = []) {
35355
35297
  mkdirSync8(arcDir, { recursive: true });
@@ -35360,7 +35302,7 @@ function collectFrameworkDeps(arcDir, rootDir, packages, sharedDeps = []) {
35360
35302
  try {
35361
35303
  const cliDir = dirname7(fileURLToPath6(import.meta.url));
35362
35304
  const arcOtelPkgPath = Bun.resolveSync("@arcote.tech/arc-otel/package.json", cliDir);
35363
- const arcOtelPkg = JSON.parse(readFileSync9(arcOtelPkgPath, "utf-8"));
35305
+ const arcOtelPkg = JSON.parse(readFileSync10(arcOtelPkgPath, "utf-8"));
35364
35306
  for (const [name, spec] of Object.entries(arcOtelPkg.dependencies ?? {})) {
35365
35307
  if (name.startsWith("@opentelemetry/")) {
35366
35308
  versions[name] = spec;
@@ -35371,10 +35313,10 @@ function collectFrameworkDeps(arcDir, rootDir, packages, sharedDeps = []) {
35371
35313
  console.warn(`[arc-otel] could not resolve @arcote.tech/arc-otel \u2014 image will run without telemetry deps: ${e.message}`);
35372
35314
  }
35373
35315
  let rootArc;
35374
- const rootPkgPath = join10(rootDir, "package.json");
35316
+ const rootPkgPath = join11(rootDir, "package.json");
35375
35317
  if (existsSync9(rootPkgPath)) {
35376
35318
  try {
35377
- const rootPkg = JSON.parse(readFileSync9(rootPkgPath, "utf-8"));
35319
+ const rootPkg = JSON.parse(readFileSync10(rootPkgPath, "utf-8"));
35378
35320
  if (rootPkg.arc && typeof rootPkg.arc === "object")
35379
35321
  rootArc = rootPkg.arc;
35380
35322
  } catch {}
@@ -35386,11 +35328,11 @@ function collectFrameworkDeps(arcDir, rootDir, packages, sharedDeps = []) {
35386
35328
  dependencies: versions,
35387
35329
  ...rootArc ? { arc: rootArc } : {}
35388
35330
  };
35389
- const manifestPath = join10(arcDir, "package.json");
35331
+ const manifestPath = join11(arcDir, "package.json");
35390
35332
  writeFileSync8(manifestPath, JSON.stringify(manifest, null, 2) + `
35391
35333
  `);
35392
- const hash = sha256Hex2(readFileSync9(manifestPath));
35393
- writeFileSync8(join10(arcDir, ".deps-hash"), hash + `
35334
+ const hash = sha256Hex2(readFileSync10(manifestPath));
35335
+ writeFileSync8(join11(arcDir, ".deps-hash"), hash + `
35394
35336
  `);
35395
35337
  return { hash, manifestPath };
35396
35338
  }
@@ -35398,7 +35340,7 @@ function sha256Hex2(bytes) {
35398
35340
  return createHash("sha256").update(bytes).digest("hex");
35399
35341
  }
35400
35342
  function resolveFrameworkVersions(rootDir, packages) {
35401
- const rootPkg = JSON.parse(readFileSync9(join10(rootDir, "package.json"), "utf-8"));
35343
+ const rootPkg = JSON.parse(readFileSync10(join11(rootDir, "package.json"), "utf-8"));
35402
35344
  const rootDeps = rootPkg.dependencies ?? {};
35403
35345
  const rootDevDeps = rootPkg.devDependencies ?? {};
35404
35346
  const required = new Set(FRAMEWORK_PEERS);
@@ -35457,9 +35399,9 @@ function resolveWorkspace() {
35457
35399
  process.exit(1);
35458
35400
  }
35459
35401
  const rootDir = dirname8(packageJsonPath);
35460
- const rootPkg = JSON.parse(readFileSync10(packageJsonPath, "utf-8"));
35402
+ const rootPkg = JSON.parse(readFileSync11(packageJsonPath, "utf-8"));
35461
35403
  const appName = rootPkg.name ?? "Arc App";
35462
- const arcDir = join11(rootDir, ".arc", "platform");
35404
+ const arcDir = join12(rootDir, ".arc", "platform");
35463
35405
  log2("Scanning workspaces...");
35464
35406
  const packages = discoverPackages(rootDir);
35465
35407
  if (packages.length > 0) {
@@ -35469,10 +35411,10 @@ function resolveWorkspace() {
35469
35411
  }
35470
35412
  let manifest;
35471
35413
  for (const name of ["manifest.json", "manifest.webmanifest"]) {
35472
- const manifestPath = join11(rootDir, name);
35414
+ const manifestPath = join12(rootDir, name);
35473
35415
  if (existsSync10(manifestPath)) {
35474
35416
  try {
35475
- const data = JSON.parse(readFileSync10(manifestPath, "utf-8"));
35417
+ const data = JSON.parse(readFileSync11(manifestPath, "utf-8"));
35476
35418
  const icons = data.icons;
35477
35419
  manifest = {
35478
35420
  path: manifestPath,
@@ -35491,9 +35433,9 @@ function resolveWorkspace() {
35491
35433
  rootPkg,
35492
35434
  appName,
35493
35435
  arcDir,
35494
- browserDir: join11(arcDir, "browser"),
35495
- assetsDir: join11(arcDir, "assets"),
35496
- publicDir: join11(rootDir, "public"),
35436
+ browserDir: join12(arcDir, "browser"),
35437
+ assetsDir: join12(arcDir, "assets"),
35438
+ publicDir: join12(rootDir, "public"),
35497
35439
  packages,
35498
35440
  manifest
35499
35441
  };
@@ -35503,11 +35445,12 @@ async function buildAll(ws, opts = {}) {
35503
35445
  const noCache = opts.noCache ?? false;
35504
35446
  const themePath = ws.rootPkg.arc?.theme;
35505
35447
  log2(`Building (concurrency parallel${noCache ? ", no-cache" : ""})...`);
35448
+ assertOneModulePerPackage(ws.packages);
35506
35449
  await buildContextPackages(ws.rootDir, ws.packages, cache, noCache);
35507
35450
  copyContextServerBundles(ws);
35508
35451
  const accessMap = await extractAccessMap(ws.rootDir, ws.packages);
35509
35452
  mkdirSync9(ws.arcDir, { recursive: true });
35510
- writeFileSync9(join11(ws.arcDir, "access.json"), JSON.stringify(accessMap, null, 2) + `
35453
+ writeFileSync9(join12(ws.arcDir, "access.json"), JSON.stringify(accessMap, null, 2) + `
35511
35454
  `);
35512
35455
  const plan = planChunks(ws.packages, accessMap);
35513
35456
  ok(`Chunks: ${plan.chunks.map((c) => `${c}(${plan.groups.get(c)?.length ?? 0})`).join(", ")}`);
@@ -35523,7 +35466,7 @@ async function buildAll(ws, opts = {}) {
35523
35466
  collectFrameworkDeps(ws.arcDir, ws.rootDir, ws.packages);
35524
35467
  saveBuildCache(ws.arcDir, cache);
35525
35468
  const finalManifest = assembleManifest(ws, browserResult, cache);
35526
- writeFileSync9(join11(ws.arcDir, "manifest.json"), JSON.stringify(finalManifest, null, 2));
35469
+ writeFileSync9(join12(ws.arcDir, "manifest.json"), JSON.stringify(finalManifest, null, 2));
35527
35470
  return finalManifest;
35528
35471
  }
35529
35472
  function assembleManifest(ws, browser, cache) {
@@ -35537,40 +35480,40 @@ function assembleManifest(ws, browser, cache) {
35537
35480
  };
35538
35481
  }
35539
35482
  function copyContextServerBundles(ws) {
35540
- const outDir = join11(ws.arcDir, "server");
35483
+ const outDir = join12(ws.arcDir, "server");
35541
35484
  mkdirSync9(outDir, { recursive: true });
35542
35485
  for (const pkg of ws.packages) {
35543
35486
  if (!isContextPackage(pkg.packageJson))
35544
35487
  continue;
35545
- const src = join11(pkg.path, "dist", "server", "main", "index.js");
35488
+ const src = join12(pkg.path, "dist", "server", "main", "index.js");
35546
35489
  if (!existsSync10(src)) {
35547
35490
  err(`Server bundle missing for ${pkg.name}: ${src}`);
35548
35491
  continue;
35549
35492
  }
35550
35493
  const safeName = pkg.path.split("/").pop();
35551
- const dst = join11(outDir, `${safeName}.js`);
35494
+ const dst = join12(outDir, `${safeName}.js`);
35552
35495
  copyFileSync(src, dst);
35553
35496
  }
35554
35497
  }
35555
35498
  function resolveAssetSource(from, pkgDir, rootDir) {
35556
35499
  if (from.startsWith("./") || from.startsWith("../")) {
35557
- const resolved = join11(pkgDir, from);
35500
+ const resolved = join12(pkgDir, from);
35558
35501
  return existsSync10(resolved) ? resolved : null;
35559
35502
  }
35560
35503
  const candidates = [
35561
- join11(rootDir, "node_modules", from),
35562
- join11(pkgDir, "node_modules", from)
35504
+ join12(rootDir, "node_modules", from),
35505
+ join12(pkgDir, "node_modules", from)
35563
35506
  ];
35564
35507
  for (const c of candidates) {
35565
35508
  if (existsSync10(c))
35566
35509
  return c;
35567
35510
  }
35568
- const bunCacheDir = join11(rootDir, "node_modules", ".bun");
35511
+ const bunCacheDir = join12(rootDir, "node_modules", ".bun");
35569
35512
  if (existsSync10(bunCacheDir)) {
35570
- for (const entry of readdirSync5(bunCacheDir, { withFileTypes: true })) {
35513
+ for (const entry of readdirSync6(bunCacheDir, { withFileTypes: true })) {
35571
35514
  if (!entry.isDirectory())
35572
35515
  continue;
35573
- const candidate = join11(bunCacheDir, entry.name, "node_modules", from);
35516
+ const candidate = join12(bunCacheDir, entry.name, "node_modules", from);
35574
35517
  if (existsSync10(candidate))
35575
35518
  return candidate;
35576
35519
  }
@@ -35578,11 +35521,11 @@ function resolveAssetSource(from, pkgDir, rootDir) {
35578
35521
  return null;
35579
35522
  }
35580
35523
  function readBrowserAssets(pkgDir) {
35581
- const pkgJsonPath = join11(pkgDir, "package.json");
35524
+ const pkgJsonPath = join12(pkgDir, "package.json");
35582
35525
  if (!existsSync10(pkgJsonPath))
35583
35526
  return [];
35584
35527
  try {
35585
- const pkg = JSON.parse(readFileSync10(pkgJsonPath, "utf-8"));
35528
+ const pkg = JSON.parse(readFileSync11(pkgJsonPath, "utf-8"));
35586
35529
  const assets = pkg.arc?.browserAssets;
35587
35530
  if (!Array.isArray(assets))
35588
35531
  return [];
@@ -35592,14 +35535,14 @@ function readBrowserAssets(pkgDir) {
35592
35535
  }
35593
35536
  }
35594
35537
  function discoverBrowserAssets(ws) {
35595
- const arcDir = join11(ws.rootDir, "node_modules", "@arcote.tech");
35538
+ const arcDir = join12(ws.rootDir, "node_modules", "@arcote.tech");
35596
35539
  if (!existsSync10(arcDir))
35597
35540
  return [];
35598
35541
  const out = [];
35599
- for (const entry of readdirSync5(arcDir, { withFileTypes: true })) {
35542
+ for (const entry of readdirSync6(arcDir, { withFileTypes: true })) {
35600
35543
  if (!entry.isDirectory() && !entry.isSymbolicLink())
35601
35544
  continue;
35602
- const pkgDir = join11(arcDir, entry.name);
35545
+ const pkgDir = join12(arcDir, entry.name);
35603
35546
  const assets = readBrowserAssets(pkgDir);
35604
35547
  for (const asset of assets) {
35605
35548
  const src = resolveAssetSource(asset.from, pkgDir, ws.rootDir);
@@ -35624,7 +35567,7 @@ async function copyBrowserAssets(ws, cache, noCache) {
35624
35567
  to: a.to,
35625
35568
  mtime: mtimeOf(a.src)
35626
35569
  })));
35627
- const requiredOutputs = assets.map((a) => join11(ws.assetsDir, a.to));
35570
+ const requiredOutputs = assets.map((a) => join12(ws.assetsDir, a.to));
35628
35571
  if (!noCache && isCacheHit(cache, unitId, inputHash, requiredOutputs)) {
35629
35572
  console.log(` \u2713 cached: browser-assets (${assets.length})`);
35630
35573
  return;
@@ -35632,10 +35575,10 @@ async function copyBrowserAssets(ws, cache, noCache) {
35632
35575
  console.log(` building: browser-assets (${assets.length})`);
35633
35576
  const outputHashes = {};
35634
35577
  for (const asset of assets) {
35635
- const dest = join11(ws.assetsDir, asset.to);
35578
+ const dest = join12(ws.assetsDir, asset.to);
35636
35579
  mkdirSync9(dirname8(dest), { recursive: true });
35637
35580
  copyFileSync(asset.src, dest);
35638
- outputHashes[asset.to] = sha256Hex(readFileSync10(dest));
35581
+ outputHashes[asset.to] = sha256Hex(readFileSync11(dest));
35639
35582
  }
35640
35583
  updateCache(cache, unitId, inputHash, { outputHashes });
35641
35584
  }
@@ -35643,15 +35586,15 @@ async function loadServerContext(ws) {
35643
35586
  globalThis.ONLY_SERVER = true;
35644
35587
  globalThis.ONLY_BROWSER = false;
35645
35588
  globalThis.ONLY_CLIENT = false;
35646
- const platformDir = join11(process.cwd(), "node_modules", "@arcote.tech", "platform");
35647
- const platformPkg = JSON.parse(readFileSync10(join11(platformDir, "package.json"), "utf-8"));
35648
- 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");
35649
35592
  await import(platformEntry);
35650
- const serverDir = join11(ws.arcDir, "server");
35651
- 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")) : [];
35652
35595
  if (bundles.length > 0) {
35653
35596
  for (const file of bundles) {
35654
- const bundlePath = join11(serverDir, file);
35597
+ const bundlePath = join12(serverDir, file);
35655
35598
  try {
35656
35599
  await import(bundlePath);
35657
35600
  } catch (e) {
@@ -35661,7 +35604,7 @@ async function loadServerContext(ws) {
35661
35604
  } else if (ws.packages.length > 0) {
35662
35605
  const ctxPackages = ws.packages.filter((p) => isContextPackage(p.packageJson));
35663
35606
  for (const ctx of ctxPackages) {
35664
- const serverDist = join11(ctx.path, "dist", "server", "main", "index.js");
35607
+ const serverDist = join12(ctx.path, "dist", "server", "main", "index.js");
35665
35608
  if (!existsSync10(serverDist)) {
35666
35609
  err(`Context server dist not found: ${serverDist}`);
35667
35610
  continue;
@@ -35691,21 +35634,21 @@ async function platformBuild(opts = {}) {
35691
35634
  }
35692
35635
 
35693
35636
  // src/commands/platform-deploy.ts
35694
- import { existsSync as existsSync17, readFileSync as readFileSync14 } from "fs";
35695
- 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";
35696
35639
  import { fileURLToPath as fileURLToPath8 } from "url";
35697
35640
 
35698
35641
  // src/deploy/bootstrap.ts
35699
35642
  var {spawn: spawn4 } = globalThis.Bun;
35700
35643
  import { mkdirSync as mkdirSync12, writeFileSync as writeFileSync13 } from "fs";
35701
35644
  import { tmpdir as tmpdir2 } from "os";
35702
- import { dirname as dirname9, join as join17 } from "path";
35645
+ import { dirname as dirname9, join as join18 } from "path";
35703
35646
 
35704
35647
  // src/deploy/ansible.ts
35705
35648
  import { spawn as nodeSpawn } from "child_process";
35706
35649
  import { existsSync as existsSync11, mkdirSync as mkdirSync10, writeFileSync as writeFileSync10 } from "fs";
35707
35650
  import { homedir, tmpdir } from "os";
35708
- import { join as join12 } from "path";
35651
+ import { join as join13 } from "path";
35709
35652
 
35710
35653
  // src/deploy/assets.ts
35711
35654
  var TERRAFORM_MAIN_TF = `terraform {
@@ -35979,28 +35922,28 @@ var ASSETS = {
35979
35922
  };
35980
35923
  async function materializeAssets(targetDir, files) {
35981
35924
  const { mkdirSync: mkdirSync10, writeFileSync: writeFileSync10 } = await import("fs");
35982
- const { join: join12 } = await import("path");
35925
+ const { join: join13 } = await import("path");
35983
35926
  mkdirSync10(targetDir, { recursive: true });
35984
35927
  for (const [name, content] of Object.entries(files)) {
35985
- writeFileSync10(join12(targetDir, name), content);
35928
+ writeFileSync10(join13(targetDir, name), content);
35986
35929
  }
35987
35930
  }
35988
35931
 
35989
35932
  // src/deploy/ansible.ts
35990
35933
  function pickSshKeyForAnsible(configured) {
35991
35934
  if (configured) {
35992
- const expanded = configured.startsWith("~") ? join12(homedir(), configured.slice(1)) : configured;
35935
+ const expanded = configured.startsWith("~") ? join13(homedir(), configured.slice(1)) : configured;
35993
35936
  return existsSync11(expanded) ? expanded : null;
35994
35937
  }
35995
35938
  for (const name of ["id_ed25519", "id_ecdsa", "id_rsa"]) {
35996
- const path4 = join12(homedir(), ".ssh", name);
35939
+ const path4 = join13(homedir(), ".ssh", name);
35997
35940
  if (existsSync11(path4))
35998
35941
  return path4;
35999
35942
  }
36000
35943
  return null;
36001
35944
  }
36002
35945
  async function runAnsible(inputs) {
36003
- const workDir = join12(tmpdir(), "arc-deploy", `ansible-${Date.now()}`);
35946
+ const workDir = join13(tmpdir(), "arc-deploy", `ansible-${Date.now()}`);
36004
35947
  mkdirSync10(workDir, { recursive: true });
36005
35948
  await materializeAssets(workDir, ASSETS.ansible);
36006
35949
  const user = inputs.asRoot ? "root" : inputs.target.user;
@@ -36017,7 +35960,7 @@ async function runAnsible(inputs) {
36017
35960
  ""
36018
35961
  ].join(`
36019
35962
  `);
36020
- writeFileSync10(join12(workDir, "inventory.ini"), inventory);
35963
+ writeFileSync10(join13(workDir, "inventory.ini"), inventory);
36021
35964
  const extraVarsJson = JSON.stringify({
36022
35965
  username: inputs.target.user,
36023
35966
  ssh_port: port,
@@ -37065,10 +37008,10 @@ import { spawn as nodeSpawn2 } from "child_process";
37065
37008
  import { createHash as createHash2 } from "crypto";
37066
37009
  import { existsSync as existsSync12, mkdirSync as mkdirSync11, writeFileSync as writeFileSync11 } from "fs";
37067
37010
  import { homedir as homedir2 } from "os";
37068
- import { join as join13 } from "path";
37011
+ import { join as join14 } from "path";
37069
37012
  async function runTerraform(inputs) {
37070
37013
  const wsHash = createHash2("sha256").update(inputs.workspaceDir).digest("hex").slice(0, 16);
37071
- const workDir = join13(homedir2(), ".arc-deploy", wsHash, "tf");
37014
+ const workDir = join14(homedir2(), ".arc-deploy", wsHash, "tf");
37072
37015
  mkdirSync11(workDir, { recursive: true });
37073
37016
  await materializeAssets(workDir, ASSETS.terraform);
37074
37017
  const sshPubKey = inputs.tf.sshPublicKey ?? expandHome("~/.ssh/id_ed25519.pub");
@@ -37085,7 +37028,7 @@ async function runTerraform(inputs) {
37085
37028
  ].join(`
37086
37029
  `) + `
37087
37030
  `;
37088
- writeFileSync11(join13(workDir, "terraform.tfvars"), tfvars);
37031
+ writeFileSync11(join14(workDir, "terraform.tfvars"), tfvars);
37089
37032
  await runTf(workDir, ["init", "-input=false", "-no-color"]);
37090
37033
  await runTf(workDir, [
37091
37034
  "apply",
@@ -37143,20 +37086,20 @@ function expandHome(p) {
37143
37086
  }
37144
37087
 
37145
37088
  // src/deploy/config.ts
37146
- import { existsSync as existsSync14, readFileSync as readFileSync12, writeFileSync as writeFileSync12 } from "fs";
37147
- 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";
37148
37091
 
37149
37092
  // src/deploy/env-file.ts
37150
- import { appendFileSync, existsSync as existsSync13, readFileSync as readFileSync11 } from "fs";
37151
- import { join as join14 } from "path";
37093
+ import { appendFileSync, existsSync as existsSync13, readFileSync as readFileSync12 } from "fs";
37094
+ import { join as join15 } from "path";
37152
37095
  function loadDeployEnvFiles(rootDir, envNames) {
37153
- const globalsPath = join14(rootDir, "deploy.arc.env");
37154
- 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) : {};
37155
37098
  const perEnv = {};
37156
37099
  for (const name of envNames) {
37157
- const envPath = join14(rootDir, `deploy.arc.${name}.env`);
37100
+ const envPath = join15(rootDir, `deploy.arc.${name}.env`);
37158
37101
  if (existsSync13(envPath)) {
37159
- perEnv[name] = parseEnvFile(readFileSync11(envPath, "utf-8"), envPath);
37102
+ perEnv[name] = parseEnvFile(readFileSync12(envPath, "utf-8"), envPath);
37160
37103
  }
37161
37104
  }
37162
37105
  return { globals, perEnv };
@@ -37172,16 +37115,16 @@ function ensurePersistedSecret(rootDir, scope, key, generate) {
37172
37115
  if (process.env[key])
37173
37116
  return process.env[key];
37174
37117
  const fileName = scope === "globals" ? "deploy.arc.env" : `deploy.arc.${scope}.env`;
37175
- const path4 = join14(rootDir, fileName);
37118
+ const path4 = join15(rootDir, fileName);
37176
37119
  if (existsSync13(path4)) {
37177
- const existing = parseEnvFile(readFileSync11(path4, "utf-8"), path4);
37120
+ const existing = parseEnvFile(readFileSync12(path4, "utf-8"), path4);
37178
37121
  if (existing[key]) {
37179
37122
  process.env[key] = existing[key];
37180
37123
  return existing[key];
37181
37124
  }
37182
37125
  }
37183
37126
  const value = generate();
37184
- const prefix = existsSync13(path4) && !readFileSync11(path4, "utf-8").endsWith(`
37127
+ const prefix = existsSync13(path4) && !readFileSync12(path4, "utf-8").endsWith(`
37185
37128
  `) ? `
37186
37129
  ` : "";
37187
37130
  appendFileSync(path4, `${prefix}${key}=${value}
@@ -37217,7 +37160,7 @@ function parseEnvFile(content, pathForErrors) {
37217
37160
  // src/deploy/config.ts
37218
37161
  var DEPLOY_CONFIG_FILE = "deploy.arc.json";
37219
37162
  function deployConfigPath(rootDir) {
37220
- return join15(rootDir, DEPLOY_CONFIG_FILE);
37163
+ return join16(rootDir, DEPLOY_CONFIG_FILE);
37221
37164
  }
37222
37165
  function deployConfigExists(rootDir) {
37223
37166
  return existsSync14(deployConfigPath(rootDir));
@@ -37227,7 +37170,7 @@ function loadDeployConfig(rootDir) {
37227
37170
  if (!existsSync14(path4)) {
37228
37171
  throw new Error(`Missing ${DEPLOY_CONFIG_FILE} at ${path4}`);
37229
37172
  }
37230
- const raw = readFileSync12(path4, "utf-8");
37173
+ const raw = readFileSync13(path4, "utf-8");
37231
37174
  let parsed;
37232
37175
  try {
37233
37176
  parsed = JSON.parse(raw);
@@ -37250,7 +37193,7 @@ function loadDeployConfig(rootDir) {
37250
37193
  }
37251
37194
  function saveDeployConfig(rootDir, cfg) {
37252
37195
  const path4 = deployConfigPath(rootDir);
37253
- const raw = existsSync14(path4) ? JSON.parse(readFileSync12(path4, "utf-8")) : {};
37196
+ const raw = existsSync14(path4) ? JSON.parse(readFileSync13(path4, "utf-8")) : {};
37254
37197
  raw.target = { ...raw.target, ...cfg.target };
37255
37198
  writeFileSync12(path4, JSON.stringify(raw, null, 2) + `
37256
37199
  `);
@@ -37463,14 +37406,14 @@ function cfgErr(path4, expected) {
37463
37406
  var {spawn: spawn3 } = globalThis.Bun;
37464
37407
  import { existsSync as existsSync15 } from "fs";
37465
37408
  import { homedir as homedir3 } from "os";
37466
- import { join as join16 } from "path";
37409
+ import { join as join17 } from "path";
37467
37410
  function pickSshKey(target) {
37468
37411
  if (target.sshKey) {
37469
- 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;
37470
37413
  return existsSync15(expanded) ? expanded : null;
37471
37414
  }
37472
37415
  for (const name of ["id_ed25519", "id_ecdsa", "id_rsa"]) {
37473
- const path4 = join16(homedir3(), ".ssh", name);
37416
+ const path4 = join17(homedir3(), ".ssh", name);
37474
37417
  if (existsSync15(path4))
37475
37418
  return path4;
37476
37419
  }
@@ -37706,7 +37649,7 @@ async function isRegistryRunning(cfg) {
37706
37649
  }
37707
37650
  async function upStack(inputs) {
37708
37651
  const { cfg } = inputs;
37709
- const workDir = join17(tmpdir2(), "arc-deploy", `stack-${Date.now()}`);
37652
+ const workDir = join18(tmpdir2(), "arc-deploy", `stack-${Date.now()}`);
37710
37653
  mkdirSync12(workDir, { recursive: true });
37711
37654
  await assertRegistryDnsResolves(cfg);
37712
37655
  const password = process.env[cfg.registry.passwordEnv];
@@ -37714,9 +37657,9 @@ async function upStack(inputs) {
37714
37657
  throw new Error(`Registry password env var ${cfg.registry.passwordEnv} is not set. ` + `Set it (e.g. \`export ${cfg.registry.passwordEnv}=...\`) before bootstrap.`);
37715
37658
  }
37716
37659
  const htpasswdLine = await generateHtpasswd(cfg.registry.username, password);
37717
- writeFileSync13(join17(workDir, "htpasswd"), htpasswdLine);
37718
- writeFileSync13(join17(workDir, "Caddyfile"), generateCaddyfile(cfg));
37719
- 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 }));
37720
37663
  let observabilityFiles = null;
37721
37664
  let observabilityHtpasswd = null;
37722
37665
  if (cfg.observability?.enabled) {
@@ -37728,30 +37671,30 @@ async function upStack(inputs) {
37728
37671
  }
37729
37672
  observabilityHtpasswd = await generateCaddyBasicAuthLine("admin", adminPassword);
37730
37673
  for (const [relPath, contents] of Object.entries(observabilityFiles)) {
37731
- const fullPath = join17(workDir, relPath);
37674
+ const fullPath = join18(workDir, relPath);
37732
37675
  mkdirSync12(dirname9(fullPath), { recursive: true });
37733
37676
  writeFileSync13(fullPath, contents);
37734
37677
  }
37735
- writeFileSync13(join17(workDir, "observability-htpasswd"), observabilityHtpasswd);
37678
+ writeFileSync13(join18(workDir, "observability-htpasswd"), observabilityHtpasswd);
37736
37679
  }
37737
37680
  await assertExec(cfg.target, `sudo mkdir -p ${cfg.target.remoteDir} && sudo chown ${cfg.target.user}:${cfg.target.user} ${cfg.target.remoteDir}`);
37738
37681
  for (const name of Object.keys(cfg.envs)) {
37739
37682
  await assertExec(cfg.target, `mkdir -p ${cfg.target.remoteDir}/${name}`);
37740
37683
  }
37741
37684
  await assertExec(cfg.target, `mkdir -p ${cfg.target.remoteDir}/registry-auth`);
37742
- await scpUpload(cfg.target, join17(workDir, "Caddyfile"), `${cfg.target.remoteDir}/Caddyfile`);
37743
- await scpUpload(cfg.target, join17(workDir, "docker-compose.yml"), `${cfg.target.remoteDir}/docker-compose.yml`);
37744
- 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`);
37745
37688
  if (observabilityFiles && observabilityHtpasswd) {
37746
37689
  await assertExec(cfg.target, `mkdir -p ${cfg.target.remoteDir}/observability/grafana-dashboards`);
37747
37690
  for (const relPath of Object.keys(observabilityFiles)) {
37748
- const localDir = dirname9(join17(workDir, relPath));
37691
+ const localDir = dirname9(join18(workDir, relPath));
37749
37692
  mkdirSync12(localDir, { recursive: true });
37750
37693
  }
37751
37694
  for (const relPath of Object.keys(observabilityFiles)) {
37752
- await scpUpload(cfg.target, join17(workDir, relPath), `${cfg.target.remoteDir}/${relPath}`);
37695
+ await scpUpload(cfg.target, join18(workDir, relPath), `${cfg.target.remoteDir}/${relPath}`);
37753
37696
  }
37754
- 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`);
37755
37698
  }
37756
37699
  await assertExec(cfg.target, `touch ${cfg.target.remoteDir}/.env`);
37757
37700
  if (cfg.observability?.enabled) {
@@ -37919,12 +37862,12 @@ import {
37919
37862
  copyFileSync as copyFileSync2,
37920
37863
  existsSync as existsSync16,
37921
37864
  mkdirSync as mkdirSync13,
37922
- readFileSync as readFileSync13,
37865
+ readFileSync as readFileSync14,
37923
37866
  realpathSync as realpathSync2,
37924
37867
  writeFileSync as writeFileSync14
37925
37868
  } from "fs";
37926
37869
  import { tmpdir as tmpdir3 } from "os";
37927
- import { dirname as dirname10, join as join18 } from "path";
37870
+ import { dirname as dirname10, join as join19 } from "path";
37928
37871
  import { fileURLToPath as fileURLToPath7 } from "url";
37929
37872
 
37930
37873
  // src/deploy/image-template.ts
@@ -37974,7 +37917,7 @@ function generateDockerfile(inputs) {
37974
37917
  // src/deploy/image.ts
37975
37918
  async function buildImage(ws, opts) {
37976
37919
  await ensureDocker();
37977
- const manifestPath = join18(ws.arcDir, "manifest.json");
37920
+ const manifestPath = join19(ws.arcDir, "manifest.json");
37978
37921
  if (!existsSync16(manifestPath)) {
37979
37922
  throw new Error(`No build manifest at ${manifestPath}. Run \`arc platform build\` first or omit --skip-build.`);
37980
37923
  }
@@ -37985,9 +37928,9 @@ async function buildImage(ws, opts) {
37985
37928
  const dockerfileInputs = collectDockerfileInputs(ws);
37986
37929
  const dockerfile = generateDockerfile(dockerfileInputs);
37987
37930
  const buildContextDir = ws.rootDir;
37988
- const dockerfileDir = join18(tmpdir3(), `arc-image-${Date.now()}`);
37931
+ const dockerfileDir = join19(tmpdir3(), `arc-image-${Date.now()}`);
37989
37932
  mkdirSync13(dockerfileDir, { recursive: true });
37990
- const dockerfilePath = join18(dockerfileDir, "Dockerfile");
37933
+ const dockerfilePath = join19(dockerfileDir, "Dockerfile");
37991
37934
  writeFileSync14(dockerfilePath, dockerfile);
37992
37935
  const buildArgs = [
37993
37936
  "build",
@@ -38013,19 +37956,19 @@ async function buildImage(ws, opts) {
38013
37956
  }
38014
37957
  function embedCliBundle(ws) {
38015
37958
  const source = locateCliBundle();
38016
- const target = join18(ws.arcDir, "host.js");
37959
+ const target = join19(ws.arcDir, "host.js");
38017
37960
  copyFileSync2(source, target);
38018
37961
  }
38019
37962
  function locateCliBundle() {
38020
37963
  const here = fileURLToPath7(import.meta.url);
38021
37964
  let cur = dirname10(here);
38022
37965
  while (cur !== "/" && cur !== "") {
38023
- const candidate = join18(cur, "package.json");
37966
+ const candidate = join19(cur, "package.json");
38024
37967
  if (existsSync16(candidate)) {
38025
37968
  try {
38026
- const pkg = JSON.parse(readFileSync13(candidate, "utf-8"));
37969
+ const pkg = JSON.parse(readFileSync14(candidate, "utf-8"));
38027
37970
  if (pkg.name === "@arcote.tech/arc-cli") {
38028
- const distIndex = join18(realpathSync2(cur), "dist", "index.js");
37971
+ const distIndex = join19(realpathSync2(cur), "dist", "index.js");
38029
37972
  if (!existsSync16(distIndex)) {
38030
37973
  throw new Error(`arc-cli bundle missing at ${distIndex}. Run \`bun run build\` in packages/cli/.`);
38031
37974
  }
@@ -38058,17 +38001,17 @@ async function ensureDocker() {
38058
38001
  }
38059
38002
  }
38060
38003
  function computeContentHash(manifestPath) {
38061
- const raw = JSON.parse(readFileSync13(manifestPath, "utf-8"));
38004
+ const raw = JSON.parse(readFileSync14(manifestPath, "utf-8"));
38062
38005
  delete raw.buildTime;
38063
38006
  const canonical = JSON.stringify(raw);
38064
38007
  return createHash3("sha256").update(canonical).digest("hex").slice(0, 12);
38065
38008
  }
38066
38009
  function collectDockerfileInputs(ws) {
38067
- const hasPublicDir = existsSync16(join18(ws.rootDir, "public"));
38068
- const hasLocales = existsSync16(join18(ws.rootDir, "locales"));
38010
+ const hasPublicDir = existsSync16(join19(ws.rootDir, "public"));
38011
+ const hasLocales = existsSync16(join19(ws.rootDir, "locales"));
38069
38012
  let manifestPath;
38070
38013
  for (const name of ["manifest.webmanifest", "manifest.json"]) {
38071
- if (existsSync16(join18(ws.rootDir, name))) {
38014
+ if (existsSync16(join19(ws.rootDir, name))) {
38072
38015
  manifestPath = name;
38073
38016
  break;
38074
38017
  }
@@ -38897,7 +38840,7 @@ async function platformDeploy(envArg, options = {}) {
38897
38840
  err(`Unknown env "${envArg}". Known: ${Object.keys(cfg.envs).join(", ")}`);
38898
38841
  process.exit(1);
38899
38842
  })() : Object.keys(cfg.envs);
38900
- const manifestPath = join19(ws.arcDir, "manifest.json");
38843
+ const manifestPath = join20(ws.arcDir, "manifest.json");
38901
38844
  if (!options.imageTag) {
38902
38845
  const needBuild = options.rebuild || !existsSync17(manifestPath);
38903
38846
  if (needBuild && !options.skipBuild) {
@@ -38970,9 +38913,9 @@ function readCliVersion2() {
38970
38913
  let cur = dirname11(fileURLToPath8(import.meta.url));
38971
38914
  const root = dirname11(cur).startsWith("/") ? "/" : ".";
38972
38915
  while (cur !== root && cur !== "") {
38973
- const candidate = join19(cur, "package.json");
38916
+ const candidate = join20(cur, "package.json");
38974
38917
  if (existsSync17(candidate)) {
38975
- const pkg = JSON.parse(readFileSync14(candidate, "utf-8"));
38918
+ const pkg = JSON.parse(readFileSync15(candidate, "utf-8"));
38976
38919
  if (pkg.name === "@arcote.tech/arc-cli") {
38977
38920
  return pkg.version ?? "unknown";
38978
38921
  }
@@ -38988,8 +38931,8 @@ function readCliVersion2() {
38988
38931
  }
38989
38932
  }
38990
38933
  async function hashDeployConfig(rootDir) {
38991
- const p2 = join19(rootDir, "deploy.arc.json");
38992
- const content = readFileSync14(p2);
38934
+ const p2 = join20(rootDir, "deploy.arc.json");
38935
+ const content = readFileSync15(p2);
38993
38936
  const hasher = new Bun.CryptoHasher("sha256");
38994
38937
  hasher.update(content);
38995
38938
  hasher.update(readCliVersion2());
@@ -38997,8 +38940,8 @@ async function hashDeployConfig(rootDir) {
38997
38940
  }
38998
38941
 
38999
38942
  // src/platform/startup.ts
39000
- import { existsSync as existsSync19, readFileSync as readFileSync15, watch } from "fs";
39001
- import { join as join21 } from "path";
38943
+ import { existsSync as existsSync19, readFileSync as readFileSync16, watch } from "fs";
38944
+ import { join as join22 } from "path";
39002
38945
 
39003
38946
  // ../host/src/create-server.ts
39004
38947
  var import_jsonwebtoken = __toESM(require_jsonwebtoken(), 1);
@@ -40055,66 +39998,14 @@ function arcHttpHandlers(ch, cm) {
40055
39998
  ];
40056
39999
  }
40057
40000
  // ../host/src/middleware/ws.ts
40058
- import {
40059
- ScopedModel as ScopedModel3,
40060
- ScopedStore,
40061
- checkItemMatchesWhere
40062
- } from "@arcote.tech/arc";
40063
- var viewSubscribers = new Map;
40001
+ import { LiveQuery } from "@arcote.tech/arc";
40002
+ var clientQuerySubs = new Map;
40064
40003
  function cleanupClientSubs(clientId) {
40065
- const prefix = `${clientId}:`;
40066
- for (const subs of viewSubscribers.values()) {
40067
- for (const key of subs.keys()) {
40068
- if (key.startsWith(prefix))
40069
- subs.delete(key);
40070
- }
40071
- }
40072
- }
40073
- function filterChangeForSubscriber(change, restrictions) {
40074
- const newMatches = change.newRow !== null && (restrictions === null || checkItemMatchesWhere(change.newRow, restrictions));
40075
- if (newMatches) {
40076
- return { type: "set", id: change.id, item: change.newRow };
40077
- }
40078
- const oldMatches = change.oldRow !== null && (restrictions === null || checkItemMatchesWhere(change.oldRow, restrictions));
40079
- if (oldMatches) {
40080
- return { type: "delete", id: change.id, item: null };
40081
- }
40082
- return null;
40083
- }
40084
- function broadcastViewChanges(committed, connectionManager) {
40085
- const byStore = new Map;
40086
- for (const change of committed) {
40087
- let list = byStore.get(change.store);
40088
- if (!list) {
40089
- list = [];
40090
- byStore.set(change.store, list);
40091
- }
40092
- list.push(change);
40093
- }
40094
- for (const [viewName, changes] of byStore) {
40095
- const subs = viewSubscribers.get(viewName);
40096
- if (!subs || subs.size === 0)
40097
- continue;
40098
- for (const sub of subs.values()) {
40099
- const filtered = [];
40100
- for (const change of changes) {
40101
- const wireChange = filterChangeForSubscriber(change, sub.restrictions);
40102
- if (wireChange)
40103
- filtered.push(wireChange);
40104
- }
40105
- if (filtered.length === 0)
40106
- continue;
40107
- if (sub.buffer) {
40108
- sub.buffer.push(...filtered);
40109
- continue;
40110
- }
40111
- connectionManager.sendToClient(sub.clientId, {
40112
- type: "view-changes",
40113
- element: viewName,
40114
- scope: sub.scope,
40115
- changes: filtered
40116
- });
40117
- }
40004
+ const subs = clientQuerySubs.get(clientId);
40005
+ if (subs) {
40006
+ for (const live of subs.values())
40007
+ live.stop();
40008
+ clientQuerySubs.delete(clientId);
40118
40009
  }
40119
40010
  }
40120
40011
  function scopeAuthHandler() {
@@ -40213,82 +40104,62 @@ function executeCommandHandler() {
40213
40104
  return true;
40214
40105
  };
40215
40106
  }
40216
- function viewSubscriptionHandler() {
40107
+ function querySubscriptionHandler() {
40217
40108
  return async (client, message, ctx) => {
40218
- if (message.type === "subscribe-view") {
40219
- const { element: elementName } = message;
40109
+ if (message.type === "subscribe-query") {
40110
+ const { subscriptionId, descriptor } = message;
40220
40111
  const scope = message.scope ?? "default";
40221
40112
  const scopeToken = client.scopeTokens.get(scope) ?? null;
40222
40113
  let rawToken = scopeToken?.raw ?? null;
40223
40114
  if (!rawToken && client.scopeTokens.size > 0) {
40224
40115
  rawToken = client.scopeTokens.values().next().value.raw;
40225
40116
  }
40226
- const scoped = new ScopedModel3(ctx.contextHandler.getModel(), scope);
40227
- if (rawToken)
40228
- scoped.setToken(rawToken);
40229
- const element = ctx.contextHandler.getModel().context.get(elementName);
40230
- if (!element || typeof element.getRestrictionsFor !== "function") {
40231
- ctx.connectionManager.sendToClient(client.id, {
40232
- type: "error",
40233
- message: `Cannot subscribe to view "${elementName}": unknown element`
40234
- });
40235
- return true;
40236
- }
40237
- const { restrictions, denied } = element.getRestrictionsFor(scoped.getAdapters());
40238
- const subKey = `${client.id}:${scope}`;
40239
- if (denied) {
40240
- viewSubscribers.get(elementName)?.delete(subKey);
40241
- ctx.connectionManager.sendToClient(client.id, {
40242
- type: "view-snapshot",
40243
- element: elementName,
40244
- scope,
40245
- items: []
40246
- });
40247
- return true;
40248
- }
40249
- const subscriber = {
40250
- clientId: client.id,
40251
- scope,
40252
- restrictions,
40253
- buffer: []
40254
- };
40255
- if (!viewSubscribers.has(elementName)) {
40256
- viewSubscribers.set(elementName, new Map);
40117
+ clientQuerySubs.get(client.id)?.get(subscriptionId)?.stop();
40118
+ const live = new LiveQuery(ctx.contextHandler.getModel(), descriptor, scope, rawToken, (update) => {
40119
+ if (update.type === "changes") {
40120
+ ctx.connectionManager.sendToClient(client.id, {
40121
+ type: "query-changes",
40122
+ subscriptionId,
40123
+ changes: update.changes
40124
+ });
40125
+ } else {
40126
+ ctx.connectionManager.sendToClient(client.id, {
40127
+ type: "query-snapshot",
40128
+ subscriptionId,
40129
+ result: update.result ?? null
40130
+ });
40131
+ }
40132
+ });
40133
+ if (!clientQuerySubs.has(client.id)) {
40134
+ clientQuerySubs.set(client.id, new Map);
40257
40135
  }
40258
- viewSubscribers.get(elementName).set(subKey, subscriber);
40136
+ clientQuerySubs.get(client.id).set(subscriptionId, live);
40259
40137
  try {
40260
- const store = ctx.contextHandler.getDataStorage().getStore(elementName);
40261
- const items = restrictions ? await new ScopedStore(store, restrictions, false).find({}) : await store.find({});
40138
+ const result = await live.start();
40262
40139
  ctx.connectionManager.sendToClient(client.id, {
40263
- type: "view-snapshot",
40264
- element: elementName,
40265
- scope,
40266
- items
40140
+ type: "query-snapshot",
40141
+ subscriptionId,
40142
+ result: result ?? null
40267
40143
  });
40144
+ live.flush();
40268
40145
  } catch (err3) {
40269
- viewSubscribers.get(elementName)?.delete(subKey);
40270
- console.error(`[Arc] View subscription error (${elementName}):`, err3);
40146
+ live.stop();
40147
+ clientQuerySubs.get(client.id)?.delete(subscriptionId);
40148
+ console.error(`[Arc] Query subscription error (${descriptor?.element}.${descriptor?.method}):`, err3);
40271
40149
  ctx.connectionManager.sendToClient(client.id, {
40272
40150
  type: "error",
40273
- message: `Failed to subscribe to view "${elementName}"`
40274
- });
40275
- return true;
40276
- }
40277
- const buffered = subscriber.buffer;
40278
- subscriber.buffer = null;
40279
- if (buffered.length > 0) {
40280
- ctx.connectionManager.sendToClient(client.id, {
40281
- type: "view-changes",
40282
- element: elementName,
40283
- scope,
40284
- changes: buffered
40151
+ message: `Failed to subscribe to query "${descriptor?.element}.${descriptor?.method}"`
40285
40152
  });
40286
40153
  }
40287
40154
  return true;
40288
40155
  }
40289
- if (message.type === "unsubscribe-view") {
40290
- const scope = message.scope ?? "default";
40291
- viewSubscribers.get(message.element)?.delete(`${client.id}:${scope}`);
40156
+ if (message.type === "unsubscribe-query") {
40157
+ const subs = clientQuerySubs.get(client.id);
40158
+ const live = subs?.get(message.subscriptionId);
40159
+ if (live) {
40160
+ live.stop();
40161
+ subs.delete(message.subscriptionId);
40162
+ }
40292
40163
  return true;
40293
40164
  }
40294
40165
  return false;
@@ -40300,7 +40171,7 @@ function arcWsHandlers() {
40300
40171
  syncEventsHandler(),
40301
40172
  requestSyncHandler(),
40302
40173
  executeCommandHandler(),
40303
- viewSubscriptionHandler()
40174
+ querySubscriptionHandler()
40304
40175
  ];
40305
40176
  }
40306
40177
  // ../host/src/create-server.ts
@@ -40313,7 +40184,6 @@ async function createArcServer(config) {
40313
40184
  const cronScheduler = new CronScheduler(contextHandler);
40314
40185
  cronScheduler.start();
40315
40186
  const connectionManager = new ConnectionManager;
40316
- contextHandler.getEventPublisher().onViewChanges((changes) => broadcastViewChanges(changes, connectionManager));
40317
40187
  function buildCorsHeaders(req) {
40318
40188
  const origin = req?.headers.get("Origin") || "*";
40319
40189
  return {
@@ -40491,7 +40361,7 @@ async function createArcServer(config) {
40491
40361
  // src/platform/server.ts
40492
40362
  init_i18n();
40493
40363
  import { existsSync as existsSync18, mkdirSync as mkdirSync14 } from "fs";
40494
- import { join as join20 } from "path";
40364
+ import { join as join21 } from "path";
40495
40365
  async function resolveDbAdapterFactory(dbPath) {
40496
40366
  const databaseUrl = process.env.DATABASE_URL;
40497
40367
  if (databaseUrl && databaseUrl.length > 0) {
@@ -40623,15 +40493,13 @@ function parseArcTokensHeader(header) {
40623
40493
  return payloads;
40624
40494
  }
40625
40495
  async function filterManifestForTokens(manifest, moduleAccessMap, tokenPayloads) {
40626
- const allowedGroups = new Set;
40496
+ const tokensByName = new Map;
40627
40497
  for (const t of tokenPayloads) {
40628
40498
  if (t?.tokenType)
40629
- allowedGroups.add(t.tokenType);
40499
+ tokensByName.set(t.tokenType, t);
40630
40500
  }
40631
40501
  const filteredGroups = {};
40632
40502
  for (const [name, group] of Object.entries(manifest.groups)) {
40633
- if (!allowedGroups.has(name))
40634
- continue;
40635
40503
  let allGranted = true;
40636
40504
  for (const moduleName of group.modules) {
40637
40505
  const access = moduleAccessMap.get(moduleName);
@@ -40639,9 +40507,7 @@ async function filterManifestForTokens(manifest, moduleAccessMap, tokenPayloads)
40639
40507
  continue;
40640
40508
  let granted = false;
40641
40509
  for (const rule of access.rules) {
40642
- if (rule.token.name !== name)
40643
- continue;
40644
- const matching = tokenPayloads.find((t) => t.tokenType === name);
40510
+ const matching = tokensByName.get(rule.token.name);
40645
40511
  if (!matching)
40646
40512
  continue;
40647
40513
  granted = rule.check ? await rule.check(matching) : true;
@@ -40682,28 +40548,28 @@ function staticFilesHandler(ws, devMode, getManifest) {
40682
40548
  return new Response("Forbidden", { status: 403, headers: ctx.corsHeaders });
40683
40549
  }
40684
40550
  }
40685
- return serveFile(join20(ws.browserDir, file), {
40551
+ return serveFile(join21(ws.browserDir, file), {
40686
40552
  ...ctx.corsHeaders,
40687
40553
  "Cache-Control": devMode ? "no-cache" : "max-age=31536000,immutable"
40688
40554
  });
40689
40555
  }
40690
40556
  if (path4.startsWith("/locales/"))
40691
- return serveFile(join20(ws.arcDir, path4.slice(1)), {
40557
+ return serveFile(join21(ws.arcDir, path4.slice(1)), {
40692
40558
  ...ctx.corsHeaders,
40693
40559
  "Cache-Control": devMode ? "no-cache" : "max-age=300,stale-while-revalidate=3600"
40694
40560
  });
40695
40561
  if (path4.startsWith("/assets/"))
40696
- return serveFile(join20(ws.assetsDir, path4.slice(8)), {
40562
+ return serveFile(join21(ws.assetsDir, path4.slice(8)), {
40697
40563
  ...ctx.corsHeaders,
40698
40564
  "Cache-Control": devMode ? "no-cache" : "max-age=31536000,immutable"
40699
40565
  });
40700
40566
  if (path4 === "/styles.css")
40701
- return serveFile(join20(ws.arcDir, "styles.css"), {
40567
+ return serveFile(join21(ws.arcDir, "styles.css"), {
40702
40568
  ...ctx.corsHeaders,
40703
40569
  "Cache-Control": devMode ? "no-cache" : "max-age=31536000,immutable"
40704
40570
  });
40705
40571
  if (path4 === "/theme.css")
40706
- return serveFile(join20(ws.arcDir, "theme.css"), {
40572
+ return serveFile(join21(ws.arcDir, "theme.css"), {
40707
40573
  ...ctx.corsHeaders,
40708
40574
  "Cache-Control": devMode ? "no-cache" : "max-age=31536000,immutable"
40709
40575
  });
@@ -40711,7 +40577,7 @@ function staticFilesHandler(ws, devMode, getManifest) {
40711
40577
  return serveFile(ws.manifest.path, ctx.corsHeaders);
40712
40578
  }
40713
40579
  if (path4.lastIndexOf(".") > path4.lastIndexOf("/")) {
40714
- const publicFile = join20(ws.publicDir, path4.slice(1));
40580
+ const publicFile = join21(ws.publicDir, path4.slice(1));
40715
40581
  if (existsSync18(publicFile))
40716
40582
  return serveFile(publicFile, ctx.corsHeaders);
40717
40583
  }
@@ -40866,7 +40732,7 @@ async function startPlatformServer(opts) {
40866
40732
  stop: () => server.stop()
40867
40733
  };
40868
40734
  }
40869
- const dbPath = opts.dbPath || join20(ws.arcDir, "data", "arc.db");
40735
+ const dbPath = opts.dbPath || join21(ws.arcDir, "data", "arc.db");
40870
40736
  const baseDbFactory = await resolveDbAdapterFactory(dbPath);
40871
40737
  let dbAdapterFactory = baseDbFactory;
40872
40738
  if (telemetry) {
@@ -40907,17 +40773,17 @@ async function startPlatformServer(opts) {
40907
40773
  async function startPlatform(opts) {
40908
40774
  const { ws, devMode } = opts;
40909
40775
  const port = opts.port ?? parseInt(process.env.PORT || "5005", 10);
40910
- 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");
40911
40777
  let manifest;
40912
40778
  if (devMode) {
40913
40779
  manifest = await buildAll(ws);
40914
40780
  } else {
40915
- const manifestPath = join21(ws.arcDir, "manifest.json");
40781
+ const manifestPath = join22(ws.arcDir, "manifest.json");
40916
40782
  if (!existsSync19(manifestPath)) {
40917
40783
  err("No build found. Run `arc platform build` first.");
40918
40784
  process.exit(1);
40919
40785
  }
40920
- manifest = JSON.parse(readFileSync15(manifestPath, "utf-8"));
40786
+ manifest = JSON.parse(readFileSync16(manifestPath, "utf-8"));
40921
40787
  }
40922
40788
  log2("Loading server context...");
40923
40789
  const { context: context2, moduleAccess } = await loadServerContext(ws);
@@ -40993,7 +40859,7 @@ function attachDevWatcher(ws, platform3) {
40993
40859
  }, 300);
40994
40860
  };
40995
40861
  for (const pkg of ws.packages) {
40996
- const srcDir = join21(pkg.path, "src");
40862
+ const srcDir = join22(pkg.path, "src");
40997
40863
  if (!existsSync19(srcDir))
40998
40864
  continue;
40999
40865
  watch(srcDir, { recursive: true }, (_event, filename) => {
@@ -41004,7 +40870,7 @@ function attachDevWatcher(ws, platform3) {
41004
40870
  triggerRebuild();
41005
40871
  });
41006
40872
  }
41007
- const localesDir = join21(ws.rootDir, "locales");
40873
+ const localesDir = join22(ws.rootDir, "locales");
41008
40874
  if (existsSync19(localesDir)) {
41009
40875
  watch(localesDir, { recursive: false }, (_event, filename) => {
41010
40876
  if (!filename?.endsWith(".po"))