@voltro/plugin-releases 0.84.0 → 0.85.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.
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import { ReleaseNotFound as e, ReleaseRefused as t } from "./errors.js";
2
2
  import { a as n, i as r, n as i, o as a, r as o, s, t as c } from "./rollout-CGRKMTfv.js";
3
- import { _ as l, a as u, c as ee, d as te, f as ne, g as d, h as f, l as re, m as ie, o as ae, p as oe, r as p, s as se, t as m, u as ce, v as h, y as g } from "./rpc-DeEV8l3U.js";
4
- import { Effect as _, Runtime as le, Schema as ue } from "effect";
5
- import { randomBytes as v } from "node:crypto";
6
- import { AsyncLocalStorage as y } from "node:async_hooks";
7
- import { definePlugin as de } from "@voltro/protocol";
8
- import { and as fe, boolean as b, eq as x, id as S, integer as C, json as w, queryFor as T, registerRetention as pe, retentionTtlMsFromEnv as me, table as E, text as D, timestamp as O } from "@voltro/database";
3
+ import { _ as l, a as u, c as ee, d as te, f as ne, g as d, h as f, l as re, m as ie, o as ae, p as oe, r as p, s as se, t as m, u as ce, v as h, y as g } from "./rpc-DrfZqJIA.js";
4
+ import { Effect as _, Result as v, Schema as le } from "effect";
5
+ import { randomBytes as y } from "node:crypto";
6
+ import { AsyncLocalStorage as b } from "node:async_hooks";
7
+ import { definePlugin as ue } from "@voltro/protocol";
8
+ import { and as de, boolean as x, eq as S, id as C, inSet as fe, integer as w, json as T, queryFor as E, registerRetention as pe, retentionTtlMsFromEnv as me, table as D, text as O, timestamp as k } from "@voltro/database";
9
9
  import { newTraceId as he } from "@voltro/runtime";
10
10
  var ge = (e, t, n) => {
11
11
  let r = /* @__PURE__ */ new Set();
@@ -16,93 +16,93 @@ var ge = (e, t, n) => {
16
16
  for (let e of s.slice(o)) r.has(e.id) || i.push(e);
17
17
  }
18
18
  return i;
19
- }, k = [
19
+ }, A = [
20
20
  "web",
21
21
  "ios",
22
22
  "android",
23
23
  "macos",
24
24
  "windows",
25
25
  "linux"
26
- ], A = [
26
+ ], j = [
27
27
  "pwa",
28
28
  "expo",
29
29
  "tauri",
30
30
  "electron"
31
- ], j = E(d, {
32
- id: S({ prefix: "relapp" }),
33
- app: D(),
34
- platform: D(),
35
- shell: D(),
36
- revision: C().default(0),
37
- bundleId: D().nullable(),
38
- updaterPublicKey: D().nullable(),
39
- runtimeVersion: D().nullable(),
40
- createdAt: O().default("now")
31
+ ], M = D(d, {
32
+ id: C({ prefix: "relapp" }),
33
+ app: O(),
34
+ platform: O(),
35
+ shell: O(),
36
+ revision: w().default(0),
37
+ bundleId: O().nullable(),
38
+ updaterPublicKey: O().nullable(),
39
+ runtimeVersion: O().nullable(),
40
+ createdAt: k().default("now")
41
41
  }).index("byReleaseAppTarget", [
42
42
  "app",
43
43
  "platform",
44
44
  "shell"
45
- ]), M = E(h, {
46
- id: S({ prefix: "relch" }),
47
- appId: D(),
48
- name: D(),
49
- currentReleaseId: D().nullable(),
50
- minSupportedVersion: D().nullable(),
51
- killSwitches: w().nullable(),
52
- public: b().default(!1),
53
- allowUnsigned: b().default(!1),
54
- updatedAt: O().default("now").onUpdate("now")
55
- }).index("byReleaseChannelApp", ["appId", "name"]), N = E(f, {
56
- id: S({ prefix: "rel" }),
57
- appId: D(),
58
- channelId: D(),
59
- version: D(),
60
- buildId: D().nullable(),
61
- notes: D().nullable(),
62
- state: D().default("draft"),
63
- rolloutPercent: C().default(0),
64
- rolloutSalt: D(),
65
- cohorts: w().nullable(),
66
- createdBy: D().nullable(),
67
- createdAt: O().default("now"),
68
- updatedAt: O().default("now").onUpdate("now")
69
- }).index("byReleaseAppChannel", ["appId", "channelId"]).index("byReleaseVersion", ["appId", "version"]), P = E(l, {
70
- id: S({ prefix: "relart" }),
71
- releaseId: D(),
72
- kind: D(),
73
- target: D().nullable(),
74
- url: D(),
75
- sha256: D(),
76
- size: C(),
77
- signature: D().nullable(),
78
- notarized: b().default(!1),
79
- assetKey: D().nullable(),
80
- contentType: D().nullable(),
81
- createdAt: O().default("now")
82
- }).index("byReleaseArtifactRelease", ["releaseId"]), F = E(g, {
83
- id: S({ prefix: "relev" }),
84
- appId: D(),
85
- channelId: D().nullable(),
86
- releaseId: D().nullable(),
87
- kind: D(),
88
- reason: D().nullable(),
89
- actorId: D().nullable(),
90
- detail: w().nullable(),
91
- at: O().default("now")
92
- }).index("byReleaseEventApp", ["appId", "at"]), I = [
93
- j,
45
+ ]), N = D(h, {
46
+ id: C({ prefix: "relch" }),
47
+ appId: O(),
48
+ name: O(),
49
+ currentReleaseId: O().nullable(),
50
+ minSupportedVersion: O().nullable(),
51
+ killSwitches: T().nullable(),
52
+ public: x().default(!1),
53
+ allowUnsigned: x().default(!1),
54
+ updatedAt: k().default("now").onUpdate("now")
55
+ }).index("byReleaseChannelApp", ["appId", "name"]), P = D(f, {
56
+ id: C({ prefix: "rel" }),
57
+ appId: O(),
58
+ channelId: O(),
59
+ version: O(),
60
+ buildId: O().nullable(),
61
+ notes: O().nullable(),
62
+ state: O().default("draft"),
63
+ rolloutPercent: w().default(0),
64
+ rolloutSalt: O(),
65
+ cohorts: T().nullable(),
66
+ createdBy: O().nullable(),
67
+ createdAt: k().default("now"),
68
+ updatedAt: k().default("now").onUpdate("now")
69
+ }).index("byReleaseAppChannel", ["appId", "channelId"]).index("byReleaseVersion", ["appId", "version"]), F = D(l, {
70
+ id: C({ prefix: "relart" }),
71
+ releaseId: O(),
72
+ kind: O(),
73
+ target: O().nullable(),
74
+ url: O(),
75
+ sha256: O(),
76
+ size: w(),
77
+ signature: O().nullable(),
78
+ notarized: x().default(!1),
79
+ assetKey: O().nullable(),
80
+ contentType: O().nullable(),
81
+ createdAt: k().default("now")
82
+ }).index("byReleaseArtifactRelease", ["releaseId"]), I = D(g, {
83
+ id: C({ prefix: "relev" }),
84
+ appId: O(),
85
+ channelId: O().nullable(),
86
+ releaseId: O().nullable(),
87
+ kind: O(),
88
+ reason: O().nullable(),
89
+ actorId: O().nullable(),
90
+ detail: T().nullable(),
91
+ at: k().default("now")
92
+ }).index("byReleaseEventApp", ["appId", "at"]), L = [
94
93
  M,
95
94
  N,
96
95
  P,
97
- F
98
- ], L = (e, t) => t.currentVersion !== null && c(e.version, t.currentVersion) <= 0 ? !1 : o({
96
+ F,
97
+ I
98
+ ], R = (e, t) => t.currentVersion !== null && c(e.version, t.currentVersion) <= 0 ? !1 : o({
99
99
  installId: t.installId,
100
100
  salt: e.rolloutSalt,
101
101
  percent: e.rolloutPercent,
102
102
  ...t.cohorts === void 0 ? {} : { installCohorts: t.cohorts },
103
103
  releaseCohorts: e.cohorts
104
- }), R = (e, t) => {
105
- if (!L(e, t)) return null;
104
+ }), z = (e, t) => {
105
+ if (!R(e, t)) return null;
106
106
  let n = e.artifacts.find((e) => e.target === t.target && e.signature !== null);
107
107
  return !n || n.signature === null ? null : {
108
108
  version: e.version,
@@ -113,7 +113,7 @@ var ge = (e, t, n) => {
113
113
  url: n.url
114
114
  } }
115
115
  };
116
- }, z = (e, t, n = {}) => {
116
+ }, B = (e, t, n = {}) => {
117
117
  let r = {
118
118
  win: [
119
119
  "nsis",
@@ -138,14 +138,14 @@ var ge = (e, t, n) => {
138
138
  a.push(`path: ${o.url}`);
139
139
  let s = n[o.sha256];
140
140
  return a.push(s === void 0 ? `sha256: ${o.sha256}` : `sha512: ${s}`), a.push(`releaseDate: '${e.createdAt.toISOString()}'`), e.notes && a.push(`releaseNotes: ${JSON.stringify(e.notes)}`), a.join("\n") + "\n";
141
- }, B = (e, t, n) => {
141
+ }, _e = (e, t, n) => {
142
142
  if (n.rolledBackTo !== null) return {
143
143
  kind: "rollback",
144
144
  commitTime: n.rolledBackTo.commitTime.toISOString()
145
145
  };
146
146
  if (e === null || n.runtimeVersion !== null && n.runtimeVersion !== t.runtimeVersion) return { kind: "no-update" };
147
147
  let r = e.buildId ?? e.id;
148
- if (t.currentUpdateId === r || !L(e, {
148
+ if (t.currentUpdateId === r || !R(e, {
149
149
  installId: t.installId,
150
150
  currentVersion: null,
151
151
  ...t.cohorts === void 0 ? {} : { cohorts: t.cohorts }
@@ -178,7 +178,7 @@ var ge = (e, t, n) => {
178
178
  }
179
179
  }
180
180
  };
181
- }, V = {
181
+ }, ve = {
182
182
  macos: ["dmg", "zip"],
183
183
  windows: [
184
184
  "nsis",
@@ -192,8 +192,8 @@ var ge = (e, t, n) => {
192
192
  ],
193
193
  ios: ["ipa"],
194
194
  android: ["apk", "aab"]
195
- }, _e = (e, t, n) => {
196
- let r = V[t] ?? [], i = e.artifacts.filter((e) => r.includes(e.kind));
195
+ }, V = (e, t, n) => {
196
+ let r = ve[t] ?? [], i = e.artifacts.filter((e) => r.includes(e.kind));
197
197
  if (i.length === 0) return null;
198
198
  if (n !== void 0) {
199
199
  let e = i.find((e) => e.target !== null && e.target.endsWith(n));
@@ -214,7 +214,7 @@ var ge = (e, t, n) => {
214
214
  "cache-control": "no-store",
215
215
  ...n
216
216
  }
217
- }), ve = (e, t, n) => ({
217
+ }), ye = (e, t, n) => ({
218
218
  status: e,
219
219
  body: t,
220
220
  contentType: n,
@@ -223,15 +223,15 @@ var ge = (e, t, n) => {
223
223
  status: e,
224
224
  body: "",
225
225
  headers: { "cache-control": "no-store" }
226
- }), G = (e) => `${e}_${v(9).toString("base64url")}`, ye = (n) => {
227
- let r = (n.prefix ?? "/releases").replace(/\/+$/, ""), a = n.now ?? Date.now, o, s = new y(), p = new y(), v = () => {
226
+ }), G = (e) => `${e}_${y(9).toString("base64url")}`, be = (n) => {
227
+ let r = (n.prefix ?? "/releases").replace(/\/+$/, ""), a = n.now ?? Date.now, o, s = new b(), p = new b(), y = () => {
228
228
  let e = s.getStore();
229
229
  if (e) return e;
230
230
  if (!o) throw new t({ reason: "releases: the app store is not bound yet" });
231
231
  return o;
232
- }, b = null, S = async (e) => {
233
- let t = (await v().query(T(j).where(x("app", e.app)).descriptor)).find((t) => t.platform === e.platform && t.shell === e.shell), n = !t;
234
- t ||= await v().insert(d, {
232
+ }, x = null, C = async (e) => {
233
+ let t = (await y().query(E(M).where(S("app", e.app)).descriptor)).find((t) => t.platform === e.platform && t.shell === e.shell), n = !t;
234
+ t ||= await y().insert(d, {
235
235
  id: G("relapp"),
236
236
  app: e.app,
237
237
  platform: e.platform,
@@ -242,12 +242,12 @@ var ge = (e, t, n) => {
242
242
  runtimeVersion: e.runtimeVersion ?? null,
243
243
  createdAt: new Date(a())
244
244
  });
245
- let r = await v().query(T(M).where(x("appId", t.id)).descriptor);
245
+ let r = await y().query(E(N).where(S("appId", t.id)).descriptor);
246
246
  for (let n of e.channels ?? [
247
247
  "stable",
248
248
  "beta",
249
249
  "internal"
250
- ]) r.some((e) => e.name === n) || await v().insert(h, {
250
+ ]) r.some((e) => e.name === n) || await y().insert(h, {
251
251
  id: G("relch"),
252
252
  appId: t.id,
253
253
  name: n,
@@ -262,19 +262,19 @@ var ge = (e, t, n) => {
262
262
  row: t,
263
263
  created: n
264
264
  };
265
- }, C = async () => {
266
- for (let e of n.apps) await S(e);
267
- }, w = () => b ?? Promise.resolve(), E = (t, n, r) => _.tryPromise({
265
+ }, w = async () => {
266
+ for (let e of n.apps) await C(e);
267
+ }, T = () => x ?? Promise.resolve(), D = (t, n, r) => _.tryPromise({
268
268
  try: async () => {
269
- await w();
270
- let i = await v().query(T(j).where(x("app", t)).descriptor), a = p.getStore(), o = i.find((e) => (a === void 0 || e.id === a) && (n === void 0 || e.platform === n) && (r === void 0 || e.shell === r));
269
+ await T();
270
+ let i = await y().query(E(M).where(S("app", t)).descriptor), a = p.getStore(), o = i.find((e) => (a === void 0 || e.id === a) && (n === void 0 || e.platform === n) && (r === void 0 || e.shell === r));
271
271
  if (!o) throw new e({ app: t });
272
272
  return o;
273
273
  },
274
274
  catch: (n) => n instanceof e ? n : new e({ app: t })
275
- }), D = (t, n, r) => _.tryPromise({
275
+ }), O = (t, n, r) => _.tryPromise({
276
276
  try: async () => {
277
- let i = (await v().query(T(M).where(x("appId", t)).descriptor)).find((e) => e.name === n);
277
+ let i = (await y().query(E(N).where(S("appId", t)).descriptor)).find((e) => e.name === n);
278
278
  if (!i) throw new e({
279
279
  app: r,
280
280
  channel: n
@@ -285,10 +285,10 @@ var ge = (e, t, n) => {
285
285
  app: r,
286
286
  channel: n
287
287
  })
288
- }), O = (e) => v().query(T(N).where(x("id", e)).descriptor).then((e) => e[0] ?? null), k = (e) => v().query(T(N).where(x("appId", e)).descriptor), A = (e) => v().query(T(P).where(x("releaseId", e)).descriptor), L = (e) => v().query(T(M).where(x("appId", e)).descriptor), V = (e) => n.retention?.perChannel?.[e] ?? n.retention?.keep ?? 10, ye = async (e, t) => {
289
- let n = await L(e), r = ge(await k(e), n, V);
288
+ }), k = (e) => y().query(E(P).where(S("id", e)).descriptor).then((e) => e[0] ?? null), A = (e) => y().query(E(P).where(S("appId", e)).descriptor), j = (e) => y().query(E(F).where(S("releaseId", e)).descriptor), R = (e) => y().query(E(N).where(S("appId", e)).descriptor), ve = (e) => n.retention?.perChannel?.[e] ?? n.retention?.keep ?? 10, be = async (e, t) => {
289
+ let n = await R(e), r = ge(await A(e), n, ve);
290
290
  if (r.length === 0) return [];
291
- for (let e of r) await v().deleteMany(l, { where: x("releaseId", e.id) }), await v().delete(f, e.id);
291
+ for (let e of r) await y().deleteMany(l, { where: S("releaseId", e.id) }), await y().delete(f, e.id);
292
292
  return await q({
293
293
  appId: e,
294
294
  kind: "prune",
@@ -298,8 +298,8 @@ var ge = (e, t, n) => {
298
298
  channel: n.find((t) => t.id === e.channelId)?.name ?? null
299
299
  })) }
300
300
  }), r;
301
- }, Se = async (e) => {
302
- let t = await A(e.id);
301
+ }, Ce = async (e) => {
302
+ let t = await j(e.id);
303
303
  return {
304
304
  id: e.id,
305
305
  version: e.version,
@@ -321,7 +321,7 @@ var ge = (e, t, n) => {
321
321
  }))
322
322
  };
323
323
  }, K = async (e, t) => {
324
- let n = await _.runPromise(E(e)), r = await _.runPromise(D(n.id, t, e)), i = r.currentReleaseId ? await O(r.currentReleaseId) : null;
324
+ let n = await _.runPromise(D(e)), r = await _.runPromise(O(n.id, t, e)), i = r.currentReleaseId ? await k(r.currentReleaseId) : null;
325
325
  return {
326
326
  app: e,
327
327
  channel: t,
@@ -332,7 +332,7 @@ var ge = (e, t, n) => {
332
332
  rolloutSalt: i?.rolloutSalt ?? null,
333
333
  cohorts: i?.cohorts ?? null
334
334
  };
335
- }, q = (e) => v().insert(g, {
335
+ }, q = (e) => y().insert(g, {
336
336
  id: G("relev"),
337
337
  appId: e.appId,
338
338
  channelId: e.channelId ?? null,
@@ -342,41 +342,54 @@ var ge = (e, t, n) => {
342
342
  actorId: e.actorId ?? null,
343
343
  detail: e.detail ?? null,
344
344
  at: new Date(a())
345
- }), J = (e) => e.request.subject.type === "user" ? e.request.subject.id : null, Ce = {
345
+ }), J = (e) => e.request.subject.type === "user" ? e.request.subject.id : null, we = {
346
346
  ...se,
347
347
  description: "The reactive release policy of a channel: latest version, minimum supported version, kill switches, rollout.",
348
348
  execute: (e) => _.tryPromise({
349
349
  try: () => K(e.app, e.channel),
350
350
  catch: (e) => e
351
351
  })
352
- }, we = {
352
+ }, Te = {
353
353
  ...ae,
354
354
  description: "Every release of an app target, newest first.",
355
355
  execute: (e) => _.gen(function* () {
356
- let t = yield* E(e.app), n = yield* _.promise(() => k(t.id)), r = yield* _.promise(() => v().query(T(M).where(x("appId", t.id)).descriptor)), i = [];
356
+ let t = yield* D(e.app), n = yield* _.promise(() => A(t.id)), r = yield* _.promise(() => y().query(E(N).where(S("appId", t.id)).descriptor)), i = /* @__PURE__ */ new Map();
357
+ for (let e of r) i.has(e.id) || i.set(e.id, e.name);
358
+ let a = /* @__PURE__ */ new Map(), o = [...new Set(n.map((e) => e.id))];
359
+ for (let e = 0; e < o.length; e += 500) {
360
+ let t = yield* _.promise(() => y().query(E(F).where(fe("releaseId", o.slice(e, e + 500))).descriptor));
361
+ for (let e of t) {
362
+ let t = a.get(e.releaseId) ?? {
363
+ count: 0,
364
+ signed: !0
365
+ };
366
+ t.count++, t.signed &&= e.signature !== null, a.set(e.releaseId, t);
367
+ }
368
+ }
369
+ let s = [];
357
370
  for (let e of [...n].sort((e, t) => t.createdAt.getTime() - e.createdAt.getTime() || c(t.version, e.version))) {
358
- let t = yield* _.promise(() => A(e.id));
359
- i.push({
371
+ let t = a.get(e.id);
372
+ s.push({
360
373
  id: e.id,
361
374
  version: e.version,
362
375
  buildId: e.buildId,
363
376
  state: e.state,
364
377
  rolloutPercent: e.rolloutPercent,
365
- channel: r.find((t) => t.id === e.channelId)?.name ?? "?",
378
+ channel: i.get(e.channelId) ?? "?",
366
379
  createdAt: e.createdAt.toISOString(),
367
- artifacts: t.length,
368
- signed: t.length > 0 && t.every((e) => e.signature !== null)
380
+ artifacts: t?.count ?? 0,
381
+ signed: t !== void 0 && t.count > 0 && t.signed
369
382
  });
370
383
  }
371
- return i;
384
+ return s;
372
385
  })
373
- }, Te = {
386
+ }, Ee = {
374
387
  ...re,
375
388
  description: "Register an app target at runtime, with its channels; idempotent.",
376
389
  execute: (e) => _.tryPromise({
377
390
  try: async () => {
378
- await w();
379
- let { row: t, created: n } = await S({
391
+ await T();
392
+ let { row: t, created: n } = await C({
380
393
  app: e.app,
381
394
  platform: e.platform,
382
395
  shell: e.shell,
@@ -392,14 +405,14 @@ var ge = (e, t, n) => {
392
405
  },
393
406
  catch: (n) => new t({ reason: `releases: ${e.app} could not be registered: ${n instanceof Error ? n.message : String(n)}` })
394
407
  })
395
- }, Ee = {
408
+ }, De = {
396
409
  ...u,
397
410
  description: "Create a draft release of a version on a channel.",
398
411
  execute: (e, n) => _.gen(function* () {
399
- let r = yield* E(e.app), i = yield* D(r.id, e.channel, e.app);
400
- if ((yield* _.promise(() => k(r.id))).some((t) => t.version === e.version)) return yield* _.fail(new t({ reason: `version ${e.version} already exists for ${e.app}` }));
412
+ let r = yield* D(e.app), i = yield* O(r.id, e.channel, e.app);
413
+ if ((yield* _.promise(() => A(r.id))).some((t) => t.version === e.version)) return yield* _.fail(new t({ reason: `version ${e.version} already exists for ${e.app}` }));
401
414
  let o = G("rel");
402
- return yield* _.promise(() => v().insert(f, {
415
+ return yield* _.promise(() => y().insert(f, {
403
416
  id: o,
404
417
  appId: r.id,
405
418
  channelId: i.id,
@@ -420,20 +433,20 @@ var ge = (e, t, n) => {
420
433
  kind: "create",
421
434
  actorId: J(n),
422
435
  detail: { version: e.version }
423
- })), yield* _.promise(() => ye(r.id, J(n))), { id: o };
436
+ })), yield* _.promise(() => be(r.id, J(n))), { id: o };
424
437
  })
425
- }, De = {
438
+ }, Oe = {
426
439
  ...ie,
427
440
  description: "Attach an artefact (a file the client downloads) to a release.",
428
441
  execute: (n, r) => _.gen(function* () {
429
- let i = yield* _.promise(() => O(n.releaseId));
442
+ let i = yield* _.promise(() => k(n.releaseId));
430
443
  if (!i) return yield* _.fail(new e({
431
444
  app: "?",
432
445
  version: n.releaseId
433
446
  }));
434
447
  if (!/^[a-f0-9]{64}$/i.test(n.sha256)) return yield* _.fail(new t({ reason: "sha256 must be 64 hex characters" }));
435
448
  let o = G("relart");
436
- return yield* _.promise(() => v().insert(l, {
449
+ return yield* _.promise(() => y().insert(l, {
437
450
  id: o,
438
451
  releaseId: i.id,
439
452
  kind: n.kind,
@@ -457,24 +470,24 @@ var ge = (e, t, n) => {
457
470
  }
458
471
  })), { id: o };
459
472
  })
460
- }, Oe = {
473
+ }, ke = {
461
474
  ...ee,
462
475
  description: "Point a channel at a release (no rebuild) and start its rollout.",
463
476
  execute: (n, r) => _.gen(function* () {
464
- let i = yield* E(n.app), o = yield* D(i.id, n.to, n.app), s = yield* _.promise(() => k(i.id)), c = s.find((e) => e.version === n.version);
477
+ let i = yield* D(n.app), o = yield* O(i.id, n.to, n.app), s = yield* _.promise(() => A(i.id)), c = s.find((e) => e.version === n.version);
465
478
  if (!c) return yield* _.fail(new e({
466
479
  app: n.app,
467
480
  version: n.version
468
481
  }));
469
- let l = yield* _.promise(() => A(c.id));
482
+ let l = yield* _.promise(() => j(c.id));
470
483
  if (!o.allowUnsigned && l.some((e) => e.signature === null)) return yield* _.fail(new t({ reason: `release ${n.version} has unsigned artefacts; channel ${n.to} refuses them` }));
471
484
  let u = Math.max(0, Math.min(100, n.rolloutPercent ?? 100));
472
- return yield* _.promise(() => v().update(f, c.id, {
485
+ return yield* _.promise(() => y().update(f, c.id, {
473
486
  channelId: o.id,
474
487
  state: u >= 100 ? "complete" : "rolling",
475
488
  rolloutPercent: u,
476
489
  updatedAt: new Date(a())
477
- })), yield* _.promise(() => v().update(h, o.id, {
490
+ })), yield* _.promise(() => y().update(h, o.id, {
478
491
  currentReleaseId: c.id,
479
492
  updatedAt: new Date(a())
480
493
  })), yield* _.promise(() => q({
@@ -494,19 +507,19 @@ var ge = (e, t, n) => {
494
507
  rolloutPercent: u
495
508
  };
496
509
  })
497
- }, ke = {
510
+ }, Ae = {
498
511
  ...ne,
499
512
  description: "Change the rollout percentage of the channel's current release.",
500
513
  execute: (n, r) => _.gen(function* () {
501
- let i = yield* E(n.app), o = yield* D(i.id, n.channel, n.app);
514
+ let i = yield* D(n.app), o = yield* O(i.id, n.channel, n.app);
502
515
  if (!o.currentReleaseId) return yield* _.fail(new t({ reason: `channel ${n.channel} has no current release` }));
503
- let s = yield* _.promise(() => O(o.currentReleaseId));
516
+ let s = yield* _.promise(() => k(o.currentReleaseId));
504
517
  if (!s) return yield* _.fail(new e({
505
518
  app: n.app,
506
519
  channel: n.channel
507
520
  }));
508
521
  let c = Math.max(0, Math.min(100, n.percent));
509
- return yield* _.promise(() => v().update(f, s.id, {
522
+ return yield* _.promise(() => y().update(f, s.id, {
510
523
  rolloutPercent: c,
511
524
  state: c >= 100 ? "complete" : "rolling",
512
525
  updatedAt: new Date(a())
@@ -522,17 +535,17 @@ var ge = (e, t, n) => {
522
535
  rolloutPercent: c
523
536
  };
524
537
  })
525
- }, Ae = {
538
+ }, je = {
526
539
  ...te,
527
540
  description: "Point the channel back at the previous release; the current one is marked rolled back.",
528
541
  execute: (e, n) => _.gen(function* () {
529
- let r = yield* E(e.app), i = yield* D(r.id, e.channel, e.app);
542
+ let r = yield* D(e.app), i = yield* O(r.id, e.channel, e.app);
530
543
  if (!i.currentReleaseId) return yield* _.fail(new t({ reason: `channel ${e.channel} has no current release` }));
531
- let o = yield* _.promise(() => k(r.id)), s = o.find((e) => e.id === i.currentReleaseId), l = o.filter((e) => e.channelId === i.id && e.id !== i.currentReleaseId && e.state !== "rolledBack").sort((e, t) => c(t.version, e.version))[0];
532
- return s && (yield* _.promise(() => v().update(f, s.id, {
544
+ let o = yield* _.promise(() => A(r.id)), s = o.find((e) => e.id === i.currentReleaseId), l = o.filter((e) => e.channelId === i.id && e.id !== i.currentReleaseId && e.state !== "rolledBack").sort((e, t) => c(t.version, e.version))[0];
545
+ return s && (yield* _.promise(() => y().update(f, s.id, {
533
546
  state: "rolledBack",
534
547
  updatedAt: new Date(a())
535
- }))), yield* _.promise(() => v().update(h, i.id, {
548
+ }))), yield* _.promise(() => y().update(h, i.id, {
536
549
  currentReleaseId: l?.id ?? null,
537
550
  updatedAt: new Date(a())
538
551
  })), yield* _.promise(() => q({
@@ -548,12 +561,12 @@ var ge = (e, t, n) => {
548
561
  }
549
562
  })), { version: l?.version ?? null };
550
563
  })
551
- }, je = {
564
+ }, Me = {
552
565
  ...oe,
553
566
  description: "Set the minimum supported version and kill switches of a channel.",
554
567
  execute: (e, t) => _.gen(function* () {
555
- let n = yield* E(e.app), r = yield* D(n.id, e.channel, e.app);
556
- return yield* _.promise(() => v().update(h, r.id, {
568
+ let n = yield* D(e.app), r = yield* O(n.id, e.channel, e.app);
569
+ return yield* _.promise(() => y().update(h, r.id, {
557
570
  ...e.minSupportedVersion === void 0 ? {} : { minSupportedVersion: e.minSupportedVersion },
558
571
  ...e.killSwitches === void 0 ? {} : { killSwitches: e.killSwitches },
559
572
  updatedAt: new Date(a())
@@ -570,13 +583,13 @@ var ge = (e, t, n) => {
570
583
  })), yield* _.promise(() => K(e.app, e.channel));
571
584
  })
572
585
  }, Y = async (e, t) => {
573
- let n = await _.runPromise(E(e)), r = await _.runPromise(D(n.id, t, e)), i = r.currentReleaseId ? await O(r.currentReleaseId) : null;
586
+ let n = await _.runPromise(D(e)), r = await _.runPromise(O(n.id, t, e)), i = r.currentReleaseId ? await k(r.currentReleaseId) : null;
574
587
  return {
575
588
  app: n,
576
589
  channel: r,
577
- release: i ? await Se(i) : null
590
+ release: i ? await Ce(i) : null
578
591
  };
579
- }, X = (e, t) => t.get("install") ?? e.headers["x-voltro-install-id"] ?? e.headers["expo-embedded-update-id"] ?? "anonymous", Me = {
592
+ }, X = (e, t) => t.get("install") ?? e.headers["x-voltro-install-id"] ?? e.headers["expo-embedded-update-id"] ?? "anonymous", Ne = {
580
593
  method: "GET",
581
594
  path: r,
582
595
  handle: async (t) => {
@@ -587,7 +600,7 @@ var ge = (e, t, n) => {
587
600
  if (s === "tauri") {
588
601
  let [, , e, r, i] = n;
589
602
  if (!e || !r || !i) return U(400, { error: "expected /tauri/:channel/:target/:currentVersion" });
590
- let { release: s } = await Y(o, e), c = s ? R(s, {
603
+ let { release: s } = await Y(o, e), c = s ? z(s, {
591
604
  installId: X(t, a),
592
605
  currentVersion: i,
593
606
  target: r
@@ -597,7 +610,7 @@ var ge = (e, t, n) => {
597
610
  if (s === "expo" && n[2] === "manifest") {
598
611
  let e = t.headers["expo-channel-name"] ?? a.get("channel") ?? "stable", n = t.headers["expo-platform"] ?? a.get("platform"), r = t.headers["expo-runtime-version"] ?? a.get("runtimeVersion") ?? "";
599
612
  if (n !== "ios" && n !== "android") return U(400, { error: "expo-platform must be ios or android" });
600
- let { app: i, release: s } = await Y(o, e), c = B(s, {
613
+ let { app: i, release: s } = await Y(o, e), c = _e(s, {
601
614
  installId: X(t, a),
602
615
  currentVersion: null,
603
616
  platform: n,
@@ -627,8 +640,8 @@ var ge = (e, t, n) => {
627
640
  if (!e || !t) return U(400, { error: "expected /electron/:channel/latest.yml" });
628
641
  let r = t === "latest-mac.yml" ? "mac" : t === "latest-linux.yml" ? "linux" : t === "latest.yml" ? "win" : null;
629
642
  if (r === null) return U(404, { error: "not_found" });
630
- let { release: i } = await Y(o, e), a = i ? z(i, r) : null;
631
- return a ? ve(200, a, "text/yaml; charset=utf-8") : W(204);
643
+ let { release: i } = await Y(o, e), a = i ? B(i, r) : null;
644
+ return a ? ye(200, a, "text/yaml; charset=utf-8") : W(204);
632
645
  }
633
646
  if (s === "download") {
634
647
  let [, , e] = n;
@@ -638,7 +651,7 @@ var ge = (e, t, n) => {
638
651
  if (!i) return W(204);
639
652
  let s = a.get("os") ?? H(t.headers["user-agent"] ?? "")?.os, c = a.get("arch") ?? H(t.headers["user-agent"] ?? "")?.arch;
640
653
  if (!s) return U(400, { error: "os is required (?os=macos|windows|linux|ios|android)" });
641
- let l = _e(i, s, c);
654
+ let l = V(i, s, c);
642
655
  return l ? {
643
656
  status: 302,
644
657
  body: "",
@@ -672,21 +685,27 @@ var ge = (e, t, n) => {
672
685
  }, Z = (e) => ({
673
686
  kind: "json",
674
687
  data: e
675
- }), Ne = [{
688
+ }), Pe = [{
676
689
  method: "GET",
677
690
  path: "/overview",
678
691
  scope: "shared-store",
679
692
  description: "Every app target with its channels, current versions and rollout state.",
680
693
  handler: () => _.promise(async () => {
681
- let e = await v().query(T(j).descriptor), t = await v().query(T(M).descriptor), n = await v().query(T(N).descriptor);
694
+ let e = await y().query(E(M).descriptor), t = await y().query(E(N).descriptor), n = await y().query(E(P).descriptor), r = /* @__PURE__ */ new Map();
695
+ for (let e of t) {
696
+ let t = r.get(e.appId);
697
+ t === void 0 ? r.set(e.appId, [e]) : t.push(e);
698
+ }
699
+ let i = /* @__PURE__ */ new Map(), a = /* @__PURE__ */ new Map();
700
+ for (let e of n) i.has(e.id) || i.set(e.id, e), a.set(e.appId, (a.get(e.appId) ?? 0) + 1);
682
701
  return Z({ apps: e.map((e) => ({
683
702
  id: e.id,
684
703
  app: e.app,
685
704
  platform: e.platform,
686
705
  shell: e.shell,
687
706
  revision: e.revision,
688
- channels: t.filter((t) => t.appId === e.id).map((e) => {
689
- let t = n.find((t) => t.id === e.currentReleaseId);
707
+ channels: (r.get(e.id) ?? []).map((e) => {
708
+ let t = e.currentReleaseId === null ? void 0 : i.get(e.currentReleaseId);
690
709
  return {
691
710
  name: e.name,
692
711
  current: t?.version ?? null,
@@ -696,7 +715,7 @@ var ge = (e, t, n) => {
696
715
  public: e.public
697
716
  };
698
717
  }),
699
- releases: n.filter((t) => t.appId === e.id).length
718
+ releases: a.get(e.id) ?? 0
700
719
  })) });
701
720
  })
702
721
  }, {
@@ -705,41 +724,41 @@ var ge = (e, t, n) => {
705
724
  scope: "shared-store",
706
725
  description: "The last 100 release events (promote, rollback, rollout, policy).",
707
726
  handler: () => _.promise(async () => {
708
- let e = await v().query(T(F).descriptor), t = await v().query(T(M).descriptor), n = new Map(t.map((e) => [e.id, e.name]));
727
+ let e = await y().query(E(I).descriptor), t = await y().query(E(N).descriptor), n = new Map(t.map((e) => [e.id, e.name]));
709
728
  return Z({ events: [...e].sort((e, t) => t.at.getTime() - e.at.getTime()).slice(0, 100).map((e) => ({
710
729
  ...e,
711
730
  channelName: e.channelId === null ? null : n.get(e.channelId) ?? null,
712
731
  at: e.at.toISOString()
713
732
  })) });
714
733
  })
715
- }], Pe = async (n, r) => {
716
- let i = n.releaseId === void 0 ? null : await O(n.releaseId);
734
+ }], Fe = async (n, r) => {
735
+ let i = n.releaseId === void 0 ? null : await k(n.releaseId);
717
736
  if (n.releaseId !== void 0 && !i) throw new e({ app: n.releaseId });
718
- let a = (await v().query(T(j).descriptor)).filter((e) => (n.app === void 0 || e.app === n.app) && (n.platform === void 0 || e.platform === n.platform) && (n.shell === void 0 || e.shell === n.shell) && (i === null || e.id === i.appId) && (n.expectedTarget === void 0 || e.id === n.expectedTarget.id));
737
+ let a = (await y().query(E(M).descriptor)).filter((e) => (n.app === void 0 || e.app === n.app) && (n.platform === void 0 || e.platform === n.platform) && (n.shell === void 0 || e.shell === n.shell) && (i === null || e.id === i.appId) && (n.expectedTarget === void 0 || e.id === n.expectedTarget.id));
719
738
  if (a.length === 0 && r) return;
720
739
  if (a.length === 0) throw new e({ app: n.app ?? n.releaseId ?? "" });
721
740
  if (a.length !== 1) throw new t({ reason: "Multiple release targets match this app. Select an exact target." });
722
741
  let o = a[0], s = n.expectedTarget?.revision ?? o.revision;
723
- if (!Number.isSafeInteger(s) || s < 0 || s >= 2 ** 53 - 1 || o.revision !== s || await v().updateMany("_voltro_release_apps", { revision: s + 1 }, { where: fe(x("id", o.id), x("revision", s)) }) !== 1) throw new t({ reason: "Release target changed. Refresh and review the action again." });
742
+ if (!Number.isSafeInteger(s) || s < 0 || s >= 2 ** 53 - 1 || o.revision !== s || await y().updateMany("_voltro_release_apps", { revision: s + 1 }, { where: de(S("id", o.id), S("revision", s)) }) !== 1) throw new t({ reason: "Release target changed. Refresh and review the action again." });
724
743
  return o.id;
725
- }, Fe = (e) => e.kind === "mutation" ? {
744
+ }, Ie = (e) => e.kind === "mutation" ? {
726
745
  ...e,
727
746
  execute: (t, n) => _.gen(function* () {
728
- let r = yield* _.runtime();
747
+ let r = yield* _.context();
729
748
  return yield* _.tryPromise({
730
749
  try: async (i) => {
731
- await w();
750
+ await T();
732
751
  let a = (a) => s.run(a, async () => {
733
- let a = await Pe(t, e.name === "releases.registerApp"), o = () => le.runPromise(r)(e.execute(t, n).pipe(_.either), { signal: i }), s = a === void 0 ? await o() : await p.run(a, o);
734
- if (s._tag === "Left") throw s.left;
735
- return s.right;
752
+ let a = await Fe(t, e.name === "releases.registerApp"), o = () => _.runPromiseWith(r)(e.execute(t, n).pipe(_.result), { signal: i }), s = a === void 0 ? await o() : await p.run(a, o);
753
+ if (v.isFailure(s)) throw s.failure;
754
+ return s.success;
736
755
  });
737
- return n.mutationStore ? a(n.mutationStore) : v().transactional(a);
756
+ return n.mutationStore ? a(n.mutationStore) : y().transactional(a);
738
757
  },
739
758
  catch: (e) => e
740
759
  });
741
760
  })
742
- } : e, Ie = () => ({ request: {
761
+ } : e, Le = () => ({ request: {
743
762
  subject: {
744
763
  type: "anonymous",
745
764
  id: null,
@@ -762,23 +781,23 @@ var ge = (e, t, n) => {
762
781
  return Q(400, "invalid JSON body");
763
782
  }
764
783
  if (typeof i != "object" || !i || Array.isArray(i)) return Q(400, "expected a JSON object body");
765
- let a = yield* ue.decodeUnknown(r.input)(i).pipe(_.either);
766
- if (a._tag === "Left") return Q(400, "invalid action input");
767
- let o = yield* Fe(r).execute(a.right, Ie()).pipe(_.either);
768
- if (o._tag === "Left") {
769
- let n = o.left;
784
+ let a = yield* le.decodeUnknownEffect(r.input)(i).pipe(_.result);
785
+ if (v.isFailure(a)) return Q(400, "invalid action input");
786
+ let o = yield* Ie(r).execute(a.success, Le()).pipe(_.result);
787
+ if (v.isFailure(o)) {
788
+ let n = o.failure;
770
789
  return n instanceof e ? Q(404, `not found: ${n.app}${n.version ? ` ${n.version}` : ""}${n.channel ? ` (${n.channel})` : ""}`) : n instanceof t ? Q(409, n.reason) : Q(500, "release action failed");
771
790
  }
772
- return Z({ result: o.right });
791
+ return Z({ result: o.success });
773
792
  })
774
- }), Le = [
775
- $("/apps", Te, "Register an app target at runtime: { app, platform, shell, channels? }."),
776
- $("/create", Ee, "Create a draft release: { app, channel, version, buildId?, notes? }."),
777
- $("/upload", De, "Attach an artefact: { releaseId, kind, target?, url, sha256, size, signature? }."),
778
- $("/promote", Oe, "Promote a version onto a channel: { app, version, to, rolloutPercent? }."),
779
- $("/rollout", ke, "Set the rollout percentage: { app, channel, percent }."),
780
- $("/rollback", Ae, "Roll a channel back: { app, channel, reason }."),
781
- $("/policy", je, "Set the policy: { app, channel, minSupportedVersion?, killSwitches?, reason }.")
793
+ }), Re = [
794
+ $("/apps", Ee, "Register an app target at runtime: { app, platform, shell, channels? }."),
795
+ $("/create", De, "Create a draft release: { app, channel, version, buildId?, notes? }."),
796
+ $("/upload", Oe, "Attach an artefact: { releaseId, kind, target?, url, sha256, size, signature? }."),
797
+ $("/promote", ke, "Promote a version onto a channel: { app, version, to, rolloutPercent? }."),
798
+ $("/rollout", Ae, "Set the rollout percentage: { app, channel, percent }."),
799
+ $("/rollback", je, "Roll a channel back: { app, channel, reason }."),
800
+ $("/policy", Me, "Set the policy: { app, channel, minSupportedVersion?, killSwitches?, reason }.")
782
801
  ];
783
802
  return pe({
784
803
  source: "plugin",
@@ -786,7 +805,7 @@ var ge = (e, t, n) => {
786
805
  timeColumn: "at",
787
806
  ttlMs: me(process.env.VOLTRO_RELEASE_EVENTS_TTL_HOURS, 8760),
788
807
  envVar: "VOLTRO_RELEASE_EVENTS_TTL_HOURS"
789
- }), de({
808
+ }), ue({
790
809
  name: n.name ? `${m}#${n.name}` : m,
791
810
  description: "Release channels, artefacts and rollouts — the self-hosted update server behind Tauri, Expo OTA, electron-updater and PWA feeds, with a reactive release policy.",
792
811
  permissions: [
@@ -795,7 +814,6 @@ var ge = (e, t, n) => {
795
814
  "store:write"
796
815
  ],
797
816
  routes: [
798
- Ce,
799
817
  we,
800
818
  Te,
801
819
  Ee,
@@ -803,32 +821,33 @@ var ge = (e, t, n) => {
803
821
  Oe,
804
822
  ke,
805
823
  Ae,
806
- je
807
- ].map(Fe),
824
+ je,
825
+ Me
826
+ ].map(Ie),
808
827
  rpcClientDescriptors: ce,
809
- httpRoutes: [Me],
810
- inspectEndpoints: [...Ne, ...Le],
811
- extendSchema: { tables: [...I] },
812
- declaredEnv: xe,
813
- privacy: be,
828
+ httpRoutes: [Ne],
829
+ inspectEndpoints: [...Pe, ...Re],
830
+ extendSchema: { tables: [...L] },
831
+ declaredEnv: Se,
832
+ privacy: xe,
814
833
  bindDataStore: (e) => {
815
- o = e, b = C(), b.catch(() => {});
834
+ o = e, x = w(), x.catch(() => {});
816
835
  },
817
836
  onActivate: () => _.tryPromise({
818
837
  catch: (e) => /* @__PURE__ */ Error(`@voltro/plugin-releases could not seed its targets and channels: ${e instanceof Error ? e.message : String(e)}`),
819
- try: () => w()
838
+ try: () => T()
820
839
  })
821
840
  });
822
- }, be = { collects: [{
841
+ }, xe = { collects: [{
823
842
  category: "device-id",
824
843
  linked: !1,
825
844
  tracking: !1,
826
845
  purposes: ["app-functionality"]
827
- }] }, xe = [{
846
+ }] }, Se = [{
828
847
  name: "VOLTRO_RELEASE_EVENTS_TTL_HOURS",
829
848
  required: !1,
830
849
  secret: !1,
831
850
  description: "Hours a release event (create, upload, promote, rollout, rollback, policy, prune) is kept before the retention sweep removes it. Default 8760 (one year)."
832
851
  }];
833
852
  //#endregion
834
- export { m as BASE_NAME, p as RELEASES_MANAGE_SCOPE, f as RELEASES_TABLE, d as RELEASE_APPS_TABLE, l as RELEASE_ARTIFACTS_TABLE, h as RELEASE_CHANNELS_TABLE, g as RELEASE_EVENTS_TABLE, k as RELEASE_PLATFORMS, A as RELEASE_SHELLS, e as ReleaseNotFound, t as ReleaseRefused, c as compareVersions, i as evaluatePolicy, o as inRollout, r as isNewerVersion, L as offeredTo, H as osFromUserAgent, n as parseVersion, _e as pickDownload, j as releaseAppsTable, P as releaseArtifactsTable, M as releaseChannelsTable, F as releaseEventsTable, I as releaseTables, ye as releasesPlugin, N as releasesTable, z as renderElectronLatestYml, B as renderExpoFeed, R as renderTauriFeed, a as rolloutBucket, s as sortVersionsDesc };
853
+ export { m as BASE_NAME, p as RELEASES_MANAGE_SCOPE, f as RELEASES_TABLE, d as RELEASE_APPS_TABLE, l as RELEASE_ARTIFACTS_TABLE, h as RELEASE_CHANNELS_TABLE, g as RELEASE_EVENTS_TABLE, A as RELEASE_PLATFORMS, j as RELEASE_SHELLS, e as ReleaseNotFound, t as ReleaseRefused, c as compareVersions, i as evaluatePolicy, o as inRollout, r as isNewerVersion, R as offeredTo, H as osFromUserAgent, n as parseVersion, V as pickDownload, M as releaseAppsTable, F as releaseArtifactsTable, N as releaseChannelsTable, I as releaseEventsTable, L as releaseTables, be as releasesPlugin, P as releasesTable, B as renderElectronLatestYml, _e as renderExpoFeed, z as renderTauriFeed, a as rolloutBucket, s as sortVersionsDesc };