castle-web-sdk 0.4.8 → 0.4.9

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/dist/castle.d.ts CHANGED
@@ -8,7 +8,8 @@ export { Pass } from "./passes";
8
8
  export type { CastlePassApi, PassOfferResult, PassOfferStatus, } from "./passes";
9
9
  export { Portal } from "./portal";
10
10
  export type { CastlePortalApi, PortalOpenResult, PortalOpenStatus, PortalPrefetchResult, PortalPrefetchStatus, } from "./portal";
11
- export { CARD_RATIO, initCard, onBeforeRestart, setup, writeFile } from "./runtime";
11
+ export { CARD_RATIO, initCard, onBeforeRestart, onFilesChanged, onSaveReloadState, requestReload, setup, takeReloadState, writeFile, } from "./runtime";
12
+ export type { FileChange, FilesChangedEvent } from "./runtime";
12
13
  export { SharedStorage, Storage } from "./storage";
13
14
  export { Time } from "./time";
14
15
  export type { CastleClockZone, CastleDateParts, CastleTimeApi } from "./time";
package/dist/castle.js CHANGED
@@ -5,7 +5,7 @@ export { Leaderboard } from "./leaderboard";
5
5
  export { Lifecycle } from "./lifecycle";
6
6
  export { Pass } from "./passes";
7
7
  export { Portal } from "./portal";
8
- export { CARD_RATIO, initCard, onBeforeRestart, setup, writeFile } from "./runtime";
8
+ export { CARD_RATIO, initCard, onBeforeRestart, onFilesChanged, onSaveReloadState, requestReload, setup, takeReloadState, writeFile, } from "./runtime";
9
9
  export { SharedStorage, Storage } from "./storage";
10
10
  export { Time } from "./time";
11
11
  export { User } from "./user";
package/dist/runtime.d.ts CHANGED
@@ -7,9 +7,29 @@ interface LocalResponse {
7
7
  error?: string;
8
8
  [key: string]: unknown;
9
9
  }
10
+ export interface FileChange {
11
+ /** Deck-relative POSIX path. */
12
+ path: string;
13
+ event: "add" | "change" | "delete";
14
+ /**
15
+ * Deck-relative paths affected via the Vite import graph: the changed file
16
+ * itself (when imported by anything) plus every transitive importer. Empty
17
+ * when the file isn't part of the module graph.
18
+ */
19
+ affected: string[];
20
+ }
21
+ export interface FilesChangedEvent {
22
+ changes: FileChange[];
23
+ /** Union of every change's path + affected set, deduped. */
24
+ affected: string[];
25
+ }
10
26
  export declare function setup(): void;
11
27
  export declare function writeFile(path: string, contents: string): Promise<LocalResponse>;
12
28
  export declare function initCard(): HTMLDivElement;
13
29
  export declare function sendLocalCommand<C extends CommandName>(command: C, params: CommandParams[C]): Promise<CommandResponseEnvelope>;
30
+ export declare function onFilesChanged(listener: (event: FilesChangedEvent) => void): () => void;
14
31
  export declare function onBeforeRestart(hook: () => void | Promise<void>): () => void;
32
+ export declare function onSaveReloadState(key: string, save: () => unknown): () => void;
33
+ export declare function takeReloadState<T = unknown>(key: string): T | null;
34
+ export declare function requestReload(): void;
15
35
  export {};
package/dist/runtime.js CHANGED
@@ -342,7 +342,10 @@ function handleLocalMessage(msg) {
342
342
  });
343
343
  }
344
344
  else if (msg.type === "restart") {
345
- scheduleRestart();
345
+ scheduleReload(RESTART_DEBOUNCE_MS);
346
+ }
347
+ else if (msg.type === "files_changed") {
348
+ dispatchFilesChanged(msg);
346
349
  }
347
350
  else if (msg.type === "write_file_response") {
348
351
  resolveLocalRequest(msg);
@@ -351,18 +354,106 @@ function handleLocalMessage(msg) {
351
354
  resolveLocalCommand(msg);
352
355
  }
353
356
  }
357
+ // Files-changed listeners, registered by consumers (the kit engine, editors).
358
+ // The SDK only delivers the event; deciding what to re-read or reload is the
359
+ // consumer's job.
360
+ const filesChangedListeners = new Set();
361
+ export function onFilesChanged(listener) {
362
+ filesChangedListeners.add(listener);
363
+ return () => filesChangedListeners.delete(listener);
364
+ }
365
+ function dispatchFilesChanged(msg) {
366
+ const rawChanges = Array.isArray(msg.changes) ? msg.changes : [];
367
+ const changes = [];
368
+ for (const raw of rawChanges) {
369
+ if (typeof raw?.path !== "string")
370
+ continue;
371
+ const event = raw.event;
372
+ if (event !== "add" && event !== "change" && event !== "delete")
373
+ continue;
374
+ changes.push({
375
+ path: raw.path,
376
+ event,
377
+ affected: Array.isArray(raw.affected)
378
+ ? raw.affected.filter((p) => typeof p === "string")
379
+ : [],
380
+ });
381
+ }
382
+ if (changes.length === 0)
383
+ return;
384
+ const affected = Array.isArray(msg.affected)
385
+ ? msg.affected.filter((p) => typeof p === "string")
386
+ : [];
387
+ const event = { changes, affected };
388
+ for (const listener of filesChangedListeners) {
389
+ try {
390
+ listener(event);
391
+ }
392
+ catch {
393
+ // one consumer's failure shouldn't starve the others
394
+ }
395
+ }
396
+ }
354
397
  // Restart (from `castle-web restart` / task agents) is debounced so a burst
355
398
  // of reload requests -- several tasks finishing close together -- produces
356
399
  // one reload. Before reloading, registered hooks run (the kit editor flushes
357
- // its debounced unsaved edits there) so in-flight work isn't lost.
400
+ // its debounced unsaved edits there, save-state hooks stash their state) so
401
+ // in-flight work isn't lost.
358
402
  const RESTART_DEBOUNCE_MS = 1500;
403
+ // Consumer-requested reloads (a code file changed) coalesce on a short window:
404
+ // the serve already batched the fs events, so this only folds multiple
405
+ // consumers requesting at once.
406
+ const REQUEST_RELOAD_DEBOUNCE_MS = 250;
359
407
  let restartTimer = null;
360
408
  const beforeRestartHooks = new Set();
361
409
  export function onBeforeRestart(hook) {
362
410
  beforeRestartHooks.add(hook);
363
411
  return () => beforeRestartHooks.delete(hook);
364
412
  }
365
- function scheduleRestart() {
413
+ // Save-state hooks: consumers register a saver keyed by a stable string; right
414
+ // before a reload every saver runs and its value is stashed in sessionStorage.
415
+ // After the reload the consumer calls takeReloadState(key) to pick it back up.
416
+ // Simple stash — enough for editor selection / lightweight runtime state.
417
+ const RELOAD_STATE_PREFIX = "castle-reload-state:";
418
+ const reloadStateSavers = new Map();
419
+ export function onSaveReloadState(key, save) {
420
+ reloadStateSavers.set(key, save);
421
+ return () => {
422
+ if (reloadStateSavers.get(key) === save)
423
+ reloadStateSavers.delete(key);
424
+ };
425
+ }
426
+ export function takeReloadState(key) {
427
+ try {
428
+ const raw = sessionStorage.getItem(RELOAD_STATE_PREFIX + key);
429
+ if (raw === null)
430
+ return null;
431
+ sessionStorage.removeItem(RELOAD_STATE_PREFIX + key);
432
+ return JSON.parse(raw);
433
+ }
434
+ catch {
435
+ return null;
436
+ }
437
+ }
438
+ function stashReloadState() {
439
+ for (const [key, save] of reloadStateSavers) {
440
+ try {
441
+ const value = save();
442
+ if (value === undefined)
443
+ continue;
444
+ sessionStorage.setItem(RELOAD_STATE_PREFIX + key, JSON.stringify(value));
445
+ }
446
+ catch {
447
+ // a failed saver shouldn't block the reload
448
+ }
449
+ }
450
+ }
451
+ // Reload this page after honoring the save hooks. Consumers call this when a
452
+ // files-changed event tells them code they run on has changed.
453
+ export function requestReload() {
454
+ scheduleReload(REQUEST_RELOAD_DEBOUNCE_MS);
455
+ }
456
+ function scheduleReload(delayMs) {
366
457
  if (restartTimer !== null)
367
458
  clearTimeout(restartTimer);
368
459
  restartTimer = setTimeout(() => {
@@ -373,9 +464,10 @@ function scheduleRestart() {
373
464
  catch {
374
465
  // a failed flush shouldn't block the reload
375
466
  }
467
+ stashReloadState();
376
468
  location.reload();
377
469
  })();
378
- }, RESTART_DEBOUNCE_MS);
470
+ }, delayMs);
379
471
  }
380
472
  function localWsUrl(path) {
381
473
  const url = new URL(path, location.href);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "castle-web-sdk",
3
- "version": "0.4.8",
3
+ "version": "0.4.9",
4
4
  "type": "module",
5
5
  "main": "dist/castle.js",
6
6
  "types": "dist/castle.d.ts",