@lantharos/pult 0.0.0-stage → 0.9.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/package.json CHANGED
@@ -1,6 +1,44 @@
1
1
  {
2
2
  "name": "@lantharos/pult",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.9.0",
4
+ "description": "The other side of your app",
5
+ "license": "MIT",
6
+ "keywords": [
7
+ "back office",
8
+ "admin panel",
9
+ "internal tools",
10
+ "console",
11
+ "feature flags",
12
+ "status page"
13
+ ],
14
+ "homepage": "https://pult.sh",
15
+ "bugs": "https://github.com/Lantharos/Pult/issues",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/Lantharos/Pult.git",
19
+ "directory": "packages/pult"
20
+ },
21
+ "type": "module",
22
+ "sideEffects": false,
23
+ "engines": {
24
+ "node": ">=22.17.0"
25
+ },
26
+ "exports": {
27
+ ".": {
28
+ "types": "./index.d.mts",
29
+ "default": "./index.mjs"
30
+ },
31
+ "./package.json": "./package.json"
32
+ },
33
+ "bin": {
34
+ "pult": "./cli.mjs"
35
+ },
36
+ "publishConfig": {
37
+ "access": "public"
38
+ },
39
+ "dependencies": {
40
+ "jiti": "2.7.0",
41
+ "@standard-schema/spec": "1.1.0",
42
+ "croner": "10.0.1"
43
+ }
44
+ }
@@ -0,0 +1,661 @@
1
+ //#region ../protocol/src/manifest.ts
2
+ const HOME = "home";
3
+ const FLAG_ACTION = "flag:";
4
+ const DEFAULT_STATES = [
5
+ "open",
6
+ "in progress",
7
+ "handled"
8
+ ];
9
+ function actionId(resource, action) {
10
+ return `${resource}.${action}`;
11
+ }
12
+ function splitAction(id) {
13
+ const dot = id.indexOf(".");
14
+ return dot === -1 ? {
15
+ resource: null,
16
+ name: id
17
+ } : {
18
+ resource: id.slice(0, dot),
19
+ name: id.slice(dot + 1)
20
+ };
21
+ }
22
+ function findActionDef(manifest, id) {
23
+ const { resource, name } = splitAction(id);
24
+ if (!manifest) return null;
25
+ return (resource === null ? manifest.actions[name] : manifest.resources[resource]?.actions[name]) ?? null;
26
+ }
27
+ //#endregion
28
+ //#region ../protocol/src/blocks.ts
29
+ function eachBlock(blocks, visit) {
30
+ for (const block of blocks) {
31
+ visit(block);
32
+ if (block.type === "columns") for (const column of block.columns) eachBlock(column, visit);
33
+ }
34
+ }
35
+ function mapBlocks(blocks, map) {
36
+ return blocks.flatMap((block) => {
37
+ const mapped = map(block);
38
+ if (!mapped) return [];
39
+ if (mapped.type !== "columns") return [mapped];
40
+ return [{
41
+ type: "columns",
42
+ columns: mapped.columns.map((column) => mapBlocks(column, map))
43
+ }];
44
+ });
45
+ }
46
+ function valuesIn(block) {
47
+ switch (block.type) {
48
+ case "fields": return block.fields.map((field) => field.value);
49
+ case "stats": return block.stats.map((stat) => stat.value);
50
+ case "table": return block.rows.flat();
51
+ default: return [];
52
+ }
53
+ }
54
+ function shownValue(value) {
55
+ return value && typeof value === "object" && "edit" in value ? value.edit.value : value;
56
+ }
57
+ //#endregion
58
+ //#region ../protocol/src/signing.ts
59
+ const SIGNATURE_TOLERANCE_MS = 3e5;
60
+ const encoder = new TextEncoder();
61
+ function parseKey(key) {
62
+ const match = /^pult_([a-z0-9]+)_([A-Za-z0-9_-]{32,})$/.exec(key.trim());
63
+ if (!match) throw new Error("Malformed Pult key");
64
+ return {
65
+ environment: match[1],
66
+ secret: match[2]
67
+ };
68
+ }
69
+ function randomToken(bytes = 32) {
70
+ return base64url(crypto.getRandomValues(new Uint8Array(bytes)));
71
+ }
72
+ async function sha256(data) {
73
+ const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data;
74
+ const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", bytes));
75
+ return Array.from(digest, (byte) => byte.toString(16).padStart(2, "0")).join("");
76
+ }
77
+ function base64url(bytes) {
78
+ let binary = "";
79
+ for (const byte of bytes) binary += String.fromCharCode(byte);
80
+ return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
81
+ }
82
+ const keyCache = /* @__PURE__ */ new Map();
83
+ function hmacKey(secret) {
84
+ let key = keyCache.get(secret);
85
+ if (!key) {
86
+ key = crypto.subtle.importKey("raw", encoder.encode(secret), {
87
+ name: "HMAC",
88
+ hash: "SHA-256"
89
+ }, false, ["sign", "verify"]);
90
+ keyCache.set(secret, key);
91
+ }
92
+ return key;
93
+ }
94
+ function payload(scope, timestamp, nonce, body) {
95
+ return encoder.encode(`${scope}\n${timestamp}\n${nonce}\n${body}`);
96
+ }
97
+ async function sign(secret, scope, body) {
98
+ const timestamp = String(Date.now());
99
+ const nonce = randomToken(16);
100
+ const mac = await crypto.subtle.sign("HMAC", await hmacKey(secret), payload(scope, timestamp, nonce, body));
101
+ return {
102
+ timestamp,
103
+ nonce,
104
+ signature: base64url(new Uint8Array(mac))
105
+ };
106
+ }
107
+ async function verify(secret, scope, body, headers, now = Date.now()) {
108
+ const timestamp = Number(headers.timestamp);
109
+ if (!Number.isFinite(timestamp) || Math.abs(now - timestamp) > 3e5) return "stale";
110
+ const mac = fromBase64url(headers.signature);
111
+ if (!mac) return "invalid";
112
+ return await crypto.subtle.verify("HMAC", await hmacKey(secret), mac, payload(scope, headers.timestamp, headers.nonce, body)) ? null : "invalid";
113
+ }
114
+ function fromBase64url(value) {
115
+ try {
116
+ const binary = atob(value.replaceAll("-", "+").replaceAll("_", "/"));
117
+ return Uint8Array.from(binary, (char) => char.charCodeAt(0));
118
+ } catch {
119
+ return null;
120
+ }
121
+ }
122
+ var NonceWindow = class {
123
+ ttl;
124
+ #seen = /* @__PURE__ */ new Map();
125
+ constructor(ttl = SIGNATURE_TOLERANCE_MS * 2) {
126
+ this.ttl = ttl;
127
+ }
128
+ claim(nonce, now = Date.now()) {
129
+ for (const [seen, at] of this.#seen) {
130
+ if (now - at < this.ttl) break;
131
+ this.#seen.delete(seen);
132
+ }
133
+ if (this.#seen.has(nonce)) return false;
134
+ this.#seen.set(nonce, now);
135
+ return true;
136
+ }
137
+ };
138
+ //#endregion
139
+ //#region ../protocol/src/wire.ts
140
+ function refKey(ref) {
141
+ return `${ref.resource}:${ref.id}`;
142
+ }
143
+ const HEADER = {
144
+ environment: "pult-environment",
145
+ timestamp: "pult-timestamp",
146
+ nonce: "pult-nonce",
147
+ signature: "pult-signature"
148
+ };
149
+ //#endregion
150
+ //#region src/define/core.ts
151
+ const KIND = Symbol.for("pult.kind");
152
+ const TRACING = Symbol.for("pult.tracing");
153
+ const FRAME = /\(?((?:file:\/\/)?[^()\s]+?):(\d+):\d+\)?\s*$/;
154
+ const RUNTIME = /^node:|[\\/]jiti[\\/]/;
155
+ const UNKNOWN = {
156
+ file: "",
157
+ line: 0
158
+ };
159
+ const flags = globalThis;
160
+ async function traceOrigins(work) {
161
+ flags[TRACING] = true;
162
+ try {
163
+ return await work();
164
+ } finally {
165
+ flags[TRACING] = false;
166
+ }
167
+ }
168
+ function captureOrigin() {
169
+ if (!flags[TRACING]) return UNKNOWN;
170
+ const [own, ...callers] = ((/* @__PURE__ */ new Error()).stack?.split("\n").slice(1) ?? []).flatMap(parseFrame);
171
+ if (!own) return UNKNOWN;
172
+ const sdk = own.file.replace(/[^\\/]*$/, "");
173
+ return callers.find(({ file }) => !file.startsWith(sdk) && !RUNTIME.test(file)) ?? UNKNOWN;
174
+ }
175
+ function parseFrame(frame) {
176
+ const match = FRAME.exec(frame);
177
+ return match ? [{
178
+ file: match[1].replace(/^file:\/\//, ""),
179
+ line: Number(match[2])
180
+ }] : [];
181
+ }
182
+ function kindOf(value) {
183
+ return value && typeof value === "object" && KIND in value ? value[KIND] : null;
184
+ }
185
+ function humanize(name) {
186
+ const spaced = name.replace(/[-_]+/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2");
187
+ return spaced.charAt(0).toUpperCase() + spaced.slice(1);
188
+ }
189
+ //#endregion
190
+ //#region src/define/inbox/view.ts
191
+ function view(name, options) {
192
+ return {
193
+ [KIND]: "view",
194
+ origin: captureOrigin(),
195
+ name,
196
+ options
197
+ };
198
+ }
199
+ function normalizeFilter(filter = {}) {
200
+ const { state, ...rest } = filter;
201
+ return {
202
+ ...rest,
203
+ ...state ? { state: [state].flat() } : {}
204
+ };
205
+ }
206
+ //#endregion
207
+ //#region src/blocks.ts
208
+ function field(value) {
209
+ if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : { date: value.toISOString() };
210
+ return value ?? null;
211
+ }
212
+ function fieldValues(fields) {
213
+ return Object.fromEntries(Object.entries(fields).filter(([, value]) => value !== void 0).map(([key, value]) => [key, field(value)]));
214
+ }
215
+ function fieldList(fields) {
216
+ return Object.entries(fields).filter(([, value]) => value !== void 0).map(([label, value]) => ({
217
+ label,
218
+ value: field(value)
219
+ }));
220
+ }
221
+ function ref(resource, id, title) {
222
+ return {
223
+ resource: typeof resource === "string" ? resource : resource.name,
224
+ id,
225
+ ...title ? { title } : {}
226
+ };
227
+ }
228
+ function chart(source, options = {}) {
229
+ const range = options.range ?? "24h";
230
+ if (source === "occurrences") return {
231
+ type: "chart",
232
+ metric: source,
233
+ range,
234
+ title: options.title
235
+ };
236
+ if (kindOf(source) === "inbox") {
237
+ const inbox = source;
238
+ return {
239
+ type: "chart",
240
+ inbox: inbox.name,
241
+ range,
242
+ title: options.title ?? inbox.options.label
243
+ };
244
+ }
245
+ return {
246
+ type: "chart",
247
+ points: source.map(({ at, value }) => ({
248
+ at: +at,
249
+ value
250
+ })),
251
+ title: options.title
252
+ };
253
+ }
254
+ function listing(target, filter) {
255
+ if (kindOf(target) === "view") {
256
+ const view = target;
257
+ return {
258
+ inbox: view.options.inbox.name,
259
+ filter: normalizeFilter({
260
+ ...view.options.filter,
261
+ ...filter
262
+ }),
263
+ sort: view.options.sort ?? "-lastSeen",
264
+ view: view.name,
265
+ label: view.options.label ?? humanize(view.name)
266
+ };
267
+ }
268
+ const inbox = target;
269
+ return {
270
+ inbox: inbox.name,
271
+ filter: normalizeFilter(filter),
272
+ sort: "-lastSeen",
273
+ view: null,
274
+ label: inbox.options.label ?? humanize(inbox.name)
275
+ };
276
+ }
277
+ const idOf = (action) => typeof action === "string" ? action : action.id;
278
+ function button(input) {
279
+ if (typeof input === "object" && "action" in input) return {
280
+ action: idOf(input.action),
281
+ record: input.record ?? null,
282
+ ...input.input && { input: JSON.parse(JSON.stringify(input.input)) },
283
+ ...input.label && { label: input.label }
284
+ };
285
+ return {
286
+ action: idOf(input),
287
+ record: null
288
+ };
289
+ }
290
+ function bindActions(blocks, resource, record) {
291
+ if (!resource) return blocks;
292
+ const own = (action) => !action.includes(".") && kindOf(resource.actions[action]) === "action";
293
+ const bind = (target) => own(target.action) ? {
294
+ ...target,
295
+ action: actionId(resource.name, target.action),
296
+ record: target.record ?? record
297
+ } : target;
298
+ const value = (field) => field && typeof field === "object" && "edit" in field ? { edit: bind(field.edit) } : field;
299
+ return mapBlocks(blocks, (block) => {
300
+ if (block.type === "actions") return {
301
+ ...block,
302
+ actions: block.actions.map(bind)
303
+ };
304
+ if (block.type === "form") return {
305
+ ...block,
306
+ form: bind(block.form)
307
+ };
308
+ if (block.type === "fields") return {
309
+ ...block,
310
+ fields: block.fields.map((entry) => ({
311
+ ...entry,
312
+ value: value(entry.value)
313
+ }))
314
+ };
315
+ if (block.type === "stats") return {
316
+ ...block,
317
+ stats: block.stats.map((stat) => ({
318
+ ...stat,
319
+ value: value(stat.value)
320
+ }))
321
+ };
322
+ if (block.type === "table") return {
323
+ ...block,
324
+ rows: block.rows.map((row) => row.map(value))
325
+ };
326
+ return block;
327
+ });
328
+ }
329
+ const b = {
330
+ markdown: (text) => ({
331
+ type: "markdown",
332
+ text
333
+ }),
334
+ fields: (fields) => ({
335
+ type: "fields",
336
+ fields: fieldList(fields)
337
+ }),
338
+ code: (code, options = {}) => ({
339
+ type: "code",
340
+ code,
341
+ ...options
342
+ }),
343
+ chart,
344
+ attachment: (attachment) => ({
345
+ type: "attachment",
346
+ ...attachment
347
+ }),
348
+ record: (resource, id, title) => ({
349
+ type: "record",
350
+ ref: ref(resource, id, title)
351
+ }),
352
+ ref: (resource, id, title) => ({ record: ref(resource, id, title) }),
353
+ sensitive: (value, options = {}) => ({ sensitive: {
354
+ value: String(value),
355
+ ...options
356
+ } }),
357
+ timeline: (entries) => ({
358
+ type: "timeline",
359
+ entries: entries.map(({ at, text }) => ({
360
+ at: +at,
361
+ text
362
+ }))
363
+ }),
364
+ table: (rows, columns = Object.keys(rows[0] ?? {})) => ({
365
+ type: "table",
366
+ columns,
367
+ rows: rows.map((row) => columns.map((column) => field(row[column])))
368
+ }),
369
+ stats: (stats) => ({
370
+ type: "stats",
371
+ stats: Object.entries(stats).filter(([, stat]) => stat !== void 0).map(([label, stat]) => stat && typeof stat === "object" && "value" in stat && !(stat instanceof Date) ? {
372
+ label,
373
+ value: field(stat.value),
374
+ ...stat.note && { note: stat.note }
375
+ } : {
376
+ label,
377
+ value: field(stat)
378
+ })
379
+ }),
380
+ actions: (...buttons) => ({
381
+ type: "actions",
382
+ actions: buttons.map(button)
383
+ }),
384
+ form: (action) => ({
385
+ type: "form",
386
+ form: button(action)
387
+ }),
388
+ columns: (...columns) => ({
389
+ type: "columns",
390
+ columns
391
+ }),
392
+ count: (target, options = {}) => {
393
+ const { inbox, filter, view, label } = listing(target, options.filter);
394
+ return {
395
+ type: "count",
396
+ inbox,
397
+ filter,
398
+ view,
399
+ label: options.label ?? label
400
+ };
401
+ },
402
+ items: (target, options = {}) => ({
403
+ type: "items",
404
+ ...listing(target, options.filter),
405
+ ...options.label && { label: options.label },
406
+ limit: options.limit ?? 5
407
+ }),
408
+ flag: (flag) => ({
409
+ type: "flag",
410
+ flag: flag.name
411
+ }),
412
+ edit: (value, action, options = {}) => ({ edit: {
413
+ value: field(value),
414
+ action: idOf(action),
415
+ record: options.record ?? null,
416
+ field: options.field ?? null
417
+ } })
418
+ };
419
+ function isPendingAttachment(block) {
420
+ return block.type === "attachment" && "data" in block;
421
+ }
422
+ function hasPendingAttachments(blocks) {
423
+ return blocks.some((block) => isPendingAttachment(block) || block.type === "columns" && block.columns.some(hasPendingAttachments));
424
+ }
425
+ //#endregion
426
+ //#region src/client/transport.ts
427
+ const DEFAULT_URL = "https://app.pult.sh";
428
+ const UPLOAD_REUSE_MS = 36e5;
429
+ var PultError = class extends Error {
430
+ status;
431
+ code;
432
+ constructor(message, status, code) {
433
+ super(message);
434
+ this.status = status;
435
+ this.code = code;
436
+ this.name = "PultError";
437
+ }
438
+ };
439
+ var Transport = class {
440
+ send;
441
+ key;
442
+ url;
443
+ #uploads = /* @__PURE__ */ new Map();
444
+ constructor(key, url, send) {
445
+ this.send = send;
446
+ this.key = parseKey(key);
447
+ this.url = url.replace(/\/$/, "");
448
+ }
449
+ request(method, path, body) {
450
+ return this.json(method, path, body === void 0 ? "" : JSON.stringify(body));
451
+ }
452
+ json(method, path, json) {
453
+ return this.#fetch(method, path, json, json, "application/json");
454
+ }
455
+ async upload(name, contentType, bytes) {
456
+ const hex = await sha256(bytes);
457
+ const id = `${hex}:${name}:${contentType}`;
458
+ const now = Date.now();
459
+ const recent = this.#uploads.get(id);
460
+ if (recent && now - recent.at < UPLOAD_REUSE_MS) return recent.key;
461
+ for (const [other, { at }] of this.#uploads) {
462
+ if (now - at < UPLOAD_REUSE_MS) break;
463
+ this.#uploads.delete(other);
464
+ }
465
+ const key = this.#fetch("POST", `/v1/attachments?name=${encodeURIComponent(name)}`, bytes, hex, contentType);
466
+ this.#uploads.delete(id);
467
+ this.#uploads.set(id, {
468
+ key,
469
+ at: now
470
+ });
471
+ key.catch(() => this.#uploads.delete(id));
472
+ return key;
473
+ }
474
+ async #fetch(method, path, body, signed, contentType) {
475
+ const signature = await sign(this.key.secret, `${method} ${path}`, signed);
476
+ const response = await this.send(`${this.url}${path}`, {
477
+ method,
478
+ body: method === "GET" ? void 0 : body,
479
+ headers: {
480
+ "content-type": contentType,
481
+ [HEADER.environment]: this.key.environment,
482
+ [HEADER.timestamp]: signature.timestamp,
483
+ [HEADER.nonce]: signature.nonce,
484
+ [HEADER.signature]: signature.signature
485
+ }
486
+ });
487
+ const json = await response.json().catch(() => null);
488
+ if (!response.ok) throw new PultError(json?.error ?? response.statusText, response.status, json?.code);
489
+ return json;
490
+ }
491
+ };
492
+ //#endregion
493
+ //#region src/define/controls/flag.ts
494
+ function flag(name, options = {}) {
495
+ return {
496
+ [KIND]: "flag",
497
+ origin: captureOrigin(),
498
+ name,
499
+ options: {
500
+ ...options,
501
+ default: options.default ?? (options.value ? void 0 : false)
502
+ }
503
+ };
504
+ }
505
+ function guards(flag) {
506
+ return [flag.options.guard ?? []].flat();
507
+ }
508
+ //#endregion
509
+ //#region src/define/collect.ts
510
+ function collect(modules) {
511
+ const found = {
512
+ resources: /* @__PURE__ */ new Map(),
513
+ actions: /* @__PURE__ */ new Map(),
514
+ inboxes: /* @__PURE__ */ new Map(),
515
+ roles: /* @__PURE__ */ new Map(),
516
+ views: /* @__PURE__ */ new Map(),
517
+ pages: /* @__PURE__ */ new Map(),
518
+ flags: /* @__PURE__ */ new Map(),
519
+ notify: /* @__PURE__ */ new Map(),
520
+ spaces: /* @__PURE__ */ new Map(),
521
+ status: null,
522
+ config: null
523
+ };
524
+ const claim = (map, value, kind) => {
525
+ const existing = map.get(value.name);
526
+ if (existing && existing !== value) throw new Error(`Two ${kind} are named "${value.name}"`);
527
+ map.set(value.name, value);
528
+ };
529
+ const add = (value) => {
530
+ switch (kindOf(value)) {
531
+ case "resource": {
532
+ const resource = value;
533
+ return claim(found.resources, resource, "resources");
534
+ }
535
+ case "action": {
536
+ const action = value;
537
+ if (action.resource === null) claim(found.actions, action, "actions");
538
+ return;
539
+ }
540
+ case "inbox": {
541
+ const inbox = value;
542
+ claim(found.inboxes, inbox, "inboxes");
543
+ if (inbox.options.resource) add(inbox.options.resource);
544
+ return;
545
+ }
546
+ case "role": {
547
+ const role = value;
548
+ claim(found.roles, role, "roles");
549
+ for (const inbox of role.options.inboxes ?? []) add(inbox);
550
+ for (const resource of role.options.resources ?? []) add(resource);
551
+ for (const action of role.options.actions ?? []) add(action);
552
+ for (const page of role.options.pages ?? []) add(page);
553
+ for (const flag of role.options.flags ?? []) add(flag);
554
+ return;
555
+ }
556
+ case "view": {
557
+ const view = value;
558
+ claim(found.views, view, "views");
559
+ add(view.options.inbox);
560
+ return;
561
+ }
562
+ case "page": {
563
+ const page = value;
564
+ return claim(found.pages, page, "pages");
565
+ }
566
+ case "flag": {
567
+ const flag = value;
568
+ claim(found.flags, flag, "flags");
569
+ if (flag.options.target) add(flag.options.target);
570
+ for (const guard of guards(flag)) add(guard.inbox);
571
+ return;
572
+ }
573
+ case "space": {
574
+ const space = value;
575
+ claim(found.spaces, space, "spaces");
576
+ if (space.home) add(space.home);
577
+ for (const inbox of space.options.inboxes ?? []) add(inbox);
578
+ for (const resource of space.options.resources ?? []) add(resource);
579
+ for (const page of space.options.pages ?? []) add(page);
580
+ for (const flag of space.options.flags ?? []) add(flag);
581
+ return;
582
+ }
583
+ case "notify": {
584
+ const rule = value;
585
+ claim(found.notify, rule, "notifications");
586
+ for (const inbox of rule.options.inboxes) add(inbox);
587
+ for (const recipient of rule.options.to) add(recipient);
588
+ return;
589
+ }
590
+ case "status": {
591
+ const page = value;
592
+ if (found.status && found.status !== page) throw new Error("A project has one status page, and this one defines two");
593
+ found.status = page;
594
+ for (const recipient of page.options.alert ?? []) add(recipient);
595
+ return;
596
+ }
597
+ case "config": found.config = value;
598
+ }
599
+ };
600
+ for (const exports of modules) for (const value of Object.values(exports)) add(value);
601
+ return found;
602
+ }
603
+ function findAction(definitions, id) {
604
+ const { resource, name } = splitAction(id);
605
+ const action = resource === null ? definitions.actions.get(name) : definitions.resources.get(resource)?.actions[name];
606
+ return action && kindOf(action) === "action" ? action : null;
607
+ }
608
+ //#endregion
609
+ //#region src/schema.ts
610
+ const libraryOptions = { zod: {
611
+ unrepresentable: "any",
612
+ override: ({ zodSchema, jsonSchema }) => {
613
+ if (zodSchema._zod.def.type === "date") Object.assign(jsonSchema, {
614
+ type: "string",
615
+ format: "date-time"
616
+ });
617
+ }
618
+ } };
619
+ function toJsonSchema(schema) {
620
+ const standard = schema["~standard"];
621
+ if (!standard.jsonSchema) throw new Error(`Schemas from "${schema["~standard"].vendor}" can't describe themselves as JSON Schema. Use a library that implements Standard JSON Schema, such as zod, valibot or arktype.`);
622
+ const { $schema: _, ...json } = standard.jsonSchema.input({
623
+ target: "draft-2020-12",
624
+ libraryOptions: libraryOptions[standard.vendor]
625
+ });
626
+ return json;
627
+ }
628
+ var ValidationError = class extends Error {
629
+ issues;
630
+ constructor(issues) {
631
+ super(issues.map(describeIssue).join("; "));
632
+ this.issues = issues;
633
+ this.name = "ValidationError";
634
+ }
635
+ };
636
+ function describeIssue(issue) {
637
+ const path = issue.path?.map((segment) => typeof segment === "object" ? segment.key : segment).join(".");
638
+ return path ? `${path}: ${issue.message}` : issue.message;
639
+ }
640
+ async function validate(schema, value) {
641
+ const result = await schema["~standard"].validate(value);
642
+ if (result.issues) throw new ValidationError(result.issues);
643
+ return result.value;
644
+ }
645
+ function reviveDates(schema, value) {
646
+ if (!schema || value === null || value === void 0) return value;
647
+ if (schema.format === "date-time" && typeof value === "string") return new Date(value);
648
+ for (const branch of schema.anyOf ?? schema.oneOf ?? []) {
649
+ const revived = reviveDates(branch, value);
650
+ if (revived !== value) return revived;
651
+ }
652
+ if (Array.isArray(value) && schema.items) return value.map((entry) => reviveDates(schema.items, entry));
653
+ if (typeof value === "object" && schema.properties) {
654
+ const out = { ...value };
655
+ for (const [key, property] of Object.entries(schema.properties)) if (key in out) out[key] = reviveDates(property, out[key]);
656
+ return out;
657
+ }
658
+ return value;
659
+ }
660
+ //#endregion
661
+ export { mapBlocks as A, traceOrigins as C, sign as D, NonceWindow as E, HOME as F, actionId as I, findActionDef as L, valuesIn as M, DEFAULT_STATES as N, verify as O, FLAG_ACTION as P, kindOf as S, refKey as T, normalizeFilter as _, collect as a, captureOrigin as b, guards as c, Transport as d, b as f, isPendingAttachment as g, hasPendingAttachments as h, validate as i, shownValue as j, eachBlock as k, DEFAULT_URL as l, fieldValues as m, reviveDates as n, findAction as o, bindActions as p, toJsonSchema as r, flag as s, ValidationError as t, PultError as u, view as v, HEADER as w, humanize as x, KIND as y };