@voltro/protocol 0.11.3 → 0.12.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/rest.js CHANGED
@@ -1,43 +1,58 @@
1
- import { a as e, i as t, o as n, r, t as i } from "./serverErrorBus-B3hDwGoL.js";
2
- import { s as a } from "./auth-DCE6m7Bo.js";
1
+ import { a as e, i as t, o as n, r, t as i } from "./serverErrorBus-DhIVDkCi.js";
2
+ import { s as a } from "./auth-S3cnL6Tz.js";
3
3
  import { Effect as o, Schema as s } from "effect";
4
4
  //#region src/publicApi.ts
5
- var c = (e, t) => t.method ?? (e === "query" ? "GET" : "POST"), l = (e, t) => t.path ?? `/${t.version ?? "v1"}/${e.replace(/\./g, "/")}`, u = (e, t) => {
6
- let n = e.publicApi;
7
- if (n === void 0) throw Error(`publicApiRoute: descriptor '${e.name}' has no publicApi annotation`);
8
- let r = c(e.kind, n), i = l(e.name, n), a = r === "GET" ? "query" : "body", o = s.Struct({ [a]: e.input }), u = (n.scopes ?? []).map((e) => f(e));
9
- return p({
10
- method: r,
11
- path: i,
12
- input: o,
5
+ var c = (e, t) => t.method ?? (e === "query" ? "GET" : "POST"), l = (e, t) => t.path ?? `/${t.version ?? "v1"}/${e.replace(/\./g, "/")}`, u = (e, t, n) => {
6
+ let r = e.publicApi;
7
+ if (r === void 0) throw Error(`publicApiRoute: descriptor '${e.name}' has no publicApi annotation`);
8
+ let i = c(e.kind, r), a = l(e.name, r), o = i === "GET" ? "query" : "body", u = s.Struct({ [o]: e.input }), d = (r.scopes ?? []).map((e) => f(e)), p = r.stream === "sse" && e.kind === "query" && n !== void 0, _ = p ? (e, t) => h((r) => n(e[o], t, (e) => {
9
+ r(g(String(e._tag ?? "message"), e));
10
+ })) : (e, n) => t(e[o], n);
11
+ return m({
12
+ method: i,
13
+ path: a,
14
+ input: u,
13
15
  output: e.output,
14
- handler: (e, n) => t(e[a], n),
15
- ...u.length > 0 ? { guards: u } : {},
16
- ...n.summary === void 0 ? {} : { summary: n.summary },
17
- ...n.description === void 0 ? {} : { description: n.description },
18
- ...n.deprecated === void 0 ? {} : { deprecated: n.deprecated },
19
- ...n.sunset === void 0 ? {} : { sunset: n.sunset }
16
+ handler: _,
17
+ ...p ? { streaming: !0 } : {},
18
+ ...d.length > 0 ? { guards: d } : {},
19
+ ...r.summary === void 0 ? {} : { summary: r.summary },
20
+ ...r.description === void 0 ? {} : { description: r.description },
21
+ ...r.deprecated === void 0 ? {} : { deprecated: r.deprecated },
22
+ ...r.sunset === void 0 ? {} : { sunset: r.sunset }
20
23
  });
21
- }, d = (e) => e.filter((e) => e.descriptor.publicApi !== void 0).map((e) => u(e.descriptor, e.invoke)).sort((e, t) => e.path.localeCompare(t.path)), f = (e) => (t) => {
24
+ }, d = (e) => e.filter((e) => e.descriptor.publicApi !== void 0).map((e) => u(e.descriptor, e.invoke, e.subscribe)).sort((e, t) => e.path.localeCompare(t.path)), f = (e) => (t) => {
22
25
  let n = t.subject.scopes;
23
26
  if (!(n && (n.includes(e) || n.includes("admin:full")))) return {
24
27
  status: 403,
25
28
  message: `Missing required scope: ${e}`
26
29
  };
27
- }, p = (e) => e, m = (e, t, n) => ({
30
+ }, p = (e) => (t) => {
31
+ let n = t.subject.scopes;
32
+ if (!(n && (n.includes("admin:full") || e.some((e) => n.includes(e))))) return {
33
+ status: 403,
34
+ message: `Missing required scope: one of ${e.join(", ")}`
35
+ };
36
+ }, m = (e) => e, h = (e, t = {}) => ({
37
+ __voltroRestStream: !0,
38
+ stream: {
39
+ subscribe: e,
40
+ ...t.keepAliveMs === void 0 ? {} : { keepAliveMs: t.keepAliveMs }
41
+ }
42
+ }), g = (e, t) => `event: ${e}\n${(typeof t == "string" ? t : JSON.stringify(t)).split("\n").map((e) => `data: ${e}`).join("\n")}\n\n`, _ = (e) => typeof e == "object" && !!e && e.__voltroRestStream === !0, v = (e, t, n) => ({
28
43
  status: e,
29
44
  contentType: "application/json; charset=utf-8",
30
45
  body: JSON.stringify(t),
31
46
  ...n ? { headers: n } : {}
32
- }), h = (e) => {
47
+ }), y = (e) => {
33
48
  if (e.length === 0) return;
34
49
  let t = new TextDecoder().decode(e);
35
50
  if (t.trim() !== "") return JSON.parse(t);
36
- }, g = (e) => {
51
+ }, b = (e) => {
37
52
  let t = {};
38
53
  for (let [n, r] of new URLSearchParams(e)) t[n] = r;
39
54
  return t;
40
- }, _ = (e, t) => {
55
+ }, x = (e, t) => {
41
56
  let n = e.split("/").filter((e) => e.length > 0), r = t.split("/").filter((e) => e.length > 0), i = {};
42
57
  for (let e = 0; e < n.length; e++) {
43
58
  let t = n[e];
@@ -51,22 +66,22 @@ var c = (e, t) => t.method ?? (e === "query" ? "GET" : "POST"), l = (e, t) => t.
51
66
  }
52
67
  }
53
68
  return i;
54
- }, v = (e, t) => ({
55
- query: g(e.query),
69
+ }, S = (e, t) => ({
70
+ query: b(e.query),
56
71
  params: t,
57
- body: h(e.rawBody)
58
- }), y = /* @__PURE__ */ new Set([
72
+ body: y(e.rawBody)
73
+ }), C = /* @__PURE__ */ new Set([
59
74
  "POST",
60
75
  "PUT",
61
76
  "PATCH",
62
77
  "DELETE"
63
- ]), b = (c, l) => {
78
+ ]), w = (c, l) => {
64
79
  let u = c.input ? s.decodeUnknown(c.input) : void 0, d = s.encode(c.output), f = {};
65
80
  return c.deprecated !== void 0 && (f.Deprecation = "true"), c.sunset !== void 0 && (f.Sunset = c.sunset), {
66
81
  method: "*",
67
82
  path: c.path,
68
83
  handle: async (s) => {
69
- if (s.method.toUpperCase() !== c.method) return m(405, {
84
+ if (s.method.toUpperCase() !== c.method) return v(405, {
70
85
  error: "Method Not Allowed",
71
86
  allow: c.method
72
87
  }, {
@@ -75,68 +90,74 @@ var c = (e, t) => t.method ?? (e === "query" ? "GET" : "POST"), l = (e, t) => t.
75
90
  });
76
91
  if (c.sunset !== void 0) {
77
92
  let e = Date.parse(c.sunset);
78
- if (!Number.isNaN(e) && Date.now() >= e) return m(410, {
93
+ if (!Number.isNaN(e) && Date.now() >= e) return v(410, {
79
94
  error: "Gone",
80
95
  ...c.deprecated === void 0 ? {} : { replacement: c.deprecated }
81
96
  }, f);
82
97
  }
83
98
  let p;
84
99
  if (u) {
85
- let e = v(s, _(c.path, s.path)), t = await o.runPromise(u(e).pipe(o.map((e) => ({
100
+ let e = S(s, x(c.path, s.path)), t = await o.runPromise(u(e).pipe(o.map((e) => ({
86
101
  ok: !0,
87
102
  value: e
88
103
  })), o.catchAll((e) => o.succeed({
89
104
  ok: !1,
90
105
  cause: e
91
106
  }))));
92
- if (!t.ok) return m(400, {
107
+ if (!t.ok) return v(400, {
93
108
  error: "Invalid request",
94
109
  detail: String(t.cause)
95
110
  }, f);
96
111
  p = t.value;
97
112
  }
98
- let h = l.resolveSubject ? await l.resolveSubject(s.headers) : a(s.headers["x-tenant"] ?? null), g = {
99
- subject: h,
113
+ let m = l.resolveSubject ? await l.resolveSubject(s.headers) : a(s.headers["x-tenant"] ?? null), h = {
114
+ subject: m,
100
115
  headers: s.headers,
101
116
  store: l.store
102
117
  };
103
118
  if (c.guards) for (let e of c.guards) {
104
- let t = await e(g);
105
- if (t) return m(t.status, { error: t.message }, f);
119
+ let t = await e(h);
120
+ if (t) return v(t.status, { error: t.message }, f);
106
121
  }
107
- let b = l.idempotency, x = b && y.has(c.method) ? s.headers[b.header.toLowerCase()] : void 0, S = b && x ? n(h.tenantId, c.method, c.path) : "";
108
- if (b && x) {
109
- let e = await r(b.store, S, x, b.ttlMs, Date.now());
110
- if (e.kind === "replay") return m(e.response.status, e.response.body, {
122
+ let g = l.idempotency, y = g && C.has(c.method) ? s.headers[g.header.toLowerCase()] : void 0, b = g && y ? n(m.tenantId, c.method, c.path) : "";
123
+ if (g && y) {
124
+ let e = await r(g.store, b, y, g.ttlMs, Date.now());
125
+ if (e.kind === "replay") return v(e.response.status, e.response.body, {
111
126
  ...f,
112
127
  "Idempotency-Replayed": "true"
113
128
  });
114
- if (e.kind === "conflict") return m(409, { error: "A request with this Idempotency-Key is already being processed" }, f);
129
+ if (e.kind === "conflict") return v(409, { error: "A request with this Idempotency-Key is already being processed" }, f);
115
130
  }
116
131
  try {
117
- let t = await c.handler(p, g), n = await o.runPromise(d(t));
118
- return b && x && await e(b.store, S, x, {
132
+ let t = await c.handler(p, h);
133
+ if (_(t)) return {
134
+ status: 200,
135
+ headers: f,
136
+ stream: t.stream
137
+ };
138
+ let n = await o.runPromise(d(t));
139
+ return g && y && await e(g.store, b, y, {
119
140
  status: 200,
120
141
  body: n
121
- }, Date.now()), m(200, n, f);
142
+ }, Date.now()), v(200, n, f);
122
143
  } catch (e) {
123
- if (b && x && await t(b.store, S, x), typeof e == "object" && e && typeof e.status == "number") {
144
+ if (g && y && await t(g.store, b, y), typeof e == "object" && e && typeof e.status == "number") {
124
145
  let t = e;
125
- return m(t.status, { error: t.message ?? "Error" }, f);
146
+ return v(t.status, { error: t.message ?? "Error" }, f);
126
147
  }
127
148
  return i({
128
149
  error: e,
129
150
  source: "rest",
130
151
  name: `${c.method} ${c.path}`,
131
152
  fields: { status: 500 }
132
- }), m(500, { error: "Internal Server Error" }, f);
153
+ }), v(500, { error: "Internal Server Error" }, f);
133
154
  }
134
155
  }
135
156
  };
136
- }, x = (e, t = {}) => e.map((e) => b(e, t)), S = async (e, t) => {
157
+ }, T = (e, t = {}) => e.map((e) => w(e, t)), E = async (e, t) => {
137
158
  let n = await e[0].handle(t);
138
159
  for (let r = 1; r < e.length && n.status === 405; r++) n = await e[r].handle(t);
139
160
  return n;
140
161
  };
141
162
  //#endregion
142
- export { d as collectPublicApiRoutes, p as defineRestRoute, c as derivePublicMethod, l as derivePublicPath, S as dispatchSharedPath, _ as matchPathParams, u as publicApiRoute, f as requireScope, x as restRoutesToHttpRoutes };
163
+ export { d as collectPublicApiRoutes, m as defineRestRoute, c as derivePublicMethod, l as derivePublicPath, E as dispatchSharedPath, _ as isRestStreamResponse, x as matchPathParams, u as publicApiRoute, p as requireAnyScope, f as requireScope, T as restRoutesToHttpRoutes, h as sse, g as sseFrame };
Binary file
package/dist/session.js CHANGED
@@ -1,4 +1,4 @@
1
- import { i as e } from "./auth-DCE6m7Bo.js";
1
+ import { i as e } from "./auth-S3cnL6Tz.js";
2
2
  import { Schema as t } from "effect";
3
3
  import { createHmac as n, timingSafeEqual as r } from "node:crypto";
4
4
  //#region src/session.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voltro/protocol",
3
- "version": "0.11.3",
3
+ "version": "0.12.0",
4
4
  "description": "The Voltro wire + plugin contract — defineQuery/Mutation/Action/Stream, definePlugin, sessions / JWT / API-keys, and the RPC protocol.",
5
5
  "keywords": [
6
6
  "voltro",
@@ -53,7 +53,7 @@
53
53
  },
54
54
  "dependencies": {
55
55
  "@effect/sql": "^0.51.1",
56
- "@voltro/database": "0.11.3",
56
+ "@voltro/database": "0.12.0",
57
57
  "jose": "^6.2.3"
58
58
  },
59
59
  "peerDependencies": {
Binary file