@dcl/sdk 7.26.0 → 7.26.1-31714079767.commit-96e9a29
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/atom.d.ts +19 -0
- package/atom.js +83 -0
- package/future.d.ts +8 -0
- package/future.js +26 -0
- package/network/binary-message-bus.d.ts +6 -3
- package/network/binary-message-bus.js +9 -5
- package/network/chunking.d.ts +5 -0
- package/network/chunking.js +38 -0
- package/network/events/implementation.d.ts +93 -0
- package/network/events/implementation.js +221 -0
- package/network/events/index.d.ts +42 -0
- package/network/events/index.js +43 -0
- package/network/events/protocol.d.ts +27 -0
- package/network/events/protocol.js +66 -0
- package/network/events/registry.d.ts +8 -0
- package/network/events/registry.js +3 -0
- package/network/index.d.ts +8 -2
- package/network/index.js +16 -3
- package/network/message-bus-sync.d.ts +16 -3
- package/network/message-bus-sync.js +161 -103
- package/network/server/index.d.ts +14 -0
- package/network/server/index.js +219 -0
- package/network/server/utils.d.ts +18 -0
- package/network/server/utils.js +135 -0
- package/network/state.js +3 -5
- package/package.json +6 -6
- package/server/env-var.d.ts +15 -0
- package/server/env-var.js +31 -0
- package/server/index.d.ts +2 -0
- package/server/index.js +3 -0
- package/server/storage/constants.d.ts +79 -0
- package/server/storage/constants.js +19 -0
- package/server/storage/index.d.ts +45 -0
- package/server/storage/index.js +51 -0
- package/server/storage/player.d.ts +45 -0
- package/server/storage/player.js +198 -0
- package/server/storage/scene.d.ts +40 -0
- package/server/storage/scene.js +188 -0
- package/server/storage/value-cache.d.ts +1 -0
- package/server/storage/value-cache.js +52 -0
- package/server/storage/write-queue.d.ts +1 -0
- package/server/storage/write-queue.js +71 -0
- package/server/storage-url.d.ts +13 -0
- package/server/storage-url.js +42 -0
- package/server/utils.d.ts +35 -0
- package/server/utils.js +61 -0
- package/src/atom.ts +98 -0
- package/src/future.ts +38 -0
- package/src/network/binary-message-bus.ts +9 -4
- package/src/network/chunking.ts +45 -0
- package/src/network/events/implementation.ts +271 -0
- package/src/network/events/index.ts +48 -0
- package/src/network/events/protocol.ts +94 -0
- package/src/network/events/registry.ts +18 -0
- package/src/network/index.ts +40 -3
- package/src/network/message-bus-sync.ts +176 -112
- package/src/network/server/index.ts +301 -0
- package/src/network/server/utils.ts +189 -0
- package/src/network/state.ts +3 -4
- package/src/server/env-var.ts +36 -0
- package/src/server/index.ts +12 -0
- package/src/server/storage/constants.ts +111 -0
- package/src/server/storage/index.ts +74 -0
- package/src/server/storage/player.ts +289 -0
- package/src/server/storage/scene.ts +270 -0
- package/src/server/storage/value-cache.ts +92 -0
- package/src/server/storage/write-queue.ts +131 -0
- package/src/server/storage-url.ts +49 -0
- package/src/server/utils.ts +76 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { Entity } from '@dcl/ecs/dist/engine'
|
|
2
|
+
import { CrdtMessageProtocol, NetworkParent } from '@dcl/ecs'
|
|
3
|
+
import { ReceiveMessage } from '@dcl/ecs/dist/runtime/types'
|
|
4
|
+
import { ReceiveNetworkMessage } from '@dcl/ecs/dist/systems/crdt/types'
|
|
5
|
+
import { ByteBuffer, ReadWriteByteBuffer } from '@dcl/ecs/dist/serialization/ByteBuffer'
|
|
6
|
+
import { AuthoritativePutComponentOperation, PutComponentOperation } from '@dcl/ecs/dist/serialization/crdt'
|
|
7
|
+
import {
|
|
8
|
+
CrdtMessage,
|
|
9
|
+
CrdtMessageBody,
|
|
10
|
+
CrdtMessageHeader,
|
|
11
|
+
CrdtMessageType,
|
|
12
|
+
DeleteComponentMessage,
|
|
13
|
+
DeleteComponentNetworkMessage,
|
|
14
|
+
DeleteEntityMessage,
|
|
15
|
+
DeleteEntityNetworkMessage,
|
|
16
|
+
PutComponentMessage,
|
|
17
|
+
AuthoritativePutComponentMessage,
|
|
18
|
+
PutNetworkComponentMessage
|
|
19
|
+
} from '@dcl/ecs/dist/serialization/crdt/types'
|
|
20
|
+
import { DeleteComponent } from '@dcl/ecs/dist/serialization/crdt/deleteComponent'
|
|
21
|
+
import { DeleteEntity } from '@dcl/ecs/dist/serialization/crdt/deleteEntity'
|
|
22
|
+
import { INetowrkEntityType } from '@dcl/ecs/dist/components/types'
|
|
23
|
+
import { PutNetworkComponentOperation } from '@dcl/ecs/dist/serialization/crdt/network/putComponentNetwork'
|
|
24
|
+
import { DeleteComponentNetwork } from '@dcl/ecs/dist/serialization/crdt/network/deleteComponentNetwork'
|
|
25
|
+
import { DeleteEntityNetwork } from '@dcl/ecs/dist/serialization/crdt/network/deleteEntityNetwork'
|
|
26
|
+
import { TransformSchema, COMPONENT_ID as TransformComponentId } from '@dcl/ecs/dist/components/manual/Transform'
|
|
27
|
+
|
|
28
|
+
export type NetworkMessage = (
|
|
29
|
+
| PutNetworkComponentMessage
|
|
30
|
+
| DeleteComponentNetworkMessage
|
|
31
|
+
| DeleteEntityNetworkMessage
|
|
32
|
+
) & { messageBuffer: Uint8Array }
|
|
33
|
+
|
|
34
|
+
export type RegularMessage = (
|
|
35
|
+
| PutComponentMessage
|
|
36
|
+
| AuthoritativePutComponentMessage
|
|
37
|
+
| DeleteComponentMessage
|
|
38
|
+
| DeleteEntityMessage
|
|
39
|
+
) & {
|
|
40
|
+
messageBuffer: Uint8Array
|
|
41
|
+
}
|
|
42
|
+
export function readMessages(data: Uint8Array): (NetworkMessage | RegularMessage)[] {
|
|
43
|
+
const buffer = new ReadWriteByteBuffer(data)
|
|
44
|
+
const messages: (NetworkMessage | RegularMessage)[] = []
|
|
45
|
+
let header: CrdtMessageHeader | null
|
|
46
|
+
while ((header = CrdtMessageProtocol.getHeader(buffer))) {
|
|
47
|
+
const offset = buffer.currentReadOffset()
|
|
48
|
+
let message: CrdtMessage | undefined = undefined
|
|
49
|
+
|
|
50
|
+
// Network messages
|
|
51
|
+
if (header.type === CrdtMessageType.DELETE_COMPONENT_NETWORK) {
|
|
52
|
+
message = DeleteComponentNetwork.read(buffer)!
|
|
53
|
+
} else if (header.type === CrdtMessageType.PUT_COMPONENT_NETWORK) {
|
|
54
|
+
message = PutNetworkComponentOperation.read(buffer)!
|
|
55
|
+
} else if (header.type === CrdtMessageType.DELETE_ENTITY_NETWORK) {
|
|
56
|
+
message = DeleteEntityNetwork.read(buffer)!
|
|
57
|
+
}
|
|
58
|
+
// Regular messages
|
|
59
|
+
else if (header.type === CrdtMessageType.PUT_COMPONENT) {
|
|
60
|
+
message = PutComponentOperation.read(buffer)!
|
|
61
|
+
} else if (header.type === CrdtMessageType.AUTHORITATIVE_PUT_COMPONENT) {
|
|
62
|
+
message = AuthoritativePutComponentOperation.read(buffer)!
|
|
63
|
+
} else if (header.type === CrdtMessageType.DELETE_COMPONENT) {
|
|
64
|
+
message = DeleteComponent.read(buffer)!
|
|
65
|
+
} else if (header.type === CrdtMessageType.DELETE_ENTITY) {
|
|
66
|
+
message = DeleteEntity.read(buffer)!
|
|
67
|
+
} else {
|
|
68
|
+
// consume unknown messages
|
|
69
|
+
buffer.incrementReadOffset(header.length)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (message) {
|
|
73
|
+
messages.push({
|
|
74
|
+
...message,
|
|
75
|
+
messageBuffer: buffer.buffer().subarray(offset, buffer.currentReadOffset())
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return messages
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function isNetworkMessage(message: ReceiveMessage): message is ReceiveNetworkMessage {
|
|
83
|
+
return [
|
|
84
|
+
CrdtMessageType.DELETE_COMPONENT_NETWORK,
|
|
85
|
+
CrdtMessageType.DELETE_ENTITY_NETWORK,
|
|
86
|
+
CrdtMessageType.PUT_COMPONENT_NETWORK
|
|
87
|
+
].includes(message.type)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function networkMessageToLocal(
|
|
91
|
+
message: ReceiveNetworkMessage,
|
|
92
|
+
localEntityId: Entity,
|
|
93
|
+
destinationBuffer: ByteBuffer,
|
|
94
|
+
// Optional network parent component for transform fixing
|
|
95
|
+
networkParentComponent?: typeof NetworkParent,
|
|
96
|
+
// Force corrections - converts PUT_COMPONENT_NETWORK to authoritative_PUT_COMPONENT
|
|
97
|
+
forceCorrections = false
|
|
98
|
+
): CrdtMessageBody {
|
|
99
|
+
if (message.type === CrdtMessageType.PUT_COMPONENT_NETWORK) {
|
|
100
|
+
let messageData = message.data
|
|
101
|
+
|
|
102
|
+
// Fix transform parent if needed for Unity/engine processing
|
|
103
|
+
if (message.componentId === TransformComponentId && networkParentComponent) {
|
|
104
|
+
const parentNetwork = networkParentComponent.getOrNull(localEntityId)
|
|
105
|
+
messageData = fixTransformParent(message, parentNetwork?.entityId)
|
|
106
|
+
}
|
|
107
|
+
if (forceCorrections) {
|
|
108
|
+
// Use AUTHORITATIVE_PUT_COMPONENT for forced state updates
|
|
109
|
+
AuthoritativePutComponentOperation.write(
|
|
110
|
+
localEntityId,
|
|
111
|
+
message.timestamp,
|
|
112
|
+
message.componentId,
|
|
113
|
+
messageData,
|
|
114
|
+
destinationBuffer
|
|
115
|
+
)
|
|
116
|
+
return {
|
|
117
|
+
type: CrdtMessageType.AUTHORITATIVE_PUT_COMPONENT,
|
|
118
|
+
componentId: message.componentId,
|
|
119
|
+
timestamp: message.timestamp,
|
|
120
|
+
data: messageData,
|
|
121
|
+
entityId: localEntityId
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
// Normal PUT_COMPONENT conversion
|
|
125
|
+
PutComponentOperation.write(localEntityId, message.timestamp, message.componentId, messageData, destinationBuffer)
|
|
126
|
+
return {
|
|
127
|
+
type: CrdtMessageType.PUT_COMPONENT,
|
|
128
|
+
componentId: message.componentId,
|
|
129
|
+
timestamp: message.timestamp,
|
|
130
|
+
data: messageData,
|
|
131
|
+
entityId: localEntityId
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
} else if (message.type === CrdtMessageType.DELETE_COMPONENT_NETWORK) {
|
|
135
|
+
DeleteComponent.write(localEntityId, message.componentId, message.timestamp, destinationBuffer)
|
|
136
|
+
return {
|
|
137
|
+
type: CrdtMessageType.DELETE_COMPONENT,
|
|
138
|
+
componentId: message.componentId,
|
|
139
|
+
timestamp: message.timestamp,
|
|
140
|
+
entityId: localEntityId
|
|
141
|
+
}
|
|
142
|
+
} else if (message.type === CrdtMessageType.DELETE_ENTITY_NETWORK) {
|
|
143
|
+
DeleteEntity.write(localEntityId, destinationBuffer)
|
|
144
|
+
return {
|
|
145
|
+
type: CrdtMessageType.DELETE_ENTITY,
|
|
146
|
+
entityId: localEntityId
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
throw 1
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function localMessageToNetwork(
|
|
153
|
+
message: ReceiveMessage,
|
|
154
|
+
network: INetowrkEntityType,
|
|
155
|
+
destinationBuffer: ByteBuffer
|
|
156
|
+
) {
|
|
157
|
+
if (message.type === CrdtMessageType.PUT_COMPONENT) {
|
|
158
|
+
PutNetworkComponentOperation.write(
|
|
159
|
+
network.entityId,
|
|
160
|
+
message.timestamp,
|
|
161
|
+
message.componentId,
|
|
162
|
+
network.networkId,
|
|
163
|
+
message.data,
|
|
164
|
+
destinationBuffer
|
|
165
|
+
)
|
|
166
|
+
} else if (message.type === CrdtMessageType.DELETE_COMPONENT) {
|
|
167
|
+
DeleteComponentNetwork.write(
|
|
168
|
+
network.entityId,
|
|
169
|
+
message.componentId,
|
|
170
|
+
message.timestamp,
|
|
171
|
+
network.networkId,
|
|
172
|
+
destinationBuffer
|
|
173
|
+
)
|
|
174
|
+
} else if (message.type === CrdtMessageType.DELETE_ENTITY) {
|
|
175
|
+
DeleteEntityNetwork.write(network.entityId, network.networkId, destinationBuffer)
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function fixTransformParent(message: ReceiveMessage, parent?: Entity): Uint8Array {
|
|
180
|
+
const buffer = new ReadWriteByteBuffer()
|
|
181
|
+
const transform = 'data' in message && TransformSchema.deserialize(new ReadWriteByteBuffer(message.data))
|
|
182
|
+
|
|
183
|
+
if (!transform) throw new Error('Invalid parent transform')
|
|
184
|
+
|
|
185
|
+
// Generate new transform raw data with the parent
|
|
186
|
+
const newTransform = { ...transform, parent }
|
|
187
|
+
TransformSchema.serialize(newTransform, buffer)
|
|
188
|
+
return buffer.toBinary()
|
|
189
|
+
}
|
package/src/network/state.ts
CHANGED
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
TriggerAreaResult,
|
|
29
29
|
ComponentDefinition
|
|
30
30
|
} from '@dcl/ecs'
|
|
31
|
-
import { LIVEKIT_MAX_SIZE } from '
|
|
31
|
+
import { LIVEKIT_MAX_SIZE } from './server'
|
|
32
32
|
|
|
33
33
|
export const NOT_SYNC_COMPONENTS: ComponentDefinition<unknown>[] = [
|
|
34
34
|
VideoEvent,
|
|
@@ -79,9 +79,9 @@ export function engineToCrdt(engine: IEngine): Uint8Array[] {
|
|
|
79
79
|
if (!shouldSyncComponent(itComponentDefinition)) {
|
|
80
80
|
continue
|
|
81
81
|
}
|
|
82
|
+
|
|
82
83
|
itComponentDefinition.dumpCrdtStateToBuffer(crdtBuffer, (entity) => {
|
|
83
|
-
|
|
84
|
-
return isNetworkEntity
|
|
84
|
+
return NetworkEntity.has(entity)
|
|
85
85
|
})
|
|
86
86
|
}
|
|
87
87
|
|
|
@@ -103,7 +103,6 @@ export function engineToCrdt(engine: IEngine): Uint8Array[] {
|
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
// If the message itself is larger than the limit, we need to handle it specially
|
|
106
|
-
// For now, we'll skip it to prevent infinite loops
|
|
107
106
|
if (messageSize / 1024 > LIVEKIT_MAX_SIZE) {
|
|
108
107
|
console.error(
|
|
109
108
|
`Message too large (${messageSize} bytes), skipping component ${message.componentId} for entity ${message.entityId}`
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { getStorageServerUrl } from './storage-url'
|
|
2
|
+
import { assertIsServer, wrapSignedFetch } from './utils'
|
|
3
|
+
|
|
4
|
+
const MODULE_NAME = 'EnvVar'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* EnvVar provides methods to fetch environment variables from the
|
|
8
|
+
* Server Side Storage service. This module only works when running
|
|
9
|
+
* on server-side scenes.
|
|
10
|
+
*/
|
|
11
|
+
export const EnvVar = {
|
|
12
|
+
/**
|
|
13
|
+
* Fetches a specific environment variable by key as plain text.
|
|
14
|
+
*
|
|
15
|
+
* @param key - The name of the environment variable to fetch
|
|
16
|
+
* @returns A promise that resolves to the plain text value, or empty string if not found
|
|
17
|
+
* @throws Error if not running on a server-side scene
|
|
18
|
+
*/
|
|
19
|
+
async get(key: string): Promise<string> {
|
|
20
|
+
assertIsServer(MODULE_NAME)
|
|
21
|
+
|
|
22
|
+
const baseUrl = await getStorageServerUrl()
|
|
23
|
+
const url = `${baseUrl}/env/${encodeURIComponent(key)}`
|
|
24
|
+
|
|
25
|
+
const [error, data] = await wrapSignedFetch<{ value: string }>({
|
|
26
|
+
url
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
if (error) {
|
|
30
|
+
console.error(`Failed to fetch environment variable '${key}': ${error}`)
|
|
31
|
+
return ''
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return data?.value ?? ''
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
export const MODULE_NAME = 'Storage'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Options for getValues pagination and filtering.
|
|
5
|
+
*/
|
|
6
|
+
export interface GetValuesOptions {
|
|
7
|
+
prefix?: string
|
|
8
|
+
limit?: number
|
|
9
|
+
offset?: number
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Result of getValues with pagination metadata.
|
|
14
|
+
*/
|
|
15
|
+
export interface GetValuesResult {
|
|
16
|
+
/** Key-value entries for the current page. */
|
|
17
|
+
data: Array<{ key: string; value: unknown }>
|
|
18
|
+
pagination: {
|
|
19
|
+
offset: number
|
|
20
|
+
total: number
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Per-call options for Storage get().
|
|
26
|
+
*/
|
|
27
|
+
export interface GetOptions {
|
|
28
|
+
/**
|
|
29
|
+
* When true, bypasses the read cache and forces a network read (the result
|
|
30
|
+
* still refreshes the cache). Concurrent gets for the same key still share
|
|
31
|
+
* one in-flight request. Default: false.
|
|
32
|
+
*/
|
|
33
|
+
fresh?: boolean
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Per-call options for Storage set().
|
|
38
|
+
*/
|
|
39
|
+
export interface SetOptions {
|
|
40
|
+
/**
|
|
41
|
+
* When true, skips the network write if the serialized value matches the
|
|
42
|
+
* last value known to be stored for this key (from a previous successful
|
|
43
|
+
* set() or get()). Overrides the configured default. Default: true.
|
|
44
|
+
*/
|
|
45
|
+
skipIfUnchanged?: boolean
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Module-wide configuration for Storage, applied to both scene-scoped and
|
|
50
|
+
* player-scoped storage via Storage.configure().
|
|
51
|
+
*/
|
|
52
|
+
export interface StorageOptions {
|
|
53
|
+
/**
|
|
54
|
+
* Default for set()'s skipIfUnchanged when not passed per call. A skip only
|
|
55
|
+
* suppresses a proven no-op: the exact serialized value was confirmed stored
|
|
56
|
+
* by a previous network round-trip within cacheMaxAgeMs. Default: true.
|
|
57
|
+
*/
|
|
58
|
+
skipIfUnchanged?: boolean
|
|
59
|
+
/**
|
|
60
|
+
* When true, get() serves values from the local cache within cacheMaxAgeMs,
|
|
61
|
+
* including confirmed "not found" results, without a network request.
|
|
62
|
+
* Out-of-band writers (e.g. CLI storage commands) may not be visible for up
|
|
63
|
+
* to cacheMaxAgeMs; pass { fresh: true } per call to force a network read.
|
|
64
|
+
* Default: true.
|
|
65
|
+
*/
|
|
66
|
+
cacheReads?: boolean
|
|
67
|
+
/**
|
|
68
|
+
* Max cache entries per scope (scene / player) before oldest entries are
|
|
69
|
+
* evicted. Entries hold the serialized value body, so memory scales with
|
|
70
|
+
* value size — this bounds entry count, not bytes. Default: 512.
|
|
71
|
+
*/
|
|
72
|
+
cacheMaxEntries?: number
|
|
73
|
+
/**
|
|
74
|
+
* Max age of a cache entry in milliseconds, bounding both read-cache
|
|
75
|
+
* staleness and write-dedup trust; older entries are treated as unknown.
|
|
76
|
+
* The default is deliberately short so out-of-band writers (CLI storage
|
|
77
|
+
* commands, dashboards) become visible within a minute, while still
|
|
78
|
+
* absorbing hot per-frame reads. Default: 60000 (1 minute).
|
|
79
|
+
*/
|
|
80
|
+
cacheMaxAgeMs?: number
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Fully-resolved storage configuration shared by scene and player storage.
|
|
85
|
+
* @internal
|
|
86
|
+
*/
|
|
87
|
+
export interface StorageConfigState {
|
|
88
|
+
skipIfUnchanged: boolean
|
|
89
|
+
cacheReads: boolean
|
|
90
|
+
cacheMaxEntries: number
|
|
91
|
+
cacheMaxAgeMs: number
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Default values for the resolved storage configuration.
|
|
96
|
+
* @internal
|
|
97
|
+
*/
|
|
98
|
+
export const DEFAULT_STORAGE_CONFIG: Readonly<StorageConfigState> = {
|
|
99
|
+
skipIfUnchanged: true,
|
|
100
|
+
cacheReads: true,
|
|
101
|
+
cacheMaxEntries: 512,
|
|
102
|
+
cacheMaxAgeMs: 60 * 1000
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Creates a mutable config object, later mutated by Storage.configure().
|
|
107
|
+
* @internal
|
|
108
|
+
*/
|
|
109
|
+
export function createStorageConfig(overrides?: StorageOptions): StorageConfigState {
|
|
110
|
+
return { ...DEFAULT_STORAGE_CONFIG, ...overrides }
|
|
111
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { createStorageConfig, StorageOptions } from './constants'
|
|
2
|
+
import { createSceneStorage, ISceneStorage } from './scene'
|
|
3
|
+
import { createPlayerStorage, IPlayerStorage } from './player'
|
|
4
|
+
|
|
5
|
+
// Re-export interfaces and types
|
|
6
|
+
export { GetOptions, GetValuesOptions, GetValuesResult, SetOptions, StorageOptions } from './constants'
|
|
7
|
+
export { ISceneStorage } from './scene'
|
|
8
|
+
export { IPlayerStorage } from './player'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Storage interface with methods for scene-scoped and player-scoped storage.
|
|
12
|
+
*/
|
|
13
|
+
export interface IStorage extends ISceneStorage {
|
|
14
|
+
/** Player-scoped storage for key-value pairs */
|
|
15
|
+
player: IPlayerStorage
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Sets module-wide defaults for both scene-scoped and player-scoped storage.
|
|
19
|
+
* Merges the given partial options into the current configuration.
|
|
20
|
+
* Note: cacheMaxEntries applies per scope (scene and player each keep their
|
|
21
|
+
* own cache) and bounds entry count, not bytes (entries hold serialized
|
|
22
|
+
* value bodies).
|
|
23
|
+
*/
|
|
24
|
+
configure(options: StorageOptions): void
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Creates the Storage module with scene-scoped and player-scoped storage.
|
|
29
|
+
*/
|
|
30
|
+
const createStorage = (): IStorage => {
|
|
31
|
+
const config = createStorageConfig()
|
|
32
|
+
const sceneStorage = createSceneStorage(config)
|
|
33
|
+
const playerStorage = createPlayerStorage(config)
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
// Spread scene storage methods at top level
|
|
37
|
+
get: sceneStorage.get,
|
|
38
|
+
set: sceneStorage.set,
|
|
39
|
+
delete: sceneStorage.delete,
|
|
40
|
+
getValues: sceneStorage.getValues,
|
|
41
|
+
// Keep player as nested property
|
|
42
|
+
player: playerStorage,
|
|
43
|
+
configure(options: StorageOptions): void {
|
|
44
|
+
for (const [key, value] of Object.entries(options)) {
|
|
45
|
+
if (value !== undefined) (config as unknown as Record<string, unknown>)[key] = value
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Storage provides methods to store and retrieve key-value data from the
|
|
53
|
+
* Server Side Storage service.
|
|
54
|
+
*
|
|
55
|
+
* - Use Storage.get/set/delete/getValues for scene-scoped storage
|
|
56
|
+
* - Use Storage.player.get/set/delete/getValues for player-scoped storage
|
|
57
|
+
* - Use Storage.configure to change module-wide defaults (e.g. skipIfUnchanged, cacheReads)
|
|
58
|
+
*
|
|
59
|
+
* Reads are cached by default: get() serves values known from a network
|
|
60
|
+
* round-trip within the last cacheMaxAgeMs (including confirmed "not found"
|
|
61
|
+
* results) without hitting the service, and concurrent gets for the same key
|
|
62
|
+
* share one request. Overlapping writes to the same key are serialized so the
|
|
63
|
+
* service commits them in issue order, and rapid writes coalesce to the
|
|
64
|
+
* latest value; every set()/delete() resolves once its value (or the newer
|
|
65
|
+
* value that superseded it) is durably applied. By default (skipIfUnchanged)
|
|
66
|
+
* an unchanged set is skipped, but only when a previous confirmed round-trip
|
|
67
|
+
* proved the exact value is already stored. Out-of-band writers (e.g. CLI storage commands)
|
|
68
|
+
* may not be visible for up to cacheMaxAgeMs; use get(key, { fresh: true })
|
|
69
|
+
* for an authoritative read or Storage.configure({ cacheReads: false }) to
|
|
70
|
+
* disable read caching.
|
|
71
|
+
*
|
|
72
|
+
* This module only works when running on server-side scenes.
|
|
73
|
+
*/
|
|
74
|
+
export const Storage: IStorage = createStorage()
|