@arcote.tech/arc-host 0.7.14 → 0.7.16

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
@@ -4677,88 +4677,6 @@ function queryHandler(ch) {
4677
4677
  }
4678
4678
  };
4679
4679
  }
4680
- var streamIdCounter = 0;
4681
- var streamConnections = new Map;
4682
- function streamHandler(ch) {
4683
- return (_req, url, ctx) => {
4684
- if (!url.pathname.startsWith("/stream/") || _req.method !== "GET")
4685
- return null;
4686
- const viewName = url.pathname.split("/stream/")[1];
4687
- if (!viewName) {
4688
- return new Response("Invalid stream path", {
4689
- status: 400,
4690
- headers: ctx.corsHeaders
4691
- });
4692
- }
4693
- const findOptions = {};
4694
- const whereParam = url.searchParams.get("where");
4695
- if (whereParam) {
4696
- try {
4697
- findOptions.where = JSON.parse(whereParam);
4698
- } catch {
4699
- return new Response("Invalid 'where' parameter", {
4700
- status: 400,
4701
- headers: ctx.corsHeaders
4702
- });
4703
- }
4704
- }
4705
- const orderByParam = url.searchParams.get("orderBy");
4706
- if (orderByParam) {
4707
- try {
4708
- findOptions.orderBy = JSON.parse(orderByParam);
4709
- } catch {
4710
- return new Response("Invalid 'orderBy' parameter", {
4711
- status: 400,
4712
- headers: ctx.corsHeaders
4713
- });
4714
- }
4715
- }
4716
- const limitParam = url.searchParams.get("limit");
4717
- if (limitParam)
4718
- findOptions.limit = parseInt(limitParam, 10);
4719
- const streamId = `stream_${++streamIdCounter}_${Date.now()}`;
4720
- const rawToken = ctx.rawToken;
4721
- const stream = new ReadableStream({
4722
- start(controller) {
4723
- const scoped = new ScopedModel2(ch.getModel(), "stream");
4724
- if (rawToken)
4725
- scoped.setToken(rawToken);
4726
- const descriptor = { element: viewName, method: "find", args: [findOptions] };
4727
- const sendData = async () => {
4728
- try {
4729
- const data = await scoped.callQuery(descriptor);
4730
- controller.enqueue(new TextEncoder().encode(`data: ${JSON.stringify({ type: "data", data })}
4731
-
4732
- `));
4733
- } catch {
4734
- unsubscribe();
4735
- }
4736
- };
4737
- sendData();
4738
- const unsubscribe = ch.getEventPublisher().subscribe("*", () => sendData());
4739
- streamConnections.set(streamId, { id: streamId, controller, unsubscribe });
4740
- controller.enqueue(new TextEncoder().encode(`data: ${JSON.stringify({ type: "connected", streamId })}
4741
-
4742
- `));
4743
- },
4744
- cancel() {
4745
- const conn = streamConnections.get(streamId);
4746
- if (conn) {
4747
- conn.unsubscribe();
4748
- streamConnections.delete(streamId);
4749
- }
4750
- }
4751
- });
4752
- return new Response(stream, {
4753
- headers: {
4754
- ...ctx.corsHeaders,
4755
- "Content-Type": "text/event-stream",
4756
- "Cache-Control": "no-cache",
4757
- Connection: "keep-alive"
4758
- }
4759
- });
4760
- };
4761
- }
4762
4680
  function eventSyncHandler(ch) {
4763
4681
  return async (req, url, ctx) => {
4764
4682
  if (url.pathname !== "/sync/events" || req.method !== "POST")
@@ -4859,25 +4777,19 @@ function arcHttpHandlers(ch, cm) {
4859
4777
  healthHandler(cm),
4860
4778
  commandHandler(ch),
4861
4779
  queryHandler(ch),
4862
- streamHandler(ch),
4863
4780
  eventSyncHandler(ch),
4864
4781
  routeHandler(ch)
4865
4782
  ];
4866
4783
  }
4867
- function cleanupStreams() {
4868
- for (const conn of streamConnections.values())
4869
- conn.unsubscribe();
4870
- streamConnections.clear();
4871
- }
4872
4784
  // src/middleware/ws.ts
4873
- import { ScopedModel as ScopedModel3 } from "@arcote.tech/arc";
4874
- var clientViewSubs = new Map;
4785
+ import { LiveQuery } from "@arcote.tech/arc";
4786
+ var clientQuerySubs = new Map;
4875
4787
  function cleanupClientSubs(clientId) {
4876
- const subs = clientViewSubs.get(clientId);
4788
+ const subs = clientQuerySubs.get(clientId);
4877
4789
  if (subs) {
4878
- for (const unsub of subs.values())
4879
- unsub();
4880
- clientViewSubs.delete(clientId);
4790
+ for (const live of subs.values())
4791
+ live.stop();
4792
+ clientQuerySubs.delete(clientId);
4881
4793
  }
4882
4794
  }
4883
4795
  function scopeAuthHandler() {
@@ -4903,6 +4815,8 @@ function syncEventsHandler() {
4903
4815
  for (const c of ctx.connectionManager.getAllClients()) {
4904
4816
  if (c.id === client.id)
4905
4817
  continue;
4818
+ if (!c.wantsEventSync)
4819
+ continue;
4906
4820
  const clientTokens = ctx.connectionManager.getAllScopeTokens(c.id);
4907
4821
  const authorized = filterEventsForTokens(clientTokens, persisted, ctx.contextHandler.getEventDefinitions());
4908
4822
  if (authorized.length > 0) {
@@ -4925,6 +4839,7 @@ function requestSyncHandler() {
4925
4839
  return async (client, message, ctx) => {
4926
4840
  if (message.type !== "request-sync")
4927
4841
  return false;
4842
+ client.wantsEventSync = true;
4928
4843
  const allTokens = ctx.connectionManager.getAllScopeTokens(client.id);
4929
4844
  const token = allTokens.length > 0 ? allTokens[0] : null;
4930
4845
  const events = await ctx.contextHandler.getEventsSince(message.lastHostEventId, token);
@@ -4976,70 +4891,58 @@ function executeCommandHandler() {
4976
4891
  function querySubscriptionHandler() {
4977
4892
  return async (client, message, ctx) => {
4978
4893
  if (message.type === "subscribe-query") {
4979
- const { subscriptionId, descriptor, scope } = message;
4980
- const scopeToken = scope ? client.scopeTokens.get(scope) : null;
4894
+ const { subscriptionId, descriptor } = message;
4895
+ const scope = message.scope ?? "default";
4896
+ const scopeToken = client.scopeTokens.get(scope) ?? null;
4981
4897
  let rawToken = scopeToken?.raw ?? null;
4982
4898
  if (!rawToken && client.scopeTokens.size > 0) {
4983
4899
  rawToken = client.scopeTokens.values().next().value.raw;
4984
4900
  }
4985
- const scoped = new ScopedModel3(ctx.contextHandler.getModel(), scope ?? "default");
4986
- if (rawToken)
4987
- scoped.setToken(rawToken);
4988
- let lastResultJson = "";
4989
- const sendData = async () => {
4990
- try {
4991
- const data = await scoped.callQuery(descriptor);
4992
- const json = JSON.stringify(data ?? null);
4993
- if (json === lastResultJson)
4994
- return;
4995
- lastResultJson = json;
4901
+ clientQuerySubs.get(client.id)?.get(subscriptionId)?.stop();
4902
+ const live = new LiveQuery(ctx.contextHandler.getModel(), descriptor, scope, rawToken, (update) => {
4903
+ if (update.type === "changes") {
4996
4904
  ctx.connectionManager.sendToClient(client.id, {
4997
- type: "query-data",
4905
+ type: "query-changes",
4998
4906
  subscriptionId,
4999
- data: data ?? null
4907
+ changes: update.changes
4908
+ });
4909
+ } else {
4910
+ ctx.connectionManager.sendToClient(client.id, {
4911
+ type: "query-snapshot",
4912
+ subscriptionId,
4913
+ result: update.result ?? null
5000
4914
  });
5001
- } catch (err) {
5002
- console.error(`[Arc] Query subscription error:`, err);
5003
- }
5004
- };
5005
- sendData();
5006
- const element = ctx.contextHandler.getModel().context.get(descriptor.element);
5007
- const handlers = element?.getHandlers?.();
5008
- const eventTypes = handlers ? Object.keys(handlers) : [];
5009
- let debounceTimer;
5010
- const debouncedSend = () => {
5011
- if (debounceTimer)
5012
- clearTimeout(debounceTimer);
5013
- debounceTimer = setTimeout(() => sendData(), 50);
5014
- };
5015
- const unsubscribes = [];
5016
- if (eventTypes.length > 0) {
5017
- for (const eventType of eventTypes) {
5018
- unsubscribes.push(ctx.contextHandler.getEventPublisher().subscribe(eventType, debouncedSend));
5019
4915
  }
5020
- } else {
5021
- unsubscribes.push(ctx.contextHandler.getEventPublisher().subscribe("*", debouncedSend));
4916
+ });
4917
+ if (!clientQuerySubs.has(client.id)) {
4918
+ clientQuerySubs.set(client.id, new Map);
5022
4919
  }
5023
- const cleanup = () => {
5024
- if (debounceTimer)
5025
- clearTimeout(debounceTimer);
5026
- for (const unsub of unsubscribes)
5027
- unsub();
5028
- };
5029
- if (!clientViewSubs.has(client.id)) {
5030
- clientViewSubs.set(client.id, new Map);
4920
+ clientQuerySubs.get(client.id).set(subscriptionId, live);
4921
+ try {
4922
+ const result = await live.start();
4923
+ ctx.connectionManager.sendToClient(client.id, {
4924
+ type: "query-snapshot",
4925
+ subscriptionId,
4926
+ result: result ?? null
4927
+ });
4928
+ live.flush();
4929
+ } catch (err) {
4930
+ live.stop();
4931
+ clientQuerySubs.get(client.id)?.delete(subscriptionId);
4932
+ console.error(`[Arc] Query subscription error (${descriptor?.element}.${descriptor?.method}):`, err);
4933
+ ctx.connectionManager.sendToClient(client.id, {
4934
+ type: "error",
4935
+ message: `Failed to subscribe to query "${descriptor?.element}.${descriptor?.method}"`
4936
+ });
5031
4937
  }
5032
- clientViewSubs.get(client.id).set(subscriptionId, cleanup);
5033
4938
  return true;
5034
4939
  }
5035
4940
  if (message.type === "unsubscribe-query") {
5036
- const subs = clientViewSubs.get(client.id);
5037
- if (subs) {
5038
- const unsub = subs.get(message.subscriptionId);
5039
- if (unsub) {
5040
- unsub();
5041
- subs.delete(message.subscriptionId);
5042
- }
4941
+ const subs = clientQuerySubs.get(client.id);
4942
+ const live = subs?.get(message.subscriptionId);
4943
+ if (live) {
4944
+ live.stop();
4945
+ subs.delete(message.subscriptionId);
5043
4946
  }
5044
4947
  return true;
5045
4948
  }
@@ -5235,7 +5138,6 @@ async function createArcServer(config) {
5235
5138
  cronScheduler,
5236
5139
  stop: () => {
5237
5140
  cronScheduler.stop();
5238
- cleanupStreams();
5239
5141
  server.stop();
5240
5142
  }
5241
5143
  };
@@ -5249,7 +5151,6 @@ function hostLiveModel(context, dbAdapterFactory) {
5249
5151
  }
5250
5152
  export {
5251
5153
  syncEventsHandler,
5252
- streamHandler,
5253
5154
  scopeAuthHandler,
5254
5155
  routeHandler,
5255
5156
  requestSyncHandler,
@@ -5263,7 +5164,6 @@ export {
5263
5164
  eventSyncHandler,
5264
5165
  createArcServer,
5265
5166
  commandHandler,
5266
- cleanupStreams,
5267
5167
  cleanupClientSubs,
5268
5168
  canTokenReceiveEvent,
5269
5169
  canTokenEmitEvent,
@@ -5274,5 +5174,5 @@ export {
5274
5174
  ConnectionManager
5275
5175
  };
5276
5176
 
5277
- //# debugId=D9115398B339A37F64756E2164756E21
5177
+ //# debugId=5FB48DB48B7AE0DA64756E2164756E21
5278
5178
  //# sourceMappingURL=index.js.map