@stardeck-customer-apps/testing 0.7.0 → 0.8.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 +28 -3
- package/dist/index.d.mts +68 -7
- package/dist/index.d.ts +68 -7
- package/dist/index.js +53 -0
- package/dist/index.mjs +53 -0
- package/dist/next/headers-shim.js +2 -0
- package/dist/next/headers-shim.mjs +2 -0
- package/dist/setup.js +43 -0
- package/dist/setup.mjs +43 -0
- package/package.json +4 -4
package/SKILL.md
CHANGED
|
@@ -175,7 +175,8 @@ externalId })` helper models a trusted platform/channel link, and
|
|
|
175
175
|
`.all()`, `.latest()`, `.to(recipient)`, `.channel("line")`, `.count`, `.clear()`.
|
|
176
176
|
- `app.edge` — edge print/display/bindings from edge-sdk: `.prints`, `.displays`,
|
|
177
177
|
`.testPrints`, `.latestPrint()`, `.latestDisplay()`, `.bindings`,
|
|
178
|
-
`.seedDevices()`, `.seedPeripherals()`, `.seedBindings()`,
|
|
178
|
+
`.seedDevices()`, `.seedPeripherals()`, `.seedBindings()`,
|
|
179
|
+
`.seedCapabilities(caps, peripheralId?)`, `.count`, `.clear()`.
|
|
179
180
|
- `app.query(sql, params?)` / `app.db` — direct database access for
|
|
180
181
|
assertions and seeding.
|
|
181
182
|
- `callRoute(handler, opts)` — invoke an App Router route handler with a real
|
|
@@ -242,8 +243,32 @@ cursor, limit })`; `list()` intentionally keeps its previous array shape.
|
|
|
242
243
|
- `client.slack` / `client.line` / `client.facebook` send endpoints — captured
|
|
243
244
|
in `app.messages`.
|
|
244
245
|
- `createEdgeClient()` (print, pair/unpair, list devices/peripherals, show/clear
|
|
245
|
-
display, test print) — captured in `app.edge`; seed
|
|
246
|
-
pairing pickers via `app.edge.seedDevices()` /
|
|
246
|
+
display, test print, `getCapabilities()`) — captured in `app.edge`; seed
|
|
247
|
+
devices/peripherals for pairing pickers via `app.edge.seedDevices()` /
|
|
248
|
+
`seedPeripherals()`.
|
|
249
|
+
|
|
250
|
+
### Receipt layout and printer capabilities
|
|
251
|
+
|
|
252
|
+
`edge.getCapabilities(target)` answers with a stock 80mm text-mode printer
|
|
253
|
+
unless the test says otherwise. Seed a different one to exercise layout,
|
|
254
|
+
degradation, and preview code — per peripheral when a test models more than one
|
|
255
|
+
station:
|
|
256
|
+
|
|
257
|
+
```ts
|
|
258
|
+
app.edge.seedPeripherals([kitchenPrinter, counterPrinter]);
|
|
259
|
+
app.edge.seedCapabilities({ ...caps58mm, renderMode: "raster" }, kitchenPrinter.id);
|
|
260
|
+
|
|
261
|
+
const caps = await edge.getCapabilities({ peripheralId: kitchenPrinter.id });
|
|
262
|
+
expect(caps.columns).toBe(32);
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
The simulator resolves the target the way production does: an unpaired alias
|
|
266
|
+
throws `BindingError` (`BINDING_NOT_FOUND`) and an unknown peripheral 404s, so
|
|
267
|
+
target-resolution bugs surface in tests rather than in the field.
|
|
268
|
+
|
|
269
|
+
Printed receipts are captured whole, so assert on the ordered blocks an app
|
|
270
|
+
produced — `app.edge.latestPrint()?.receipt.blocks` — not just its totals.
|
|
271
|
+
|
|
247
272
|
- `next/headers` (`headers()`/`cookies()`) inside handlers under `callRoute`.
|
|
248
273
|
|
|
249
274
|
## End-to-end (Playwright) suite
|
package/dist/index.d.mts
CHANGED
|
@@ -204,17 +204,54 @@ interface TestMessages {
|
|
|
204
204
|
clear(): void;
|
|
205
205
|
get count(): number;
|
|
206
206
|
}
|
|
207
|
+
interface ReceiptItem {
|
|
208
|
+
name: string;
|
|
209
|
+
quantity: number;
|
|
210
|
+
unitPrice: number;
|
|
211
|
+
total: number;
|
|
212
|
+
note?: string;
|
|
213
|
+
}
|
|
214
|
+
type ReceiptAlign = "left" | "center" | "right";
|
|
215
|
+
type ReceiptTextSize = "small" | "normal" | "large";
|
|
216
|
+
/** Mirrors the edge SDK's block model so assertions can inspect the exact
|
|
217
|
+
* ordered content an app printed, not just its totals. */
|
|
218
|
+
type ReceiptBlock = {
|
|
219
|
+
type: "text";
|
|
220
|
+
text: string;
|
|
221
|
+
align?: ReceiptAlign;
|
|
222
|
+
bold?: boolean;
|
|
223
|
+
size?: ReceiptTextSize;
|
|
224
|
+
} | {
|
|
225
|
+
type: "columns";
|
|
226
|
+
left: string;
|
|
227
|
+
right: string;
|
|
228
|
+
bold?: boolean;
|
|
229
|
+
size?: ReceiptTextSize;
|
|
230
|
+
} | {
|
|
231
|
+
type: "separator";
|
|
232
|
+
style?: "dashed" | "solid";
|
|
233
|
+
} | {
|
|
234
|
+
type: "spacer";
|
|
235
|
+
lines?: number;
|
|
236
|
+
} | {
|
|
237
|
+
type: "items";
|
|
238
|
+
rows: ReceiptItem[];
|
|
239
|
+
} | {
|
|
240
|
+
type: "barcode";
|
|
241
|
+
format: "qr" | "code128";
|
|
242
|
+
data: string;
|
|
243
|
+
align?: ReceiptAlign;
|
|
244
|
+
/** QR width as a percentage of the printable width (5–100). Omitted →
|
|
245
|
+
* the printer's default cell size. Mirrors the edge SDK. */
|
|
246
|
+
widthPercent?: number;
|
|
247
|
+
};
|
|
207
248
|
interface ReceiptPayload {
|
|
249
|
+
paperWidth?: "58mm" | "80mm";
|
|
250
|
+
blocks?: ReceiptBlock[];
|
|
208
251
|
header?: {
|
|
209
252
|
lines: string[];
|
|
210
253
|
};
|
|
211
|
-
items?:
|
|
212
|
-
name: string;
|
|
213
|
-
quantity: number;
|
|
214
|
-
unitPrice: number;
|
|
215
|
-
total: number;
|
|
216
|
-
note?: string;
|
|
217
|
-
}>;
|
|
254
|
+
items?: ReceiptItem[];
|
|
218
255
|
subtotal?: number;
|
|
219
256
|
tax?: number;
|
|
220
257
|
discount?: number;
|
|
@@ -232,6 +269,23 @@ interface ReceiptPayload {
|
|
|
232
269
|
};
|
|
233
270
|
rawEscPos?: string;
|
|
234
271
|
}
|
|
272
|
+
/** Mirrors the edge SDK's PrinterCapabilities so tests can drive
|
|
273
|
+
* `edge.getCapabilities()` without the SDK being a dependency here. */
|
|
274
|
+
interface PrinterCapabilities {
|
|
275
|
+
paperWidth: "58mm" | "80mm";
|
|
276
|
+
columns: number;
|
|
277
|
+
dotsPerLine: number;
|
|
278
|
+
renderMode: "text" | "raster";
|
|
279
|
+
textEncoding: "utf8" | "tis620";
|
|
280
|
+
unicodeText: boolean;
|
|
281
|
+
thaiText: boolean;
|
|
282
|
+
bold: boolean;
|
|
283
|
+
textSizes: ReceiptTextSize[];
|
|
284
|
+
barcodeFormats: Array<"qr" | "code128">;
|
|
285
|
+
rasterImages: boolean;
|
|
286
|
+
cut: boolean;
|
|
287
|
+
cashDrawer: boolean;
|
|
288
|
+
}
|
|
235
289
|
interface DeviceInfo {
|
|
236
290
|
id: string;
|
|
237
291
|
displayName: string;
|
|
@@ -301,6 +355,13 @@ interface TestEdge {
|
|
|
301
355
|
seedDevices(devices: DeviceInfo[]): void;
|
|
302
356
|
seedPeripherals(peripherals: PeripheralInfo[]): void;
|
|
303
357
|
seedBindings(bindings: BindingInfo[]): void;
|
|
358
|
+
/**
|
|
359
|
+
* Answer `edge.getCapabilities()` with a specific printer — e.g. a 58mm or a
|
|
360
|
+
* raster/Thai one — so layout and preview code can be tested against it.
|
|
361
|
+
* Pass a `peripheralId` to model stations with different printers; without
|
|
362
|
+
* one this sets the default every untargeted lookup falls back to.
|
|
363
|
+
*/
|
|
364
|
+
seedCapabilities(capabilities: PrinterCapabilities | null, peripheralId?: string): void;
|
|
304
365
|
clear(): void;
|
|
305
366
|
get count(): number;
|
|
306
367
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -204,17 +204,54 @@ interface TestMessages {
|
|
|
204
204
|
clear(): void;
|
|
205
205
|
get count(): number;
|
|
206
206
|
}
|
|
207
|
+
interface ReceiptItem {
|
|
208
|
+
name: string;
|
|
209
|
+
quantity: number;
|
|
210
|
+
unitPrice: number;
|
|
211
|
+
total: number;
|
|
212
|
+
note?: string;
|
|
213
|
+
}
|
|
214
|
+
type ReceiptAlign = "left" | "center" | "right";
|
|
215
|
+
type ReceiptTextSize = "small" | "normal" | "large";
|
|
216
|
+
/** Mirrors the edge SDK's block model so assertions can inspect the exact
|
|
217
|
+
* ordered content an app printed, not just its totals. */
|
|
218
|
+
type ReceiptBlock = {
|
|
219
|
+
type: "text";
|
|
220
|
+
text: string;
|
|
221
|
+
align?: ReceiptAlign;
|
|
222
|
+
bold?: boolean;
|
|
223
|
+
size?: ReceiptTextSize;
|
|
224
|
+
} | {
|
|
225
|
+
type: "columns";
|
|
226
|
+
left: string;
|
|
227
|
+
right: string;
|
|
228
|
+
bold?: boolean;
|
|
229
|
+
size?: ReceiptTextSize;
|
|
230
|
+
} | {
|
|
231
|
+
type: "separator";
|
|
232
|
+
style?: "dashed" | "solid";
|
|
233
|
+
} | {
|
|
234
|
+
type: "spacer";
|
|
235
|
+
lines?: number;
|
|
236
|
+
} | {
|
|
237
|
+
type: "items";
|
|
238
|
+
rows: ReceiptItem[];
|
|
239
|
+
} | {
|
|
240
|
+
type: "barcode";
|
|
241
|
+
format: "qr" | "code128";
|
|
242
|
+
data: string;
|
|
243
|
+
align?: ReceiptAlign;
|
|
244
|
+
/** QR width as a percentage of the printable width (5–100). Omitted →
|
|
245
|
+
* the printer's default cell size. Mirrors the edge SDK. */
|
|
246
|
+
widthPercent?: number;
|
|
247
|
+
};
|
|
207
248
|
interface ReceiptPayload {
|
|
249
|
+
paperWidth?: "58mm" | "80mm";
|
|
250
|
+
blocks?: ReceiptBlock[];
|
|
208
251
|
header?: {
|
|
209
252
|
lines: string[];
|
|
210
253
|
};
|
|
211
|
-
items?:
|
|
212
|
-
name: string;
|
|
213
|
-
quantity: number;
|
|
214
|
-
unitPrice: number;
|
|
215
|
-
total: number;
|
|
216
|
-
note?: string;
|
|
217
|
-
}>;
|
|
254
|
+
items?: ReceiptItem[];
|
|
218
255
|
subtotal?: number;
|
|
219
256
|
tax?: number;
|
|
220
257
|
discount?: number;
|
|
@@ -232,6 +269,23 @@ interface ReceiptPayload {
|
|
|
232
269
|
};
|
|
233
270
|
rawEscPos?: string;
|
|
234
271
|
}
|
|
272
|
+
/** Mirrors the edge SDK's PrinterCapabilities so tests can drive
|
|
273
|
+
* `edge.getCapabilities()` without the SDK being a dependency here. */
|
|
274
|
+
interface PrinterCapabilities {
|
|
275
|
+
paperWidth: "58mm" | "80mm";
|
|
276
|
+
columns: number;
|
|
277
|
+
dotsPerLine: number;
|
|
278
|
+
renderMode: "text" | "raster";
|
|
279
|
+
textEncoding: "utf8" | "tis620";
|
|
280
|
+
unicodeText: boolean;
|
|
281
|
+
thaiText: boolean;
|
|
282
|
+
bold: boolean;
|
|
283
|
+
textSizes: ReceiptTextSize[];
|
|
284
|
+
barcodeFormats: Array<"qr" | "code128">;
|
|
285
|
+
rasterImages: boolean;
|
|
286
|
+
cut: boolean;
|
|
287
|
+
cashDrawer: boolean;
|
|
288
|
+
}
|
|
235
289
|
interface DeviceInfo {
|
|
236
290
|
id: string;
|
|
237
291
|
displayName: string;
|
|
@@ -301,6 +355,13 @@ interface TestEdge {
|
|
|
301
355
|
seedDevices(devices: DeviceInfo[]): void;
|
|
302
356
|
seedPeripherals(peripherals: PeripheralInfo[]): void;
|
|
303
357
|
seedBindings(bindings: BindingInfo[]): void;
|
|
358
|
+
/**
|
|
359
|
+
* Answer `edge.getCapabilities()` with a specific printer — e.g. a 58mm or a
|
|
360
|
+
* raster/Thai one — so layout and preview code can be tested against it.
|
|
361
|
+
* Pass a `peripheralId` to model stations with different printers; without
|
|
362
|
+
* one this sets the default every untargeted lookup falls back to.
|
|
363
|
+
*/
|
|
364
|
+
seedCapabilities(capabilities: PrinterCapabilities | null, peripheralId?: string): void;
|
|
304
365
|
clear(): void;
|
|
305
366
|
get count(): number;
|
|
306
367
|
}
|
package/dist/index.js
CHANGED
|
@@ -117,6 +117,8 @@ var state = globalSingleton("state", () => ({
|
|
|
117
117
|
edgeDevices: [],
|
|
118
118
|
edgePeripherals: [],
|
|
119
119
|
edgeBindings: /* @__PURE__ */ new Map(),
|
|
120
|
+
edgeCapabilities: null,
|
|
121
|
+
edgePeripheralCapabilities: /* @__PURE__ */ new Map(),
|
|
120
122
|
allowNetwork: false
|
|
121
123
|
}));
|
|
122
124
|
function requireDb() {
|
|
@@ -1877,6 +1879,44 @@ function buildBinding(alias, peripheralId) {
|
|
|
1877
1879
|
updatedAt: now2()
|
|
1878
1880
|
};
|
|
1879
1881
|
}
|
|
1882
|
+
var DEFAULT_CAPABILITIES = {
|
|
1883
|
+
paperWidth: "80mm",
|
|
1884
|
+
columns: 48,
|
|
1885
|
+
dotsPerLine: 576,
|
|
1886
|
+
renderMode: "text",
|
|
1887
|
+
textEncoding: "utf8",
|
|
1888
|
+
unicodeText: false,
|
|
1889
|
+
thaiText: false,
|
|
1890
|
+
bold: true,
|
|
1891
|
+
textSizes: ["small", "normal", "large"],
|
|
1892
|
+
barcodeFormats: ["qr"],
|
|
1893
|
+
rasterImages: true,
|
|
1894
|
+
cut: true,
|
|
1895
|
+
cashDrawer: false
|
|
1896
|
+
};
|
|
1897
|
+
function handleCapabilities(request) {
|
|
1898
|
+
const url = new URL(request.url);
|
|
1899
|
+
const alias = url.searchParams.get("alias");
|
|
1900
|
+
const deviceId = url.searchParams.get("deviceId");
|
|
1901
|
+
let peripheralId = url.searchParams.get("peripheralId");
|
|
1902
|
+
if (alias) {
|
|
1903
|
+
const binding = state.edgeBindings.get(alias);
|
|
1904
|
+
if (!binding) return bindingNotFound(alias);
|
|
1905
|
+
peripheralId = binding.peripheral?.id ?? null;
|
|
1906
|
+
}
|
|
1907
|
+
if (peripheralId) {
|
|
1908
|
+
const peripheral = findPeripheral(peripheralId);
|
|
1909
|
+
if (!peripheral) {
|
|
1910
|
+
return edgeFailure("Peripheral not found or its device is not granted to this project", 404);
|
|
1911
|
+
}
|
|
1912
|
+
const seeded = state.edgePeripheralCapabilities.get(peripheralId);
|
|
1913
|
+
return success({ capabilities: seeded ?? state.edgeCapabilities ?? DEFAULT_CAPABILITIES });
|
|
1914
|
+
}
|
|
1915
|
+
if (deviceId && !state.edgeDevices.some((d) => d.id === deviceId)) {
|
|
1916
|
+
return edgeFailure("Project does not have access to this device", 403);
|
|
1917
|
+
}
|
|
1918
|
+
return success({ capabilities: state.edgeCapabilities ?? DEFAULT_CAPABILITIES });
|
|
1919
|
+
}
|
|
1880
1920
|
function handleListDevices() {
|
|
1881
1921
|
return success({ devices: [...state.edgeDevices] });
|
|
1882
1922
|
}
|
|
@@ -1980,6 +2020,9 @@ async function handleEdgeRequest(request, subPath) {
|
|
|
1980
2020
|
if (subPath === "/print" && method === "POST") {
|
|
1981
2021
|
return handlePrint(request);
|
|
1982
2022
|
}
|
|
2023
|
+
if (subPath === "/capabilities" && method === "GET") {
|
|
2024
|
+
return handleCapabilities(request);
|
|
2025
|
+
}
|
|
1983
2026
|
if (subPath === "/devices" && method === "GET") {
|
|
1984
2027
|
return handleListDevices();
|
|
1985
2028
|
}
|
|
@@ -2041,7 +2084,17 @@ function createEdge() {
|
|
|
2041
2084
|
state.edgeBindings.set(binding.alias, binding);
|
|
2042
2085
|
}
|
|
2043
2086
|
},
|
|
2087
|
+
seedCapabilities(capabilities, peripheralId) {
|
|
2088
|
+
if (peripheralId === void 0) {
|
|
2089
|
+
state.edgeCapabilities = capabilities;
|
|
2090
|
+
return;
|
|
2091
|
+
}
|
|
2092
|
+
if (capabilities === null) state.edgePeripheralCapabilities.delete(peripheralId);
|
|
2093
|
+
else state.edgePeripheralCapabilities.set(peripheralId, capabilities);
|
|
2094
|
+
},
|
|
2044
2095
|
clear() {
|
|
2096
|
+
state.edgeCapabilities = null;
|
|
2097
|
+
state.edgePeripheralCapabilities.clear();
|
|
2045
2098
|
state.edgePrints = [];
|
|
2046
2099
|
state.edgeDisplays = [];
|
|
2047
2100
|
state.edgeTestPrints = [];
|
package/dist/index.mjs
CHANGED
|
@@ -71,6 +71,8 @@ var state = globalSingleton("state", () => ({
|
|
|
71
71
|
edgeDevices: [],
|
|
72
72
|
edgePeripherals: [],
|
|
73
73
|
edgeBindings: /* @__PURE__ */ new Map(),
|
|
74
|
+
edgeCapabilities: null,
|
|
75
|
+
edgePeripheralCapabilities: /* @__PURE__ */ new Map(),
|
|
74
76
|
allowNetwork: false
|
|
75
77
|
}));
|
|
76
78
|
function requireDb() {
|
|
@@ -1831,6 +1833,44 @@ function buildBinding(alias, peripheralId) {
|
|
|
1831
1833
|
updatedAt: now2()
|
|
1832
1834
|
};
|
|
1833
1835
|
}
|
|
1836
|
+
var DEFAULT_CAPABILITIES = {
|
|
1837
|
+
paperWidth: "80mm",
|
|
1838
|
+
columns: 48,
|
|
1839
|
+
dotsPerLine: 576,
|
|
1840
|
+
renderMode: "text",
|
|
1841
|
+
textEncoding: "utf8",
|
|
1842
|
+
unicodeText: false,
|
|
1843
|
+
thaiText: false,
|
|
1844
|
+
bold: true,
|
|
1845
|
+
textSizes: ["small", "normal", "large"],
|
|
1846
|
+
barcodeFormats: ["qr"],
|
|
1847
|
+
rasterImages: true,
|
|
1848
|
+
cut: true,
|
|
1849
|
+
cashDrawer: false
|
|
1850
|
+
};
|
|
1851
|
+
function handleCapabilities(request) {
|
|
1852
|
+
const url = new URL(request.url);
|
|
1853
|
+
const alias = url.searchParams.get("alias");
|
|
1854
|
+
const deviceId = url.searchParams.get("deviceId");
|
|
1855
|
+
let peripheralId = url.searchParams.get("peripheralId");
|
|
1856
|
+
if (alias) {
|
|
1857
|
+
const binding = state.edgeBindings.get(alias);
|
|
1858
|
+
if (!binding) return bindingNotFound(alias);
|
|
1859
|
+
peripheralId = binding.peripheral?.id ?? null;
|
|
1860
|
+
}
|
|
1861
|
+
if (peripheralId) {
|
|
1862
|
+
const peripheral = findPeripheral(peripheralId);
|
|
1863
|
+
if (!peripheral) {
|
|
1864
|
+
return edgeFailure("Peripheral not found or its device is not granted to this project", 404);
|
|
1865
|
+
}
|
|
1866
|
+
const seeded = state.edgePeripheralCapabilities.get(peripheralId);
|
|
1867
|
+
return success({ capabilities: seeded ?? state.edgeCapabilities ?? DEFAULT_CAPABILITIES });
|
|
1868
|
+
}
|
|
1869
|
+
if (deviceId && !state.edgeDevices.some((d) => d.id === deviceId)) {
|
|
1870
|
+
return edgeFailure("Project does not have access to this device", 403);
|
|
1871
|
+
}
|
|
1872
|
+
return success({ capabilities: state.edgeCapabilities ?? DEFAULT_CAPABILITIES });
|
|
1873
|
+
}
|
|
1834
1874
|
function handleListDevices() {
|
|
1835
1875
|
return success({ devices: [...state.edgeDevices] });
|
|
1836
1876
|
}
|
|
@@ -1934,6 +1974,9 @@ async function handleEdgeRequest(request, subPath) {
|
|
|
1934
1974
|
if (subPath === "/print" && method === "POST") {
|
|
1935
1975
|
return handlePrint(request);
|
|
1936
1976
|
}
|
|
1977
|
+
if (subPath === "/capabilities" && method === "GET") {
|
|
1978
|
+
return handleCapabilities(request);
|
|
1979
|
+
}
|
|
1937
1980
|
if (subPath === "/devices" && method === "GET") {
|
|
1938
1981
|
return handleListDevices();
|
|
1939
1982
|
}
|
|
@@ -1995,7 +2038,17 @@ function createEdge() {
|
|
|
1995
2038
|
state.edgeBindings.set(binding.alias, binding);
|
|
1996
2039
|
}
|
|
1997
2040
|
},
|
|
2041
|
+
seedCapabilities(capabilities, peripheralId) {
|
|
2042
|
+
if (peripheralId === void 0) {
|
|
2043
|
+
state.edgeCapabilities = capabilities;
|
|
2044
|
+
return;
|
|
2045
|
+
}
|
|
2046
|
+
if (capabilities === null) state.edgePeripheralCapabilities.delete(peripheralId);
|
|
2047
|
+
else state.edgePeripheralCapabilities.set(peripheralId, capabilities);
|
|
2048
|
+
},
|
|
1998
2049
|
clear() {
|
|
2050
|
+
state.edgeCapabilities = null;
|
|
2051
|
+
state.edgePeripheralCapabilities.clear();
|
|
1999
2052
|
state.edgePrints = [];
|
|
2000
2053
|
state.edgeDisplays = [];
|
|
2001
2054
|
state.edgeTestPrints = [];
|
package/dist/setup.js
CHANGED
|
@@ -57,6 +57,8 @@ var state = globalSingleton("state", () => ({
|
|
|
57
57
|
edgeDevices: [],
|
|
58
58
|
edgePeripherals: [],
|
|
59
59
|
edgeBindings: /* @__PURE__ */ new Map(),
|
|
60
|
+
edgeCapabilities: null,
|
|
61
|
+
edgePeripheralCapabilities: /* @__PURE__ */ new Map(),
|
|
60
62
|
allowNetwork: false
|
|
61
63
|
}));
|
|
62
64
|
function requireDb() {
|
|
@@ -1473,6 +1475,44 @@ function buildBinding(alias, peripheralId) {
|
|
|
1473
1475
|
updatedAt: now2()
|
|
1474
1476
|
};
|
|
1475
1477
|
}
|
|
1478
|
+
var DEFAULT_CAPABILITIES = {
|
|
1479
|
+
paperWidth: "80mm",
|
|
1480
|
+
columns: 48,
|
|
1481
|
+
dotsPerLine: 576,
|
|
1482
|
+
renderMode: "text",
|
|
1483
|
+
textEncoding: "utf8",
|
|
1484
|
+
unicodeText: false,
|
|
1485
|
+
thaiText: false,
|
|
1486
|
+
bold: true,
|
|
1487
|
+
textSizes: ["small", "normal", "large"],
|
|
1488
|
+
barcodeFormats: ["qr"],
|
|
1489
|
+
rasterImages: true,
|
|
1490
|
+
cut: true,
|
|
1491
|
+
cashDrawer: false
|
|
1492
|
+
};
|
|
1493
|
+
function handleCapabilities(request) {
|
|
1494
|
+
const url = new URL(request.url);
|
|
1495
|
+
const alias = url.searchParams.get("alias");
|
|
1496
|
+
const deviceId = url.searchParams.get("deviceId");
|
|
1497
|
+
let peripheralId = url.searchParams.get("peripheralId");
|
|
1498
|
+
if (alias) {
|
|
1499
|
+
const binding = state.edgeBindings.get(alias);
|
|
1500
|
+
if (!binding) return bindingNotFound(alias);
|
|
1501
|
+
peripheralId = binding.peripheral?.id ?? null;
|
|
1502
|
+
}
|
|
1503
|
+
if (peripheralId) {
|
|
1504
|
+
const peripheral = findPeripheral(peripheralId);
|
|
1505
|
+
if (!peripheral) {
|
|
1506
|
+
return edgeFailure("Peripheral not found or its device is not granted to this project", 404);
|
|
1507
|
+
}
|
|
1508
|
+
const seeded = state.edgePeripheralCapabilities.get(peripheralId);
|
|
1509
|
+
return success({ capabilities: seeded ?? state.edgeCapabilities ?? DEFAULT_CAPABILITIES });
|
|
1510
|
+
}
|
|
1511
|
+
if (deviceId && !state.edgeDevices.some((d) => d.id === deviceId)) {
|
|
1512
|
+
return edgeFailure("Project does not have access to this device", 403);
|
|
1513
|
+
}
|
|
1514
|
+
return success({ capabilities: state.edgeCapabilities ?? DEFAULT_CAPABILITIES });
|
|
1515
|
+
}
|
|
1476
1516
|
function handleListDevices() {
|
|
1477
1517
|
return success({ devices: [...state.edgeDevices] });
|
|
1478
1518
|
}
|
|
@@ -1576,6 +1616,9 @@ async function handleEdgeRequest(request, subPath) {
|
|
|
1576
1616
|
if (subPath === "/print" && method === "POST") {
|
|
1577
1617
|
return handlePrint(request);
|
|
1578
1618
|
}
|
|
1619
|
+
if (subPath === "/capabilities" && method === "GET") {
|
|
1620
|
+
return handleCapabilities(request);
|
|
1621
|
+
}
|
|
1579
1622
|
if (subPath === "/devices" && method === "GET") {
|
|
1580
1623
|
return handleListDevices();
|
|
1581
1624
|
}
|
package/dist/setup.mjs
CHANGED
|
@@ -33,6 +33,8 @@ var state = globalSingleton("state", () => ({
|
|
|
33
33
|
edgeDevices: [],
|
|
34
34
|
edgePeripherals: [],
|
|
35
35
|
edgeBindings: /* @__PURE__ */ new Map(),
|
|
36
|
+
edgeCapabilities: null,
|
|
37
|
+
edgePeripheralCapabilities: /* @__PURE__ */ new Map(),
|
|
36
38
|
allowNetwork: false
|
|
37
39
|
}));
|
|
38
40
|
function requireDb() {
|
|
@@ -1449,6 +1451,44 @@ function buildBinding(alias, peripheralId) {
|
|
|
1449
1451
|
updatedAt: now2()
|
|
1450
1452
|
};
|
|
1451
1453
|
}
|
|
1454
|
+
var DEFAULT_CAPABILITIES = {
|
|
1455
|
+
paperWidth: "80mm",
|
|
1456
|
+
columns: 48,
|
|
1457
|
+
dotsPerLine: 576,
|
|
1458
|
+
renderMode: "text",
|
|
1459
|
+
textEncoding: "utf8",
|
|
1460
|
+
unicodeText: false,
|
|
1461
|
+
thaiText: false,
|
|
1462
|
+
bold: true,
|
|
1463
|
+
textSizes: ["small", "normal", "large"],
|
|
1464
|
+
barcodeFormats: ["qr"],
|
|
1465
|
+
rasterImages: true,
|
|
1466
|
+
cut: true,
|
|
1467
|
+
cashDrawer: false
|
|
1468
|
+
};
|
|
1469
|
+
function handleCapabilities(request) {
|
|
1470
|
+
const url = new URL(request.url);
|
|
1471
|
+
const alias = url.searchParams.get("alias");
|
|
1472
|
+
const deviceId = url.searchParams.get("deviceId");
|
|
1473
|
+
let peripheralId = url.searchParams.get("peripheralId");
|
|
1474
|
+
if (alias) {
|
|
1475
|
+
const binding = state.edgeBindings.get(alias);
|
|
1476
|
+
if (!binding) return bindingNotFound(alias);
|
|
1477
|
+
peripheralId = binding.peripheral?.id ?? null;
|
|
1478
|
+
}
|
|
1479
|
+
if (peripheralId) {
|
|
1480
|
+
const peripheral = findPeripheral(peripheralId);
|
|
1481
|
+
if (!peripheral) {
|
|
1482
|
+
return edgeFailure("Peripheral not found or its device is not granted to this project", 404);
|
|
1483
|
+
}
|
|
1484
|
+
const seeded = state.edgePeripheralCapabilities.get(peripheralId);
|
|
1485
|
+
return success({ capabilities: seeded ?? state.edgeCapabilities ?? DEFAULT_CAPABILITIES });
|
|
1486
|
+
}
|
|
1487
|
+
if (deviceId && !state.edgeDevices.some((d) => d.id === deviceId)) {
|
|
1488
|
+
return edgeFailure("Project does not have access to this device", 403);
|
|
1489
|
+
}
|
|
1490
|
+
return success({ capabilities: state.edgeCapabilities ?? DEFAULT_CAPABILITIES });
|
|
1491
|
+
}
|
|
1452
1492
|
function handleListDevices() {
|
|
1453
1493
|
return success({ devices: [...state.edgeDevices] });
|
|
1454
1494
|
}
|
|
@@ -1552,6 +1592,9 @@ async function handleEdgeRequest(request, subPath) {
|
|
|
1552
1592
|
if (subPath === "/print" && method === "POST") {
|
|
1553
1593
|
return handlePrint(request);
|
|
1554
1594
|
}
|
|
1595
|
+
if (subPath === "/capabilities" && method === "GET") {
|
|
1596
|
+
return handleCapabilities(request);
|
|
1597
|
+
}
|
|
1555
1598
|
if (subPath === "/devices" && method === "GET") {
|
|
1556
1599
|
return handleListDevices();
|
|
1557
1600
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stardeck-customer-apps/testing",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
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",
|
|
@@ -65,8 +65,8 @@
|
|
|
65
65
|
"@electric-sql/pglite": "^0.3.0"
|
|
66
66
|
},
|
|
67
67
|
"peerDependencies": {
|
|
68
|
-
"@stardeck-customer-apps/integrations-sdk": ">=1.6.0",
|
|
69
68
|
"@stardeck-customer-apps/edge-sdk": ">=1.4.0",
|
|
69
|
+
"@stardeck-customer-apps/integrations-sdk": ">=1.6.0",
|
|
70
70
|
"@stardeck-customer-apps/payments-sdk": ">=0.1.0",
|
|
71
71
|
"@stardeck-customer-apps/storage-sdk": ">=0.1.0",
|
|
72
72
|
"next": "^14.0.0 || ^15.0.0 || ^16.0.0",
|
|
@@ -93,8 +93,8 @@
|
|
|
93
93
|
"@eslint/js": "^9.0.0",
|
|
94
94
|
"@neondatabase/serverless": "^1.0.0",
|
|
95
95
|
"@stardeck-customer-apps/data-store-sdk": "*",
|
|
96
|
-
"@stardeck-customer-apps/email-sdk": "*",
|
|
97
96
|
"@stardeck-customer-apps/edge-sdk": "*",
|
|
97
|
+
"@stardeck-customer-apps/email-sdk": "*",
|
|
98
98
|
"@stardeck-customer-apps/integrations-sdk": "*",
|
|
99
99
|
"@stardeck-customer-apps/payments-sdk": "*",
|
|
100
100
|
"@stardeck-customer-apps/storage-sdk": "*",
|
|
@@ -102,7 +102,7 @@
|
|
|
102
102
|
"@types/node": "^24.10.1",
|
|
103
103
|
"kysely": "^0.27.0",
|
|
104
104
|
"kysely-neon": "^2.0.0",
|
|
105
|
-
"next": "^
|
|
105
|
+
"next": "^16.2.12",
|
|
106
106
|
"tsup": "^8.0.0",
|
|
107
107
|
"typescript": "^5.0.0",
|
|
108
108
|
"typescript-eslint": "^8.0.0",
|