@tokenoftrust/storefront-runner 2.4.54 → 2.4.56

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.
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Release and hand-off gates for whole-order fulfillment.
3
+ *
4
+ * Evidence, payment, stock, refund, and cancel are rechecked from stored source
5
+ * truth. Compliance `fresh` is not evidence. A carrier account is tenant
6
+ * configuration: its absence is not a hold, and this module does not select a vendor.
7
+ */
8
+ import {
9
+ decideCommerceCommand,
10
+ paymentIsCaptured,
11
+ type CommerceEvidence,
12
+ type ComplianceRuleProfile,
13
+ type EvidenceState,
14
+ type FulfillmentState,
15
+ type LabelState,
16
+ type PaymentState,
17
+ type ProviderOutcome,
18
+ type StockState,
19
+ } from "@tot/public-runtime";
20
+ import { COMPLIANCE_RULE_PROFILES } from "@/config/compliance/rulesets";
21
+
22
+ export const RELEASE_HOLDS = [
23
+ "evidence-unknown",
24
+ "evidence-stale",
25
+ "evidence-unreadable",
26
+ "evidence-absent",
27
+ "operator-hold",
28
+ "payment-pending",
29
+ "payment-unknown",
30
+ "payment-not-captured",
31
+ "refund-pending",
32
+ "refunded",
33
+ "refund-unknown",
34
+ "cancel-pending",
35
+ "cancelled",
36
+ "stock-unknown",
37
+ "stock-unconfirmed",
38
+ "fulfillment-unknown",
39
+ ] as const;
40
+
41
+ export type ReleaseHold = (typeof RELEASE_HOLDS)[number];
42
+
43
+ export interface CarrierPolicy {
44
+ adultSignatureRequired: boolean;
45
+ prohibitedCarriers: readonly string[];
46
+ /** Null when the tenant has not configured an account. Absence is not a hold. */
47
+ carrierAccountId: string | null;
48
+ }
49
+
50
+ export interface ReadinessInput {
51
+ payment: PaymentState;
52
+ stock: StockState;
53
+ fulfillment: FulfillmentState;
54
+ label: LabelState;
55
+ evidence: CommerceEvidence;
56
+ operatorHold: boolean;
57
+ cancelPending: boolean;
58
+ providerOutcome: ProviderOutcome | null;
59
+ adultSignature: "accepted" | "refused" | "absent";
60
+ adultSignatureRequired: boolean;
61
+ }
62
+
63
+ const EVIDENCE_HOLDS: Record<EvidenceState, ReleaseHold | null> = {
64
+ fresh: null,
65
+ UNKNOWN: "evidence-unknown",
66
+ stale: "evidence-stale",
67
+ unreadable: "evidence-unreadable",
68
+ absent: "evidence-absent",
69
+ };
70
+
71
+ function add(holds: ReleaseHold[], hold: ReleaseHold | null): void {
72
+ if (hold && !holds.includes(hold)) holds.push(hold);
73
+ }
74
+
75
+ /** Holds that forbid creating a shipment intent. Compliance is not an input. */
76
+ export function releaseHolds(input: ReadinessInput): ReleaseHold[] {
77
+ const holds: ReleaseHold[] = [];
78
+ for (const field of ["verification", "tax", "risk", "stock"] as const) {
79
+ const value = input.evidence[field];
80
+ add(holds, value in EVIDENCE_HOLDS ? EVIDENCE_HOLDS[value as EvidenceState] : "evidence-unreadable");
81
+ }
82
+ if (input.operatorHold) add(holds, "operator-hold");
83
+ if (input.cancelPending) add(holds, "cancel-pending");
84
+ if (input.fulfillment === "cancelled") add(holds, "cancelled");
85
+ if (input.fulfillment === "UNKNOWN") add(holds, "fulfillment-unknown");
86
+ if (input.payment === "pending" || input.payment === "authorized") add(holds, "payment-pending");
87
+ else if (input.payment === "UNKNOWN" || input.payment === "capture_unknown") add(holds, "payment-unknown");
88
+ else if (input.payment === "refund_requested") add(holds, "refund-pending");
89
+ else if (input.payment === "refunded") add(holds, "refunded");
90
+ else if (input.payment === "refund_unknown") add(holds, "refund-unknown");
91
+ else if (input.payment === "voided") add(holds, "cancelled");
92
+ else if (!paymentIsCaptured(input.payment)) add(holds, "payment-not-captured");
93
+ if (input.stock === "UNKNOWN") add(holds, "stock-unknown");
94
+ else if (input.stock !== "confirmed") add(holds, "stock-unconfirmed");
95
+ return holds;
96
+ }
97
+
98
+ export function canRelease(input: ReadinessInput): boolean {
99
+ return input.fulfillment === "ready" && releaseHolds(input).length === 0;
100
+ }
101
+
102
+ export function handoffRefusal(input: ReadinessInput): ReleaseHold | "adult-signature" | "label" | "reconcile" | "fulfillment" | "evidence" | "payment" | "stock" | "reuse" | null {
103
+ const holds = releaseHolds(input);
104
+ if (holds.length > 0) return holds[0] ?? "fulfillment";
105
+ const decision = decideCommerceCommand({
106
+ command: "hand-off",
107
+ payment: input.payment,
108
+ stock: input.stock,
109
+ fulfillment: input.fulfillment,
110
+ label: input.label,
111
+ evidence: input.evidence,
112
+ providerOutcome: input.providerOutcome,
113
+ });
114
+ if (!decision.admit) return decision.reason === "admitted" ? "fulfillment" : decision.reason;
115
+ if (input.adultSignatureRequired && input.adultSignature !== "accepted") return "adult-signature";
116
+ return null;
117
+ }
118
+
119
+ export function carrierPolicyForTenant(tenant: {
120
+ tenant_id: string;
121
+ compliance?: { ruleProfile?: string };
122
+ carrierAccountId?: unknown;
123
+ }): CarrierPolicy {
124
+ const profile = tenant.compliance?.ruleProfile;
125
+ const build =
126
+ profile && Object.hasOwn(COMPLIANCE_RULE_PROFILES, profile)
127
+ ? COMPLIANCE_RULE_PROFILES[profile as ComplianceRuleProfile]
128
+ : undefined;
129
+ const ruleset = build ? build(tenant.tenant_id) : null;
130
+ const raw = tenant.carrierAccountId;
131
+ const carrierAccountId = typeof raw === "string" && raw.trim() !== "" ? raw.trim() : null;
132
+ const enabled = ruleset?.pactAct.enabled === true;
133
+ return {
134
+ adultSignatureRequired: enabled && ruleset.pactAct.adultSignatureRequired === true,
135
+ prohibitedCarriers: enabled ? [...ruleset.pactAct.prohibitedCarriers] : [],
136
+ carrierAccountId,
137
+ };
138
+ }
139
+
140
+ /** A supplied carrier is never a selection. USPS is refused when the policy prohibits it. */
141
+ export function carrierSelectionRefusal(
142
+ policy: CarrierPolicy,
143
+ carrier: string | undefined,
144
+ ): "carrier-prohibited" | "vendor-not-selected" | null {
145
+ if (carrier === undefined) return null;
146
+ const normalized = carrier.trim().toUpperCase();
147
+ if (normalized === "") return "vendor-not-selected";
148
+ if (policy.prohibitedCarriers.some((item) => item.toUpperCase() === normalized)) return "carrier-prohibited";
149
+ return "vendor-not-selected";
150
+ }
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Tenant-scoped SQL seam for scrappy commerce tables.
3
+ *
4
+ * Batch phase, holds, packing slips, and label intents are rows in
5
+ * `scrappy_shipment_audit` (append-only). The commerce migration has no batch
6
+ * table; this store does not create one.
7
+ */
8
+ import type { D1Like } from "../d1/catalog.js";
9
+
10
+ export interface SqlStatement {
11
+ sql: string;
12
+ params: readonly unknown[];
13
+ }
14
+
15
+ export interface FulfillmentDatabase {
16
+ get<T extends Record<string, unknown>>(sql: string, params?: readonly unknown[]): Promise<T | undefined>;
17
+ all<T extends Record<string, unknown>>(sql: string, params?: readonly unknown[]): Promise<T[]>;
18
+ /** One transaction. A unique-constraint loss returns zeros so the caller can re-read. */
19
+ batch(statements: readonly SqlStatement[]): Promise<number[]>;
20
+ }
21
+
22
+ interface SqliteStatement {
23
+ all(...params: unknown[]): Array<Record<string, unknown>>;
24
+ get(...params: unknown[]): Record<string, unknown> | undefined;
25
+ run(...params: unknown[]): { changes?: number | bigint };
26
+ }
27
+
28
+ export interface SqliteDatabase {
29
+ prepare(sql: string): SqliteStatement;
30
+ exec(sql: string): void;
31
+ }
32
+
33
+ function changesOf(result: { changes?: number | bigint } | undefined): number {
34
+ const changes = result?.changes ?? 0;
35
+ return typeof changes === "bigint" ? Number(changes) : changes;
36
+ }
37
+
38
+ function lostRace(error: unknown): boolean {
39
+ return error instanceof Error && error.message.includes("UNIQUE constraint failed");
40
+ }
41
+
42
+ export function fulfillmentDbFromSqlite(db: SqliteDatabase): FulfillmentDatabase {
43
+ db.exec("PRAGMA foreign_keys = ON");
44
+ let chain: Promise<unknown> = Promise.resolve();
45
+ const enqueue = <T>(fn: () => T): Promise<T> => {
46
+ const run = chain.then(
47
+ () => fn(),
48
+ () => fn(),
49
+ );
50
+ chain = run.then(
51
+ () => undefined,
52
+ () => undefined,
53
+ );
54
+ return run;
55
+ };
56
+ return {
57
+ get: (sql, params = []) => enqueue(() => db.prepare(sql).get(...params) as never),
58
+ all: (sql, params = []) => enqueue(() => db.prepare(sql).all(...params) as never),
59
+ batch: (statements) =>
60
+ enqueue(() => {
61
+ if (statements.length === 0) return [];
62
+ db.exec("BEGIN IMMEDIATE");
63
+ const changes: number[] = [];
64
+ try {
65
+ for (const statement of statements) {
66
+ changes.push(changesOf(db.prepare(statement.sql).run(...statement.params)));
67
+ }
68
+ db.exec("COMMIT");
69
+ return changes;
70
+ } catch (error) {
71
+ try {
72
+ db.exec("ROLLBACK");
73
+ } catch {
74
+ /* the transaction is already closed */
75
+ }
76
+ if (lostRace(error)) return statements.map(() => 0);
77
+ throw new Error("storage", { cause: error });
78
+ }
79
+ }),
80
+ };
81
+ }
82
+
83
+ export function fulfillmentDbFromD1(db: D1Like): FulfillmentDatabase {
84
+ return {
85
+ async get(sql, params = []) {
86
+ const row = await db.prepare(sql).bind(...params).first();
87
+ return (row ?? undefined) as never;
88
+ },
89
+ async all(sql, params = []) {
90
+ const result = await db.prepare(sql).bind(...params).all();
91
+ return (result.results ?? []) as never;
92
+ },
93
+ async batch(statements) {
94
+ if (statements.length === 0) return [];
95
+ if (!db.batch) throw new Error("storage");
96
+ try {
97
+ const results = await db.batch(statements.map((statement) => db.prepare(statement.sql).bind(...statement.params)));
98
+ return results.map((result) => {
99
+ if (!result || typeof result !== "object") return 0;
100
+ const meta = Reflect.get(result, "meta") as { changes?: number } | undefined;
101
+ return Number(meta?.changes ?? 0);
102
+ });
103
+ } catch (error) {
104
+ if (lostRace(error)) return statements.map(() => 0);
105
+ throw new Error("storage", { cause: error });
106
+ }
107
+ },
108
+ };
109
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Authenticated owner API for whole-order batch claim and batch read.
3
+ */
4
+ import type { APIContext } from "astro";
5
+ import { requireAdminApiEntry } from "@/lib/auth/adminApiGuard";
6
+ import {
7
+ fulfillmentResultResponse,
8
+ isOpaqueId,
9
+ openFulfillment,
10
+ parseFulfillmentBody,
11
+ readJsonBody,
12
+ } from "@/lib/fulfillment/fulfillmentService";
13
+
14
+ export const prerender = false;
15
+
16
+ function ownerRefusal(status: number, reason: string): Response {
17
+ return new Response(JSON.stringify({ error: reason }), {
18
+ status,
19
+ headers: {
20
+ "content-type": "application/json; charset=utf-8",
21
+ "cache-control": "no-store",
22
+ "x-robots-tag": "noindex, nofollow",
23
+ },
24
+ });
25
+ }
26
+
27
+ async function requireOwner(context: APIContext): Promise<Response | null> {
28
+ const access = await requireAdminApiEntry(context, { requireOwner: true });
29
+ if (access.ok) return null;
30
+ return ownerRefusal(access.status, access.reason);
31
+ }
32
+
33
+ export async function GET(context: APIContext): Promise<Response> {
34
+ const refused = await requireOwner(context);
35
+ if (refused) return refused;
36
+ const opened = await openFulfillment(context);
37
+ if (!opened.ok) return opened.response;
38
+ const batchId = new URL(context.request.url).searchParams.get("batchId") ?? "";
39
+ if (!isOpaqueId(batchId)) return fulfillmentResultResponse({ ok: false, code: "not-found" }, () => ({}));
40
+ return fulfillmentResultResponse(await opened.service.batch(opened.actor, batchId), (batch) => batch);
41
+ }
42
+
43
+ export async function POST(context: APIContext): Promise<Response> {
44
+ const refused = await requireOwner(context);
45
+ if (refused) return refused;
46
+ const raw = await readJsonBody(context.request);
47
+ if (!raw.ok) return fulfillmentResultResponse(raw, () => ({}));
48
+ const parsed = parseFulfillmentBody(raw.value);
49
+ if (!parsed.ok) return fulfillmentResultResponse(parsed, () => ({}));
50
+ if (!parsed.value.orderIds || parsed.value.orderIds.length === 0) {
51
+ return fulfillmentResultResponse({ ok: false, code: "malformed" }, () => ({}));
52
+ }
53
+ const opened = await openFulfillment(context, parsed.value.tenantId ?? null);
54
+ if (!opened.ok) return opened.response;
55
+ return fulfillmentResultResponse(
56
+ await opened.service.claim(opened.actor, parsed.value.orderIds, parsed.value.expectedVersions),
57
+ (batch) => batch,
58
+ );
59
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Owner-only retrieval of a stored ship-to address.
3
+ * The read runs only after the owner gate admits the caller.
4
+ */
5
+ import type { APIContext } from "astro";
6
+ import { requireAdminApiEntry } from "@/lib/auth/adminApiGuard";
7
+ import {
8
+ fulfillmentResultResponse,
9
+ isOpaqueId,
10
+ openFulfillment,
11
+ } from "@/lib/fulfillment/fulfillmentService";
12
+
13
+ export const prerender = false;
14
+
15
+ export async function GET(context: APIContext): Promise<Response> {
16
+ const access = await requireAdminApiEntry(context, { requireOwner: true });
17
+ if (!access.ok) {
18
+ return new Response(JSON.stringify({ error: access.reason }), {
19
+ status: access.status,
20
+ headers: {
21
+ "content-type": "application/json; charset=utf-8",
22
+ "cache-control": "no-store",
23
+ "x-robots-tag": "noindex, nofollow",
24
+ },
25
+ });
26
+ }
27
+ const orderId = context.params.id ?? "";
28
+ if (!isOpaqueId(orderId)) return fulfillmentResultResponse({ ok: false, code: "not-found" }, () => ({}));
29
+ const opened = await openFulfillment(context);
30
+ if (!opened.ok) return opened.response;
31
+ return fulfillmentResultResponse(await opened.service.readAddress(opened.actor, orderId), (address) => address);
32
+ }
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Authenticated owner API for one order's fulfillment commands.
3
+ * The response is state codes and the fulfillment view. It never returns an address.
4
+ */
5
+ import type { APIContext } from "astro";
6
+ import { requireAdminApiEntry } from "@/lib/auth/adminApiGuard";
7
+ import {
8
+ fulfillmentResultResponse,
9
+ isOpaqueId,
10
+ openFulfillment,
11
+ parseFulfillmentBody,
12
+ readJsonBody,
13
+ type FulfillmentCommandOptions,
14
+ type LabelOutcomeInput,
15
+ } from "@/lib/fulfillment/fulfillmentService";
16
+
17
+ export const prerender = false;
18
+
19
+ function ownerRefusal(status: number, reason: string): Response {
20
+ return new Response(JSON.stringify({ error: reason }), {
21
+ status,
22
+ headers: {
23
+ "content-type": "application/json; charset=utf-8",
24
+ "cache-control": "no-store",
25
+ "x-robots-tag": "noindex, nofollow",
26
+ },
27
+ });
28
+ }
29
+
30
+ async function requireOwner(context: APIContext): Promise<Response | null> {
31
+ const access = await requireAdminApiEntry(context, { requireOwner: true });
32
+ if (access.ok) return null;
33
+ return ownerRefusal(access.status, access.reason);
34
+ }
35
+
36
+ const ACTIONS = new Set([
37
+ "pick",
38
+ "pack",
39
+ "release",
40
+ "hold",
41
+ "clear-hold",
42
+ "cancel",
43
+ "refund",
44
+ "label-outcome",
45
+ "hand-off",
46
+ "return",
47
+ ]);
48
+
49
+ function fail(code: string): Response {
50
+ return fulfillmentResultResponse({ ok: false, code }, () => ({}));
51
+ }
52
+
53
+ export async function GET(context: APIContext): Promise<Response> {
54
+ const refused = await requireOwner(context);
55
+ if (refused) return refused;
56
+ const orderId = context.params.id ?? "";
57
+ if (!isOpaqueId(orderId)) return fail("not-found");
58
+ const opened = await openFulfillment(context);
59
+ if (!opened.ok) return opened.response;
60
+ return fulfillmentResultResponse(await opened.service.get(opened.actor, orderId), (view) => view);
61
+ }
62
+
63
+ export async function POST(context: APIContext): Promise<Response> {
64
+ const refused = await requireOwner(context);
65
+ if (refused) return refused;
66
+ const orderId = context.params.id ?? "";
67
+ if (!isOpaqueId(orderId)) return fail("not-found");
68
+ const raw = await readJsonBody(context.request);
69
+ if (!raw.ok) return fulfillmentResultResponse(raw, () => ({}));
70
+ const parsed = parseFulfillmentBody(raw.value);
71
+ if (!parsed.ok) return fulfillmentResultResponse(parsed, () => ({}));
72
+ if (!ACTIONS.has(parsed.value.action)) return fail("malformed");
73
+ const opened = await openFulfillment(context, parsed.value.tenantId ?? null);
74
+ if (!opened.ok) return opened.response;
75
+ const { service, actor } = opened;
76
+ const options: FulfillmentCommandOptions = {
77
+ ...(parsed.value.expectedVersion !== undefined ? { expectedVersion: parsed.value.expectedVersion } : {}),
78
+ ...(parsed.value.scope !== undefined ? { scope: parsed.value.scope } : {}),
79
+ ...(parsed.value.carrier !== undefined ? { carrier: parsed.value.carrier } : {}),
80
+ ...(parsed.value.lines !== undefined ? { lines: parsed.value.lines } : {}),
81
+ };
82
+ switch (parsed.value.action) {
83
+ case "pick":
84
+ return fulfillmentResultResponse(await service.pick(actor, orderId, options), (view) => view);
85
+ case "pack":
86
+ return fulfillmentResultResponse(await service.pack(actor, orderId, options), (view) => view);
87
+ case "release":
88
+ return fulfillmentResultResponse(await service.release(actor, orderId, options), (view) => view);
89
+ case "hold":
90
+ return fulfillmentResultResponse(await service.hold(actor, orderId, options), (view) => view);
91
+ case "clear-hold":
92
+ return fulfillmentResultResponse(await service.clearOperatorHold(actor, orderId, options), (view) => view);
93
+ case "cancel":
94
+ return fulfillmentResultResponse(await service.requestCancel(actor, orderId, options), (view) => view);
95
+ case "refund":
96
+ return fulfillmentResultResponse(await service.requestRefund(actor, orderId, options), (view) => view);
97
+ case "hand-off":
98
+ return fulfillmentResultResponse(await service.handOff(actor, orderId, options), (view) => view);
99
+ case "return":
100
+ return fulfillmentResultResponse(service.createReturn(actor, orderId), () => ({}));
101
+ case "label-outcome": {
102
+ const outcome = parsed.value.outcome;
103
+ const adultSignature = parsed.value.adultSignature;
104
+ if (outcome !== "purchased" && outcome !== "unknown" && outcome !== "voided") return fail("malformed");
105
+ if (adultSignature !== "accepted" && adultSignature !== "refused" && adultSignature !== "absent") return fail("malformed");
106
+ const input: LabelOutcomeInput = {
107
+ outcome,
108
+ adultSignature,
109
+ ...(parsed.value.carrier !== undefined ? { carrier: parsed.value.carrier } : {}),
110
+ ...(parsed.value.expectedVersion !== undefined ? { expectedVersion: parsed.value.expectedVersion } : {}),
111
+ };
112
+ return fulfillmentResultResponse(await service.applyLabelOutcome(actor, orderId, input), (view) => view);
113
+ }
114
+ default:
115
+ return fail("malformed");
116
+ }
117
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokenoftrust/storefront-runner",
3
- "version": "2.4.54",
3
+ "version": "2.4.56",
4
4
  "license": "SEE LICENSE IN LICENSE",
5
5
  "description": "World-shareable storefront runner: multi-tenant renderer on Astro/Cloudflare. No control plane.",
6
6
  "packageManager": "pnpm@11.9.0",
@@ -87,7 +87,7 @@ importers:
87
87
  version: 4.12.1(playwright-core@1.61.1)
88
88
  '@cloudflare/vitest-pool-workers':
89
89
  specifier: 0.12.21
90
- version: 0.12.21(@vitest/runner@2.1.9)(@vitest/snapshot@2.1.9)(vitest@2.1.8(@types/node@24.13.3)(happy-dom@20.10.6)(lightningcss@1.32.0))
90
+ version: 0.12.21(@vitest/runner@2.1.9)(@vitest/snapshot@2.1.9)(vitest@2.1.8(@types/node@24.13.3)(happy-dom@20.11.2)(lightningcss@1.32.0))
91
91
  '@playwright/test':
92
92
  specifier: ^1.61.1
93
93
  version: 1.61.1
@@ -106,6 +106,9 @@ importers:
106
106
  esbuild:
107
107
  specifier: 0.28.1
108
108
  version: 0.28.1
109
+ happy-dom:
110
+ specifier: 20.11.2
111
+ version: 20.11.2
109
112
  tsx:
110
113
  specifier: ^4.22.4
111
114
  version: 4.22.4
@@ -114,7 +117,7 @@ importers:
114
117
  version: 5.9.3
115
118
  vitest:
116
119
  specifier: 2.1.8
117
- version: 2.1.8(@types/node@24.13.3)(happy-dom@20.10.6)(lightningcss@1.32.0)
120
+ version: 2.1.8(@types/node@24.13.3)(happy-dom@20.11.2)(lightningcss@1.32.0)
118
121
  wrangler:
119
122
  specifier: ^4.105.0
120
123
  version: 4.105.0
@@ -139,7 +142,7 @@ importers:
139
142
  version: 5.9.3
140
143
  vitest:
141
144
  specifier: ^2.1.8
142
- version: 2.1.8(@types/node@24.13.3)(happy-dom@20.10.6)(lightningcss@1.32.0)
145
+ version: 2.1.8(@types/node@24.13.3)(happy-dom@20.11.2)(lightningcss@1.32.0)
143
146
 
144
147
  packages:
145
148
 
@@ -2789,8 +2792,8 @@ packages:
2789
2792
  h3@1.15.11:
2790
2793
  resolution: {integrity: sha512-L3THSe2MPeBwgIZVSH5zLdBBU90TOxarvhK9d04IDY2AmVS8j2Jz2LIWtwsGOU3lu2I5jCN7FNvVfY2+XyF+mg==}
2791
2794
 
2792
- happy-dom@20.10.6:
2793
- resolution: {integrity: sha512-6QD0ilzDDt93tX44y8tbmZdAcdTRYDhUP+Asgi6pC8Pp5IA3cvaZGyoVN/EGtlq9ziT65iPuBBn3ASLr6hCgVw==}
2795
+ happy-dom@20.11.2:
2796
+ resolution: {integrity: sha512-7MB+bJLkxu3SowAfBJbjW+c55kNz5tkR45gu2qzrxznezhLeN5YIlJbwUgSzlGc+qWoZ8Ykg71H5ezz69xixrw==}
2794
2797
  engines: {node: '>=20.0.0'}
2795
2798
 
2796
2799
  has-flag@4.0.0:
@@ -4437,14 +4440,14 @@ snapshots:
4437
4440
  - utf-8-validate
4438
4441
  - workerd
4439
4442
 
4440
- '@cloudflare/vitest-pool-workers@0.12.21(@vitest/runner@2.1.9)(@vitest/snapshot@2.1.9)(vitest@2.1.8(@types/node@24.13.3)(happy-dom@20.10.6)(lightningcss@1.32.0))':
4443
+ '@cloudflare/vitest-pool-workers@0.12.21(@vitest/runner@2.1.9)(@vitest/snapshot@2.1.9)(vitest@2.1.8(@types/node@24.13.3)(happy-dom@20.11.2)(lightningcss@1.32.0))':
4441
4444
  dependencies:
4442
4445
  '@vitest/runner': 2.1.9
4443
4446
  '@vitest/snapshot': 2.1.9
4444
4447
  cjs-module-lexer: 1.4.3
4445
4448
  esbuild: 0.27.3
4446
4449
  miniflare: 4.20260310.0
4447
- vitest: 2.1.8(@types/node@24.13.3)(happy-dom@20.10.6)(lightningcss@1.32.0)
4450
+ vitest: 2.1.8(@types/node@24.13.3)(happy-dom@20.11.2)(lightningcss@1.32.0)
4448
4451
  wrangler: 4.72.0
4449
4452
  transitivePeerDependencies:
4450
4453
  - '@cloudflare/workers-types'
@@ -5372,11 +5375,11 @@ snapshots:
5372
5375
  '@tiptap/extension-text': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))
5373
5376
  '@tiptap/extension-text-style': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))
5374
5377
  '@tiptap/extension-underline': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))
5375
- '@tiptap/html': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)(happy-dom@20.10.6)
5378
+ '@tiptap/html': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)(happy-dom@20.11.2)
5376
5379
  '@tiptap/pm': 3.27.1
5377
5380
  '@tiptap/suggestion': 3.27.1(@floating-ui/dom@1.7.6)(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)
5378
5381
  emojibase: 17.0.0
5379
- happy-dom: 20.10.6
5382
+ happy-dom: 20.11.2
5380
5383
  markdown-it: 14.2.0
5381
5384
  transitivePeerDependencies:
5382
5385
  - '@floating-ui/dom'
@@ -5558,11 +5561,11 @@ snapshots:
5558
5561
  dependencies:
5559
5562
  '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1)
5560
5563
 
5561
- '@tiptap/html@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)(happy-dom@20.10.6)':
5564
+ '@tiptap/html@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)(happy-dom@20.11.2)':
5562
5565
  dependencies:
5563
5566
  '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1)
5564
5567
  '@tiptap/pm': 3.27.1
5565
- happy-dom: 20.10.6
5568
+ happy-dom: 20.11.2
5566
5569
 
5567
5570
  '@tiptap/pm@3.27.1':
5568
5571
  dependencies:
@@ -6349,7 +6352,7 @@ snapshots:
6349
6352
  ufo: 1.6.4
6350
6353
  uncrypto: 0.1.3
6351
6354
 
6352
- happy-dom@20.10.6:
6355
+ happy-dom@20.11.2:
6353
6356
  dependencies:
6354
6357
  '@types/node': 24.13.3
6355
6358
  '@types/whatwg-mimetype': 3.0.2
@@ -7330,7 +7333,7 @@ snapshots:
7330
7333
  optionalDependencies:
7331
7334
  vite: 8.1.0(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0)
7332
7335
 
7333
- vitest@2.1.8(@types/node@24.13.3)(happy-dom@20.10.6)(lightningcss@1.32.0):
7336
+ vitest@2.1.8(@types/node@24.13.3)(happy-dom@20.11.2)(lightningcss@1.32.0):
7334
7337
  dependencies:
7335
7338
  '@vitest/expect': 2.1.8
7336
7339
  '@vitest/mocker': 2.1.8(vite@5.4.21(@types/node@24.13.3)(lightningcss@1.32.0))
@@ -7354,7 +7357,7 @@ snapshots:
7354
7357
  why-is-node-running: 2.3.0
7355
7358
  optionalDependencies:
7356
7359
  '@types/node': 24.13.3
7357
- happy-dom: 20.10.6
7360
+ happy-dom: 20.11.2
7358
7361
  transitivePeerDependencies:
7359
7362
  - less
7360
7363
  - lightningcss