@jgengine/convex 0.17.0 → 0.18.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/server.d.ts CHANGED
@@ -1,13 +1,14 @@
1
- import type { Auth, DataModelFromSchemaDefinition, DocumentByName, GenericMutationCtx } from "convex/server";
1
+ import type { Auth, FunctionReference, DataModelFromSchemaDefinition, DocumentByName, GenericMutationCtx, GenericQueryCtx } from "convex/server";
2
2
  import type { GenericId } from "convex/values";
3
- import type { ChatMessage } from "@jgengine/core/game/chat";
3
+ import { type ChatMessage } from "@jgengine/core/game/chat";
4
4
  import type { ChatSendOutcome } from "@jgengine/core/multiplayer/chatContract";
5
5
  import { type PoseSyncRules } from "@jgengine/core/multiplayer/presenceModel";
6
+ import type { CommandScope } from "@jgengine/core/runtime/commandRunner";
6
7
  import type { GameRuntime } from "@jgengine/core/runtime/gameRuntime";
7
8
  import type { LeaderboardIncrement } from "@jgengine/core/runtime/hostPersistence";
8
9
  import { type MatchmakingMode } from "@jgengine/core/runtime/hostPolicy";
9
10
  import type { SaveConfig } from "@jgengine/core/runtime/save";
10
- import type { GameRuntimeSnapshot } from "@jgengine/core/runtime/snapshot";
11
+ import type { GameRuntimeSnapshot, RuntimePlayerRow } from "@jgengine/core/runtime/snapshot";
11
12
  /** @internal Re-export shared host-policy pure helpers so existing convex imports keep working. */
12
13
  export { JG_MAX_MEMBERS_PER_SERVER, canJoinPrivateServer, isListablePublicly, validateSlotsPerServer, type MatchmakingMode, } from "@jgengine/core/runtime/hostPolicy";
13
14
  /** @internal */
@@ -18,6 +19,7 @@ export declare function jgengineTables(): {
18
19
  gameId: string;
19
20
  joinCode?: string | undefined;
20
21
  lastSavedAt?: number | undefined;
22
+ memberCount?: number | undefined;
21
23
  memberUserIds: string[];
22
24
  mode?: string | undefined;
23
25
  modeConfig?: any;
@@ -31,6 +33,7 @@ export declare function jgengineTables(): {
31
33
  slotsPerServer: number;
32
34
  status: "closed" | "open" | "running";
33
35
  tickAnchorMs: number;
36
+ topology?: "rooms" | "shared" | undefined;
34
37
  updatedAt: number;
35
38
  visibility?: "private" | "public" | undefined;
36
39
  }, {
@@ -40,6 +43,8 @@ export declare function jgengineTables(): {
40
43
  modeConfig: import("convex/values").VAny<any, "optional", string>;
41
44
  visibility: import("convex/values").VUnion<"private" | "public" | undefined, [import("convex/values").VLiteral<"public", "required">, import("convex/values").VLiteral<"private", "required">], "optional", never>;
42
45
  joinCode: import("convex/values").VString<string | undefined, "optional">;
46
+ topology: import("convex/values").VUnion<"rooms" | "shared" | undefined, [import("convex/values").VLiteral<"shared", "required">, import("convex/values").VLiteral<"rooms", "required">], "optional", never>;
47
+ memberCount: import("convex/values").VFloat64<number | undefined, "optional">;
43
48
  memberUserIds: import("convex/values").VArray<string[], import("convex/values").VString<string, "required">, "required">;
44
49
  slotsPerServer: import("convex/values").VFloat64<number, "required">;
45
50
  save: import("convex/values").VUnion<"none" | {
@@ -60,29 +65,89 @@ export declare function jgengineTables(): {
60
65
  dirtyAt: import("convex/values").VFloat64<number | undefined, "optional">;
61
66
  createdAt: import("convex/values").VFloat64<number, "required">;
62
67
  updatedAt: import("convex/values").VFloat64<number, "required">;
63
- }, "required", "createdAt" | "dirtyAt" | "gameId" | "joinCode" | "lastSavedAt" | "memberUserIds" | "mode" | "modeConfig" | "revision" | "save" | "save.auto" | "save.scope" | "serverState" | "sessionPlayers" | "slotsPerServer" | "status" | "tickAnchorMs" | "updatedAt" | "visibility" | `modeConfig.${string}` | `serverState.${string}` | `sessionPlayers.${string}`>, {
68
+ }, "required", "createdAt" | "dirtyAt" | "gameId" | "joinCode" | "lastSavedAt" | "memberCount" | "memberUserIds" | "mode" | "modeConfig" | "revision" | "save" | "save.auto" | "save.scope" | "serverState" | "sessionPlayers" | "slotsPerServer" | "status" | "tickAnchorMs" | "topology" | "updatedAt" | "visibility" | `modeConfig.${string}` | `serverState.${string}` | `sessionPlayers.${string}`>, {
64
69
  by_dirty: ["dirtyAt", "_creationTime"];
65
70
  by_game_and_status: ["gameId", "status", "_creationTime"];
66
71
  by_game_status_tick: ["gameId", "status", "tickAnchorMs", "_creationTime"];
67
72
  by_status: ["status", "_creationTime"];
68
73
  }, {}, {}>;
74
+ jgServerCapacity: import("convex/server").TableDefinition<import("convex/values").VObject<{
75
+ gameId: string;
76
+ memberCount: number;
77
+ mode?: string | undefined;
78
+ serverId: GenericId<"jgGameServers">;
79
+ slotsPerServer: number;
80
+ status: "closed" | "open" | "running";
81
+ }, {
82
+ serverId: import("convex/values").VId<GenericId<"jgGameServers">, "required">;
83
+ gameId: import("convex/values").VString<string, "required">;
84
+ status: import("convex/values").VUnion<"closed" | "open" | "running", [import("convex/values").VLiteral<"open", "required">, import("convex/values").VLiteral<"running", "required">, import("convex/values").VLiteral<"closed", "required">], "required", never>;
85
+ mode: import("convex/values").VString<string | undefined, "optional">;
86
+ memberCount: import("convex/values").VFloat64<number, "required">;
87
+ slotsPerServer: import("convex/values").VFloat64<number, "required">;
88
+ }, "required", "gameId" | "memberCount" | "mode" | "serverId" | "slotsPerServer" | "status">, {
89
+ by_server: ["serverId", "_creationTime"];
90
+ }, {}, {}>;
91
+ jgServerMembers: import("convex/server").TableDefinition<import("convex/values").VObject<{
92
+ joinedAt: number;
93
+ serverId: GenericId<"jgGameServers">;
94
+ userId: string;
95
+ }, {
96
+ serverId: import("convex/values").VId<GenericId<"jgGameServers">, "required">;
97
+ userId: import("convex/values").VString<string, "required">;
98
+ joinedAt: import("convex/values").VFloat64<number, "required">;
99
+ }, "required", "joinedAt" | "serverId" | "userId">, {
100
+ by_server: ["serverId", "_creationTime"];
101
+ by_server_and_user: ["serverId", "userId", "_creationTime"];
102
+ }, {}, {}>;
103
+ jgRateLimits: import("convex/server").TableDefinition<import("convex/values").VObject<{
104
+ count: number;
105
+ expiresAt: number;
106
+ key: string;
107
+ startedAt: number;
108
+ }, {
109
+ key: import("convex/values").VString<string, "required">;
110
+ startedAt: import("convex/values").VFloat64<number, "required">;
111
+ count: import("convex/values").VFloat64<number, "required">;
112
+ expiresAt: import("convex/values").VFloat64<number, "required">;
113
+ }, "required", "count" | "expiresAt" | "key" | "startedAt">, {
114
+ by_expires: ["expiresAt", "_creationTime"];
115
+ by_key: ["key", "_creationTime"];
116
+ }, {}, {}>;
117
+ jgClientErrors: import("convex/server").TableDefinition<import("convex/values").VObject<{
118
+ createdAt: number;
119
+ details?: any;
120
+ message: string;
121
+ userId: string;
122
+ }, {
123
+ userId: import("convex/values").VString<string, "required">;
124
+ message: import("convex/values").VString<string, "required">;
125
+ details: import("convex/values").VAny<any, "optional", string>;
126
+ createdAt: import("convex/values").VFloat64<number, "required">;
127
+ }, "required", "createdAt" | "details" | "message" | "userId" | `details.${string}`>, {
128
+ by_created: ["createdAt", "_creationTime"];
129
+ }, {}, {}>;
69
130
  jgPlayerProfiles: import("convex/server").TableDefinition<import("convex/values").VObject<{
70
131
  createdAt: number;
71
132
  dirtyAt?: number | undefined;
72
133
  gameId: string;
73
134
  playerState: any;
74
135
  revision: number;
136
+ sessionServerId?: GenericId<"jgGameServers"> | undefined;
137
+ sessionState?: any;
75
138
  updatedAt: number;
76
139
  userId: string;
77
140
  }, {
78
141
  userId: import("convex/values").VString<string, "required">;
79
142
  gameId: import("convex/values").VString<string, "required">;
80
143
  playerState: import("convex/values").VAny<any, "required", string>;
144
+ sessionState: import("convex/values").VAny<any, "optional", string>;
145
+ sessionServerId: import("convex/values").VId<GenericId<"jgGameServers"> | undefined, "optional">;
81
146
  revision: import("convex/values").VFloat64<number, "required">;
82
147
  dirtyAt: import("convex/values").VFloat64<number | undefined, "optional">;
83
148
  createdAt: import("convex/values").VFloat64<number, "required">;
84
149
  updatedAt: import("convex/values").VFloat64<number, "required">;
85
- }, "required", "createdAt" | "dirtyAt" | "gameId" | "playerState" | "revision" | "updatedAt" | "userId" | `playerState.${string}`>, {
150
+ }, "required", "createdAt" | "dirtyAt" | "gameId" | "playerState" | "revision" | "sessionServerId" | "sessionState" | "updatedAt" | "userId" | `playerState.${string}` | `sessionState.${string}`>, {
86
151
  by_user_and_game: ["userId", "gameId", "_creationTime"];
87
152
  }, {}, {}>;
88
153
  jgWorldChunks: import("convex/server").TableDefinition<import("convex/values").VObject<{
@@ -135,9 +200,17 @@ export declare function jgengineTables(): {
135
200
  by_server_and_action: ["serverId", "action", "_creationTime"];
136
201
  }, {}, {}>;
137
202
  jgPoses: import("convex/server").TableDefinition<import("convex/values").VObject<{
203
+ chunkKey?: string | undefined;
204
+ homeGameId?: string | undefined;
205
+ kind?: string | undefined;
206
+ label?: string | undefined;
207
+ lastWorldSnapshotAt?: number | undefined;
208
+ ownerActorId?: string | undefined;
209
+ revokedAt?: number | undefined;
138
210
  rotationPitch: number;
139
211
  rotationY: number;
140
212
  serverId: string;
213
+ sessionId?: string | undefined;
141
214
  updatedAt: number;
142
215
  userId: string;
143
216
  x: number;
@@ -145,19 +218,40 @@ export declare function jgengineTables(): {
145
218
  z: number;
146
219
  }, {
147
220
  serverId: import("convex/values").VString<string, "required">;
221
+ homeGameId: import("convex/values").VString<string | undefined, "optional">;
222
+ ownerActorId: import("convex/values").VString<string | undefined, "optional">;
223
+ lastWorldSnapshotAt: import("convex/values").VFloat64<number | undefined, "optional">;
224
+ chunkKey: import("convex/values").VString<string | undefined, "optional">;
148
225
  userId: import("convex/values").VString<string, "required">;
226
+ /** One row per live session, so a second tab is a second row rather than a fight over one. */
227
+ sessionId: import("convex/values").VString<string | undefined, "optional">;
228
+ /** Actor class, e.g. `"player"` / `"agent"`, which selects the pose rules applied to it. */
229
+ kind: import("convex/values").VString<string | undefined, "optional">;
230
+ /** Display name carried on the row, so nameplates need no per-frame join against users. */
231
+ label: import("convex/values").VString<string | undefined, "optional">;
149
232
  x: import("convex/values").VFloat64<number, "required">;
150
233
  y: import("convex/values").VFloat64<number, "required">;
151
234
  z: import("convex/values").VFloat64<number, "required">;
152
235
  rotationY: import("convex/values").VFloat64<number, "required">;
153
236
  rotationPitch: import("convex/values").VFloat64<number, "required">;
154
237
  updatedAt: import("convex/values").VFloat64<number, "required">;
155
- }, "required", "rotationPitch" | "rotationY" | "serverId" | "updatedAt" | "userId" | "x" | "y" | "z">, {
238
+ /** Set when the session was displaced or left; the reaper deletes the row once it goes idle. */
239
+ revokedAt: import("convex/values").VFloat64<number | undefined, "optional">;
240
+ }, "required", "chunkKey" | "homeGameId" | "kind" | "label" | "lastWorldSnapshotAt" | "ownerActorId" | "revokedAt" | "rotationPitch" | "rotationY" | "serverId" | "sessionId" | "updatedAt" | "userId" | "x" | "y" | "z">, {
241
+ by_home_game: ["homeGameId", "_creationTime"];
242
+ by_home_game_revoked_updated: ["homeGameId", "revokedAt", "updatedAt", "_creationTime"];
243
+ by_revoked: ["revokedAt", "_creationTime"];
244
+ by_revoked_updated: ["revokedAt", "updatedAt", "_creationTime"];
156
245
  by_server: ["serverId", "_creationTime"];
246
+ by_server_and_chunk: ["serverId", "chunkKey", "_creationTime"];
157
247
  by_server_and_user: ["serverId", "userId", "_creationTime"];
248
+ by_server_user_revoked_updated: ["serverId", "userId", "revokedAt", "updatedAt", "_creationTime"];
249
+ by_updated: ["updatedAt", "_creationTime"];
250
+ by_user: ["userId", "_creationTime"];
158
251
  }, {}, {}>;
159
252
  jgChatMessages: import("convex/server").TableDefinition<import("convex/values").VObject<{
160
253
  at: number;
254
+ authorName?: string | undefined;
161
255
  body: string;
162
256
  channelId: string;
163
257
  serverId: string;
@@ -167,9 +261,11 @@ export declare function jgengineTables(): {
167
261
  channelId: import("convex/values").VString<string, "required">;
168
262
  userId: import("convex/values").VString<string, "required">;
169
263
  body: import("convex/values").VString<string, "required">;
264
+ authorName: import("convex/values").VString<string | undefined, "optional">;
170
265
  at: import("convex/values").VFloat64<number, "required">;
171
- }, "required", "at" | "body" | "channelId" | "serverId" | "userId">, {
172
- by_server_channel: ["serverId", "channelId", "_creationTime"];
266
+ }, "required", "at" | "authorName" | "body" | "channelId" | "serverId" | "userId">, {
267
+ by_at: ["at", "_creationTime"];
268
+ by_server_channel: ["serverId", "channelId", "at", "_creationTime"];
173
269
  }, {}, {}>;
174
270
  };
175
271
  /**
@@ -211,6 +307,7 @@ declare const schemaForTypes: import("convex/server").SchemaDefinition<{
211
307
  gameId: string;
212
308
  joinCode?: string | undefined;
213
309
  lastSavedAt?: number | undefined;
310
+ memberCount?: number | undefined;
214
311
  memberUserIds: string[];
215
312
  mode?: string | undefined;
216
313
  modeConfig?: any;
@@ -224,6 +321,7 @@ declare const schemaForTypes: import("convex/server").SchemaDefinition<{
224
321
  slotsPerServer: number;
225
322
  status: "closed" | "open" | "running";
226
323
  tickAnchorMs: number;
324
+ topology?: "rooms" | "shared" | undefined;
227
325
  updatedAt: number;
228
326
  visibility?: "private" | "public" | undefined;
229
327
  }, {
@@ -233,6 +331,8 @@ declare const schemaForTypes: import("convex/server").SchemaDefinition<{
233
331
  modeConfig: import("convex/values").VAny<any, "optional", string>;
234
332
  visibility: import("convex/values").VUnion<"private" | "public" | undefined, [import("convex/values").VLiteral<"public", "required">, import("convex/values").VLiteral<"private", "required">], "optional", never>;
235
333
  joinCode: import("convex/values").VString<string | undefined, "optional">;
334
+ topology: import("convex/values").VUnion<"rooms" | "shared" | undefined, [import("convex/values").VLiteral<"shared", "required">, import("convex/values").VLiteral<"rooms", "required">], "optional", never>;
335
+ memberCount: import("convex/values").VFloat64<number | undefined, "optional">;
236
336
  memberUserIds: import("convex/values").VArray<string[], import("convex/values").VString<string, "required">, "required">;
237
337
  slotsPerServer: import("convex/values").VFloat64<number, "required">;
238
338
  save: import("convex/values").VUnion<"none" | {
@@ -253,29 +353,89 @@ declare const schemaForTypes: import("convex/server").SchemaDefinition<{
253
353
  dirtyAt: import("convex/values").VFloat64<number | undefined, "optional">;
254
354
  createdAt: import("convex/values").VFloat64<number, "required">;
255
355
  updatedAt: import("convex/values").VFloat64<number, "required">;
256
- }, "required", "createdAt" | "dirtyAt" | "gameId" | "joinCode" | "lastSavedAt" | "memberUserIds" | "mode" | "modeConfig" | "revision" | "save" | "save.auto" | "save.scope" | "serverState" | "sessionPlayers" | "slotsPerServer" | "status" | "tickAnchorMs" | "updatedAt" | "visibility" | `modeConfig.${string}` | `serverState.${string}` | `sessionPlayers.${string}`>, {
356
+ }, "required", "createdAt" | "dirtyAt" | "gameId" | "joinCode" | "lastSavedAt" | "memberCount" | "memberUserIds" | "mode" | "modeConfig" | "revision" | "save" | "save.auto" | "save.scope" | "serverState" | "sessionPlayers" | "slotsPerServer" | "status" | "tickAnchorMs" | "topology" | "updatedAt" | "visibility" | `modeConfig.${string}` | `serverState.${string}` | `sessionPlayers.${string}`>, {
257
357
  by_dirty: ["dirtyAt", "_creationTime"];
258
358
  by_game_and_status: ["gameId", "status", "_creationTime"];
259
359
  by_game_status_tick: ["gameId", "status", "tickAnchorMs", "_creationTime"];
260
360
  by_status: ["status", "_creationTime"];
261
361
  }, {}, {}>;
362
+ jgServerCapacity: import("convex/server").TableDefinition<import("convex/values").VObject<{
363
+ gameId: string;
364
+ memberCount: number;
365
+ mode?: string | undefined;
366
+ serverId: GenericId<"jgGameServers">;
367
+ slotsPerServer: number;
368
+ status: "closed" | "open" | "running";
369
+ }, {
370
+ serverId: import("convex/values").VId<GenericId<"jgGameServers">, "required">;
371
+ gameId: import("convex/values").VString<string, "required">;
372
+ status: import("convex/values").VUnion<"closed" | "open" | "running", [import("convex/values").VLiteral<"open", "required">, import("convex/values").VLiteral<"running", "required">, import("convex/values").VLiteral<"closed", "required">], "required", never>;
373
+ mode: import("convex/values").VString<string | undefined, "optional">;
374
+ memberCount: import("convex/values").VFloat64<number, "required">;
375
+ slotsPerServer: import("convex/values").VFloat64<number, "required">;
376
+ }, "required", "gameId" | "memberCount" | "mode" | "serverId" | "slotsPerServer" | "status">, {
377
+ by_server: ["serverId", "_creationTime"];
378
+ }, {}, {}>;
379
+ jgServerMembers: import("convex/server").TableDefinition<import("convex/values").VObject<{
380
+ joinedAt: number;
381
+ serverId: GenericId<"jgGameServers">;
382
+ userId: string;
383
+ }, {
384
+ serverId: import("convex/values").VId<GenericId<"jgGameServers">, "required">;
385
+ userId: import("convex/values").VString<string, "required">;
386
+ joinedAt: import("convex/values").VFloat64<number, "required">;
387
+ }, "required", "joinedAt" | "serverId" | "userId">, {
388
+ by_server: ["serverId", "_creationTime"];
389
+ by_server_and_user: ["serverId", "userId", "_creationTime"];
390
+ }, {}, {}>;
391
+ jgRateLimits: import("convex/server").TableDefinition<import("convex/values").VObject<{
392
+ count: number;
393
+ expiresAt: number;
394
+ key: string;
395
+ startedAt: number;
396
+ }, {
397
+ key: import("convex/values").VString<string, "required">;
398
+ startedAt: import("convex/values").VFloat64<number, "required">;
399
+ count: import("convex/values").VFloat64<number, "required">;
400
+ expiresAt: import("convex/values").VFloat64<number, "required">;
401
+ }, "required", "count" | "expiresAt" | "key" | "startedAt">, {
402
+ by_expires: ["expiresAt", "_creationTime"];
403
+ by_key: ["key", "_creationTime"];
404
+ }, {}, {}>;
405
+ jgClientErrors: import("convex/server").TableDefinition<import("convex/values").VObject<{
406
+ createdAt: number;
407
+ details?: any;
408
+ message: string;
409
+ userId: string;
410
+ }, {
411
+ userId: import("convex/values").VString<string, "required">;
412
+ message: import("convex/values").VString<string, "required">;
413
+ details: import("convex/values").VAny<any, "optional", string>;
414
+ createdAt: import("convex/values").VFloat64<number, "required">;
415
+ }, "required", "createdAt" | "details" | "message" | "userId" | `details.${string}`>, {
416
+ by_created: ["createdAt", "_creationTime"];
417
+ }, {}, {}>;
262
418
  jgPlayerProfiles: import("convex/server").TableDefinition<import("convex/values").VObject<{
263
419
  createdAt: number;
264
420
  dirtyAt?: number | undefined;
265
421
  gameId: string;
266
422
  playerState: any;
267
423
  revision: number;
424
+ sessionServerId?: GenericId<"jgGameServers"> | undefined;
425
+ sessionState?: any;
268
426
  updatedAt: number;
269
427
  userId: string;
270
428
  }, {
271
429
  userId: import("convex/values").VString<string, "required">;
272
430
  gameId: import("convex/values").VString<string, "required">;
273
431
  playerState: import("convex/values").VAny<any, "required", string>;
432
+ sessionState: import("convex/values").VAny<any, "optional", string>;
433
+ sessionServerId: import("convex/values").VId<GenericId<"jgGameServers"> | undefined, "optional">;
274
434
  revision: import("convex/values").VFloat64<number, "required">;
275
435
  dirtyAt: import("convex/values").VFloat64<number | undefined, "optional">;
276
436
  createdAt: import("convex/values").VFloat64<number, "required">;
277
437
  updatedAt: import("convex/values").VFloat64<number, "required">;
278
- }, "required", "createdAt" | "dirtyAt" | "gameId" | "playerState" | "revision" | "updatedAt" | "userId" | `playerState.${string}`>, {
438
+ }, "required", "createdAt" | "dirtyAt" | "gameId" | "playerState" | "revision" | "sessionServerId" | "sessionState" | "updatedAt" | "userId" | `playerState.${string}` | `sessionState.${string}`>, {
279
439
  by_user_and_game: ["userId", "gameId", "_creationTime"];
280
440
  }, {}, {}>;
281
441
  jgWorldChunks: import("convex/server").TableDefinition<import("convex/values").VObject<{
@@ -328,9 +488,17 @@ declare const schemaForTypes: import("convex/server").SchemaDefinition<{
328
488
  by_server_and_action: ["serverId", "action", "_creationTime"];
329
489
  }, {}, {}>;
330
490
  jgPoses: import("convex/server").TableDefinition<import("convex/values").VObject<{
491
+ chunkKey?: string | undefined;
492
+ homeGameId?: string | undefined;
493
+ kind?: string | undefined;
494
+ label?: string | undefined;
495
+ lastWorldSnapshotAt?: number | undefined;
496
+ ownerActorId?: string | undefined;
497
+ revokedAt?: number | undefined;
331
498
  rotationPitch: number;
332
499
  rotationY: number;
333
500
  serverId: string;
501
+ sessionId?: string | undefined;
334
502
  updatedAt: number;
335
503
  userId: string;
336
504
  x: number;
@@ -338,19 +506,40 @@ declare const schemaForTypes: import("convex/server").SchemaDefinition<{
338
506
  z: number;
339
507
  }, {
340
508
  serverId: import("convex/values").VString<string, "required">;
509
+ homeGameId: import("convex/values").VString<string | undefined, "optional">;
510
+ ownerActorId: import("convex/values").VString<string | undefined, "optional">;
511
+ lastWorldSnapshotAt: import("convex/values").VFloat64<number | undefined, "optional">;
512
+ chunkKey: import("convex/values").VString<string | undefined, "optional">;
341
513
  userId: import("convex/values").VString<string, "required">;
514
+ /** One row per live session, so a second tab is a second row rather than a fight over one. */
515
+ sessionId: import("convex/values").VString<string | undefined, "optional">;
516
+ /** Actor class, e.g. `"player"` / `"agent"`, which selects the pose rules applied to it. */
517
+ kind: import("convex/values").VString<string | undefined, "optional">;
518
+ /** Display name carried on the row, so nameplates need no per-frame join against users. */
519
+ label: import("convex/values").VString<string | undefined, "optional">;
342
520
  x: import("convex/values").VFloat64<number, "required">;
343
521
  y: import("convex/values").VFloat64<number, "required">;
344
522
  z: import("convex/values").VFloat64<number, "required">;
345
523
  rotationY: import("convex/values").VFloat64<number, "required">;
346
524
  rotationPitch: import("convex/values").VFloat64<number, "required">;
347
525
  updatedAt: import("convex/values").VFloat64<number, "required">;
348
- }, "required", "rotationPitch" | "rotationY" | "serverId" | "updatedAt" | "userId" | "x" | "y" | "z">, {
526
+ /** Set when the session was displaced or left; the reaper deletes the row once it goes idle. */
527
+ revokedAt: import("convex/values").VFloat64<number | undefined, "optional">;
528
+ }, "required", "chunkKey" | "homeGameId" | "kind" | "label" | "lastWorldSnapshotAt" | "ownerActorId" | "revokedAt" | "rotationPitch" | "rotationY" | "serverId" | "sessionId" | "updatedAt" | "userId" | "x" | "y" | "z">, {
529
+ by_home_game: ["homeGameId", "_creationTime"];
530
+ by_home_game_revoked_updated: ["homeGameId", "revokedAt", "updatedAt", "_creationTime"];
531
+ by_revoked: ["revokedAt", "_creationTime"];
532
+ by_revoked_updated: ["revokedAt", "updatedAt", "_creationTime"];
349
533
  by_server: ["serverId", "_creationTime"];
534
+ by_server_and_chunk: ["serverId", "chunkKey", "_creationTime"];
350
535
  by_server_and_user: ["serverId", "userId", "_creationTime"];
536
+ by_server_user_revoked_updated: ["serverId", "userId", "revokedAt", "updatedAt", "_creationTime"];
537
+ by_updated: ["updatedAt", "_creationTime"];
538
+ by_user: ["userId", "_creationTime"];
351
539
  }, {}, {}>;
352
540
  jgChatMessages: import("convex/server").TableDefinition<import("convex/values").VObject<{
353
541
  at: number;
542
+ authorName?: string | undefined;
354
543
  body: string;
355
544
  channelId: string;
356
545
  serverId: string;
@@ -360,14 +549,18 @@ declare const schemaForTypes: import("convex/server").SchemaDefinition<{
360
549
  channelId: import("convex/values").VString<string, "required">;
361
550
  userId: import("convex/values").VString<string, "required">;
362
551
  body: import("convex/values").VString<string, "required">;
552
+ authorName: import("convex/values").VString<string | undefined, "optional">;
363
553
  at: import("convex/values").VFloat64<number, "required">;
364
- }, "required", "at" | "body" | "channelId" | "serverId" | "userId">, {
365
- by_server_channel: ["serverId", "channelId", "_creationTime"];
554
+ }, "required", "at" | "authorName" | "body" | "channelId" | "serverId" | "userId">, {
555
+ by_at: ["at", "_creationTime"];
556
+ by_server_channel: ["serverId", "channelId", "at", "_creationTime"];
366
557
  }, {}, {}>;
367
558
  }, true>;
368
559
  /** Data model of the {@link jgengineTables} schema — the shape a host's own Convex ctx must satisfy to
369
560
  * call the exported persistence helpers. */
370
561
  export type JGDataModel = DataModelFromSchemaDefinition<typeof schemaForTypes>;
562
+ /** Read-only context accepted by engine host queries. */
563
+ export type JGQueryCtx = GenericQueryCtx<JGDataModel>;
371
564
  /** Mutation ctx accepted by {@link loadServerSnapshot} / {@link persistServerSnapshot}. */
372
565
  export type JGMutationCtx = GenericMutationCtx<JGDataModel>;
373
566
  /** A `jgGameServers` row — the server handle the persistence helpers load from and write back to. */
@@ -389,13 +582,11 @@ export declare function resolveActor(ctx: {
389
582
  *
390
583
  * Persisting a narrowed snapshot is safe: the plan merges session state over the stored map and
391
584
  * writes only dirty profiles and dirty chunks, so members and chunks left unhydrated are untouched.
585
+ *
586
+ * The registered `runCommand` mutation takes one, and a {@link CommandDef} can derive its own from
587
+ * its input via `scope`, so a game gets bounded hydration without writing its own mutation.
392
588
  */
393
- export type LoadSnapshotScope = {
394
- /** Member ids to hydrate; omit for the whole roster. */
395
- players?: readonly string[];
396
- /** Chunk keys to hydrate; omit for every chunk of the server, `[]` for none. */
397
- chunkKeys?: readonly string[];
398
- };
589
+ export type LoadSnapshotScope = CommandScope;
399
590
  /**
400
591
  * Hydrate a server's `GameRuntimeSnapshot` from its row, its members' profiles, and its world chunks.
401
592
  * Reach for it when writing your own mutation that must read runtime state before touching host tables.
@@ -430,6 +621,14 @@ export type RunCommandArgs = {
430
621
  command: string;
431
622
  input: unknown;
432
623
  externalId?: string;
624
+ /**
625
+ * How much of the server to hydrate before applying the command. Overrides whatever the
626
+ * {@link CommandDef} declares through its own `scope`; omit both to hydrate the whole world.
627
+ */
628
+ scope?: LoadSnapshotScope;
629
+ /** Trusted host override after game-specific authorization; never forward a client-supplied actor. */
630
+ actorUserId?: string;
631
+ beforePersist?: (ctx: JGMutationCtx, loaded: LoadedServerSnapshot) => Promise<void>;
433
632
  };
434
633
  /** A server resolved for runtime work: its row, its registered runtime, and its hydrated snapshot. */
435
634
  export type LoadedServerSnapshot = {
@@ -443,6 +642,7 @@ export type LoadedServerSnapshot = {
443
642
  * table writes in one transaction, which a pre-registered mutation cannot do.
444
643
  */
445
644
  export type GameServerHelpers = {
645
+ ensureServer: (ctx: JGMutationCtx, gameId: string) => Promise<ServerDoc>;
446
646
  loadSnapshot: (ctx: JGMutationCtx, serverId: string, scope?: LoadSnapshotScope) => Promise<LoadedServerSnapshot | null>;
447
647
  applyCommand: (args: {
448
648
  gameId: string;
@@ -461,6 +661,7 @@ export type GameServerHelpers = {
461
661
  };
462
662
  persistSnapshot: (ctx: JGMutationCtx, server: ServerDoc, snapshot: GameRuntimeSnapshot, save?: SaveConfig) => Promise<boolean>;
463
663
  runCommand: (ctx: JGMutationCtx, args: RunCommandArgs) => Promise<RunCommandOutcome>;
664
+ resetPlayerProfile: (ctx: JGMutationCtx, serverId: string, userId: string) => Promise<RuntimePlayerRow>;
464
665
  };
465
666
  /** How many running servers one `tickActiveServers` transaction may hydrate, tick, and persist. */
466
667
  export declare const DEFAULT_MAX_SERVERS_PER_TICK = 32;
@@ -469,10 +670,9 @@ export declare const MAX_CHUNKS_PER_QUERY = 64;
469
670
  /**
470
671
  * Build the hosted-server Convex functions for a set of game runtimes.
471
672
  *
472
- * **Supported world size.** One server is one Convex document holding the world row, the member
473
- * roster, and every member's session state, and the host rewrites that document whenever the world
474
- * changes. `slotsPerServer` is therefore capped at {@link JG_MAX_MEMBERS_PER_SERVER} (256) and a
475
- * larger value throws here, at wiring time, rather than at the write that overflows the document.
673
+ * Room topology keeps its roster and session map in the server document and caps capacity at
674
+ * {@link JG_MAX_MEMBERS_PER_SERVER}. Shared topology stores membership, capacity, and per-player
675
+ * session state separately; player-only commands and roster changes do not rewrite the world row.
476
676
  * World geometry that outgrows one row belongs in `jgWorldChunks`, hydrated by key
477
677
  * ({@link LoadSnapshotScope}) and read by clients through `getChunks` — not in `serverState`.
478
678
  *
@@ -484,6 +684,7 @@ export declare function createGameServerFunctions(options?: {
484
684
  runtimes?: GameRuntime[];
485
685
  auth?: JgAuthMode;
486
686
  slotsPerServer?: number;
687
+ topology?: "shared" | "rooms";
487
688
  matchmaking?: MatchmakingMode;
488
689
  maxServersPerTick?: number;
489
690
  allowedFeedActions?: readonly string[];
@@ -497,6 +698,23 @@ export declare function createGameServerFunctions(options?: {
497
698
  serverId?: GenericId<"jgGameServers"> | undefined;
498
699
  visibility?: "private" | "public" | undefined;
499
700
  }, Promise<{
701
+ ok: false;
702
+ reason: "unauthorized";
703
+ serverId?: undefined;
704
+ isNew?: undefined;
705
+ } | {
706
+ ok: false;
707
+ reason: "closed";
708
+ serverId?: undefined;
709
+ isNew?: undefined;
710
+ } | {
711
+ ok: false;
712
+ reason: "full";
713
+ serverId?: undefined;
714
+ isNew?: undefined;
715
+ } | {
716
+ reason?: undefined;
717
+ ok: true;
500
718
  serverId: GenericId<"jgGameServers">;
501
719
  isNew: boolean;
502
720
  }>>;
@@ -508,6 +726,10 @@ export declare function createGameServerFunctions(options?: {
508
726
  command: string;
509
727
  externalId?: string | undefined;
510
728
  input: any;
729
+ scope?: {
730
+ chunkKeys?: string[] | undefined;
731
+ players?: string[] | undefined;
732
+ } | undefined;
511
733
  serverId: GenericId<"jgGameServers">;
512
734
  }, Promise<RunCommandOutcome>>;
513
735
  flushSave: import("convex/server").RegisteredMutation<"public", {
@@ -545,8 +767,17 @@ export declare function createGameServerFunctions(options?: {
545
767
  mode: string | undefined;
546
768
  memberCount: number;
547
769
  slotsPerServer: number;
548
- revision: number;
549
- updatedAt: number;
770
+ } | null>>;
771
+ getServerCapacity: import("convex/server").RegisteredQuery<"public", {
772
+ externalId?: string | undefined;
773
+ serverId: GenericId<"jgGameServers">;
774
+ }, Promise<{
775
+ _id: GenericId<"jgGameServers">;
776
+ gameId: string;
777
+ status: "closed" | "open" | "running";
778
+ mode: string | undefined;
779
+ memberCount: number;
780
+ slotsPerServer: number;
550
781
  } | null>>;
551
782
  getChunks: import("convex/server").RegisteredQuery<"public", {
552
783
  chunkKeys: string[];
@@ -630,41 +861,198 @@ export declare function createLeaderboardFunctions(options?: {
630
861
  gameId: string;
631
862
  }, Promise<null>>;
632
863
  };
633
- /** @internal */
864
+ /** One member's pose as `list` reports it. */
865
+ export type PresenceListRow = {
866
+ userId: string;
867
+ sessionId?: string;
868
+ kind?: string;
869
+ label?: string;
870
+ position: {
871
+ x: number;
872
+ y: number;
873
+ z: number;
874
+ };
875
+ rotationY: number;
876
+ rotationPitch: number;
877
+ lastSeenAt: number;
878
+ };
879
+ /** What `sync` hands back: the pose the server holds after clamping, and whether this session had been displaced. */
880
+ export type PresenceSyncResult = {
881
+ pose: {
882
+ x: number;
883
+ y: number;
884
+ z: number;
885
+ rotationY: number;
886
+ rotationPitch: number;
887
+ };
888
+ lastSeenAt: number;
889
+ displaced: boolean;
890
+ };
891
+ /**
892
+ * The pose lane of a hosted server: the backend behind `PresenceSync`, which replicates where each
893
+ * member of one `jgGameServers` server is standing. It is not the backend for `PresenceTransport` —
894
+ * that contract (`ConvexPresenceFunctions` in `./convexPresenceTransport`) is a game-owned world's
895
+ * own presence, keyed by the game's own ids, with its own notion of who counts as a resident. A game
896
+ * on `createConvexPresenceTransport` supplies those functions itself; these do not substitute for them.
897
+ * @internal
898
+ */
634
899
  export declare function createPresenceFunctions(options?: {
635
900
  auth?: JgAuthMode;
901
+ /** How recently a row must have been written to appear in `list`. */
636
902
  freshWindowMs?: number;
637
- poseRules?: PoseSyncRules;
903
+ /** How long a row may go unwritten before `reapIdlePresence` deletes it. */
904
+ idleCutoffMs?: number;
905
+ /** Rows deleted per reaper run, so one sweep is a bounded transaction. */
906
+ maxReapPerRun?: number;
907
+ /** Movement clamping, optionally per actor `kind` — an agent need not obey a human's speed cap. */
908
+ poseRules?: PoseSyncRules | ((kind: string | undefined) => PoseSyncRules);
909
+ /** Where a session with no row starts. Async and ctx-bearing, so a spawn can read the world. */
910
+ resolveSpawn?: (ctx: JGMutationCtx, args: {
911
+ serverId: string;
912
+ userId: string;
913
+ kind?: string;
914
+ }) => Promise<{
915
+ x: number;
916
+ y: number;
917
+ z: number;
918
+ } | null> | {
919
+ x: number;
920
+ y: number;
921
+ z: number;
922
+ } | null;
638
923
  }): {
639
924
  list: import("convex/server").RegisteredQuery<"public", {
640
925
  externalId?: string | undefined;
641
926
  serverId: string;
642
- }, Promise<{
643
- userId: string;
644
- position: {
645
- x: number;
646
- y: number;
647
- z: number;
648
- };
649
- rotationY: number;
650
- rotationPitch: number;
651
- lastSeenAt: number;
652
- }[]>>;
927
+ viewerChunkKey?: string | undefined;
928
+ }, Promise<PresenceListRow[]>>;
653
929
  sync: import("convex/server").RegisteredMutation<"public", {
654
930
  externalId?: string | undefined;
655
- pose: {
931
+ kind?: string | undefined;
932
+ label?: string | undefined;
933
+ pose?: {
656
934
  rotationPitch: number;
657
935
  rotationY: number;
658
936
  x: number;
659
937
  y: number;
660
938
  z: number;
661
- };
939
+ } | undefined;
662
940
  serverId: string;
663
- }, Promise<null>>;
941
+ sessionId?: string | undefined;
942
+ }, Promise<PresenceSyncResult | null>>;
664
943
  leave: import("convex/server").RegisteredMutation<"public", {
665
944
  externalId?: string | undefined;
666
945
  serverId: string;
946
+ sessionId?: string | undefined;
667
947
  }, Promise<null>>;
948
+ reapIdlePresence: import("convex/server").RegisteredMutation<"internal", {}, Promise<{
949
+ reaped: number;
950
+ }>>;
951
+ };
952
+ /** One active player delivered to an online-system batch. */
953
+ export type OnlinePlayer = {
954
+ serverId: string;
955
+ userId: string;
956
+ homeGameId?: string;
957
+ };
958
+ /**
959
+ * Scan one bounded presence page and schedule its handler and continuation in separate transactions.
960
+ * @capability online-player-batches Schedule bounded active-player or home-game work in separate transactions.
961
+ */
962
+ export declare function forEachOnlinePlayer(ctx: JGMutationCtx, options: {
963
+ batchSize?: number;
964
+ handler: FunctionReference<"mutation", "internal", {
965
+ players: OnlinePlayer[];
966
+ nowMs: number;
967
+ }>;
968
+ continuation: FunctionReference<"mutation", "internal", {
969
+ cursor?: string | null;
970
+ nowMs?: number;
971
+ }>;
972
+ cursor?: string | null;
973
+ nowMs?: number;
974
+ freshWindowMs?: number;
975
+ /** Deliver one representative per home game when several agents share a simulation. */
976
+ groupBy?: "userId" | "homeGameId";
977
+ }): Promise<{
978
+ scheduled: number;
979
+ remaining: boolean;
980
+ }>;
981
+ /**
982
+ * Consume one request in a fixed rate window; concurrent callers serialize on the indexed key.
983
+ * @capability host-rate-limit Enforce indexed per-key request windows inside authoritative mutations.
984
+ */
985
+ export declare function rateLimit(ctx: JGMutationCtx, args: {
986
+ key: string;
987
+ windowMs: number;
988
+ max: number;
989
+ nowMs?: number;
990
+ }): Promise<{
991
+ ok: boolean;
992
+ retryAfterMs: number;
993
+ }>;
994
+ /**
995
+ * Write validated chat in a host transaction after the caller resolves its actor and world.
996
+ * @capability host-chat-send Validate, rate-limit and persist chat inside an authorized host mutation.
997
+ */
998
+ export declare function sendChatMessage(ctx: JGMutationCtx, args: {
999
+ serverId: string;
1000
+ userId: string;
1001
+ body: string;
1002
+ channelId?: string;
1003
+ authorName?: string;
1004
+ maxBodyLength?: number;
1005
+ minIntervalMs?: number;
1006
+ }): Promise<ChatSendOutcome>;
1007
+ /**
1008
+ * Bounded recent chat for a resolved server; authorization belongs to the calling query.
1009
+ * @capability host-chat-history Read bounded channel history inside an authorized host query.
1010
+ */
1011
+ export declare function recentChatMessages(ctx: {
1012
+ db: Pick<JGQueryCtx["db"], "query">;
1013
+ }, args: {
1014
+ serverId: string;
1015
+ channelId?: string;
1016
+ limit?: number;
1017
+ }): Promise<{
1018
+ _creationTime: number;
1019
+ _id: GenericId<"jgChatMessages">;
1020
+ at: number;
1021
+ authorName?: string | undefined;
1022
+ body: string;
1023
+ channelId: string;
1024
+ serverId: string;
1025
+ userId: string;
1026
+ }[]>;
1027
+ /**
1028
+ * Authenticated, bounded client diagnostics and bounded retention sweeps.
1029
+ * @capability client-error-reporting Accept authenticated bounded diagnostics with cooldown and retention sweeps.
1030
+ */
1031
+ export declare function createClientErrorFunctions(): {
1032
+ reportClientError: import("convex/server").RegisteredMutation<"public", {
1033
+ agentPrompt?: string | undefined;
1034
+ componentStack?: string | undefined;
1035
+ kind?: string | undefined;
1036
+ message: string;
1037
+ pathname?: string | undefined;
1038
+ stack?: string | undefined;
1039
+ url?: string | undefined;
1040
+ userAgent?: string | undefined;
1041
+ }, Promise<{
1042
+ ok: boolean;
1043
+ reason: string;
1044
+ } | {
1045
+ reason?: undefined;
1046
+ ok: boolean;
1047
+ }>>;
1048
+ pruneClientErrors: import("convex/server").RegisteredMutation<"internal", {}, Promise<{
1049
+ deleted: number;
1050
+ remaining: boolean;
1051
+ }>>;
1052
+ pruneRateLimits: import("convex/server").RegisteredMutation<"internal", {}, Promise<{
1053
+ deleted: number;
1054
+ remaining: boolean;
1055
+ }>>;
668
1056
  };
669
1057
  /** @internal */
670
1058
  export declare function createChatFunctions(options?: {
@@ -678,17 +1066,33 @@ export declare function createChatFunctions(options?: {
678
1066
  externalId?: string | undefined;
679
1067
  serverId: string;
680
1068
  }, Promise<ChatMessage[]>>;
1069
+ recent: import("convex/server").RegisteredQuery<"public", {
1070
+ channelId: string;
1071
+ externalId?: string | undefined;
1072
+ serverId: string;
1073
+ }, Promise<ChatMessage[]>>;
681
1074
  sendMessage: import("convex/server").RegisteredMutation<"public", {
682
1075
  body: string;
683
1076
  channelId: string;
684
1077
  externalId?: string | undefined;
685
1078
  serverId: string;
686
1079
  }, Promise<ChatSendOutcome>>;
1080
+ pruneChatMessages: import("convex/server").RegisteredMutation<"internal", {}, Promise<{
1081
+ deleted: number;
1082
+ remaining: boolean;
1083
+ }>>;
687
1084
  };
688
1085
  export type JgCronSpec = {
689
1086
  name: string;
690
1087
  intervalSeconds: number;
691
- functionKey: "tickActiveServers" | "flushDirtyServers";
1088
+ functionKey: "tickActiveServers" | "flushDirtyServers" | "reapIdlePresence" | "pruneChatMessages" | "pruneClientErrors" | "pruneRateLimits";
1089
+ /** Which factory's exports the function lives in, i.e. which `convex/*.ts` file to reach it through. */
1090
+ module: "runtime" | "presence" | "chat" | "clientErrors";
692
1091
  };
693
1092
  /** @internal */
694
- export declare function jgengineCronSpecs(): readonly JgCronSpec[];
1093
+ export declare function jgengineCronSpecs(options?: {
1094
+ runtimes?: readonly Pick<GameRuntime, "hasTick">[];
1095
+ intervalSeconds?: Partial<Record<JgCronSpec["functionKey"], number>>;
1096
+ chat?: boolean;
1097
+ clientErrors?: boolean;
1098
+ }): readonly JgCronSpec[];