@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.
- package/dist/{engine-BXVTLiXU.d.ts → approval-Z7iaWibT.d.ts} +195 -4
- package/dist/blob/index.d.ts +26 -0
- package/dist/blob/index.js +127 -0
- package/dist/blob/index.js.map +1 -0
- package/dist/chunk-LBEYCOSR.js +351 -0
- package/dist/chunk-LBEYCOSR.js.map +1 -0
- package/dist/connectors/github.d.ts +26 -1
- package/dist/connectors/github.js +62 -0
- package/dist/connectors/github.js.map +1 -1
- package/dist/eve/index.d.ts +459 -4
- package/dist/eve/index.js +506 -33
- package/dist/eve/index.js.map +1 -1
- package/dist/index.d.ts +92 -3
- package/dist/index.js +257 -62
- package/dist/index.js.map +1 -1
- package/dist/next-page.d.ts +5 -0
- package/dist/next-page.js +75 -0
- package/dist/next-page.js.map +1 -0
- package/dist/next.d.ts +12 -0
- package/dist/next.js +45 -0
- package/dist/next.js.map +1 -0
- package/package.json +8 -8
- package/dist/chunk-BSDWPFKE.js +0 -197
- package/dist/chunk-BSDWPFKE.js.map +0 -1
|
@@ -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
|
-
|
|
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":[]}
|