@vouched-dev/schema 0.1.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 (53) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +12 -0
  3. package/dist/agent-id.d.ts +13 -0
  4. package/dist/agent-id.js +29 -0
  5. package/dist/api.d.ts +475 -0
  6. package/dist/api.js +260 -0
  7. package/dist/base64url.d.ts +4 -0
  8. package/dist/base64url.js +43 -0
  9. package/dist/credential.d.ts +85 -0
  10. package/dist/credential.js +67 -0
  11. package/dist/db/agents.d.ts +128 -0
  12. package/dist/db/agents.js +31 -0
  13. package/dist/db/client.d.ts +1443 -0
  14. package/dist/db/client.js +28 -0
  15. package/dist/db/credentials.d.ts +143 -0
  16. package/dist/db/credentials.js +16 -0
  17. package/dist/db/events.d.ts +160 -0
  18. package/dist/db/events.js +23 -0
  19. package/dist/db/feed-items.d.ts +109 -0
  20. package/dist/db/feed-items.js +16 -0
  21. package/dist/db/index.d.ts +43 -0
  22. package/dist/db/index.js +17 -0
  23. package/dist/db/migrate.d.ts +1 -0
  24. package/dist/db/migrate.js +30 -0
  25. package/dist/db/migrator.d.ts +2 -0
  26. package/dist/db/migrator.js +20 -0
  27. package/dist/db/operators.d.ts +109 -0
  28. package/dist/db/operators.js +11 -0
  29. package/dist/db/quota-counters.d.ts +92 -0
  30. package/dist/db/quota-counters.js +11 -0
  31. package/dist/db/ratings.d.ts +160 -0
  32. package/dist/db/ratings.js +27 -0
  33. package/dist/db/scores.d.ts +160 -0
  34. package/dist/db/scores.js +15 -0
  35. package/dist/db/task-outcomes.d.ts +126 -0
  36. package/dist/db/task-outcomes.js +18 -0
  37. package/dist/db/tasks.d.ts +251 -0
  38. package/dist/db/tasks.js +26 -0
  39. package/dist/db/timestamps.d.ts +4 -0
  40. package/dist/db/timestamps.js +24 -0
  41. package/dist/db/url.d.ts +12 -0
  42. package/dist/db/url.js +25 -0
  43. package/dist/dimensions.d.ts +20 -0
  44. package/dist/dimensions.js +11 -0
  45. package/dist/envelope.d.ts +16 -0
  46. package/dist/envelope.js +70 -0
  47. package/dist/events.d.ts +153 -0
  48. package/dist/events.js +96 -0
  49. package/dist/index.d.ts +9 -0
  50. package/dist/index.js +9 -0
  51. package/dist/tasks.d.ts +46 -0
  52. package/dist/tasks.js +41 -0
  53. package/package.json +52 -0
@@ -0,0 +1,126 @@
1
+ export declare const taskOutcomes: import("drizzle-orm/pg-core").PgTableWithColumns<{
2
+ name: "task_outcomes";
3
+ schema: undefined;
4
+ columns: {
5
+ createdAt: import("drizzle-orm/pg-core").PgColumn<{
6
+ name: "created_at";
7
+ tableName: "task_outcomes";
8
+ dataType: "date";
9
+ columnType: "PgTimestamp";
10
+ data: Date;
11
+ driverParam: string;
12
+ notNull: true;
13
+ hasDefault: true;
14
+ isPrimaryKey: false;
15
+ isAutoincrement: false;
16
+ hasRuntimeDefault: false;
17
+ enumValues: undefined;
18
+ baseColumn: never;
19
+ identity: undefined;
20
+ generated: undefined;
21
+ }, {}, {}>;
22
+ updatedAt: import("drizzle-orm/pg-core").PgColumn<{
23
+ name: "updated_at";
24
+ tableName: "task_outcomes";
25
+ dataType: "date";
26
+ columnType: "PgTimestamp";
27
+ data: Date;
28
+ driverParam: string;
29
+ notNull: true;
30
+ hasDefault: true;
31
+ isPrimaryKey: false;
32
+ isAutoincrement: false;
33
+ hasRuntimeDefault: false;
34
+ enumValues: undefined;
35
+ baseColumn: never;
36
+ identity: undefined;
37
+ generated: undefined;
38
+ }, {}, {}>;
39
+ id: import("drizzle-orm/pg-core").PgColumn<{
40
+ name: "id";
41
+ tableName: "task_outcomes";
42
+ dataType: "string";
43
+ columnType: "PgUUID";
44
+ data: string;
45
+ driverParam: string;
46
+ notNull: true;
47
+ hasDefault: false;
48
+ isPrimaryKey: true;
49
+ isAutoincrement: false;
50
+ hasRuntimeDefault: false;
51
+ enumValues: undefined;
52
+ baseColumn: never;
53
+ identity: undefined;
54
+ generated: undefined;
55
+ }, {}, {}>;
56
+ taskId: import("drizzle-orm/pg-core").PgColumn<{
57
+ name: "task_id";
58
+ tableName: "task_outcomes";
59
+ dataType: "string";
60
+ columnType: "PgUUID";
61
+ data: string;
62
+ driverParam: string;
63
+ notNull: true;
64
+ hasDefault: false;
65
+ isPrimaryKey: false;
66
+ isAutoincrement: false;
67
+ hasRuntimeDefault: false;
68
+ enumValues: undefined;
69
+ baseColumn: never;
70
+ identity: undefined;
71
+ generated: undefined;
72
+ }, {}, {}>;
73
+ reportingAgentId: import("drizzle-orm/pg-core").PgColumn<{
74
+ name: "reporting_agent_id";
75
+ tableName: "task_outcomes";
76
+ dataType: "string";
77
+ columnType: "PgText";
78
+ data: string;
79
+ driverParam: string;
80
+ notNull: true;
81
+ hasDefault: false;
82
+ isPrimaryKey: false;
83
+ isAutoincrement: false;
84
+ hasRuntimeDefault: false;
85
+ enumValues: [string, ...string[]];
86
+ baseColumn: never;
87
+ identity: undefined;
88
+ generated: undefined;
89
+ }, {}, {}>;
90
+ outcome: import("drizzle-orm/pg-core").PgColumn<{
91
+ name: "outcome";
92
+ tableName: "task_outcomes";
93
+ dataType: "string";
94
+ columnType: "PgText";
95
+ data: string;
96
+ driverParam: string;
97
+ notNull: true;
98
+ hasDefault: false;
99
+ isPrimaryKey: false;
100
+ isAutoincrement: false;
101
+ hasRuntimeDefault: false;
102
+ enumValues: [string, ...string[]];
103
+ baseColumn: never;
104
+ identity: undefined;
105
+ generated: undefined;
106
+ }, {}, {}>;
107
+ evidence: import("drizzle-orm/pg-core").PgColumn<{
108
+ name: "evidence";
109
+ tableName: "task_outcomes";
110
+ dataType: "json";
111
+ columnType: "PgJsonb";
112
+ data: unknown;
113
+ driverParam: unknown;
114
+ notNull: false;
115
+ hasDefault: false;
116
+ isPrimaryKey: false;
117
+ isAutoincrement: false;
118
+ hasRuntimeDefault: false;
119
+ enumValues: undefined;
120
+ baseColumn: never;
121
+ identity: undefined;
122
+ generated: undefined;
123
+ }, {}, {}>;
124
+ };
125
+ dialect: 'pg';
126
+ }>;
@@ -0,0 +1,18 @@
1
+ import { jsonb, pgTable, text, unique, uuid } from 'drizzle-orm/pg-core';
2
+ import { agents } from './agents.js';
3
+ import { tasks } from './tasks.js';
4
+ import { timestamps } from './timestamps.js';
5
+ export const taskOutcomes = pgTable('task_outcomes', {
6
+ id: uuid('id').primaryKey(),
7
+ taskId: uuid('task_id')
8
+ .notNull()
9
+ .references(() => tasks.id),
10
+ reportingAgentId: text('reporting_agent_id')
11
+ .notNull()
12
+ .references(() => agents.id),
13
+ outcome: text('outcome').notNull(),
14
+ evidence: jsonb('evidence'),
15
+ ...timestamps,
16
+ }, (t) => [
17
+ unique('task_outcomes_task_id_reporting_agent_id_unique').on(t.taskId, t.reportingAgentId),
18
+ ]);
@@ -0,0 +1,251 @@
1
+ export declare const tasks: import("drizzle-orm/pg-core").PgTableWithColumns<{
2
+ name: "tasks";
3
+ schema: undefined;
4
+ columns: {
5
+ createdAt: import("drizzle-orm/pg-core").PgColumn<{
6
+ name: "created_at";
7
+ tableName: "tasks";
8
+ dataType: "date";
9
+ columnType: "PgTimestamp";
10
+ data: Date;
11
+ driverParam: string;
12
+ notNull: true;
13
+ hasDefault: true;
14
+ isPrimaryKey: false;
15
+ isAutoincrement: false;
16
+ hasRuntimeDefault: false;
17
+ enumValues: undefined;
18
+ baseColumn: never;
19
+ identity: undefined;
20
+ generated: undefined;
21
+ }, {}, {}>;
22
+ updatedAt: import("drizzle-orm/pg-core").PgColumn<{
23
+ name: "updated_at";
24
+ tableName: "tasks";
25
+ dataType: "date";
26
+ columnType: "PgTimestamp";
27
+ data: Date;
28
+ driverParam: string;
29
+ notNull: true;
30
+ hasDefault: true;
31
+ isPrimaryKey: false;
32
+ isAutoincrement: false;
33
+ hasRuntimeDefault: false;
34
+ enumValues: undefined;
35
+ baseColumn: never;
36
+ identity: undefined;
37
+ generated: undefined;
38
+ }, {}, {}>;
39
+ id: import("drizzle-orm/pg-core").PgColumn<{
40
+ name: "id";
41
+ tableName: "tasks";
42
+ dataType: "string";
43
+ columnType: "PgUUID";
44
+ data: string;
45
+ driverParam: string;
46
+ notNull: true;
47
+ hasDefault: false;
48
+ isPrimaryKey: true;
49
+ isAutoincrement: false;
50
+ hasRuntimeDefault: false;
51
+ enumValues: undefined;
52
+ baseColumn: never;
53
+ identity: undefined;
54
+ generated: undefined;
55
+ }, {}, {}>;
56
+ posterAgentId: import("drizzle-orm/pg-core").PgColumn<{
57
+ name: "poster_agent_id";
58
+ tableName: "tasks";
59
+ dataType: "string";
60
+ columnType: "PgText";
61
+ data: string;
62
+ driverParam: string;
63
+ notNull: true;
64
+ hasDefault: false;
65
+ isPrimaryKey: false;
66
+ isAutoincrement: false;
67
+ hasRuntimeDefault: false;
68
+ enumValues: [string, ...string[]];
69
+ baseColumn: never;
70
+ identity: undefined;
71
+ generated: undefined;
72
+ }, {}, {}>;
73
+ claimantAgentId: import("drizzle-orm/pg-core").PgColumn<{
74
+ name: "claimant_agent_id";
75
+ tableName: "tasks";
76
+ dataType: "string";
77
+ columnType: "PgText";
78
+ data: string;
79
+ driverParam: string;
80
+ notNull: false;
81
+ hasDefault: false;
82
+ isPrimaryKey: false;
83
+ isAutoincrement: false;
84
+ hasRuntimeDefault: false;
85
+ enumValues: [string, ...string[]];
86
+ baseColumn: never;
87
+ identity: undefined;
88
+ generated: undefined;
89
+ }, {}, {}>;
90
+ taskType: import("drizzle-orm/pg-core").PgColumn<{
91
+ name: "task_type";
92
+ tableName: "tasks";
93
+ dataType: "string";
94
+ columnType: "PgText";
95
+ data: string;
96
+ driverParam: string;
97
+ notNull: true;
98
+ hasDefault: false;
99
+ isPrimaryKey: false;
100
+ isAutoincrement: false;
101
+ hasRuntimeDefault: false;
102
+ enumValues: [string, ...string[]];
103
+ baseColumn: never;
104
+ identity: undefined;
105
+ generated: undefined;
106
+ }, {}, {}>;
107
+ spec: import("drizzle-orm/pg-core").PgColumn<{
108
+ name: "spec";
109
+ tableName: "tasks";
110
+ dataType: "json";
111
+ columnType: "PgJsonb";
112
+ data: unknown;
113
+ driverParam: unknown;
114
+ notNull: true;
115
+ hasDefault: false;
116
+ isPrimaryKey: false;
117
+ isAutoincrement: false;
118
+ hasRuntimeDefault: false;
119
+ enumValues: undefined;
120
+ baseColumn: never;
121
+ identity: undefined;
122
+ generated: undefined;
123
+ }, {}, {}>;
124
+ verification: import("drizzle-orm/pg-core").PgColumn<{
125
+ name: "verification";
126
+ tableName: "tasks";
127
+ dataType: "json";
128
+ columnType: "PgJsonb";
129
+ data: unknown;
130
+ driverParam: unknown;
131
+ notNull: true;
132
+ hasDefault: false;
133
+ isPrimaryKey: false;
134
+ isAutoincrement: false;
135
+ hasRuntimeDefault: false;
136
+ enumValues: undefined;
137
+ baseColumn: never;
138
+ identity: undefined;
139
+ generated: undefined;
140
+ }, {}, {}>;
141
+ postedAt: import("drizzle-orm/pg-core").PgColumn<{
142
+ name: "posted_at";
143
+ tableName: "tasks";
144
+ dataType: "date";
145
+ columnType: "PgTimestamp";
146
+ data: Date;
147
+ driverParam: string;
148
+ notNull: true;
149
+ hasDefault: true;
150
+ isPrimaryKey: false;
151
+ isAutoincrement: false;
152
+ hasRuntimeDefault: false;
153
+ enumValues: undefined;
154
+ baseColumn: never;
155
+ identity: undefined;
156
+ generated: undefined;
157
+ }, {}, {}>;
158
+ claimedAt: import("drizzle-orm/pg-core").PgColumn<{
159
+ name: "claimed_at";
160
+ tableName: "tasks";
161
+ dataType: "date";
162
+ columnType: "PgTimestamp";
163
+ data: Date;
164
+ driverParam: string;
165
+ notNull: false;
166
+ hasDefault: false;
167
+ isPrimaryKey: false;
168
+ isAutoincrement: false;
169
+ hasRuntimeDefault: false;
170
+ enumValues: undefined;
171
+ baseColumn: never;
172
+ identity: undefined;
173
+ generated: undefined;
174
+ }, {}, {}>;
175
+ submittedAt: import("drizzle-orm/pg-core").PgColumn<{
176
+ name: "submitted_at";
177
+ tableName: "tasks";
178
+ dataType: "date";
179
+ columnType: "PgTimestamp";
180
+ data: Date;
181
+ driverParam: string;
182
+ notNull: false;
183
+ hasDefault: false;
184
+ isPrimaryKey: false;
185
+ isAutoincrement: false;
186
+ hasRuntimeDefault: false;
187
+ enumValues: undefined;
188
+ baseColumn: never;
189
+ identity: undefined;
190
+ generated: undefined;
191
+ }, {}, {}>;
192
+ verifiedAt: import("drizzle-orm/pg-core").PgColumn<{
193
+ name: "verified_at";
194
+ tableName: "tasks";
195
+ dataType: "date";
196
+ columnType: "PgTimestamp";
197
+ data: Date;
198
+ driverParam: string;
199
+ notNull: false;
200
+ hasDefault: false;
201
+ isPrimaryKey: false;
202
+ isAutoincrement: false;
203
+ hasRuntimeDefault: false;
204
+ enumValues: undefined;
205
+ baseColumn: never;
206
+ identity: undefined;
207
+ generated: undefined;
208
+ }, {}, {}>;
209
+ submission: import("drizzle-orm/pg-core").PgColumn<{
210
+ name: "submission";
211
+ tableName: "tasks";
212
+ dataType: "json";
213
+ columnType: "PgJsonb";
214
+ data: {
215
+ text: string;
216
+ };
217
+ driverParam: unknown;
218
+ notNull: false;
219
+ hasDefault: false;
220
+ isPrimaryKey: false;
221
+ isAutoincrement: false;
222
+ hasRuntimeDefault: false;
223
+ enumValues: undefined;
224
+ baseColumn: never;
225
+ identity: undefined;
226
+ generated: undefined;
227
+ }, {}, {
228
+ $type: {
229
+ text: string;
230
+ };
231
+ }>;
232
+ expiresAt: import("drizzle-orm/pg-core").PgColumn<{
233
+ name: "expires_at";
234
+ tableName: "tasks";
235
+ dataType: "date";
236
+ columnType: "PgTimestamp";
237
+ data: Date;
238
+ driverParam: string;
239
+ notNull: true;
240
+ hasDefault: false;
241
+ isPrimaryKey: false;
242
+ isAutoincrement: false;
243
+ hasRuntimeDefault: false;
244
+ enumValues: undefined;
245
+ baseColumn: never;
246
+ identity: undefined;
247
+ generated: undefined;
248
+ }, {}, {}>;
249
+ };
250
+ dialect: 'pg';
251
+ }>;
@@ -0,0 +1,26 @@
1
+ import { jsonb, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
2
+ import { agents } from './agents.js';
3
+ import { timestamps } from './timestamps.js';
4
+ export const tasks = pgTable('tasks', {
5
+ id: uuid('id').primaryKey(),
6
+ posterAgentId: text('poster_agent_id')
7
+ .notNull()
8
+ .references(() => agents.id),
9
+ claimantAgentId: text('claimant_agent_id').references(() => agents.id),
10
+ taskType: text('task_type').notNull(),
11
+ spec: jsonb('spec').notNull(),
12
+ verification: jsonb('verification').notNull(),
13
+ postedAt: timestamp('posted_at', { withTimezone: true })
14
+ .notNull()
15
+ .defaultNow(),
16
+ claimedAt: timestamp('claimed_at', { withTimezone: true }),
17
+ submittedAt: timestamp('submitted_at', { withTimezone: true }),
18
+ verifiedAt: timestamp('verified_at', { withTimezone: true }),
19
+ // What the claimant submitted, null until submit. Only the poster and the
20
+ // claimant may read it. Wrapped in an object because a bare jsonb string
21
+ // does not survive a read. postgres-js parses jsonb and Drizzle then
22
+ // JSON.parses any string it gets back, so "123" would return as 123.
23
+ submission: jsonb('submission').$type(),
24
+ expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(),
25
+ ...timestamps,
26
+ });
@@ -0,0 +1,4 @@
1
+ export declare const timestamps: {
2
+ createdAt: import("drizzle-orm").HasDefault<import("drizzle-orm").NotNull<import("drizzle-orm/pg-core").PgTimestampBuilderInitial<"created_at">>>;
3
+ updatedAt: import("drizzle-orm").HasDefault<import("drizzle-orm").NotNull<import("drizzle-orm/pg-core").PgTimestampBuilderInitial<"updated_at">>>;
4
+ };
@@ -0,0 +1,24 @@
1
+ import { timestamp } from 'drizzle-orm/pg-core';
2
+ // Shared columns for every table except events.
3
+ //
4
+ // A column builder like timestamp('created_at', ...) describes a column but
5
+ // is not bound to a table yet. pgTable binds it when the table is declared,
6
+ // so one plain object of builders can be spread into many tables.
7
+ //
8
+ // export const agents = pgTable('agents', {
9
+ // id: text('id').primaryKey(),
10
+ // ...timestamps,
11
+ // });
12
+ //
13
+ // Both columns are set by the database. defaultNow() fills them on insert and
14
+ // the set_updated_at() trigger in the second migration bumps updated_at on
15
+ // every update. We do not use Drizzle's $onUpdate, because that only runs
16
+ // when the update goes through Drizzle.
17
+ export const timestamps = {
18
+ createdAt: timestamp('created_at', { withTimezone: true })
19
+ .notNull()
20
+ .defaultNow(),
21
+ updatedAt: timestamp('updated_at', { withTimezone: true })
22
+ .notNull()
23
+ .defaultNow(),
24
+ };
@@ -0,0 +1,12 @@
1
+ import type { Options } from 'postgres';
2
+ /**
3
+ * Turns DATABASE_URL into arguments for postgres.js.
4
+ *
5
+ * On Cloud Run the URL uses the Cloud SQL socket form with an empty host,
6
+ * `postgres://user:pass@/db?host=/cloudsql/project:region:instance`.
7
+ * postgres.js cannot parse an empty host and ignores `?host=`, so this moves
8
+ * the socket directory into the `path` option. Other URLs pass through.
9
+ *
10
+ * Errors never include the URL, because it carries the password.
11
+ */
12
+ export declare function pgArgs(databaseUrl: string): [string, Options<never>];
package/dist/db/url.js ADDED
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Turns DATABASE_URL into arguments for postgres.js.
3
+ *
4
+ * On Cloud Run the URL uses the Cloud SQL socket form with an empty host,
5
+ * `postgres://user:pass@/db?host=/cloudsql/project:region:instance`.
6
+ * postgres.js cannot parse an empty host and ignores `?host=`, so this moves
7
+ * the socket directory into the `path` option. Other URLs pass through.
8
+ *
9
+ * Errors never include the URL, because it carries the password.
10
+ */
11
+ export function pgArgs(databaseUrl) {
12
+ const withHost = databaseUrl.replace(/^(postgres(?:ql)?:\/\/[^/@]*@)\//, '$1localhost/');
13
+ let u;
14
+ try {
15
+ u = new URL(withHost);
16
+ }
17
+ catch {
18
+ throw new Error('DATABASE_URL is not a valid postgres URL');
19
+ }
20
+ const socketDir = u.searchParams.get('host');
21
+ if (!socketDir)
22
+ return [databaseUrl, {}];
23
+ u.searchParams.delete('host');
24
+ return [u.toString(), { path: `${socketDir}/.s.PGSQL.${u.port || 5432}` }];
25
+ }
@@ -0,0 +1,20 @@
1
+ import { z } from 'zod';
2
+ export declare const TaskType: z.ZodString;
3
+ export type TaskType = z.infer<typeof TaskType>;
4
+ export declare const BaseDimension: z.ZodEnum<{
5
+ cost_latency: "cost_latency";
6
+ provenance: "provenance";
7
+ reliability: "reliability";
8
+ safety: "safety";
9
+ }>;
10
+ export type BaseDimension = z.infer<typeof BaseDimension>;
11
+ export declare const CompetenceDimension: z.ZodTemplateLiteral<`competence:${string}`>;
12
+ export type CompetenceDimension = z.infer<typeof CompetenceDimension>;
13
+ export declare const Dimension: z.ZodUnion<readonly [z.ZodEnum<{
14
+ cost_latency: "cost_latency";
15
+ provenance: "provenance";
16
+ reliability: "reliability";
17
+ safety: "safety";
18
+ }>, z.ZodTemplateLiteral<`competence:${string}`>]>;
19
+ export type Dimension = z.infer<typeof Dimension>;
20
+ export declare const competence: (taskType: TaskType) => CompetenceDimension;
@@ -0,0 +1,11 @@
1
+ import { z } from 'zod';
2
+ export const TaskType = z.string().regex(/^[a-z0-9_-]{1,32}$/);
3
+ export const BaseDimension = z.enum([
4
+ 'reliability',
5
+ 'safety',
6
+ 'cost_latency',
7
+ 'provenance',
8
+ ]);
9
+ export const CompetenceDimension = z.templateLiteral(['competence:', TaskType]);
10
+ export const Dimension = z.union([BaseDimension, CompetenceDimension]);
11
+ export const competence = (taskType) => CompetenceDimension.parse(`competence:${taskType}`);
@@ -0,0 +1,16 @@
1
+ import { z } from 'zod';
2
+ export declare const Jws: z.ZodString;
3
+ export declare const JwsHeader: z.ZodObject<{
4
+ alg: z.ZodLiteral<"EdDSA">;
5
+ kid: z.ZodString;
6
+ }, z.core.$strict>;
7
+ export type JwsHeader = z.infer<typeof JwsHeader>;
8
+ export declare class EnvelopeError extends Error {
9
+ name: string;
10
+ }
11
+ export declare function sign(payload: unknown, privateKey: Uint8Array, kid: string): Promise<string>;
12
+ export declare function decodeHeader(jws: string): JwsHeader;
13
+ export declare function verify(jws: string, publicKey: Uint8Array): Promise<{
14
+ header: JwsHeader;
15
+ payload: unknown;
16
+ }>;
@@ -0,0 +1,70 @@
1
+ import { signAsync, verifyAsync } from '@noble/ed25519';
2
+ import { z } from 'zod';
3
+ import { base64urlDecode, base64urlEncode, utf8Decode, utf8Encode, } from './base64url.js';
4
+ // The outer cap matches the API body limit. Routes set a tighter one.
5
+ export const Jws = z
6
+ .string()
7
+ .max(262144)
8
+ .regex(/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/, 'Expected JWS compact');
9
+ export const JwsHeader = z.strictObject({
10
+ alg: z.literal('EdDSA'),
11
+ kid: z.string().min(1).max(128),
12
+ });
13
+ export class EnvelopeError extends Error {
14
+ name = 'EnvelopeError';
15
+ }
16
+ function split(jws) {
17
+ if (!Jws.safeParse(jws).success) {
18
+ throw new EnvelopeError('Envelope is not three base64url parts');
19
+ }
20
+ const [header, payload, signature] = jws.split('.');
21
+ return { header, payload, signature };
22
+ }
23
+ function parseJson(part, what) {
24
+ try {
25
+ return JSON.parse(utf8Decode(base64urlDecode(part)));
26
+ }
27
+ catch {
28
+ throw new EnvelopeError(`Envelope ${what} is not base64url JSON`);
29
+ }
30
+ }
31
+ function parseHeader(part) {
32
+ const result = JwsHeader.safeParse(parseJson(part, 'header'));
33
+ if (!result.success) {
34
+ throw new EnvelopeError('Envelope header must be alg EdDSA with a kid');
35
+ }
36
+ return result.data;
37
+ }
38
+ export async function sign(payload, privateKey, kid) {
39
+ const header = JwsHeader.parse({ alg: 'EdDSA', kid });
40
+ const body = JSON.stringify(payload);
41
+ if (body === undefined)
42
+ throw new EnvelopeError('Payload is not JSON');
43
+ const signingInput = `${base64urlEncode(utf8Encode(JSON.stringify(header)))}.${base64urlEncode(utf8Encode(body))}`;
44
+ const signature = await signAsync(utf8Encode(signingInput), privateKey);
45
+ return `${signingInput}.${base64urlEncode(signature)}`;
46
+ }
47
+ export function decodeHeader(jws) {
48
+ return parseHeader(split(jws).header);
49
+ }
50
+ export async function verify(jws, publicKey) {
51
+ const parts = split(jws);
52
+ const signingInput = utf8Encode(`${parts.header}.${parts.payload}`);
53
+ let ok = false;
54
+ try {
55
+ const signature = base64urlDecode(parts.signature);
56
+ ok =
57
+ signature.length === 64 &&
58
+ publicKey.length === 32 &&
59
+ (await verifyAsync(signature, signingInput, publicKey, {
60
+ zip215: false,
61
+ }));
62
+ }
63
+ catch {
64
+ ok = false;
65
+ }
66
+ if (!ok)
67
+ throw new EnvelopeError('Envelope signature is invalid');
68
+ const header = parseHeader(parts.header);
69
+ return { header, payload: parseJson(parts.payload, 'payload') };
70
+ }