@stardeck-customer-apps/testing 0.24.0 → 0.25.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/SKILL.md +5 -1
- package/dist/index.d.mts +33 -1
- package/dist/index.d.ts +33 -1
- package/dist/index.js +107 -1
- package/dist/index.mjs +107 -1
- package/dist/next/headers-shim.js +2 -0
- package/dist/next/headers-shim.mjs +2 -0
- package/dist/setup.js +98 -0
- package/dist/setup.mjs +98 -0
- package/package.json +1 -1
package/SKILL.md
CHANGED
|
@@ -565,7 +565,11 @@ cursor, limit })`; `list()` intentionally keeps its previous array shape.
|
|
|
565
565
|
in `app.storage` against the simulated `STORAGE_URL` host.
|
|
566
566
|
- `client.slack` / `client.line` / `client.facebook` send endpoints — captured
|
|
567
567
|
in `app.messages`.
|
|
568
|
-
- `createEdgeClient()` (print,
|
|
568
|
+
- `createEdgeClient()` (print, `enqueueReceipt()`/`getPrintJob()` durable receipts: idempotent per key,
|
|
569
|
+
conflicts and refused options mirrored, `app.edge.durablePrints`, and
|
|
570
|
+
`app.edge.settleNextDurableJob({ status: "needs_attention", reason, outcome })` to drive the
|
|
571
|
+
uncertain/not-printed UI, or `{ status: "sent_to_printer", simulated: true }` to mirror a
|
|
572
|
+
virtual-printer job (the status then carries `simulated: true`); pair/unpair, list devices/peripherals, show/clear
|
|
569
573
|
display, test print, `getCapabilities()`) — captured in `app.edge`; seed
|
|
570
574
|
devices/peripherals for pairing pickers via `app.edge.seedDevices()` /
|
|
571
575
|
`seedPeripherals()`.
|
package/dist/index.d.mts
CHANGED
|
@@ -542,6 +542,29 @@ interface CapturedPrint {
|
|
|
542
542
|
logo?: boolean;
|
|
543
543
|
copies: number;
|
|
544
544
|
}
|
|
545
|
+
/** One `edge.enqueueReceipt()` the app made, as the simulator accepted it. */
|
|
546
|
+
interface CapturedDurablePrint {
|
|
547
|
+
jobId: string;
|
|
548
|
+
idempotencyKey: string;
|
|
549
|
+
alias?: string;
|
|
550
|
+
deviceId?: string;
|
|
551
|
+
peripheralId?: string;
|
|
552
|
+
receipt: ReceiptPayload;
|
|
553
|
+
logo?: boolean;
|
|
554
|
+
}
|
|
555
|
+
/** How a simulated durable job settles. Mirrors the platform's status contract. */
|
|
556
|
+
type SimulatedDurableOutcome = {
|
|
557
|
+
status: "sent_to_printer";
|
|
558
|
+
simulated?: boolean;
|
|
559
|
+
} | {
|
|
560
|
+
status: "queued";
|
|
561
|
+
simulated?: boolean;
|
|
562
|
+
} | {
|
|
563
|
+
status: "needs_attention";
|
|
564
|
+
reason: string;
|
|
565
|
+
outcome: "not_printed" | "uncertain";
|
|
566
|
+
message?: string;
|
|
567
|
+
};
|
|
545
568
|
interface CapturedDisplay {
|
|
546
569
|
action: "show" | "clear";
|
|
547
570
|
alias?: string;
|
|
@@ -557,6 +580,15 @@ interface TestEdge {
|
|
|
557
580
|
get prints(): CapturedPrint[];
|
|
558
581
|
get displays(): CapturedDisplay[];
|
|
559
582
|
get testPrints(): CapturedTestPrint[];
|
|
583
|
+
/** Durable receipts accepted by `edge.enqueueReceipt()` (one per distinct idempotency key). */
|
|
584
|
+
get durablePrints(): CapturedDurablePrint[];
|
|
585
|
+
/**
|
|
586
|
+
* Decide how the NEXT accepted durable job settles when the app reads it with
|
|
587
|
+
* `edge.getPrintJob()`. Without this every job reads `sent_to_printer`. Queue
|
|
588
|
+
* several to model a run of jobs; use `needs_attention` with `uncertain` to
|
|
589
|
+
* test the "check the printer before reprinting" path.
|
|
590
|
+
*/
|
|
591
|
+
settleNextDurableJob(outcome: SimulatedDurableOutcome): void;
|
|
560
592
|
latestPrint(): CapturedPrint | undefined;
|
|
561
593
|
latestDisplay(): CapturedDisplay | undefined;
|
|
562
594
|
get bindings(): BindingInfo[];
|
|
@@ -742,4 +774,4 @@ declare const TEST_ENV_DEFAULTS: {
|
|
|
742
774
|
/** Default location of the DDL snapshot written by `generate-types`. */
|
|
743
775
|
declare const DEFAULT_SCHEMA_PATH = "./src/generated/data-store-schema.sql";
|
|
744
776
|
|
|
745
|
-
export { type BindingInfo, CONTROL_PLANE_TEST_URL, type CallRouteOptions, type CapturedCheckout, type CapturedDisplay, type CapturedEmail, type CapturedIdentity, type CapturedIdentityLink, type CapturedMessage, type CapturedPrint, type CapturedStarlensCall, type CapturedTestPrint, type CapturedUpload, DATA_STORE_TEST_HOST, DATA_STORE_TEST_URL, DEFAULT_SCHEMA_PATH, type DeviceInfo, type PaymentSimulatorFailure, type PaymentSimulatorOperation, type PeripheralInfo, STORAGE_TEST_HOST, STORAGE_TEST_URL, type SessionTokens, type SimBoltCharge, type SimBoltConnection, type SimBoltIntentRecord, type SimBoltIntentStatus, type StarlensReply, TEST_ENV_DEFAULTS, type TestApp, type TestAppOptions, TestDataStoreInput, type TestDirectory, type TestEdge, type TestInbox, type TestMessages, type TestPayments, type TestStarlens, type TestStorage, type TestUser, WORKFLOW_NAME_PREFIX, callRoute, createTestApp, describeWorkflow, parseWorkflowName };
|
|
777
|
+
export { type BindingInfo, CONTROL_PLANE_TEST_URL, type CallRouteOptions, type CapturedCheckout, type CapturedDisplay, type CapturedDurablePrint, type CapturedEmail, type CapturedIdentity, type CapturedIdentityLink, type CapturedMessage, type CapturedPrint, type CapturedStarlensCall, type CapturedTestPrint, type CapturedUpload, DATA_STORE_TEST_HOST, DATA_STORE_TEST_URL, DEFAULT_SCHEMA_PATH, type DeviceInfo, type PaymentSimulatorFailure, type PaymentSimulatorOperation, type PeripheralInfo, STORAGE_TEST_HOST, STORAGE_TEST_URL, type SessionTokens, type SimBoltCharge, type SimBoltConnection, type SimBoltIntentRecord, type SimBoltIntentStatus, type SimulatedDurableOutcome, type StarlensReply, TEST_ENV_DEFAULTS, type TestApp, type TestAppOptions, TestDataStoreInput, type TestDirectory, type TestEdge, type TestInbox, type TestMessages, type TestPayments, type TestStarlens, type TestStorage, type TestUser, WORKFLOW_NAME_PREFIX, callRoute, createTestApp, describeWorkflow, parseWorkflowName };
|
package/dist/index.d.ts
CHANGED
|
@@ -542,6 +542,29 @@ interface CapturedPrint {
|
|
|
542
542
|
logo?: boolean;
|
|
543
543
|
copies: number;
|
|
544
544
|
}
|
|
545
|
+
/** One `edge.enqueueReceipt()` the app made, as the simulator accepted it. */
|
|
546
|
+
interface CapturedDurablePrint {
|
|
547
|
+
jobId: string;
|
|
548
|
+
idempotencyKey: string;
|
|
549
|
+
alias?: string;
|
|
550
|
+
deviceId?: string;
|
|
551
|
+
peripheralId?: string;
|
|
552
|
+
receipt: ReceiptPayload;
|
|
553
|
+
logo?: boolean;
|
|
554
|
+
}
|
|
555
|
+
/** How a simulated durable job settles. Mirrors the platform's status contract. */
|
|
556
|
+
type SimulatedDurableOutcome = {
|
|
557
|
+
status: "sent_to_printer";
|
|
558
|
+
simulated?: boolean;
|
|
559
|
+
} | {
|
|
560
|
+
status: "queued";
|
|
561
|
+
simulated?: boolean;
|
|
562
|
+
} | {
|
|
563
|
+
status: "needs_attention";
|
|
564
|
+
reason: string;
|
|
565
|
+
outcome: "not_printed" | "uncertain";
|
|
566
|
+
message?: string;
|
|
567
|
+
};
|
|
545
568
|
interface CapturedDisplay {
|
|
546
569
|
action: "show" | "clear";
|
|
547
570
|
alias?: string;
|
|
@@ -557,6 +580,15 @@ interface TestEdge {
|
|
|
557
580
|
get prints(): CapturedPrint[];
|
|
558
581
|
get displays(): CapturedDisplay[];
|
|
559
582
|
get testPrints(): CapturedTestPrint[];
|
|
583
|
+
/** Durable receipts accepted by `edge.enqueueReceipt()` (one per distinct idempotency key). */
|
|
584
|
+
get durablePrints(): CapturedDurablePrint[];
|
|
585
|
+
/**
|
|
586
|
+
* Decide how the NEXT accepted durable job settles when the app reads it with
|
|
587
|
+
* `edge.getPrintJob()`. Without this every job reads `sent_to_printer`. Queue
|
|
588
|
+
* several to model a run of jobs; use `needs_attention` with `uncertain` to
|
|
589
|
+
* test the "check the printer before reprinting" path.
|
|
590
|
+
*/
|
|
591
|
+
settleNextDurableJob(outcome: SimulatedDurableOutcome): void;
|
|
560
592
|
latestPrint(): CapturedPrint | undefined;
|
|
561
593
|
latestDisplay(): CapturedDisplay | undefined;
|
|
562
594
|
get bindings(): BindingInfo[];
|
|
@@ -742,4 +774,4 @@ declare const TEST_ENV_DEFAULTS: {
|
|
|
742
774
|
/** Default location of the DDL snapshot written by `generate-types`. */
|
|
743
775
|
declare const DEFAULT_SCHEMA_PATH = "./src/generated/data-store-schema.sql";
|
|
744
776
|
|
|
745
|
-
export { type BindingInfo, CONTROL_PLANE_TEST_URL, type CallRouteOptions, type CapturedCheckout, type CapturedDisplay, type CapturedEmail, type CapturedIdentity, type CapturedIdentityLink, type CapturedMessage, type CapturedPrint, type CapturedStarlensCall, type CapturedTestPrint, type CapturedUpload, DATA_STORE_TEST_HOST, DATA_STORE_TEST_URL, DEFAULT_SCHEMA_PATH, type DeviceInfo, type PaymentSimulatorFailure, type PaymentSimulatorOperation, type PeripheralInfo, STORAGE_TEST_HOST, STORAGE_TEST_URL, type SessionTokens, type SimBoltCharge, type SimBoltConnection, type SimBoltIntentRecord, type SimBoltIntentStatus, type StarlensReply, TEST_ENV_DEFAULTS, type TestApp, type TestAppOptions, TestDataStoreInput, type TestDirectory, type TestEdge, type TestInbox, type TestMessages, type TestPayments, type TestStarlens, type TestStorage, type TestUser, WORKFLOW_NAME_PREFIX, callRoute, createTestApp, describeWorkflow, parseWorkflowName };
|
|
777
|
+
export { type BindingInfo, CONTROL_PLANE_TEST_URL, type CallRouteOptions, type CapturedCheckout, type CapturedDisplay, type CapturedDurablePrint, type CapturedEmail, type CapturedIdentity, type CapturedIdentityLink, type CapturedMessage, type CapturedPrint, type CapturedStarlensCall, type CapturedTestPrint, type CapturedUpload, DATA_STORE_TEST_HOST, DATA_STORE_TEST_URL, DEFAULT_SCHEMA_PATH, type DeviceInfo, type PaymentSimulatorFailure, type PaymentSimulatorOperation, type PeripheralInfo, STORAGE_TEST_HOST, STORAGE_TEST_URL, type SessionTokens, type SimBoltCharge, type SimBoltConnection, type SimBoltIntentRecord, type SimBoltIntentStatus, type SimulatedDurableOutcome, type StarlensReply, TEST_ENV_DEFAULTS, type TestApp, type TestAppOptions, TestDataStoreInput, type TestDirectory, type TestEdge, type TestInbox, type TestMessages, type TestPayments, type TestStarlens, type TestStorage, type TestUser, WORKFLOW_NAME_PREFIX, callRoute, createTestApp, describeWorkflow, parseWorkflowName };
|
package/dist/index.js
CHANGED
|
@@ -365,6 +365,8 @@ var state = globalSingleton("state", () => ({
|
|
|
365
365
|
edgeDisplays: [],
|
|
366
366
|
edgeTestPrints: [],
|
|
367
367
|
edgePrintCounter: 0,
|
|
368
|
+
edgeDurableJobs: /* @__PURE__ */ new Map(),
|
|
369
|
+
edgeDurableOutcomes: [],
|
|
368
370
|
edgeDevices: [],
|
|
369
371
|
edgePeripherals: [],
|
|
370
372
|
edgeBindings: /* @__PURE__ */ new Map(),
|
|
@@ -3382,6 +3384,95 @@ async function handlePrint(request) {
|
|
|
3382
3384
|
status: "completed"
|
|
3383
3385
|
});
|
|
3384
3386
|
}
|
|
3387
|
+
var IDEMPOTENCY_KEY = /^[A-Za-z0-9_.:-]{1,128}$/;
|
|
3388
|
+
async function handleEnqueue(request) {
|
|
3389
|
+
const body = await readJsonBody(request);
|
|
3390
|
+
const key = typeof body.idempotencyKey === "string" ? body.idempotencyKey : "";
|
|
3391
|
+
if (!IDEMPOTENCY_KEY.test(key)) {
|
|
3392
|
+
return edgeFailure("idempotencyKey must be 1-128 characters of A-Z a-z 0-9 _ . : -");
|
|
3393
|
+
}
|
|
3394
|
+
if (body.openDrawer === true) {
|
|
3395
|
+
return edgeFailure("Durable receipts cannot open the cash drawer; a retry would kick it again");
|
|
3396
|
+
}
|
|
3397
|
+
if (body.copies !== void 0 && body.copies !== 1) {
|
|
3398
|
+
return edgeFailure(
|
|
3399
|
+
"Durable receipts print exactly one copy; submit a second job for another copy"
|
|
3400
|
+
);
|
|
3401
|
+
}
|
|
3402
|
+
const receipt = body.receipt ?? {};
|
|
3403
|
+
if (receipt.rawEscPos !== void 0) {
|
|
3404
|
+
return edgeFailure("rawEscPos is not supported for durable receipts");
|
|
3405
|
+
}
|
|
3406
|
+
const requestHash = JSON.stringify([
|
|
3407
|
+
body.alias,
|
|
3408
|
+
body.deviceId,
|
|
3409
|
+
body.peripheralId,
|
|
3410
|
+
receipt,
|
|
3411
|
+
body.logo
|
|
3412
|
+
]);
|
|
3413
|
+
const existing = [...state.edgeDurableJobs.values()].find(
|
|
3414
|
+
(j) => j.captured.idempotencyKey === key
|
|
3415
|
+
);
|
|
3416
|
+
if (existing) {
|
|
3417
|
+
if (existing.requestHash !== requestHash) {
|
|
3418
|
+
return edgeFailure(
|
|
3419
|
+
"This idempotencyKey was already used for a different receipt, target or options.",
|
|
3420
|
+
409,
|
|
3421
|
+
"IDEMPOTENCY_KEY_CONFLICT"
|
|
3422
|
+
);
|
|
3423
|
+
}
|
|
3424
|
+
return json({ success: true, data: durableView(existing.captured.jobId, "queued") }, 200);
|
|
3425
|
+
}
|
|
3426
|
+
const jobId = nextJobId();
|
|
3427
|
+
const captured = {
|
|
3428
|
+
jobId,
|
|
3429
|
+
idempotencyKey: key,
|
|
3430
|
+
alias: body.alias ? String(body.alias) : void 0,
|
|
3431
|
+
deviceId: body.deviceId ? String(body.deviceId) : void 0,
|
|
3432
|
+
peripheralId: body.peripheralId ? String(body.peripheralId) : void 0,
|
|
3433
|
+
receipt,
|
|
3434
|
+
logo: body.logo === true ? true : body.logo === false ? false : void 0
|
|
3435
|
+
};
|
|
3436
|
+
const outcome = state.edgeDurableOutcomes.shift() ?? { status: "sent_to_printer" };
|
|
3437
|
+
state.edgeDurableJobs.set(jobId, { requestHash, captured, outcome });
|
|
3438
|
+
return json({ success: true, data: durableView(jobId, "queued") }, 202);
|
|
3439
|
+
}
|
|
3440
|
+
function durableView(jobId, as) {
|
|
3441
|
+
const job = state.edgeDurableJobs.get(jobId);
|
|
3442
|
+
const createdAt = now2();
|
|
3443
|
+
const simulated = job.outcome.status !== "needs_attention" && job.outcome.simulated ? { simulated: true } : {};
|
|
3444
|
+
const base = {
|
|
3445
|
+
...simulated,
|
|
3446
|
+
jobId,
|
|
3447
|
+
attempts: as === "queued" ? 0 : 1,
|
|
3448
|
+
createdAt,
|
|
3449
|
+
expiresAt: new Date(Date.parse(createdAt) + 10 * 6e4).toISOString(),
|
|
3450
|
+
target: {
|
|
3451
|
+
deviceId: job.captured.deviceId ?? "sim-device",
|
|
3452
|
+
peripheralId: job.captured.peripheralId ?? "sim-peripheral"
|
|
3453
|
+
}
|
|
3454
|
+
};
|
|
3455
|
+
const outcome = as === "queued" ? { status: "queued" } : job.outcome;
|
|
3456
|
+
if (outcome.status === "needs_attention") {
|
|
3457
|
+
return {
|
|
3458
|
+
...base,
|
|
3459
|
+
status: "needs_attention",
|
|
3460
|
+
attention: {
|
|
3461
|
+
reason: outcome.reason,
|
|
3462
|
+
outcome: outcome.outcome,
|
|
3463
|
+
message: outcome.message ?? "Delivery stopped."
|
|
3464
|
+
}
|
|
3465
|
+
};
|
|
3466
|
+
}
|
|
3467
|
+
if (outcome.status === "sent_to_printer") {
|
|
3468
|
+
return { ...base, status: "sent_to_printer", sentToPrinterAt: createdAt };
|
|
3469
|
+
}
|
|
3470
|
+
return { ...base, status: "queued" };
|
|
3471
|
+
}
|
|
3472
|
+
function handleGetDurable(jobId) {
|
|
3473
|
+
if (!state.edgeDurableJobs.has(jobId)) return edgeFailure("Print job not found", 404);
|
|
3474
|
+
return success(durableView(jobId));
|
|
3475
|
+
}
|
|
3385
3476
|
async function handleShowDisplay(request) {
|
|
3386
3477
|
const body = await readJsonBody(request);
|
|
3387
3478
|
const captured = {
|
|
@@ -3431,6 +3522,13 @@ async function handleEdgeRequest(request, subPath) {
|
|
|
3431
3522
|
if (subPath === "/print" && method === "POST") {
|
|
3432
3523
|
return handlePrint(request);
|
|
3433
3524
|
}
|
|
3525
|
+
if (subPath === "/print-jobs" && method === "POST") {
|
|
3526
|
+
return handleEnqueue(request);
|
|
3527
|
+
}
|
|
3528
|
+
const durableMatch = subPath.match(/^\/print-jobs\/([^/]+)$/);
|
|
3529
|
+
if (durableMatch && method === "GET") {
|
|
3530
|
+
return handleGetDurable(decodeURIComponent(durableMatch[1]));
|
|
3531
|
+
}
|
|
3434
3532
|
if (subPath === "/capabilities" && method === "GET") {
|
|
3435
3533
|
return handleCapabilities(request);
|
|
3436
3534
|
}
|
|
@@ -3474,6 +3572,12 @@ function createEdge() {
|
|
|
3474
3572
|
get testPrints() {
|
|
3475
3573
|
return [...state.edgeTestPrints];
|
|
3476
3574
|
},
|
|
3575
|
+
get durablePrints() {
|
|
3576
|
+
return [...state.edgeDurableJobs.values()].map((j) => j.captured);
|
|
3577
|
+
},
|
|
3578
|
+
settleNextDurableJob(outcome) {
|
|
3579
|
+
state.edgeDurableOutcomes.push(outcome);
|
|
3580
|
+
},
|
|
3477
3581
|
latestPrint() {
|
|
3478
3582
|
return state.edgePrints[state.edgePrints.length - 1];
|
|
3479
3583
|
},
|
|
@@ -3510,12 +3614,14 @@ function createEdge() {
|
|
|
3510
3614
|
state.edgeDisplays = [];
|
|
3511
3615
|
state.edgeTestPrints = [];
|
|
3512
3616
|
state.edgePrintCounter = 0;
|
|
3617
|
+
state.edgeDurableJobs.clear();
|
|
3618
|
+
state.edgeDurableOutcomes = [];
|
|
3513
3619
|
state.edgeDevices = [];
|
|
3514
3620
|
state.edgePeripherals = [];
|
|
3515
3621
|
state.edgeBindings.clear();
|
|
3516
3622
|
},
|
|
3517
3623
|
get count() {
|
|
3518
|
-
return state.edgePrints.length + state.edgeDisplays.length + state.edgeTestPrints.length;
|
|
3624
|
+
return state.edgePrints.length + state.edgeDisplays.length + state.edgeTestPrints.length + state.edgeDurableJobs.size;
|
|
3519
3625
|
}
|
|
3520
3626
|
};
|
|
3521
3627
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -326,6 +326,8 @@ var state = globalSingleton("state", () => ({
|
|
|
326
326
|
edgeDisplays: [],
|
|
327
327
|
edgeTestPrints: [],
|
|
328
328
|
edgePrintCounter: 0,
|
|
329
|
+
edgeDurableJobs: /* @__PURE__ */ new Map(),
|
|
330
|
+
edgeDurableOutcomes: [],
|
|
329
331
|
edgeDevices: [],
|
|
330
332
|
edgePeripherals: [],
|
|
331
333
|
edgeBindings: /* @__PURE__ */ new Map(),
|
|
@@ -3343,6 +3345,95 @@ async function handlePrint(request) {
|
|
|
3343
3345
|
status: "completed"
|
|
3344
3346
|
});
|
|
3345
3347
|
}
|
|
3348
|
+
var IDEMPOTENCY_KEY = /^[A-Za-z0-9_.:-]{1,128}$/;
|
|
3349
|
+
async function handleEnqueue(request) {
|
|
3350
|
+
const body = await readJsonBody(request);
|
|
3351
|
+
const key = typeof body.idempotencyKey === "string" ? body.idempotencyKey : "";
|
|
3352
|
+
if (!IDEMPOTENCY_KEY.test(key)) {
|
|
3353
|
+
return edgeFailure("idempotencyKey must be 1-128 characters of A-Z a-z 0-9 _ . : -");
|
|
3354
|
+
}
|
|
3355
|
+
if (body.openDrawer === true) {
|
|
3356
|
+
return edgeFailure("Durable receipts cannot open the cash drawer; a retry would kick it again");
|
|
3357
|
+
}
|
|
3358
|
+
if (body.copies !== void 0 && body.copies !== 1) {
|
|
3359
|
+
return edgeFailure(
|
|
3360
|
+
"Durable receipts print exactly one copy; submit a second job for another copy"
|
|
3361
|
+
);
|
|
3362
|
+
}
|
|
3363
|
+
const receipt = body.receipt ?? {};
|
|
3364
|
+
if (receipt.rawEscPos !== void 0) {
|
|
3365
|
+
return edgeFailure("rawEscPos is not supported for durable receipts");
|
|
3366
|
+
}
|
|
3367
|
+
const requestHash = JSON.stringify([
|
|
3368
|
+
body.alias,
|
|
3369
|
+
body.deviceId,
|
|
3370
|
+
body.peripheralId,
|
|
3371
|
+
receipt,
|
|
3372
|
+
body.logo
|
|
3373
|
+
]);
|
|
3374
|
+
const existing = [...state.edgeDurableJobs.values()].find(
|
|
3375
|
+
(j) => j.captured.idempotencyKey === key
|
|
3376
|
+
);
|
|
3377
|
+
if (existing) {
|
|
3378
|
+
if (existing.requestHash !== requestHash) {
|
|
3379
|
+
return edgeFailure(
|
|
3380
|
+
"This idempotencyKey was already used for a different receipt, target or options.",
|
|
3381
|
+
409,
|
|
3382
|
+
"IDEMPOTENCY_KEY_CONFLICT"
|
|
3383
|
+
);
|
|
3384
|
+
}
|
|
3385
|
+
return json({ success: true, data: durableView(existing.captured.jobId, "queued") }, 200);
|
|
3386
|
+
}
|
|
3387
|
+
const jobId = nextJobId();
|
|
3388
|
+
const captured = {
|
|
3389
|
+
jobId,
|
|
3390
|
+
idempotencyKey: key,
|
|
3391
|
+
alias: body.alias ? String(body.alias) : void 0,
|
|
3392
|
+
deviceId: body.deviceId ? String(body.deviceId) : void 0,
|
|
3393
|
+
peripheralId: body.peripheralId ? String(body.peripheralId) : void 0,
|
|
3394
|
+
receipt,
|
|
3395
|
+
logo: body.logo === true ? true : body.logo === false ? false : void 0
|
|
3396
|
+
};
|
|
3397
|
+
const outcome = state.edgeDurableOutcomes.shift() ?? { status: "sent_to_printer" };
|
|
3398
|
+
state.edgeDurableJobs.set(jobId, { requestHash, captured, outcome });
|
|
3399
|
+
return json({ success: true, data: durableView(jobId, "queued") }, 202);
|
|
3400
|
+
}
|
|
3401
|
+
function durableView(jobId, as) {
|
|
3402
|
+
const job = state.edgeDurableJobs.get(jobId);
|
|
3403
|
+
const createdAt = now2();
|
|
3404
|
+
const simulated = job.outcome.status !== "needs_attention" && job.outcome.simulated ? { simulated: true } : {};
|
|
3405
|
+
const base = {
|
|
3406
|
+
...simulated,
|
|
3407
|
+
jobId,
|
|
3408
|
+
attempts: as === "queued" ? 0 : 1,
|
|
3409
|
+
createdAt,
|
|
3410
|
+
expiresAt: new Date(Date.parse(createdAt) + 10 * 6e4).toISOString(),
|
|
3411
|
+
target: {
|
|
3412
|
+
deviceId: job.captured.deviceId ?? "sim-device",
|
|
3413
|
+
peripheralId: job.captured.peripheralId ?? "sim-peripheral"
|
|
3414
|
+
}
|
|
3415
|
+
};
|
|
3416
|
+
const outcome = as === "queued" ? { status: "queued" } : job.outcome;
|
|
3417
|
+
if (outcome.status === "needs_attention") {
|
|
3418
|
+
return {
|
|
3419
|
+
...base,
|
|
3420
|
+
status: "needs_attention",
|
|
3421
|
+
attention: {
|
|
3422
|
+
reason: outcome.reason,
|
|
3423
|
+
outcome: outcome.outcome,
|
|
3424
|
+
message: outcome.message ?? "Delivery stopped."
|
|
3425
|
+
}
|
|
3426
|
+
};
|
|
3427
|
+
}
|
|
3428
|
+
if (outcome.status === "sent_to_printer") {
|
|
3429
|
+
return { ...base, status: "sent_to_printer", sentToPrinterAt: createdAt };
|
|
3430
|
+
}
|
|
3431
|
+
return { ...base, status: "queued" };
|
|
3432
|
+
}
|
|
3433
|
+
function handleGetDurable(jobId) {
|
|
3434
|
+
if (!state.edgeDurableJobs.has(jobId)) return edgeFailure("Print job not found", 404);
|
|
3435
|
+
return success(durableView(jobId));
|
|
3436
|
+
}
|
|
3346
3437
|
async function handleShowDisplay(request) {
|
|
3347
3438
|
const body = await readJsonBody(request);
|
|
3348
3439
|
const captured = {
|
|
@@ -3392,6 +3483,13 @@ async function handleEdgeRequest(request, subPath) {
|
|
|
3392
3483
|
if (subPath === "/print" && method === "POST") {
|
|
3393
3484
|
return handlePrint(request);
|
|
3394
3485
|
}
|
|
3486
|
+
if (subPath === "/print-jobs" && method === "POST") {
|
|
3487
|
+
return handleEnqueue(request);
|
|
3488
|
+
}
|
|
3489
|
+
const durableMatch = subPath.match(/^\/print-jobs\/([^/]+)$/);
|
|
3490
|
+
if (durableMatch && method === "GET") {
|
|
3491
|
+
return handleGetDurable(decodeURIComponent(durableMatch[1]));
|
|
3492
|
+
}
|
|
3395
3493
|
if (subPath === "/capabilities" && method === "GET") {
|
|
3396
3494
|
return handleCapabilities(request);
|
|
3397
3495
|
}
|
|
@@ -3435,6 +3533,12 @@ function createEdge() {
|
|
|
3435
3533
|
get testPrints() {
|
|
3436
3534
|
return [...state.edgeTestPrints];
|
|
3437
3535
|
},
|
|
3536
|
+
get durablePrints() {
|
|
3537
|
+
return [...state.edgeDurableJobs.values()].map((j) => j.captured);
|
|
3538
|
+
},
|
|
3539
|
+
settleNextDurableJob(outcome) {
|
|
3540
|
+
state.edgeDurableOutcomes.push(outcome);
|
|
3541
|
+
},
|
|
3438
3542
|
latestPrint() {
|
|
3439
3543
|
return state.edgePrints[state.edgePrints.length - 1];
|
|
3440
3544
|
},
|
|
@@ -3471,12 +3575,14 @@ function createEdge() {
|
|
|
3471
3575
|
state.edgeDisplays = [];
|
|
3472
3576
|
state.edgeTestPrints = [];
|
|
3473
3577
|
state.edgePrintCounter = 0;
|
|
3578
|
+
state.edgeDurableJobs.clear();
|
|
3579
|
+
state.edgeDurableOutcomes = [];
|
|
3474
3580
|
state.edgeDevices = [];
|
|
3475
3581
|
state.edgePeripherals = [];
|
|
3476
3582
|
state.edgeBindings.clear();
|
|
3477
3583
|
},
|
|
3478
3584
|
get count() {
|
|
3479
|
-
return state.edgePrints.length + state.edgeDisplays.length + state.edgeTestPrints.length;
|
|
3585
|
+
return state.edgePrints.length + state.edgeDisplays.length + state.edgeTestPrints.length + state.edgeDurableJobs.size;
|
|
3480
3586
|
}
|
|
3481
3587
|
};
|
|
3482
3588
|
}
|
|
@@ -87,6 +87,8 @@ var state = globalSingleton("state", () => ({
|
|
|
87
87
|
edgeDisplays: [],
|
|
88
88
|
edgeTestPrints: [],
|
|
89
89
|
edgePrintCounter: 0,
|
|
90
|
+
edgeDurableJobs: /* @__PURE__ */ new Map(),
|
|
91
|
+
edgeDurableOutcomes: [],
|
|
90
92
|
edgeDevices: [],
|
|
91
93
|
edgePeripherals: [],
|
|
92
94
|
edgeBindings: /* @__PURE__ */ new Map(),
|
|
@@ -60,6 +60,8 @@ var state = globalSingleton("state", () => ({
|
|
|
60
60
|
edgeDisplays: [],
|
|
61
61
|
edgeTestPrints: [],
|
|
62
62
|
edgePrintCounter: 0,
|
|
63
|
+
edgeDurableJobs: /* @__PURE__ */ new Map(),
|
|
64
|
+
edgeDurableOutcomes: [],
|
|
63
65
|
edgeDevices: [],
|
|
64
66
|
edgePeripherals: [],
|
|
65
67
|
edgeBindings: /* @__PURE__ */ new Map(),
|
package/dist/setup.js
CHANGED
|
@@ -92,6 +92,8 @@ var state = globalSingleton("state", () => ({
|
|
|
92
92
|
edgeDisplays: [],
|
|
93
93
|
edgeTestPrints: [],
|
|
94
94
|
edgePrintCounter: 0,
|
|
95
|
+
edgeDurableJobs: /* @__PURE__ */ new Map(),
|
|
96
|
+
edgeDurableOutcomes: [],
|
|
95
97
|
edgeDevices: [],
|
|
96
98
|
edgePeripherals: [],
|
|
97
99
|
edgeBindings: /* @__PURE__ */ new Map(),
|
|
@@ -2527,6 +2529,95 @@ async function handlePrint(request) {
|
|
|
2527
2529
|
status: "completed"
|
|
2528
2530
|
});
|
|
2529
2531
|
}
|
|
2532
|
+
var IDEMPOTENCY_KEY = /^[A-Za-z0-9_.:-]{1,128}$/;
|
|
2533
|
+
async function handleEnqueue(request) {
|
|
2534
|
+
const body = await readJsonBody(request);
|
|
2535
|
+
const key = typeof body.idempotencyKey === "string" ? body.idempotencyKey : "";
|
|
2536
|
+
if (!IDEMPOTENCY_KEY.test(key)) {
|
|
2537
|
+
return edgeFailure("idempotencyKey must be 1-128 characters of A-Z a-z 0-9 _ . : -");
|
|
2538
|
+
}
|
|
2539
|
+
if (body.openDrawer === true) {
|
|
2540
|
+
return edgeFailure("Durable receipts cannot open the cash drawer; a retry would kick it again");
|
|
2541
|
+
}
|
|
2542
|
+
if (body.copies !== void 0 && body.copies !== 1) {
|
|
2543
|
+
return edgeFailure(
|
|
2544
|
+
"Durable receipts print exactly one copy; submit a second job for another copy"
|
|
2545
|
+
);
|
|
2546
|
+
}
|
|
2547
|
+
const receipt = body.receipt ?? {};
|
|
2548
|
+
if (receipt.rawEscPos !== void 0) {
|
|
2549
|
+
return edgeFailure("rawEscPos is not supported for durable receipts");
|
|
2550
|
+
}
|
|
2551
|
+
const requestHash = JSON.stringify([
|
|
2552
|
+
body.alias,
|
|
2553
|
+
body.deviceId,
|
|
2554
|
+
body.peripheralId,
|
|
2555
|
+
receipt,
|
|
2556
|
+
body.logo
|
|
2557
|
+
]);
|
|
2558
|
+
const existing = [...state.edgeDurableJobs.values()].find(
|
|
2559
|
+
(j) => j.captured.idempotencyKey === key
|
|
2560
|
+
);
|
|
2561
|
+
if (existing) {
|
|
2562
|
+
if (existing.requestHash !== requestHash) {
|
|
2563
|
+
return edgeFailure(
|
|
2564
|
+
"This idempotencyKey was already used for a different receipt, target or options.",
|
|
2565
|
+
409,
|
|
2566
|
+
"IDEMPOTENCY_KEY_CONFLICT"
|
|
2567
|
+
);
|
|
2568
|
+
}
|
|
2569
|
+
return json({ success: true, data: durableView(existing.captured.jobId, "queued") }, 200);
|
|
2570
|
+
}
|
|
2571
|
+
const jobId = nextJobId();
|
|
2572
|
+
const captured = {
|
|
2573
|
+
jobId,
|
|
2574
|
+
idempotencyKey: key,
|
|
2575
|
+
alias: body.alias ? String(body.alias) : void 0,
|
|
2576
|
+
deviceId: body.deviceId ? String(body.deviceId) : void 0,
|
|
2577
|
+
peripheralId: body.peripheralId ? String(body.peripheralId) : void 0,
|
|
2578
|
+
receipt,
|
|
2579
|
+
logo: body.logo === true ? true : body.logo === false ? false : void 0
|
|
2580
|
+
};
|
|
2581
|
+
const outcome = state.edgeDurableOutcomes.shift() ?? { status: "sent_to_printer" };
|
|
2582
|
+
state.edgeDurableJobs.set(jobId, { requestHash, captured, outcome });
|
|
2583
|
+
return json({ success: true, data: durableView(jobId, "queued") }, 202);
|
|
2584
|
+
}
|
|
2585
|
+
function durableView(jobId, as) {
|
|
2586
|
+
const job = state.edgeDurableJobs.get(jobId);
|
|
2587
|
+
const createdAt = now2();
|
|
2588
|
+
const simulated = job.outcome.status !== "needs_attention" && job.outcome.simulated ? { simulated: true } : {};
|
|
2589
|
+
const base = {
|
|
2590
|
+
...simulated,
|
|
2591
|
+
jobId,
|
|
2592
|
+
attempts: as === "queued" ? 0 : 1,
|
|
2593
|
+
createdAt,
|
|
2594
|
+
expiresAt: new Date(Date.parse(createdAt) + 10 * 6e4).toISOString(),
|
|
2595
|
+
target: {
|
|
2596
|
+
deviceId: job.captured.deviceId ?? "sim-device",
|
|
2597
|
+
peripheralId: job.captured.peripheralId ?? "sim-peripheral"
|
|
2598
|
+
}
|
|
2599
|
+
};
|
|
2600
|
+
const outcome = as === "queued" ? { status: "queued" } : job.outcome;
|
|
2601
|
+
if (outcome.status === "needs_attention") {
|
|
2602
|
+
return {
|
|
2603
|
+
...base,
|
|
2604
|
+
status: "needs_attention",
|
|
2605
|
+
attention: {
|
|
2606
|
+
reason: outcome.reason,
|
|
2607
|
+
outcome: outcome.outcome,
|
|
2608
|
+
message: outcome.message ?? "Delivery stopped."
|
|
2609
|
+
}
|
|
2610
|
+
};
|
|
2611
|
+
}
|
|
2612
|
+
if (outcome.status === "sent_to_printer") {
|
|
2613
|
+
return { ...base, status: "sent_to_printer", sentToPrinterAt: createdAt };
|
|
2614
|
+
}
|
|
2615
|
+
return { ...base, status: "queued" };
|
|
2616
|
+
}
|
|
2617
|
+
function handleGetDurable(jobId) {
|
|
2618
|
+
if (!state.edgeDurableJobs.has(jobId)) return edgeFailure("Print job not found", 404);
|
|
2619
|
+
return success(durableView(jobId));
|
|
2620
|
+
}
|
|
2530
2621
|
async function handleShowDisplay(request) {
|
|
2531
2622
|
const body = await readJsonBody(request);
|
|
2532
2623
|
const captured = {
|
|
@@ -2576,6 +2667,13 @@ async function handleEdgeRequest(request, subPath) {
|
|
|
2576
2667
|
if (subPath === "/print" && method === "POST") {
|
|
2577
2668
|
return handlePrint(request);
|
|
2578
2669
|
}
|
|
2670
|
+
if (subPath === "/print-jobs" && method === "POST") {
|
|
2671
|
+
return handleEnqueue(request);
|
|
2672
|
+
}
|
|
2673
|
+
const durableMatch = subPath.match(/^\/print-jobs\/([^/]+)$/);
|
|
2674
|
+
if (durableMatch && method === "GET") {
|
|
2675
|
+
return handleGetDurable(decodeURIComponent(durableMatch[1]));
|
|
2676
|
+
}
|
|
2579
2677
|
if (subPath === "/capabilities" && method === "GET") {
|
|
2580
2678
|
return handleCapabilities(request);
|
|
2581
2679
|
}
|
package/dist/setup.mjs
CHANGED
|
@@ -68,6 +68,8 @@ var state = globalSingleton("state", () => ({
|
|
|
68
68
|
edgeDisplays: [],
|
|
69
69
|
edgeTestPrints: [],
|
|
70
70
|
edgePrintCounter: 0,
|
|
71
|
+
edgeDurableJobs: /* @__PURE__ */ new Map(),
|
|
72
|
+
edgeDurableOutcomes: [],
|
|
71
73
|
edgeDevices: [],
|
|
72
74
|
edgePeripherals: [],
|
|
73
75
|
edgeBindings: /* @__PURE__ */ new Map(),
|
|
@@ -2503,6 +2505,95 @@ async function handlePrint(request) {
|
|
|
2503
2505
|
status: "completed"
|
|
2504
2506
|
});
|
|
2505
2507
|
}
|
|
2508
|
+
var IDEMPOTENCY_KEY = /^[A-Za-z0-9_.:-]{1,128}$/;
|
|
2509
|
+
async function handleEnqueue(request) {
|
|
2510
|
+
const body = await readJsonBody(request);
|
|
2511
|
+
const key = typeof body.idempotencyKey === "string" ? body.idempotencyKey : "";
|
|
2512
|
+
if (!IDEMPOTENCY_KEY.test(key)) {
|
|
2513
|
+
return edgeFailure("idempotencyKey must be 1-128 characters of A-Z a-z 0-9 _ . : -");
|
|
2514
|
+
}
|
|
2515
|
+
if (body.openDrawer === true) {
|
|
2516
|
+
return edgeFailure("Durable receipts cannot open the cash drawer; a retry would kick it again");
|
|
2517
|
+
}
|
|
2518
|
+
if (body.copies !== void 0 && body.copies !== 1) {
|
|
2519
|
+
return edgeFailure(
|
|
2520
|
+
"Durable receipts print exactly one copy; submit a second job for another copy"
|
|
2521
|
+
);
|
|
2522
|
+
}
|
|
2523
|
+
const receipt = body.receipt ?? {};
|
|
2524
|
+
if (receipt.rawEscPos !== void 0) {
|
|
2525
|
+
return edgeFailure("rawEscPos is not supported for durable receipts");
|
|
2526
|
+
}
|
|
2527
|
+
const requestHash = JSON.stringify([
|
|
2528
|
+
body.alias,
|
|
2529
|
+
body.deviceId,
|
|
2530
|
+
body.peripheralId,
|
|
2531
|
+
receipt,
|
|
2532
|
+
body.logo
|
|
2533
|
+
]);
|
|
2534
|
+
const existing = [...state.edgeDurableJobs.values()].find(
|
|
2535
|
+
(j) => j.captured.idempotencyKey === key
|
|
2536
|
+
);
|
|
2537
|
+
if (existing) {
|
|
2538
|
+
if (existing.requestHash !== requestHash) {
|
|
2539
|
+
return edgeFailure(
|
|
2540
|
+
"This idempotencyKey was already used for a different receipt, target or options.",
|
|
2541
|
+
409,
|
|
2542
|
+
"IDEMPOTENCY_KEY_CONFLICT"
|
|
2543
|
+
);
|
|
2544
|
+
}
|
|
2545
|
+
return json({ success: true, data: durableView(existing.captured.jobId, "queued") }, 200);
|
|
2546
|
+
}
|
|
2547
|
+
const jobId = nextJobId();
|
|
2548
|
+
const captured = {
|
|
2549
|
+
jobId,
|
|
2550
|
+
idempotencyKey: key,
|
|
2551
|
+
alias: body.alias ? String(body.alias) : void 0,
|
|
2552
|
+
deviceId: body.deviceId ? String(body.deviceId) : void 0,
|
|
2553
|
+
peripheralId: body.peripheralId ? String(body.peripheralId) : void 0,
|
|
2554
|
+
receipt,
|
|
2555
|
+
logo: body.logo === true ? true : body.logo === false ? false : void 0
|
|
2556
|
+
};
|
|
2557
|
+
const outcome = state.edgeDurableOutcomes.shift() ?? { status: "sent_to_printer" };
|
|
2558
|
+
state.edgeDurableJobs.set(jobId, { requestHash, captured, outcome });
|
|
2559
|
+
return json({ success: true, data: durableView(jobId, "queued") }, 202);
|
|
2560
|
+
}
|
|
2561
|
+
function durableView(jobId, as) {
|
|
2562
|
+
const job = state.edgeDurableJobs.get(jobId);
|
|
2563
|
+
const createdAt = now2();
|
|
2564
|
+
const simulated = job.outcome.status !== "needs_attention" && job.outcome.simulated ? { simulated: true } : {};
|
|
2565
|
+
const base = {
|
|
2566
|
+
...simulated,
|
|
2567
|
+
jobId,
|
|
2568
|
+
attempts: as === "queued" ? 0 : 1,
|
|
2569
|
+
createdAt,
|
|
2570
|
+
expiresAt: new Date(Date.parse(createdAt) + 10 * 6e4).toISOString(),
|
|
2571
|
+
target: {
|
|
2572
|
+
deviceId: job.captured.deviceId ?? "sim-device",
|
|
2573
|
+
peripheralId: job.captured.peripheralId ?? "sim-peripheral"
|
|
2574
|
+
}
|
|
2575
|
+
};
|
|
2576
|
+
const outcome = as === "queued" ? { status: "queued" } : job.outcome;
|
|
2577
|
+
if (outcome.status === "needs_attention") {
|
|
2578
|
+
return {
|
|
2579
|
+
...base,
|
|
2580
|
+
status: "needs_attention",
|
|
2581
|
+
attention: {
|
|
2582
|
+
reason: outcome.reason,
|
|
2583
|
+
outcome: outcome.outcome,
|
|
2584
|
+
message: outcome.message ?? "Delivery stopped."
|
|
2585
|
+
}
|
|
2586
|
+
};
|
|
2587
|
+
}
|
|
2588
|
+
if (outcome.status === "sent_to_printer") {
|
|
2589
|
+
return { ...base, status: "sent_to_printer", sentToPrinterAt: createdAt };
|
|
2590
|
+
}
|
|
2591
|
+
return { ...base, status: "queued" };
|
|
2592
|
+
}
|
|
2593
|
+
function handleGetDurable(jobId) {
|
|
2594
|
+
if (!state.edgeDurableJobs.has(jobId)) return edgeFailure("Print job not found", 404);
|
|
2595
|
+
return success(durableView(jobId));
|
|
2596
|
+
}
|
|
2506
2597
|
async function handleShowDisplay(request) {
|
|
2507
2598
|
const body = await readJsonBody(request);
|
|
2508
2599
|
const captured = {
|
|
@@ -2552,6 +2643,13 @@ async function handleEdgeRequest(request, subPath) {
|
|
|
2552
2643
|
if (subPath === "/print" && method === "POST") {
|
|
2553
2644
|
return handlePrint(request);
|
|
2554
2645
|
}
|
|
2646
|
+
if (subPath === "/print-jobs" && method === "POST") {
|
|
2647
|
+
return handleEnqueue(request);
|
|
2648
|
+
}
|
|
2649
|
+
const durableMatch = subPath.match(/^\/print-jobs\/([^/]+)$/);
|
|
2650
|
+
if (durableMatch && method === "GET") {
|
|
2651
|
+
return handleGetDurable(decodeURIComponent(durableMatch[1]));
|
|
2652
|
+
}
|
|
2555
2653
|
if (subPath === "/capabilities" && method === "GET") {
|
|
2556
2654
|
return handleCapabilities(request);
|
|
2557
2655
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stardeck-customer-apps/testing",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.1",
|
|
4
4
|
"description": "Vitest test harness for Stardeck customer apps — in-process Postgres (PGlite) plus a control-plane simulator so the real Stardeck SDKs run unmodified in tests",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|