@arcote.tech/arc-host 0.3.4 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/index.d.ts +1 -2
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +1024 -384
  4. package/dist/index.js.map +12 -8
  5. package/dist/src/connection-manager.d.ts +16 -1
  6. package/dist/src/connection-manager.d.ts.map +1 -1
  7. package/dist/src/context-handler.d.ts +3 -5
  8. package/dist/src/context-handler.d.ts.map +1 -1
  9. package/dist/src/create-server.d.ts +36 -0
  10. package/dist/src/create-server.d.ts.map +1 -0
  11. package/dist/src/cron-scheduler.d.ts +30 -0
  12. package/dist/src/cron-scheduler.d.ts.map +1 -0
  13. package/dist/src/event-auth.d.ts +6 -1
  14. package/dist/src/event-auth.d.ts.map +1 -1
  15. package/dist/src/index.d.ts +6 -2
  16. package/dist/src/index.d.ts.map +1 -1
  17. package/dist/src/middleware/http.d.ts +15 -0
  18. package/dist/src/middleware/http.d.ts.map +1 -0
  19. package/dist/src/middleware/index.d.ts +4 -0
  20. package/dist/src/middleware/index.d.ts.map +1 -0
  21. package/dist/src/middleware/types.d.ts +31 -0
  22. package/dist/src/middleware/types.d.ts.map +1 -0
  23. package/dist/src/middleware/ws.d.ts +9 -0
  24. package/dist/src/middleware/ws.d.ts.map +1 -0
  25. package/dist/src/types.d.ts +25 -4
  26. package/dist/src/types.d.ts.map +1 -1
  27. package/index.ts +2 -4
  28. package/package.json +2 -1
  29. package/src/connection-manager.ts +37 -7
  30. package/src/context-handler.ts +22 -23
  31. package/src/create-server.ts +213 -0
  32. package/src/cron-scheduler.ts +124 -0
  33. package/src/event-auth.ts +26 -1
  34. package/src/index.ts +39 -9
  35. package/src/middleware/http.ts +414 -0
  36. package/src/middleware/index.ts +27 -0
  37. package/src/middleware/types.ts +42 -0
  38. package/src/middleware/ws.ts +266 -0
  39. package/src/types.ts +22 -4
  40. package/dist/src/arc-host.d.ts +0 -81
  41. package/dist/src/arc-host.d.ts.map +0 -1
  42. package/src/arc-host.ts +0 -858
package/dist/index.js CHANGED
@@ -3673,20 +3673,18 @@ var require_jsonwebtoken = __commonJS((exports, module) => {
3673
3673
  };
3674
3674
  });
3675
3675
 
3676
- // src/arc-host.ts
3676
+ // src/create-server.ts
3677
3677
  var import_jsonwebtoken = __toESM(require_jsonwebtoken(), 1);
3678
- import { liveQuery } from "@arcote.tech/arc";
3679
3678
 
3680
3679
  // src/connection-manager.ts
3681
3680
  class ConnectionManager {
3682
3681
  clients = new Map;
3683
3682
  clientIdCounter = 0;
3684
- addClient(ws, token, rawToken) {
3683
+ addClient(ws) {
3685
3684
  const clientId = `client_${++this.clientIdCounter}_${Date.now()}`;
3686
3685
  const client = {
3687
3686
  id: clientId,
3688
- token,
3689
- rawToken,
3687
+ scopeTokens: new Map,
3690
3688
  lastHostEventId: null,
3691
3689
  ws
3692
3690
  };
@@ -3706,6 +3704,22 @@ class ConnectionManager {
3706
3704
  return;
3707
3705
  return this.clients.get(clientId);
3708
3706
  }
3707
+ setScopeToken(clientId, scope, decoded, raw) {
3708
+ const client = this.clients.get(clientId);
3709
+ if (client) {
3710
+ client.scopeTokens.set(scope, { decoded, raw });
3711
+ }
3712
+ }
3713
+ getScopeToken(clientId, scope) {
3714
+ const client = this.clients.get(clientId);
3715
+ return client?.scopeTokens.get(scope);
3716
+ }
3717
+ getAllScopeTokens(clientId) {
3718
+ const client = this.clients.get(clientId);
3719
+ if (!client)
3720
+ return [];
3721
+ return Array.from(client.scopeTokens.values()).map((v) => v.decoded);
3722
+ }
3709
3723
  updateLastSyncedEventId(clientId, hostEventId) {
3710
3724
  const client = this.clients.get(clientId);
3711
3725
  if (client) {
@@ -3741,10 +3755,10 @@ class ConnectionManager {
3741
3755
 
3742
3756
  // src/context-handler.ts
3743
3757
  import {
3744
- AuthAdapter,
3745
3758
  LocalEventPublisher,
3746
3759
  MasterDataStorage,
3747
3760
  Model,
3761
+ ScopedModel,
3748
3762
  mutationExecutor
3749
3763
  } from "@arcote.tech/arc";
3750
3764
 
@@ -3812,6 +3826,17 @@ function filterEventsForToken(token, events, eventDefinitions) {
3812
3826
  return canTokenReceiveEvent(token, eventDef, eventInstance);
3813
3827
  });
3814
3828
  }
3829
+ function filterEventsForTokens(tokens, events, eventDefinitions) {
3830
+ if (tokens.length === 0) {
3831
+ return filterEventsForToken(null, events, eventDefinitions);
3832
+ }
3833
+ return events.filter((eventInstance) => {
3834
+ const eventDef = eventDefinitions.get(eventInstance.type);
3835
+ if (!eventDef)
3836
+ return false;
3837
+ return tokens.some((token) => canTokenReceiveEvent(token, eventDef, eventInstance));
3838
+ });
3839
+ }
3815
3840
 
3816
3841
  // src/context-handler.ts
3817
3842
  class ContextHandler {
@@ -3819,7 +3844,6 @@ class ContextHandler {
3819
3844
  model;
3820
3845
  dataStorage;
3821
3846
  eventPublisher;
3822
- authAdapter;
3823
3847
  eventDefinitions = new Map;
3824
3848
  hostEventIdCounter = 0;
3825
3849
  initialized = false;
@@ -3827,12 +3851,10 @@ class ContextHandler {
3827
3851
  this.context = context;
3828
3852
  this.dataStorage = new MasterDataStorage(dbAdapter);
3829
3853
  this.eventPublisher = new LocalEventPublisher(this.dataStorage);
3830
- this.authAdapter = new AuthAdapter;
3831
3854
  this.model = new Model(context, {
3832
3855
  adapters: {
3833
3856
  dataStorage: this.dataStorage,
3834
- eventPublisher: this.eventPublisher,
3835
- authAdapter: this.authAdapter
3857
+ eventPublisher: this.eventPublisher
3836
3858
  },
3837
3859
  environment: "server"
3838
3860
  });
@@ -3849,21 +3871,26 @@ class ContextHandler {
3849
3871
  this.initialized = true;
3850
3872
  }
3851
3873
  async executeCommand(commandName, params, rawToken) {
3852
- const mutations = mutationExecutor(this.model);
3853
- const command = mutations[commandName];
3874
+ const scoped = new ScopedModel(this.model, "request");
3875
+ if (rawToken)
3876
+ scoped.setToken(rawToken);
3877
+ const mutations = mutationExecutor(scoped);
3878
+ let command;
3879
+ if (commandName.includes(".")) {
3880
+ const [elementName, methodName] = commandName.split(".");
3881
+ const element = mutations[elementName];
3882
+ command = element?.[methodName];
3883
+ } else {
3884
+ command = mutations[commandName];
3885
+ }
3854
3886
  if (!command) {
3855
3887
  throw new Error(`Command '${commandName}' not found`);
3856
3888
  }
3857
- this.authAdapter.setToken(rawToken);
3858
3889
  try {
3859
- return await command(params);
3890
+ const result = await command(params);
3891
+ return result;
3860
3892
  } catch (error) {
3861
- console.error(`[ARC] Command '${commandName}' failed:`);
3862
- console.error(`[ARC] Params:`, JSON.stringify(params, null, 2));
3863
- console.error(`[ARC] Error:`, error);
3864
- if (error instanceof Error) {
3865
- console.error(`[ARC] Stack:`, error.stack);
3866
- }
3893
+ console.error(`[ARC] Command '${commandName}' failed:`, error);
3867
3894
  throw error;
3868
3895
  }
3869
3896
  }
@@ -3939,315 +3966,618 @@ class ContextHandler {
3939
3966
  getEventDefinitions() {
3940
3967
  return this.eventDefinitions;
3941
3968
  }
3942
- setAuthToken(rawToken) {
3943
- this.authAdapter.setToken(rawToken);
3969
+ getEventPublisher() {
3970
+ return this.eventPublisher;
3944
3971
  }
3945
3972
  }
3946
3973
 
3947
- // src/arc-host.ts
3948
- class ArcHost {
3949
- config;
3950
- server;
3951
- connectionManager = new ConnectionManager;
3952
- contextHandler;
3953
- streamConnections = new Map;
3954
- jwtSecret;
3955
- port;
3956
- streamIdCounter = 0;
3957
- constructor(config) {
3958
- this.config = config;
3959
- this.jwtSecret = config.jwtSecret || process.env.JWT_SECRET || "arc-host-secret-change-in-production";
3960
- this.port = config.port || 5005;
3961
- }
3962
- async start() {
3963
- const dbAdapter = this.config.dbAdapterFactory(this.config.context);
3964
- this.contextHandler = new ContextHandler(this.config.context, dbAdapter);
3965
- await this.contextHandler.init();
3966
- this.setupServer();
3967
- }
3968
- verifyToken(token) {
3969
- try {
3970
- const decoded = import_jsonwebtoken.default.verify(token, this.jwtSecret);
3971
- if (decoded.tokenName && !decoded.tokenType) {
3972
- return {
3973
- tokenType: decoded.tokenName,
3974
- params: decoded.params || {},
3975
- iat: decoded.iat,
3976
- exp: decoded.exp
3977
- };
3978
- }
3979
- return decoded;
3980
- } catch {
3981
- try {
3982
- const parts = token.split(".");
3983
- if (parts.length !== 3)
3984
- return null;
3985
- const payload = JSON.parse(atob(parts[1]));
3986
- return {
3987
- tokenType: payload.tokenName,
3988
- params: payload.params || {},
3989
- iat: payload.iat,
3990
- exp: payload.exp
3991
- };
3992
- } catch {
3993
- return null;
3994
- }
3995
- }
3974
+ // ../../node_modules/croner/dist/croner.js
3975
+ function h(n, t, e, r, s, i, a, l) {
3976
+ return h.fromTZ(h.tp(n, t, e, r, s, i, a), l);
3977
+ }
3978
+ h.fromTZISO = (n, t, e) => h.fromTZ(k(n, t), e);
3979
+ h.fromTZ = function(n, t) {
3980
+ let e = new Date(Date.UTC(n.y, n.m - 1, n.d, n.h, n.i, n.s)), r = D(n.tz, e), s = new Date(e.getTime() - r), i = D(n.tz, s);
3981
+ if (i - r === 0)
3982
+ return s;
3983
+ {
3984
+ let a = new Date(e.getTime() - i), l = D(n.tz, a);
3985
+ if (l - i === 0)
3986
+ return a;
3987
+ if (!t && l - i > 0)
3988
+ return a;
3989
+ if (t)
3990
+ throw new Error("Invalid date passed to fromTZ()");
3991
+ return s;
3996
3992
  }
3997
- async handleMessage(ws, message) {
3998
- const client = this.connectionManager.getClientByWs(ws);
3999
- if (!client) {
4000
- this.sendError(ws, "Client not found");
3993
+ };
3994
+ h.toTZ = function(n, t) {
3995
+ let e = n.toLocaleString("en-US", { timeZone: t }).replace(/[\u202f]/, " "), r = new Date(e);
3996
+ return { y: r.getFullYear(), m: r.getMonth() + 1, d: r.getDate(), h: r.getHours(), i: r.getMinutes(), s: r.getSeconds(), tz: t };
3997
+ };
3998
+ h.tp = (n, t, e, r, s, i, a) => ({ y: n, m: t, d: e, h: r, i: s, s: i, tz: a });
3999
+ function D(n, t = new Date) {
4000
+ let e = t.toLocaleString("en-US", { timeZone: n, timeZoneName: "shortOffset" }).split(" ").slice(-1)[0], r = t.toLocaleString("en-US").replace(/[\u202f]/, " ");
4001
+ return Date.parse(`${r} GMT`) - Date.parse(`${r} ${e}`);
4002
+ }
4003
+ function k(n, t) {
4004
+ let e = new Date(Date.parse(n));
4005
+ if (isNaN(e))
4006
+ throw new Error("minitz: Invalid ISO8601 passed to parser.");
4007
+ let r = n.substring(9);
4008
+ return n.includes("Z") || r.includes("-") || r.includes("+") ? h.tp(e.getUTCFullYear(), e.getUTCMonth() + 1, e.getUTCDate(), e.getUTCHours(), e.getUTCMinutes(), e.getUTCSeconds(), "Etc/UTC") : h.tp(e.getFullYear(), e.getMonth() + 1, e.getDate(), e.getHours(), e.getMinutes(), e.getSeconds(), t);
4009
+ }
4010
+ h.minitz = h;
4011
+ var b = 32;
4012
+ var p = 31 | b;
4013
+ var v = [1, 2, 4, 8, 16];
4014
+ var d = class {
4015
+ pattern;
4016
+ timezone;
4017
+ second;
4018
+ minute;
4019
+ hour;
4020
+ day;
4021
+ month;
4022
+ dayOfWeek;
4023
+ lastDayOfMonth;
4024
+ starDOM;
4025
+ starDOW;
4026
+ constructor(t, e) {
4027
+ this.pattern = t, this.timezone = e, this.second = Array(60).fill(0), this.minute = Array(60).fill(0), this.hour = Array(24).fill(0), this.day = Array(31).fill(0), this.month = Array(12).fill(0), this.dayOfWeek = Array(7).fill(0), this.lastDayOfMonth = false, this.starDOM = false, this.starDOW = false, this.parse();
4028
+ }
4029
+ parse() {
4030
+ if (!(typeof this.pattern == "string" || this.pattern instanceof String))
4031
+ throw new TypeError("CronPattern: Pattern has to be of type string.");
4032
+ this.pattern.indexOf("@") >= 0 && (this.pattern = this.handleNicknames(this.pattern).trim());
4033
+ let t = this.pattern.replace(/\s+/g, " ").split(" ");
4034
+ if (t.length < 5 || t.length > 6)
4035
+ throw new TypeError("CronPattern: invalid configuration format ('" + this.pattern + "'), exactly five or six space separated parts are required.");
4036
+ if (t.length === 5 && t.unshift("0"), t[3].indexOf("L") >= 0 && (t[3] = t[3].replace("L", ""), this.lastDayOfMonth = true), t[3] == "*" && (this.starDOM = true), t[4].length >= 3 && (t[4] = this.replaceAlphaMonths(t[4])), t[5].length >= 3 && (t[5] = this.replaceAlphaDays(t[5])), t[5] == "*" && (this.starDOW = true), this.pattern.indexOf("?") >= 0) {
4037
+ let e = new f(new Date, this.timezone).getDate(true);
4038
+ t[0] = t[0].replace("?", e.getSeconds().toString()), t[1] = t[1].replace("?", e.getMinutes().toString()), t[2] = t[2].replace("?", e.getHours().toString()), this.starDOM || (t[3] = t[3].replace("?", e.getDate().toString())), t[4] = t[4].replace("?", (e.getMonth() + 1).toString()), this.starDOW || (t[5] = t[5].replace("?", e.getDay().toString()));
4039
+ }
4040
+ this.throwAtIllegalCharacters(t), this.partToArray("second", t[0], 0, 1), this.partToArray("minute", t[1], 0, 1), this.partToArray("hour", t[2], 0, 1), this.partToArray("day", t[3], -1, 1), this.partToArray("month", t[4], -1, 1), this.partToArray("dayOfWeek", t[5], 0, p), this.dayOfWeek[7] && (this.dayOfWeek[0] = this.dayOfWeek[7]);
4041
+ }
4042
+ partToArray(t, e, r, s) {
4043
+ let i = this[t], a = t === "day" && this.lastDayOfMonth;
4044
+ if (e === "" && !a)
4045
+ throw new TypeError("CronPattern: configuration entry " + t + " (" + e + ") is empty, check for trailing spaces.");
4046
+ if (e === "*")
4047
+ return i.fill(s);
4048
+ let l = e.split(",");
4049
+ if (l.length > 1)
4050
+ for (let o = 0;o < l.length; o++)
4051
+ this.partToArray(t, l[o], r, s);
4052
+ else
4053
+ e.indexOf("-") !== -1 && e.indexOf("/") !== -1 ? this.handleRangeWithStepping(e, t, r, s) : e.indexOf("-") !== -1 ? this.handleRange(e, t, r, s) : e.indexOf("/") !== -1 ? this.handleStepping(e, t, r, s) : e !== "" && this.handleNumber(e, t, r, s);
4054
+ }
4055
+ throwAtIllegalCharacters(t) {
4056
+ for (let e = 0;e < t.length; e++)
4057
+ if ((e === 5 ? /[^/*0-9,\-#L]+/ : /[^/*0-9,-]+/).test(t[e]))
4058
+ throw new TypeError("CronPattern: configuration entry " + e + " (" + t[e] + ") contains illegal characters.");
4059
+ }
4060
+ handleNumber(t, e, r, s) {
4061
+ let i = this.extractNth(t, e), a = parseInt(i[0], 10) + r;
4062
+ if (isNaN(a))
4063
+ throw new TypeError("CronPattern: " + e + " is not a number: '" + t + "'");
4064
+ this.setPart(e, a, i[1] || s);
4065
+ }
4066
+ setPart(t, e, r) {
4067
+ if (!Object.prototype.hasOwnProperty.call(this, t))
4068
+ throw new TypeError("CronPattern: Invalid part specified: " + t);
4069
+ if (t === "dayOfWeek") {
4070
+ if (e === 7 && (e = 0), e < 0 || e > 6)
4071
+ throw new RangeError("CronPattern: Invalid value for dayOfWeek: " + e);
4072
+ this.setNthWeekdayOfMonth(e, r);
4001
4073
  return;
4002
4074
  }
4003
- switch (message.type) {
4004
- case "sync-events":
4005
- await this.handleSyncEvents(client.id, message, client.token);
4006
- break;
4007
- case "request-sync":
4008
- await this.handleRequestSync(client.id, message, client.token);
4009
- break;
4010
- case "execute-command":
4011
- await this.handleExecuteCommand(client.id, message, client.rawToken);
4012
- break;
4013
- default:
4014
- this.sendError(ws, `Unknown message type: ${message.type}`);
4015
- }
4075
+ if (t === "second" || t === "minute") {
4076
+ if (e < 0 || e >= 60)
4077
+ throw new RangeError("CronPattern: Invalid value for " + t + ": " + e);
4078
+ } else if (t === "hour") {
4079
+ if (e < 0 || e >= 24)
4080
+ throw new RangeError("CronPattern: Invalid value for " + t + ": " + e);
4081
+ } else if (t === "day") {
4082
+ if (e < 0 || e >= 31)
4083
+ throw new RangeError("CronPattern: Invalid value for " + t + ": " + e);
4084
+ } else if (t === "month" && (e < 0 || e >= 12))
4085
+ throw new RangeError("CronPattern: Invalid value for " + t + ": " + e);
4086
+ this[t][e] = r;
4087
+ }
4088
+ handleRangeWithStepping(t, e, r, s) {
4089
+ let i = this.extractNth(t, e), a = i[0].match(/^(\d+)-(\d+)\/(\d+)$/);
4090
+ if (a === null)
4091
+ throw new TypeError("CronPattern: Syntax error, illegal range with stepping: '" + t + "'");
4092
+ let [, l, o, u] = a, c = parseInt(l, 10) + r, w = parseInt(o, 10) + r, C = parseInt(u, 10);
4093
+ if (isNaN(c))
4094
+ throw new TypeError("CronPattern: Syntax error, illegal lower range (NaN)");
4095
+ if (isNaN(w))
4096
+ throw new TypeError("CronPattern: Syntax error, illegal upper range (NaN)");
4097
+ if (isNaN(C))
4098
+ throw new TypeError("CronPattern: Syntax error, illegal stepping: (NaN)");
4099
+ if (C === 0)
4100
+ throw new TypeError("CronPattern: Syntax error, illegal stepping: 0");
4101
+ if (C > this[e].length)
4102
+ throw new TypeError("CronPattern: Syntax error, steps cannot be greater than maximum value of part (" + this[e].length + ")");
4103
+ if (c > w)
4104
+ throw new TypeError("CronPattern: From value is larger than to value: '" + t + "'");
4105
+ for (let T = c;T <= w; T += C)
4106
+ this.setPart(e, T, i[1] || s);
4107
+ }
4108
+ extractNth(t, e) {
4109
+ let r = t, s;
4110
+ if (r.includes("#")) {
4111
+ if (e !== "dayOfWeek")
4112
+ throw new Error("CronPattern: nth (#) only allowed in day-of-week field");
4113
+ s = r.split("#")[1], r = r.split("#")[0];
4114
+ }
4115
+ return [r, s];
4116
+ }
4117
+ handleRange(t, e, r, s) {
4118
+ let i = this.extractNth(t, e), a = i[0].split("-");
4119
+ if (a.length !== 2)
4120
+ throw new TypeError("CronPattern: Syntax error, illegal range: '" + t + "'");
4121
+ let l = parseInt(a[0], 10) + r, o = parseInt(a[1], 10) + r;
4122
+ if (isNaN(l))
4123
+ throw new TypeError("CronPattern: Syntax error, illegal lower range (NaN)");
4124
+ if (isNaN(o))
4125
+ throw new TypeError("CronPattern: Syntax error, illegal upper range (NaN)");
4126
+ if (l > o)
4127
+ throw new TypeError("CronPattern: From value is larger than to value: '" + t + "'");
4128
+ for (let u = l;u <= o; u++)
4129
+ this.setPart(e, u, i[1] || s);
4130
+ }
4131
+ handleStepping(t, e, r, s) {
4132
+ let i = this.extractNth(t, e), a = i[0].split("/");
4133
+ if (a.length !== 2)
4134
+ throw new TypeError("CronPattern: Syntax error, illegal stepping: '" + t + "'");
4135
+ a[0] === "" && (a[0] = "*");
4136
+ let l = 0;
4137
+ a[0] !== "*" && (l = parseInt(a[0], 10) + r);
4138
+ let o = parseInt(a[1], 10);
4139
+ if (isNaN(o))
4140
+ throw new TypeError("CronPattern: Syntax error, illegal stepping: (NaN)");
4141
+ if (o === 0)
4142
+ throw new TypeError("CronPattern: Syntax error, illegal stepping: 0");
4143
+ if (o > this[e].length)
4144
+ throw new TypeError("CronPattern: Syntax error, max steps for part is (" + this[e].length + ")");
4145
+ for (let u = l;u < this[e].length; u += o)
4146
+ this.setPart(e, u, i[1] || s);
4147
+ }
4148
+ replaceAlphaDays(t) {
4149
+ return t.replace(/-sun/gi, "-7").replace(/sun/gi, "0").replace(/mon/gi, "1").replace(/tue/gi, "2").replace(/wed/gi, "3").replace(/thu/gi, "4").replace(/fri/gi, "5").replace(/sat/gi, "6");
4150
+ }
4151
+ replaceAlphaMonths(t) {
4152
+ return t.replace(/jan/gi, "1").replace(/feb/gi, "2").replace(/mar/gi, "3").replace(/apr/gi, "4").replace(/may/gi, "5").replace(/jun/gi, "6").replace(/jul/gi, "7").replace(/aug/gi, "8").replace(/sep/gi, "9").replace(/oct/gi, "10").replace(/nov/gi, "11").replace(/dec/gi, "12");
4153
+ }
4154
+ handleNicknames(t) {
4155
+ let e = t.trim().toLowerCase();
4156
+ return e === "@yearly" || e === "@annually" ? "0 0 1 1 *" : e === "@monthly" ? "0 0 1 * *" : e === "@weekly" ? "0 0 * * 0" : e === "@daily" ? "0 0 * * *" : e === "@hourly" ? "0 * * * *" : t;
4157
+ }
4158
+ setNthWeekdayOfMonth(t, e) {
4159
+ if (typeof e != "number" && e === "L")
4160
+ this.dayOfWeek[t] = this.dayOfWeek[t] | b;
4161
+ else if (e === p)
4162
+ this.dayOfWeek[t] = p;
4163
+ else if (e < 6 && e > 0)
4164
+ this.dayOfWeek[t] = this.dayOfWeek[t] | v[e - 1];
4165
+ else
4166
+ throw new TypeError(`CronPattern: nth weekday out of range, should be 1-5 or L. Value: ${e}, Type: ${typeof e}`);
4016
4167
  }
4017
- async handleSyncEvents(clientId, message, token) {
4018
- const persistedEvents = await this.contextHandler.persistEvents(message.events, clientId, token);
4019
- if (persistedEvents.length === 0) {
4020
- return;
4168
+ };
4169
+ var O = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
4170
+ var m = [["month", "year", 0], ["day", "month", -1], ["hour", "day", 0], ["minute", "hour", 0], ["second", "minute", 0]];
4171
+ var f = class n {
4172
+ tz;
4173
+ ms;
4174
+ second;
4175
+ minute;
4176
+ hour;
4177
+ day;
4178
+ month;
4179
+ year;
4180
+ constructor(t, e) {
4181
+ if (this.tz = e, t && t instanceof Date)
4182
+ if (!isNaN(t))
4183
+ this.fromDate(t);
4184
+ else
4185
+ throw new TypeError("CronDate: Invalid date passed to CronDate constructor");
4186
+ else if (t === undefined)
4187
+ this.fromDate(new Date);
4188
+ else if (t && typeof t == "string")
4189
+ this.fromString(t);
4190
+ else if (t instanceof n)
4191
+ this.fromCronDate(t);
4192
+ else
4193
+ throw new TypeError("CronDate: Invalid type (" + typeof t + ") passed to CronDate constructor");
4194
+ }
4195
+ isNthWeekdayOfMonth(t, e, r, s) {
4196
+ let a = new Date(Date.UTC(t, e, r)).getUTCDay(), l = 0;
4197
+ for (let o = 1;o <= r; o++)
4198
+ new Date(Date.UTC(t, e, o)).getUTCDay() === a && l++;
4199
+ if (s & p && v[l - 1] & s)
4200
+ return true;
4201
+ if (s & b) {
4202
+ let o = new Date(Date.UTC(t, e + 1, 0)).getUTCDate();
4203
+ for (let u = r + 1;u <= o; u++)
4204
+ if (new Date(Date.UTC(t, e, u)).getUTCDay() === a)
4205
+ return false;
4206
+ return true;
4021
4207
  }
4022
- const clients = this.connectionManager.getAllClients();
4023
- for (const client of clients) {
4024
- if (client.id === clientId)
4025
- continue;
4026
- const authorizedEvents = filterEventsForToken(client.token, persistedEvents, this.contextHandler.getEventDefinitions());
4027
- if (authorizedEvents.length > 0) {
4028
- this.connectionManager.sendToClient(client.id, {
4029
- type: "events",
4030
- events: authorizedEvents
4031
- });
4208
+ return false;
4209
+ }
4210
+ fromDate(t) {
4211
+ if (this.tz !== undefined)
4212
+ if (typeof this.tz == "number")
4213
+ this.ms = t.getUTCMilliseconds(), this.second = t.getUTCSeconds(), this.minute = t.getUTCMinutes() + this.tz, this.hour = t.getUTCHours(), this.day = t.getUTCDate(), this.month = t.getUTCMonth(), this.year = t.getUTCFullYear(), this.apply();
4214
+ else {
4215
+ let e = h.toTZ(t, this.tz);
4216
+ this.ms = t.getMilliseconds(), this.second = e.s, this.minute = e.i, this.hour = e.h, this.day = e.d, this.month = e.m - 1, this.year = e.y;
4217
+ }
4218
+ else
4219
+ this.ms = t.getMilliseconds(), this.second = t.getSeconds(), this.minute = t.getMinutes(), this.hour = t.getHours(), this.day = t.getDate(), this.month = t.getMonth(), this.year = t.getFullYear();
4220
+ }
4221
+ fromCronDate(t) {
4222
+ this.tz = t.tz, this.year = t.year, this.month = t.month, this.day = t.day, this.hour = t.hour, this.minute = t.minute, this.second = t.second, this.ms = t.ms;
4223
+ }
4224
+ apply() {
4225
+ if (this.month > 11 || this.day > O[this.month] || this.hour > 59 || this.minute > 59 || this.second > 59 || this.hour < 0 || this.minute < 0 || this.second < 0) {
4226
+ let t = new Date(Date.UTC(this.year, this.month, this.day, this.hour, this.minute, this.second, this.ms));
4227
+ return this.ms = t.getUTCMilliseconds(), this.second = t.getUTCSeconds(), this.minute = t.getUTCMinutes(), this.hour = t.getUTCHours(), this.day = t.getUTCDate(), this.month = t.getUTCMonth(), this.year = t.getUTCFullYear(), true;
4228
+ } else
4229
+ return false;
4230
+ }
4231
+ fromString(t) {
4232
+ if (typeof this.tz == "number") {
4233
+ let e = h.fromTZISO(t);
4234
+ this.ms = e.getUTCMilliseconds(), this.second = e.getUTCSeconds(), this.minute = e.getUTCMinutes(), this.hour = e.getUTCHours(), this.day = e.getUTCDate(), this.month = e.getUTCMonth(), this.year = e.getUTCFullYear(), this.apply();
4235
+ } else
4236
+ return this.fromDate(h.fromTZISO(t, this.tz));
4237
+ }
4238
+ findNext(t, e, r, s) {
4239
+ let i = this[e], a;
4240
+ r.lastDayOfMonth && (this.month !== 1 ? a = O[this.month] : a = new Date(Date.UTC(this.year, this.month + 1, 0, 0, 0, 0, 0)).getUTCDate());
4241
+ let l = !r.starDOW && e == "day" ? new Date(Date.UTC(this.year, this.month, 1, 0, 0, 0, 0)).getUTCDay() : undefined;
4242
+ for (let o = this[e] + s;o < r[e].length; o++) {
4243
+ let u = r[e][o];
4244
+ if (e === "day" && r.lastDayOfMonth && o - s == a && (u = 1), e === "day" && !r.starDOW) {
4245
+ let c = r.dayOfWeek[(l + (o - s - 1)) % 7];
4246
+ if (c && c & p)
4247
+ c = this.isNthWeekdayOfMonth(this.year, this.month, o - s, c) ? 1 : 0;
4248
+ else if (c)
4249
+ throw new Error(`CronDate: Invalid value for dayOfWeek encountered. ${c}`);
4250
+ t.legacyMode && !r.starDOM ? u = u || c : u = u && c;
4251
+ }
4252
+ if (u)
4253
+ return this[e] = o - s, i !== this[e] ? 2 : 1;
4254
+ }
4255
+ return 3;
4256
+ }
4257
+ recurse(t, e, r) {
4258
+ let s = this.findNext(e, m[r][0], t, m[r][2]);
4259
+ if (s > 1) {
4260
+ let i = r + 1;
4261
+ for (;i < m.length; )
4262
+ this[m[i][0]] = -m[i][2], i++;
4263
+ if (s === 3)
4264
+ return this[m[r][1]]++, this[m[r][0]] = -m[r][2], this.apply(), this.recurse(t, e, 0);
4265
+ if (this.apply())
4266
+ return this.recurse(t, e, r - 1);
4267
+ }
4268
+ return r += 1, r >= m.length ? this : this.year >= 3000 ? null : this.recurse(t, e, r);
4269
+ }
4270
+ increment(t, e, r) {
4271
+ return this.second += e.interval !== undefined && e.interval > 1 && r ? e.interval : 1, this.ms = 0, this.apply(), this.recurse(t, e, 0);
4272
+ }
4273
+ getDate(t) {
4274
+ return t || this.tz === undefined ? new Date(this.year, this.month, this.day, this.hour, this.minute, this.second, this.ms) : typeof this.tz == "number" ? new Date(Date.UTC(this.year, this.month, this.day, this.hour, this.minute - this.tz, this.second, this.ms)) : h.fromTZ(h.tp(this.year, this.month + 1, this.day, this.hour, this.minute, this.second, this.tz), false);
4275
+ }
4276
+ getTime() {
4277
+ return this.getDate(false).getTime();
4278
+ }
4279
+ };
4280
+ function N(n2) {
4281
+ if (n2 === undefined && (n2 = {}), delete n2.name, n2.legacyMode = n2.legacyMode === undefined ? true : n2.legacyMode, n2.paused = n2.paused === undefined ? false : n2.paused, n2.maxRuns = n2.maxRuns === undefined ? 1 / 0 : n2.maxRuns, n2.catch = n2.catch === undefined ? false : n2.catch, n2.interval = n2.interval === undefined ? 0 : parseInt(n2.interval.toString(), 10), n2.utcOffset = n2.utcOffset === undefined ? undefined : parseInt(n2.utcOffset.toString(), 10), n2.unref = n2.unref === undefined ? false : n2.unref, n2.startAt && (n2.startAt = new f(n2.startAt, n2.timezone)), n2.stopAt && (n2.stopAt = new f(n2.stopAt, n2.timezone)), n2.interval !== null) {
4282
+ if (isNaN(n2.interval))
4283
+ throw new Error("CronOptions: Supplied value for interval is not a number");
4284
+ if (n2.interval < 0)
4285
+ throw new Error("CronOptions: Supplied value for interval can not be negative");
4286
+ }
4287
+ if (n2.utcOffset !== undefined) {
4288
+ if (isNaN(n2.utcOffset))
4289
+ throw new Error("CronOptions: Invalid value passed for utcOffset, should be number representing minutes offset from UTC.");
4290
+ if (n2.utcOffset < -870 || n2.utcOffset > 870)
4291
+ throw new Error("CronOptions: utcOffset out of bounds.");
4292
+ if (n2.utcOffset !== undefined && n2.timezone)
4293
+ throw new Error("CronOptions: Combining 'utcOffset' with 'timezone' is not allowed.");
4294
+ }
4295
+ if (n2.unref !== true && n2.unref !== false)
4296
+ throw new Error("CronOptions: Unref should be either true, false or undefined(false).");
4297
+ return n2;
4298
+ }
4299
+ function g(n2) {
4300
+ return Object.prototype.toString.call(n2) === "[object Function]" || typeof n2 == "function" || n2 instanceof Function;
4301
+ }
4302
+ function S(n2) {
4303
+ return g(n2);
4304
+ }
4305
+ function P(n2) {
4306
+ typeof Deno < "u" && typeof Deno.unrefTimer < "u" ? Deno.unrefTimer(n2) : n2 && typeof n2.unref < "u" && n2.unref();
4307
+ }
4308
+ var _ = 30 * 1000;
4309
+ var y = [];
4310
+ var R = class {
4311
+ name;
4312
+ options;
4313
+ _states;
4314
+ fn;
4315
+ constructor(t, e, r) {
4316
+ let s, i;
4317
+ if (g(e))
4318
+ i = e;
4319
+ else if (typeof e == "object")
4320
+ s = e;
4321
+ else if (e !== undefined)
4322
+ throw new Error("Cron: Invalid argument passed for optionsIn. Should be one of function, or object (options).");
4323
+ if (g(r))
4324
+ i = r;
4325
+ else if (typeof r == "object")
4326
+ s = r;
4327
+ else if (r !== undefined)
4328
+ throw new Error("Cron: Invalid argument passed for funcIn. Should be one of function, or object (options).");
4329
+ if (this.name = s?.name, this.options = N(s), this._states = { kill: false, blocking: false, previousRun: undefined, currentRun: undefined, once: undefined, currentTimeout: undefined, maxRuns: s ? s.maxRuns : undefined, paused: s ? s.paused : false, pattern: new d("* * * * *") }, t && (t instanceof Date || typeof t == "string" && t.indexOf(":") > 0) ? this._states.once = new f(t, this.options.timezone || this.options.utcOffset) : this._states.pattern = new d(t, this.options.timezone), this.name) {
4330
+ if (y.find((l) => l.name === this.name))
4331
+ throw new Error("Cron: Tried to initialize new named job '" + this.name + "', but name already taken.");
4332
+ y.push(this);
4333
+ }
4334
+ return i !== undefined && S(i) && (this.fn = i, this.schedule()), this;
4335
+ }
4336
+ nextRun(t) {
4337
+ let e = this._next(t);
4338
+ return e ? e.getDate(false) : null;
4339
+ }
4340
+ nextRuns(t, e) {
4341
+ this._states.maxRuns !== undefined && t > this._states.maxRuns && (t = this._states.maxRuns);
4342
+ let r = [], s = e || this._states.currentRun || undefined;
4343
+ for (;t-- && (s = this.nextRun(s)); )
4344
+ r.push(s);
4345
+ return r;
4346
+ }
4347
+ getPattern() {
4348
+ return this._states.pattern ? this._states.pattern.pattern : undefined;
4349
+ }
4350
+ isRunning() {
4351
+ let t = this.nextRun(this._states.currentRun), e = !this._states.paused, r = this.fn !== undefined, s = !this._states.kill;
4352
+ return e && r && s && t !== null;
4353
+ }
4354
+ isStopped() {
4355
+ return this._states.kill;
4356
+ }
4357
+ isBusy() {
4358
+ return this._states.blocking;
4359
+ }
4360
+ currentRun() {
4361
+ return this._states.currentRun ? this._states.currentRun.getDate() : null;
4362
+ }
4363
+ previousRun() {
4364
+ return this._states.previousRun ? this._states.previousRun.getDate() : null;
4365
+ }
4366
+ msToNext(t) {
4367
+ let e = this._next(t);
4368
+ return e ? t instanceof f || t instanceof Date ? e.getTime() - t.getTime() : e.getTime() - new f(t).getTime() : null;
4369
+ }
4370
+ stop() {
4371
+ this._states.kill = true, this._states.currentTimeout && clearTimeout(this._states.currentTimeout);
4372
+ let t = y.indexOf(this);
4373
+ t >= 0 && y.splice(t, 1);
4374
+ }
4375
+ pause() {
4376
+ return this._states.paused = true, !this._states.kill;
4377
+ }
4378
+ resume() {
4379
+ return this._states.paused = false, !this._states.kill;
4380
+ }
4381
+ schedule(t) {
4382
+ if (t && this.fn)
4383
+ throw new Error("Cron: It is not allowed to schedule two functions using the same Croner instance.");
4384
+ t && (this.fn = t);
4385
+ let e = this.msToNext(), r = this.nextRun(this._states.currentRun);
4386
+ return e == null || isNaN(e) || r === null ? this : (e > _ && (e = _), this._states.currentTimeout = setTimeout(() => this._checkTrigger(r), e), this._states.currentTimeout && this.options.unref && P(this._states.currentTimeout), this);
4387
+ }
4388
+ async _trigger(t) {
4389
+ if (this._states.blocking = true, this._states.currentRun = new f(undefined, this.options.timezone || this.options.utcOffset), this.options.catch)
4390
+ try {
4391
+ this.fn !== undefined && await this.fn(this, this.options.context);
4392
+ } catch (e) {
4393
+ g(this.options.catch) && this.options.catch(e, this);
4032
4394
  }
4395
+ else
4396
+ this.fn !== undefined && await this.fn(this, this.options.context);
4397
+ this._states.previousRun = new f(t, this.options.timezone || this.options.utcOffset), this._states.blocking = false;
4398
+ }
4399
+ async trigger() {
4400
+ await this._trigger();
4401
+ }
4402
+ runsLeft() {
4403
+ return this._states.maxRuns;
4404
+ }
4405
+ _checkTrigger(t) {
4406
+ let e = new Date, r = !this._states.paused && e.getTime() >= t.getTime(), s = this._states.blocking && this.options.protect;
4407
+ r && !s ? (this._states.maxRuns !== undefined && this._states.maxRuns--, this._trigger()) : r && s && g(this.options.protect) && setTimeout(() => this.options.protect(this), 0), this.schedule();
4408
+ }
4409
+ _next(t) {
4410
+ let e = !!(t || this._states.currentRun), r = false;
4411
+ !t && this.options.startAt && this.options.interval && ([t, e] = this._calculatePreviousRun(t, e), r = !t), t = new f(t, this.options.timezone || this.options.utcOffset), this.options.startAt && t && t.getTime() < this.options.startAt.getTime() && (t = this.options.startAt);
4412
+ let s = this._states.once || new f(t, this.options.timezone || this.options.utcOffset);
4413
+ return !r && s !== this._states.once && (s = s.increment(this._states.pattern, this.options, e)), this._states.once && this._states.once.getTime() <= t.getTime() || s === null || this._states.maxRuns !== undefined && this._states.maxRuns <= 0 || this._states.kill || this.options.stopAt && s.getTime() >= this.options.stopAt.getTime() ? null : s;
4414
+ }
4415
+ _calculatePreviousRun(t, e) {
4416
+ let r = new f(undefined, this.options.timezone || this.options.utcOffset), s = t;
4417
+ if (this.options.startAt.getTime() <= r.getTime()) {
4418
+ s = this.options.startAt;
4419
+ let i = s.getTime() + this.options.interval * 1000;
4420
+ for (;i <= r.getTime(); )
4421
+ s = new f(s, this.options.timezone || this.options.utcOffset).increment(this._states.pattern, this.options, true), i = s.getTime() + this.options.interval * 1000;
4422
+ e = true;
4033
4423
  }
4034
- const lastEvent = persistedEvents[persistedEvents.length - 1];
4035
- this.connectionManager.updateLastSyncedEventId(clientId, lastEvent.hostId);
4036
- this.connectionManager.sendToClient(clientId, {
4037
- type: "sync-complete",
4038
- lastHostEventId: lastEvent.hostId
4039
- });
4424
+ return s === null && (s = undefined), [s, e];
4040
4425
  }
4041
- async handleRequestSync(clientId, message, token) {
4042
- const events = await this.contextHandler.getEventsSince(message.lastHostEventId, token);
4043
- this.connectionManager.sendToClient(clientId, {
4044
- type: "events",
4045
- events
4046
- });
4047
- if (events.length > 0) {
4048
- const lastEvent = events[events.length - 1];
4049
- this.connectionManager.updateLastSyncedEventId(clientId, lastEvent.hostId);
4050
- this.connectionManager.sendToClient(clientId, {
4051
- type: "sync-complete",
4052
- lastHostEventId: lastEvent.hostId
4053
- });
4054
- } else {
4055
- this.connectionManager.sendToClient(clientId, {
4056
- type: "sync-complete",
4057
- lastHostEventId: message.lastHostEventId || ""
4426
+ };
4427
+
4428
+ // src/cron-scheduler.ts
4429
+ class CronScheduler {
4430
+ jobs = [];
4431
+ contextHandler;
4432
+ constructor(contextHandler) {
4433
+ this.contextHandler = contextHandler;
4434
+ }
4435
+ start() {
4436
+ const context = this.contextHandler.context;
4437
+ const cronEntries = this.discoverCronMethods(context);
4438
+ if (cronEntries.length === 0)
4439
+ return;
4440
+ console.log(`[ARC:Cron] Discovered ${cronEntries.length} cron method(s):`);
4441
+ for (const entry of cronEntries) {
4442
+ console.log(`[ARC:Cron] ${entry.aggregateName}.${entry.methodName} \u2192 "${entry.cronExpression}"`);
4443
+ const task = new R(entry.cronExpression, async () => {
4444
+ await this.executeCronMethod(entry);
4058
4445
  });
4446
+ this.jobs.push({ entry, task });
4447
+ }
4448
+ }
4449
+ stop() {
4450
+ for (const job of this.jobs) {
4451
+ job.task.stop();
4452
+ }
4453
+ this.jobs = [];
4454
+ console.log("[ARC:Cron] All cron jobs stopped.");
4455
+ }
4456
+ discoverCronMethods(context) {
4457
+ const entries = [];
4458
+ for (const element of context.elements) {
4459
+ const ctor = element.ctor;
4460
+ if (ctor?.__aggregateCronMethods) {
4461
+ entries.push(...ctor.__aggregateCronMethods);
4462
+ }
4059
4463
  }
4464
+ return entries;
4060
4465
  }
4061
- async handleExecuteCommand(clientId, message, rawToken) {
4466
+ async executeCronMethod(entry) {
4467
+ const commandName = `${entry.aggregateName}.${entry.methodName}`;
4062
4468
  try {
4063
- const result = await this.contextHandler.executeCommand(message.commandName, message.params, rawToken);
4064
- this.connectionManager.sendToClient(clientId, {
4065
- type: "command-result",
4066
- requestId: message.requestId,
4067
- result
4068
- });
4469
+ const dataStorage = this.contextHandler.getDataStorage();
4470
+ const store = dataStorage.getStore(entry.aggregateName);
4471
+ const instances = await store.find({});
4472
+ if (instances.length === 0) {
4473
+ console.log(`[ARC:Cron] ${commandName}: no instances found, skipping.`);
4474
+ return;
4475
+ }
4476
+ console.log(`[ARC:Cron] ${commandName}: executing for ${instances.length} instance(s)...`);
4477
+ for (const instance of instances) {
4478
+ try {
4479
+ await this.contextHandler.executeCommand(commandName, { _id: instance._id }, null);
4480
+ } catch (error) {
4481
+ console.error(`[ARC:Cron] ${commandName} failed for instance ${instance._id}:`, error);
4482
+ }
4483
+ }
4069
4484
  } catch (error) {
4070
- this.connectionManager.sendToClient(clientId, {
4071
- type: "command-result",
4072
- requestId: message.requestId,
4073
- error: error.message
4074
- });
4485
+ console.error(`[ARC:Cron] ${commandName} failed:`, error);
4075
4486
  }
4076
4487
  }
4077
- sendError(ws, message) {
4078
- ws.send(JSON.stringify({ type: "error", message }));
4079
- }
4080
- setupServer() {
4081
- const self = this;
4082
- this.server = Bun.serve({
4083
- port: this.port,
4084
- idleTimeout: 255,
4085
- fetch(req, server) {
4086
- const url = new URL(req.url);
4087
- const corsHeaders = {
4088
- "Access-Control-Allow-Origin": "*",
4089
- "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
4090
- "Access-Control-Allow-Headers": "Content-Type, Authorization"
4091
- };
4092
- if (req.method === "OPTIONS") {
4093
- return new Response(null, { headers: corsHeaders });
4094
- }
4095
- const authHeader = req.headers.get("Authorization");
4096
- const token = authHeader?.replace("Bearer ", "") || url.searchParams.get("token");
4097
- let tokenPayload = null;
4098
- if (token) {
4099
- tokenPayload = self.verifyToken(token);
4100
- }
4101
- if (url.pathname === "/ws" && req.headers.get("Upgrade") === "websocket") {
4102
- if (server.upgrade(req, {
4103
- data: {
4104
- clientId: "",
4105
- token: tokenPayload,
4106
- rawToken: token
4107
- }
4108
- })) {
4109
- return;
4110
- }
4111
- return new Response("WebSocket upgrade failed", {
4112
- status: 500,
4113
- headers: corsHeaders
4114
- });
4115
- }
4116
- if (url.pathname === "/health") {
4117
- return new Response(JSON.stringify({
4118
- status: "ok",
4119
- clients: self.connectionManager.clientCount
4120
- }), {
4121
- headers: { ...corsHeaders, "Content-Type": "application/json" }
4122
- });
4123
- }
4124
- if (url.pathname.startsWith("/command/") && req.method === "POST") {
4125
- return self.handleHttpCommand(req, url, corsHeaders, token);
4126
- }
4127
- if (url.pathname.startsWith("/query/") && req.method === "POST") {
4128
- return self.handleHttpQuery(req, url, tokenPayload, corsHeaders, token);
4129
- }
4130
- if (url.pathname.startsWith("/stream/") && req.method === "GET") {
4131
- return self.handleHttpStream(req, url, tokenPayload, corsHeaders, token);
4132
- }
4133
- if (url.pathname === "/sync/events" && req.method === "POST") {
4134
- return self.handleHttpEventSync(req, tokenPayload, corsHeaders);
4135
- }
4136
- if (url.pathname.startsWith("/route/")) {
4137
- return self.handleHttpRoute(req, url, tokenPayload, corsHeaders, token);
4138
- }
4139
- return new Response("Not Found", { status: 404, headers: corsHeaders });
4140
- },
4141
- websocket: {
4142
- open(ws) {
4143
- const tokenPayload = ws.data?.token || null;
4144
- const rawToken = ws.data?.rawToken || null;
4145
- const client = self.connectionManager.addClient(ws, tokenPayload, rawToken);
4146
- console.log(`Client connected: ${client.id}`);
4147
- },
4148
- message(ws, messageStr) {
4149
- try {
4150
- const message = JSON.parse(messageStr);
4151
- self.handleMessage(ws, message);
4152
- } catch (error) {
4153
- console.error("Failed to parse message:", error);
4154
- self.sendError(ws, "Invalid message format");
4155
- }
4156
- },
4157
- close(ws) {
4158
- const client = self.connectionManager.getClientByWs(ws);
4159
- if (client) {
4160
- console.log(`Client disconnected: ${client.id}`);
4161
- self.connectionManager.removeClient(client.id);
4162
- }
4488
+ }
4489
+
4490
+ // src/middleware/http.ts
4491
+ import { ScopedModel as ScopedModel2 } from "@arcote.tech/arc";
4492
+ async function parseCommandParams(req) {
4493
+ const contentType = req.headers.get("Content-Type") || "";
4494
+ if (contentType.includes("multipart/form-data")) {
4495
+ const formData = await req.formData();
4496
+ const params = {};
4497
+ for (const [key, value] of formData.entries()) {
4498
+ if (typeof value === "object" && value !== null && "name" in value && "size" in value) {
4499
+ params[key] = value;
4500
+ } else {
4501
+ try {
4502
+ params[key] = JSON.parse(value);
4503
+ } catch {
4504
+ params[key] = value;
4163
4505
  }
4164
4506
  }
4165
- });
4166
- console.log(`\u2705 Arc Host running on http://localhost:${this.port}`);
4507
+ }
4508
+ return params;
4167
4509
  }
4168
- async handleHttpCommand(req, url, corsHeaders, rawToken) {
4510
+ return await req.json();
4511
+ }
4512
+ function healthHandler(cm) {
4513
+ return (_req, url, ctx) => {
4514
+ if (url.pathname !== "/health")
4515
+ return null;
4516
+ return Response.json({ status: "ok", clients: cm.clientCount }, { headers: ctx.corsHeaders });
4517
+ };
4518
+ }
4519
+ function commandHandler(ch) {
4520
+ return async (req, url, ctx) => {
4521
+ if (!url.pathname.startsWith("/command/") || req.method !== "POST")
4522
+ return null;
4169
4523
  const commandName = url.pathname.split("/command/")[1];
4170
4524
  if (!commandName) {
4171
4525
  return new Response("Invalid command path", {
4172
4526
  status: 400,
4173
- headers: corsHeaders
4527
+ headers: ctx.corsHeaders
4174
4528
  });
4175
4529
  }
4176
4530
  try {
4177
- const params = await this.parseCommandParams(req);
4178
- const result = await this.contextHandler.executeCommand(commandName, params, rawToken);
4179
- return new Response(JSON.stringify(result ?? { success: true }), {
4180
- headers: { ...corsHeaders, "Content-Type": "application/json" }
4531
+ const params = await parseCommandParams(req);
4532
+ const result = await ch.executeCommand(commandName, params, ctx.rawToken);
4533
+ return Response.json(result ?? { success: true }, {
4534
+ headers: ctx.corsHeaders
4181
4535
  });
4182
4536
  } catch (error) {
4183
- console.error(`[ARC HTTP] Command '${commandName}' error:`, error);
4184
- if (error instanceof Error && error.stack) {
4185
- console.error(`[ARC HTTP] Stack trace:`, error.stack);
4186
- }
4187
- return new Response(JSON.stringify({ error: error.message }), {
4188
- status: 500,
4189
- headers: { ...corsHeaders, "Content-Type": "application/json" }
4190
- });
4537
+ console.error(`[ARC] Command '${commandName}' error:`, error);
4538
+ return Response.json({ error: error.message }, { status: 500, headers: ctx.corsHeaders });
4191
4539
  }
4192
- }
4193
- async parseCommandParams(req) {
4194
- const contentType = req.headers.get("Content-Type") || "";
4195
- if (contentType.includes("multipart/form-data")) {
4196
- const formData = await req.formData();
4197
- const params = {};
4198
- for (const [key, value] of formData.entries()) {
4199
- if (typeof value === "object" && value !== null && "name" in value && "size" in value) {
4200
- params[key] = value;
4201
- } else {
4202
- try {
4203
- params[key] = JSON.parse(value);
4204
- } catch {
4205
- params[key] = value;
4206
- }
4207
- }
4208
- }
4209
- return params;
4210
- }
4211
- return await req.json();
4212
- }
4213
- async handleHttpQuery(req, url, _token, corsHeaders, rawToken) {
4540
+ };
4541
+ }
4542
+ function queryHandler(ch) {
4543
+ return async (req, url, ctx) => {
4544
+ if (!url.pathname.startsWith("/query/") || req.method !== "POST")
4545
+ return null;
4214
4546
  const viewName = url.pathname.split("/query/")[1];
4215
4547
  if (!viewName) {
4216
4548
  return new Response("Invalid query path", {
4217
4549
  status: 400,
4218
- headers: corsHeaders
4550
+ headers: ctx.corsHeaders
4219
4551
  });
4220
4552
  }
4221
4553
  try {
4222
4554
  const params = await req.json();
4223
- const viewElement = this.contextHandler.getModel().context.get(viewName);
4224
- if (!viewElement || !viewElement.queryContext) {
4225
- return new Response(JSON.stringify({ error: "View not found" }), {
4226
- status: 404,
4227
- headers: { ...corsHeaders, "Content-Type": "application/json" }
4228
- });
4229
- }
4230
- this.contextHandler.setAuthToken(rawToken);
4231
- const model = this.contextHandler.getModel();
4232
- const adapters = model.getAdapters();
4233
- const queryCtx = viewElement.queryContext(adapters);
4555
+ const viewElement = ch.getModel().context.get(viewName);
4556
+ if (!viewElement?.queryContext) {
4557
+ return Response.json({ error: "View not found" }, { status: 404, headers: ctx.corsHeaders });
4558
+ }
4559
+ const scoped = new ScopedModel2(ch.getModel(), "request");
4560
+ if (ctx.rawToken)
4561
+ scoped.setToken(ctx.rawToken);
4562
+ const queryCtx = viewElement.queryContext(scoped.getAdapters());
4234
4563
  const result = await queryCtx.find(params);
4235
- return new Response(JSON.stringify(result), {
4236
- headers: { ...corsHeaders, "Content-Type": "application/json" }
4237
- });
4564
+ return Response.json(result, { headers: ctx.corsHeaders });
4238
4565
  } catch (error) {
4239
- return new Response(JSON.stringify({ error: error.message }), {
4240
- status: 500,
4241
- headers: { ...corsHeaders, "Content-Type": "application/json" }
4242
- });
4566
+ return Response.json({ error: error.message }, { status: 500, headers: ctx.corsHeaders });
4243
4567
  }
4244
- }
4245
- handleHttpStream(_req, url, token, corsHeaders, rawToken) {
4568
+ };
4569
+ }
4570
+ var streamIdCounter = 0;
4571
+ var streamConnections = new Map;
4572
+ function streamHandler(ch) {
4573
+ return (_req, url, ctx) => {
4574
+ if (!url.pathname.startsWith("/stream/") || _req.method !== "GET")
4575
+ return null;
4246
4576
  const viewName = url.pathname.split("/stream/")[1];
4247
4577
  if (!viewName) {
4248
4578
  return new Response("Invalid stream path", {
4249
4579
  status: 400,
4250
- headers: corsHeaders
4580
+ headers: ctx.corsHeaders
4251
4581
  });
4252
4582
  }
4253
4583
  const findOptions = {};
@@ -4258,7 +4588,7 @@ class ArcHost {
4258
4588
  } catch {
4259
4589
  return new Response("Invalid 'where' parameter", {
4260
4590
  status: 400,
4261
- headers: corsHeaders
4591
+ headers: ctx.corsHeaders
4262
4592
  });
4263
4593
  }
4264
4594
  }
@@ -4269,92 +4599,81 @@ class ArcHost {
4269
4599
  } catch {
4270
4600
  return new Response("Invalid 'orderBy' parameter", {
4271
4601
  status: 400,
4272
- headers: corsHeaders
4602
+ headers: ctx.corsHeaders
4273
4603
  });
4274
4604
  }
4275
4605
  }
4276
4606
  const limitParam = url.searchParams.get("limit");
4277
- if (limitParam) {
4607
+ if (limitParam)
4278
4608
  findOptions.limit = parseInt(limitParam, 10);
4279
- }
4280
- const streamId = `stream_${++this.streamIdCounter}_${Date.now()}`;
4281
- const self = this;
4609
+ const streamId = `stream_${++streamIdCounter}_${Date.now()}`;
4610
+ const rawToken = ctx.rawToken;
4282
4611
  const stream = new ReadableStream({
4283
4612
  start(controller) {
4284
- const model = self.contextHandler.getModel();
4285
- self.contextHandler.setAuthToken(rawToken);
4286
- const { unsubscribe } = liveQuery(model, async (q) => {
4287
- const view = q[viewName];
4288
- if (!view) {
4289
- throw new Error(`View '${viewName}' not found`);
4290
- }
4291
- return view.find(findOptions);
4292
- }, (data) => {
4293
- const event = `data: ${JSON.stringify({ type: "data", data })}
4294
-
4295
- `;
4613
+ const scoped = new ScopedModel2(ch.getModel(), "stream");
4614
+ if (rawToken)
4615
+ scoped.setToken(rawToken);
4616
+ const descriptor = { element: viewName, method: "find", args: [findOptions] };
4617
+ const sendData = async () => {
4296
4618
  try {
4297
- controller.enqueue(new TextEncoder().encode(event));
4619
+ const data = await scoped.callQuery(descriptor);
4620
+ controller.enqueue(new TextEncoder().encode(`data: ${JSON.stringify({ type: "data", data })}
4621
+
4622
+ `));
4298
4623
  } catch {
4299
4624
  unsubscribe();
4300
4625
  }
4301
- });
4302
- self.streamConnections.set(streamId, {
4303
- id: streamId,
4304
- controller,
4305
- unsubscribe
4306
- });
4307
- const connectEvent = `data: ${JSON.stringify({ type: "connected", streamId })}
4626
+ };
4627
+ sendData();
4628
+ const unsubscribe = ch.getEventPublisher().subscribe("*", () => sendData());
4629
+ streamConnections.set(streamId, { id: streamId, controller, unsubscribe });
4630
+ controller.enqueue(new TextEncoder().encode(`data: ${JSON.stringify({ type: "connected", streamId })}
4308
4631
 
4309
- `;
4310
- controller.enqueue(new TextEncoder().encode(connectEvent));
4632
+ `));
4311
4633
  },
4312
4634
  cancel() {
4313
- const conn = self.streamConnections.get(streamId);
4635
+ const conn = streamConnections.get(streamId);
4314
4636
  if (conn) {
4315
4637
  conn.unsubscribe();
4316
- self.streamConnections.delete(streamId);
4638
+ streamConnections.delete(streamId);
4317
4639
  }
4318
4640
  }
4319
4641
  });
4320
4642
  return new Response(stream, {
4321
4643
  headers: {
4322
- ...corsHeaders,
4644
+ ...ctx.corsHeaders,
4323
4645
  "Content-Type": "text/event-stream",
4324
4646
  "Cache-Control": "no-cache",
4325
4647
  Connection: "keep-alive"
4326
4648
  }
4327
4649
  });
4328
- }
4329
- async handleHttpEventSync(req, token, corsHeaders) {
4650
+ };
4651
+ }
4652
+ function eventSyncHandler(ch) {
4653
+ return async (req, url, ctx) => {
4654
+ if (url.pathname !== "/sync/events" || req.method !== "POST")
4655
+ return null;
4330
4656
  try {
4331
4657
  const body = await req.json();
4332
4658
  const events = body.events || [];
4333
- const persistedEvents = await this.contextHandler.persistEvents(events.map((e) => ({
4659
+ const persisted = await ch.persistEvents(events.map((e) => ({
4334
4660
  localId: e.localId,
4335
4661
  type: e.type,
4336
4662
  payload: e.payload,
4337
4663
  createdAt: e.createdAt
4338
- })), "http-sync", token);
4339
- return new Response(JSON.stringify({
4340
- success: true,
4341
- syncedIds: persistedEvents.map((e) => e.localId)
4342
- }), {
4343
- headers: { ...corsHeaders, "Content-Type": "application/json" }
4344
- });
4664
+ })), "http-sync", ctx.tokenPayload);
4665
+ return Response.json({ success: true, syncedIds: persisted.map((e) => e.localId) }, { headers: ctx.corsHeaders });
4345
4666
  } catch (error) {
4346
- return new Response(JSON.stringify({
4347
- success: false,
4348
- error: error.message
4349
- }), {
4350
- status: 500,
4351
- headers: { ...corsHeaders, "Content-Type": "application/json" }
4352
- });
4667
+ return Response.json({ success: false, error: error.message }, { status: 500, headers: ctx.corsHeaders });
4353
4668
  }
4354
- }
4355
- async handleHttpRoute(req, url, tokenPayload, corsHeaders, rawToken) {
4669
+ };
4670
+ }
4671
+ function routeHandler(ch) {
4672
+ return async (req, url, ctx) => {
4673
+ if (!url.pathname.startsWith("/route/"))
4674
+ return null;
4356
4675
  const method = req.method;
4357
- const context = this.contextHandler.getModel().context;
4676
+ const context = ch.getModel().context;
4358
4677
  let matchedRoute = null;
4359
4678
  let routeParams = {};
4360
4679
  for (const element of context.elements) {
@@ -4369,31 +4688,21 @@ class ArcHost {
4369
4688
  }
4370
4689
  }
4371
4690
  if (!matchedRoute) {
4372
- return new Response(JSON.stringify({ error: "Route not found" }), {
4373
- status: 404,
4374
- headers: { ...corsHeaders, "Content-Type": "application/json" }
4375
- });
4691
+ return Response.json({ error: "Route not found" }, { status: 404, headers: ctx.corsHeaders });
4376
4692
  }
4377
4693
  const handler = matchedRoute.getHandler(method);
4378
4694
  if (!handler) {
4379
- return new Response(JSON.stringify({ error: `Method ${method} not allowed` }), {
4380
- status: 405,
4381
- headers: { ...corsHeaders, "Content-Type": "application/json" }
4382
- });
4695
+ return Response.json({ error: `Method ${method} not allowed` }, { status: 405, headers: ctx.corsHeaders });
4383
4696
  }
4384
4697
  if (!matchedRoute.isPublic && matchedRoute.hasProtections) {
4385
- if (!tokenPayload) {
4386
- return new Response(JSON.stringify({ error: "Unauthorized" }), {
4387
- status: 401,
4388
- headers: { ...corsHeaders, "Content-Type": "application/json" }
4389
- });
4698
+ if (!ctx.tokenPayload) {
4699
+ return Response.json({ error: "Unauthorized" }, { status: 401, headers: ctx.corsHeaders });
4390
4700
  }
4391
- this.contextHandler.setAuthToken(rawToken);
4392
4701
  let isAuthorized = false;
4393
4702
  for (const protection of matchedRoute.protections) {
4394
- if (protection.token.name === tokenPayload.tokenType) {
4703
+ if (protection.token.name === ctx.tokenPayload.tokenType) {
4395
4704
  const mockTokenInstance = {
4396
- params: tokenPayload.params,
4705
+ params: ctx.tokenPayload.params,
4397
4706
  getTokenDefinition: () => protection.token
4398
4707
  };
4399
4708
  const allowed = await protection.check(mockTokenInstance);
@@ -4404,68 +4713,399 @@ class ArcHost {
4404
4713
  }
4405
4714
  }
4406
4715
  if (!isAuthorized) {
4407
- return new Response(JSON.stringify({ error: "Forbidden" }), {
4408
- status: 403,
4409
- headers: { ...corsHeaders, "Content-Type": "application/json" }
4410
- });
4716
+ return Response.json({ error: "Forbidden" }, { status: 403, headers: ctx.corsHeaders });
4411
4717
  }
4412
4718
  } else if (!matchedRoute.isPublic && !matchedRoute.hasProtections) {
4413
- if (!tokenPayload) {
4414
- return new Response(JSON.stringify({ error: "Unauthorized" }), {
4415
- status: 401,
4416
- headers: { ...corsHeaders, "Content-Type": "application/json" }
4417
- });
4418
- }
4419
- }
4420
- this.contextHandler.setAuthToken(rawToken);
4421
- const model = this.contextHandler.getModel();
4422
- const adapters = model.getAdapters();
4423
- const authParams = tokenPayload ? { params: tokenPayload.params, tokenName: tokenPayload.tokenType } : undefined;
4424
- const routeContext = matchedRoute.buildContext(adapters, authParams);
4719
+ if (!ctx.tokenPayload) {
4720
+ return Response.json({ error: "Unauthorized" }, { status: 401, headers: ctx.corsHeaders });
4721
+ }
4722
+ }
4723
+ const scoped = new ScopedModel2(ch.getModel(), "request");
4724
+ if (ctx.rawToken)
4725
+ scoped.setToken(ctx.rawToken);
4726
+ const authParams = ctx.tokenPayload ? {
4727
+ params: ctx.tokenPayload.params,
4728
+ tokenName: ctx.tokenPayload.tokenType
4729
+ } : undefined;
4730
+ const routeContext = matchedRoute.buildContext(scoped.getAdapters(), authParams);
4425
4731
  try {
4426
4732
  const response = await handler(routeContext, req, routeParams, url);
4427
4733
  const newHeaders = new Headers(response.headers);
4428
- for (const [key, value] of Object.entries(corsHeaders)) {
4734
+ for (const [key, value] of Object.entries(ctx.corsHeaders))
4429
4735
  newHeaders.set(key, value);
4430
- }
4431
- return new Response(response.body, {
4736
+ const body = response.status >= 300 && response.status < 400 ? null : response.body;
4737
+ return new Response(body, {
4432
4738
  status: response.status,
4433
4739
  statusText: response.statusText,
4434
4740
  headers: newHeaders
4435
4741
  });
4436
4742
  } catch (error) {
4437
- return new Response(JSON.stringify({ error: error.message }), {
4438
- status: 500,
4439
- headers: { ...corsHeaders, "Content-Type": "application/json" }
4440
- });
4743
+ return Response.json({ error: error.message }, { status: 500, headers: ctx.corsHeaders });
4441
4744
  }
4745
+ };
4746
+ }
4747
+ function arcHttpHandlers(ch, cm) {
4748
+ return [
4749
+ healthHandler(cm),
4750
+ commandHandler(ch),
4751
+ queryHandler(ch),
4752
+ streamHandler(ch),
4753
+ eventSyncHandler(ch),
4754
+ routeHandler(ch)
4755
+ ];
4756
+ }
4757
+ function cleanupStreams() {
4758
+ for (const conn of streamConnections.values())
4759
+ conn.unsubscribe();
4760
+ streamConnections.clear();
4761
+ }
4762
+ // src/middleware/ws.ts
4763
+ import { ScopedModel as ScopedModel3 } from "@arcote.tech/arc";
4764
+ var clientViewSubs = new Map;
4765
+ function cleanupClientSubs(clientId) {
4766
+ const subs = clientViewSubs.get(clientId);
4767
+ if (subs) {
4768
+ for (const unsub of subs.values())
4769
+ unsub();
4770
+ clientViewSubs.delete(clientId);
4442
4771
  }
4443
- stop() {
4444
- for (const conn of this.streamConnections.values()) {
4445
- conn.unsubscribe();
4772
+ }
4773
+ function scopeAuthHandler() {
4774
+ return async (client, message, ctx) => {
4775
+ if (message.type !== "scope:auth")
4776
+ return false;
4777
+ const decoded = ctx.verifyToken(message.token);
4778
+ if (decoded) {
4779
+ ctx.connectionManager.setScopeToken(client.id, message.scope, decoded, message.token);
4780
+ }
4781
+ return true;
4782
+ };
4783
+ }
4784
+ function syncEventsHandler() {
4785
+ return async (client, message, ctx) => {
4786
+ if (message.type !== "sync-events")
4787
+ return false;
4788
+ const allTokens = ctx.connectionManager.getAllScopeTokens(client.id);
4789
+ const token = allTokens.length > 0 ? allTokens[0] : null;
4790
+ const persisted = await ctx.contextHandler.persistEvents(message.events, client.id, token);
4791
+ if (persisted.length === 0)
4792
+ return true;
4793
+ for (const c of ctx.connectionManager.getAllClients()) {
4794
+ if (c.id === client.id)
4795
+ continue;
4796
+ const clientTokens = ctx.connectionManager.getAllScopeTokens(c.id);
4797
+ const authorized = filterEventsForTokens(clientTokens, persisted, ctx.contextHandler.getEventDefinitions());
4798
+ if (authorized.length > 0) {
4799
+ ctx.connectionManager.sendToClient(c.id, {
4800
+ type: "events",
4801
+ events: authorized
4802
+ });
4803
+ }
4804
+ }
4805
+ const last = persisted[persisted.length - 1];
4806
+ ctx.connectionManager.updateLastSyncedEventId(client.id, last.hostId);
4807
+ ctx.connectionManager.sendToClient(client.id, {
4808
+ type: "sync-complete",
4809
+ lastHostEventId: last.hostId
4810
+ });
4811
+ return true;
4812
+ };
4813
+ }
4814
+ function requestSyncHandler() {
4815
+ return async (client, message, ctx) => {
4816
+ if (message.type !== "request-sync")
4817
+ return false;
4818
+ const allTokens = ctx.connectionManager.getAllScopeTokens(client.id);
4819
+ const token = allTokens.length > 0 ? allTokens[0] : null;
4820
+ const events = await ctx.contextHandler.getEventsSince(message.lastHostEventId, token);
4821
+ ctx.connectionManager.sendToClient(client.id, {
4822
+ type: "events",
4823
+ events
4824
+ });
4825
+ if (events.length > 0) {
4826
+ const last = events[events.length - 1];
4827
+ ctx.connectionManager.updateLastSyncedEventId(client.id, last.hostId);
4828
+ ctx.connectionManager.sendToClient(client.id, {
4829
+ type: "sync-complete",
4830
+ lastHostEventId: last.hostId
4831
+ });
4832
+ } else {
4833
+ ctx.connectionManager.sendToClient(client.id, {
4834
+ type: "sync-complete",
4835
+ lastHostEventId: message.lastHostEventId || ""
4836
+ });
4837
+ }
4838
+ return true;
4839
+ };
4840
+ }
4841
+ function executeCommandHandler() {
4842
+ return async (client, message, ctx) => {
4843
+ if (message.type !== "execute-command")
4844
+ return false;
4845
+ let rawToken = null;
4846
+ if (client.scopeTokens.size > 0) {
4847
+ rawToken = client.scopeTokens.values().next().value.raw;
4848
+ }
4849
+ try {
4850
+ const result = await ctx.contextHandler.executeCommand(message.commandName, message.params, rawToken);
4851
+ ctx.connectionManager.sendToClient(client.id, {
4852
+ type: "command-result",
4853
+ requestId: message.requestId,
4854
+ result
4855
+ });
4856
+ } catch (error) {
4857
+ ctx.connectionManager.sendToClient(client.id, {
4858
+ type: "command-result",
4859
+ requestId: message.requestId,
4860
+ error: error.message
4861
+ });
4862
+ }
4863
+ return true;
4864
+ };
4865
+ }
4866
+ function querySubscriptionHandler() {
4867
+ return async (client, message, ctx) => {
4868
+ if (message.type === "subscribe-query") {
4869
+ const { subscriptionId, descriptor, scope } = message;
4870
+ const scopeToken = scope ? client.scopeTokens.get(scope) : null;
4871
+ let rawToken = scopeToken?.raw ?? null;
4872
+ if (!rawToken && client.scopeTokens.size > 0) {
4873
+ rawToken = client.scopeTokens.values().next().value.raw;
4874
+ }
4875
+ const scoped = new ScopedModel3(ctx.contextHandler.getModel(), scope ?? "default");
4876
+ if (rawToken)
4877
+ scoped.setToken(rawToken);
4878
+ let lastResultJson = "";
4879
+ const sendData = async () => {
4880
+ try {
4881
+ const data = await scoped.callQuery(descriptor);
4882
+ const json = JSON.stringify(data ?? null);
4883
+ if (json === lastResultJson)
4884
+ return;
4885
+ lastResultJson = json;
4886
+ ctx.connectionManager.sendToClient(client.id, {
4887
+ type: "query-data",
4888
+ subscriptionId,
4889
+ data: data ?? null
4890
+ });
4891
+ } catch (err) {
4892
+ console.error(`[Arc] Query subscription error:`, err);
4893
+ }
4894
+ };
4895
+ sendData();
4896
+ const element = ctx.contextHandler.getModel().context.get(descriptor.element);
4897
+ const eventTypes = element?.getElements?.()?.map((e) => e.name) ?? [];
4898
+ let debounceTimer;
4899
+ const debouncedSend = () => {
4900
+ if (debounceTimer)
4901
+ clearTimeout(debounceTimer);
4902
+ debounceTimer = setTimeout(() => sendData(), 50);
4903
+ };
4904
+ const unsubscribes = [];
4905
+ if (eventTypes.length > 0) {
4906
+ for (const eventType of eventTypes) {
4907
+ unsubscribes.push(ctx.contextHandler.getEventPublisher().subscribe(eventType, debouncedSend));
4908
+ }
4909
+ } else {
4910
+ unsubscribes.push(ctx.contextHandler.getEventPublisher().subscribe("*", debouncedSend));
4911
+ }
4912
+ const cleanup = () => {
4913
+ if (debounceTimer)
4914
+ clearTimeout(debounceTimer);
4915
+ for (const unsub of unsubscribes)
4916
+ unsub();
4917
+ };
4918
+ if (!clientViewSubs.has(client.id)) {
4919
+ clientViewSubs.set(client.id, new Map);
4920
+ }
4921
+ clientViewSubs.get(client.id).set(subscriptionId, cleanup);
4922
+ return true;
4923
+ }
4924
+ if (message.type === "unsubscribe-query") {
4925
+ const subs = clientViewSubs.get(client.id);
4926
+ if (subs) {
4927
+ const unsub = subs.get(message.subscriptionId);
4928
+ if (unsub) {
4929
+ unsub();
4930
+ subs.delete(message.subscriptionId);
4931
+ }
4932
+ }
4933
+ return true;
4934
+ }
4935
+ return false;
4936
+ };
4937
+ }
4938
+ function arcWsHandlers() {
4939
+ return [
4940
+ scopeAuthHandler(),
4941
+ syncEventsHandler(),
4942
+ requestSyncHandler(),
4943
+ executeCommandHandler(),
4944
+ querySubscriptionHandler()
4945
+ ];
4946
+ }
4947
+ // src/create-server.ts
4948
+ async function createArcServer(config) {
4949
+ const jwtSecret = config.jwtSecret || process.env.JWT_SECRET || "arc-host-secret-change-in-production";
4950
+ const port = config.port || 5005;
4951
+ const dbAdapter = config.dbAdapterFactory(config.context);
4952
+ const contextHandler = new ContextHandler(config.context, dbAdapter);
4953
+ await contextHandler.init();
4954
+ const cronScheduler = new CronScheduler(contextHandler);
4955
+ cronScheduler.start();
4956
+ const connectionManager = new ConnectionManager;
4957
+ const corsHeaders = {
4958
+ "Access-Control-Allow-Origin": "*",
4959
+ "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
4960
+ "Access-Control-Allow-Headers": "Content-Type, Authorization, X-Arc-Scope"
4961
+ };
4962
+ function verifyToken(token) {
4963
+ try {
4964
+ const decoded = import_jsonwebtoken.default.verify(token, jwtSecret);
4965
+ if (decoded.tokenName && !decoded.tokenType) {
4966
+ return {
4967
+ tokenType: decoded.tokenName,
4968
+ params: decoded.params || {},
4969
+ iat: decoded.iat,
4970
+ exp: decoded.exp
4971
+ };
4972
+ }
4973
+ return decoded;
4974
+ } catch {
4975
+ try {
4976
+ const parts = token.split(".");
4977
+ if (parts.length !== 3)
4978
+ return null;
4979
+ const payload = JSON.parse(atob(parts[1]));
4980
+ return {
4981
+ tokenType: payload.tokenName,
4982
+ params: payload.params || {},
4983
+ iat: payload.iat,
4984
+ exp: payload.exp
4985
+ };
4986
+ } catch {
4987
+ return null;
4988
+ }
4446
4989
  }
4447
- this.streamConnections.clear();
4448
- this.server?.stop();
4449
4990
  }
4991
+ const defaultHttp = arcHttpHandlers(contextHandler, connectionManager);
4992
+ const defaultWs = arcWsHandlers();
4993
+ const httpHandlers = config.httpHandlers ? [...defaultHttp, ...config.httpHandlers] : defaultHttp;
4994
+ const wsHandlers = config.wsHandlers ? [...defaultWs, ...config.wsHandlers] : defaultWs;
4995
+ const wsCtx = {
4996
+ contextHandler,
4997
+ connectionManager,
4998
+ verifyToken
4999
+ };
5000
+ const server = Bun.serve({
5001
+ port,
5002
+ idleTimeout: 255,
5003
+ async fetch(req, server2) {
5004
+ const url = new URL(req.url);
5005
+ if (req.method === "OPTIONS") {
5006
+ return new Response(null, { headers: corsHeaders });
5007
+ }
5008
+ const authHeader = req.headers.get("Authorization");
5009
+ const rawToken = authHeader?.replace("Bearer ", "") || url.searchParams.get("token");
5010
+ const tokenPayload = rawToken ? verifyToken(rawToken) : null;
5011
+ if (url.pathname === "/ws" && req.headers.get("Upgrade") === "websocket") {
5012
+ if (server2.upgrade(req, { data: { clientId: "" } }))
5013
+ return;
5014
+ return new Response("WebSocket upgrade failed", {
5015
+ status: 500,
5016
+ headers: corsHeaders
5017
+ });
5018
+ }
5019
+ const reqCtx = {
5020
+ rawToken,
5021
+ tokenPayload,
5022
+ corsHeaders
5023
+ };
5024
+ for (const handler of httpHandlers) {
5025
+ const response = await handler(req, url, reqCtx);
5026
+ if (response)
5027
+ return response;
5028
+ }
5029
+ return new Response("Not Found", {
5030
+ status: 404,
5031
+ headers: corsHeaders
5032
+ });
5033
+ },
5034
+ websocket: {
5035
+ open(ws) {
5036
+ connectionManager.addClient(ws);
5037
+ },
5038
+ async message(ws, messageStr) {
5039
+ const client = connectionManager.getClientByWs(ws);
5040
+ if (!client)
5041
+ return;
5042
+ try {
5043
+ const message = JSON.parse(messageStr);
5044
+ for (const handler of wsHandlers) {
5045
+ const handled = await handler(client, message, wsCtx);
5046
+ if (handled)
5047
+ break;
5048
+ }
5049
+ } catch (error) {
5050
+ console.error("Failed to parse WS message:", error);
5051
+ }
5052
+ },
5053
+ close(ws) {
5054
+ const client = connectionManager.getClientByWs(ws);
5055
+ if (client) {
5056
+ cleanupClientSubs(client.id);
5057
+ config.onWsClose?.(client.id);
5058
+ connectionManager.removeClient(client.id);
5059
+ }
5060
+ }
5061
+ }
5062
+ });
5063
+ console.log(`Arc Server running on http://localhost:${port}`);
5064
+ return {
5065
+ server,
5066
+ contextHandler,
5067
+ connectionManager,
5068
+ cronScheduler,
5069
+ stop: () => {
5070
+ cronScheduler.stop();
5071
+ cleanupStreams();
5072
+ server.stop();
5073
+ }
5074
+ };
4450
5075
  }
4451
5076
  // index.ts
4452
5077
  function hostLiveModel(context, dbAdapterFactory) {
4453
- const host = new ArcHost({
5078
+ return createArcServer({
4454
5079
  context,
4455
5080
  dbAdapterFactory
4456
5081
  });
4457
- host.start();
4458
- return host;
4459
5082
  }
4460
5083
  export {
5084
+ syncEventsHandler,
5085
+ streamHandler,
5086
+ scopeAuthHandler,
5087
+ routeHandler,
5088
+ requestSyncHandler,
5089
+ querySubscriptionHandler,
5090
+ queryHandler,
4461
5091
  hostLiveModel,
5092
+ healthHandler,
5093
+ filterEventsForTokens,
4462
5094
  filterEventsForToken,
5095
+ executeCommandHandler,
5096
+ eventSyncHandler,
5097
+ createArcServer,
5098
+ commandHandler,
5099
+ cleanupStreams,
5100
+ cleanupClientSubs,
4463
5101
  canTokenReceiveEvent,
4464
5102
  canTokenEmitEvent,
5103
+ arcWsHandlers,
5104
+ arcHttpHandlers,
5105
+ CronScheduler,
4465
5106
  ContextHandler,
4466
- ConnectionManager,
4467
- ArcHost
5107
+ ConnectionManager
4468
5108
  };
4469
5109
 
4470
- //# debugId=3D741A6ED14793D164756E2164756E21
5110
+ //# debugId=8DE239ADA7ED79E164756E2164756E21
4471
5111
  //# sourceMappingURL=index.js.map