deepspace 0.4.5 → 0.5.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/README.md +90 -0
- package/dist/cli.js +3171 -1166
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +31 -4
- package/dist/index.js +511 -366
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +27 -22
- package/dist/server.js +117 -94
- package/dist/server.js.map +1 -1
- package/dist/testing.js.map +1 -1
- package/dist/worker.d.ts +27 -22
- package/dist/worker.js +117 -94
- package/dist/worker.js.map +1 -1
- package/features/admin-page/src/AdminPage.tsx +3 -5
- package/features/canvas/src/CanvasEditorPage.tsx +0 -2
- package/features/canvas/src/components/CanvasView.tsx +1 -1
- package/features/docs/src/LibrarySidebar.tsx +7 -1
- package/features/docs/src/use-yjs-room-with-awareness.ts +4 -0
- package/features/file-attachments/src/AttachmentsPage.tsx +7 -1
- package/features/file-attachments/src/useFilePreview.ts +7 -5
- package/features/file-manager/src/FileManagerPage.tsx +2 -3
- package/features/integration-test/feature.json +10 -3
- package/features/integration-test/src/IntegrationTestPage.tsx +15 -12
- package/features/landing/src/LandingPage.tsx +1 -1
- package/features/landing/src/sections/FeaturesGridSection.tsx +0 -92
- package/features/messaging/src/components/chat/MessageItem.tsx +5 -1
- package/features/messaging/src/hooks/useChatChannel.ts +5 -1
- package/features/presence-test/feature.json +2 -1
- package/features/sidebar/src/AppSidebar.tsx +1 -1
- package/features/tasks/src/TasksPage.tsx +1 -1
- package/features/testing/feature.json +2 -1
- package/features/testing/src/TestPage.tsx +8 -8
- package/package.json +4 -4
- package/scripts/add-feature.cjs +277 -272
- package/dist/testing.d.ts +0 -115
package/dist/server.d.ts
CHANGED
|
@@ -488,10 +488,10 @@ interface CronContext {
|
|
|
488
488
|
query(collection: string, opts?: {
|
|
489
489
|
where?: Record<string, unknown>;
|
|
490
490
|
limit?: number;
|
|
491
|
-
}): Promise<
|
|
492
|
-
create(collection: string, data: Record<string, unknown>): Promise<
|
|
493
|
-
update(collection: string, recordId: string, data: Record<string, unknown>): Promise<
|
|
494
|
-
delete(collection: string, recordId: string): Promise<
|
|
491
|
+
}): Promise<unknown[]>;
|
|
492
|
+
create(collection: string, data: Record<string, unknown>): Promise<unknown>;
|
|
493
|
+
update(collection: string, recordId: string, data: Record<string, unknown>): Promise<unknown>;
|
|
494
|
+
delete(collection: string, recordId: string): Promise<unknown>;
|
|
495
495
|
};
|
|
496
496
|
/**
|
|
497
497
|
* Call a platform integration endpoint (e.g. "openai/chat-completion",
|
|
@@ -499,7 +499,7 @@ interface CronContext {
|
|
|
499
499
|
* APP_OWNER_JWT the rest of the SDK uses for server-side billed calls.
|
|
500
500
|
*/
|
|
501
501
|
integrations: {
|
|
502
|
-
call(endpoint: string, params?: Record<string, unknown>): Promise<
|
|
502
|
+
call(endpoint: string, params?: Record<string, unknown>): Promise<unknown>;
|
|
503
503
|
};
|
|
504
504
|
/** App owner's user ID */
|
|
505
505
|
ownerUserId: string;
|
|
@@ -1390,7 +1390,7 @@ declare abstract class BaseRoom<E = Record<string, unknown>> {
|
|
|
1390
1390
|
fetch(request: Request): Promise<Response>;
|
|
1391
1391
|
private handleWebSocketUpgrade;
|
|
1392
1392
|
webSocketMessage(ws: WebSocket, message: ArrayBuffer | string): Promise<void>;
|
|
1393
|
-
webSocketClose(ws: WebSocket,
|
|
1393
|
+
webSocketClose(ws: WebSocket, _code: number, _reason: string): Promise<void>;
|
|
1394
1394
|
webSocketError(ws: WebSocket, error: unknown): Promise<void>;
|
|
1395
1395
|
alarm(): Promise<void>;
|
|
1396
1396
|
/**
|
|
@@ -1398,7 +1398,7 @@ declare abstract class BaseRoom<E = Record<string, unknown>> {
|
|
|
1398
1398
|
* Return an augmented attachment to serialize on the WebSocket,
|
|
1399
1399
|
* or void to use the default attachment.
|
|
1400
1400
|
*/
|
|
1401
|
-
protected onConnect(
|
|
1401
|
+
protected onConnect(_ws: WebSocket, _user: UserAttachment): UserAttachment | void | Promise<UserAttachment | void>;
|
|
1402
1402
|
/**
|
|
1403
1403
|
* Called for each parsed JSON message.
|
|
1404
1404
|
*/
|
|
@@ -1413,7 +1413,7 @@ declare abstract class BaseRoom<E = Record<string, unknown>> {
|
|
|
1413
1413
|
/**
|
|
1414
1414
|
* Called when a WebSocket disconnects.
|
|
1415
1415
|
*/
|
|
1416
|
-
protected onDisconnect(
|
|
1416
|
+
protected onDisconnect(_ws: WebSocket, _user: UserAttachment): void | Promise<void>;
|
|
1417
1417
|
/**
|
|
1418
1418
|
* Called for HTTP requests that are NOT WebSocket upgrades.
|
|
1419
1419
|
*/
|
|
@@ -1670,7 +1670,6 @@ declare class SchemaRegistry {
|
|
|
1670
1670
|
registerTrusted(schema: CollectionSchema): void;
|
|
1671
1671
|
get(name: string): CollectionSchema | undefined;
|
|
1672
1672
|
has(name: string): boolean;
|
|
1673
|
-
hasTrusted(name: string): boolean;
|
|
1674
1673
|
all(): CollectionSchema[];
|
|
1675
1674
|
names(): string[];
|
|
1676
1675
|
}
|
|
@@ -1742,6 +1741,12 @@ declare class RecordRoom<E = Record<string, unknown>> extends BaseRoom<E> {
|
|
|
1742
1741
|
private ownerUserId;
|
|
1743
1742
|
/** True until the first fetch() completes — detects hibernation wake-up */
|
|
1744
1743
|
private freshConstruct;
|
|
1744
|
+
/**
|
|
1745
|
+
* Per-connection `[DO Perf]` timing logs are noisy on every hot path, so
|
|
1746
|
+
* they're gated behind a `DEEPSPACE_DO_PERF` env binding (set it to any
|
|
1747
|
+
* truthy value on the worker to opt in). Off by default.
|
|
1748
|
+
*/
|
|
1749
|
+
private get perfLogEnabled();
|
|
1745
1750
|
constructor(state: DurableObjectState, env: unknown, schemas?: CollectionSchema[], config?: RecordRoomConfig);
|
|
1746
1751
|
private getPermissionContext;
|
|
1747
1752
|
fetch(request: Request): Promise<Response>;
|
|
@@ -1754,12 +1759,13 @@ declare class RecordRoom<E = Record<string, unknown>> extends BaseRoom<E> {
|
|
|
1754
1759
|
private ensureCollectionTable;
|
|
1755
1760
|
private ensureAllCollectionTables;
|
|
1756
1761
|
protected onConnect(ws: WebSocket, user: UserAttachment): Promise<ConnectionAttachment>;
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1762
|
+
/**
|
|
1763
|
+
* Required to satisfy BaseRoom's abstract contract, but never invoked:
|
|
1764
|
+
* RecordRoom overrides `webSocketMessage`/`webSocketClose` directly (below),
|
|
1765
|
+
* so the BaseRoom dispatch path that would call this never runs. All real
|
|
1766
|
+
* message handling lives in `handleRecordMessage`.
|
|
1767
|
+
*/
|
|
1768
|
+
protected onMessage(): void;
|
|
1763
1769
|
webSocketMessage(ws: WebSocket, message: ArrayBuffer | string): Promise<void>;
|
|
1764
1770
|
webSocketClose(ws: WebSocket, code: number, reason: string): Promise<void>;
|
|
1765
1771
|
webSocketError(ws: WebSocket, error: unknown): Promise<void>;
|
|
@@ -1803,12 +1809,12 @@ declare class YjsRoom<E = Record<string, unknown>> extends BaseRoom<E> {
|
|
|
1803
1809
|
private getDoc;
|
|
1804
1810
|
private persistDoc;
|
|
1805
1811
|
protected onConnect(ws: WebSocket, user: UserAttachment): YjsAttachment;
|
|
1806
|
-
protected onMessage(
|
|
1812
|
+
protected onMessage(_ws: WebSocket, _user: UserAttachment, _message: {
|
|
1807
1813
|
type: string;
|
|
1808
1814
|
[key: string]: unknown;
|
|
1809
1815
|
}): void;
|
|
1810
1816
|
protected onBinaryMessage(ws: WebSocket, user: UserAttachment, data: ArrayBuffer): void;
|
|
1811
|
-
protected onDisconnect(ws: WebSocket,
|
|
1817
|
+
protected onDisconnect(ws: WebSocket, _user: UserAttachment): void;
|
|
1812
1818
|
private handleSync;
|
|
1813
1819
|
private handleAwareness;
|
|
1814
1820
|
private readAwarenessUpdates;
|
|
@@ -1890,13 +1896,13 @@ declare abstract class GameRoom<E = Record<string, unknown>> extends BaseRoom<E>
|
|
|
1890
1896
|
*/
|
|
1891
1897
|
protected abstract onTick(state: Record<string, unknown>, inputs: GameInput[], tick: number): Record<string, unknown> | undefined | Promise<Record<string, unknown> | undefined>;
|
|
1892
1898
|
/** Called when a player connects */
|
|
1893
|
-
protected onPlayerJoin(
|
|
1899
|
+
protected onPlayerJoin(_player: Player): void;
|
|
1894
1900
|
/** Called when a player disconnects */
|
|
1895
|
-
protected onPlayerLeave(
|
|
1901
|
+
protected onPlayerLeave(_player: Player): void;
|
|
1896
1902
|
/** Called when the game starts */
|
|
1897
1903
|
protected onGameStart(): void;
|
|
1898
1904
|
/** Called when the game ends */
|
|
1899
|
-
protected onGameEnd(
|
|
1905
|
+
protected onGameEnd(_finalState: Record<string, unknown>): void;
|
|
1900
1906
|
/**
|
|
1901
1907
|
* Called once when the DO first hydrates persisted state from storage.
|
|
1902
1908
|
* Receives the parsed state blob as it was written by a previous build.
|
|
@@ -3005,8 +3011,7 @@ interface VerifiedAuth {
|
|
|
3005
3011
|
userId: string;
|
|
3006
3012
|
claims: JwtClaims;
|
|
3007
3013
|
}
|
|
3008
|
-
|
|
3009
|
-
}
|
|
3014
|
+
type VerifyResult = VerifiedAuth;
|
|
3010
3015
|
interface TokenDebugInfo {
|
|
3011
3016
|
iss?: string | null;
|
|
3012
3017
|
aud?: string | string[] | null;
|
package/dist/server.js
CHANGED
|
@@ -475,7 +475,8 @@ function buildCronContext(env, ownerUserId, roomId = "default") {
|
|
|
475
475
|
if (opts?.where) params.where = opts.where;
|
|
476
476
|
if (opts?.limit) params.limit = opts.limit;
|
|
477
477
|
const data = await executeTool3("records.query", params);
|
|
478
|
-
|
|
478
|
+
const records2 = data?.records;
|
|
479
|
+
return records2 ?? [];
|
|
479
480
|
},
|
|
480
481
|
async create(collection, data) {
|
|
481
482
|
return executeTool3("records.create", { collection, data });
|
|
@@ -632,7 +633,8 @@ async function composioTools(env, options) {
|
|
|
632
633
|
if (!list.ok || !list.success) {
|
|
633
634
|
throw new Error(`composioTools: failed to list Composio tools: ${list.detail}`);
|
|
634
635
|
}
|
|
635
|
-
const
|
|
636
|
+
const listItems = list.data?.items;
|
|
637
|
+
const items = Array.isArray(listItems) ? listItems : [];
|
|
636
638
|
const tools = {};
|
|
637
639
|
for (const item of items) {
|
|
638
640
|
if (!item?.slug) continue;
|
|
@@ -1333,7 +1335,7 @@ var BaseRoom = class {
|
|
|
1333
1335
|
console.error(`[${this.constructor.name}] Message error:`, e);
|
|
1334
1336
|
}
|
|
1335
1337
|
}
|
|
1336
|
-
async webSocketClose(ws,
|
|
1338
|
+
async webSocketClose(ws, _code, _reason) {
|
|
1337
1339
|
const attachment = ws.deserializeAttachment();
|
|
1338
1340
|
if (attachment) {
|
|
1339
1341
|
try {
|
|
@@ -1358,12 +1360,12 @@ var BaseRoom = class {
|
|
|
1358
1360
|
* Return an augmented attachment to serialize on the WebSocket,
|
|
1359
1361
|
* or void to use the default attachment.
|
|
1360
1362
|
*/
|
|
1361
|
-
onConnect(
|
|
1363
|
+
onConnect(_ws, _user) {
|
|
1362
1364
|
}
|
|
1363
1365
|
/**
|
|
1364
1366
|
* Called when a WebSocket disconnects.
|
|
1365
1367
|
*/
|
|
1366
|
-
onDisconnect(
|
|
1368
|
+
onDisconnect(_ws, _user) {
|
|
1367
1369
|
}
|
|
1368
1370
|
// ==========================================================================
|
|
1369
1371
|
// Utilities
|
|
@@ -1611,7 +1613,7 @@ function coerceValue(value, storage, interpretation) {
|
|
|
1611
1613
|
if (m) return parseFloat(m[1]) / 100;
|
|
1612
1614
|
}
|
|
1613
1615
|
if (interpretation.kind === "currency") {
|
|
1614
|
-
const cleaned2 = s.replace(/[^0-9
|
|
1616
|
+
const cleaned2 = s.replace(/[^0-9.-]/g, "");
|
|
1615
1617
|
const n2 = parseFloat(cleaned2);
|
|
1616
1618
|
if (!isNaN(n2)) return n2;
|
|
1617
1619
|
return s;
|
|
@@ -1801,9 +1803,6 @@ var SchemaRegistry = class {
|
|
|
1801
1803
|
has(name) {
|
|
1802
1804
|
return this.trusted.has(name);
|
|
1803
1805
|
}
|
|
1804
|
-
hasTrusted(name) {
|
|
1805
|
-
return this.trusted.has(name);
|
|
1806
|
-
}
|
|
1807
1806
|
all() {
|
|
1808
1807
|
return Array.from(this.trusted.values());
|
|
1809
1808
|
}
|
|
@@ -2118,7 +2117,7 @@ function putRecord(ctx, collection, recordId, data, userId, userRole, skipUserRb
|
|
|
2118
2117
|
const columns = getResolvedColumns(schema);
|
|
2119
2118
|
const existing = getRecord(ctx.sql, collection, recordId, schema);
|
|
2120
2119
|
const isUpdate = existing !== null;
|
|
2121
|
-
|
|
2120
|
+
const mergedData = isUpdate ? { ...existing.data, ...data } : { ...data };
|
|
2122
2121
|
for (const col of columns) {
|
|
2123
2122
|
if (col.readonly) continue;
|
|
2124
2123
|
if (!isUpdate) {
|
|
@@ -2173,7 +2172,10 @@ function putRecord(ctx, collection, recordId, data, userId, userRole, skipUserRb
|
|
|
2173
2172
|
const permCtx = ctx.getPermissionContext();
|
|
2174
2173
|
if (isUpdate) {
|
|
2175
2174
|
if (!canUpdate(schema, userRole, { ...existing, recordId }, userId, permCtx)) {
|
|
2176
|
-
return {
|
|
2175
|
+
return {
|
|
2176
|
+
success: false,
|
|
2177
|
+
error: `UPDATE DENIED: role=${userRole}, collection=${collection}`
|
|
2178
|
+
};
|
|
2177
2179
|
}
|
|
2178
2180
|
const fieldError = checkFieldPermissions(schema, userRole, finalData, existing.data);
|
|
2179
2181
|
if (fieldError) {
|
|
@@ -2181,7 +2183,10 @@ function putRecord(ctx, collection, recordId, data, userId, userRole, skipUserRb
|
|
|
2181
2183
|
}
|
|
2182
2184
|
} else {
|
|
2183
2185
|
if (!canCreate(schema, userRole)) {
|
|
2184
|
-
return {
|
|
2186
|
+
return {
|
|
2187
|
+
success: false,
|
|
2188
|
+
error: `CREATE DENIED: role=${userRole}, collection=${collection}`
|
|
2189
|
+
};
|
|
2185
2190
|
}
|
|
2186
2191
|
}
|
|
2187
2192
|
}
|
|
@@ -2203,7 +2208,10 @@ function putRecord(ctx, collection, recordId, data, userId, userRole, skipUserRb
|
|
|
2203
2208
|
);
|
|
2204
2209
|
if (existing2.toArray().length > 0) {
|
|
2205
2210
|
const fields = schema.uniqueOn.map((f) => `${f}=${finalData[f] ?? "null"}`).join(", ");
|
|
2206
|
-
return {
|
|
2211
|
+
return {
|
|
2212
|
+
success: false,
|
|
2213
|
+
error: `Duplicate: a record with ${fields} already exists in ${collection}`
|
|
2214
|
+
};
|
|
2207
2215
|
}
|
|
2208
2216
|
}
|
|
2209
2217
|
if (isUpdate) {
|
|
@@ -2217,13 +2225,16 @@ function putRecord(ctx, collection, recordId, data, userId, userRole, skipUserRb
|
|
|
2217
2225
|
ctx.sql.exec(`UPDATE "${tbl}" SET ${setClauses.join(", ")} WHERE _row_id = ?`, ...params);
|
|
2218
2226
|
} else {
|
|
2219
2227
|
const colIds = Object.keys(colValues);
|
|
2220
|
-
const allCols = [
|
|
2228
|
+
const allCols = [
|
|
2229
|
+
"_row_id",
|
|
2230
|
+
"_created_by",
|
|
2231
|
+
"_created_at",
|
|
2232
|
+
"_updated_at",
|
|
2233
|
+
...colIds.map((c) => `"${c}"`)
|
|
2234
|
+
];
|
|
2221
2235
|
const placeholders = allCols.map(() => "?").join(", ");
|
|
2222
2236
|
const params = [recordId, userId, now, now, ...colIds.map((c) => colValues[c])];
|
|
2223
|
-
ctx.sql.exec(
|
|
2224
|
-
`INSERT INTO "${tbl}" (${allCols.join(", ")}) VALUES (${placeholders})`,
|
|
2225
|
-
...params
|
|
2226
|
-
);
|
|
2237
|
+
ctx.sql.exec(`INSERT INTO "${tbl}" (${allCols.join(", ")}) VALUES (${placeholders})`, ...params);
|
|
2227
2238
|
}
|
|
2228
2239
|
const saved = getRecord(ctx.sql, collection, recordId, schema);
|
|
2229
2240
|
const record = {
|
|
@@ -3456,9 +3467,21 @@ var RecordRoom = class extends BaseRoom {
|
|
|
3456
3467
|
ownerUserId;
|
|
3457
3468
|
/** True until the first fetch() completes — detects hibernation wake-up */
|
|
3458
3469
|
freshConstruct = true;
|
|
3470
|
+
/**
|
|
3471
|
+
* Per-connection `[DO Perf]` timing logs are noisy on every hot path, so
|
|
3472
|
+
* they're gated behind a `DEEPSPACE_DO_PERF` env binding (set it to any
|
|
3473
|
+
* truthy value on the worker to opt in). Off by default.
|
|
3474
|
+
*/
|
|
3475
|
+
get perfLogEnabled() {
|
|
3476
|
+
return !!this.env?.DEEPSPACE_DO_PERF;
|
|
3477
|
+
}
|
|
3459
3478
|
constructor(state, env, schemas = [], config = {}) {
|
|
3460
3479
|
super(state, env);
|
|
3461
|
-
this.schemaRegistry = new SchemaRegistry([
|
|
3480
|
+
this.schemaRegistry = new SchemaRegistry([
|
|
3481
|
+
...SYSTEM_COLLECTION_SCHEMAS,
|
|
3482
|
+
BASE_USERS_SCHEMA,
|
|
3483
|
+
...schemas
|
|
3484
|
+
]);
|
|
3462
3485
|
this.ownerUserId = config.ownerUserId ?? null;
|
|
3463
3486
|
const wsCount = this.state.getWebSockets().length;
|
|
3464
3487
|
if (wsCount > 0) {
|
|
@@ -3542,10 +3565,7 @@ var RecordRoom = class extends BaseRoom {
|
|
|
3542
3565
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3543
3566
|
for (const row of oldUsers) {
|
|
3544
3567
|
const r = row;
|
|
3545
|
-
const existing = this.sql.exec(
|
|
3546
|
-
`SELECT 1 FROM c_users WHERE _row_id = ?`,
|
|
3547
|
-
r.user_id
|
|
3548
|
-
).toArray();
|
|
3568
|
+
const existing = this.sql.exec(`SELECT 1 FROM c_users WHERE _row_id = ?`, r.user_id).toArray();
|
|
3549
3569
|
if (existing.length > 0) continue;
|
|
3550
3570
|
this.sql.exec(
|
|
3551
3571
|
`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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
@@ -3579,7 +3599,6 @@ var RecordRoom = class extends BaseRoom {
|
|
|
3579
3599
|
}
|
|
3580
3600
|
let migrated = 0;
|
|
3581
3601
|
let skipped = 0;
|
|
3582
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3583
3602
|
for (const row of rows) {
|
|
3584
3603
|
const r = row;
|
|
3585
3604
|
const schema = this.schemaRegistry.get(r.collection);
|
|
@@ -3588,17 +3607,11 @@ var RecordRoom = class extends BaseRoom {
|
|
|
3588
3607
|
continue;
|
|
3589
3608
|
}
|
|
3590
3609
|
const tbl = collectionTableName(r.collection);
|
|
3591
|
-
const tblExists = this.sql.exec(
|
|
3592
|
-
`SELECT name FROM sqlite_master WHERE type='table' AND name=?`,
|
|
3593
|
-
tbl
|
|
3594
|
-
).toArray().length > 0;
|
|
3610
|
+
const tblExists = this.sql.exec(`SELECT name FROM sqlite_master WHERE type='table' AND name=?`, tbl).toArray().length > 0;
|
|
3595
3611
|
if (!tblExists) {
|
|
3596
3612
|
this.ensureCollectionTable(schema);
|
|
3597
3613
|
}
|
|
3598
|
-
const existing = this.sql.exec(
|
|
3599
|
-
`SELECT 1 FROM "${tbl}" WHERE _row_id = ?`,
|
|
3600
|
-
r.record_id
|
|
3601
|
-
).toArray();
|
|
3614
|
+
const existing = this.sql.exec(`SELECT 1 FROM "${tbl}" WHERE _row_id = ?`, r.record_id).toArray();
|
|
3602
3615
|
if (existing.length > 0) {
|
|
3603
3616
|
skipped++;
|
|
3604
3617
|
continue;
|
|
@@ -3613,9 +3626,21 @@ var RecordRoom = class extends BaseRoom {
|
|
|
3613
3626
|
const columns = schema.columns.map(resolveColumn);
|
|
3614
3627
|
const colValues = dataToColumnValues(data, columns);
|
|
3615
3628
|
const colIds = Object.keys(colValues);
|
|
3616
|
-
const allCols = [
|
|
3629
|
+
const allCols = [
|
|
3630
|
+
"_row_id",
|
|
3631
|
+
"_created_by",
|
|
3632
|
+
"_created_at",
|
|
3633
|
+
"_updated_at",
|
|
3634
|
+
...colIds.map((c) => `"${c}"`)
|
|
3635
|
+
];
|
|
3617
3636
|
const placeholders = allCols.map(() => "?").join(", ");
|
|
3618
|
-
const params = [
|
|
3637
|
+
const params = [
|
|
3638
|
+
r.record_id,
|
|
3639
|
+
r.created_by,
|
|
3640
|
+
r.created_at,
|
|
3641
|
+
r.updated_at,
|
|
3642
|
+
...colIds.map((c) => colValues[c])
|
|
3643
|
+
];
|
|
3619
3644
|
this.sql.exec(
|
|
3620
3645
|
`INSERT INTO "${tbl}" (${allCols.join(", ")}) VALUES (${placeholders})`,
|
|
3621
3646
|
...params
|
|
@@ -3623,7 +3648,9 @@ var RecordRoom = class extends BaseRoom {
|
|
|
3623
3648
|
migrated++;
|
|
3624
3649
|
}
|
|
3625
3650
|
if (migrated > 0 || skipped > 0) {
|
|
3626
|
-
console.log(
|
|
3651
|
+
console.log(
|
|
3652
|
+
`[RecordRoom] Migrated ${migrated} records from document-mode, ${skipped} skipped`
|
|
3653
|
+
);
|
|
3627
3654
|
}
|
|
3628
3655
|
this.sql.exec(`DROP TABLE IF EXISTS records`);
|
|
3629
3656
|
} catch (error) {
|
|
@@ -3646,7 +3673,12 @@ var RecordRoom = class extends BaseRoom {
|
|
|
3646
3673
|
// ============================================================================
|
|
3647
3674
|
async onConnect(ws, user) {
|
|
3648
3675
|
await this.ensureInitialized();
|
|
3649
|
-
const timing = this._fetchTiming ?? {
|
|
3676
|
+
const timing = this._fetchTiming ?? {
|
|
3677
|
+
fetchStart: Date.now(),
|
|
3678
|
+
isColdStart: false,
|
|
3679
|
+
needsInit: false,
|
|
3680
|
+
initMs: 0
|
|
3681
|
+
};
|
|
3650
3682
|
const userId = user.userId;
|
|
3651
3683
|
const userName = user.userName;
|
|
3652
3684
|
const userEmail = user.userEmail;
|
|
@@ -3657,7 +3689,16 @@ var RecordRoom = class extends BaseRoom {
|
|
|
3657
3689
|
const isOwner2 = this.ownerUserId != null && userId === this.ownerUserId;
|
|
3658
3690
|
const regStart = Date.now();
|
|
3659
3691
|
const defaultRole = this.schemaRegistry.get("users")?.defaultRole ?? "member";
|
|
3660
|
-
const registeredUser = await registerUser(
|
|
3692
|
+
const registeredUser = await registerUser(
|
|
3693
|
+
this.sql,
|
|
3694
|
+
userId,
|
|
3695
|
+
userName,
|
|
3696
|
+
userEmail,
|
|
3697
|
+
userImageUrl,
|
|
3698
|
+
isOwner2,
|
|
3699
|
+
defaultRole,
|
|
3700
|
+
this.schemaRegistry
|
|
3701
|
+
);
|
|
3661
3702
|
const regMs = Date.now() - regStart;
|
|
3662
3703
|
attachment = {
|
|
3663
3704
|
userId: registeredUser.id,
|
|
@@ -3671,7 +3712,10 @@ var RecordRoom = class extends BaseRoom {
|
|
|
3671
3712
|
};
|
|
3672
3713
|
this.send(ws, { type: MSG.USER_INFO, payload: registeredUser });
|
|
3673
3714
|
const totalMs = Date.now() - timing.fetchStart;
|
|
3674
|
-
|
|
3715
|
+
if (this.perfLogEnabled)
|
|
3716
|
+
console.log(
|
|
3717
|
+
`[DO Perf] cold=${timing.isColdStart} | init: ${timing.initMs}ms reg: ${regMs}ms | total: ${totalMs}ms`
|
|
3718
|
+
);
|
|
3675
3719
|
} else {
|
|
3676
3720
|
attachment = {
|
|
3677
3721
|
userId,
|
|
@@ -3682,63 +3726,37 @@ var RecordRoom = class extends BaseRoom {
|
|
|
3682
3726
|
yjsSubscriptions: [],
|
|
3683
3727
|
yjsClientId: this.nextYjsClientId++
|
|
3684
3728
|
};
|
|
3685
|
-
this.send(ws, {
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3729
|
+
this.send(ws, {
|
|
3730
|
+
type: MSG.USER_INFO,
|
|
3731
|
+
payload: {
|
|
3732
|
+
id: userId,
|
|
3733
|
+
name: "Anonymous",
|
|
3734
|
+
email: "",
|
|
3735
|
+
role: ROLE_ANONYMOUS,
|
|
3736
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3737
|
+
lastSeenAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3738
|
+
}
|
|
3739
|
+
});
|
|
3693
3740
|
const totalMs = Date.now() - timing.fetchStart;
|
|
3694
|
-
|
|
3741
|
+
if (this.perfLogEnabled)
|
|
3742
|
+
console.log(`[DO Perf] cold=${timing.isColdStart} anon | total: ${totalMs}ms`);
|
|
3695
3743
|
}
|
|
3696
3744
|
this._fetchTiming = null;
|
|
3697
3745
|
return attachment;
|
|
3698
3746
|
}
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
await this.handleRecordMessage(ws, attachment, msg);
|
|
3708
|
-
} catch (e) {
|
|
3709
|
-
const errMsg = e instanceof Error ? e.message : String(e);
|
|
3710
|
-
console.error(`[RecordRoom] Message handler error: ${errMsg}`);
|
|
3711
|
-
this.send(ws, { type: MSG.ERROR, payload: { error: `Invalid message: ${errMsg}` } });
|
|
3712
|
-
}
|
|
3713
|
-
}
|
|
3714
|
-
async onBinaryMessage(ws, user, data) {
|
|
3715
|
-
await this.ensureInitialized();
|
|
3716
|
-
const attachment = ws.deserializeAttachment();
|
|
3717
|
-
if (!attachment) return;
|
|
3718
|
-
try {
|
|
3719
|
-
await handleYjsBinaryMessage(
|
|
3720
|
-
this.createYjsContext(),
|
|
3721
|
-
ws,
|
|
3722
|
-
attachment,
|
|
3723
|
-
new Uint8Array(data)
|
|
3724
|
-
);
|
|
3725
|
-
} catch (e) {
|
|
3726
|
-
console.error("[RecordRoom] Yjs binary message error:", e);
|
|
3727
|
-
}
|
|
3728
|
-
}
|
|
3729
|
-
onDisconnect(ws, user) {
|
|
3730
|
-
console.log(`[RecordRoom] webSocketClose`);
|
|
3731
|
-
try {
|
|
3732
|
-
const attachment = ws.deserializeAttachment();
|
|
3733
|
-
if (attachment) {
|
|
3734
|
-
broadcastAwarenessRemoval(this.createYjsContext(), attachment);
|
|
3735
|
-
}
|
|
3736
|
-
} catch {
|
|
3737
|
-
}
|
|
3747
|
+
/**
|
|
3748
|
+
* Required to satisfy BaseRoom's abstract contract, but never invoked:
|
|
3749
|
+
* RecordRoom overrides `webSocketMessage`/`webSocketClose` directly (below),
|
|
3750
|
+
* so the BaseRoom dispatch path that would call this never runs. All real
|
|
3751
|
+
* message handling lives in `handleRecordMessage`.
|
|
3752
|
+
*/
|
|
3753
|
+
onMessage() {
|
|
3754
|
+
throw new Error("RecordRoom.onMessage is unreachable; use webSocketMessage");
|
|
3738
3755
|
}
|
|
3739
3756
|
// ============================================================================
|
|
3740
|
-
// Hibernation API
|
|
3741
|
-
//
|
|
3757
|
+
// Hibernation API — RecordRoom overrides these directly (rather than using
|
|
3758
|
+
// BaseRoom's onMessage/onBinaryMessage/onDisconnect hooks) so it can run
|
|
3759
|
+
// ensureInitialized() before every message after a hibernation wake-up.
|
|
3742
3760
|
// ============================================================================
|
|
3743
3761
|
async webSocketMessage(ws, message) {
|
|
3744
3762
|
await this.ensureInitialized();
|
|
@@ -3809,7 +3827,12 @@ var RecordRoom = class extends BaseRoom {
|
|
|
3809
3827
|
handleUserList(userCtx, ws, attachment);
|
|
3810
3828
|
break;
|
|
3811
3829
|
case MSG.USER_UPDATE:
|
|
3812
|
-
handleUserUpdate(
|
|
3830
|
+
handleUserUpdate(
|
|
3831
|
+
recordCtx,
|
|
3832
|
+
ws,
|
|
3833
|
+
attachment,
|
|
3834
|
+
payload
|
|
3835
|
+
);
|
|
3813
3836
|
break;
|
|
3814
3837
|
case MSG.SET_ROLE:
|
|
3815
3838
|
await handleSetRole(userCtx, ws, attachment, payload);
|
|
@@ -3975,7 +3998,7 @@ var YjsRoom = class extends BaseRoom {
|
|
|
3975
3998
|
this.sendAwarenessSnapshot(ws);
|
|
3976
3999
|
return attachment;
|
|
3977
4000
|
}
|
|
3978
|
-
onMessage(
|
|
4001
|
+
onMessage(_ws, _user, _message) {
|
|
3979
4002
|
}
|
|
3980
4003
|
onBinaryMessage(ws, user, data) {
|
|
3981
4004
|
const bytes = new Uint8Array(data);
|
|
@@ -3987,7 +4010,7 @@ var YjsRoom = class extends BaseRoom {
|
|
|
3987
4010
|
this.handleAwareness(ws, decoder, bytes);
|
|
3988
4011
|
}
|
|
3989
4012
|
}
|
|
3990
|
-
onDisconnect(ws,
|
|
4013
|
+
onDisconnect(ws, _user) {
|
|
3991
4014
|
const attachment = ws.deserializeAttachment();
|
|
3992
4015
|
if (attachment?.awarenessClientId == null) return;
|
|
3993
4016
|
const clientId = attachment.awarenessClientId;
|
|
@@ -4348,16 +4371,16 @@ var GameRoom = class extends BaseRoom {
|
|
|
4348
4371
|
return this.currentTick;
|
|
4349
4372
|
}
|
|
4350
4373
|
/** Called when a player connects */
|
|
4351
|
-
onPlayerJoin(
|
|
4374
|
+
onPlayerJoin(_player) {
|
|
4352
4375
|
}
|
|
4353
4376
|
/** Called when a player disconnects */
|
|
4354
|
-
onPlayerLeave(
|
|
4377
|
+
onPlayerLeave(_player) {
|
|
4355
4378
|
}
|
|
4356
4379
|
/** Called when the game starts */
|
|
4357
4380
|
onGameStart() {
|
|
4358
4381
|
}
|
|
4359
4382
|
/** Called when the game ends */
|
|
4360
|
-
onGameEnd(
|
|
4383
|
+
onGameEnd(_finalState) {
|
|
4361
4384
|
}
|
|
4362
4385
|
/**
|
|
4363
4386
|
* Called once when the DO first hydrates persisted state from storage.
|