@voltro/runtime 0.85.0 → 0.86.0

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 (160) hide show
  1. package/CHANGELOG.md +666 -0
  2. package/THIRD-PARTY-NOTICES.md +5 -33
  3. package/dist/aggregate.js +42 -0
  4. package/dist/aggregateExtrema.js +80 -0
  5. package/dist/aggregateIvm.js +115 -0
  6. package/dist/analyticsMirror.js +228 -0
  7. package/dist/analyticsSink.js +38 -0
  8. package/dist/apiKeyUsage.js +33 -0
  9. package/dist/apiKeys.js +223 -0
  10. package/dist/apiKeysWiring.js +114 -0
  11. package/dist/approvalBuiltins.js +10 -0
  12. package/dist/approvals.js +269 -0
  13. package/dist/authMiddlewareLayer.js +47 -0
  14. package/dist/bindEvent.js +72 -0
  15. package/dist/bootListener.js +76 -0
  16. package/dist/bootRefusal.js +7 -0
  17. package/dist/bootStoreCodec.js +70 -0
  18. package/dist/businessRules.js +67 -0
  19. package/dist/changeClaimsTable.js +12 -0
  20. package/dist/circuitBreaker.js +72 -0
  21. package/dist/columnOnUpdate.js +23 -0
  22. package/dist/composeAnalytics.js +108 -0
  23. package/dist/computedQuery.js +4 -0
  24. package/dist/connectionBuiltins.js +81 -0
  25. package/dist/connectionCredentials.js +66 -0
  26. package/dist/connectionState.js +34 -0
  27. package/dist/connectionVault.js +410 -0
  28. package/dist/connectionsFacade.js +54 -0
  29. package/dist/coordinatedSchedule.js +139 -0
  30. package/dist/crdtHistory.js +138 -0
  31. package/dist/crdtMerge.js +179 -0
  32. package/dist/crdtVersionsTable.js +25 -0
  33. package/dist/credentialBound.js +12 -0
  34. package/dist/crud.js +72 -0
  35. package/dist/dataStore.js +679 -0
  36. package/dist/dataloader.js +61 -0
  37. package/dist/declaredTablesGuard.js +48 -0
  38. package/dist/defineExecutor.js +4 -0
  39. package/dist/dependencyGraph.js +32 -0
  40. package/dist/devices.js +228 -0
  41. package/dist/devicesWiring.js +168 -0
  42. package/dist/dialectReplicationAdapter.js +29 -0
  43. package/dist/dispatcher.js +447 -0
  44. package/dist/effectStore.js +71 -0
  45. package/dist/eventBus.js +260 -0
  46. package/dist/eventPublish.js +49 -0
  47. package/dist/events.js +193 -0
  48. package/dist/expectation.js +52 -0
  49. package/dist/expectationEvaluator.js +308 -0
  50. package/dist/experiment.js +96 -0
  51. package/dist/experimentEvaluator.js +245 -0
  52. package/dist/externalizedTasks.js +25 -0
  53. package/dist/fieldCipher.js +61 -0
  54. package/dist/fingerprint.js +21 -0
  55. package/dist/finops.js +42 -0
  56. package/dist/finopsAccountant.js +216 -0
  57. package/dist/handlerBody.js +8 -0
  58. package/dist/healthProbes.js +55 -0
  59. package/dist/httpClientSsrf.js +49 -0
  60. package/dist/httpClientTracing.js +11 -0
  61. package/dist/httpCompression.js +76 -0
  62. package/dist/httpKeepAlive.js +12 -0
  63. package/dist/httpRouteMounts.js +47 -0
  64. package/dist/idempotencyStore.js +70 -0
  65. package/dist/idleDetector.js +21 -0
  66. package/dist/index.d.ts +88 -69
  67. package/dist/index.js +144 -15292
  68. package/dist/insertRequiredColumns.js +19 -0
  69. package/dist/inspectWorkflow.js +1 -0
  70. package/dist/instanceMembership.js +98 -0
  71. package/dist/ivm.js +125 -0
  72. package/dist/kvDataStore.js +86 -0
  73. package/dist/kvFacade.js +32 -0
  74. package/dist/matcher.js +304 -0
  75. package/dist/memoryColumnDefaults.js +11 -0
  76. package/dist/memoryQueryJoins.js +114 -0
  77. package/dist/metrics.js +161 -0
  78. package/dist/observability.js +123 -0
  79. package/dist/observedGraph.js +52 -0
  80. package/dist/originGuard.js +116 -0
  81. package/dist/outbox.js +352 -0
  82. package/dist/pluginRefRules.js +65 -0
  83. package/dist/policyGuardResolver.js +55 -0
  84. package/dist/preview.js +62 -0
  85. package/dist/projectedRows.js +12 -0
  86. package/dist/provenance.js +63 -0
  87. package/dist/rangeIndex.js +89 -0
  88. package/dist/rawReadScope.js +17 -0
  89. package/dist/reaction.js +85 -0
  90. package/dist/reactiveConfig.js +11 -0
  91. package/dist/readClassification.js +57 -0
  92. package/dist/readRecorder.js +66 -0
  93. package/dist/rebac.js +104 -0
  94. package/dist/rebacInterceptor.js +81 -0
  95. package/dist/registry.js +29 -0
  96. package/dist/relevantFields.js +65 -0
  97. package/dist/replicaHealth.js +42 -0
  98. package/dist/replicaLocality.js +44 -0
  99. package/dist/replicaObservations.js +121 -0
  100. package/dist/replicaSelection.js +32 -0
  101. package/dist/replicatedDataStore.js +239 -0
  102. package/dist/requestLoader.js +7 -0
  103. package/dist/resumeCensus.js +28 -0
  104. package/dist/revalidation.js +22 -0
  105. package/dist/rowFilter.js +65 -0
  106. package/dist/rowStateBytes.js +45 -0
  107. package/dist/rpcBodyCap.js +63 -0
  108. package/dist/rpcConnections.js +83 -0
  109. package/dist/rpcDefectDiagnostics.js +15 -0
  110. package/dist/rpcServer.js +992 -0
  111. package/dist/runAsSystem.js +23 -0
  112. package/dist/rywPositionStore.js +84 -0
  113. package/dist/schedule.js +23 -0
  114. package/dist/scheduleBackfillRequests.js +151 -0
  115. package/dist/scheduleClaimsTable.js +11 -0
  116. package/dist/scheduleRunsTable.js +37 -0
  117. package/dist/scheduler.js +835 -0
  118. package/dist/schemaRegistry.js +65 -0
  119. package/dist/secretsResolver.js +35 -0
  120. package/dist/securityHeaders.js +33 -0
  121. package/dist/servePipeline.js +305 -0
  122. package/dist/serverOnlyAudit.js +66 -0
  123. package/dist/serverTracer.js +15 -0
  124. package/dist/serviceCredentials.js +40 -0
  125. package/dist/singleNodeOrchestrator.js +91 -0
  126. package/dist/sourceResolution.js +42 -0
  127. package/dist/storeMiddleware.js +880 -0
  128. package/dist/storeQuery.js +204 -0
  129. package/dist/storeRead.js +10 -0
  130. package/dist/subscriber.js +7 -0
  131. package/dist/subscriptionOutbox.js +139 -0
  132. package/dist/subscriptionRegistry.js +82 -0
  133. package/dist/subscriptionResume.js +132 -0
  134. package/dist/subscriptionSocketMetrics.js +65 -0
  135. package/dist/systemSubject.js +5 -0
  136. package/dist/tenantScope.js +23 -0
  137. package/dist/tenantSelection.js +27 -0
  138. package/dist/timeline.js +105 -0
  139. package/dist/traceContext.js +5 -0
  140. package/dist/traceSink.js +63 -0
  141. package/dist/traceSpansTable.js +29 -0
  142. package/dist/traceparent.js +34 -0
  143. package/dist/transactionSubject.js +12 -0
  144. package/dist/trustedProxy.js +155 -0
  145. package/dist/undo.js +129 -0
  146. package/dist/undoBuiltins.js +69 -0
  147. package/dist/undoCapture.js +74 -0
  148. package/dist/undoLogTable.js +16 -0
  149. package/dist/upsertBinding.js +20 -0
  150. package/dist/wakeOrchestrator.js +34 -0
  151. package/dist/wakeRegistry.js +71 -0
  152. package/dist/wakeRouter.js +63 -0
  153. package/dist/wakeSupervisor.js +62 -0
  154. package/dist/wakeupsTable.js +12 -0
  155. package/dist/webSocketGatewayRuntime.js +114 -0
  156. package/dist/webhookVerification.js +18 -0
  157. package/dist/wireColumnStrip.js +84 -0
  158. package/dist/workflowReadBuiltins.js +87 -0
  159. package/dist/workflowRuntime.js +708 -0
  160. package/package.json +7 -7
@@ -0,0 +1,34 @@
1
+ //#region src/connectionState.ts
2
+ var e = Symbol.for("@voltro/runtime/connectionState"), t = globalThis, n = t[e] ??= /* @__PURE__ */ new WeakMap(), r = (e, t) => {
3
+ let r = n.get(e);
4
+ if (r === void 0 && !e.signal.aborted) {
5
+ r = /* @__PURE__ */ new Map(), n.set(e, r);
6
+ let t = r;
7
+ e.signal.addEventListener("abort", () => {
8
+ t.clear(), n.delete(e);
9
+ }, { once: !0 });
10
+ }
11
+ let i = r, a = (n) => {
12
+ let r = () => {
13
+ !e.signal.aborted && i !== void 0 && n(i);
14
+ };
15
+ t === void 0 ? r() : t(async () => {
16
+ r();
17
+ });
18
+ };
19
+ return {
20
+ get: (t) => e.signal.aborted ? void 0 : structuredClone(i?.get(t)),
21
+ has: (t) => !e.signal.aborted && i?.has(t) === !0,
22
+ set: (e, t) => {
23
+ let n = structuredClone(t);
24
+ a((t) => {
25
+ t.set(e, n);
26
+ });
27
+ },
28
+ delete: (e) => a((t) => {
29
+ t.delete(e);
30
+ })
31
+ };
32
+ };
33
+ //#endregion
34
+ export { r as makeConnectionState };
@@ -0,0 +1,410 @@
1
+ import { decryptField as e, encryptField as t, getFieldCipher as n } from "./fieldCipher.js";
2
+ import { and as r, eq as i, fieldBinding as a, generateId as o, id as s, json as c, lte as l, table as u, text as d, timestamp as f } from "@voltro/database";
3
+ import { ConnectionHandshakeFailed as p } from "@voltro/protocol";
4
+ import { createLogger as m } from "@voltro/logger";
5
+ //#region src/connectionVault.ts
6
+ var h = m({ scope: "voltro:connections" }), g = "_voltro_connections", _ = {
7
+ kind: "typeid",
8
+ prefix: "conn"
9
+ }, v = u(g, {
10
+ id: s({ prefix: "conn" }),
11
+ connectionId: d(),
12
+ subjectId: d(),
13
+ tenantId: d().nullable(),
14
+ kind: d().oneOf(["oauth2", "pat"]),
15
+ status: d().oneOf([
16
+ "connected",
17
+ "expired",
18
+ "revoked",
19
+ "error"
20
+ ]),
21
+ accessToken: d(),
22
+ refreshToken: d().nullable(),
23
+ expiresAt: f().nullable(),
24
+ scopes: d().nullable(),
25
+ accountId: d().nullable(),
26
+ accountLabel: d().nullable(),
27
+ lastError: d().nullable(),
28
+ refreshLockedUntil: f(),
29
+ metadata: c().nullable(),
30
+ connectedAt: f(),
31
+ updatedAt: f()
32
+ }).unique("byConnectionSubject", ["connectionId", "subjectId"]).index(["subjectId"]), y = "_voltro_connection_grants", ee = {
33
+ kind: "typeid",
34
+ prefix: "cgrant"
35
+ }, b = u(y, {
36
+ id: s({ prefix: "cgrant" }),
37
+ state: d().unique(),
38
+ connectionId: d(),
39
+ subjectId: d(),
40
+ tenantId: d().nullable(),
41
+ codeVerifier: d().nullable(),
42
+ redirectTo: d(),
43
+ createdAt: f(),
44
+ expiresAt: f()
45
+ }).index(["expiresAt"]), x = (e) => typeof e == "function" ? e() : e, te = /^[a-z][a-z0-9-]*$/, ne = (e) => {
46
+ if (!te.test(e.id)) throw Error(`defineConnection: id "${e.id}" must be lowercase kebab-case (it becomes a URL path segment).`);
47
+ if (e.kind === "oauth2") {
48
+ for (let t of ["authorizeUrl", "tokenUrl"]) if (typeof e[t] != "string" || e[t].length === 0) throw Error(`defineConnection("${e.id}"): \`${t}\` is required for kind 'oauth2'.`);
49
+ for (let t of ["clientId", "clientSecret"]) {
50
+ let n = e[t];
51
+ if (typeof n != "function" && (typeof n != "string" || n.length === 0)) throw Error(`defineConnection("${e.id}"): \`${t}\` is required for kind 'oauth2' — a string, or a thunk (\`() => serverEnv.X\`) read when the connection is used.`);
52
+ }
53
+ }
54
+ return e;
55
+ }, S = (e) => e.label ?? e.id, C = (e) => {
56
+ if (e.length !== 0 && n() === void 0) throw Error(`connections: ${e.length} connection(s) declared (${e.map((e) => e.id).join(", ")}) but no field cipher is registered. Connection tokens are stored encrypted and there is no plaintext fallback. Set VOLTRO_FIELD_ENCRYPTION_KEY (\`voltro secret generate field-encryption\`; \`voltro dev\` mints one), or call setFieldCipher at boot.`);
57
+ }, w = (e) => {
58
+ let t = /* @__PURE__ */ new Map();
59
+ for (let n of e) {
60
+ if (t.has(n.id)) throw Error(`connections: duplicate connection id "${n.id}" — each *.connection.ts must declare a distinct id.`);
61
+ t.set(n.id, n);
62
+ }
63
+ return {
64
+ get: (e) => t.get(e),
65
+ list: () => [...t.values()]
66
+ };
67
+ }, T = (e) => {
68
+ if (e == null) return null;
69
+ let t = e instanceof Date ? e : new Date(String(e));
70
+ return Number.isNaN(t.getTime()) ? null : t;
71
+ }, E = (e) => T(e)?.toISOString() ?? null, D = (e) => String(e ?? "").match(/[^ ]+/g) ?? [], O = (e, t) => t === void 0 ? {
72
+ connectionId: e.id,
73
+ kind: e.kind,
74
+ label: S(e),
75
+ status: "disconnected",
76
+ accountId: null,
77
+ accountLabel: null,
78
+ scopes: [],
79
+ expiresAt: null,
80
+ lastError: null,
81
+ connectedAt: null
82
+ } : {
83
+ connectionId: e.id,
84
+ kind: e.kind,
85
+ label: S(e),
86
+ status: String(t.status ?? "connected"),
87
+ accountId: t.accountId ?? null,
88
+ accountLabel: t.accountLabel ?? null,
89
+ scopes: D(t.scopes),
90
+ expiresAt: E(t.expiresAt),
91
+ lastError: t.lastError ?? null,
92
+ connectedAt: E(t.connectedAt)
93
+ }, k = async (e, t, n) => (await e.query({
94
+ table: g,
95
+ predicate: r(i("connectionId", t), i("subjectId", n)),
96
+ order: [],
97
+ take: 1,
98
+ skip: void 0,
99
+ projection: void 0
100
+ }))[0], A = async (e) => {
101
+ let n = (e.now ?? (() => /* @__PURE__ */ new Date()))(), s = e.tokens.expiresAt === void 0 ? null : new Date(e.tokens.expiresAt), c = await e.store.query({
102
+ table: g,
103
+ predicate: r(i("connectionId", e.definition.id), i("subjectId", e.subjectId)),
104
+ order: [],
105
+ take: 1,
106
+ skip: void 0,
107
+ projection: ["id"]
108
+ }), l = typeof c[0]?.id == "string" ? c[0].id : o(_, g), u = {
109
+ id: l,
110
+ connectionId: e.definition.id,
111
+ subjectId: e.subjectId,
112
+ tenantId: e.tenantId,
113
+ kind: e.definition.kind,
114
+ status: "connected",
115
+ accessToken: t(e.tokens.accessToken, a(g, "accessToken", l)),
116
+ refreshToken: e.tokens.refreshToken === void 0 ? null : t(e.tokens.refreshToken, a(g, "refreshToken", l)),
117
+ expiresAt: s,
118
+ scopes: e.tokens.scopes.length > 0 ? e.tokens.scopes.join(" ") : null,
119
+ accountId: e.account?.accountId ?? null,
120
+ accountLabel: e.account?.accountLabel ?? null,
121
+ lastError: null,
122
+ refreshLockedUntil: /* @__PURE__ */ new Date(0),
123
+ metadata: e.account?.metadata ?? null,
124
+ connectedAt: n,
125
+ updatedAt: n
126
+ };
127
+ return re(e.store, u);
128
+ }, re = (e, t) => e.upsert(g, t, {
129
+ conflictColumns: ["connectionId", "subjectId"],
130
+ update: [
131
+ "kind",
132
+ "status",
133
+ "accessToken",
134
+ "refreshToken",
135
+ "expiresAt",
136
+ "scopes",
137
+ "accountId",
138
+ "accountLabel",
139
+ "lastError",
140
+ "refreshLockedUntil",
141
+ "metadata",
142
+ "updatedAt"
143
+ ]
144
+ }), j = async (e, t, n) => {
145
+ let r = await k(e, t, n);
146
+ return r !== void 0 && e.delete(g, r.id);
147
+ }, M = async (e, t, n) => {
148
+ let r = await e.query({
149
+ table: g,
150
+ predicate: i("subjectId", n),
151
+ order: [],
152
+ take: void 0,
153
+ skip: void 0,
154
+ projection: void 0
155
+ }), a = new Map(r.map((e) => [String(e.connectionId), e]));
156
+ return t.list().map((e) => O(e, a.get(e.id)));
157
+ }, N = (e, t) => globalThis.fetch(e, t), P = async (e, t, n) => {
158
+ let r = e.fetchImpl ?? N, i, a;
159
+ try {
160
+ let n = await r(e.tokenUrl, {
161
+ method: "POST",
162
+ headers: {
163
+ "content-type": "application/x-www-form-urlencoded",
164
+ accept: "application/json"
165
+ },
166
+ body: new URLSearchParams(t).toString()
167
+ });
168
+ i = n.status, a = await n.text();
169
+ } catch (t) {
170
+ throw new p({
171
+ connectionId: e.id,
172
+ reason: `token endpoint unreachable: ${String(t)}`,
173
+ transient: !0
174
+ });
175
+ }
176
+ let o;
177
+ try {
178
+ o = JSON.parse(a);
179
+ } catch {
180
+ o = {};
181
+ }
182
+ if (i < 200 || i >= 300) throw new p({
183
+ connectionId: e.id,
184
+ reason: `token endpoint ${i}: ${o.error ?? ""} ${o.error_description ?? a.slice(0, 200)}`.trim(),
185
+ transient: i >= 500
186
+ });
187
+ if (typeof o.access_token != "string" || o.access_token.length === 0) throw new p({
188
+ connectionId: e.id,
189
+ reason: "token response carried no access_token",
190
+ transient: !1
191
+ });
192
+ return {
193
+ accessToken: o.access_token,
194
+ ...typeof o.refresh_token == "string" ? { refreshToken: o.refresh_token } : {},
195
+ ...Number.isFinite(o.expires_in) ? { expiresAt: n() + o.expires_in * 1e3 } : {},
196
+ scopes: (o.scope ?? e.scopes?.join(" ") ?? "").match(/[^ ]+/g) ?? []
197
+ };
198
+ }, F = (e, t, n = Date.now) => P(e, {
199
+ grant_type: "authorization_code",
200
+ client_id: x(e.clientId),
201
+ client_secret: x(e.clientSecret),
202
+ code: t.code,
203
+ redirect_uri: t.redirectUri,
204
+ ...t.codeVerifier ? { code_verifier: t.codeVerifier } : {}
205
+ }, n), I = (e, t, n = Date.now) => P(e, {
206
+ grant_type: "refresh_token",
207
+ client_id: x(e.clientId),
208
+ client_secret: x(e.clientSecret),
209
+ refresh_token: t
210
+ }, n), L = 6e4, R = 3e4, z = class extends Error {
211
+ connectionId;
212
+ subjectId;
213
+ _tag = "ConnectionNotConnected";
214
+ constructor(e, t) {
215
+ super(`connection "${e}" is not connected for this subject`), this.connectionId = e, this.subjectId = t, this.name = "ConnectionNotConnected";
216
+ }
217
+ }, B = (e) => new Promise((t) => {
218
+ setTimeout(t, e);
219
+ }), V = /* @__PURE__ */ new Map(), H = (e, t) => `${e}\u0000${t}`, U = () => {
220
+ V.clear();
221
+ }, W = (t, n) => ({
222
+ connectionId: t.id,
223
+ kind: t.kind,
224
+ accessToken: e(String(n.accessToken), a(g, "accessToken", n.id)),
225
+ expiresAt: T(n.expiresAt),
226
+ scopes: D(n.scopes),
227
+ accountId: n.accountId ?? null,
228
+ accountLabel: n.accountLabel ?? null
229
+ }), G = (e, t, n) => {
230
+ let r = T(e.expiresAt);
231
+ return r !== null && r.getTime() - n < t;
232
+ }, K = async (e) => {
233
+ let t = e.now ?? Date.now, { store: n, definition: r, subjectId: i } = e, a = await k(n, r.id, i);
234
+ if (a === void 0) throw new z(r.id, i);
235
+ if (r.kind === "pat" || !G(a, r.refreshSkewMs ?? 6e4, t())) return W(r, a);
236
+ let o = H(r.id, i), s = V.get(o);
237
+ if (s !== void 0) return s;
238
+ let c = q({
239
+ ...e,
240
+ now: t,
241
+ row: a
242
+ }).finally(() => {
243
+ V.delete(o);
244
+ });
245
+ return V.set(o, c), c;
246
+ }, q = async (n) => {
247
+ let { store: o, subjectId: s, now: c } = n, u = n.definition, d = n.sleep ?? B, f = u.refreshSkewMs ?? 6e4, m = n.row.id;
248
+ if (await o.updateMany("_voltro_connections", { refreshLockedUntil: new Date(c() + 3e4) }, { where: r(i("id", m), l("refreshLockedUntil", new Date(c()))) }) === 0) {
249
+ for (let e = 0; e < 10; e++) {
250
+ await d(200);
251
+ let e = await k(o, u.id, s);
252
+ if (e === void 0) throw new z(u.id, s);
253
+ if (!G(e, f, c())) return W(u, e);
254
+ }
255
+ throw new p({
256
+ connectionId: u.id,
257
+ reason: "another replica held the refresh lease and did not publish a fresh token in time",
258
+ transient: !0
259
+ });
260
+ }
261
+ let _ = n.row.refreshToken;
262
+ if (_ == null) throw await o.update(g, m, {
263
+ status: "expired",
264
+ lastError: "access token expired and no refresh token is on file — reconnect required",
265
+ refreshLockedUntil: /* @__PURE__ */ new Date(0),
266
+ updatedAt: new Date(c())
267
+ }), new p({
268
+ connectionId: u.id,
269
+ reason: "access token expired and no refresh token is on file — reconnect required",
270
+ transient: !1
271
+ });
272
+ let v;
273
+ try {
274
+ v = await I(u, e(String(_), a(g, "refreshToken", m)), c);
275
+ } catch (e) {
276
+ let n = e instanceof p ? e : new p({
277
+ connectionId: u.id,
278
+ reason: String(e),
279
+ transient: !0
280
+ });
281
+ throw await o.update(g, m, {
282
+ status: n.transient ? "error" : "revoked",
283
+ lastError: n.reason,
284
+ refreshLockedUntil: /* @__PURE__ */ new Date(0),
285
+ updatedAt: new Date(c()),
286
+ ...n.transient ? {} : {
287
+ accessToken: t("", a(g, "accessToken", m)),
288
+ refreshToken: null,
289
+ expiresAt: null
290
+ }
291
+ }), h.warn("connection refresh failed", {
292
+ connectionId: u.id,
293
+ transient: n.transient,
294
+ reason: n.reason
295
+ }), n;
296
+ }
297
+ let y = {
298
+ status: "connected",
299
+ accessToken: t(v.accessToken, a(g, "accessToken", m)),
300
+ ...v.refreshToken === void 0 ? {} : { refreshToken: t(v.refreshToken, a(g, "refreshToken", m)) },
301
+ expiresAt: v.expiresAt === void 0 ? null : new Date(v.expiresAt),
302
+ ...v.scopes.length > 0 ? { scopes: v.scopes.join(" ") } : {},
303
+ lastError: null,
304
+ refreshLockedUntil: /* @__PURE__ */ new Date(0),
305
+ updatedAt: new Date(c())
306
+ };
307
+ return W(u, await o.update(g, m, y) ?? {
308
+ ...n.row,
309
+ ...y
310
+ });
311
+ }, J = (e = 32) => {
312
+ let t = new Uint8Array(e);
313
+ return globalThis.crypto.getRandomValues(t), Array.from(t, (e) => e.toString(16).padStart(2, "0")).join("");
314
+ }, Y = (e) => {
315
+ let t = "";
316
+ for (let n of e) t += String.fromCharCode(n);
317
+ return btoa(t).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
318
+ }, ie = async (e) => {
319
+ let t = await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(e));
320
+ return Y(new Uint8Array(t));
321
+ }, X = 6e5, Z = (e) => `/_voltro/connections/${e}/callback`, Q = (e) => e === void 0 || e.length === 0 || !e.startsWith("/") || e.startsWith("//") || e.startsWith("/\\") ? "/" : e, $ = (e, t) => e.redirectUri ?? `${t.replace(/\/+$/, "")}${Z(e.id)}`, ae = async (e) => {
322
+ let n = (e.now ?? (() => /* @__PURE__ */ new Date()))(), { definition: r } = e, i = J(), s = r.pkce === !1 ? null : J(48), c = $(r, e.publicUrl), l = o(ee, y);
323
+ await e.store.insert(y, {
324
+ id: l,
325
+ state: i,
326
+ connectionId: r.id,
327
+ subjectId: e.subjectId,
328
+ tenantId: e.tenantId,
329
+ codeVerifier: s === null ? null : t(s, a(y, "codeVerifier", l)),
330
+ redirectTo: Q(e.redirectTo),
331
+ createdAt: n,
332
+ expiresAt: new Date(n.getTime() + X)
333
+ });
334
+ let u = new URLSearchParams({
335
+ response_type: "code",
336
+ client_id: x(r.clientId),
337
+ redirect_uri: c,
338
+ state: i,
339
+ ...r.scopes && r.scopes.length > 0 ? { scope: r.scopes.join(" ") } : {},
340
+ ...r.authorizeParams ?? {}
341
+ });
342
+ s !== null && (u.set("code_challenge", await ie(s)), u.set("code_challenge_method", "S256"));
343
+ let d = r.authorizeUrl.includes("?") ? "&" : "?";
344
+ return {
345
+ authorizeUrl: `${r.authorizeUrl}${d}${u.toString()}`,
346
+ state: i
347
+ };
348
+ }, oe = async (t) => {
349
+ let n = (t.now ?? (() => /* @__PURE__ */ new Date()))(), r = (await t.store.query({
350
+ table: y,
351
+ predicate: i("state", t.state),
352
+ order: [],
353
+ take: 1,
354
+ skip: void 0,
355
+ projection: void 0
356
+ }))[0];
357
+ if (r === void 0) throw new p({
358
+ connectionId: "unknown",
359
+ reason: "unknown or already-used handshake state",
360
+ transient: !1
361
+ });
362
+ await t.store.delete(y, r.id);
363
+ let o = String(r.connectionId), s = T(r.expiresAt);
364
+ if (s !== null && s.getTime() < n.getTime()) throw new p({
365
+ connectionId: o,
366
+ reason: "handshake expired — start again",
367
+ transient: !1
368
+ });
369
+ let c = t.registry.get(o);
370
+ if (c === void 0 || c.kind !== "oauth2") throw new p({
371
+ connectionId: o,
372
+ reason: "no oauth2 connection is declared under that id",
373
+ transient: !1
374
+ });
375
+ let l = r.codeVerifier === null || r.codeVerifier === void 0 ? null : e(String(r.codeVerifier), a(y, "codeVerifier", r.id)), u = await F(c, {
376
+ code: t.code,
377
+ redirectUri: $(c, t.publicUrl),
378
+ codeVerifier: l
379
+ }, () => n.getTime()), d = c.identify === void 0 ? void 0 : await c.identify(u).catch((e) => {
380
+ h.warn("connection identify failed; storing without account details", {
381
+ connectionId: o,
382
+ reason: String(e)
383
+ });
384
+ });
385
+ return await A({
386
+ store: t.store,
387
+ definition: c,
388
+ subjectId: String(r.subjectId),
389
+ tenantId: r.tenantId ?? null,
390
+ tokens: u,
391
+ ...d === void 0 ? {} : { account: d },
392
+ now: () => n
393
+ }), {
394
+ connectionId: o,
395
+ redirectTo: Q(String(r.redirectTo ?? "/"))
396
+ };
397
+ }, se = async (e, t = /* @__PURE__ */ new Date()) => {
398
+ let n = await e.query({
399
+ table: y,
400
+ predicate: l("expiresAt", t),
401
+ order: [],
402
+ take: void 0,
403
+ skip: void 0,
404
+ projection: ["id"]
405
+ }), r = 0;
406
+ for (let t of n) await e.delete("_voltro_connection_grants", t.id) && r++;
407
+ return r;
408
+ };
409
+ //#endregion
410
+ export { g as CONNECTIONS_TABLE, y as CONNECTION_GRANTS_TABLE, z as ConnectionNotConnected, L as DEFAULT_REFRESH_SKEW_MS, X as GRANT_TTL_MS, R as REFRESH_LEASE_MS, b as _voltroConnectionGrantsTable, v as _voltroConnectionsTable, C as assertConnectionCipherConfigured, ae as beginOAuthConnect, U as clearConnectionRefreshFlights, oe as completeOAuthConnect, Z as connectionCallbackPath, S as connectionLabel, ne as defineConnection, F as exchangeAuthorizationCode, j as forgetCredential, M as listConnectionStates, w as makeConnectionRegistry, x as oauthCredentialValue, O as projectConnectionState, I as refreshAccessToken, K as resolveConnection, $ as resolveRedirectUri, Q as sanitizeRedirectTo, A as storeCredential, se as sweepExpiredGrants };
@@ -0,0 +1,54 @@
1
+ import { ConnectionNotConnected as e, listConnectionStates as t, resolveConnection as n } from "./connectionVault.js";
2
+ //#region src/connectionsFacade.ts
3
+ var r, i = (e) => {
4
+ r = e;
5
+ }, a = () => r, o = (e, t) => {
6
+ let n = r;
7
+ return n === void 0 ? Promise.reject(/* @__PURE__ */ Error(`resolveConnectionForSubject("${t}"): no connection vault is registered — the app declares no \`*.connection.ts\`, or this call ran before boot completed.`)) : n(e, t);
8
+ }, s = (t) => async (r, i) => {
9
+ let a = t.registry.get(i);
10
+ if (a === void 0) throw Error(`connection "${i}" is not declared. Declared: ${t.registry.list().map((e) => e.id).join(", ") || "(none)"}.`);
11
+ let o = r.id ?? null;
12
+ if (o === null || o === "") throw new e(i, "<anonymous>");
13
+ return n({
14
+ store: t.store,
15
+ definition: a,
16
+ subjectId: o
17
+ });
18
+ }, c = (r) => {
19
+ let i = r.subject.id ?? null, a = async (t) => {
20
+ let a = r.registry.get(t);
21
+ if (a === void 0) throw Error(`ctx.connections.get("${t}"): no such connection is declared. Declared: ${r.registry.list().map((e) => e.id).join(", ") || "(none)"}.`);
22
+ if (i === null || i === "") throw new e(t, "<anonymous>");
23
+ return n({
24
+ store: r.store,
25
+ definition: a,
26
+ subjectId: i
27
+ });
28
+ };
29
+ return {
30
+ get: a,
31
+ tryGet: async (t) => {
32
+ try {
33
+ return await a(t);
34
+ } catch (t) {
35
+ if (t instanceof e) return null;
36
+ throw t;
37
+ }
38
+ },
39
+ list: async () => i === null || i === "" ? r.registry.list().map((e) => ({
40
+ connectionId: e.id,
41
+ kind: e.kind,
42
+ label: e.label ?? e.id,
43
+ status: "disconnected",
44
+ accountId: null,
45
+ accountLabel: null,
46
+ scopes: [],
47
+ expiresAt: null,
48
+ lastError: null,
49
+ connectedAt: null
50
+ })) : t(r.store, r.registry, i)
51
+ };
52
+ };
53
+ //#endregion
54
+ export { a as getConnectionResolver, s as makeConnectionResolver, c as makeConnectionsFacade, o as resolveConnectionForSubject, i as setConnectionResolver };
@@ -0,0 +1,139 @@
1
+ import { cronForInterval as e, externalizationRefusal as t, registerExternalizedTask as n, unregisterExternalizedTask as r } from "./externalizedTasks.js";
2
+ //#region src/coordinatedSchedule.ts
3
+ var i = (e) => {
4
+ if (e === void 0) return null;
5
+ let t = e.nextDueInMs;
6
+ return {
7
+ idle: e.idle === !0,
8
+ ...t === null ? { nextDueInMs: null } : typeof t == "number" && Number.isFinite(t) ? { nextDueInMs: t } : {}
9
+ };
10
+ }, a = /* @__PURE__ */ new WeakMap(), o = (e) => a.get(e)?.() ?? null, s = 3e4, c = (e = process.env) => {
11
+ let t = e.VOLTRO_POLL_CEILING_MS;
12
+ if (t === void 0) return s;
13
+ let n = Number(t);
14
+ return Number.isFinite(n) && n > 0 ? n : s;
15
+ }, l = (o, s, l, d) => {
16
+ let f = Math.max(s, d.maxIdleIntervalMs ?? c()), p = !1, m, h = !1, g = s, _ = !1, v = !1, y = 0, b = null, x = {
17
+ ticks: 0,
18
+ claimWins: 0,
19
+ claimLosses: 0,
20
+ claimFailures: 0,
21
+ taskFailures: 0,
22
+ wakes: 0,
23
+ coalescedWakes: 0,
24
+ lastTickAt: null,
25
+ lastWakeAt: null,
26
+ lastDecision: "starting",
27
+ lastOutcome: null
28
+ }, S = (e) => {
29
+ p || (m !== void 0 && clearTimeout(m), m = void 0, b = null, e === "disarm" ? h = !0 : (h = !1, g = Math.max(0, Math.min(e, f)), b = Date.now() + g, m = setTimeout(() => {
30
+ C();
31
+ }, g), typeof m.unref == "function" && m.unref()));
32
+ }, C = async () => {
33
+ if (p) return;
34
+ if (_) {
35
+ v = !0;
36
+ return;
37
+ }
38
+ x.ticks++, x.lastTickAt = Date.now(), x.lastOutcome = null, b = null, _ = !0;
39
+ let e = new Date(Math.floor(Date.now() / s) * s);
40
+ try {
41
+ let t = !1;
42
+ try {
43
+ t = await d.coordinator.tryClaim(o, e, s);
44
+ } catch (e) {
45
+ x.claimFailures++, x.lastDecision = "claim-failed", d.log?.warn("coordinated schedule: claim check failed — skipping this tick", {
46
+ task: o,
47
+ reason: e?.message ?? String(e)
48
+ }), S(s);
49
+ return;
50
+ }
51
+ if (!t) {
52
+ x.claimLosses++, x.lastDecision = "claim-lost", S(s);
53
+ return;
54
+ }
55
+ let n;
56
+ x.claimWins++;
57
+ try {
58
+ n = await l();
59
+ } catch (e) {
60
+ x.taskFailures++, x.lastDecision = "task-failed", d.log?.warn("coordinated schedule: task threw", {
61
+ task: o,
62
+ reason: e?.message ?? String(e)
63
+ }), S(s);
64
+ return;
65
+ }
66
+ x.lastOutcome = i(n);
67
+ let r = u(x.lastOutcome ?? void 0, s, g, f, d.disarmWhenIdle ?? !1);
68
+ x.lastDecision = r === "disarm" ? "idle-disarmed" : n?.idle ? "idle-backoff" : "active", S(r);
69
+ } finally {
70
+ _ = !1, v && !p && (v = !1, S(0));
71
+ }
72
+ }, w = async () => {
73
+ x.ticks++, x.lastTickAt = Date.now(), x.lastOutcome = null;
74
+ let e = new Date(Math.floor(Date.now() / s) * s), t;
75
+ try {
76
+ t = await (d.externalClaim ?? d.coordinator).tryClaim(o, e, s);
77
+ } catch (e) {
78
+ throw x.claimFailures++, x.lastDecision = "claim-failed", e;
79
+ }
80
+ if (!t) return x.claimLosses++, x.lastDecision = "claim-lost", "deduplicated";
81
+ x.claimWins++;
82
+ try {
83
+ let e = await l();
84
+ x.lastOutcome = i(e), x.lastDecision = "active";
85
+ } catch (e) {
86
+ throw x.taskFailures++, x.lastDecision = "task-failed", e;
87
+ }
88
+ return "ran";
89
+ }, T = d.externalize === !0 ? t(o, s) : void 0, E = d.externalize === !0 && T === void 0;
90
+ T !== void 0 && d.log?.warn(`coordinated schedule: REFUSING to externalise this task — ${T}`, {
91
+ task: o,
92
+ intervalMs: s
93
+ }), E ? (n({
94
+ name: o,
95
+ intervalMs: s,
96
+ cron: e(s),
97
+ run: w
98
+ }), d.log?.info("coordinated schedule: externalised — no in-process timer", {
99
+ task: o,
100
+ intervalMs: s
101
+ })) : S(s);
102
+ let D = {
103
+ name: o,
104
+ currentIntervalMs: () => g,
105
+ isArmed: () => !p && !h && !E,
106
+ wake: () => {
107
+ if (p) return;
108
+ x.wakes++, x.lastWakeAt = Date.now();
109
+ let e = Date.now();
110
+ !h && e - y < s ? x.coalescedWakes++ : (x.lastDecision = "wake", y = e, _ ? v = !0 : S(0));
111
+ },
112
+ stop: () => {
113
+ p || (p = !0, m !== void 0 && clearTimeout(m), m = void 0, b = null, E && r(o));
114
+ }
115
+ };
116
+ return a.set(D, () => ({
117
+ ...x,
118
+ lastOutcome: x.lastOutcome === null ? null : { ...x.lastOutcome },
119
+ task: o,
120
+ state: p ? "stopped" : E ? "externalized" : _ ? "running" : h ? "disarmed" : "armed",
121
+ baseIntervalMs: s,
122
+ currentIntervalMs: g,
123
+ maxIdleIntervalMs: f,
124
+ nextTickAt: b,
125
+ disarmWhenIdle: d.disarmWhenIdle ?? !1
126
+ })), D;
127
+ }, u = (e, t, n, r, i = !1) => {
128
+ if (e === void 0 || !e.idle) return t;
129
+ let a = typeof e.nextDueInMs == "number" && Number.isFinite(e.nextDueInMs) ? e.nextDueInMs : void 0;
130
+ if (a === void 0 && e.nextDueInMs === null && i) return "disarm";
131
+ let o = Math.min(Math.max(n, t) * 2, r);
132
+ return a === void 0 ? o : Math.max(t, i ? a : Math.min(o, a));
133
+ }, d = (e) => (t, n, r, i) => l(t, n, r, {
134
+ ...e,
135
+ ...e.externalizeTask === void 0 ? {} : { externalize: e.externalizeTask(t) },
136
+ ...i?.disarmWhenIdle === void 0 ? {} : { disarmWhenIdle: i.disarmWhenIdle }
137
+ });
138
+ //#endregion
139
+ export { s as DEFAULT_MAX_IDLE_INTERVAL_MS, o as inspectCoordinatedSchedule, d as makeCoordinatedScheduler, u as nextDelay, c as readPollCeilingMs, l as scheduleCoordinated };