@stardeck-customer-apps/testing 0.25.0 → 0.26.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
@@ -568,7 +568,8 @@ cursor, limit })`; `list()` intentionally keeps its previous array shape.
568
568
  - `createEdgeClient()` (print, `enqueueReceipt()`/`getPrintJob()` durable receipts: idempotent per key,
569
569
  conflicts and refused options mirrored, `app.edge.durablePrints`, and
570
570
  `app.edge.settleNextDurableJob({ status: "needs_attention", reason, outcome })` to drive the
571
- uncertain/not-printed UI; pair/unpair, list devices/peripherals, show/clear
571
+ uncertain/not-printed UI, or `{ status: "sent_to_printer", simulated: true }` to mirror a
572
+ virtual-printer job (the status then carries `simulated: true`); pair/unpair, list devices/peripherals, show/clear
572
573
  display, test print, `getCapabilities()`) — captured in `app.edge`; seed
573
574
  devices/peripherals for pairing pickers via `app.edge.seedDevices()` /
574
575
  `seedPeripherals()`.
@@ -585,6 +586,12 @@ cursor, limit })`; `list()` intentionally keeps its previous array shape.
585
586
  high request with no image input answers `effort: "low"` as production does;
586
587
  pass `effort: "low"` in `app.starlens.mock(...)` to simulate an image production
587
588
  could not decode.
589
+ - `wakeSchedule(key, at)` from the scheduling SDK — there is no scheduler in tests: each
590
+ wake is recorded in `app.schedules`. After a write that creates work for an adaptive job,
591
+ assert the job was asked for in time:
592
+ `expect(app.schedules.earliestWake("booking-janitor")!.getTime()).toBeLessThanOrEqual(dueAt.getTime())`.
593
+ `app.schedules.wakesFor(key)` lists them; `reset()` clears them. Run the job's handler
594
+ yourself to test what it does and the `nextRun()` it returns.
588
595
 
589
596
  ### Receipt layout and printer capabilities
590
597
 
package/dist/index.d.mts CHANGED
@@ -555,8 +555,10 @@ interface CapturedDurablePrint {
555
555
  /** How a simulated durable job settles. Mirrors the platform's status contract. */
556
556
  type SimulatedDurableOutcome = {
557
557
  status: "sent_to_printer";
558
+ simulated?: boolean;
558
559
  } | {
559
560
  status: "queued";
561
+ simulated?: boolean;
560
562
  } | {
561
563
  status: "needs_attention";
562
564
  reason: string;
@@ -650,6 +652,20 @@ interface TestStarlens {
650
652
  clear(): void;
651
653
  get count(): number;
652
654
  }
655
+ /** A wake the app asked for: run `scheduleKey` by `at` (scheduling-sdk `wakeSchedule`). */
656
+ interface CapturedScheduleWake {
657
+ scheduleKey: string;
658
+ at: Date;
659
+ requestedAt: Date;
660
+ }
661
+ interface TestSchedules {
662
+ /** Every wake the app asked for, oldest first. */
663
+ get wakes(): CapturedScheduleWake[];
664
+ wakesFor(scheduleKey: string): CapturedScheduleWake[];
665
+ /** The earliest `at` any wake asked of `scheduleKey`, or undefined if none did. */
666
+ earliestWake(scheduleKey: string): Date | undefined;
667
+ clear(): void;
668
+ }
653
669
  interface TestAppOptions {
654
670
  /**
655
671
  * The LIFF ID the platform forwards to the app for a LINE MINI App shop. It is
@@ -702,6 +718,8 @@ interface TestApp {
702
718
  edge: TestEdge;
703
719
  /** Scripted replies and captured calls for the Starlens SDK. */
704
720
  starlens: TestStarlens;
721
+ /** Wakes the app asked of its adaptive schedules (`wakeSchedule`). */
722
+ schedules: TestSchedules;
705
723
  /** Convenience for raw SQL: `app.query("SELECT ...", [param])`. */
706
724
  query<T = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<T[]>;
707
725
  /**
@@ -772,4 +790,4 @@ declare const TEST_ENV_DEFAULTS: {
772
790
  /** Default location of the DDL snapshot written by `generate-types`. */
773
791
  declare const DEFAULT_SCHEMA_PATH = "./src/generated/data-store-schema.sql";
774
792
 
775
- export { type BindingInfo, CONTROL_PLANE_TEST_URL, type CallRouteOptions, type CapturedCheckout, type CapturedDisplay, type CapturedDurablePrint, type CapturedEmail, type CapturedIdentity, type CapturedIdentityLink, type CapturedMessage, type CapturedPrint, type CapturedStarlensCall, type CapturedTestPrint, type CapturedUpload, DATA_STORE_TEST_HOST, DATA_STORE_TEST_URL, DEFAULT_SCHEMA_PATH, type DeviceInfo, type PaymentSimulatorFailure, type PaymentSimulatorOperation, type PeripheralInfo, STORAGE_TEST_HOST, STORAGE_TEST_URL, type SessionTokens, type SimBoltCharge, type SimBoltConnection, type SimBoltIntentRecord, type SimBoltIntentStatus, type SimulatedDurableOutcome, type StarlensReply, TEST_ENV_DEFAULTS, type TestApp, type TestAppOptions, TestDataStoreInput, type TestDirectory, type TestEdge, type TestInbox, type TestMessages, type TestPayments, type TestStarlens, type TestStorage, type TestUser, WORKFLOW_NAME_PREFIX, callRoute, createTestApp, describeWorkflow, parseWorkflowName };
793
+ export { type BindingInfo, CONTROL_PLANE_TEST_URL, type CallRouteOptions, type CapturedCheckout, type CapturedDisplay, type CapturedDurablePrint, type CapturedEmail, type CapturedIdentity, type CapturedIdentityLink, type CapturedMessage, type CapturedPrint, type CapturedScheduleWake, type CapturedStarlensCall, type CapturedTestPrint, type CapturedUpload, DATA_STORE_TEST_HOST, DATA_STORE_TEST_URL, DEFAULT_SCHEMA_PATH, type DeviceInfo, type PaymentSimulatorFailure, type PaymentSimulatorOperation, type PeripheralInfo, STORAGE_TEST_HOST, STORAGE_TEST_URL, type SessionTokens, type SimBoltCharge, type SimBoltConnection, type SimBoltIntentRecord, type SimBoltIntentStatus, type SimulatedDurableOutcome, type StarlensReply, TEST_ENV_DEFAULTS, type TestApp, type TestAppOptions, TestDataStoreInput, type TestDirectory, type TestEdge, type TestInbox, type TestMessages, type TestPayments, type TestSchedules, type TestStarlens, type TestStorage, type TestUser, WORKFLOW_NAME_PREFIX, callRoute, createTestApp, describeWorkflow, parseWorkflowName };
package/dist/index.d.ts CHANGED
@@ -555,8 +555,10 @@ interface CapturedDurablePrint {
555
555
  /** How a simulated durable job settles. Mirrors the platform's status contract. */
556
556
  type SimulatedDurableOutcome = {
557
557
  status: "sent_to_printer";
558
+ simulated?: boolean;
558
559
  } | {
559
560
  status: "queued";
561
+ simulated?: boolean;
560
562
  } | {
561
563
  status: "needs_attention";
562
564
  reason: string;
@@ -650,6 +652,20 @@ interface TestStarlens {
650
652
  clear(): void;
651
653
  get count(): number;
652
654
  }
655
+ /** A wake the app asked for: run `scheduleKey` by `at` (scheduling-sdk `wakeSchedule`). */
656
+ interface CapturedScheduleWake {
657
+ scheduleKey: string;
658
+ at: Date;
659
+ requestedAt: Date;
660
+ }
661
+ interface TestSchedules {
662
+ /** Every wake the app asked for, oldest first. */
663
+ get wakes(): CapturedScheduleWake[];
664
+ wakesFor(scheduleKey: string): CapturedScheduleWake[];
665
+ /** The earliest `at` any wake asked of `scheduleKey`, or undefined if none did. */
666
+ earliestWake(scheduleKey: string): Date | undefined;
667
+ clear(): void;
668
+ }
653
669
  interface TestAppOptions {
654
670
  /**
655
671
  * The LIFF ID the platform forwards to the app for a LINE MINI App shop. It is
@@ -702,6 +718,8 @@ interface TestApp {
702
718
  edge: TestEdge;
703
719
  /** Scripted replies and captured calls for the Starlens SDK. */
704
720
  starlens: TestStarlens;
721
+ /** Wakes the app asked of its adaptive schedules (`wakeSchedule`). */
722
+ schedules: TestSchedules;
705
723
  /** Convenience for raw SQL: `app.query("SELECT ...", [param])`. */
706
724
  query<T = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<T[]>;
707
725
  /**
@@ -772,4 +790,4 @@ declare const TEST_ENV_DEFAULTS: {
772
790
  /** Default location of the DDL snapshot written by `generate-types`. */
773
791
  declare const DEFAULT_SCHEMA_PATH = "./src/generated/data-store-schema.sql";
774
792
 
775
- export { type BindingInfo, CONTROL_PLANE_TEST_URL, type CallRouteOptions, type CapturedCheckout, type CapturedDisplay, type CapturedDurablePrint, type CapturedEmail, type CapturedIdentity, type CapturedIdentityLink, type CapturedMessage, type CapturedPrint, type CapturedStarlensCall, type CapturedTestPrint, type CapturedUpload, DATA_STORE_TEST_HOST, DATA_STORE_TEST_URL, DEFAULT_SCHEMA_PATH, type DeviceInfo, type PaymentSimulatorFailure, type PaymentSimulatorOperation, type PeripheralInfo, STORAGE_TEST_HOST, STORAGE_TEST_URL, type SessionTokens, type SimBoltCharge, type SimBoltConnection, type SimBoltIntentRecord, type SimBoltIntentStatus, type SimulatedDurableOutcome, type StarlensReply, TEST_ENV_DEFAULTS, type TestApp, type TestAppOptions, TestDataStoreInput, type TestDirectory, type TestEdge, type TestInbox, type TestMessages, type TestPayments, type TestStarlens, type TestStorage, type TestUser, WORKFLOW_NAME_PREFIX, callRoute, createTestApp, describeWorkflow, parseWorkflowName };
793
+ export { type BindingInfo, CONTROL_PLANE_TEST_URL, type CallRouteOptions, type CapturedCheckout, type CapturedDisplay, type CapturedDurablePrint, type CapturedEmail, type CapturedIdentity, type CapturedIdentityLink, type CapturedMessage, type CapturedPrint, type CapturedScheduleWake, type CapturedStarlensCall, type CapturedTestPrint, type CapturedUpload, DATA_STORE_TEST_HOST, DATA_STORE_TEST_URL, DEFAULT_SCHEMA_PATH, type DeviceInfo, type PaymentSimulatorFailure, type PaymentSimulatorOperation, type PeripheralInfo, STORAGE_TEST_HOST, STORAGE_TEST_URL, type SessionTokens, type SimBoltCharge, type SimBoltConnection, type SimBoltIntentRecord, type SimBoltIntentStatus, type SimulatedDurableOutcome, type StarlensReply, TEST_ENV_DEFAULTS, type TestApp, type TestAppOptions, TestDataStoreInput, type TestDirectory, type TestEdge, type TestInbox, type TestMessages, type TestPayments, type TestSchedules, type TestStarlens, type TestStorage, type TestUser, WORKFLOW_NAME_PREFIX, callRoute, createTestApp, describeWorkflow, parseWorkflowName };
package/dist/index.js CHANGED
@@ -338,6 +338,7 @@ var state = globalSingleton("state", () => ({
338
338
  identitySuppressClaimDelivery: false,
339
339
  identityAllowedReturnOrigins: /* @__PURE__ */ new Set([APP_TEST_ORIGIN]),
340
340
  checkouts: [],
341
+ scheduleWakes: [],
341
342
  checkoutCounter: 0,
342
343
  sessionStatuses: /* @__PURE__ */ new Map(),
343
344
  idempotentCheckouts: /* @__PURE__ */ new Map(),
@@ -3440,7 +3441,9 @@ async function handleEnqueue(request) {
3440
3441
  function durableView(jobId, as) {
3441
3442
  const job = state.edgeDurableJobs.get(jobId);
3442
3443
  const createdAt = now2();
3444
+ const simulated = job.outcome.status !== "needs_attention" && job.outcome.simulated ? { simulated: true } : {};
3443
3445
  const base = {
3446
+ ...simulated,
3444
3447
  jobId,
3445
3448
  attempts: as === "queued" ? 0 : 1,
3446
3449
  createdAt,
@@ -3624,6 +3627,32 @@ function createEdge() {
3624
3627
  };
3625
3628
  }
3626
3629
 
3630
+ // src/simulator/schedules.ts
3631
+ async function handleScheduleWake(request, scheduleKey) {
3632
+ if (request.method !== "POST") return failure(`Method ${request.method} not allowed`, 405);
3633
+ const body = await readJsonBody(request);
3634
+ const at = typeof body.at === "string" ? new Date(body.at) : null;
3635
+ if (!at || Number.isNaN(at.getTime())) return failure("`at` must be an ISO date-time", 400);
3636
+ state.scheduleWakes.push({ scheduleKey, at, requestedAt: /* @__PURE__ */ new Date() });
3637
+ return success({ woken: true });
3638
+ }
3639
+ function createSchedules() {
3640
+ return {
3641
+ get wakes() {
3642
+ return state.scheduleWakes;
3643
+ },
3644
+ wakesFor(scheduleKey) {
3645
+ return state.scheduleWakes.filter((wake) => wake.scheduleKey === scheduleKey);
3646
+ },
3647
+ earliestWake(scheduleKey) {
3648
+ return this.wakesFor(scheduleKey).map((wake) => wake.at).sort((a, b) => a.getTime() - b.getTime())[0];
3649
+ },
3650
+ clear() {
3651
+ state.scheduleWakes = [];
3652
+ }
3653
+ };
3654
+ }
3655
+
3627
3656
  // src/simulator/starlens.ts
3628
3657
  var LATEST_VERSION = "v2";
3629
3658
  function starlensFailure(error, status, code) {
@@ -3788,8 +3817,10 @@ function requiresDeploymentHmac(request, url) {
3788
3817
  );
3789
3818
  const edgeMatch = url.pathname.match(/^\/api\/deployments\/[^/]+\/edge(\/.*)?$/);
3790
3819
  const isStarlens = url.pathname === "/api/starlens/extract";
3791
- return !!(isStarlens || dataStoreMatch || isEmail || identitiesMatch || storeMatch || messagingMatch || edgeMatch);
3820
+ const scheduleWakeMatch = url.pathname.match(SCHEDULE_WAKE_PATH);
3821
+ return !!(isStarlens || scheduleWakeMatch || dataStoreMatch || isEmail || identitiesMatch || storeMatch || messagingMatch || edgeMatch);
3792
3822
  }
3823
+ var SCHEDULE_WAKE_PATH = /^\/api\/deployments\/[^/]+\/schedules\/([^/]+)\/wake$/;
3793
3824
  function deploymentIdFromPath(url) {
3794
3825
  const match = url.pathname.match(/\/api\/deployments\/([^/]+)(?:\/|$)/);
3795
3826
  if (!match) return null;
@@ -3854,6 +3885,10 @@ async function handleSimulatedRequest(request, url) {
3854
3885
  if (edgeMatch) {
3855
3886
  return handleEdgeRequest(request, edgeMatch[1] ?? "");
3856
3887
  }
3888
+ const scheduleWakeMatch = url.pathname.match(SCHEDULE_WAKE_PATH);
3889
+ if (scheduleWakeMatch) {
3890
+ return handleScheduleWake(request, decodeURIComponent(scheduleWakeMatch[1]));
3891
+ }
3857
3892
  if (url.pathname === "/api/starlens/extract" && request.method === "POST") {
3858
3893
  return handleStarlensExtract(request, signedDeploymentId);
3859
3894
  }
@@ -3878,7 +3913,7 @@ async function handleSimulatedRequest(request, url) {
3878
3913
  }
3879
3914
  }
3880
3915
  return failure(
3881
- `[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.`,
3916
+ `[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, schedule wakes, auth verify/refresh, Neon /sql.`,
3882
3917
  404
3883
3918
  );
3884
3919
  }
@@ -4057,6 +4092,7 @@ async function createTestApp(options = {}) {
4057
4092
  const messages = createMessages();
4058
4093
  const edge = createEdge();
4059
4094
  const starlens = createStarlens();
4095
+ const schedules = createSchedules();
4060
4096
  const app = {
4061
4097
  db,
4062
4098
  inbox,
@@ -4066,6 +4102,7 @@ async function createTestApp(options = {}) {
4066
4102
  messages,
4067
4103
  edge,
4068
4104
  starlens,
4105
+ schedules,
4069
4106
  async query(sql, params = []) {
4070
4107
  const result = await db.query(sql, params);
4071
4108
  return result.rows;
@@ -4114,6 +4151,7 @@ async function createTestApp(options = {}) {
4114
4151
  messages.clear();
4115
4152
  edge.clear();
4116
4153
  starlens.clear();
4154
+ schedules.clear();
4117
4155
  },
4118
4156
  async close() {
4119
4157
  state.db = null;
@@ -4129,6 +4167,7 @@ async function createTestApp(options = {}) {
4129
4167
  messages.clear();
4130
4168
  edge.clear();
4131
4169
  starlens.clear();
4170
+ schedules.clear();
4132
4171
  uninstallFetchRouter();
4133
4172
  await db.close();
4134
4173
  }
package/dist/index.mjs CHANGED
@@ -299,6 +299,7 @@ var state = globalSingleton("state", () => ({
299
299
  identitySuppressClaimDelivery: false,
300
300
  identityAllowedReturnOrigins: /* @__PURE__ */ new Set([APP_TEST_ORIGIN]),
301
301
  checkouts: [],
302
+ scheduleWakes: [],
302
303
  checkoutCounter: 0,
303
304
  sessionStatuses: /* @__PURE__ */ new Map(),
304
305
  idempotentCheckouts: /* @__PURE__ */ new Map(),
@@ -3401,7 +3402,9 @@ async function handleEnqueue(request) {
3401
3402
  function durableView(jobId, as) {
3402
3403
  const job = state.edgeDurableJobs.get(jobId);
3403
3404
  const createdAt = now2();
3405
+ const simulated = job.outcome.status !== "needs_attention" && job.outcome.simulated ? { simulated: true } : {};
3404
3406
  const base = {
3407
+ ...simulated,
3405
3408
  jobId,
3406
3409
  attempts: as === "queued" ? 0 : 1,
3407
3410
  createdAt,
@@ -3585,6 +3588,32 @@ function createEdge() {
3585
3588
  };
3586
3589
  }
3587
3590
 
3591
+ // src/simulator/schedules.ts
3592
+ async function handleScheduleWake(request, scheduleKey) {
3593
+ if (request.method !== "POST") return failure(`Method ${request.method} not allowed`, 405);
3594
+ const body = await readJsonBody(request);
3595
+ const at = typeof body.at === "string" ? new Date(body.at) : null;
3596
+ if (!at || Number.isNaN(at.getTime())) return failure("`at` must be an ISO date-time", 400);
3597
+ state.scheduleWakes.push({ scheduleKey, at, requestedAt: /* @__PURE__ */ new Date() });
3598
+ return success({ woken: true });
3599
+ }
3600
+ function createSchedules() {
3601
+ return {
3602
+ get wakes() {
3603
+ return state.scheduleWakes;
3604
+ },
3605
+ wakesFor(scheduleKey) {
3606
+ return state.scheduleWakes.filter((wake) => wake.scheduleKey === scheduleKey);
3607
+ },
3608
+ earliestWake(scheduleKey) {
3609
+ return this.wakesFor(scheduleKey).map((wake) => wake.at).sort((a, b) => a.getTime() - b.getTime())[0];
3610
+ },
3611
+ clear() {
3612
+ state.scheduleWakes = [];
3613
+ }
3614
+ };
3615
+ }
3616
+
3588
3617
  // src/simulator/starlens.ts
3589
3618
  var LATEST_VERSION = "v2";
3590
3619
  function starlensFailure(error, status, code) {
@@ -3749,8 +3778,10 @@ function requiresDeploymentHmac(request, url) {
3749
3778
  );
3750
3779
  const edgeMatch = url.pathname.match(/^\/api\/deployments\/[^/]+\/edge(\/.*)?$/);
3751
3780
  const isStarlens = url.pathname === "/api/starlens/extract";
3752
- return !!(isStarlens || dataStoreMatch || isEmail || identitiesMatch || storeMatch || messagingMatch || edgeMatch);
3781
+ const scheduleWakeMatch = url.pathname.match(SCHEDULE_WAKE_PATH);
3782
+ return !!(isStarlens || scheduleWakeMatch || dataStoreMatch || isEmail || identitiesMatch || storeMatch || messagingMatch || edgeMatch);
3753
3783
  }
3784
+ var SCHEDULE_WAKE_PATH = /^\/api\/deployments\/[^/]+\/schedules\/([^/]+)\/wake$/;
3754
3785
  function deploymentIdFromPath(url) {
3755
3786
  const match = url.pathname.match(/\/api\/deployments\/([^/]+)(?:\/|$)/);
3756
3787
  if (!match) return null;
@@ -3815,6 +3846,10 @@ async function handleSimulatedRequest(request, url) {
3815
3846
  if (edgeMatch) {
3816
3847
  return handleEdgeRequest(request, edgeMatch[1] ?? "");
3817
3848
  }
3849
+ const scheduleWakeMatch = url.pathname.match(SCHEDULE_WAKE_PATH);
3850
+ if (scheduleWakeMatch) {
3851
+ return handleScheduleWake(request, decodeURIComponent(scheduleWakeMatch[1]));
3852
+ }
3818
3853
  if (url.pathname === "/api/starlens/extract" && request.method === "POST") {
3819
3854
  return handleStarlensExtract(request, signedDeploymentId);
3820
3855
  }
@@ -3839,7 +3874,7 @@ async function handleSimulatedRequest(request, url) {
3839
3874
  }
3840
3875
  }
3841
3876
  return failure(
3842
- `[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.`,
3877
+ `[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, schedule wakes, auth verify/refresh, Neon /sql.`,
3843
3878
  404
3844
3879
  );
3845
3880
  }
@@ -4018,6 +4053,7 @@ async function createTestApp(options = {}) {
4018
4053
  const messages = createMessages();
4019
4054
  const edge = createEdge();
4020
4055
  const starlens = createStarlens();
4056
+ const schedules = createSchedules();
4021
4057
  const app = {
4022
4058
  db,
4023
4059
  inbox,
@@ -4027,6 +4063,7 @@ async function createTestApp(options = {}) {
4027
4063
  messages,
4028
4064
  edge,
4029
4065
  starlens,
4066
+ schedules,
4030
4067
  async query(sql, params = []) {
4031
4068
  const result = await db.query(sql, params);
4032
4069
  return result.rows;
@@ -4075,6 +4112,7 @@ async function createTestApp(options = {}) {
4075
4112
  messages.clear();
4076
4113
  edge.clear();
4077
4114
  starlens.clear();
4115
+ schedules.clear();
4078
4116
  },
4079
4117
  async close() {
4080
4118
  state.db = null;
@@ -4090,6 +4128,7 @@ async function createTestApp(options = {}) {
4090
4128
  messages.clear();
4091
4129
  edge.clear();
4092
4130
  starlens.clear();
4131
+ schedules.clear();
4093
4132
  uninstallFetchRouter();
4094
4133
  await db.close();
4095
4134
  }
@@ -60,6 +60,7 @@ var state = globalSingleton("state", () => ({
60
60
  identitySuppressClaimDelivery: false,
61
61
  identityAllowedReturnOrigins: /* @__PURE__ */ new Set([APP_TEST_ORIGIN]),
62
62
  checkouts: [],
63
+ scheduleWakes: [],
63
64
  checkoutCounter: 0,
64
65
  sessionStatuses: /* @__PURE__ */ new Map(),
65
66
  idempotentCheckouts: /* @__PURE__ */ new Map(),
@@ -33,6 +33,7 @@ var state = globalSingleton("state", () => ({
33
33
  identitySuppressClaimDelivery: false,
34
34
  identityAllowedReturnOrigins: /* @__PURE__ */ new Set([APP_TEST_ORIGIN]),
35
35
  checkouts: [],
36
+ scheduleWakes: [],
36
37
  checkoutCounter: 0,
37
38
  sessionStatuses: /* @__PURE__ */ new Map(),
38
39
  idempotentCheckouts: /* @__PURE__ */ new Map(),
package/dist/setup.js CHANGED
@@ -65,6 +65,7 @@ var state = globalSingleton("state", () => ({
65
65
  identitySuppressClaimDelivery: false,
66
66
  identityAllowedReturnOrigins: /* @__PURE__ */ new Set([APP_TEST_ORIGIN]),
67
67
  checkouts: [],
68
+ scheduleWakes: [],
68
69
  checkoutCounter: 0,
69
70
  sessionStatuses: /* @__PURE__ */ new Map(),
70
71
  idempotentCheckouts: /* @__PURE__ */ new Map(),
@@ -2585,7 +2586,9 @@ async function handleEnqueue(request) {
2585
2586
  function durableView(jobId, as) {
2586
2587
  const job = state.edgeDurableJobs.get(jobId);
2587
2588
  const createdAt = now2();
2589
+ const simulated = job.outcome.status !== "needs_attention" && job.outcome.simulated ? { simulated: true } : {};
2588
2590
  const base = {
2591
+ ...simulated,
2589
2592
  jobId,
2590
2593
  attempts: as === "queued" ? 0 : 1,
2591
2594
  createdAt,
@@ -2705,6 +2708,16 @@ async function handleEdgeRequest(request, subPath) {
2705
2708
  return edgeFailure(`No edge simulator for ${method} .../edge${subPath}`, 404);
2706
2709
  }
2707
2710
 
2711
+ // src/simulator/schedules.ts
2712
+ async function handleScheduleWake(request, scheduleKey) {
2713
+ if (request.method !== "POST") return failure(`Method ${request.method} not allowed`, 405);
2714
+ const body = await readJsonBody(request);
2715
+ const at = typeof body.at === "string" ? new Date(body.at) : null;
2716
+ if (!at || Number.isNaN(at.getTime())) return failure("`at` must be an ISO date-time", 400);
2717
+ state.scheduleWakes.push({ scheduleKey, at, requestedAt: /* @__PURE__ */ new Date() });
2718
+ return success({ woken: true });
2719
+ }
2720
+
2708
2721
  // src/simulator/starlens.ts
2709
2722
  var LATEST_VERSION = "v2";
2710
2723
  function starlensFailure(error, status, code) {
@@ -2842,8 +2855,10 @@ function requiresDeploymentHmac(request, url) {
2842
2855
  );
2843
2856
  const edgeMatch = url.pathname.match(/^\/api\/deployments\/[^/]+\/edge(\/.*)?$/);
2844
2857
  const isStarlens = url.pathname === "/api/starlens/extract";
2845
- return !!(isStarlens || dataStoreMatch || isEmail || identitiesMatch || storeMatch || messagingMatch || edgeMatch);
2858
+ const scheduleWakeMatch = url.pathname.match(SCHEDULE_WAKE_PATH);
2859
+ return !!(isStarlens || scheduleWakeMatch || dataStoreMatch || isEmail || identitiesMatch || storeMatch || messagingMatch || edgeMatch);
2846
2860
  }
2861
+ var SCHEDULE_WAKE_PATH = /^\/api\/deployments\/[^/]+\/schedules\/([^/]+)\/wake$/;
2847
2862
  function deploymentIdFromPath(url) {
2848
2863
  const match = url.pathname.match(/\/api\/deployments\/([^/]+)(?:\/|$)/);
2849
2864
  if (!match) return null;
@@ -2908,6 +2923,10 @@ async function handleSimulatedRequest(request, url) {
2908
2923
  if (edgeMatch) {
2909
2924
  return handleEdgeRequest(request, edgeMatch[1] ?? "");
2910
2925
  }
2926
+ const scheduleWakeMatch = url.pathname.match(SCHEDULE_WAKE_PATH);
2927
+ if (scheduleWakeMatch) {
2928
+ return handleScheduleWake(request, decodeURIComponent(scheduleWakeMatch[1]));
2929
+ }
2911
2930
  if (url.pathname === "/api/starlens/extract" && request.method === "POST") {
2912
2931
  return handleStarlensExtract(request, signedDeploymentId);
2913
2932
  }
@@ -2932,7 +2951,7 @@ async function handleSimulatedRequest(request, url) {
2932
2951
  }
2933
2952
  }
2934
2953
  return failure(
2935
- `[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.`,
2954
+ `[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, schedule wakes, auth verify/refresh, Neon /sql.`,
2936
2955
  404
2937
2956
  );
2938
2957
  }
package/dist/setup.mjs CHANGED
@@ -41,6 +41,7 @@ var state = globalSingleton("state", () => ({
41
41
  identitySuppressClaimDelivery: false,
42
42
  identityAllowedReturnOrigins: /* @__PURE__ */ new Set([APP_TEST_ORIGIN]),
43
43
  checkouts: [],
44
+ scheduleWakes: [],
44
45
  checkoutCounter: 0,
45
46
  sessionStatuses: /* @__PURE__ */ new Map(),
46
47
  idempotentCheckouts: /* @__PURE__ */ new Map(),
@@ -2561,7 +2562,9 @@ async function handleEnqueue(request) {
2561
2562
  function durableView(jobId, as) {
2562
2563
  const job = state.edgeDurableJobs.get(jobId);
2563
2564
  const createdAt = now2();
2565
+ const simulated = job.outcome.status !== "needs_attention" && job.outcome.simulated ? { simulated: true } : {};
2564
2566
  const base = {
2567
+ ...simulated,
2565
2568
  jobId,
2566
2569
  attempts: as === "queued" ? 0 : 1,
2567
2570
  createdAt,
@@ -2681,6 +2684,16 @@ async function handleEdgeRequest(request, subPath) {
2681
2684
  return edgeFailure(`No edge simulator for ${method} .../edge${subPath}`, 404);
2682
2685
  }
2683
2686
 
2687
+ // src/simulator/schedules.ts
2688
+ async function handleScheduleWake(request, scheduleKey) {
2689
+ if (request.method !== "POST") return failure(`Method ${request.method} not allowed`, 405);
2690
+ const body = await readJsonBody(request);
2691
+ const at = typeof body.at === "string" ? new Date(body.at) : null;
2692
+ if (!at || Number.isNaN(at.getTime())) return failure("`at` must be an ISO date-time", 400);
2693
+ state.scheduleWakes.push({ scheduleKey, at, requestedAt: /* @__PURE__ */ new Date() });
2694
+ return success({ woken: true });
2695
+ }
2696
+
2684
2697
  // src/simulator/starlens.ts
2685
2698
  var LATEST_VERSION = "v2";
2686
2699
  function starlensFailure(error, status, code) {
@@ -2818,8 +2831,10 @@ function requiresDeploymentHmac(request, url) {
2818
2831
  );
2819
2832
  const edgeMatch = url.pathname.match(/^\/api\/deployments\/[^/]+\/edge(\/.*)?$/);
2820
2833
  const isStarlens = url.pathname === "/api/starlens/extract";
2821
- return !!(isStarlens || dataStoreMatch || isEmail || identitiesMatch || storeMatch || messagingMatch || edgeMatch);
2834
+ const scheduleWakeMatch = url.pathname.match(SCHEDULE_WAKE_PATH);
2835
+ return !!(isStarlens || scheduleWakeMatch || dataStoreMatch || isEmail || identitiesMatch || storeMatch || messagingMatch || edgeMatch);
2822
2836
  }
2837
+ var SCHEDULE_WAKE_PATH = /^\/api\/deployments\/[^/]+\/schedules\/([^/]+)\/wake$/;
2823
2838
  function deploymentIdFromPath(url) {
2824
2839
  const match = url.pathname.match(/\/api\/deployments\/([^/]+)(?:\/|$)/);
2825
2840
  if (!match) return null;
@@ -2884,6 +2899,10 @@ async function handleSimulatedRequest(request, url) {
2884
2899
  if (edgeMatch) {
2885
2900
  return handleEdgeRequest(request, edgeMatch[1] ?? "");
2886
2901
  }
2902
+ const scheduleWakeMatch = url.pathname.match(SCHEDULE_WAKE_PATH);
2903
+ if (scheduleWakeMatch) {
2904
+ return handleScheduleWake(request, decodeURIComponent(scheduleWakeMatch[1]));
2905
+ }
2887
2906
  if (url.pathname === "/api/starlens/extract" && request.method === "POST") {
2888
2907
  return handleStarlensExtract(request, signedDeploymentId);
2889
2908
  }
@@ -2908,7 +2927,7 @@ async function handleSimulatedRequest(request, url) {
2908
2927
  }
2909
2928
  }
2910
2929
  return failure(
2911
- `[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.`,
2930
+ `[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, schedule wakes, auth verify/refresh, Neon /sql.`,
2912
2931
  404
2913
2932
  );
2914
2933
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stardeck-customer-apps/testing",
3
- "version": "0.25.0",
3
+ "version": "0.26.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",
@@ -100,6 +100,7 @@
100
100
  "@stardeck-customer-apps/email-sdk": "*",
101
101
  "@stardeck-customer-apps/integrations-sdk": "*",
102
102
  "@stardeck-customer-apps/payments-sdk": "*",
103
+ "@stardeck-customer-apps/scheduling-sdk": "*",
103
104
  "@stardeck-customer-apps/storage-sdk": "*",
104
105
  "@stardeck-customer-apps/tsconfig": "*",
105
106
  "@types/node": "^24.10.1",