@stardeck-customer-apps/testing 0.14.0 → 0.15.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 +8 -0
- package/dist/index.d.mts +41 -1
- package/dist/index.d.ts +41 -1
- package/dist/index.js +113 -2
- package/dist/index.mjs +113 -2
- package/dist/next/headers-shim.js +3 -0
- package/dist/next/headers-shim.mjs +3 -0
- package/dist/setup.js +82 -2
- package/dist/setup.mjs +82 -2
- package/package.json +9 -3
package/SKILL.md
CHANGED
|
@@ -451,6 +451,14 @@ cursor, limit })`; `list()` intentionally keeps its previous array shape.
|
|
|
451
451
|
display, test print, `getCapabilities()`) — captured in `app.edge`; seed
|
|
452
452
|
devices/peripherals for pairing pickers via `app.edge.seedDevices()` /
|
|
453
453
|
`seedPeripherals()`.
|
|
454
|
+
- `createStarlensClient().extract()` from the starlens SDK — there is no model in
|
|
455
|
+
tests: register what Starlens answers with `app.starlens.mock({ output, issues })`
|
|
456
|
+
(a successful extraction; each issue path comes back `null` in `output`, as in
|
|
457
|
+
production) or `app.starlens.reject("not a receipt")` (the 422 the SDK maps to
|
|
458
|
+
`UnsupportedDocumentError`). An unregistered call fails with a descriptive 502 so a
|
|
459
|
+
test never passes on an invented extraction. `app.starlens.calls` records each
|
|
460
|
+
request's version, input names/media types (never bytes), schema and
|
|
461
|
+
instructions — assert your `.describe()` aliases reached the wire there.
|
|
454
462
|
|
|
455
463
|
### Receipt layout and printer capabilities
|
|
456
464
|
|
package/dist/index.d.mts
CHANGED
|
@@ -462,6 +462,44 @@ interface TestEdge {
|
|
|
462
462
|
clear(): void;
|
|
463
463
|
get count(): number;
|
|
464
464
|
}
|
|
465
|
+
/** One Starlens request the app made, as the simulator saw it (names only, never bytes). */
|
|
466
|
+
interface CapturedStarlensCall {
|
|
467
|
+
version: string;
|
|
468
|
+
inputs: {
|
|
469
|
+
name: string;
|
|
470
|
+
mediaType: string | null;
|
|
471
|
+
source: "url" | "data";
|
|
472
|
+
}[];
|
|
473
|
+
schema: Record<string, unknown>;
|
|
474
|
+
instructions?: string;
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* What the simulated Starlens answers next. `{ output, issues }` is returned as a
|
|
478
|
+
* successful extraction with every issue path nulled in `output`, exactly as the control
|
|
479
|
+
* plane does; `{ reject }` becomes the 422 `unsupported_document` refusal the SDK maps to
|
|
480
|
+
* `UnsupportedDocumentError`.
|
|
481
|
+
*/
|
|
482
|
+
type StarlensReply = {
|
|
483
|
+
output: unknown;
|
|
484
|
+
issues?: {
|
|
485
|
+
path: string;
|
|
486
|
+
reason: string;
|
|
487
|
+
}[];
|
|
488
|
+
} | {
|
|
489
|
+
reject: string;
|
|
490
|
+
};
|
|
491
|
+
interface TestStarlens {
|
|
492
|
+
/** Queue the reply for the next `extract()` call. */
|
|
493
|
+
mock(reply: StarlensReply): void;
|
|
494
|
+
/** Queue a refusal for the next `extract()` call. */
|
|
495
|
+
reject(reason: string): void;
|
|
496
|
+
/** Reply used whenever the queue is empty (null = fail with a descriptive 502). */
|
|
497
|
+
mockDefault(reply: StarlensReply | null): void;
|
|
498
|
+
get calls(): CapturedStarlensCall[];
|
|
499
|
+
latestCall(): CapturedStarlensCall | undefined;
|
|
500
|
+
clear(): void;
|
|
501
|
+
get count(): number;
|
|
502
|
+
}
|
|
465
503
|
interface TestAppOptions {
|
|
466
504
|
/** Data stores exposed through the production-shaped STARDECK_DATA_STORES manifest. */
|
|
467
505
|
dataStores?: TestDataStoreInput[];
|
|
@@ -506,6 +544,8 @@ interface TestApp {
|
|
|
506
544
|
messages: TestMessages;
|
|
507
545
|
/** Captured edge print/display ops and peripheral bindings from edge-sdk. */
|
|
508
546
|
edge: TestEdge;
|
|
547
|
+
/** Scripted replies and captured calls for the Starlens SDK. */
|
|
548
|
+
starlens: TestStarlens;
|
|
509
549
|
/** Convenience for raw SQL: `app.query("SELECT ...", [param])`. */
|
|
510
550
|
query<T = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<T[]>;
|
|
511
551
|
/**
|
|
@@ -571,4 +611,4 @@ declare const TEST_ENV_DEFAULTS: {
|
|
|
571
611
|
/** Default location of the DDL snapshot written by `generate-types`. */
|
|
572
612
|
declare const DEFAULT_SCHEMA_PATH = "./src/generated/data-store-schema.sql";
|
|
573
613
|
|
|
574
|
-
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, 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, TEST_ENV_DEFAULTS, type TestApp, type TestAppOptions, TestDataStoreInput, type TestDirectory, type TestEdge, type TestInbox, type TestMessages, type TestPayments, type TestStorage, type TestUser, WORKFLOW_NAME_PREFIX, callRoute, createTestApp, describeWorkflow, parseWorkflowName };
|
|
614
|
+
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 };
|
package/dist/index.d.ts
CHANGED
|
@@ -462,6 +462,44 @@ interface TestEdge {
|
|
|
462
462
|
clear(): void;
|
|
463
463
|
get count(): number;
|
|
464
464
|
}
|
|
465
|
+
/** One Starlens request the app made, as the simulator saw it (names only, never bytes). */
|
|
466
|
+
interface CapturedStarlensCall {
|
|
467
|
+
version: string;
|
|
468
|
+
inputs: {
|
|
469
|
+
name: string;
|
|
470
|
+
mediaType: string | null;
|
|
471
|
+
source: "url" | "data";
|
|
472
|
+
}[];
|
|
473
|
+
schema: Record<string, unknown>;
|
|
474
|
+
instructions?: string;
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* What the simulated Starlens answers next. `{ output, issues }` is returned as a
|
|
478
|
+
* successful extraction with every issue path nulled in `output`, exactly as the control
|
|
479
|
+
* plane does; `{ reject }` becomes the 422 `unsupported_document` refusal the SDK maps to
|
|
480
|
+
* `UnsupportedDocumentError`.
|
|
481
|
+
*/
|
|
482
|
+
type StarlensReply = {
|
|
483
|
+
output: unknown;
|
|
484
|
+
issues?: {
|
|
485
|
+
path: string;
|
|
486
|
+
reason: string;
|
|
487
|
+
}[];
|
|
488
|
+
} | {
|
|
489
|
+
reject: string;
|
|
490
|
+
};
|
|
491
|
+
interface TestStarlens {
|
|
492
|
+
/** Queue the reply for the next `extract()` call. */
|
|
493
|
+
mock(reply: StarlensReply): void;
|
|
494
|
+
/** Queue a refusal for the next `extract()` call. */
|
|
495
|
+
reject(reason: string): void;
|
|
496
|
+
/** Reply used whenever the queue is empty (null = fail with a descriptive 502). */
|
|
497
|
+
mockDefault(reply: StarlensReply | null): void;
|
|
498
|
+
get calls(): CapturedStarlensCall[];
|
|
499
|
+
latestCall(): CapturedStarlensCall | undefined;
|
|
500
|
+
clear(): void;
|
|
501
|
+
get count(): number;
|
|
502
|
+
}
|
|
465
503
|
interface TestAppOptions {
|
|
466
504
|
/** Data stores exposed through the production-shaped STARDECK_DATA_STORES manifest. */
|
|
467
505
|
dataStores?: TestDataStoreInput[];
|
|
@@ -506,6 +544,8 @@ interface TestApp {
|
|
|
506
544
|
messages: TestMessages;
|
|
507
545
|
/** Captured edge print/display ops and peripheral bindings from edge-sdk. */
|
|
508
546
|
edge: TestEdge;
|
|
547
|
+
/** Scripted replies and captured calls for the Starlens SDK. */
|
|
548
|
+
starlens: TestStarlens;
|
|
509
549
|
/** Convenience for raw SQL: `app.query("SELECT ...", [param])`. */
|
|
510
550
|
query<T = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<T[]>;
|
|
511
551
|
/**
|
|
@@ -571,4 +611,4 @@ declare const TEST_ENV_DEFAULTS: {
|
|
|
571
611
|
/** Default location of the DDL snapshot written by `generate-types`. */
|
|
572
612
|
declare const DEFAULT_SCHEMA_PATH = "./src/generated/data-store-schema.sql";
|
|
573
613
|
|
|
574
|
-
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, 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, TEST_ENV_DEFAULTS, type TestApp, type TestAppOptions, TestDataStoreInput, type TestDirectory, type TestEdge, type TestInbox, type TestMessages, type TestPayments, type TestStorage, type TestUser, WORKFLOW_NAME_PREFIX, callRoute, createTestApp, describeWorkflow, parseWorkflowName };
|
|
614
|
+
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 };
|
package/dist/index.js
CHANGED
|
@@ -144,6 +144,9 @@ var state = globalSingleton("state", () => ({
|
|
|
144
144
|
edgeBindings: /* @__PURE__ */ new Map(),
|
|
145
145
|
edgeCapabilities: null,
|
|
146
146
|
edgePeripheralCapabilities: /* @__PURE__ */ new Map(),
|
|
147
|
+
starlensCalls: [],
|
|
148
|
+
starlensReplies: [],
|
|
149
|
+
starlensDefaultReply: null,
|
|
147
150
|
allowNetwork: false
|
|
148
151
|
}));
|
|
149
152
|
function requireDb() {
|
|
@@ -2553,6 +2556,104 @@ function createEdge() {
|
|
|
2553
2556
|
};
|
|
2554
2557
|
}
|
|
2555
2558
|
|
|
2559
|
+
// src/simulator/starlens.ts
|
|
2560
|
+
function starlensFailure(error, status, code) {
|
|
2561
|
+
return json({ success: false, error, code }, status);
|
|
2562
|
+
}
|
|
2563
|
+
function nullFlagged(output, issues) {
|
|
2564
|
+
const copy = JSON.parse(JSON.stringify(output ?? null));
|
|
2565
|
+
for (const { path } of issues) {
|
|
2566
|
+
const keys = path.replace(/^output\./, "").replace(/\[(\d+)\]/g, ".$1").split(".");
|
|
2567
|
+
const last = keys.pop();
|
|
2568
|
+
const parent = keys.reduce(
|
|
2569
|
+
(o, k) => o != null && typeof o === "object" ? o[k] : void 0,
|
|
2570
|
+
copy
|
|
2571
|
+
);
|
|
2572
|
+
if (parent != null && typeof parent === "object" && Object.prototype.hasOwnProperty.call(parent, last)) {
|
|
2573
|
+
parent[last] = null;
|
|
2574
|
+
}
|
|
2575
|
+
}
|
|
2576
|
+
return copy;
|
|
2577
|
+
}
|
|
2578
|
+
async function handleStarlensExtract(request, signedDeploymentId) {
|
|
2579
|
+
const body = await readJsonBody(request);
|
|
2580
|
+
if (signedDeploymentId !== null && body.deploymentId !== signedDeploymentId) {
|
|
2581
|
+
return starlensFailure("Deployment ID mismatch", 401, "AUTHENTICATION_ERROR");
|
|
2582
|
+
}
|
|
2583
|
+
const inputs = Array.isArray(body.inputs) ? body.inputs : [];
|
|
2584
|
+
if (inputs.length === 0) {
|
|
2585
|
+
return starlensFailure(
|
|
2586
|
+
"Validation failed \u2014 inputs: at least one input",
|
|
2587
|
+
400,
|
|
2588
|
+
"VALIDATION_ERROR"
|
|
2589
|
+
);
|
|
2590
|
+
}
|
|
2591
|
+
const schema = body.schema;
|
|
2592
|
+
if (!schema || schema.type !== "object" || typeof schema.properties !== "object") {
|
|
2593
|
+
return starlensFailure(
|
|
2594
|
+
"Validation failed \u2014 schema.properties is required",
|
|
2595
|
+
400,
|
|
2596
|
+
"VALIDATION_ERROR"
|
|
2597
|
+
);
|
|
2598
|
+
}
|
|
2599
|
+
const call = {
|
|
2600
|
+
version: typeof body.version === "string" ? body.version : "latest",
|
|
2601
|
+
inputs: inputs.map((i) => ({
|
|
2602
|
+
name: typeof i.filename === "string" ? i.filename : typeof i.url === "string" ? i.url : "upload",
|
|
2603
|
+
mediaType: typeof i.mediaType === "string" ? i.mediaType : null,
|
|
2604
|
+
source: typeof i.url === "string" ? "url" : "data"
|
|
2605
|
+
})),
|
|
2606
|
+
schema,
|
|
2607
|
+
instructions: typeof body.instructions === "string" ? body.instructions : void 0
|
|
2608
|
+
};
|
|
2609
|
+
state.starlensCalls.push(call);
|
|
2610
|
+
const reply = state.starlensReplies.shift() ?? state.starlensDefaultReply;
|
|
2611
|
+
if (!reply) {
|
|
2612
|
+
return starlensFailure(
|
|
2613
|
+
"[stardeck-testing] No Starlens reply registered. Call app.starlens.mock({ output, issues }) or app.starlens.reject(reason) before the request.",
|
|
2614
|
+
502,
|
|
2615
|
+
"model_failed"
|
|
2616
|
+
);
|
|
2617
|
+
}
|
|
2618
|
+
if ("reject" in reply) {
|
|
2619
|
+
return starlensFailure(reply.reject, 422, "unsupported_document");
|
|
2620
|
+
}
|
|
2621
|
+
const issues = reply.issues ?? [];
|
|
2622
|
+
return success({
|
|
2623
|
+
version: call.version === "latest" ? "v1" : call.version,
|
|
2624
|
+
output: nullFlagged(reply.output, issues),
|
|
2625
|
+
issues,
|
|
2626
|
+
usage: { inputTokens: 0, outputTokens: 0, cost: 0 }
|
|
2627
|
+
});
|
|
2628
|
+
}
|
|
2629
|
+
function createStarlens() {
|
|
2630
|
+
return {
|
|
2631
|
+
mock(reply) {
|
|
2632
|
+
state.starlensReplies.push(reply);
|
|
2633
|
+
},
|
|
2634
|
+
reject(reason) {
|
|
2635
|
+
state.starlensReplies.push({ reject: reason });
|
|
2636
|
+
},
|
|
2637
|
+
mockDefault(reply) {
|
|
2638
|
+
state.starlensDefaultReply = reply;
|
|
2639
|
+
},
|
|
2640
|
+
get calls() {
|
|
2641
|
+
return state.starlensCalls;
|
|
2642
|
+
},
|
|
2643
|
+
latestCall() {
|
|
2644
|
+
return state.starlensCalls[state.starlensCalls.length - 1];
|
|
2645
|
+
},
|
|
2646
|
+
clear() {
|
|
2647
|
+
state.starlensCalls = [];
|
|
2648
|
+
state.starlensReplies = [];
|
|
2649
|
+
state.starlensDefaultReply = null;
|
|
2650
|
+
},
|
|
2651
|
+
get count() {
|
|
2652
|
+
return state.starlensCalls.length;
|
|
2653
|
+
}
|
|
2654
|
+
};
|
|
2655
|
+
}
|
|
2656
|
+
|
|
2556
2657
|
// src/simulator/router.ts
|
|
2557
2658
|
var fetchHolder = globalSingleton("fetch-holder", () => ({
|
|
2558
2659
|
originalFetch: null
|
|
@@ -2574,7 +2675,8 @@ function requiresDeploymentHmac(request, url) {
|
|
|
2574
2675
|
/^\/api\/deployments\/[^/]+\/integrations\/(slack|line|facebook)(\/.*)?$/
|
|
2575
2676
|
);
|
|
2576
2677
|
const edgeMatch = url.pathname.match(/^\/api\/deployments\/[^/]+\/edge(\/.*)?$/);
|
|
2577
|
-
|
|
2678
|
+
const isStarlens = url.pathname === "/api/starlens/extract";
|
|
2679
|
+
return !!(isStarlens || dataStoreMatch || isEmail || identitiesMatch || storeMatch || messagingMatch || edgeMatch);
|
|
2578
2680
|
}
|
|
2579
2681
|
function deploymentIdFromPath(url) {
|
|
2580
2682
|
const match = url.pathname.match(/\/api\/deployments\/([^/]+)(?:\/|$)/);
|
|
@@ -2604,6 +2706,7 @@ async function handleSimulatedRequest(request, url) {
|
|
|
2604
2706
|
);
|
|
2605
2707
|
const edgeMatch = url.pathname.match(/^\/api\/deployments\/[^/]+\/edge(\/.*)?$/);
|
|
2606
2708
|
const isStorageHost = url.hostname === STORAGE_TEST_HOST;
|
|
2709
|
+
let signedDeploymentId = null;
|
|
2607
2710
|
if (requiresDeploymentHmac(request, url)) {
|
|
2608
2711
|
const authHeader = request.headers.get("X-Stardeck-Auth");
|
|
2609
2712
|
if (!authHeader) {
|
|
@@ -2615,6 +2718,7 @@ async function handleSimulatedRequest(request, url) {
|
|
|
2615
2718
|
if (!authPayload || pathDeploymentId !== null && authPayload.deploymentId !== pathDeploymentId) {
|
|
2616
2719
|
return failure("Invalid authentication", 401);
|
|
2617
2720
|
}
|
|
2721
|
+
signedDeploymentId = authPayload.deploymentId;
|
|
2618
2722
|
}
|
|
2619
2723
|
if (isStorageHost) {
|
|
2620
2724
|
return handleStorageRequest(request, url);
|
|
@@ -2635,6 +2739,9 @@ async function handleSimulatedRequest(request, url) {
|
|
|
2635
2739
|
if (edgeMatch) {
|
|
2636
2740
|
return handleEdgeRequest(request, edgeMatch[1] ?? "");
|
|
2637
2741
|
}
|
|
2742
|
+
if (url.pathname === "/api/starlens/extract" && request.method === "POST") {
|
|
2743
|
+
return handleStarlensExtract(request, signedDeploymentId);
|
|
2744
|
+
}
|
|
2638
2745
|
if (dataStoreMatch) {
|
|
2639
2746
|
const subPath = dataStoreMatch[1] ?? "";
|
|
2640
2747
|
const db = requireDb();
|
|
@@ -2656,7 +2763,7 @@ async function handleSimulatedRequest(request, url) {
|
|
|
2656
2763
|
}
|
|
2657
2764
|
}
|
|
2658
2765
|
return failure(
|
|
2659
|
-
`[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.`,
|
|
2766
|
+
`[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, starlens extract, auth verify/refresh, Neon /sql.`,
|
|
2660
2767
|
404
|
|
2661
2768
|
);
|
|
2662
2769
|
}
|
|
@@ -2824,6 +2931,7 @@ async function createTestApp(options = {}) {
|
|
|
2824
2931
|
const storage = createStorage();
|
|
2825
2932
|
const messages = createMessages();
|
|
2826
2933
|
const edge = createEdge();
|
|
2934
|
+
const starlens = createStarlens();
|
|
2827
2935
|
const app = {
|
|
2828
2936
|
db,
|
|
2829
2937
|
inbox,
|
|
@@ -2832,6 +2940,7 @@ async function createTestApp(options = {}) {
|
|
|
2832
2940
|
storage,
|
|
2833
2941
|
messages,
|
|
2834
2942
|
edge,
|
|
2943
|
+
starlens,
|
|
2835
2944
|
async query(sql, params = []) {
|
|
2836
2945
|
const result = await db.query(sql, params);
|
|
2837
2946
|
return result.rows;
|
|
@@ -2873,6 +2982,7 @@ async function createTestApp(options = {}) {
|
|
|
2873
2982
|
storage.clear();
|
|
2874
2983
|
messages.clear();
|
|
2875
2984
|
edge.clear();
|
|
2985
|
+
starlens.clear();
|
|
2876
2986
|
},
|
|
2877
2987
|
async close() {
|
|
2878
2988
|
state.db = null;
|
|
@@ -2887,6 +2997,7 @@ async function createTestApp(options = {}) {
|
|
|
2887
2997
|
storage.clear();
|
|
2888
2998
|
messages.clear();
|
|
2889
2999
|
edge.clear();
|
|
3000
|
+
starlens.clear();
|
|
2890
3001
|
uninstallFetchRouter();
|
|
2891
3002
|
await db.close();
|
|
2892
3003
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -95,6 +95,9 @@ var state = globalSingleton("state", () => ({
|
|
|
95
95
|
edgeBindings: /* @__PURE__ */ new Map(),
|
|
96
96
|
edgeCapabilities: null,
|
|
97
97
|
edgePeripheralCapabilities: /* @__PURE__ */ new Map(),
|
|
98
|
+
starlensCalls: [],
|
|
99
|
+
starlensReplies: [],
|
|
100
|
+
starlensDefaultReply: null,
|
|
98
101
|
allowNetwork: false
|
|
99
102
|
}));
|
|
100
103
|
function requireDb() {
|
|
@@ -2504,6 +2507,104 @@ function createEdge() {
|
|
|
2504
2507
|
};
|
|
2505
2508
|
}
|
|
2506
2509
|
|
|
2510
|
+
// src/simulator/starlens.ts
|
|
2511
|
+
function starlensFailure(error, status, code) {
|
|
2512
|
+
return json({ success: false, error, code }, status);
|
|
2513
|
+
}
|
|
2514
|
+
function nullFlagged(output, issues) {
|
|
2515
|
+
const copy = JSON.parse(JSON.stringify(output ?? null));
|
|
2516
|
+
for (const { path } of issues) {
|
|
2517
|
+
const keys = path.replace(/^output\./, "").replace(/\[(\d+)\]/g, ".$1").split(".");
|
|
2518
|
+
const last = keys.pop();
|
|
2519
|
+
const parent = keys.reduce(
|
|
2520
|
+
(o, k) => o != null && typeof o === "object" ? o[k] : void 0,
|
|
2521
|
+
copy
|
|
2522
|
+
);
|
|
2523
|
+
if (parent != null && typeof parent === "object" && Object.prototype.hasOwnProperty.call(parent, last)) {
|
|
2524
|
+
parent[last] = null;
|
|
2525
|
+
}
|
|
2526
|
+
}
|
|
2527
|
+
return copy;
|
|
2528
|
+
}
|
|
2529
|
+
async function handleStarlensExtract(request, signedDeploymentId) {
|
|
2530
|
+
const body = await readJsonBody(request);
|
|
2531
|
+
if (signedDeploymentId !== null && body.deploymentId !== signedDeploymentId) {
|
|
2532
|
+
return starlensFailure("Deployment ID mismatch", 401, "AUTHENTICATION_ERROR");
|
|
2533
|
+
}
|
|
2534
|
+
const inputs = Array.isArray(body.inputs) ? body.inputs : [];
|
|
2535
|
+
if (inputs.length === 0) {
|
|
2536
|
+
return starlensFailure(
|
|
2537
|
+
"Validation failed \u2014 inputs: at least one input",
|
|
2538
|
+
400,
|
|
2539
|
+
"VALIDATION_ERROR"
|
|
2540
|
+
);
|
|
2541
|
+
}
|
|
2542
|
+
const schema = body.schema;
|
|
2543
|
+
if (!schema || schema.type !== "object" || typeof schema.properties !== "object") {
|
|
2544
|
+
return starlensFailure(
|
|
2545
|
+
"Validation failed \u2014 schema.properties is required",
|
|
2546
|
+
400,
|
|
2547
|
+
"VALIDATION_ERROR"
|
|
2548
|
+
);
|
|
2549
|
+
}
|
|
2550
|
+
const call = {
|
|
2551
|
+
version: typeof body.version === "string" ? body.version : "latest",
|
|
2552
|
+
inputs: inputs.map((i) => ({
|
|
2553
|
+
name: typeof i.filename === "string" ? i.filename : typeof i.url === "string" ? i.url : "upload",
|
|
2554
|
+
mediaType: typeof i.mediaType === "string" ? i.mediaType : null,
|
|
2555
|
+
source: typeof i.url === "string" ? "url" : "data"
|
|
2556
|
+
})),
|
|
2557
|
+
schema,
|
|
2558
|
+
instructions: typeof body.instructions === "string" ? body.instructions : void 0
|
|
2559
|
+
};
|
|
2560
|
+
state.starlensCalls.push(call);
|
|
2561
|
+
const reply = state.starlensReplies.shift() ?? state.starlensDefaultReply;
|
|
2562
|
+
if (!reply) {
|
|
2563
|
+
return starlensFailure(
|
|
2564
|
+
"[stardeck-testing] No Starlens reply registered. Call app.starlens.mock({ output, issues }) or app.starlens.reject(reason) before the request.",
|
|
2565
|
+
502,
|
|
2566
|
+
"model_failed"
|
|
2567
|
+
);
|
|
2568
|
+
}
|
|
2569
|
+
if ("reject" in reply) {
|
|
2570
|
+
return starlensFailure(reply.reject, 422, "unsupported_document");
|
|
2571
|
+
}
|
|
2572
|
+
const issues = reply.issues ?? [];
|
|
2573
|
+
return success({
|
|
2574
|
+
version: call.version === "latest" ? "v1" : call.version,
|
|
2575
|
+
output: nullFlagged(reply.output, issues),
|
|
2576
|
+
issues,
|
|
2577
|
+
usage: { inputTokens: 0, outputTokens: 0, cost: 0 }
|
|
2578
|
+
});
|
|
2579
|
+
}
|
|
2580
|
+
function createStarlens() {
|
|
2581
|
+
return {
|
|
2582
|
+
mock(reply) {
|
|
2583
|
+
state.starlensReplies.push(reply);
|
|
2584
|
+
},
|
|
2585
|
+
reject(reason) {
|
|
2586
|
+
state.starlensReplies.push({ reject: reason });
|
|
2587
|
+
},
|
|
2588
|
+
mockDefault(reply) {
|
|
2589
|
+
state.starlensDefaultReply = reply;
|
|
2590
|
+
},
|
|
2591
|
+
get calls() {
|
|
2592
|
+
return state.starlensCalls;
|
|
2593
|
+
},
|
|
2594
|
+
latestCall() {
|
|
2595
|
+
return state.starlensCalls[state.starlensCalls.length - 1];
|
|
2596
|
+
},
|
|
2597
|
+
clear() {
|
|
2598
|
+
state.starlensCalls = [];
|
|
2599
|
+
state.starlensReplies = [];
|
|
2600
|
+
state.starlensDefaultReply = null;
|
|
2601
|
+
},
|
|
2602
|
+
get count() {
|
|
2603
|
+
return state.starlensCalls.length;
|
|
2604
|
+
}
|
|
2605
|
+
};
|
|
2606
|
+
}
|
|
2607
|
+
|
|
2507
2608
|
// src/simulator/router.ts
|
|
2508
2609
|
var fetchHolder = globalSingleton("fetch-holder", () => ({
|
|
2509
2610
|
originalFetch: null
|
|
@@ -2525,7 +2626,8 @@ function requiresDeploymentHmac(request, url) {
|
|
|
2525
2626
|
/^\/api\/deployments\/[^/]+\/integrations\/(slack|line|facebook)(\/.*)?$/
|
|
2526
2627
|
);
|
|
2527
2628
|
const edgeMatch = url.pathname.match(/^\/api\/deployments\/[^/]+\/edge(\/.*)?$/);
|
|
2528
|
-
|
|
2629
|
+
const isStarlens = url.pathname === "/api/starlens/extract";
|
|
2630
|
+
return !!(isStarlens || dataStoreMatch || isEmail || identitiesMatch || storeMatch || messagingMatch || edgeMatch);
|
|
2529
2631
|
}
|
|
2530
2632
|
function deploymentIdFromPath(url) {
|
|
2531
2633
|
const match = url.pathname.match(/\/api\/deployments\/([^/]+)(?:\/|$)/);
|
|
@@ -2555,6 +2657,7 @@ async function handleSimulatedRequest(request, url) {
|
|
|
2555
2657
|
);
|
|
2556
2658
|
const edgeMatch = url.pathname.match(/^\/api\/deployments\/[^/]+\/edge(\/.*)?$/);
|
|
2557
2659
|
const isStorageHost = url.hostname === STORAGE_TEST_HOST;
|
|
2660
|
+
let signedDeploymentId = null;
|
|
2558
2661
|
if (requiresDeploymentHmac(request, url)) {
|
|
2559
2662
|
const authHeader = request.headers.get("X-Stardeck-Auth");
|
|
2560
2663
|
if (!authHeader) {
|
|
@@ -2566,6 +2669,7 @@ async function handleSimulatedRequest(request, url) {
|
|
|
2566
2669
|
if (!authPayload || pathDeploymentId !== null && authPayload.deploymentId !== pathDeploymentId) {
|
|
2567
2670
|
return failure("Invalid authentication", 401);
|
|
2568
2671
|
}
|
|
2672
|
+
signedDeploymentId = authPayload.deploymentId;
|
|
2569
2673
|
}
|
|
2570
2674
|
if (isStorageHost) {
|
|
2571
2675
|
return handleStorageRequest(request, url);
|
|
@@ -2586,6 +2690,9 @@ async function handleSimulatedRequest(request, url) {
|
|
|
2586
2690
|
if (edgeMatch) {
|
|
2587
2691
|
return handleEdgeRequest(request, edgeMatch[1] ?? "");
|
|
2588
2692
|
}
|
|
2693
|
+
if (url.pathname === "/api/starlens/extract" && request.method === "POST") {
|
|
2694
|
+
return handleStarlensExtract(request, signedDeploymentId);
|
|
2695
|
+
}
|
|
2589
2696
|
if (dataStoreMatch) {
|
|
2590
2697
|
const subPath = dataStoreMatch[1] ?? "";
|
|
2591
2698
|
const db = requireDb();
|
|
@@ -2607,7 +2714,7 @@ async function handleSimulatedRequest(request, url) {
|
|
|
2607
2714
|
}
|
|
2608
2715
|
}
|
|
2609
2716
|
return failure(
|
|
2610
|
-
`[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.`,
|
|
2717
|
+
`[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, starlens extract, auth verify/refresh, Neon /sql.`,
|
|
2611
2718
|
404
|
|
2612
2719
|
);
|
|
2613
2720
|
}
|
|
@@ -2775,6 +2882,7 @@ async function createTestApp(options = {}) {
|
|
|
2775
2882
|
const storage = createStorage();
|
|
2776
2883
|
const messages = createMessages();
|
|
2777
2884
|
const edge = createEdge();
|
|
2885
|
+
const starlens = createStarlens();
|
|
2778
2886
|
const app = {
|
|
2779
2887
|
db,
|
|
2780
2888
|
inbox,
|
|
@@ -2783,6 +2891,7 @@ async function createTestApp(options = {}) {
|
|
|
2783
2891
|
storage,
|
|
2784
2892
|
messages,
|
|
2785
2893
|
edge,
|
|
2894
|
+
starlens,
|
|
2786
2895
|
async query(sql, params = []) {
|
|
2787
2896
|
const result = await db.query(sql, params);
|
|
2788
2897
|
return result.rows;
|
|
@@ -2824,6 +2933,7 @@ async function createTestApp(options = {}) {
|
|
|
2824
2933
|
storage.clear();
|
|
2825
2934
|
messages.clear();
|
|
2826
2935
|
edge.clear();
|
|
2936
|
+
starlens.clear();
|
|
2827
2937
|
},
|
|
2828
2938
|
async close() {
|
|
2829
2939
|
state.db = null;
|
|
@@ -2838,6 +2948,7 @@ async function createTestApp(options = {}) {
|
|
|
2838
2948
|
storage.clear();
|
|
2839
2949
|
messages.clear();
|
|
2840
2950
|
edge.clear();
|
|
2951
|
+
starlens.clear();
|
|
2841
2952
|
uninstallFetchRouter();
|
|
2842
2953
|
await db.close();
|
|
2843
2954
|
}
|
|
@@ -74,6 +74,9 @@ var state = globalSingleton("state", () => ({
|
|
|
74
74
|
edgeBindings: /* @__PURE__ */ new Map(),
|
|
75
75
|
edgeCapabilities: null,
|
|
76
76
|
edgePeripheralCapabilities: /* @__PURE__ */ new Map(),
|
|
77
|
+
starlensCalls: [],
|
|
78
|
+
starlensReplies: [],
|
|
79
|
+
starlensDefaultReply: null,
|
|
77
80
|
allowNetwork: false
|
|
78
81
|
}));
|
|
79
82
|
|
|
@@ -47,6 +47,9 @@ var state = globalSingleton("state", () => ({
|
|
|
47
47
|
edgeBindings: /* @__PURE__ */ new Map(),
|
|
48
48
|
edgeCapabilities: null,
|
|
49
49
|
edgePeripheralCapabilities: /* @__PURE__ */ new Map(),
|
|
50
|
+
starlensCalls: [],
|
|
51
|
+
starlensReplies: [],
|
|
52
|
+
starlensDefaultReply: null,
|
|
50
53
|
allowNetwork: false
|
|
51
54
|
}));
|
|
52
55
|
|
package/dist/setup.js
CHANGED
|
@@ -68,6 +68,9 @@ var state = globalSingleton("state", () => ({
|
|
|
68
68
|
edgeBindings: /* @__PURE__ */ new Map(),
|
|
69
69
|
edgeCapabilities: null,
|
|
70
70
|
edgePeripheralCapabilities: /* @__PURE__ */ new Map(),
|
|
71
|
+
starlensCalls: [],
|
|
72
|
+
starlensReplies: [],
|
|
73
|
+
starlensDefaultReply: null,
|
|
71
74
|
allowNetwork: false
|
|
72
75
|
}));
|
|
73
76
|
function requireDb() {
|
|
@@ -2000,6 +2003,77 @@ async function handleEdgeRequest(request, subPath) {
|
|
|
2000
2003
|
return edgeFailure(`No edge simulator for ${method} .../edge${subPath}`, 404);
|
|
2001
2004
|
}
|
|
2002
2005
|
|
|
2006
|
+
// src/simulator/starlens.ts
|
|
2007
|
+
function starlensFailure(error, status, code) {
|
|
2008
|
+
return json({ success: false, error, code }, status);
|
|
2009
|
+
}
|
|
2010
|
+
function nullFlagged(output, issues) {
|
|
2011
|
+
const copy = JSON.parse(JSON.stringify(output ?? null));
|
|
2012
|
+
for (const { path } of issues) {
|
|
2013
|
+
const keys = path.replace(/^output\./, "").replace(/\[(\d+)\]/g, ".$1").split(".");
|
|
2014
|
+
const last = keys.pop();
|
|
2015
|
+
const parent = keys.reduce(
|
|
2016
|
+
(o, k) => o != null && typeof o === "object" ? o[k] : void 0,
|
|
2017
|
+
copy
|
|
2018
|
+
);
|
|
2019
|
+
if (parent != null && typeof parent === "object" && Object.prototype.hasOwnProperty.call(parent, last)) {
|
|
2020
|
+
parent[last] = null;
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
2023
|
+
return copy;
|
|
2024
|
+
}
|
|
2025
|
+
async function handleStarlensExtract(request, signedDeploymentId) {
|
|
2026
|
+
const body = await readJsonBody(request);
|
|
2027
|
+
if (signedDeploymentId !== null && body.deploymentId !== signedDeploymentId) {
|
|
2028
|
+
return starlensFailure("Deployment ID mismatch", 401, "AUTHENTICATION_ERROR");
|
|
2029
|
+
}
|
|
2030
|
+
const inputs = Array.isArray(body.inputs) ? body.inputs : [];
|
|
2031
|
+
if (inputs.length === 0) {
|
|
2032
|
+
return starlensFailure(
|
|
2033
|
+
"Validation failed \u2014 inputs: at least one input",
|
|
2034
|
+
400,
|
|
2035
|
+
"VALIDATION_ERROR"
|
|
2036
|
+
);
|
|
2037
|
+
}
|
|
2038
|
+
const schema = body.schema;
|
|
2039
|
+
if (!schema || schema.type !== "object" || typeof schema.properties !== "object") {
|
|
2040
|
+
return starlensFailure(
|
|
2041
|
+
"Validation failed \u2014 schema.properties is required",
|
|
2042
|
+
400,
|
|
2043
|
+
"VALIDATION_ERROR"
|
|
2044
|
+
);
|
|
2045
|
+
}
|
|
2046
|
+
const call = {
|
|
2047
|
+
version: typeof body.version === "string" ? body.version : "latest",
|
|
2048
|
+
inputs: inputs.map((i) => ({
|
|
2049
|
+
name: typeof i.filename === "string" ? i.filename : typeof i.url === "string" ? i.url : "upload",
|
|
2050
|
+
mediaType: typeof i.mediaType === "string" ? i.mediaType : null,
|
|
2051
|
+
source: typeof i.url === "string" ? "url" : "data"
|
|
2052
|
+
})),
|
|
2053
|
+
schema,
|
|
2054
|
+
instructions: typeof body.instructions === "string" ? body.instructions : void 0
|
|
2055
|
+
};
|
|
2056
|
+
state.starlensCalls.push(call);
|
|
2057
|
+
const reply = state.starlensReplies.shift() ?? state.starlensDefaultReply;
|
|
2058
|
+
if (!reply) {
|
|
2059
|
+
return starlensFailure(
|
|
2060
|
+
"[stardeck-testing] No Starlens reply registered. Call app.starlens.mock({ output, issues }) or app.starlens.reject(reason) before the request.",
|
|
2061
|
+
502,
|
|
2062
|
+
"model_failed"
|
|
2063
|
+
);
|
|
2064
|
+
}
|
|
2065
|
+
if ("reject" in reply) {
|
|
2066
|
+
return starlensFailure(reply.reject, 422, "unsupported_document");
|
|
2067
|
+
}
|
|
2068
|
+
const issues = reply.issues ?? [];
|
|
2069
|
+
return success({
|
|
2070
|
+
version: call.version === "latest" ? "v1" : call.version,
|
|
2071
|
+
output: nullFlagged(reply.output, issues),
|
|
2072
|
+
issues,
|
|
2073
|
+
usage: { inputTokens: 0, outputTokens: 0, cost: 0 }
|
|
2074
|
+
});
|
|
2075
|
+
}
|
|
2076
|
+
|
|
2003
2077
|
// src/simulator/router.ts
|
|
2004
2078
|
var fetchHolder = globalSingleton("fetch-holder", () => ({
|
|
2005
2079
|
originalFetch: null
|
|
@@ -2021,7 +2095,8 @@ function requiresDeploymentHmac(request, url) {
|
|
|
2021
2095
|
/^\/api\/deployments\/[^/]+\/integrations\/(slack|line|facebook)(\/.*)?$/
|
|
2022
2096
|
);
|
|
2023
2097
|
const edgeMatch = url.pathname.match(/^\/api\/deployments\/[^/]+\/edge(\/.*)?$/);
|
|
2024
|
-
|
|
2098
|
+
const isStarlens = url.pathname === "/api/starlens/extract";
|
|
2099
|
+
return !!(isStarlens || dataStoreMatch || isEmail || identitiesMatch || storeMatch || messagingMatch || edgeMatch);
|
|
2025
2100
|
}
|
|
2026
2101
|
function deploymentIdFromPath(url) {
|
|
2027
2102
|
const match = url.pathname.match(/\/api\/deployments\/([^/]+)(?:\/|$)/);
|
|
@@ -2051,6 +2126,7 @@ async function handleSimulatedRequest(request, url) {
|
|
|
2051
2126
|
);
|
|
2052
2127
|
const edgeMatch = url.pathname.match(/^\/api\/deployments\/[^/]+\/edge(\/.*)?$/);
|
|
2053
2128
|
const isStorageHost = url.hostname === STORAGE_TEST_HOST;
|
|
2129
|
+
let signedDeploymentId = null;
|
|
2054
2130
|
if (requiresDeploymentHmac(request, url)) {
|
|
2055
2131
|
const authHeader = request.headers.get("X-Stardeck-Auth");
|
|
2056
2132
|
if (!authHeader) {
|
|
@@ -2062,6 +2138,7 @@ async function handleSimulatedRequest(request, url) {
|
|
|
2062
2138
|
if (!authPayload || pathDeploymentId !== null && authPayload.deploymentId !== pathDeploymentId) {
|
|
2063
2139
|
return failure("Invalid authentication", 401);
|
|
2064
2140
|
}
|
|
2141
|
+
signedDeploymentId = authPayload.deploymentId;
|
|
2065
2142
|
}
|
|
2066
2143
|
if (isStorageHost) {
|
|
2067
2144
|
return handleStorageRequest(request, url);
|
|
@@ -2082,6 +2159,9 @@ async function handleSimulatedRequest(request, url) {
|
|
|
2082
2159
|
if (edgeMatch) {
|
|
2083
2160
|
return handleEdgeRequest(request, edgeMatch[1] ?? "");
|
|
2084
2161
|
}
|
|
2162
|
+
if (url.pathname === "/api/starlens/extract" && request.method === "POST") {
|
|
2163
|
+
return handleStarlensExtract(request, signedDeploymentId);
|
|
2164
|
+
}
|
|
2085
2165
|
if (dataStoreMatch) {
|
|
2086
2166
|
const subPath = dataStoreMatch[1] ?? "";
|
|
2087
2167
|
const db = requireDb();
|
|
@@ -2103,7 +2183,7 @@ async function handleSimulatedRequest(request, url) {
|
|
|
2103
2183
|
}
|
|
2104
2184
|
}
|
|
2105
2185
|
return failure(
|
|
2106
|
-
`[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.`,
|
|
2186
|
+
`[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, starlens extract, auth verify/refresh, Neon /sql.`,
|
|
2107
2187
|
404
|
|
2108
2188
|
);
|
|
2109
2189
|
}
|
package/dist/setup.mjs
CHANGED
|
@@ -44,6 +44,9 @@ var state = globalSingleton("state", () => ({
|
|
|
44
44
|
edgeBindings: /* @__PURE__ */ new Map(),
|
|
45
45
|
edgeCapabilities: null,
|
|
46
46
|
edgePeripheralCapabilities: /* @__PURE__ */ new Map(),
|
|
47
|
+
starlensCalls: [],
|
|
48
|
+
starlensReplies: [],
|
|
49
|
+
starlensDefaultReply: null,
|
|
47
50
|
allowNetwork: false
|
|
48
51
|
}));
|
|
49
52
|
function requireDb() {
|
|
@@ -1976,6 +1979,77 @@ async function handleEdgeRequest(request, subPath) {
|
|
|
1976
1979
|
return edgeFailure(`No edge simulator for ${method} .../edge${subPath}`, 404);
|
|
1977
1980
|
}
|
|
1978
1981
|
|
|
1982
|
+
// src/simulator/starlens.ts
|
|
1983
|
+
function starlensFailure(error, status, code) {
|
|
1984
|
+
return json({ success: false, error, code }, status);
|
|
1985
|
+
}
|
|
1986
|
+
function nullFlagged(output, issues) {
|
|
1987
|
+
const copy = JSON.parse(JSON.stringify(output ?? null));
|
|
1988
|
+
for (const { path } of issues) {
|
|
1989
|
+
const keys = path.replace(/^output\./, "").replace(/\[(\d+)\]/g, ".$1").split(".");
|
|
1990
|
+
const last = keys.pop();
|
|
1991
|
+
const parent = keys.reduce(
|
|
1992
|
+
(o, k) => o != null && typeof o === "object" ? o[k] : void 0,
|
|
1993
|
+
copy
|
|
1994
|
+
);
|
|
1995
|
+
if (parent != null && typeof parent === "object" && Object.prototype.hasOwnProperty.call(parent, last)) {
|
|
1996
|
+
parent[last] = null;
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
return copy;
|
|
2000
|
+
}
|
|
2001
|
+
async function handleStarlensExtract(request, signedDeploymentId) {
|
|
2002
|
+
const body = await readJsonBody(request);
|
|
2003
|
+
if (signedDeploymentId !== null && body.deploymentId !== signedDeploymentId) {
|
|
2004
|
+
return starlensFailure("Deployment ID mismatch", 401, "AUTHENTICATION_ERROR");
|
|
2005
|
+
}
|
|
2006
|
+
const inputs = Array.isArray(body.inputs) ? body.inputs : [];
|
|
2007
|
+
if (inputs.length === 0) {
|
|
2008
|
+
return starlensFailure(
|
|
2009
|
+
"Validation failed \u2014 inputs: at least one input",
|
|
2010
|
+
400,
|
|
2011
|
+
"VALIDATION_ERROR"
|
|
2012
|
+
);
|
|
2013
|
+
}
|
|
2014
|
+
const schema = body.schema;
|
|
2015
|
+
if (!schema || schema.type !== "object" || typeof schema.properties !== "object") {
|
|
2016
|
+
return starlensFailure(
|
|
2017
|
+
"Validation failed \u2014 schema.properties is required",
|
|
2018
|
+
400,
|
|
2019
|
+
"VALIDATION_ERROR"
|
|
2020
|
+
);
|
|
2021
|
+
}
|
|
2022
|
+
const call = {
|
|
2023
|
+
version: typeof body.version === "string" ? body.version : "latest",
|
|
2024
|
+
inputs: inputs.map((i) => ({
|
|
2025
|
+
name: typeof i.filename === "string" ? i.filename : typeof i.url === "string" ? i.url : "upload",
|
|
2026
|
+
mediaType: typeof i.mediaType === "string" ? i.mediaType : null,
|
|
2027
|
+
source: typeof i.url === "string" ? "url" : "data"
|
|
2028
|
+
})),
|
|
2029
|
+
schema,
|
|
2030
|
+
instructions: typeof body.instructions === "string" ? body.instructions : void 0
|
|
2031
|
+
};
|
|
2032
|
+
state.starlensCalls.push(call);
|
|
2033
|
+
const reply = state.starlensReplies.shift() ?? state.starlensDefaultReply;
|
|
2034
|
+
if (!reply) {
|
|
2035
|
+
return starlensFailure(
|
|
2036
|
+
"[stardeck-testing] No Starlens reply registered. Call app.starlens.mock({ output, issues }) or app.starlens.reject(reason) before the request.",
|
|
2037
|
+
502,
|
|
2038
|
+
"model_failed"
|
|
2039
|
+
);
|
|
2040
|
+
}
|
|
2041
|
+
if ("reject" in reply) {
|
|
2042
|
+
return starlensFailure(reply.reject, 422, "unsupported_document");
|
|
2043
|
+
}
|
|
2044
|
+
const issues = reply.issues ?? [];
|
|
2045
|
+
return success({
|
|
2046
|
+
version: call.version === "latest" ? "v1" : call.version,
|
|
2047
|
+
output: nullFlagged(reply.output, issues),
|
|
2048
|
+
issues,
|
|
2049
|
+
usage: { inputTokens: 0, outputTokens: 0, cost: 0 }
|
|
2050
|
+
});
|
|
2051
|
+
}
|
|
2052
|
+
|
|
1979
2053
|
// src/simulator/router.ts
|
|
1980
2054
|
var fetchHolder = globalSingleton("fetch-holder", () => ({
|
|
1981
2055
|
originalFetch: null
|
|
@@ -1997,7 +2071,8 @@ function requiresDeploymentHmac(request, url) {
|
|
|
1997
2071
|
/^\/api\/deployments\/[^/]+\/integrations\/(slack|line|facebook)(\/.*)?$/
|
|
1998
2072
|
);
|
|
1999
2073
|
const edgeMatch = url.pathname.match(/^\/api\/deployments\/[^/]+\/edge(\/.*)?$/);
|
|
2000
|
-
|
|
2074
|
+
const isStarlens = url.pathname === "/api/starlens/extract";
|
|
2075
|
+
return !!(isStarlens || dataStoreMatch || isEmail || identitiesMatch || storeMatch || messagingMatch || edgeMatch);
|
|
2001
2076
|
}
|
|
2002
2077
|
function deploymentIdFromPath(url) {
|
|
2003
2078
|
const match = url.pathname.match(/\/api\/deployments\/([^/]+)(?:\/|$)/);
|
|
@@ -2027,6 +2102,7 @@ async function handleSimulatedRequest(request, url) {
|
|
|
2027
2102
|
);
|
|
2028
2103
|
const edgeMatch = url.pathname.match(/^\/api\/deployments\/[^/]+\/edge(\/.*)?$/);
|
|
2029
2104
|
const isStorageHost = url.hostname === STORAGE_TEST_HOST;
|
|
2105
|
+
let signedDeploymentId = null;
|
|
2030
2106
|
if (requiresDeploymentHmac(request, url)) {
|
|
2031
2107
|
const authHeader = request.headers.get("X-Stardeck-Auth");
|
|
2032
2108
|
if (!authHeader) {
|
|
@@ -2038,6 +2114,7 @@ async function handleSimulatedRequest(request, url) {
|
|
|
2038
2114
|
if (!authPayload || pathDeploymentId !== null && authPayload.deploymentId !== pathDeploymentId) {
|
|
2039
2115
|
return failure("Invalid authentication", 401);
|
|
2040
2116
|
}
|
|
2117
|
+
signedDeploymentId = authPayload.deploymentId;
|
|
2041
2118
|
}
|
|
2042
2119
|
if (isStorageHost) {
|
|
2043
2120
|
return handleStorageRequest(request, url);
|
|
@@ -2058,6 +2135,9 @@ async function handleSimulatedRequest(request, url) {
|
|
|
2058
2135
|
if (edgeMatch) {
|
|
2059
2136
|
return handleEdgeRequest(request, edgeMatch[1] ?? "");
|
|
2060
2137
|
}
|
|
2138
|
+
if (url.pathname === "/api/starlens/extract" && request.method === "POST") {
|
|
2139
|
+
return handleStarlensExtract(request, signedDeploymentId);
|
|
2140
|
+
}
|
|
2061
2141
|
if (dataStoreMatch) {
|
|
2062
2142
|
const subPath = dataStoreMatch[1] ?? "";
|
|
2063
2143
|
const db = requireDb();
|
|
@@ -2079,7 +2159,7 @@ async function handleSimulatedRequest(request, url) {
|
|
|
2079
2159
|
}
|
|
2080
2160
|
}
|
|
2081
2161
|
return failure(
|
|
2082
|
-
`[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.`,
|
|
2162
|
+
`[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, starlens extract, auth verify/refresh, Neon /sql.`,
|
|
2083
2163
|
404
|
|
2084
2164
|
);
|
|
2085
2165
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stardeck-customer-apps/testing",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.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",
|
|
@@ -70,7 +70,8 @@
|
|
|
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",
|
|
73
|
-
"vitest": ">=2.0.0"
|
|
73
|
+
"vitest": ">=2.0.0",
|
|
74
|
+
"@stardeck-customer-apps/starlens": ">=0.1.0"
|
|
74
75
|
},
|
|
75
76
|
"peerDependenciesMeta": {
|
|
76
77
|
"@stardeck-customer-apps/integrations-sdk": {
|
|
@@ -87,6 +88,9 @@
|
|
|
87
88
|
},
|
|
88
89
|
"next": {
|
|
89
90
|
"optional": true
|
|
91
|
+
},
|
|
92
|
+
"@stardeck-customer-apps/starlens": {
|
|
93
|
+
"optional": true
|
|
90
94
|
}
|
|
91
95
|
},
|
|
92
96
|
"devDependencies": {
|
|
@@ -106,6 +110,8 @@
|
|
|
106
110
|
"tsup": "^8.0.0",
|
|
107
111
|
"typescript": "^5.0.0",
|
|
108
112
|
"typescript-eslint": "^8.0.0",
|
|
109
|
-
"vitest": "^3.2.4"
|
|
113
|
+
"vitest": "^3.2.4",
|
|
114
|
+
"@stardeck-customer-apps/starlens": "*",
|
|
115
|
+
"zod": "^4.3.5"
|
|
110
116
|
}
|
|
111
117
|
}
|