@stardeck-customer-apps/testing 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/SKILL.md CHANGED
@@ -101,6 +101,15 @@ expect(app.storage.latest()?.filename).toBe("note.txt");
101
101
  const integrations = createIntegrationsClient();
102
102
  await integrations.line.push("U123", { type: "text", text: "Your order shipped" });
103
103
  expect(app.messages.channel("line").to("U123")[0].body.text).toMatch(/shipped/i);
104
+
105
+ // Receipt print + display
106
+ import { createEdgeClient } from "@stardeck-customer-apps/edge-sdk/server";
107
+
108
+ const edge = createEdgeClient();
109
+ await edge.print({ receipt: { total: 42 }, alias: "station-1", openDrawer: true });
110
+ expect(app.edge.latestPrint()?.receipt.total).toBe(42);
111
+ await edge.showDisplay({ alias: "customer-screen", url: "https://app.example.com/kiosk" });
112
+ expect(app.edge.latestDisplay()?.action).toBe("show");
104
113
  ```
105
114
 
106
115
  ## API
@@ -131,6 +140,9 @@ expect(app.messages.channel("line").to("U123")[0].body.text).toMatch(/shipped/i)
131
140
  - `app.storage` — uploads through storage-sdk: `.uploads`, `.latest()`, `.count`, `.clear()`.
132
141
  - `app.messages` — outbound Slack/LINE/Facebook sends:
133
142
  `.all()`, `.latest()`, `.to(recipient)`, `.channel("line")`, `.count`, `.clear()`.
143
+ - `app.edge` — edge print/display/bindings from edge-sdk: `.prints`, `.displays`,
144
+ `.testPrints`, `.latestPrint()`, `.latestDisplay()`, `.bindings`,
145
+ `.seedDevices()`, `.seedPeripherals()`, `.seedBindings()`, `.count`, `.clear()`.
134
146
  - `app.query(sql, params?)` / `app.db` — direct database access for
135
147
  assertions and seeding.
136
148
  - `callRoute(handler, opts)` — invoke an App Router route handler with a real
@@ -161,6 +173,9 @@ expect(app.messages.channel("line").to("U123")[0].body.text).toMatch(/shipped/i)
161
173
  in `app.storage` against the simulated `STORAGE_URL` host.
162
174
  - `client.slack` / `client.line` / `client.facebook` send endpoints — captured
163
175
  in `app.messages`.
176
+ - `createEdgeClient()` (print, pair/unpair, list devices/peripherals, show/clear
177
+ display, test print) — captured in `app.edge`; seed devices/peripherals for
178
+ pairing pickers via `app.edge.seedDevices()` / `seedPeripherals()`.
164
179
  - `next/headers` (`headers()`/`cookies()`) inside handlers under `callRoute`.
165
180
 
166
181
  ## Rules
package/dist/index.d.mts CHANGED
@@ -184,6 +184,106 @@ interface TestMessages {
184
184
  clear(): void;
185
185
  get count(): number;
186
186
  }
187
+ interface ReceiptPayload {
188
+ header?: {
189
+ lines: string[];
190
+ };
191
+ items?: Array<{
192
+ name: string;
193
+ quantity: number;
194
+ unitPrice: number;
195
+ total: number;
196
+ note?: string;
197
+ }>;
198
+ subtotal?: number;
199
+ tax?: number;
200
+ discount?: number;
201
+ total?: number;
202
+ payments?: Array<{
203
+ method: string;
204
+ amount: number;
205
+ }>;
206
+ footer?: {
207
+ lines: string[];
208
+ };
209
+ barcode?: {
210
+ type: "qr" | "code128";
211
+ data: string;
212
+ };
213
+ rawEscPos?: string;
214
+ }
215
+ interface DeviceInfo {
216
+ id: string;
217
+ displayName: string;
218
+ status: "pairing_pending" | "online" | "offline" | "decommissioned";
219
+ isDefault: boolean;
220
+ lastHeartbeatAt: string | null;
221
+ }
222
+ interface PeripheralInfo {
223
+ id: string;
224
+ displayName: string | null;
225
+ driver: string | null;
226
+ transport: "usb_lp" | "serial" | "tcp" | "usb_raw" | "display";
227
+ connected: boolean;
228
+ lastSeenAt: string | null;
229
+ device: {
230
+ id: string;
231
+ displayName: string;
232
+ status: DeviceInfo["status"];
233
+ };
234
+ }
235
+ type BindingState = "ok" | "peripheral_missing" | "grant_revoked";
236
+ interface BindingInfo {
237
+ alias: string;
238
+ state: BindingState;
239
+ peripheral: {
240
+ id: string;
241
+ displayName: string | null;
242
+ driver: string | null;
243
+ connected: boolean;
244
+ } | null;
245
+ device: {
246
+ id: string;
247
+ displayName: string;
248
+ status: DeviceInfo["status"];
249
+ } | null;
250
+ updatedAt: string;
251
+ }
252
+ interface CapturedPrint {
253
+ jobId: string;
254
+ deploymentId: string;
255
+ alias?: string;
256
+ deviceId?: string;
257
+ peripheralId?: string;
258
+ receipt: ReceiptPayload;
259
+ openDrawer: boolean;
260
+ logo?: boolean;
261
+ copies: number;
262
+ }
263
+ interface CapturedDisplay {
264
+ action: "show" | "clear";
265
+ alias?: string;
266
+ peripheralId?: string;
267
+ url?: string;
268
+ }
269
+ interface CapturedTestPrint {
270
+ alias?: string;
271
+ deviceId?: string;
272
+ peripheralId?: string;
273
+ }
274
+ interface TestEdge {
275
+ get prints(): CapturedPrint[];
276
+ get displays(): CapturedDisplay[];
277
+ get testPrints(): CapturedTestPrint[];
278
+ latestPrint(): CapturedPrint | undefined;
279
+ latestDisplay(): CapturedDisplay | undefined;
280
+ get bindings(): BindingInfo[];
281
+ seedDevices(devices: DeviceInfo[]): void;
282
+ seedPeripherals(peripherals: PeripheralInfo[]): void;
283
+ seedBindings(bindings: BindingInfo[]): void;
284
+ clear(): void;
285
+ get count(): number;
286
+ }
187
287
  interface TestAppOptions {
188
288
  /**
189
289
  * Path to the schema.sql snapshot generated by
@@ -219,6 +319,8 @@ interface TestApp {
219
319
  storage: TestStorage;
220
320
  /** Captured outbound messages from integrations-sdk messaging channels. */
221
321
  messages: TestMessages;
322
+ /** Captured edge print/display ops and peripheral bindings from edge-sdk. */
323
+ edge: TestEdge;
222
324
  /** Convenience for raw SQL: `app.query("SELECT ...", [param])`. */
223
325
  query<T = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<T[]>;
224
326
  /**
@@ -273,4 +375,4 @@ declare const TEST_ENV_DEFAULTS: {
273
375
  /** Default location of the DDL snapshot written by `generate-types`. */
274
376
  declare const DEFAULT_SCHEMA_PATH = "./src/generated/data-store-schema.sql";
275
377
 
276
- export { CONTROL_PLANE_TEST_URL, type CallRouteOptions, type CapturedCheckout, type CapturedEmail, type CapturedIdentity, type CapturedIdentityLink, type CapturedMessage, type CapturedUpload, DATA_STORE_TEST_HOST, DEFAULT_SCHEMA_PATH, STORAGE_TEST_HOST, STORAGE_TEST_URL, type SessionTokens, TEST_ENV_DEFAULTS, type TestApp, type TestAppOptions, type TestDirectory, type TestInbox, type TestMessages, type TestPayments, type TestStorage, type TestUser, WORKFLOW_NAME_PREFIX, callRoute, createTestApp, describeWorkflow, parseWorkflowName };
378
+ export { type BindingInfo, CONTROL_PLANE_TEST_URL, type CallRouteOptions, type CapturedCheckout, type CapturedDisplay, type CapturedEmail, type CapturedIdentity, type CapturedIdentityLink, type CapturedMessage, type CapturedPrint, type CapturedTestPrint, type CapturedUpload, DATA_STORE_TEST_HOST, DEFAULT_SCHEMA_PATH, type DeviceInfo, type PeripheralInfo, STORAGE_TEST_HOST, STORAGE_TEST_URL, type SessionTokens, TEST_ENV_DEFAULTS, type TestApp, type TestAppOptions, type TestDirectory, type TestEdge, type TestInbox, type TestMessages, type TestPayments, type TestStorage, type TestUser, WORKFLOW_NAME_PREFIX, callRoute, createTestApp, describeWorkflow, parseWorkflowName };
package/dist/index.d.ts CHANGED
@@ -184,6 +184,106 @@ interface TestMessages {
184
184
  clear(): void;
185
185
  get count(): number;
186
186
  }
187
+ interface ReceiptPayload {
188
+ header?: {
189
+ lines: string[];
190
+ };
191
+ items?: Array<{
192
+ name: string;
193
+ quantity: number;
194
+ unitPrice: number;
195
+ total: number;
196
+ note?: string;
197
+ }>;
198
+ subtotal?: number;
199
+ tax?: number;
200
+ discount?: number;
201
+ total?: number;
202
+ payments?: Array<{
203
+ method: string;
204
+ amount: number;
205
+ }>;
206
+ footer?: {
207
+ lines: string[];
208
+ };
209
+ barcode?: {
210
+ type: "qr" | "code128";
211
+ data: string;
212
+ };
213
+ rawEscPos?: string;
214
+ }
215
+ interface DeviceInfo {
216
+ id: string;
217
+ displayName: string;
218
+ status: "pairing_pending" | "online" | "offline" | "decommissioned";
219
+ isDefault: boolean;
220
+ lastHeartbeatAt: string | null;
221
+ }
222
+ interface PeripheralInfo {
223
+ id: string;
224
+ displayName: string | null;
225
+ driver: string | null;
226
+ transport: "usb_lp" | "serial" | "tcp" | "usb_raw" | "display";
227
+ connected: boolean;
228
+ lastSeenAt: string | null;
229
+ device: {
230
+ id: string;
231
+ displayName: string;
232
+ status: DeviceInfo["status"];
233
+ };
234
+ }
235
+ type BindingState = "ok" | "peripheral_missing" | "grant_revoked";
236
+ interface BindingInfo {
237
+ alias: string;
238
+ state: BindingState;
239
+ peripheral: {
240
+ id: string;
241
+ displayName: string | null;
242
+ driver: string | null;
243
+ connected: boolean;
244
+ } | null;
245
+ device: {
246
+ id: string;
247
+ displayName: string;
248
+ status: DeviceInfo["status"];
249
+ } | null;
250
+ updatedAt: string;
251
+ }
252
+ interface CapturedPrint {
253
+ jobId: string;
254
+ deploymentId: string;
255
+ alias?: string;
256
+ deviceId?: string;
257
+ peripheralId?: string;
258
+ receipt: ReceiptPayload;
259
+ openDrawer: boolean;
260
+ logo?: boolean;
261
+ copies: number;
262
+ }
263
+ interface CapturedDisplay {
264
+ action: "show" | "clear";
265
+ alias?: string;
266
+ peripheralId?: string;
267
+ url?: string;
268
+ }
269
+ interface CapturedTestPrint {
270
+ alias?: string;
271
+ deviceId?: string;
272
+ peripheralId?: string;
273
+ }
274
+ interface TestEdge {
275
+ get prints(): CapturedPrint[];
276
+ get displays(): CapturedDisplay[];
277
+ get testPrints(): CapturedTestPrint[];
278
+ latestPrint(): CapturedPrint | undefined;
279
+ latestDisplay(): CapturedDisplay | undefined;
280
+ get bindings(): BindingInfo[];
281
+ seedDevices(devices: DeviceInfo[]): void;
282
+ seedPeripherals(peripherals: PeripheralInfo[]): void;
283
+ seedBindings(bindings: BindingInfo[]): void;
284
+ clear(): void;
285
+ get count(): number;
286
+ }
187
287
  interface TestAppOptions {
188
288
  /**
189
289
  * Path to the schema.sql snapshot generated by
@@ -219,6 +319,8 @@ interface TestApp {
219
319
  storage: TestStorage;
220
320
  /** Captured outbound messages from integrations-sdk messaging channels. */
221
321
  messages: TestMessages;
322
+ /** Captured edge print/display ops and peripheral bindings from edge-sdk. */
323
+ edge: TestEdge;
222
324
  /** Convenience for raw SQL: `app.query("SELECT ...", [param])`. */
223
325
  query<T = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<T[]>;
224
326
  /**
@@ -273,4 +375,4 @@ declare const TEST_ENV_DEFAULTS: {
273
375
  /** Default location of the DDL snapshot written by `generate-types`. */
274
376
  declare const DEFAULT_SCHEMA_PATH = "./src/generated/data-store-schema.sql";
275
377
 
276
- export { CONTROL_PLANE_TEST_URL, type CallRouteOptions, type CapturedCheckout, type CapturedEmail, type CapturedIdentity, type CapturedIdentityLink, type CapturedMessage, type CapturedUpload, DATA_STORE_TEST_HOST, DEFAULT_SCHEMA_PATH, STORAGE_TEST_HOST, STORAGE_TEST_URL, type SessionTokens, TEST_ENV_DEFAULTS, type TestApp, type TestAppOptions, type TestDirectory, type TestInbox, type TestMessages, type TestPayments, type TestStorage, type TestUser, WORKFLOW_NAME_PREFIX, callRoute, createTestApp, describeWorkflow, parseWorkflowName };
378
+ export { type BindingInfo, CONTROL_PLANE_TEST_URL, type CallRouteOptions, type CapturedCheckout, type CapturedDisplay, type CapturedEmail, type CapturedIdentity, type CapturedIdentityLink, type CapturedMessage, type CapturedPrint, type CapturedTestPrint, type CapturedUpload, DATA_STORE_TEST_HOST, DEFAULT_SCHEMA_PATH, type DeviceInfo, type PeripheralInfo, STORAGE_TEST_HOST, STORAGE_TEST_URL, type SessionTokens, TEST_ENV_DEFAULTS, type TestApp, type TestAppOptions, type TestDirectory, type TestEdge, type TestInbox, type TestMessages, type TestPayments, type TestStorage, type TestUser, WORKFLOW_NAME_PREFIX, callRoute, createTestApp, describeWorkflow, parseWorkflowName };
package/dist/index.js CHANGED
@@ -110,6 +110,13 @@ var state = globalSingleton("state", () => ({
110
110
  storageFiles: /* @__PURE__ */ new Map(),
111
111
  presignPending: /* @__PURE__ */ new Map(),
112
112
  messages: [],
113
+ edgePrints: [],
114
+ edgeDisplays: [],
115
+ edgeTestPrints: [],
116
+ edgePrintCounter: 0,
117
+ edgeDevices: [],
118
+ edgePeripherals: [],
119
+ edgeBindings: /* @__PURE__ */ new Map(),
113
120
  allowNetwork: false
114
121
  }));
115
122
  function requireDb() {
@@ -172,8 +179,8 @@ function verifyDeploymentAuthHeader(secret, header) {
172
179
  return null;
173
180
  }
174
181
  if (payload.type !== "deployment-request") return null;
175
- const now2 = Math.floor(Date.now() / 1e3);
176
- if (Math.abs(now2 - payload.timestamp) > TIMESTAMP_TOLERANCE_SECONDS) return null;
182
+ const now3 = Math.floor(Date.now() / 1e3);
183
+ if (Math.abs(now3 - payload.timestamp) > TIMESTAMP_TOLERANCE_SECONDS) return null;
177
184
  return payload;
178
185
  }
179
186
 
@@ -1475,6 +1482,224 @@ function createMessages() {
1475
1482
  };
1476
1483
  }
1477
1484
 
1485
+ // src/simulator/edge.ts
1486
+ function edgeFailure(error, status = 400, code) {
1487
+ return json({ success: false, error, ...code ? { code } : {} }, status);
1488
+ }
1489
+ function bindingNotFound(alias) {
1490
+ return edgeFailure(`No peripheral is paired to alias "${alias}"`, 404, "BINDING_NOT_FOUND");
1491
+ }
1492
+ function now2() {
1493
+ return (/* @__PURE__ */ new Date()).toISOString();
1494
+ }
1495
+ function nextJobId() {
1496
+ state.edgePrintCounter += 1;
1497
+ return `job_${state.edgePrintCounter}`;
1498
+ }
1499
+ function nextConfigVersion() {
1500
+ return state.edgeDisplays.length + 1;
1501
+ }
1502
+ function findPeripheral(id) {
1503
+ return state.edgePeripherals.find((p) => p.id === id);
1504
+ }
1505
+ function buildBinding(alias, peripheralId) {
1506
+ const peripheral = findPeripheral(peripheralId);
1507
+ return {
1508
+ alias,
1509
+ state: peripheral ? "ok" : "peripheral_missing",
1510
+ peripheral: peripheral ? {
1511
+ id: peripheral.id,
1512
+ displayName: peripheral.displayName,
1513
+ driver: peripheral.driver,
1514
+ connected: peripheral.connected
1515
+ } : null,
1516
+ device: peripheral ? {
1517
+ id: peripheral.device.id,
1518
+ displayName: peripheral.device.displayName,
1519
+ status: peripheral.device.status
1520
+ } : null,
1521
+ updatedAt: now2()
1522
+ };
1523
+ }
1524
+ function handleListDevices() {
1525
+ return success({ devices: [...state.edgeDevices] });
1526
+ }
1527
+ function handleListPeripherals(request) {
1528
+ const deviceId = new URL(request.url).searchParams.get("deviceId");
1529
+ const peripherals = deviceId ? state.edgePeripherals.filter((p) => p.device.id === deviceId) : [...state.edgePeripherals];
1530
+ return success({ peripherals });
1531
+ }
1532
+ function handleListBindings() {
1533
+ return success({ bindings: [...state.edgeBindings.values()] });
1534
+ }
1535
+ function handleGetBinding(alias) {
1536
+ const binding = state.edgeBindings.get(alias);
1537
+ if (!binding) return bindingNotFound(alias);
1538
+ return success({ binding });
1539
+ }
1540
+ async function handlePair(request) {
1541
+ const body = await readJsonBody(request);
1542
+ const alias = String(body.alias ?? "");
1543
+ const peripheralId = String(body.peripheralId ?? "");
1544
+ if (!alias) return edgeFailure("alias is required");
1545
+ if (!peripheralId) return edgeFailure("peripheralId is required");
1546
+ if (!findPeripheral(peripheralId)) {
1547
+ return edgeFailure("Peripheral not found or its device is not granted to this project", 404);
1548
+ }
1549
+ const binding = buildBinding(alias, peripheralId);
1550
+ state.edgeBindings.set(alias, binding);
1551
+ return success({ binding });
1552
+ }
1553
+ function handleUnpair(alias) {
1554
+ const existed = state.edgeBindings.delete(alias);
1555
+ if (!existed) return bindingNotFound(alias);
1556
+ return success({ deleted: true });
1557
+ }
1558
+ async function handlePrint(request) {
1559
+ const body = await readJsonBody(request);
1560
+ const jobId = nextJobId();
1561
+ const captured = {
1562
+ jobId,
1563
+ deploymentId: String(body.deploymentId ?? ""),
1564
+ alias: body.alias ? String(body.alias) : void 0,
1565
+ deviceId: body.deviceId ? String(body.deviceId) : void 0,
1566
+ peripheralId: body.peripheralId ? String(body.peripheralId) : void 0,
1567
+ receipt: body.receipt ?? {},
1568
+ openDrawer: body.openDrawer === true,
1569
+ logo: body.logo === true ? true : body.logo === false ? false : void 0,
1570
+ copies: typeof body.copies === "number" ? body.copies : 1
1571
+ };
1572
+ state.edgePrints.push(captured);
1573
+ return success({
1574
+ jobId,
1575
+ status: "completed"
1576
+ });
1577
+ }
1578
+ async function handleShowDisplay(request) {
1579
+ const body = await readJsonBody(request);
1580
+ const captured = {
1581
+ action: "show",
1582
+ alias: body.alias ? String(body.alias) : void 0,
1583
+ peripheralId: body.peripheralId ? String(body.peripheralId) : void 0,
1584
+ url: body.url ? String(body.url) : void 0
1585
+ };
1586
+ state.edgeDisplays.push(captured);
1587
+ return success({
1588
+ configVersion: nextConfigVersion(),
1589
+ pushed: true,
1590
+ state: "showing"
1591
+ });
1592
+ }
1593
+ function handleClearDisplay(request) {
1594
+ const url = new URL(request.url);
1595
+ const alias = url.searchParams.get("alias");
1596
+ const peripheralId = url.searchParams.get("peripheralId");
1597
+ const captured = {
1598
+ action: "clear",
1599
+ alias: alias ?? void 0,
1600
+ peripheralId: peripheralId ?? void 0
1601
+ };
1602
+ state.edgeDisplays.push(captured);
1603
+ return success({
1604
+ configVersion: nextConfigVersion(),
1605
+ pushed: true,
1606
+ state: "cleared"
1607
+ });
1608
+ }
1609
+ async function handleTestPrint(request) {
1610
+ const body = await readJsonBody(request);
1611
+ const captured = {
1612
+ alias: body.alias ? String(body.alias) : void 0,
1613
+ deviceId: body.deviceId ? String(body.deviceId) : void 0,
1614
+ peripheralId: body.peripheralId ? String(body.peripheralId) : void 0
1615
+ };
1616
+ state.edgeTestPrints.push(captured);
1617
+ return success({
1618
+ status: "ok",
1619
+ peripheralId: captured.peripheralId ?? captured.alias ?? "default"
1620
+ });
1621
+ }
1622
+ async function handleEdgeRequest(request, subPath) {
1623
+ const method = request.method;
1624
+ if (subPath === "/print" && method === "POST") {
1625
+ return handlePrint(request);
1626
+ }
1627
+ if (subPath === "/devices" && method === "GET") {
1628
+ return handleListDevices();
1629
+ }
1630
+ if (subPath === "/peripherals" && method === "GET") {
1631
+ return handleListPeripherals(request);
1632
+ }
1633
+ if (subPath === "/bindings" && method === "GET") {
1634
+ return handleListBindings();
1635
+ }
1636
+ if (subPath === "/bindings" && method === "PUT") {
1637
+ return handlePair(request);
1638
+ }
1639
+ const bindingMatch = subPath.match(/^\/bindings\/([^/]+)$/);
1640
+ if (bindingMatch) {
1641
+ const alias = decodeURIComponent(bindingMatch[1]);
1642
+ if (method === "GET") return handleGetBinding(alias);
1643
+ if (method === "DELETE") return handleUnpair(alias);
1644
+ }
1645
+ if (subPath === "/display" && method === "POST") {
1646
+ return handleShowDisplay(request);
1647
+ }
1648
+ if (subPath === "/display" && method === "DELETE") {
1649
+ return handleClearDisplay(request);
1650
+ }
1651
+ if (subPath === "/test-print" && method === "POST") {
1652
+ return handleTestPrint(request);
1653
+ }
1654
+ return edgeFailure(`No edge simulator for ${method} .../edge${subPath}`, 404);
1655
+ }
1656
+ function createEdge() {
1657
+ return {
1658
+ get prints() {
1659
+ return [...state.edgePrints];
1660
+ },
1661
+ get displays() {
1662
+ return [...state.edgeDisplays];
1663
+ },
1664
+ get testPrints() {
1665
+ return [...state.edgeTestPrints];
1666
+ },
1667
+ latestPrint() {
1668
+ return state.edgePrints[state.edgePrints.length - 1];
1669
+ },
1670
+ latestDisplay() {
1671
+ return state.edgeDisplays[state.edgeDisplays.length - 1];
1672
+ },
1673
+ get bindings() {
1674
+ return [...state.edgeBindings.values()];
1675
+ },
1676
+ seedDevices(devices) {
1677
+ state.edgeDevices = [...devices];
1678
+ },
1679
+ seedPeripherals(peripherals) {
1680
+ state.edgePeripherals = [...peripherals];
1681
+ },
1682
+ seedBindings(bindings) {
1683
+ state.edgeBindings.clear();
1684
+ for (const binding of bindings) {
1685
+ state.edgeBindings.set(binding.alias, binding);
1686
+ }
1687
+ },
1688
+ clear() {
1689
+ state.edgePrints = [];
1690
+ state.edgeDisplays = [];
1691
+ state.edgeTestPrints = [];
1692
+ state.edgePrintCounter = 0;
1693
+ state.edgeDevices = [];
1694
+ state.edgePeripherals = [];
1695
+ state.edgeBindings.clear();
1696
+ },
1697
+ get count() {
1698
+ return state.edgePrints.length + state.edgeDisplays.length + state.edgeTestPrints.length;
1699
+ }
1700
+ };
1701
+ }
1702
+
1478
1703
  // src/simulator/router.ts
1479
1704
  var fetchHolder = globalSingleton("fetch-holder", () => ({
1480
1705
  originalFetch: null
@@ -1495,7 +1720,8 @@ function requiresDeploymentHmac(request, url) {
1495
1720
  const messagingMatch = url.pathname.match(
1496
1721
  /^\/api\/deployments\/[^/]+\/integrations\/(slack|line|facebook)(\/.*)?$/
1497
1722
  );
1498
- return !!(dataStoreMatch || isEmail || identitiesMatch || storeMatch || messagingMatch);
1723
+ const edgeMatch = url.pathname.match(/^\/api\/deployments\/[^/]+\/edge(\/.*)?$/);
1724
+ return !!(dataStoreMatch || isEmail || identitiesMatch || storeMatch || messagingMatch || edgeMatch);
1499
1725
  }
1500
1726
  async function handleSimulatedRequest(request, url) {
1501
1727
  if (url.pathname === "/sql") {
@@ -1512,6 +1738,7 @@ async function handleSimulatedRequest(request, url) {
1512
1738
  const messagingMatch = url.pathname.match(
1513
1739
  /^\/api\/deployments\/[^/]+\/integrations\/(slack|line|facebook)(\/.*)?$/
1514
1740
  );
1741
+ const edgeMatch = url.pathname.match(/^\/api\/deployments\/[^/]+\/edge(\/.*)?$/);
1515
1742
  const isStorageHost = url.hostname === STORAGE_TEST_HOST;
1516
1743
  if (requiresDeploymentHmac(request, url)) {
1517
1744
  const authHeader = request.headers.get("X-Stardeck-Auth");
@@ -1539,6 +1766,9 @@ async function handleSimulatedRequest(request, url) {
1539
1766
  const channel = messagingMatch[1];
1540
1767
  return handleMessagingRequest(request, channel, messagingMatch[2] ?? "");
1541
1768
  }
1769
+ if (edgeMatch) {
1770
+ return handleEdgeRequest(request, edgeMatch[1] ?? "");
1771
+ }
1542
1772
  if (dataStoreMatch) {
1543
1773
  const subPath = dataStoreMatch[1] ?? "";
1544
1774
  const db = requireDb();
@@ -1560,7 +1790,7 @@ async function handleSimulatedRequest(request, url) {
1560
1790
  }
1561
1791
  }
1562
1792
  return failure(
1563
- `[stardeck-testing] No simulator for ${request.method} ${url.pathname}. Supported: data-store query/mutate/schema, email send, identities CRUD, payments store/checkout, storage upload/files, messaging send, auth verify/refresh, Neon /sql.`,
1793
+ `[stardeck-testing] No simulator for ${request.method} ${url.pathname}. Supported: data-store query/mutate/schema, email send, identities CRUD, payments store/checkout, storage upload/files, messaging send, edge print/display/bindings, auth verify/refresh, Neon /sql.`,
1564
1794
  404
1565
1795
  );
1566
1796
  }
@@ -1649,6 +1879,7 @@ async function createTestApp(options = {}) {
1649
1879
  const payments = createPayments();
1650
1880
  const storage = createStorage();
1651
1881
  const messages = createMessages();
1882
+ const edge = createEdge();
1652
1883
  const app = {
1653
1884
  db,
1654
1885
  inbox,
@@ -1656,6 +1887,7 @@ async function createTestApp(options = {}) {
1656
1887
  payments,
1657
1888
  storage,
1658
1889
  messages,
1890
+ edge,
1659
1891
  async query(sql, params = []) {
1660
1892
  const result = await db.query(sql, params);
1661
1893
  return result.rows;
@@ -1691,6 +1923,7 @@ async function createTestApp(options = {}) {
1691
1923
  payments.clear();
1692
1924
  storage.clear();
1693
1925
  messages.clear();
1926
+ edge.clear();
1694
1927
  },
1695
1928
  async close() {
1696
1929
  state.db = null;
@@ -1704,6 +1937,7 @@ async function createTestApp(options = {}) {
1704
1937
  payments.clear();
1705
1938
  storage.clear();
1706
1939
  messages.clear();
1940
+ edge.clear();
1707
1941
  uninstallFetchRouter();
1708
1942
  await db.close();
1709
1943
  }