@claxedo/workspace-relay-protocol 0.7.0 → 0.8.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/README.md CHANGED
@@ -60,7 +60,7 @@ const claims = await verifier.verify("test-token")
60
60
  | `makeTunnelPing`, `makeTunnelPong` | Stable | Helpers for heartbeat requests and replies. |
61
61
  | `TokenVerifier` and claim types | Stable | Narrow verifier interface shared by relay/runtime boundaries. |
62
62
  | `RelayHostVerifierClaims` (incl. `role`) | Stable | Claims shape for host-side verification: org, `role` (`viewer` \| `editor` \| `admin` \| `owner`), and cloud/user-hosted backing discriminant. |
63
- | `createClerkTokenVerifier` | Public beta | Verifies Clerk session tokens with issuer, audience, JWKS, and algorithm constraints. |
63
+ | `createOidcTokenVerifier` | Public beta | Verifies session JWTs with issuer, audience, JWKS, and algorithm constraints. |
64
64
  | `createHttpTokenVerifier` | Public beta | Calls an operator-controlled verifier endpoint over HTTP. |
65
65
  | `createStaticTokenVerifier` | Test/single-tenant only | Fixed token table for tests and isolated self-hosted deployments. |
66
66
 
package/dist/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  export declare const TUNNEL_PROTOCOL_VERSION = 1;
2
- export { type TokenClaims, type TokenVerifierBaseClaims, type RuntimeAccessVerifierClaims, type RelayHostVerifierClaims, type TokenVerifier, type ClerkTokenVerifierClaims, TokenVerifierError, type ClerkTokenVerifierOptions, createClerkTokenVerifier, type HttpTokenVerifierOptions, createHttpTokenVerifier, type StaticTokenVerifierOptions, createStaticTokenVerifier, } from "./token-verifier";
2
+ export declare const SESSION_STREAM_LEASE_TTL_MS = 15000;
3
+ export declare const SESSION_TURN_LEASE_TTL_MS = 60000;
4
+ export { type TokenClaims, type TokenVerifierBaseClaims, type RuntimeAccessVerifierClaims, type RelayHostVerifierClaims, type TokenVerifier, type OidcTokenVerifierClaims, TokenVerifierError, type OidcTokenVerifierOptions, createOidcTokenVerifier, type HttpTokenVerifierOptions, createHttpTokenVerifier, type StaticTokenVerifierOptions, createStaticTokenVerifier, } from "./token-verifier";
3
5
  export type TunnelHeaderMap = Record<string, string>;
4
6
  export type TunnelPing = {
5
7
  type: "ping";
package/dist/index.mjs CHANGED
@@ -1,5 +1,26 @@
1
+ // ../claxedo-helpers/dist/guards.mjs
2
+ function isRecord(value) {
3
+ return value !== null && typeof value === "object" && !Array.isArray(value);
4
+ }
5
+ function isString(value) {
6
+ return typeof value === "string";
7
+ }
8
+ function isFiniteNumber(value) {
9
+ return typeof value === "number" && Number.isFinite(value);
10
+ }
11
+ function asFiniteNumber(value) {
12
+ return isFiniteNumber(value) ? value : void 0;
13
+ }
14
+ function stringClaim(payload, key) {
15
+ const value = payload[key];
16
+ return isString(value) && value.trim().length > 0 ? value : void 0;
17
+ }
18
+ function numberClaim(payload, key) {
19
+ return asFiniteNumber(payload[key]);
20
+ }
21
+
1
22
  // src/token-verifier.ts
2
- import { createRemoteJWKSet, jwtVerify } from "jose";
23
+ import { createRemoteJWKSet, errors as joseErrors, jwtVerify } from "jose";
3
24
  var TokenVerifierError = class extends Error {
4
25
  code;
5
26
  status;
@@ -30,34 +51,41 @@ function createHttpTokenVerifier(options) {
30
51
  method: "POST",
31
52
  headers: {
32
53
  "content-type": "application/json",
33
- ...options.headers ?? {}
54
+ ...options.headers
34
55
  },
35
56
  body: JSON.stringify({ token }),
36
57
  signal: controller.signal
37
58
  });
38
59
  if (!res.ok) {
39
60
  throw new TokenVerifierError({
40
- code: "verifier_rejected",
61
+ code: res.status === 401 || res.status === 403 ? "verifier_rejected" : "verifier_unavailable",
41
62
  message: `Verifier endpoint returned ${res.status}`,
42
- status: res.status === 401 || res.status === 403 ? res.status : 401
63
+ status: res.status === 401 || res.status === 403 ? res.status : 503
43
64
  });
44
65
  }
45
66
  const body = await res.json().catch(() => null);
46
- if (!body || typeof body.subject !== "string" || !body.subject) {
67
+ if (!isRecord(body)) {
68
+ throw new TokenVerifierError({
69
+ code: "verifier_response_invalid",
70
+ message: "Verifier response is missing a subject"
71
+ });
72
+ }
73
+ const subject = stringClaim(body, "subject");
74
+ if (!subject) {
47
75
  throw new TokenVerifierError({
48
76
  code: "verifier_response_invalid",
49
77
  message: "Verifier response is missing a subject"
50
78
  });
51
79
  }
52
80
  const claims = tokenVerifierBaseClaims(body.claims);
53
- if (!claims || claims.sub !== body.subject) {
81
+ if (!claims || claims.sub !== subject) {
54
82
  throw new TokenVerifierError({
55
83
  code: "verifier_response_invalid",
56
84
  message: "Verifier response is missing required token claims"
57
85
  });
58
86
  }
59
87
  return {
60
- subject: body.subject,
88
+ subject,
61
89
  scopes: Array.isArray(body.scopes) ? body.scopes.filter((s) => typeof s === "string") : [],
62
90
  claims
63
91
  };
@@ -66,6 +94,7 @@ function createHttpTokenVerifier(options) {
66
94
  throw new TokenVerifierError({
67
95
  code: "verifier_unreachable",
68
96
  message: err instanceof Error ? err.message : String(err),
97
+ status: 503,
69
98
  cause: err
70
99
  });
71
100
  } finally {
@@ -74,11 +103,11 @@ function createHttpTokenVerifier(options) {
74
103
  }
75
104
  };
76
105
  }
77
- var clerkJwks = /* @__PURE__ */ new Map();
78
- function createClerkTokenVerifier(options) {
106
+ var oidcJwks = /* @__PURE__ */ new Map();
107
+ function createOidcTokenVerifier(options) {
79
108
  const algorithms = options.algorithms ?? ["ES256", "EdDSA", "RS256"];
80
- const remote = clerkJwks.get(options.jwksUrl) ?? createRemoteJWKSet(new URL(options.jwksUrl));
81
- clerkJwks.set(options.jwksUrl, remote);
109
+ const remote = oidcJwks.get(options.jwksUrl) ?? createRemoteJWKSet(new URL(options.jwksUrl));
110
+ oidcJwks.set(options.jwksUrl, remote);
82
111
  return {
83
112
  async verify(token) {
84
113
  try {
@@ -87,11 +116,11 @@ function createClerkTokenVerifier(options) {
87
116
  audience: options.audience,
88
117
  algorithms
89
118
  });
90
- const claims = clerkClaims(result.payload);
119
+ const claims = oidcClaims(result.payload);
91
120
  if (!claims) {
92
121
  throw new TokenVerifierError({
93
- code: "clerk_claims_invalid",
94
- message: "Clerk token is missing required session claims"
122
+ code: "oidc_claims_invalid",
123
+ message: "Session token is missing required claims"
95
124
  });
96
125
  }
97
126
  return {
@@ -101,9 +130,11 @@ function createClerkTokenVerifier(options) {
101
130
  };
102
131
  } catch (err) {
103
132
  if (err instanceof TokenVerifierError) throw err;
133
+ const unavailable = err instanceof joseErrors.JWKSTimeout || !(err instanceof joseErrors.JOSEError);
104
134
  throw new TokenVerifierError({
105
- code: "clerk_token_invalid",
106
- message: "Clerk session token is invalid",
135
+ code: unavailable ? "oidc_verifier_unavailable" : "oidc_token_invalid",
136
+ message: unavailable ? "Session verifier is unavailable" : "Session token is invalid",
137
+ status: unavailable ? 503 : 401,
107
138
  cause: err
108
139
  });
109
140
  }
@@ -111,10 +142,10 @@ function createClerkTokenVerifier(options) {
111
142
  };
112
143
  }
113
144
  function createStaticTokenVerifier(options) {
114
- const tokens = Object.assign(/* @__PURE__ */ Object.create(null), options.tokens);
145
+ const tokens = new Map(Object.entries(options.tokens));
115
146
  return {
116
147
  async verify(token) {
117
- const claims = tokens[token];
148
+ const claims = tokens.get(token);
118
149
  if (!claims) {
119
150
  throw new TokenVerifierError({
120
151
  code: "token_not_in_static_table",
@@ -125,16 +156,8 @@ function createStaticTokenVerifier(options) {
125
156
  }
126
157
  };
127
158
  }
128
- function stringClaim(input, key) {
129
- const value = input[key];
130
- return typeof value === "string" && value.trim() ? value : void 0;
131
- }
132
- function numberClaim(input, key) {
133
- const value = input[key];
134
- return typeof value === "number" && Number.isFinite(value) ? value : void 0;
135
- }
136
159
  function tokenVerifierBaseClaims(input) {
137
- if (!input || typeof input !== "object") return;
160
+ if (!isRecord(input)) return void 0;
138
161
  const iss = stringClaim(input, "iss");
139
162
  const aud = stringClaim(input, "aud");
140
163
  const sub = stringClaim(input, "sub");
@@ -143,7 +166,7 @@ function tokenVerifierBaseClaims(input) {
143
166
  const exp = numberClaim(input, "exp");
144
167
  const iat = numberClaim(input, "iat");
145
168
  const jti = stringClaim(input, "jti");
146
- if (!iss || !aud || !sub || !workspace_id || !host_id || !exp || !iat || !jti) return;
169
+ if (!iss || !aud || !sub || !workspace_id || !host_id || !exp || !iat || !jti) return void 0;
147
170
  return {
148
171
  ...input,
149
172
  iss,
@@ -156,8 +179,11 @@ function tokenVerifierBaseClaims(input) {
156
179
  jti
157
180
  };
158
181
  }
159
- function clerkClaims(input) {
160
- if (!input.iss || !input.sub || !input.exp) return;
182
+ function oidcClaims(input) {
183
+ if (!input.iss || !input.sub || !input.exp) return void 0;
184
+ const sid = stringClaim(input, "sid");
185
+ const org_id = stringClaim(input, "org_id");
186
+ const orgId = stringClaim(input, "orgId");
161
187
  return {
162
188
  ...input,
163
189
  iss: input.iss,
@@ -165,9 +191,9 @@ function clerkClaims(input) {
165
191
  exp: input.exp,
166
192
  ...input.iat ? { iat: input.iat } : {},
167
193
  ...typeof input.jti === "string" ? { jti: input.jti } : {},
168
- ...stringClaim(input, "sid") ? { sid: stringClaim(input, "sid") } : {},
169
- ...stringClaim(input, "org_id") ? { org_id: stringClaim(input, "org_id") } : {},
170
- ...stringClaim(input, "orgId") ? { orgId: stringClaim(input, "orgId") } : {}
194
+ ...sid ? { sid } : {},
195
+ ...org_id ? { org_id } : {},
196
+ ...orgId ? { orgId } : {}
171
197
  };
172
198
  }
173
199
  function scopeClaims(input) {
@@ -178,39 +204,24 @@ function scopeClaims(input) {
178
204
 
179
205
  // src/index.ts
180
206
  var TUNNEL_PROTOCOL_VERSION = 1;
181
- var tunnelMessageTypes = /* @__PURE__ */ new Set([
182
- "ping",
183
- "pong",
184
- "host.registration.update",
185
- "http.request",
186
- "http.response.start",
187
- "http.response.chunk",
188
- "http.response.end",
189
- "http.response.flow",
190
- "ws.open",
191
- "ws.frame",
192
- "ws.close",
193
- "error"
194
- ]);
207
+ var SESSION_STREAM_LEASE_TTL_MS = 15e3;
208
+ var SESSION_TURN_LEASE_TTL_MS = 6e4;
195
209
  function validateTunnelMessage(input) {
196
- if (!input || typeof input !== "object") return { ok: false, reason: "invalid" };
197
- const row = input;
198
- if (row.protocol !== TUNNEL_PROTOCOL_VERSION) {
210
+ if (!isRecord(input)) return { ok: false, reason: "invalid" };
211
+ if (input.protocol !== TUNNEL_PROTOCOL_VERSION) {
199
212
  return {
200
213
  ok: false,
201
214
  reason: "protocol_mismatch",
202
215
  expected_protocol: TUNNEL_PROTOCOL_VERSION,
203
- actual_protocol: row.protocol,
204
- ...typeof row.type === "string" ? { type: row.type } : {}
216
+ actual_protocol: input.protocol,
217
+ ...typeof input.type === "string" ? { type: input.type } : {}
205
218
  };
206
219
  }
207
- if (typeof row.type !== "string") return { ok: false, reason: "invalid" };
208
- if (!tunnelMessageTypes.has(row.type)) return { ok: false, reason: "invalid" };
209
- if (!validateTunnelMessageShape(row)) return { ok: false, reason: "invalid" };
210
- return { ok: true, message: row };
220
+ if (!isTunnelMessageRecord(input)) return { ok: false, reason: "invalid" };
221
+ return { ok: true, message: input };
211
222
  }
212
223
  function isTunnelMessage(input) {
213
- return validateTunnelMessage(input).ok;
224
+ return isRecord(input) && isTunnelMessageRecord(input);
214
225
  }
215
226
  function makeTunnelPong(input, receivedAt = Date.now()) {
216
227
  return {
@@ -229,44 +240,42 @@ function makeTunnelPing(sentAt = Date.now(), id = crypto.randomUUID()) {
229
240
  sent_at: sentAt
230
241
  };
231
242
  }
232
- function validateTunnelMessageShape(row) {
243
+ function isTunnelMessageRecord(row) {
244
+ if (row.protocol !== TUNNEL_PROTOCOL_VERSION) return false;
233
245
  switch (row.type) {
234
246
  case "ping":
235
- return isString(row.id) && isFiniteNumber(row.sent_at);
247
+ return isNonEmptyString(row.id) && isFiniteNumber(row.sent_at);
236
248
  case "pong":
237
- return isString(row.id) && isFiniteNumber(row.sent_at) && isFiniteNumber(row.received_at);
249
+ return isNonEmptyString(row.id) && isFiniteNumber(row.sent_at) && isFiniteNumber(row.received_at);
238
250
  case "host.registration.update":
239
- return isStringArray(row.workspace_ids) && isString(row.token);
251
+ return isStringArray(row.workspace_ids) && isNonEmptyString(row.token);
240
252
  case "http.request":
241
- return isString(row.request_id) && isString(row.workspace_id) && isString(row.method) && isString(row.path) && isTunnelHeaderMap(row.headers) && isOptionalBase64(row.body_base64) && typeof row.end === "boolean";
253
+ return isNonEmptyString(row.request_id) && isNonEmptyString(row.workspace_id) && isNonEmptyString(row.method) && isNonEmptyString(row.path) && isTunnelHeaderMap(row.headers) && isOptionalBase64(row.body_base64) && typeof row.end === "boolean";
242
254
  case "http.response.start":
243
- return isString(row.request_id) && isHttpStatus(row.status) && isTunnelHeaderMap(row.headers);
255
+ return isNonEmptyString(row.request_id) && isHttpStatus(row.status) && isTunnelHeaderMap(row.headers);
244
256
  case "http.response.chunk":
245
- return isString(row.request_id) && isBase64(row.body_base64);
257
+ return isNonEmptyString(row.request_id) && isBase64(row.body_base64);
246
258
  case "http.response.end":
247
- return isString(row.request_id);
259
+ return isNonEmptyString(row.request_id);
248
260
  case "http.response.flow":
249
- return isString(row.request_id) && typeof row.paused === "boolean" && (row.reason === void 0 || row.reason === "slow_consumer" || row.reason === "drained" || row.reason === "closed");
261
+ return isNonEmptyString(row.request_id) && typeof row.paused === "boolean" && (row.reason === void 0 || row.reason === "slow_consumer" || row.reason === "drained" || row.reason === "closed");
250
262
  case "ws.open":
251
- return isString(row.channel_id) && isString(row.workspace_id) && isString(row.path) && isTunnelHeaderMap(row.headers);
263
+ return isNonEmptyString(row.channel_id) && isNonEmptyString(row.workspace_id) && isNonEmptyString(row.path) && isTunnelHeaderMap(row.headers);
252
264
  case "ws.frame":
253
- return isString(row.channel_id) && isBase64(row.data_base64) && typeof row.binary === "boolean";
265
+ return isNonEmptyString(row.channel_id) && isBase64(row.data_base64) && typeof row.binary === "boolean";
254
266
  case "ws.close":
255
- return isString(row.channel_id) && (row.code === void 0 || isCloseCode(row.code)) && (row.reason === void 0 || isString(row.reason));
267
+ return isNonEmptyString(row.channel_id) && (row.code === void 0 || isCloseCode(row.code)) && (row.reason === void 0 || isNonEmptyString(row.reason));
256
268
  case "error":
257
- return (row.request_id === void 0 || isString(row.request_id)) && (row.channel_id === void 0 || isString(row.channel_id)) && isString(row.code) && isString(row.message);
269
+ return (row.request_id === void 0 || isNonEmptyString(row.request_id)) && (row.channel_id === void 0 || isNonEmptyString(row.channel_id)) && isNonEmptyString(row.code) && isNonEmptyString(row.message);
258
270
  default:
259
271
  return false;
260
272
  }
261
273
  }
262
- function isString(input) {
274
+ function isNonEmptyString(input) {
263
275
  return typeof input === "string" && input.length > 0;
264
276
  }
265
277
  function isStringArray(input) {
266
- return Array.isArray(input) && input.length > 0 && input.every(isString);
267
- }
268
- function isFiniteNumber(input) {
269
- return typeof input === "number" && Number.isFinite(input);
278
+ return Array.isArray(input) && input.length > 0 && input.every(isNonEmptyString);
270
279
  }
271
280
  function isHttpStatus(input) {
272
281
  return typeof input === "number" && Number.isInteger(input) && input >= 100 && input <= 599;
@@ -288,10 +297,12 @@ function isBase64(input) {
288
297
  return typeof input === "string" && /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(input);
289
298
  }
290
299
  export {
300
+ SESSION_STREAM_LEASE_TTL_MS,
301
+ SESSION_TURN_LEASE_TTL_MS,
291
302
  TUNNEL_PROTOCOL_VERSION,
292
303
  TokenVerifierError,
293
- createClerkTokenVerifier,
294
304
  createHttpTokenVerifier,
305
+ createOidcTokenVerifier,
295
306
  createStaticTokenVerifier,
296
307
  isTunnelMessage,
297
308
  makeTunnelPing,
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Unified token-verifier interface (P-shared).
3
3
  *
4
- * Each of the four Claxedo boundaries (relay/tunnel, agent extension,
4
+ * Each of the four Claxedo boundaries (relay/tunnel, optional modules,
5
5
  * SDK runner, workspace host) historically defined its own ad-hoc
6
6
  * verifier shape. This module is the single seam every downstream user
7
7
  * can swap a custom implementation through.
@@ -23,13 +23,27 @@ export type TokenVerifierBaseClaims = {
23
23
  iat: number;
24
24
  jti: string;
25
25
  } & Record<string, unknown>;
26
- export type RuntimeAccessVerifierClaims = TokenVerifierBaseClaims & {
26
+ export type RuntimeAccessVerifierClaims = Omit<TokenVerifierBaseClaims, "sub"> & {
27
27
  org_id: string;
28
28
  role: "viewer" | "editor" | "admin" | "owner";
29
+ principal_kind: "user" | "service";
30
+ actor_id: string;
31
+ actor_kind: "human" | "agent";
32
+ actor_public_id?: string;
33
+ actor_name?: string;
34
+ actor_avatar_url?: string;
29
35
  };
30
- export type RelayHostVerifierClaims = TokenVerifierBaseClaims & {
36
+ export type RelayHostVerifierClaims = Omit<TokenVerifierBaseClaims, "sub"> & {
31
37
  org_id: string;
32
38
  role: "viewer" | "editor" | "admin" | "owner";
39
+ principal_kind: "user" | "service";
40
+ actor_id: string;
41
+ actor_kind: "human" | "agent";
42
+ actor_public_id?: string;
43
+ actor_name?: string;
44
+ actor_avatar_url?: string;
45
+ /** Durable Runtime Access Token id; distinct from this short-lived RHT jti. */
46
+ parent_jti: string;
33
47
  } & ({
34
48
  access: "cloud";
35
49
  backing: "cloud-vm";
@@ -38,9 +52,9 @@ export type RelayHostVerifierClaims = TokenVerifierBaseClaims & {
38
52
  backing: "local-worktree";
39
53
  });
40
54
  export type TokenClaims<TClaims extends Record<string, unknown> = Record<string, unknown>> = {
41
- /** Stable subject id. The boundary is responsible for mapping this
42
- * to a domain principal (user, workspace, runtime, etc.). */
43
- subject: string;
55
+ /** Provider subject for provider-backed identity verifiers. Runtime and
56
+ * relay-host tokens instead carry canonical actor/principal claims. */
57
+ subject?: string;
44
58
  /** Scopes / capabilities asserted by the issuer. May be empty. */
45
59
  scopes: string[];
46
60
  /** Full claim payload. Carries the common verifier token contract plus
@@ -50,7 +64,7 @@ export type TokenClaims<TClaims extends Record<string, unknown> = Record<string,
50
64
  export type TokenVerifier<TClaims extends Record<string, unknown> = Record<string, unknown>> = {
51
65
  verify(token: string): Promise<TokenClaims<TClaims>>;
52
66
  };
53
- export type ClerkTokenVerifierClaims = JWTPayload & {
67
+ export type OidcTokenVerifierClaims = JWTPayload & {
54
68
  iss: string;
55
69
  sub: string;
56
70
  exp: number;
@@ -83,14 +97,14 @@ export type HttpTokenVerifierOptions = {
83
97
  timeoutMs?: number;
84
98
  };
85
99
  export declare function createHttpTokenVerifier(options: HttpTokenVerifierOptions): TokenVerifier<TokenVerifierBaseClaims>;
86
- export type ClerkTokenVerifierOptions = {
100
+ export type OidcTokenVerifierOptions = {
87
101
  issuer: string;
88
102
  jwksUrl: string;
89
103
  audience?: string;
90
104
  scopes?: string[];
91
105
  algorithms?: Array<"ES256" | "EdDSA" | "RS256">;
92
106
  };
93
- export declare function createClerkTokenVerifier(options: ClerkTokenVerifierOptions): TokenVerifier<ClerkTokenVerifierClaims>;
107
+ export declare function createOidcTokenVerifier(options: OidcTokenVerifierOptions): TokenVerifier<OidcTokenVerifierClaims>;
94
108
  export type StaticTokenVerifierOptions<TClaims extends Record<string, unknown> = Record<string, unknown>> = {
95
109
  /** Map of token string → claims to return. */
96
110
  tokens: Record<string, TokenClaims<TClaims>>;
package/package.json CHANGED
@@ -1,6 +1,4 @@
1
1
  {
2
- "name": "@claxedo/workspace-relay-protocol",
3
- "version": "0.7.0",
4
2
  "type": "module",
5
3
  "description": "Wire types, message validation, and token verifier interfaces for the Claxedo workspace relay tunnel protocol",
6
4
  "license": "MIT",
@@ -19,9 +17,9 @@
19
17
  "types": "./dist/index.d.ts",
20
18
  "exports": {
21
19
  ".": {
20
+ "development": "./src/index.ts",
21
+ "bun": "./src/index.ts",
22
22
  "types": "./dist/index.d.ts",
23
- "bun": "./dist/index.mjs",
24
- "development": "./dist/index.mjs",
25
23
  "import": "./dist/index.mjs",
26
24
  "default": "./dist/index.mjs"
27
25
  }
@@ -34,14 +32,17 @@
34
32
  "publishConfig": {
35
33
  "access": "public"
36
34
  },
37
- "dependencies": {
38
- "jose": "6.0.11"
39
- },
35
+ "name": "@claxedo/workspace-relay-protocol",
36
+ "version": "0.8.0",
40
37
  "scripts": {
41
38
  "build": "tsx scripts/build.ts",
42
39
  "prepublishOnly": "npm run build",
43
40
  "typecheck": "tsc --noEmit -p tsconfig.json",
44
- "test": "mkdir -p .artifacts/unit && bun test src --reporter=junit --reporter-outfile=.artifacts/unit/junit.xml"
41
+ "test": "mkdir -p .artifacts/unit && node ../../scripts/test-deadline.mjs bun test src --reporter=junit --reporter-outfile=.artifacts/unit/junit.xml"
42
+ },
43
+ "dependencies": {
44
+ "@claxedo/helpers": "0.1.0",
45
+ "jose": "6.0.11"
45
46
  },
46
47
  "devDependencies": {
47
48
  "@tsconfig/node-lts": "22.0.4",