@vercel/factory 0.0.0 → 0.0.1

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.
@@ -84,9 +84,9 @@ type ChangeEvidence = z.infer<typeof changeEvidenceSchema>;
84
84
  type Change = z.infer<typeof changeSchema>;
85
85
 
86
86
  /** States the engine writes on transitions. Any vocabulary value is valid. */
87
- declare const standardReceiptStates: readonly ["dispatched", "succeeded", "failed", "needs_human", "cancelled", "settled", "merged", "deployed", "rolled_out", "rolled_back"];
87
+ declare const standardReceiptStates: readonly ["approved", "rejected", "dispatched", "succeeded", "failed", "needs_human", "cancelled", "settled", "merged", "deployed", "rolled_out", "rolled_back"];
88
88
  /** Phases the standard loop uses. Any vocabulary value is valid. */
89
- declare const standardReceiptPhases: readonly ["budget", "dispatch", "implementation", "verification", "pull_request", "cleanup"];
89
+ declare const standardReceiptPhases: readonly ["approval", "budget", "dispatch", "implementation", "verification", "pull_request", "cleanup"];
90
90
  /** Kinds the standard loop writes: the standard task kinds plus "change". */
91
91
  declare const standardReceiptKinds: readonly ["code_change", "verification", "investigation", "question", "change"];
92
92
  declare const receiptStateSchema: z.ZodString;
@@ -137,7 +137,7 @@ type RepositorySlug = z.infer<typeof repositorySlugSchema>;
137
137
  type Repository = z.infer<typeof repositorySchema>;
138
138
 
139
139
  /** Sources used by the standard connectors. Any vocabulary value is valid. */
140
- declare const standardSignalSources: readonly ["github_issue", "github_pr", "github_pr_comment", "slack", "human", "github_ci_failure", "vercel_deploy_failure", "investigation", "api"];
140
+ declare const standardSignalSources: readonly ["github_issue", "github_pr", "github_pr_comment", "slack", "human", "github_ci_failure", "vercel_deploy_failure", "security_scan", "investigation", "api"];
141
141
  declare const signalSourceSchema: z.ZodString;
142
142
  declare const signalOutcomeSchema: z.ZodEnum<{
143
143
  pending: "pending";
@@ -190,6 +190,11 @@ declare const taskStateSchema: z.ZodEnum<{
190
190
  needs_human: "needs_human";
191
191
  cancelled: "cancelled";
192
192
  }>;
193
+ declare const taskApprovalSchema: z.ZodEnum<{
194
+ required: "required";
195
+ granted: "granted";
196
+ denied: "denied";
197
+ }>;
193
198
  declare const harnessSchema: z.ZodString;
194
199
  declare const taskOriginSchema: z.ZodObject<{
195
200
  signalId: z.ZodOptional<z.ZodString>;
@@ -222,6 +227,11 @@ declare const taskSchema: z.ZodObject<{
222
227
  needs_human: "needs_human";
223
228
  cancelled: "cancelled";
224
229
  }>;
230
+ approval: z.ZodOptional<z.ZodEnum<{
231
+ required: "required";
232
+ granted: "granted";
233
+ denied: "denied";
234
+ }>>;
225
235
  origin: z.ZodObject<{
226
236
  signalId: z.ZodOptional<z.ZodString>;
227
237
  parentTaskId: z.ZodOptional<z.ZodString>;
@@ -251,10 +261,17 @@ declare const taskSchema: z.ZodObject<{
251
261
  createdAt: z.ZodISODateTime;
252
262
  updatedAt: z.ZodISODateTime;
253
263
  }, z.core.$strict>;
264
+ type TaskApproval = z.infer<typeof taskApprovalSchema>;
254
265
  type TaskKind = z.infer<typeof taskKindSchema>;
255
266
  type TaskState = z.infer<typeof taskStateSchema>;
256
267
  type Task = z.infer<typeof taskSchema>;
257
268
 
269
+ /** An approval decision that does not apply to the task's current approval or state. */
270
+ declare class ApprovalError extends Error {
271
+ readonly taskId: TaskId;
272
+ readonly reason: string;
273
+ constructor(taskId: TaskId, reason: string);
274
+ }
258
275
  interface CreateSignalInput {
259
276
  repositoryId: RepositoryId;
260
277
  source: SignalSource;
@@ -274,6 +291,13 @@ interface CreateTaskInput {
274
291
  area?: string;
275
292
  dependsOn?: TaskId[];
276
293
  question?: Task["question"];
294
+ /** Stamp the task as needing a human decision before dispatch. */
295
+ approval?: "required";
296
+ }
297
+ interface ApprovalDecision {
298
+ /** Authenticated operator making the decision. */
299
+ by: string;
300
+ reason?: string;
277
301
  }
278
302
  type TaskPatch = Partial<Pick<Task, "devbox" | "changeId" | "reportRef" | "reservedUsd" | "settledUsd" | "question">>;
279
303
  interface TaskTransitionOptions {
@@ -327,6 +351,10 @@ interface FactoryStores {
327
351
  create(input: CreateTaskInput): Promise<Task>;
328
352
  get(id: TaskId): Promise<Task | null>;
329
353
  transition(id: TaskId, to: TaskState, options?: TaskTransitionOptions): Promise<Task>;
354
+ /** Grant a required approval. The task stays queued; dispatch may now take it. */
355
+ approve(id: TaskId, decision: ApprovalDecision): Promise<Task>;
356
+ /** Deny a required approval. The task is cancelled with the reason. */
357
+ reject(id: TaskId, decision: ApprovalDecision): Promise<Task>;
330
358
  list(): Promise<Task[]>;
331
359
  };
332
360
  changes: {
@@ -347,4 +375,167 @@ interface CreateStoresOptions {
347
375
  }
348
376
  declare function createStores(options: CreateStoresOptions): FactoryStores;
349
377
 
350
- export { signalOutcomeSchema as $, type AppendReceiptInput as A, changeEvidenceSchema as B, type ChangeState as C, type DecideSignalOptions as D, changeIdSchema as E, type FactoryStores as F, changeSchema as G, changeStateSchema as H, costSchema as I, createStores as J, factoryIdSchema as K, type Level as L, harnessSchema as M, levelSchema as N, receiptIdSchema as O, receiptKindSchema as P, receiptPhaseSchema as Q, type Receipt as R, type SignalOutcome as S, type Task as T, receiptSchema as U, receiptStateSchema as V, repositoryIdSchema as W, repositoryIdsSchema as X, repositorySchema as Y, repositorySlugSchema as Z, signalIdSchema as _, type TaskId as a, signalSchema as a0, signalSourceSchema as a1, standardReceiptKinds as a2, standardReceiptPhases as a3, standardReceiptStates as a4, standardSignalSources as a5, standardTaskKinds as a6, taskDevboxSchema as a7, taskIdSchema as a8, taskKindSchema as a9, taskOriginSchema as aa, taskQuestionSchema as ab, taskSchema as ac, taskStateSchema as ad, type TaskState as b, type Change as c, type ChangeEvidence as d, type ChangeId as e, type ChangePatch as f, type ChangeTransitionOptions as g, type Cost as h, type CreateChangeInput as i, type CreateSignalInput as j, type CreateStoresOptions as k, type CreateTaskInput as l, type FactoryId as m, type ReceiptId as n, type ReceiptKind as o, type ReceiptPhase as p, type ReceiptState as q, type Repository as r, type RepositoryId as s, type RepositorySlug as t, type Signal as u, type SignalId as v, type SignalSource as w, type TaskKind as x, type TaskPatch as y, type TaskTransitionOptions as z };
378
+ declare const findingPrioritySchema: z.ZodEnum<{
379
+ P0: "P0";
380
+ P1: "P1";
381
+ P2: "P2";
382
+ P3: "P3";
383
+ }>;
384
+ declare const findingCategorySchema: z.ZodEnum<{
385
+ security: "security";
386
+ correctness: "correctness";
387
+ reliability: "reliability";
388
+ performance: "performance";
389
+ compatibility: "compatibility";
390
+ accessibility: "accessibility";
391
+ documentation: "documentation";
392
+ maintainability: "maintainability";
393
+ testing: "testing";
394
+ usability: "usability";
395
+ }>;
396
+ declare const findingVerificationLevelSchema: z.ZodEnum<{
397
+ unverified: "unverified";
398
+ supported: "supported";
399
+ confirmed: "confirmed";
400
+ }>;
401
+ declare const findingVerificationMethodSchema: z.ZodEnum<{
402
+ documentation: "documentation";
403
+ code_inspection: "code_inspection";
404
+ static_analysis: "static_analysis";
405
+ manual_reproduction: "manual_reproduction";
406
+ unit_test: "unit_test";
407
+ integration_test: "integration_test";
408
+ e2e_test: "e2e_test";
409
+ runtime_observation: "runtime_observation";
410
+ }>;
411
+ /**
412
+ * What was done to check the finding. Forces a reporter to distinguish "I
413
+ * think this is a problem" from "I demonstrated it", which is the axis a human
414
+ * approving fixes cares about most.
415
+ */
416
+ declare const findingVerificationSchema: z.ZodObject<{
417
+ level: z.ZodEnum<{
418
+ unverified: "unverified";
419
+ supported: "supported";
420
+ confirmed: "confirmed";
421
+ }>;
422
+ method: z.ZodEnum<{
423
+ documentation: "documentation";
424
+ code_inspection: "code_inspection";
425
+ static_analysis: "static_analysis";
426
+ manual_reproduction: "manual_reproduction";
427
+ unit_test: "unit_test";
428
+ integration_test: "integration_test";
429
+ e2e_test: "e2e_test";
430
+ runtime_observation: "runtime_observation";
431
+ }>;
432
+ summary: z.ZodString;
433
+ limitations: z.ZodOptional<z.ZodString>;
434
+ }, z.core.$strict>;
435
+ /** A single problem found in a repository, as a scanner or agent reports it. */
436
+ declare const findingSchema: z.ZodObject<{
437
+ title: z.ZodString;
438
+ body: z.ZodString;
439
+ path: z.ZodOptional<z.ZodString>;
440
+ line: z.ZodOptional<z.ZodNumber>;
441
+ endLine: z.ZodOptional<z.ZodNumber>;
442
+ priority: z.ZodEnum<{
443
+ P0: "P0";
444
+ P1: "P1";
445
+ P2: "P2";
446
+ P3: "P3";
447
+ }>;
448
+ category: z.ZodEnum<{
449
+ security: "security";
450
+ correctness: "correctness";
451
+ reliability: "reliability";
452
+ performance: "performance";
453
+ compatibility: "compatibility";
454
+ accessibility: "accessibility";
455
+ documentation: "documentation";
456
+ maintainability: "maintainability";
457
+ testing: "testing";
458
+ usability: "usability";
459
+ }>;
460
+ verification: z.ZodObject<{
461
+ level: z.ZodEnum<{
462
+ unverified: "unverified";
463
+ supported: "supported";
464
+ confirmed: "confirmed";
465
+ }>;
466
+ method: z.ZodEnum<{
467
+ documentation: "documentation";
468
+ code_inspection: "code_inspection";
469
+ static_analysis: "static_analysis";
470
+ manual_reproduction: "manual_reproduction";
471
+ unit_test: "unit_test";
472
+ integration_test: "integration_test";
473
+ e2e_test: "e2e_test";
474
+ runtime_observation: "runtime_observation";
475
+ }>;
476
+ summary: z.ZodString;
477
+ limitations: z.ZodOptional<z.ZodString>;
478
+ }, z.core.$strict>;
479
+ tool: z.ZodOptional<z.ZodString>;
480
+ ruleId: z.ZodOptional<z.ZodString>;
481
+ }, z.core.$strict>;
482
+ type FindingPriority = z.infer<typeof findingPrioritySchema>;
483
+ type FindingCategory = z.infer<typeof findingCategorySchema>;
484
+ type FindingVerification = z.infer<typeof findingVerificationSchema>;
485
+ type Finding = z.infer<typeof findingSchema>;
486
+ /**
487
+ * Stable identity of a finding across scans, used as the signal's delivery id
488
+ * so re-reporting is a no-op instead of a second task. A scanner finding is
489
+ * the same problem when the same tool reports the same rule on the same file;
490
+ * its title is model-written prose and changes between runs, so it is left
491
+ * out. Without a rule id the title is all there is, normalised.
492
+ */
493
+ declare function findingFingerprint(repositoryId: string, finding: Finding): string;
494
+
495
+ /**
496
+ * One approval rule. Every listed field must match for the rule to apply;
497
+ * absent fields match anything. Rules are evaluated first-match.
498
+ */
499
+ declare const approvalRuleSchema: z.ZodObject<{
500
+ source: z.ZodOptional<z.ZodString>;
501
+ kind: z.ZodOptional<z.ZodString>;
502
+ priority: z.ZodOptional<z.ZodArray<z.ZodEnum<{
503
+ P0: "P0";
504
+ P1: "P1";
505
+ P2: "P2";
506
+ P3: "P3";
507
+ }>>>;
508
+ require: z.ZodBoolean;
509
+ }, z.core.$strict>;
510
+ declare const approvalPolicySchema: z.ZodArray<z.ZodObject<{
511
+ source: z.ZodOptional<z.ZodString>;
512
+ kind: z.ZodOptional<z.ZodString>;
513
+ priority: z.ZodOptional<z.ZodArray<z.ZodEnum<{
514
+ P0: "P0";
515
+ P1: "P1";
516
+ P2: "P2";
517
+ P3: "P3";
518
+ }>>>;
519
+ require: z.ZodBoolean;
520
+ }, z.core.$strict>>;
521
+ type ApprovalRule = z.infer<typeof approvalRuleSchema>;
522
+ type ApprovalPolicy = z.infer<typeof approvalPolicySchema>;
523
+ /**
524
+ * The default: fixes for security findings wait for a human; everything else
525
+ * runs as it always has. Handoffs between agents (verification) never wait,
526
+ * because a human already approved the work they continue.
527
+ */
528
+ declare const defaultApprovalPolicy: ApprovalPolicy;
529
+ interface ApprovalSubject {
530
+ source?: string;
531
+ kind: string;
532
+ priority?: FindingPriority;
533
+ }
534
+ /**
535
+ * Decide at task-creation time whether a task needs approval. Pure: the answer
536
+ * is stamped on the task and never re-evaluated, so a later policy change
537
+ * cannot silently release work a human was expecting to see.
538
+ */
539
+ declare function requiresApproval(policy: ApprovalPolicy, subject: ApprovalSubject): boolean;
540
+
541
+ export { factoryIdSchema as $, type ApprovalPolicy as A, type ReceiptPhase as B, type ChangeState as C, type DecideSignalOptions as D, type ReceiptState as E, type FactoryStores as F, type RepositoryId as G, type RepositorySlug as H, type SignalId as I, type SignalSource as J, type TaskApproval as K, type Level as L, type TaskKind as M, type TaskPatch as N, type TaskTransitionOptions as O, approvalPolicySchema as P, approvalRuleSchema as Q, type Repository as R, type SignalOutcome as S, type Task as T, changeEvidenceSchema as U, changeIdSchema as V, changeSchema as W, changeStateSchema as X, costSchema as Y, createStores as Z, defaultApprovalPolicy as _, type Finding as a, findingCategorySchema as a0, findingFingerprint as a1, findingPrioritySchema as a2, findingSchema as a3, findingVerificationLevelSchema as a4, findingVerificationMethodSchema as a5, findingVerificationSchema as a6, harnessSchema as a7, levelSchema as a8, receiptIdSchema as a9, receiptKindSchema as aa, receiptPhaseSchema as ab, receiptSchema as ac, receiptStateSchema as ad, repositoryIdSchema as ae, repositoryIdsSchema as af, repositorySchema as ag, repositorySlugSchema as ah, requiresApproval as ai, signalIdSchema as aj, signalOutcomeSchema as ak, signalSchema as al, signalSourceSchema as am, standardReceiptKinds as an, standardReceiptPhases as ao, standardReceiptStates as ap, standardSignalSources as aq, standardTaskKinds as ar, taskApprovalSchema as as, taskDevboxSchema as at, taskIdSchema as au, taskKindSchema as av, taskOriginSchema as aw, taskQuestionSchema as ax, taskSchema as ay, taskStateSchema as az, type TaskId as b, type TaskState as c, type CreateSignalInput as d, type Signal as e, type AppendReceiptInput as f, type ApprovalDecision as g, ApprovalError as h, type ApprovalRule as i, type ApprovalSubject as j, type Change as k, type ChangeEvidence as l, type ChangeId as m, type ChangePatch as n, type ChangeTransitionOptions as o, type Cost as p, type CreateChangeInput as q, type CreateStoresOptions as r, type CreateTaskInput as s, type FactoryId as t, type FindingCategory as u, type FindingPriority as v, type FindingVerification as w, type Receipt as x, type ReceiptId as y, type ReceiptKind as z };
@@ -0,0 +1,26 @@
1
+ import { S as StoreDriver } from '../driver-tRNggCCJ.js';
2
+
3
+ /** The narrow blob operations the driver needs; injectable for tests. */
4
+ interface BlobClient {
5
+ read(pathname: string): Promise<string | null>;
6
+ /** Must reject with an already-exists error when overwrite is false and the path exists. */
7
+ write(pathname: string, body: string, overwrite: boolean): Promise<void>;
8
+ listPaths(prefix: string): Promise<string[]>;
9
+ }
10
+ declare const vercelBlobClient: BlobClient;
11
+ interface BlobDriverOptions {
12
+ client?: BlobClient;
13
+ /** Path prefix inside the blob store; defaults to "factory". */
14
+ prefix?: string;
15
+ }
16
+ /**
17
+ * Store driver on Vercel Blob: durable and dependency-free (one Blob store on
18
+ * the project, no database). Inserts and receipts are exactly as safe as on
19
+ * Postgres (create-if-absent is atomic). The compare-and-swap update is
20
+ * BEST-EFFORT: the version check reads then writes without a lock, so two
21
+ * simultaneous writers can race. Acceptable for low-volume factories; use the
22
+ * Postgres driver when concurrent dispatchers are expected.
23
+ */
24
+ declare function createBlobDriver(options?: BlobDriverOptions): StoreDriver;
25
+
26
+ export { type BlobClient, type BlobDriverOptions, createBlobDriver, vercelBlobClient };
@@ -0,0 +1,127 @@
1
+ import {
2
+ RecordExistsError,
3
+ RecordNotFoundError,
4
+ VersionConflictError
5
+ } from "../chunk-CEF6VJZC.js";
6
+
7
+ // src/blob/index.ts
8
+ import { get, head, list, put } from "@vercel/blob";
9
+ function isNotFoundError(error) {
10
+ return error instanceof Error && (error.name === "BlobNotFoundError" || /does not exist|not.?found|404/iu.test(error.message));
11
+ }
12
+ function isAlreadyExistsError(error) {
13
+ return error instanceof Error && /already exists|not allowed to overwrite|overwrite/iu.test(error.message);
14
+ }
15
+ var vercelBlobClient = {
16
+ async read(pathname) {
17
+ let url;
18
+ try {
19
+ url = (await head(pathname)).url;
20
+ } catch (error) {
21
+ if (isNotFoundError(error)) {
22
+ return null;
23
+ }
24
+ throw error;
25
+ }
26
+ const result = await get(url, { access: "private" });
27
+ if (result === null) {
28
+ return null;
29
+ }
30
+ return new Response(result.stream).text();
31
+ },
32
+ async write(pathname, body, overwrite) {
33
+ await put(pathname, body, {
34
+ access: "private",
35
+ allowOverwrite: overwrite,
36
+ contentType: "application/json"
37
+ });
38
+ },
39
+ async listPaths(prefix) {
40
+ const paths = [];
41
+ let cursor;
42
+ do {
43
+ const page = await list({ prefix, cursor, limit: 1e3 });
44
+ paths.push(...page.blobs.map((blob) => blob.pathname));
45
+ cursor = page.hasMore ? page.cursor : void 0;
46
+ } while (cursor !== void 0);
47
+ return paths;
48
+ }
49
+ };
50
+ function createBlobDriver(options = {}) {
51
+ const client = options.client ?? vercelBlobClient;
52
+ const prefix = options.prefix ?? "factory";
53
+ function pathFor(collection, id) {
54
+ return `${prefix}/${collection}/${id}.json`;
55
+ }
56
+ async function readRecord(collection, id) {
57
+ let text;
58
+ try {
59
+ text = await client.read(pathFor(collection, id));
60
+ } catch (error) {
61
+ if (isNotFoundError(error)) {
62
+ return null;
63
+ }
64
+ throw error;
65
+ }
66
+ if (text === null) {
67
+ return null;
68
+ }
69
+ const parsed = JSON.parse(text);
70
+ if (typeof parsed.version !== "number") {
71
+ throw new Error(`malformed stored record at ${pathFor(collection, id)}`);
72
+ }
73
+ return parsed;
74
+ }
75
+ return {
76
+ name: "blob",
77
+ get(collection, id) {
78
+ return readRecord(collection, id);
79
+ },
80
+ async insert(collection, id, value) {
81
+ try {
82
+ await client.write(
83
+ pathFor(collection, id),
84
+ JSON.stringify({ value, version: 1 }),
85
+ false
86
+ );
87
+ } catch (error) {
88
+ if (isAlreadyExistsError(error)) {
89
+ throw new RecordExistsError(collection, id);
90
+ }
91
+ throw error;
92
+ }
93
+ },
94
+ async update(collection, id, value, expectedVersion) {
95
+ const existing = await readRecord(collection, id);
96
+ if (existing === null) {
97
+ throw new RecordNotFoundError(collection, id);
98
+ }
99
+ if (existing.version !== expectedVersion) {
100
+ throw new VersionConflictError(collection, id, expectedVersion);
101
+ }
102
+ await client.write(
103
+ pathFor(collection, id),
104
+ JSON.stringify({ value, version: expectedVersion + 1 }),
105
+ true
106
+ );
107
+ },
108
+ async list(collection) {
109
+ const dir = `${prefix}/${collection}/`;
110
+ const paths = await client.listPaths(dir);
111
+ const entries = [];
112
+ for (const pathname of paths) {
113
+ const id = pathname.slice(dir.length).replace(/\.json$/u, "");
114
+ const record = await readRecord(collection, id);
115
+ if (record !== null) {
116
+ entries.push({ id, record });
117
+ }
118
+ }
119
+ return entries;
120
+ }
121
+ };
122
+ }
123
+ export {
124
+ createBlobDriver,
125
+ vercelBlobClient
126
+ };
127
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/blob/index.ts"],"sourcesContent":["import { get, head, list, put } from \"@vercel/blob\";\nimport {\n RecordExistsError,\n RecordNotFoundError,\n VersionConflictError,\n type CollectionName,\n type StoredEntry,\n type StoreDriver,\n} from \"../store/driver\";\n\n/** The narrow blob operations the driver needs; injectable for tests. */\nexport interface BlobClient {\n read(pathname: string): Promise<string | null>;\n /** Must reject with an already-exists error when overwrite is false and the path exists. */\n write(pathname: string, body: string, overwrite: boolean): Promise<void>;\n listPaths(prefix: string): Promise<string[]>;\n}\n\n// @vercel/blob throws BlobNotFoundError with the message\n// \"Vercel Blob: The requested blob does not exist\".\nfunction isNotFoundError(error: unknown): boolean {\n return (\n error instanceof Error &&\n (error.name === \"BlobNotFoundError\" ||\n /does not exist|not.?found|404/iu.test(error.message))\n );\n}\n\nfunction isAlreadyExistsError(error: unknown): boolean {\n return (\n error instanceof Error &&\n /already exists|not allowed to overwrite|overwrite/iu.test(error.message)\n );\n}\n\nexport const vercelBlobClient: BlobClient = {\n async read(pathname) {\n let url: string;\n try {\n url = (await head(pathname)).url;\n } catch (error) {\n if (isNotFoundError(error)) {\n return null;\n }\n throw error;\n }\n const result = await get(url, { access: \"private\" });\n if (result === null) {\n return null;\n }\n return new Response(result.stream).text();\n },\n\n async write(pathname, body, overwrite) {\n await put(pathname, body, {\n access: \"private\",\n allowOverwrite: overwrite,\n contentType: \"application/json\",\n });\n },\n\n async listPaths(prefix) {\n const paths: string[] = [];\n let cursor: string | undefined;\n do {\n const page = await list({ prefix, cursor, limit: 1000 });\n paths.push(...page.blobs.map((blob) => blob.pathname));\n cursor = page.hasMore ? page.cursor : undefined;\n } while (cursor !== undefined);\n return paths;\n },\n};\n\nexport interface BlobDriverOptions {\n client?: BlobClient;\n /** Path prefix inside the blob store; defaults to \"factory\". */\n prefix?: string;\n}\n\n/**\n * Store driver on Vercel Blob: durable and dependency-free (one Blob store on\n * the project, no database). Inserts and receipts are exactly as safe as on\n * Postgres (create-if-absent is atomic). The compare-and-swap update is\n * BEST-EFFORT: the version check reads then writes without a lock, so two\n * simultaneous writers can race. Acceptable for low-volume factories; use the\n * Postgres driver when concurrent dispatchers are expected.\n */\nexport function createBlobDriver(options: BlobDriverOptions = {}): StoreDriver {\n const client = options.client ?? vercelBlobClient;\n const prefix = options.prefix ?? \"factory\";\n\n function pathFor(collection: CollectionName, id: string): string {\n return `${prefix}/${collection}/${id}.json`;\n }\n\n async function readRecord(\n collection: CollectionName,\n id: string,\n ): Promise<{ value: unknown; version: number } | null> {\n let text: string | null;\n try {\n text = await client.read(pathFor(collection, id));\n } catch (error) {\n if (isNotFoundError(error)) {\n return null;\n }\n throw error;\n }\n if (text === null) {\n return null;\n }\n const parsed = JSON.parse(text) as { value: unknown; version: number };\n if (typeof parsed.version !== \"number\") {\n throw new Error(`malformed stored record at ${pathFor(collection, id)}`);\n }\n return parsed;\n }\n\n return {\n name: \"blob\",\n\n get(collection, id) {\n return readRecord(collection, id);\n },\n\n async insert(collection, id, value) {\n try {\n await client.write(\n pathFor(collection, id),\n JSON.stringify({ value, version: 1 }),\n false,\n );\n } catch (error) {\n if (isAlreadyExistsError(error)) {\n throw new RecordExistsError(collection, id);\n }\n throw error;\n }\n },\n\n async update(collection, id, value, expectedVersion) {\n const existing = await readRecord(collection, id);\n if (existing === null) {\n throw new RecordNotFoundError(collection, id);\n }\n if (existing.version !== expectedVersion) {\n throw new VersionConflictError(collection, id, expectedVersion);\n }\n await client.write(\n pathFor(collection, id),\n JSON.stringify({ value, version: expectedVersion + 1 }),\n true,\n );\n },\n\n async list(collection) {\n const dir = `${prefix}/${collection}/`;\n const paths = await client.listPaths(dir);\n const entries: StoredEntry[] = [];\n for (const pathname of paths) {\n const id = pathname.slice(dir.length).replace(/\\.json$/u, \"\");\n const record = await readRecord(collection, id);\n if (record !== null) {\n entries.push({ id, record });\n }\n }\n return entries;\n },\n };\n}\n"],"mappings":"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","names":[]}