castle-web-sdk 0.4.45 → 0.4.46
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/README.md +449 -53
- package/dist/agent.js +4 -4
- package/dist/avatar.d.ts +12 -0
- package/dist/avatar.js +23 -0
- package/dist/castle.d.ts +10 -1
- package/dist/castle.js +5 -0
- package/dist/commands.d.ts +223 -0
- package/dist/commands.js +16 -0
- package/dist/context.d.ts +6 -0
- package/dist/experiments.d.ts +15 -0
- package/dist/experiments.js +83 -0
- package/dist/hub.d.ts +85 -0
- package/dist/hub.js +107 -0
- package/dist/multiplayer.d.ts +86 -6
- package/dist/multiplayer.js +209 -8
- package/dist/save.d.ts +197 -0
- package/dist/save.js +525 -0
- package/dist/saveCoordinator.d.ts +39 -0
- package/dist/saveCoordinator.js +252 -0
- package/dist/server/hub.d.ts +169 -0
- package/dist/server/hub.js +125 -0
- package/dist/server/platformHandle.d.ts +49 -2
- package/dist/server/storage.d.ts +58 -4
- package/dist/server/storage.js +25 -5
- package/dist/server/wrapper.d.ts +89 -6
- package/dist/server/wrapper.js +395 -30
- package/dist/store.d.ts +3 -2
- package/dist/store.js +5 -0
- package/dist/transport.d.ts +2 -1
- package/dist/transport.js +13 -1
- package/package.json +1 -1
package/dist/server/storage.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Json } from "../types";
|
|
2
2
|
import type { CharacterHistory, CharacterOperation } from "../commands";
|
|
3
3
|
import type { PlatformHandle } from "./platformHandle";
|
|
4
|
+
import { type SaveApi, type SaveBucketApi } from "../save";
|
|
4
5
|
export interface ServerStorageListOptions {
|
|
5
6
|
prefix?: string;
|
|
6
7
|
limit?: number;
|
|
@@ -15,7 +16,7 @@ export interface ServerStorageListPage<T extends Json = Json> {
|
|
|
15
16
|
/** Null means there is nothing left. A short page alone does not mean that. */
|
|
16
17
|
cursor: string | null;
|
|
17
18
|
}
|
|
18
|
-
export interface ServerStorageScope {
|
|
19
|
+
export interface ServerStorageScope extends SaveBucketApi {
|
|
19
20
|
get<T extends Json = Json>(keys: string[]): Promise<Record<string, T>>;
|
|
20
21
|
set(values: Record<string, Json>): Promise<void>;
|
|
21
22
|
remove(keys: string[]): Promise<void>;
|
|
@@ -95,6 +96,59 @@ export interface DeliverCharacterKnowledgeInput {
|
|
|
95
96
|
claimIds: string[];
|
|
96
97
|
source: CharacterKnowledgeSource;
|
|
97
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* A character acting on its own after a command woke it (autonomous character
|
|
101
|
+
* actions, bundles with `characterActions`). `declined` covers choosing
|
|
102
|
+
* nothing, too little confidence for its temperament, and an action no longer
|
|
103
|
+
* eligible when it committed; `failed` is a decision-model error.
|
|
104
|
+
*/
|
|
105
|
+
export type CharacterActionNotice = {
|
|
106
|
+
characterId: string;
|
|
107
|
+
/** `id` is the event occurrence, routine, or promise id. */
|
|
108
|
+
trigger: {
|
|
109
|
+
type: "witnessed" | "routine" | "promise_due";
|
|
110
|
+
id: string;
|
|
111
|
+
};
|
|
112
|
+
outcome: "acted" | "declined" | "failed";
|
|
113
|
+
/** When acted. */
|
|
114
|
+
actionId?: string;
|
|
115
|
+
/** When acted with a parameterized action's variant. */
|
|
116
|
+
variantId?: string;
|
|
117
|
+
/** The action's world event, when it has one. */
|
|
118
|
+
eventOccurrenceId?: string;
|
|
119
|
+
/** The decision model's probability for its choice. */
|
|
120
|
+
probability?: number;
|
|
121
|
+
};
|
|
122
|
+
export type CharacterGameStateResult = {
|
|
123
|
+
revision: number;
|
|
124
|
+
state: {
|
|
125
|
+
[key: string]: string | number | boolean;
|
|
126
|
+
};
|
|
127
|
+
promiseOutcomes: Array<{
|
|
128
|
+
id: string;
|
|
129
|
+
status: "fulfilled" | "broken";
|
|
130
|
+
due: number;
|
|
131
|
+
resolvedAtGameTime: number;
|
|
132
|
+
}>;
|
|
133
|
+
/** At most 8, omitted when no character acted, declined, or failed. */
|
|
134
|
+
characterActions?: CharacterActionNotice[];
|
|
135
|
+
};
|
|
136
|
+
export type CharacterGameStateOperation = Omit<CharacterOperation, "result"> & {
|
|
137
|
+
result: CharacterGameStateResult | null;
|
|
138
|
+
};
|
|
139
|
+
export type CharacterKnowledgeDeliveryResult = {
|
|
140
|
+
deliveryId: string;
|
|
141
|
+
recipientCharacterId: string;
|
|
142
|
+
eventOccurrenceId: string;
|
|
143
|
+
claimIds: string[];
|
|
144
|
+
source: CharacterKnowledgeSource;
|
|
145
|
+
deliveredAt: string;
|
|
146
|
+
/** At most 8, omitted when no character acted, declined, or failed. */
|
|
147
|
+
characterActions?: CharacterActionNotice[];
|
|
148
|
+
};
|
|
149
|
+
export type CharacterKnowledgeDeliveryOperation = Omit<CharacterOperation, "result"> & {
|
|
150
|
+
result: CharacterKnowledgeDeliveryResult | null;
|
|
151
|
+
};
|
|
98
152
|
export interface CompleteCharacterBeatInput {
|
|
99
153
|
operationId: string;
|
|
100
154
|
characterId: string;
|
|
@@ -189,10 +243,10 @@ export interface ServerPlaythroughCharacters {
|
|
|
189
243
|
/** Queues a funded turn whose admitted listeners are fixed at enqueue time. */
|
|
190
244
|
send(input: SendCharacterInput): Promise<CharacterOperation>;
|
|
191
245
|
recordEvent(input: RecordCharacterEventInput): Promise<CharacterOperation>;
|
|
192
|
-
setGameState(input: SetCharacterGameStateInput): Promise<
|
|
246
|
+
setGameState(input: SetCharacterGameStateInput): Promise<CharacterGameStateOperation>;
|
|
193
247
|
shareClaim(input: ShareCharacterClaimInput): Promise<CharacterOperation>;
|
|
194
248
|
inspect(): Promise<Json>;
|
|
195
|
-
deliverKnowledge(input: DeliverCharacterKnowledgeInput): Promise<
|
|
249
|
+
deliverKnowledge(input: DeliverCharacterKnowledgeInput): Promise<CharacterKnowledgeDeliveryOperation>;
|
|
196
250
|
completeBeat(input: CompleteCharacterBeatInput): Promise<CharacterOperation>;
|
|
197
251
|
evaluateInformation(input: EvaluateCharacterInformationInput): Promise<CharacterInformationEvaluationOperation>;
|
|
198
252
|
evaluateBeats(input: EvaluateCharacterBeatsInput): Promise<CharacterBeatEvaluationOperation>;
|
|
@@ -204,7 +258,7 @@ export interface ServerCharacterHistoryOptions {
|
|
|
204
258
|
limit?: number;
|
|
205
259
|
cursor?: string | null;
|
|
206
260
|
}
|
|
207
|
-
export interface ServerStorage {
|
|
261
|
+
export interface ServerStorage extends SaveApi {
|
|
208
262
|
/** Shared by everyone playing the deck, and owned by this server. */
|
|
209
263
|
readonly deck: ServerStorageScope;
|
|
210
264
|
/** One player's public data. Readable by anyone; writable by them or by this server. */
|
package/dist/server/storage.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { chunked } from "../chunk";
|
|
2
2
|
import { decodeStorageValue, encodeStorageValue, storageError, } from "../storageJson";
|
|
3
|
+
import { createSaveApi, createSaveBucket, preflightSaveRequest, SaveError, } from "../save";
|
|
3
4
|
// Deck storage for an author's session server.
|
|
4
5
|
//
|
|
5
6
|
// The same store the deck's clients use, reached over the platform channel instead of
|
|
@@ -56,8 +57,26 @@ export function createServerStorage(platformHandle) {
|
|
|
56
57
|
const result = await platformHandle.storage(op, JSON.stringify(args));
|
|
57
58
|
return JSON.parse(result);
|
|
58
59
|
};
|
|
60
|
+
// The host forwards these args to the platform's storage route unchanged,
|
|
61
|
+
// which takes a save's request as `input` and a lookup as `operationId`.
|
|
62
|
+
const saveApi = createSaveApi(async (verb, input) => {
|
|
63
|
+
if (typeof platformHandle.storage !== "function" ||
|
|
64
|
+
platformHandle.storageEnabled === false) {
|
|
65
|
+
throw new SaveError({
|
|
66
|
+
code: "CAULDRON_STORAGE_UNAVAILABLE",
|
|
67
|
+
message: "Storage is not available to this session.",
|
|
68
|
+
operationId: input.operationId,
|
|
69
|
+
retryable: false,
|
|
70
|
+
committed: false,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
const args = verb === "operation" ? input : { input };
|
|
74
|
+
preflightSaveRequest(verb, args, MAX_REQUEST_BYTES);
|
|
75
|
+
return call(verb, args);
|
|
76
|
+
}, true);
|
|
59
77
|
return {
|
|
60
|
-
|
|
78
|
+
...saveApi,
|
|
79
|
+
deck: createScope(call, "DECK", null, saveApi),
|
|
61
80
|
user(userId) {
|
|
62
81
|
// A bad id poisons the scope rather than throwing here, so the failure arrives
|
|
63
82
|
// the same way every other failure does -- as a rejection the author can await.
|
|
@@ -65,12 +84,12 @@ export function createServerStorage(platformHandle) {
|
|
|
65
84
|
// resolved by the platform to the deck's creator, quietly writing the wrong
|
|
66
85
|
// player's bucket, so nothing is sent either.
|
|
67
86
|
if (typeof userId !== "string" || userId.length === 0) {
|
|
68
|
-
return createScope(rejectMissingSubject, "USER", null);
|
|
87
|
+
return createScope(rejectMissingSubject, "USER", null, saveApi);
|
|
69
88
|
}
|
|
70
89
|
if (utf8Length(userId) > MAX_SUBJECT_BYTES) {
|
|
71
|
-
return createScope(rejectOversizeSubject, "USER",
|
|
90
|
+
return createScope(rejectOversizeSubject, "USER", userId, saveApi);
|
|
72
91
|
}
|
|
73
|
-
return createScope(call, "USER", userId);
|
|
92
|
+
return createScope(call, "USER", userId, saveApi);
|
|
74
93
|
},
|
|
75
94
|
board(name) {
|
|
76
95
|
return createBoard(call, name);
|
|
@@ -224,8 +243,9 @@ function createPlaythrough(call, subject, generation) {
|
|
|
224
243
|
},
|
|
225
244
|
});
|
|
226
245
|
}
|
|
227
|
-
function createScope(call, scope, subject) {
|
|
246
|
+
function createScope(call, scope, subject, saveApi) {
|
|
228
247
|
return {
|
|
248
|
+
...createSaveBucket(saveApi, scope === "DECK" ? "deck" : "user", subject ?? undefined),
|
|
229
249
|
async get(keys) {
|
|
230
250
|
// A bare array, not a wrapper: the platform hands back exactly what the
|
|
231
251
|
// storage verb returns, and `get` returns its entries directly.
|
package/dist/server/wrapper.d.ts
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import type { MultiplayerSessionMode, PlatformHandle, PlayerIdentity } from "./platformHandle";
|
|
2
2
|
import type { Json } from "../types";
|
|
3
|
+
import type { MultiplayerWorldCharacterCommand, MultiplayerWorldCharacterCommandResult, MultiplayerWorldCharacterCommandResultValue } from "../multiplayer";
|
|
3
4
|
import { type ServerStorage } from "./storage";
|
|
4
|
-
|
|
5
|
-
export type {
|
|
5
|
+
import { type MultiplayerHubArrival, type MultiplayerServerHub } from "./hub";
|
|
6
|
+
export type { MultiplayerSessionMode, PlatformHandle, PlatformSessionConfig, PlatformWorldCheckpoint, PlatformWorldSnapshot, PlatformWorldTimeConfig, PlayerIdentity, } from "./platformHandle";
|
|
7
|
+
export { createSaveCoordinator } from "../saveCoordinator";
|
|
8
|
+
export type { CoordinatedChange, CoordinatedSave, SaveCoordinator, SaveStatus, } from "../saveCoordinator";
|
|
9
|
+
export { SaveError } from "../save";
|
|
10
|
+
export type { SaveApi, SaveBucketApi, SaveBucketRead, SaveChange, SaveCheck, SaveClaimed, SaveClaimInput, SaveErrorEnvelope, SaveInitial, SaveInitializeInput, SaveOpened, SaveOperation, SaveReadTarget, SaveReceipt, SaveRevision, SaveRows, SaveScope, SaveSelection, SaveSnapshot, SaveTarget, SaveTransactionInput, SaveWriter, } from "../save";
|
|
11
|
+
export type { MultiplayerHubArrival, MultiplayerHubBoard, MultiplayerHubOutcome, MultiplayerHubPadUpdateInput, MultiplayerHubPadUpdateResult, MultiplayerHubPlayerResults, MultiplayerHubRecord, MultiplayerHubReportReceipt, MultiplayerHubResultInput, MultiplayerHubResultRow, MultiplayerHubResultShape, MultiplayerHubResultsRead, MultiplayerHubResultsReadInput, MultiplayerHubWearable, MultiplayerHubWearReceipt, MultiplayerServerHub, } from "./hub";
|
|
12
|
+
export type { CharacterActionNotice, CharacterGameStateOperation, CharacterGameStateResult, CharacterKnowledgeDeliveryOperation, CharacterKnowledgeDeliveryResult, CharacterKnowledgeSource, CompleteCharacterBeatInput, DeliverCharacterKnowledgeInput, RecordCharacterEventInput, SetCharacterGameStateInput, ShareCharacterClaimInput, ServerStorage, ServerStorageBoard, ServerStorageBoardScore, ServerStorageListEntry, ServerStorageListOptions, ServerStorageListPage, ServerPlaythrough, ServerPlaythroughCharacters, ServerCharacterHistoryOptions, ServerStorageScope, } from "./storage";
|
|
6
13
|
export interface MultiplayerServerPlayer extends PlayerIdentity {
|
|
7
14
|
playerId: string;
|
|
8
15
|
readonly participantId: string;
|
|
@@ -15,6 +22,12 @@ export interface MultiplayerServerSession {
|
|
|
15
22
|
readonly sessionId: string;
|
|
16
23
|
readonly deckId: string;
|
|
17
24
|
readonly mode: MultiplayerSessionMode;
|
|
25
|
+
/**
|
|
26
|
+
* The place the host started this session for (a hub door's destination, or
|
|
27
|
+
* the hub place ghost pinned the session to), or null when the host stamped
|
|
28
|
+
* none. Never set from a client.
|
|
29
|
+
*/
|
|
30
|
+
readonly place: string | null;
|
|
18
31
|
/**
|
|
19
32
|
* Deck storage that outlives the session. Nothing else here does: a session ends
|
|
20
33
|
* when its last player leaves and its memory goes with it.
|
|
@@ -22,6 +35,27 @@ export interface MultiplayerServerSession {
|
|
|
22
35
|
readonly storage: ServerStorage;
|
|
23
36
|
/** A durable checkpoint for an opted-in persistent destination. */
|
|
24
37
|
readonly world: MultiplayerServerWorld | null;
|
|
38
|
+
/**
|
|
39
|
+
* The shared characters this session can address: `session.world.characters`
|
|
40
|
+
* in a shared-character world (the same object), or a hub place's characters
|
|
41
|
+
* on one of its layers, where `session.world` stays null. Null everywhere else.
|
|
42
|
+
*/
|
|
43
|
+
readonly characters: MultiplayerServerWorldCharacters | null;
|
|
44
|
+
/**
|
|
45
|
+
* The hub place this session runs (castle.json `hub`, as ghost pinned it):
|
|
46
|
+
* results, pads and arrivals. Null for a deck without a hub block and on hosts
|
|
47
|
+
* without hub support.
|
|
48
|
+
*/
|
|
49
|
+
readonly hub: MultiplayerServerHub | null;
|
|
50
|
+
/**
|
|
51
|
+
* The variant a connected player has in the A/B experiment `name`, or null
|
|
52
|
+
* when they are in no such experiment (or have left). Castle's server
|
|
53
|
+
* resolved it and the host delivered it with the join, so it is the same arm
|
|
54
|
+
* the player's page reads and nothing a client sent.
|
|
55
|
+
*/
|
|
56
|
+
experiment(player: MultiplayerServerPlayer | string, name: string): string | null;
|
|
57
|
+
/** Every experiment a connected player is in, `{ name: variant }`. */
|
|
58
|
+
experiments(player: MultiplayerServerPlayer | string): Record<string, string>;
|
|
25
59
|
/** Sends bytes as a binary message and all other payloads as JSON. */
|
|
26
60
|
send(playerId: string, data: unknown): void;
|
|
27
61
|
/** Broadcasts bytes as a binary message and all other payloads as JSON. */
|
|
@@ -53,10 +87,26 @@ export interface MultiplayerServerWorld {
|
|
|
53
87
|
read(): Promise<MultiplayerWorldSnapshot>;
|
|
54
88
|
/** Commits exactly against expectedRevision; conflicts never partially write. */
|
|
55
89
|
commit(input: MultiplayerWorldCommitInput): Promise<MultiplayerWorldCommitReceipt>;
|
|
90
|
+
catchUp(options: MultiplayerWorldCatchUpOptions): Promise<MultiplayerWorldCatchUpResult>;
|
|
56
91
|
readonly journal: MultiplayerServerWorldJournal;
|
|
57
92
|
readonly participants: MultiplayerServerWorldParticipants;
|
|
93
|
+
readonly notifications: MultiplayerServerWorldNotifications;
|
|
58
94
|
readonly characters: MultiplayerServerWorldCharacters;
|
|
59
95
|
}
|
|
96
|
+
export interface MultiplayerWorldCatchUpOptions {
|
|
97
|
+
key?: string;
|
|
98
|
+
maxElapsedMs?: number;
|
|
99
|
+
apply(input: {
|
|
100
|
+
elapsedMs: number;
|
|
101
|
+
now: number;
|
|
102
|
+
checkpoint: MultiplayerWorldCheckpoint | null;
|
|
103
|
+
}): MultiplayerWorldCheckpoint | Promise<MultiplayerWorldCheckpoint>;
|
|
104
|
+
}
|
|
105
|
+
export interface MultiplayerWorldCatchUpResult {
|
|
106
|
+
applied: boolean;
|
|
107
|
+
elapsedMs: number;
|
|
108
|
+
revision: string;
|
|
109
|
+
}
|
|
60
110
|
export interface MultiplayerWorldJournalAppendInput {
|
|
61
111
|
operationId: string;
|
|
62
112
|
entries: readonly MultiplayerWorldJournalAppendEntry[];
|
|
@@ -112,6 +162,31 @@ export interface MultiplayerWorldParticipantsReadResult {
|
|
|
112
162
|
export interface MultiplayerServerWorldParticipants {
|
|
113
163
|
read(options?: MultiplayerWorldParticipantsReadOptions): Promise<MultiplayerWorldParticipantsReadResult>;
|
|
114
164
|
}
|
|
165
|
+
export interface MultiplayerWorldNotificationNotifyInput {
|
|
166
|
+
key: string;
|
|
167
|
+
participantIds: readonly string[];
|
|
168
|
+
templateId: string;
|
|
169
|
+
slots?: Readonly<Record<string, string>>;
|
|
170
|
+
at?: string;
|
|
171
|
+
expiresAt?: string;
|
|
172
|
+
}
|
|
173
|
+
export interface MultiplayerWorldNotificationNotifyResult {
|
|
174
|
+
key: string;
|
|
175
|
+
accepted: number;
|
|
176
|
+
skipped: number;
|
|
177
|
+
limited: number;
|
|
178
|
+
}
|
|
179
|
+
export interface MultiplayerWorldNotificationCancelInput {
|
|
180
|
+
key: string;
|
|
181
|
+
participantIds?: readonly string[];
|
|
182
|
+
}
|
|
183
|
+
export interface MultiplayerWorldNotificationCancelResult {
|
|
184
|
+
cancelled: number;
|
|
185
|
+
}
|
|
186
|
+
export interface MultiplayerServerWorldNotifications {
|
|
187
|
+
notify(input: MultiplayerWorldNotificationNotifyInput): Promise<MultiplayerWorldNotificationNotifyResult>;
|
|
188
|
+
cancel(input: MultiplayerWorldNotificationCancelInput): Promise<MultiplayerWorldNotificationCancelResult>;
|
|
189
|
+
}
|
|
115
190
|
export interface MultiplayerServerWorldCharacters {
|
|
116
191
|
create(input: MultiplayerWorldRuntimeCharacterCreateInput): Promise<MultiplayerWorldRuntimeCharacterCreateReceipt>;
|
|
117
192
|
list(): Promise<MultiplayerWorldCharacterList>;
|
|
@@ -155,14 +230,21 @@ export interface MultiplayerWorldRuntimeCharacterCreateReceipt {
|
|
|
155
230
|
export interface MultiplayerWorldCharacterList {
|
|
156
231
|
characters: readonly MultiplayerWorldCharacterSummary[];
|
|
157
232
|
}
|
|
158
|
-
|
|
233
|
+
interface MultiplayerWorldCharacterCommandRequestFor<Command extends MultiplayerWorldCharacterCommand> {
|
|
234
|
+
/** Command discriminator mirrored at the request level for TypeScript narrowing. */
|
|
235
|
+
readonly type: Command["type"];
|
|
159
236
|
readonly requestId: string;
|
|
160
237
|
readonly operationId: string;
|
|
161
238
|
readonly speaker: MultiplayerServerPlayer;
|
|
162
|
-
readonly command: Readonly<
|
|
163
|
-
apply(): Promise<
|
|
239
|
+
readonly command: Readonly<Command>;
|
|
240
|
+
apply(): Promise<MultiplayerWorldCharacterCommandResult<MultiplayerWorldCharacterCommandResultValue<Command>>>;
|
|
164
241
|
reject(code?: string): void;
|
|
165
242
|
}
|
|
243
|
+
export type MultiplayerWorldCharacterCommandRequest = {
|
|
244
|
+
[Type in MultiplayerWorldCharacterCommand["type"]]: MultiplayerWorldCharacterCommandRequestFor<Extract<MultiplayerWorldCharacterCommand, {
|
|
245
|
+
type: Type;
|
|
246
|
+
}>>;
|
|
247
|
+
}[MultiplayerWorldCharacterCommand["type"]];
|
|
166
248
|
export interface MultiplayerWorldCharacterReadRequest {
|
|
167
249
|
readonly requestId: string;
|
|
168
250
|
readonly speaker: MultiplayerServerPlayer;
|
|
@@ -194,7 +276,8 @@ export interface MultiplayerWorldCharacterTurn {
|
|
|
194
276
|
type CallbackResult = unknown;
|
|
195
277
|
export interface MultiplayerServerCallbacks {
|
|
196
278
|
onStart?: (session: MultiplayerServerSession) => CallbackResult;
|
|
197
|
-
|
|
279
|
+
/** `arrival` is set when ghost placed the player through a hub door. */
|
|
280
|
+
onPlayerJoin?: (session: MultiplayerServerSession, player: MultiplayerServerPlayer, arrival: MultiplayerHubArrival | null) => CallbackResult;
|
|
198
281
|
onPlayerLeave?: (session: MultiplayerServerSession, player: MultiplayerServerPlayer) => CallbackResult;
|
|
199
282
|
/** Receives binary messages as Uint8Array values and JSON messages otherwise. */
|
|
200
283
|
onMessage?: (session: MultiplayerServerSession, player: MultiplayerServerPlayer, data: unknown) => CallbackResult;
|