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 +2 -1
- package/dist/castle.js +1 -1
- package/dist/runtime.d.ts +20 -0
- package/dist/runtime.js +96 -4
- package/package.json +1 -1
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
},
|
|
470
|
+
}, delayMs);
|
|
379
471
|
}
|
|
380
472
|
function localWsUrl(path) {
|
|
381
473
|
const url = new URL(path, location.href);
|