deepspace 0.15.0 → 0.17.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/CHANGELOG.md +118 -0
- package/dist/cli.js +562 -237
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +26 -157
- package/dist/index.js +209 -331
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +28 -154
- package/dist/server.js +38 -426
- package/dist/server.js.map +1 -1
- package/dist/worker.d.ts +44 -227
- package/dist/worker.js +39 -433
- package/dist/worker.js.map +1 -1
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -180,19 +180,21 @@ var MAX_UPLOAD_REQUEST_BYTES = 25 * 1024 * 1024;
|
|
|
180
180
|
var UPLOAD_PART_BYTES = 20 * 1024 * 1024;
|
|
181
181
|
var MAX_UPLOAD_PARTS = Math.ceil(MAX_APP_FILE_BYTES / UPLOAD_PART_BYTES);
|
|
182
182
|
var MAX_BASE64_UPLOAD_BYTES = Math.floor(MAX_UPLOAD_REQUEST_BYTES * 3 / 4);
|
|
183
|
-
var
|
|
183
|
+
var GIB = 1024 * 1024 * 1024;
|
|
184
|
+
var ACCOUNT_STORAGE_LIMIT_BYTES = {
|
|
184
185
|
test: 0,
|
|
185
|
-
free:
|
|
186
|
-
starter:
|
|
187
|
-
premium:
|
|
188
|
-
admin:
|
|
186
|
+
free: 1 * GIB,
|
|
187
|
+
starter: 10 * GIB,
|
|
188
|
+
premium: 30 * GIB,
|
|
189
|
+
admin: 100 * GIB
|
|
189
190
|
};
|
|
190
191
|
function storageLimitForTier(tier) {
|
|
191
|
-
if (tier && Object.hasOwn(
|
|
192
|
-
return
|
|
192
|
+
if (tier && Object.hasOwn(ACCOUNT_STORAGE_LIMIT_BYTES, tier)) {
|
|
193
|
+
return ACCOUNT_STORAGE_LIMIT_BYTES[tier];
|
|
193
194
|
}
|
|
194
|
-
return
|
|
195
|
+
return ACCOUNT_STORAGE_LIMIT_BYTES.free;
|
|
195
196
|
}
|
|
197
|
+
var REPO_STORE_LIMIT_BYTES = 256 * 1024 * 1024;
|
|
196
198
|
function storageQuotaMessage(incomingBytes, usedBytes, limitBytes) {
|
|
197
199
|
const free = Math.max(0, limitBytes - usedBytes);
|
|
198
200
|
return `This upload needs ${formatBytes(incomingBytes)} but the app's file storage has ${formatBytes(free)} free (${formatBytes(usedBytes)} of ${formatBytes(limitBytes)} used). Delete files with \`deepspace app files rm <key>\`, or upgrade the owner's plan.`;
|
|
@@ -283,7 +285,7 @@ function declaredLength(request, what) {
|
|
|
283
285
|
function isKeyWithinPrefix(key, prefix) {
|
|
284
286
|
return key.startsWith(prefix);
|
|
285
287
|
}
|
|
286
|
-
async function
|
|
288
|
+
async function prefixUsage(bucket, prefix) {
|
|
287
289
|
let total = 0;
|
|
288
290
|
let cursor;
|
|
289
291
|
do {
|
|
@@ -293,6 +295,13 @@ async function allocationUsage(bucket, prefix) {
|
|
|
293
295
|
} while (cursor !== void 0);
|
|
294
296
|
return total;
|
|
295
297
|
}
|
|
298
|
+
async function allocationUsage(bucket, storage) {
|
|
299
|
+
const siblings = storage.siblingPrefixes ? await storage.siblingPrefixes() : [];
|
|
300
|
+
if (siblings === null) return null;
|
|
301
|
+
const prefixes = [storage.prefix, ...siblings.filter((p) => p !== storage.prefix)];
|
|
302
|
+
const totals = await Promise.all(prefixes.map((prefix) => prefixUsage(bucket, prefix)));
|
|
303
|
+
return totals.reduce((sum, bytes) => sum + bytes, 0);
|
|
304
|
+
}
|
|
296
305
|
async function admitStorage(bucket, storage, incomingBytes, replacesKey) {
|
|
297
306
|
if (!storage) return null;
|
|
298
307
|
const limit = await storage.limitBytes();
|
|
@@ -303,7 +312,14 @@ async function admitStorage(bucket, storage, incomingBytes, replacesKey) {
|
|
|
303
312
|
"storage_limit_unavailable"
|
|
304
313
|
);
|
|
305
314
|
}
|
|
306
|
-
const stored = await allocationUsage(bucket, storage
|
|
315
|
+
const stored = await allocationUsage(bucket, storage);
|
|
316
|
+
if (stored === null) {
|
|
317
|
+
return fail(
|
|
318
|
+
503,
|
|
319
|
+
"The storage already used by this account could not be measured; nothing was uploaded. Retry shortly.",
|
|
320
|
+
"storage_limit_unavailable"
|
|
321
|
+
);
|
|
322
|
+
}
|
|
307
323
|
const replaced = replacesKey === null ? 0 : (await bucket.head(replacesKey))?.size ?? 0;
|
|
308
324
|
const used = stored - replaced;
|
|
309
325
|
if (used + incomingBytes <= limit) return null;
|
|
@@ -319,7 +335,11 @@ async function admitAssembledObject(bucket, storage, key, objectBytes) {
|
|
|
319
335
|
console.error("[files:quota] limit unavailable at complete; keeping", key);
|
|
320
336
|
return null;
|
|
321
337
|
}
|
|
322
|
-
const stored = await allocationUsage(bucket, storage
|
|
338
|
+
const stored = await allocationUsage(bucket, storage);
|
|
339
|
+
if (stored === null) {
|
|
340
|
+
console.error("[files:quota] account usage unmeasurable at complete; keeping", key);
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
323
343
|
if (stored <= limit) return null;
|
|
324
344
|
await bucket.delete(key).catch(() => void 0);
|
|
325
345
|
return fail(
|
|
@@ -716,9 +736,10 @@ async function handleList(bucket, prefix, url, scope, auth) {
|
|
|
716
736
|
});
|
|
717
737
|
let storage;
|
|
718
738
|
if (auth.storage) {
|
|
719
|
-
|
|
739
|
+
const usedBytes = await allocationUsage(bucket, auth.storage);
|
|
740
|
+
if (usedBytes !== null) storage = { usedBytes };
|
|
720
741
|
const limitBytes = await auth.storage.limitBytes();
|
|
721
|
-
if (limitBytes !== null) storage.limitBytes = limitBytes;
|
|
742
|
+
if (storage && limitBytes !== null) storage.limitBytes = limitBytes;
|
|
722
743
|
}
|
|
723
744
|
return Response.json(
|
|
724
745
|
{ files, truncated: listed.truncated, ...storage ? { storage } : {} },
|
|
@@ -2199,15 +2220,6 @@ var MSG = {
|
|
|
2199
2220
|
ACK: "records.ack",
|
|
2200
2221
|
LIST_SCHEMAS: "records.list_schemas",
|
|
2201
2222
|
RESUBSCRIBE: "records.resubscribe",
|
|
2202
|
-
// ---- GameRoom --------------------------------------------------------
|
|
2203
|
-
GAME_STATE: "game.state",
|
|
2204
|
-
GAME_INPUT: "game.input",
|
|
2205
|
-
GAME_PLAYER_JOIN: "game.player_join",
|
|
2206
|
-
GAME_PLAYER_LEAVE: "game.player_leave",
|
|
2207
|
-
GAME_PLAYER_READY: "game.player_ready",
|
|
2208
|
-
GAME_START: "game.start",
|
|
2209
|
-
GAME_END: "game.end",
|
|
2210
|
-
GAME_TICK: "game.tick",
|
|
2211
2223
|
// ---- CanvasRoom ------------------------------------------------------
|
|
2212
2224
|
CANVAS_SHAPES: "canvas.shapes",
|
|
2213
2225
|
CANVAS_ADD: "canvas.add",
|
|
@@ -2701,14 +2713,6 @@ var serverBuild = {
|
|
|
2701
2713
|
type: MSG.YJS_JOIN,
|
|
2702
2714
|
payload: { collection, recordId, fieldName, canWrite }
|
|
2703
2715
|
}),
|
|
2704
|
-
// Game
|
|
2705
|
-
gameState: (state, tick, players, running) => ({ type: MSG.GAME_STATE, payload: { state, tick, players, running } }),
|
|
2706
|
-
gameTick: (state, tick) => ({ type: MSG.GAME_TICK, payload: { state, tick } }),
|
|
2707
|
-
gameStart: (state, tick) => ({ type: MSG.GAME_START, payload: { state, tick } }),
|
|
2708
|
-
gameEnd: (state, tick) => ({ type: MSG.GAME_END, payload: { state, tick } }),
|
|
2709
|
-
gamePlayerJoin: (player) => ({ type: MSG.GAME_PLAYER_JOIN, payload: { player } }),
|
|
2710
|
-
gamePlayerLeave: (userId) => ({ type: MSG.GAME_PLAYER_LEAVE, payload: { userId } }),
|
|
2711
|
-
gamePlayerReady: (userId) => ({ type: MSG.GAME_PLAYER_READY, payload: { userId } }),
|
|
2712
2716
|
// Canvas
|
|
2713
2717
|
canvasShapes: (shapes, viewports) => ({ type: MSG.CANVAS_SHAPES, payload: { shapes, viewports } }),
|
|
2714
2718
|
canvasAdd: (shape) => ({ type: MSG.CANVAS_ADD, payload: { shape } }),
|
|
@@ -4304,136 +4308,6 @@ var RecordRoom = class extends BaseRoom {
|
|
|
4304
4308
|
);
|
|
4305
4309
|
`);
|
|
4306
4310
|
this.ensureAllCollectionTables();
|
|
4307
|
-
await this.migrateUsersTableIfExists();
|
|
4308
|
-
await this.migrateRecordsTable();
|
|
4309
|
-
}
|
|
4310
|
-
async migrateUsersTableIfExists() {
|
|
4311
|
-
try {
|
|
4312
|
-
const tableCheck = this.sql.exec(
|
|
4313
|
-
`SELECT name FROM sqlite_master WHERE type='table' AND name='users'`
|
|
4314
|
-
);
|
|
4315
|
-
if (tableCheck.toArray().length === 0) {
|
|
4316
|
-
return;
|
|
4317
|
-
}
|
|
4318
|
-
const oldUsers = this.sql.exec(`SELECT * FROM users`).toArray();
|
|
4319
|
-
if (oldUsers.length === 0) {
|
|
4320
|
-
this.sql.exec(`DROP TABLE IF EXISTS users`);
|
|
4321
|
-
return;
|
|
4322
|
-
}
|
|
4323
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4324
|
-
for (const row of oldUsers) {
|
|
4325
|
-
const r = row;
|
|
4326
|
-
const existing = this.sql.exec(`SELECT 1 FROM c_users WHERE _row_id = ?`, r.user_id).toArray();
|
|
4327
|
-
if (existing.length > 0) continue;
|
|
4328
|
-
this.sql.exec(
|
|
4329
|
-
`INSERT INTO c_users (_row_id, _created_by, _created_at, _updated_at, col_email, col_name, col_imageurl, col_role, col_createdat, col_lastseenat) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
4330
|
-
r.user_id,
|
|
4331
|
-
r.user_id,
|
|
4332
|
-
r.created_at,
|
|
4333
|
-
now,
|
|
4334
|
-
r.email,
|
|
4335
|
-
r.name,
|
|
4336
|
-
r.image_url || null,
|
|
4337
|
-
r.role,
|
|
4338
|
-
r.created_at,
|
|
4339
|
-
r.last_seen_at
|
|
4340
|
-
);
|
|
4341
|
-
}
|
|
4342
|
-
this.sql.exec(`DROP TABLE IF EXISTS users`);
|
|
4343
|
-
} catch (error) {
|
|
4344
|
-
console.error(`[RecordRoom] Users table migration error:`, error);
|
|
4345
|
-
}
|
|
4346
|
-
}
|
|
4347
|
-
async migrateRecordsTable() {
|
|
4348
|
-
try {
|
|
4349
|
-
const tableCheck = this.sql.exec(
|
|
4350
|
-
`SELECT name FROM sqlite_master WHERE type='table' AND name='records'`
|
|
4351
|
-
);
|
|
4352
|
-
if (tableCheck.toArray().length === 0) return;
|
|
4353
|
-
const rows = this.sql.exec(`SELECT * FROM records`).toArray();
|
|
4354
|
-
if (rows.length === 0) {
|
|
4355
|
-
this.sql.exec(`DROP TABLE IF EXISTS records`);
|
|
4356
|
-
return;
|
|
4357
|
-
}
|
|
4358
|
-
const prepared = [];
|
|
4359
|
-
const blocked = {
|
|
4360
|
-
missingSchema: 0,
|
|
4361
|
-
invalidData: 0,
|
|
4362
|
-
targetConflict: 0
|
|
4363
|
-
};
|
|
4364
|
-
for (const row of rows) {
|
|
4365
|
-
const r = row;
|
|
4366
|
-
const schema = this.schemaRegistry.get(r.collection);
|
|
4367
|
-
if (!schema || !schema.columns) {
|
|
4368
|
-
blocked.missingSchema++;
|
|
4369
|
-
continue;
|
|
4370
|
-
}
|
|
4371
|
-
const tbl = collectionTableName(r.collection);
|
|
4372
|
-
const tblExists = this.sql.exec(`SELECT name FROM sqlite_master WHERE type='table' AND name=?`, tbl).toArray().length > 0;
|
|
4373
|
-
if (!tblExists) {
|
|
4374
|
-
this.ensureCollectionTable(schema);
|
|
4375
|
-
}
|
|
4376
|
-
const existing = this.sql.exec(`SELECT 1 FROM "${tbl}" WHERE _row_id = ?`, r.record_id).toArray();
|
|
4377
|
-
if (existing.length > 0) {
|
|
4378
|
-
blocked.targetConflict++;
|
|
4379
|
-
continue;
|
|
4380
|
-
}
|
|
4381
|
-
let data;
|
|
4382
|
-
try {
|
|
4383
|
-
data = JSON.parse(r.data);
|
|
4384
|
-
} catch {
|
|
4385
|
-
blocked.invalidData++;
|
|
4386
|
-
continue;
|
|
4387
|
-
}
|
|
4388
|
-
if (data === null || typeof data !== "object" || Array.isArray(data)) {
|
|
4389
|
-
blocked.invalidData++;
|
|
4390
|
-
continue;
|
|
4391
|
-
}
|
|
4392
|
-
const columns = schema.columns.map(resolveColumn);
|
|
4393
|
-
const colValues = dataToColumnValues(data, columns);
|
|
4394
|
-
const colIds = Object.keys(colValues);
|
|
4395
|
-
const allCols = [
|
|
4396
|
-
"_row_id",
|
|
4397
|
-
"_created_by",
|
|
4398
|
-
"_created_at",
|
|
4399
|
-
"_updated_at",
|
|
4400
|
-
...colIds.map((c) => `"${c}"`)
|
|
4401
|
-
];
|
|
4402
|
-
const placeholders = allCols.map(() => "?").join(", ");
|
|
4403
|
-
const params = [
|
|
4404
|
-
r.record_id,
|
|
4405
|
-
r.created_by,
|
|
4406
|
-
r.created_at,
|
|
4407
|
-
r.updated_at,
|
|
4408
|
-
...colIds.map((c) => colValues[c])
|
|
4409
|
-
];
|
|
4410
|
-
prepared.push({
|
|
4411
|
-
sql: `INSERT INTO "${tbl}" (${allCols.join(", ")}) VALUES (${placeholders})`,
|
|
4412
|
-
params
|
|
4413
|
-
});
|
|
4414
|
-
}
|
|
4415
|
-
const blockedCount = blocked.missingSchema + blocked.invalidData + blocked.targetConflict;
|
|
4416
|
-
if (blockedCount > 0) {
|
|
4417
|
-
console.error(
|
|
4418
|
-
`[RecordRoom] Legacy records migration blocked; no records migrated and the records table was retained for retry (${blockedCount} blocked: ${blocked.missingSchema} missing schema, ${blocked.invalidData} invalid data, ${blocked.targetConflict} target conflict)`
|
|
4419
|
-
);
|
|
4420
|
-
return;
|
|
4421
|
-
}
|
|
4422
|
-
this.state.storage.transactionSync(() => {
|
|
4423
|
-
for (const record of prepared) {
|
|
4424
|
-
this.sql.exec(record.sql, ...record.params);
|
|
4425
|
-
}
|
|
4426
|
-
this.sql.exec(`DROP TABLE IF EXISTS records`);
|
|
4427
|
-
});
|
|
4428
|
-
console.log(
|
|
4429
|
-
`[RecordRoom] Migrated ${prepared.length} records from document-mode; legacy records table removed`
|
|
4430
|
-
);
|
|
4431
|
-
} catch (error) {
|
|
4432
|
-
console.error(
|
|
4433
|
-
`[RecordRoom] Legacy records migration failed; no records migrated and the records table was retained for retry:`,
|
|
4434
|
-
error
|
|
4435
|
-
);
|
|
4436
|
-
}
|
|
4437
4311
|
}
|
|
4438
4312
|
// ============================================================================
|
|
4439
4313
|
// Table-Mode Schema Management
|
|
@@ -4910,6 +4784,9 @@ var YjsRoom = class extends BaseRoom {
|
|
|
4910
4784
|
}
|
|
4911
4785
|
};
|
|
4912
4786
|
|
|
4787
|
+
// src/server/rooms/canvas-room.ts
|
|
4788
|
+
import * as Y5 from "yjs";
|
|
4789
|
+
|
|
4913
4790
|
// src/shared/roles.ts
|
|
4914
4791
|
var ROLES = {
|
|
4915
4792
|
VIEWER: "viewer",
|
|
@@ -4917,271 +4794,7 @@ var ROLES = {
|
|
|
4917
4794
|
ADMIN: "admin"
|
|
4918
4795
|
};
|
|
4919
4796
|
|
|
4920
|
-
// src/server/rooms/game-room.ts
|
|
4921
|
-
var GAME_WRITE_TYPES = /* @__PURE__ */ new Set([
|
|
4922
|
-
MSG.GAME_INPUT,
|
|
4923
|
-
MSG.GAME_PLAYER_READY,
|
|
4924
|
-
MSG.GAME_START,
|
|
4925
|
-
MSG.GAME_END
|
|
4926
|
-
]);
|
|
4927
|
-
var GameRoom = class extends BaseRoom {
|
|
4928
|
-
config;
|
|
4929
|
-
players = /* @__PURE__ */ new Map();
|
|
4930
|
-
inputBuffer = [];
|
|
4931
|
-
currentTick = 0;
|
|
4932
|
-
gameState = {};
|
|
4933
|
-
running = false;
|
|
4934
|
-
initialized = false;
|
|
4935
|
-
constructor(state, env, config = {}) {
|
|
4936
|
-
super(state, env);
|
|
4937
|
-
this.config = {
|
|
4938
|
-
tickRate: config.tickRate ?? 20,
|
|
4939
|
-
minPlayers: config.minPlayers ?? 1,
|
|
4940
|
-
maxPlayers: config.maxPlayers ?? Infinity
|
|
4941
|
-
};
|
|
4942
|
-
}
|
|
4943
|
-
ensureInitialized() {
|
|
4944
|
-
if (this.initialized) return;
|
|
4945
|
-
this.initialized = true;
|
|
4946
|
-
this.sql.exec(`
|
|
4947
|
-
CREATE TABLE IF NOT EXISTS game_state (
|
|
4948
|
-
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
4949
|
-
state TEXT NOT NULL,
|
|
4950
|
-
tick INTEGER NOT NULL DEFAULT 0,
|
|
4951
|
-
updated_at TEXT NOT NULL
|
|
4952
|
-
)
|
|
4953
|
-
`);
|
|
4954
|
-
const rows = this.sql.exec("SELECT state, tick FROM game_state WHERE id = 1").toArray();
|
|
4955
|
-
if (rows.length > 0) {
|
|
4956
|
-
try {
|
|
4957
|
-
const parsed = JSON.parse(rows[0].state);
|
|
4958
|
-
this.gameState = this.onHydrateState(parsed);
|
|
4959
|
-
this.currentTick = rows[0].tick;
|
|
4960
|
-
} catch {
|
|
4961
|
-
}
|
|
4962
|
-
}
|
|
4963
|
-
}
|
|
4964
|
-
persistState() {
|
|
4965
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4966
|
-
this.sql.exec(
|
|
4967
|
-
`INSERT INTO game_state (id, state, tick, updated_at) VALUES (1, ?, ?, ?)
|
|
4968
|
-
ON CONFLICT(id) DO UPDATE SET state = ?, tick = ?, updated_at = ?`,
|
|
4969
|
-
JSON.stringify(this.gameState),
|
|
4970
|
-
this.currentTick,
|
|
4971
|
-
now,
|
|
4972
|
-
JSON.stringify(this.gameState),
|
|
4973
|
-
this.currentTick,
|
|
4974
|
-
now
|
|
4975
|
-
);
|
|
4976
|
-
}
|
|
4977
|
-
// ==========================================================================
|
|
4978
|
-
// BaseRoom Lifecycle
|
|
4979
|
-
// ==========================================================================
|
|
4980
|
-
onConnect(ws, user) {
|
|
4981
|
-
this.ensureInitialized();
|
|
4982
|
-
const role = user.role ?? ROLES.VIEWER;
|
|
4983
|
-
const canWrite = role === ROLES.MEMBER || role === ROLES.ADMIN;
|
|
4984
|
-
const attachment = {
|
|
4985
|
-
...user,
|
|
4986
|
-
joinedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4987
|
-
canWrite
|
|
4988
|
-
};
|
|
4989
|
-
this.sendTo(ws, { type: MSG.AUTH, payload: { canWrite } });
|
|
4990
|
-
if (!canWrite) {
|
|
4991
|
-
this.sendTo(ws, {
|
|
4992
|
-
type: MSG.GAME_STATE,
|
|
4993
|
-
payload: {
|
|
4994
|
-
state: this.gameState,
|
|
4995
|
-
tick: this.currentTick,
|
|
4996
|
-
players: Array.from(this.players.values()),
|
|
4997
|
-
running: this.running
|
|
4998
|
-
}
|
|
4999
|
-
});
|
|
5000
|
-
return attachment;
|
|
5001
|
-
}
|
|
5002
|
-
const player = {
|
|
5003
|
-
userId: user.userId,
|
|
5004
|
-
userName: user.userName,
|
|
5005
|
-
ready: false,
|
|
5006
|
-
connectedAt: attachment.joinedAt,
|
|
5007
|
-
data: {}
|
|
5008
|
-
};
|
|
5009
|
-
if (this.config.maxPlayers !== Infinity && this.players.size >= this.config.maxPlayers) {
|
|
5010
|
-
this.sendTo(ws, { type: MSG.ERROR, payload: { error: "Game is full" } });
|
|
5011
|
-
return attachment;
|
|
5012
|
-
}
|
|
5013
|
-
this.players.set(user.userId, player);
|
|
5014
|
-
this.sendTo(ws, {
|
|
5015
|
-
type: MSG.GAME_STATE,
|
|
5016
|
-
payload: {
|
|
5017
|
-
state: this.gameState,
|
|
5018
|
-
tick: this.currentTick,
|
|
5019
|
-
players: Array.from(this.players.values()),
|
|
5020
|
-
running: this.running
|
|
5021
|
-
}
|
|
5022
|
-
});
|
|
5023
|
-
this.broadcast({ type: MSG.GAME_PLAYER_JOIN, payload: { player } }, ws);
|
|
5024
|
-
this.onPlayerJoin(player);
|
|
5025
|
-
return attachment;
|
|
5026
|
-
}
|
|
5027
|
-
async onMessage(ws, user, message) {
|
|
5028
|
-
this.ensureInitialized();
|
|
5029
|
-
const { type, payload } = message;
|
|
5030
|
-
if (GAME_WRITE_TYPES.has(type) && !user.canWrite) {
|
|
5031
|
-
this.sendTo(ws, {
|
|
5032
|
-
type: MSG.ERROR,
|
|
5033
|
-
payload: { error: "Write access denied: spectators cannot drive the game" }
|
|
5034
|
-
});
|
|
5035
|
-
return;
|
|
5036
|
-
}
|
|
5037
|
-
switch (type) {
|
|
5038
|
-
case MSG.GAME_INPUT: {
|
|
5039
|
-
this.inputBuffer.push({
|
|
5040
|
-
userId: user.userId,
|
|
5041
|
-
action: payload.action,
|
|
5042
|
-
data: payload.data ?? {},
|
|
5043
|
-
tick: this.currentTick
|
|
5044
|
-
});
|
|
5045
|
-
break;
|
|
5046
|
-
}
|
|
5047
|
-
case MSG.GAME_PLAYER_READY: {
|
|
5048
|
-
const player = this.players.get(user.userId);
|
|
5049
|
-
if (player) {
|
|
5050
|
-
player.ready = true;
|
|
5051
|
-
this.broadcast({ type: MSG.GAME_PLAYER_READY, payload: { userId: user.userId } });
|
|
5052
|
-
this.checkAutoStart();
|
|
5053
|
-
}
|
|
5054
|
-
break;
|
|
5055
|
-
}
|
|
5056
|
-
case MSG.GAME_START: {
|
|
5057
|
-
if (!this.running) {
|
|
5058
|
-
this.startGame();
|
|
5059
|
-
}
|
|
5060
|
-
break;
|
|
5061
|
-
}
|
|
5062
|
-
case MSG.GAME_END: {
|
|
5063
|
-
if (this.running) {
|
|
5064
|
-
this.stopGame();
|
|
5065
|
-
}
|
|
5066
|
-
break;
|
|
5067
|
-
}
|
|
5068
|
-
default:
|
|
5069
|
-
this.sendTo(ws, { type: MSG.ERROR, payload: { error: `Unknown game message type: ${type}` } });
|
|
5070
|
-
}
|
|
5071
|
-
}
|
|
5072
|
-
onDisconnect(_ws, user) {
|
|
5073
|
-
const player = this.players.get(user.userId);
|
|
5074
|
-
if (player) {
|
|
5075
|
-
this.players.delete(user.userId);
|
|
5076
|
-
this.broadcast({ type: MSG.GAME_PLAYER_LEAVE, payload: { userId: user.userId } });
|
|
5077
|
-
this.onPlayerLeave(player);
|
|
5078
|
-
if (this.running && this.players.size === 0) {
|
|
5079
|
-
this.stopGame();
|
|
5080
|
-
}
|
|
5081
|
-
}
|
|
5082
|
-
}
|
|
5083
|
-
async onAlarm() {
|
|
5084
|
-
if (!this.running) return;
|
|
5085
|
-
const inputs = this.inputBuffer.splice(0);
|
|
5086
|
-
this.currentTick++;
|
|
5087
|
-
const newState = await this.onTick(this.gameState, inputs, this.currentTick);
|
|
5088
|
-
if (newState !== void 0) {
|
|
5089
|
-
this.gameState = newState;
|
|
5090
|
-
}
|
|
5091
|
-
if (this.currentTick % 10 === 0) {
|
|
5092
|
-
this.persistState();
|
|
5093
|
-
}
|
|
5094
|
-
this.broadcast({
|
|
5095
|
-
type: MSG.GAME_TICK,
|
|
5096
|
-
payload: {
|
|
5097
|
-
state: this.gameState,
|
|
5098
|
-
tick: this.currentTick
|
|
5099
|
-
}
|
|
5100
|
-
});
|
|
5101
|
-
if (this.running) {
|
|
5102
|
-
const intervalMs = 1e3 / this.config.tickRate;
|
|
5103
|
-
this.state.storage.setAlarm(Date.now() + intervalMs);
|
|
5104
|
-
}
|
|
5105
|
-
}
|
|
5106
|
-
// ==========================================================================
|
|
5107
|
-
// Game Control
|
|
5108
|
-
// ==========================================================================
|
|
5109
|
-
checkAutoStart() {
|
|
5110
|
-
if (this.running) return;
|
|
5111
|
-
const readyCount = Array.from(this.players.values()).filter((p) => p.ready).length;
|
|
5112
|
-
if (readyCount >= this.config.minPlayers && readyCount === this.players.size) {
|
|
5113
|
-
this.startGame();
|
|
5114
|
-
}
|
|
5115
|
-
}
|
|
5116
|
-
startGame() {
|
|
5117
|
-
this.running = true;
|
|
5118
|
-
this.currentTick = 0;
|
|
5119
|
-
this.inputBuffer = [];
|
|
5120
|
-
this.onGameStart();
|
|
5121
|
-
this.broadcast({ type: MSG.GAME_START, payload: { state: this.gameState, tick: 0 } });
|
|
5122
|
-
const intervalMs = 1e3 / this.config.tickRate;
|
|
5123
|
-
this.state.storage.setAlarm(Date.now() + intervalMs);
|
|
5124
|
-
}
|
|
5125
|
-
stopGame() {
|
|
5126
|
-
this.running = false;
|
|
5127
|
-
this.persistState();
|
|
5128
|
-
this.onGameEnd(this.gameState);
|
|
5129
|
-
this.broadcast({ type: MSG.GAME_END, payload: { state: this.gameState, tick: this.currentTick } });
|
|
5130
|
-
}
|
|
5131
|
-
// ==========================================================================
|
|
5132
|
-
// Protected Accessors (for subclasses)
|
|
5133
|
-
// ==========================================================================
|
|
5134
|
-
getGameState() {
|
|
5135
|
-
return this.gameState;
|
|
5136
|
-
}
|
|
5137
|
-
setGameState(state) {
|
|
5138
|
-
this.gameState = state;
|
|
5139
|
-
}
|
|
5140
|
-
getPlayers() {
|
|
5141
|
-
return Array.from(this.players.values());
|
|
5142
|
-
}
|
|
5143
|
-
isRunning() {
|
|
5144
|
-
return this.running;
|
|
5145
|
-
}
|
|
5146
|
-
getCurrentTick() {
|
|
5147
|
-
return this.currentTick;
|
|
5148
|
-
}
|
|
5149
|
-
/** Called when a player connects */
|
|
5150
|
-
onPlayerJoin(_player) {
|
|
5151
|
-
}
|
|
5152
|
-
/** Called when a player disconnects */
|
|
5153
|
-
onPlayerLeave(_player) {
|
|
5154
|
-
}
|
|
5155
|
-
/** Called when the game starts */
|
|
5156
|
-
onGameStart() {
|
|
5157
|
-
}
|
|
5158
|
-
/** Called when the game ends */
|
|
5159
|
-
onGameEnd(_finalState) {
|
|
5160
|
-
}
|
|
5161
|
-
/**
|
|
5162
|
-
* Called once when the DO first hydrates persisted state from storage.
|
|
5163
|
-
* Receives the parsed state blob as it was written by a previous build.
|
|
5164
|
-
* Return the state object to install as `gameState`.
|
|
5165
|
-
*
|
|
5166
|
-
* Subclasses with evolving schemas should override this hook to:
|
|
5167
|
-
* - merge new fields onto a default template,
|
|
5168
|
-
* - upgrade shapes across versioned states,
|
|
5169
|
-
* - or discard stale blobs entirely by returning a fresh object.
|
|
5170
|
-
*
|
|
5171
|
-
* The default implementation is a pass-through, preserving the legacy
|
|
5172
|
-
* "stored blob is gospel" behavior for subclasses that don't care.
|
|
5173
|
-
*
|
|
5174
|
-
* If JSON parsing of the stored blob fails this hook is NOT called — the
|
|
5175
|
-
* DO starts with an empty state and the subclass's `onGameStart` (or
|
|
5176
|
-
* first `onTick`) is responsible for initializing.
|
|
5177
|
-
*/
|
|
5178
|
-
onHydrateState(stored) {
|
|
5179
|
-
return stored;
|
|
5180
|
-
}
|
|
5181
|
-
};
|
|
5182
|
-
|
|
5183
4797
|
// src/server/rooms/canvas-room.ts
|
|
5184
|
-
import * as Y5 from "yjs";
|
|
5185
4798
|
var CANVAS_WRITE_TYPES = /* @__PURE__ */ new Set([
|
|
5186
4799
|
MSG.CANVAS_ADD,
|
|
5187
4800
|
MSG.CANVAS_MOVE,
|
|
@@ -7984,12 +7597,12 @@ var RefundError = class extends Error {
|
|
|
7984
7597
|
}
|
|
7985
7598
|
};
|
|
7986
7599
|
export {
|
|
7600
|
+
ACCOUNT_STORAGE_LIMIT_BYTES,
|
|
7987
7601
|
AI_CHATS_SCHEMA,
|
|
7988
7602
|
AI_MESSAGES_SCHEMA,
|
|
7989
7603
|
ALLOWED_BINDING_TYPES,
|
|
7990
7604
|
APP_ID_RE,
|
|
7991
7605
|
APP_NAME_RULES,
|
|
7992
|
-
APP_STORAGE_LIMIT_BYTES,
|
|
7993
7606
|
AUTO_PROVISIONABLE_TYPES,
|
|
7994
7607
|
AUTO_PROVISION_SENTINEL,
|
|
7995
7608
|
BASE_USERS_SCHEMA,
|
|
@@ -8012,7 +7625,6 @@ export {
|
|
|
8012
7625
|
DeepSpaceAgentProfileError,
|
|
8013
7626
|
GLOBAL_DO_TYPES,
|
|
8014
7627
|
GLOBAL_DO_TYPE_NAMES,
|
|
8015
|
-
GameRoom,
|
|
8016
7628
|
JobRoom,
|
|
8017
7629
|
MAX_APP_FILE_BYTES,
|
|
8018
7630
|
MAX_DEPLOY_ASSET_FILE_BYTES,
|