@playcademy/sdk 0.0.8 → 0.1.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/dist/index.js CHANGED
@@ -11,10 +11,19 @@ var __export = (target, all) => {
11
11
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
12
12
 
13
13
  // ../logger/src/index.ts
14
- var isBrowser = () => {
14
+ var hasProcess = () => {
15
+ return typeof process !== "undefined";
16
+ }, isBrowser = () => {
15
17
  const g = globalThis;
16
18
  return typeof g.window !== "undefined" && typeof g.document !== "undefined";
17
- }, colors, getLevelColor = (level) => {
19
+ }, colors, shouldUseColor = () => {
20
+ const preference = (process.env.LOG_COLOR ?? "auto").toLowerCase();
21
+ if (preference === "always")
22
+ return true;
23
+ if (preference === "never")
24
+ return false;
25
+ return Boolean(process.stdout && process.stdout.isTTY && true);
26
+ }, getLevelColor = (level) => {
18
27
  switch (level) {
19
28
  case "debug":
20
29
  return colors.blue;
@@ -38,7 +47,7 @@ var isBrowser = () => {
38
47
  }
39
48
  }, logOnServer = (level, message, context) => {
40
49
  const consoleMethod = getConsoleMethod(level);
41
- if (true) {
50
+ if (shouldUseColor()) {
42
51
  const timestamp = new Date().toISOString();
43
52
  const levelColor = getLevelColor(level);
44
53
  const levelUpper = level.toUpperCase().padEnd(5);
@@ -48,7 +57,10 @@ var isBrowser = () => {
48
57
  } else {
49
58
  consoleMethod(`${coloredPrefix} ${message}`);
50
59
  }
51
- } else {}
60
+ } else {
61
+ const formatted = formatLog(level, message, context);
62
+ consoleMethod(formatted);
63
+ }
52
64
  }, getConsoleMethod = (level) => {
53
65
  switch (level) {
54
66
  case "debug":
@@ -62,8 +74,22 @@ var isBrowser = () => {
62
74
  default:
63
75
  return console.log;
64
76
  }
77
+ }, formatLog = (level, message, context) => {
78
+ const timestamp = new Date().toISOString();
79
+ const logEntry = {
80
+ timestamp,
81
+ level: level.toUpperCase(),
82
+ message,
83
+ ...context && Object.keys(context).length > 0 && { context }
84
+ };
85
+ if (true) {
86
+ return JSON.stringify(logEntry, null, 2);
87
+ }
88
+ return JSON.stringify(logEntry);
65
89
  }, performLog = (level, message, context) => {
66
- if (level === "debug" && false) {}
90
+ if (level === "debug" && hasProcess() && process.env.PLAYCADEMY_EMBEDDED) {
91
+ return;
92
+ }
67
93
  if (isBrowser()) {
68
94
  logInBrowser(level, message, context);
69
95
  } else {
@@ -91,6 +117,85 @@ var init_src = __esm(() => {
91
117
  log = createLogger();
92
118
  });
93
119
 
120
+ // src/core/auth/strategies.ts
121
+ class ApiKeyAuth {
122
+ apiKey;
123
+ constructor(apiKey) {
124
+ this.apiKey = apiKey;
125
+ }
126
+ getToken() {
127
+ return this.apiKey;
128
+ }
129
+ getType() {
130
+ return "apiKey";
131
+ }
132
+ getHeaders() {
133
+ return { "x-api-key": this.apiKey };
134
+ }
135
+ }
136
+
137
+ class SessionAuth {
138
+ sessionToken;
139
+ constructor(sessionToken) {
140
+ this.sessionToken = sessionToken;
141
+ }
142
+ getToken() {
143
+ return this.sessionToken;
144
+ }
145
+ getType() {
146
+ return "session";
147
+ }
148
+ getHeaders() {
149
+ return { Authorization: `Bearer ${this.sessionToken}` };
150
+ }
151
+ }
152
+
153
+ class GameJwtAuth {
154
+ gameToken;
155
+ constructor(gameToken) {
156
+ this.gameToken = gameToken;
157
+ }
158
+ getToken() {
159
+ return this.gameToken;
160
+ }
161
+ getType() {
162
+ return "gameJwt";
163
+ }
164
+ getHeaders() {
165
+ return { Authorization: `Bearer ${this.gameToken}` };
166
+ }
167
+ }
168
+
169
+ class NoAuth {
170
+ getToken() {
171
+ return null;
172
+ }
173
+ getType() {
174
+ return "session";
175
+ }
176
+ getHeaders() {
177
+ return {};
178
+ }
179
+ }
180
+ function createAuthStrategy(token, tokenType) {
181
+ if (!token) {
182
+ return new NoAuth;
183
+ }
184
+ if (tokenType === "apiKey") {
185
+ return new ApiKeyAuth(token);
186
+ }
187
+ if (tokenType === "session") {
188
+ return new SessionAuth(token);
189
+ }
190
+ if (tokenType === "gameJwt") {
191
+ return new GameJwtAuth(token);
192
+ }
193
+ if (token.startsWith("cademy")) {
194
+ return new ApiKeyAuth(token);
195
+ }
196
+ return new GameJwtAuth(token);
197
+ }
198
+
94
199
  // src/core/auth/utils.ts
95
200
  function openPopupWindow(url, name = "auth-popup", width = 500, height = 600) {
96
201
  const left = window.screenX + (window.outerWidth - width) / 2;
@@ -146,9 +251,14 @@ function createAuthNamespace(client) {
146
251
  return {
147
252
  login: async (credentials) => {
148
253
  try {
149
- const response = await client["request"]("/auth/login", "POST", credentials);
150
- client.setToken(response.token);
151
- return { success: true, token: response.token };
254
+ const response = await client["request"]("/auth/sign-in/email", "POST", credentials);
255
+ client.setToken(response.token, "session");
256
+ return {
257
+ success: true,
258
+ token: response.token,
259
+ user: response.user,
260
+ expiresAt: response.expiresAt
261
+ };
152
262
  } catch (error) {
153
263
  return {
154
264
  success: false,
@@ -157,8 +267,29 @@ function createAuthNamespace(client) {
157
267
  }
158
268
  },
159
269
  logout: async () => {
160
- await client["request"]("/auth/logout", "POST");
270
+ try {
271
+ await client["request"]("/auth/sign-out", "POST");
272
+ } catch {}
161
273
  client.setToken(null);
274
+ },
275
+ apiKeys: {
276
+ create: async (options) => {
277
+ return client["request"]("/dev/api-keys", "POST", {
278
+ name: options?.name || `SDK Key - ${new Date().toISOString()}`,
279
+ expiresIn: options?.expiresIn !== undefined ? options.expiresIn : null,
280
+ permissions: options?.permissions || {
281
+ games: ["read", "write", "delete"],
282
+ users: ["read:self", "write:self"],
283
+ dev: ["read", "write"]
284
+ }
285
+ });
286
+ },
287
+ list: async () => {
288
+ return client["request"]("/auth/api-key/list", "GET");
289
+ },
290
+ revoke: async (keyId) => {
291
+ await client["request"]("/auth/api-key/revoke", "POST", { id: keyId });
292
+ }
162
293
  }
163
294
  };
164
295
  }
@@ -733,10 +864,25 @@ function createTTLCache(options) {
733
864
  }
734
865
 
735
866
  // src/core/request.ts
867
+ function checkDevWarnings(data) {
868
+ if (!data || typeof data !== "object")
869
+ return;
870
+ const response = data;
871
+ const warningType = response.__playcademyDevWarning;
872
+ if (!warningType)
873
+ return;
874
+ switch (warningType) {
875
+ case "timeback-disabled":
876
+ console.warn("%c⚠️ TimeBack Disabled in Dev", "background: #f59e0b; color: white; padding: 4px 8px; border-radius: 3px; font-weight: bold", `
877
+ ` + (response.__playcademyDevMessage || "TimeBack is disabled in local development"));
878
+ break;
879
+ default:
880
+ console.warn(`[Playcademy Dev Warning] ${warningType}`);
881
+ }
882
+ }
736
883
  async function request({
737
884
  path,
738
885
  baseUrl,
739
- token,
740
886
  method = "GET",
741
887
  body,
742
888
  extraHeaders = {}
@@ -750,8 +896,6 @@ async function request({
750
896
  payload = JSON.stringify(body);
751
897
  headers["Content-Type"] = "application/json";
752
898
  }
753
- if (token)
754
- headers["Authorization"] = `Bearer ${token}`;
755
899
  const res = await fetch(url, {
756
900
  method,
757
901
  headers,
@@ -770,7 +914,9 @@ async function request({
770
914
  const contentType = res.headers.get("content-type") ?? "";
771
915
  if (contentType.includes("application/json")) {
772
916
  try {
773
- return await res.json();
917
+ const parsed = await res.json();
918
+ checkDevWarnings(parsed);
919
+ return parsed;
774
920
  } catch (err) {
775
921
  if (err instanceof SyntaxError)
776
922
  return;
@@ -819,7 +965,7 @@ function createGamesNamespace(client) {
819
965
  const promise = client["request"](`/games/${gameIdOrSlug}`, "GET");
820
966
  return gameFetchCache.get(gameIdOrSlug, async () => {
821
967
  const baseGameData = await promise;
822
- if (baseGameData.gameType === "hosted" && baseGameData.assetBundleBase !== null) {
968
+ if (baseGameData.gameType === "hosted" && baseGameData.assetBundleBase !== null && baseGameData.assetBundleBase !== "") {
823
969
  const manifestData = await fetchManifest(baseGameData.assetBundleBase);
824
970
  return { ...baseGameData, manifest: manifestData };
825
971
  }
@@ -1009,153 +1155,155 @@ function createDevNamespace(client) {
1009
1155
  }
1010
1156
  },
1011
1157
  games: {
1012
- upsert: async (slug, metadata, file, hooks) => {
1013
- hooks?.onEvent?.({ type: "init" });
1014
- const game = await client["request"](`/games/${slug}`, "PUT", metadata);
1015
- if (metadata.gameType === "external" || file === null) {
1016
- return game;
1017
- }
1018
- const fileName = file instanceof File ? file.name : "game.zip";
1019
- const initiateResponse = await client["request"]("/games/uploads/initiate/", "POST", {
1020
- fileName,
1021
- gameId: game.id
1022
- });
1023
- if (hooks?.onEvent) {
1024
- await new Promise((resolve, reject) => {
1025
- const xhr = new XMLHttpRequest;
1026
- xhr.open("PUT", initiateResponse.presignedUrl, true);
1027
- const contentType = file.type || "application/octet-stream";
1028
- try {
1029
- xhr.setRequestHeader("Content-Type", contentType);
1030
- } catch {}
1031
- xhr.upload.onprogress = (event) => {
1032
- if (event.lengthComputable) {
1033
- const percent = event.loaded / event.total;
1034
- hooks.onEvent?.({
1035
- type: "s3Progress",
1036
- loaded: event.loaded,
1037
- total: event.total,
1038
- percent
1039
- });
1040
- }
1041
- };
1042
- xhr.onload = () => {
1043
- if (xhr.status >= 200 && xhr.status < 300)
1044
- resolve();
1045
- else
1046
- reject(new Error(`File upload failed: ${xhr.status} ${xhr.statusText}`));
1047
- };
1048
- xhr.onerror = () => reject(new Error("File upload failed: network error"));
1049
- xhr.send(file);
1158
+ deploy: {
1159
+ frontend: async (slug, metadata, file, hooks) => {
1160
+ hooks?.onEvent?.({ type: "init" });
1161
+ const game = await client["request"](`/games/${slug}`, "PUT", metadata);
1162
+ if (metadata.gameType === "external" || file === null) {
1163
+ return game;
1164
+ }
1165
+ const fileName = file instanceof File ? file.name : "game.zip";
1166
+ const initiateResponse = await client["request"]("/games/uploads/initiate/", "POST", {
1167
+ fileName,
1168
+ gameId: game.id
1050
1169
  });
1051
- } else {
1052
- const uploadResponse = await fetch(initiateResponse.presignedUrl, {
1053
- method: "PUT",
1054
- body: file,
1055
- headers: {
1056
- "Content-Type": file.type || "application/octet-stream"
1170
+ if (hooks?.onEvent && typeof XMLHttpRequest !== "undefined") {
1171
+ await new Promise((resolve, reject) => {
1172
+ const xhr = new XMLHttpRequest;
1173
+ xhr.open("PUT", initiateResponse.presignedUrl, true);
1174
+ const contentType = file.type || "application/octet-stream";
1175
+ try {
1176
+ xhr.setRequestHeader("Content-Type", contentType);
1177
+ } catch {}
1178
+ xhr.upload.onprogress = (event) => {
1179
+ if (event.lengthComputable) {
1180
+ const percent = event.loaded / event.total;
1181
+ hooks.onEvent?.({
1182
+ type: "s3Progress",
1183
+ loaded: event.loaded,
1184
+ total: event.total,
1185
+ percent
1186
+ });
1187
+ }
1188
+ };
1189
+ xhr.onload = () => {
1190
+ if (xhr.status >= 200 && xhr.status < 300)
1191
+ resolve();
1192
+ else
1193
+ reject(new Error(`File upload failed: ${xhr.status} ${xhr.statusText}`));
1194
+ };
1195
+ xhr.onerror = () => reject(new Error("File upload failed: network error"));
1196
+ xhr.send(file);
1197
+ });
1198
+ } else {
1199
+ const uploadResponse = await fetch(initiateResponse.presignedUrl, {
1200
+ method: "PUT",
1201
+ body: file,
1202
+ headers: {
1203
+ "Content-Type": file.type || "application/octet-stream"
1204
+ }
1205
+ });
1206
+ if (!uploadResponse.ok) {
1207
+ throw new Error(`File upload failed: ${uploadResponse.status} ${uploadResponse.statusText}`);
1057
1208
  }
1209
+ }
1210
+ const finalizeUrl = `${client.baseUrl}/games/uploads/finalize/`;
1211
+ const authToken = client.getToken();
1212
+ const tokenType = client.getTokenType();
1213
+ const headers = {
1214
+ "Content-Type": "application/json"
1215
+ };
1216
+ if (authToken) {
1217
+ if (tokenType === "apiKey") {
1218
+ headers["x-api-key"] = authToken;
1219
+ } else {
1220
+ headers["Authorization"] = `Bearer ${authToken}`;
1221
+ }
1222
+ }
1223
+ const finalizeResponse = await fetch(finalizeUrl, {
1224
+ method: "POST",
1225
+ headers,
1226
+ body: JSON.stringify({
1227
+ tempS3Key: initiateResponse.tempS3Key,
1228
+ gameId: initiateResponse.gameId,
1229
+ version: initiateResponse.version,
1230
+ slug,
1231
+ metadata,
1232
+ originalFileName: fileName
1233
+ }),
1234
+ credentials: "omit"
1058
1235
  });
1059
- if (!uploadResponse.ok) {
1060
- throw new Error(`File upload failed: ${uploadResponse.status} ${uploadResponse.statusText}`);
1236
+ if (!finalizeResponse.ok) {
1237
+ const errText = await finalizeResponse.text().catch(() => "");
1238
+ throw new Error(`Finalize request failed: ${finalizeResponse.status} ${finalizeResponse.statusText}${errText ? ` - ${errText}` : ""}`);
1061
1239
  }
1062
- }
1063
- const baseUrl = (() => {
1064
- const anyClient = client;
1065
- try {
1066
- return typeof anyClient.getBaseUrl === "function" ? anyClient.getBaseUrl() : "/api";
1067
- } catch {
1068
- return "/api";
1240
+ if (!finalizeResponse.body) {
1241
+ throw new Error("Finalize response body missing");
1069
1242
  }
1070
- })();
1071
- const finalizeUrl = baseUrl.replace(/\/$/, "") + "/games/uploads/finalize/";
1072
- const authToken = client.token;
1073
- const finalizeResponse = await fetch(finalizeUrl, {
1074
- method: "POST",
1075
- headers: {
1076
- "Content-Type": "application/json",
1077
- ...authToken ? { Authorization: `Bearer ${authToken}` } : {}
1078
- },
1079
- body: JSON.stringify({
1080
- tempS3Key: initiateResponse.tempS3Key,
1081
- gameId: initiateResponse.gameId,
1082
- version: initiateResponse.version,
1083
- slug,
1084
- metadata,
1085
- originalFileName: fileName
1086
- }),
1087
- credentials: "omit"
1088
- });
1089
- if (!finalizeResponse.ok) {
1090
- const errText = await finalizeResponse.text().catch(() => "");
1091
- throw new Error(`Finalize request failed: ${finalizeResponse.status} ${finalizeResponse.statusText}${errText ? ` - ${errText}` : ""}`);
1092
- }
1093
- if (!finalizeResponse.body) {
1094
- throw new Error("Finalize response body missing");
1095
- }
1096
- hooks?.onEvent?.({ type: "finalizeStart" });
1097
- let sawAnyServerEvent = false;
1098
- const reader = finalizeResponse.body.pipeThrough(new TextDecoderStream).getReader();
1099
- let buffer = "";
1100
- while (true) {
1101
- const { done, value } = await reader.read();
1102
- if (done) {
1103
- if (!sawAnyServerEvent) {
1104
- hooks?.onClose?.();
1105
- hooks?.onEvent?.({ type: "close" });
1243
+ hooks?.onEvent?.({ type: "finalizeStart" });
1244
+ let sawAnyServerEvent = false;
1245
+ const reader = finalizeResponse.body.pipeThrough(new TextDecoderStream).getReader();
1246
+ let buffer = "";
1247
+ while (true) {
1248
+ const { done, value } = await reader.read();
1249
+ if (done) {
1250
+ if (!sawAnyServerEvent) {
1251
+ hooks?.onClose?.();
1252
+ hooks?.onEvent?.({ type: "close" });
1253
+ }
1254
+ break;
1106
1255
  }
1107
- break;
1108
- }
1109
- buffer += value;
1110
- let eolIndex;
1111
- while ((eolIndex = buffer.indexOf(`
1256
+ buffer += value;
1257
+ let eolIndex;
1258
+ while ((eolIndex = buffer.indexOf(`
1112
1259
 
1113
1260
  `)) >= 0) {
1114
- const message = buffer.slice(0, eolIndex);
1115
- buffer = buffer.slice(eolIndex + 2);
1116
- const eventLine = message.match(/^event: (.*)$/m);
1117
- const dataLine = message.match(/^data: (.*)$/m);
1118
- if (eventLine && dataLine) {
1119
- const eventType = eventLine[1];
1120
- const eventData = JSON.parse(dataLine[1]);
1121
- if (eventType === "uploadProgress") {
1122
- sawAnyServerEvent = true;
1123
- const percent = (eventData.value ?? 0) / 100;
1124
- hooks?.onEvent?.({
1125
- type: "finalizeProgress",
1126
- percent,
1127
- currentFileLabel: eventData.currentFileLabel || ""
1128
- });
1129
- } else if (eventType === "status") {
1130
- sawAnyServerEvent = true;
1131
- if (eventData.message) {
1261
+ const message = buffer.slice(0, eolIndex);
1262
+ buffer = buffer.slice(eolIndex + 2);
1263
+ const eventLine = message.match(/^event: (.*)$/m);
1264
+ const dataLine = message.match(/^data: (.*)$/m);
1265
+ if (eventLine && dataLine) {
1266
+ const eventType = eventLine[1];
1267
+ const eventData = JSON.parse(dataLine[1]);
1268
+ if (eventType === "uploadProgress") {
1269
+ sawAnyServerEvent = true;
1270
+ const percent = (eventData.value ?? 0) / 100;
1132
1271
  hooks?.onEvent?.({
1133
- type: "finalizeStatus",
1134
- message: eventData.message
1272
+ type: "finalizeProgress",
1273
+ percent,
1274
+ currentFileLabel: eventData.currentFileLabel || ""
1135
1275
  });
1276
+ } else if (eventType === "status") {
1277
+ sawAnyServerEvent = true;
1278
+ if (eventData.message) {
1279
+ hooks?.onEvent?.({
1280
+ type: "finalizeStatus",
1281
+ message: eventData.message
1282
+ });
1283
+ }
1284
+ } else if (eventType === "complete") {
1285
+ sawAnyServerEvent = true;
1286
+ reader.cancel();
1287
+ return eventData;
1288
+ } else if (eventType === "error") {
1289
+ sawAnyServerEvent = true;
1290
+ reader.cancel();
1291
+ throw new Error(eventData.message);
1136
1292
  }
1137
- } else if (eventType === "complete") {
1138
- sawAnyServerEvent = true;
1139
- reader.cancel();
1140
- return eventData;
1141
- } else if (eventType === "error") {
1142
- sawAnyServerEvent = true;
1143
- reader.cancel();
1144
- throw new Error(eventData.message);
1145
1293
  }
1146
1294
  }
1147
1295
  }
1296
+ throw new Error("Upload completed but no final game data received");
1297
+ },
1298
+ backend: async (slug, bundle) => {
1299
+ return client["request"](`/games/${slug}/backend/deploy`, "POST", bundle);
1148
1300
  }
1149
- throw new Error("Upload completed but no final game data received");
1150
1301
  },
1151
- update: (gameId, props) => client["request"](`/games/${gameId}`, "PATCH", props),
1302
+ upsert: async (slug, metadata) => {
1303
+ return client.dev.games.deploy.frontend(slug, metadata, null);
1304
+ },
1152
1305
  delete: (gameId) => client["request"](`/games/${gameId}`, "DELETE")
1153
1306
  },
1154
- keys: {
1155
- create: (label) => client["request"](`/dev/keys`, "POST", { label }),
1156
- list: () => client["request"](`/dev/keys`, "GET"),
1157
- revoke: (keyId) => client["request"](`/dev/keys/${keyId}`, "DELETE")
1158
- },
1159
1307
  items: {
1160
1308
  create: (gameId, slug, itemData) => client["request"](`/games/${gameId}/items`, "POST", {
1161
1309
  slug,
@@ -1191,9 +1339,17 @@ function createDevNamespace(client) {
1191
1339
 
1192
1340
  // src/core/namespaces/maps.ts
1193
1341
  function createMapsNamespace(client) {
1342
+ const mapDataCache = createTTLCache({
1343
+ ttl: 5 * 60 * 1000,
1344
+ keyPrefix: "maps.data"
1345
+ });
1346
+ const mapElementsCache = createTTLCache({
1347
+ ttl: 60 * 1000,
1348
+ keyPrefix: "maps.elements"
1349
+ });
1194
1350
  return {
1195
- get: (identifier) => client["request"](`/maps/${identifier}`, "GET"),
1196
- elements: (mapId) => client["request"](`/map/elements?mapId=${mapId}`, "GET"),
1351
+ get: (identifier, options) => mapDataCache.get(identifier, () => client["request"](`/maps/${identifier}`, "GET"), options),
1352
+ elements: (mapId, options) => mapElementsCache.get(mapId, () => client["request"](`/map/elements?mapId=${mapId}`, "GET"), options),
1197
1353
  objects: {
1198
1354
  list: (mapId) => client["request"](`/maps/${mapId}/objects`, "GET"),
1199
1355
  create: (mapId, objectData) => client["request"](`/maps/${mapId}/objects`, "POST", objectData),
@@ -1201,6 +1357,7 @@ function createMapsNamespace(client) {
1201
1357
  }
1202
1358
  };
1203
1359
  }
1360
+ var init_maps = () => {};
1204
1361
 
1205
1362
  // src/core/namespaces/admin.ts
1206
1363
  function createAdminNamespace(client) {
@@ -1320,6 +1477,7 @@ var init_levels = () => {};
1320
1477
 
1321
1478
  // ../data/src/domains/game/table.ts
1322
1479
  import {
1480
+ boolean,
1323
1481
  jsonb,
1324
1482
  pgEnum,
1325
1483
  pgTable,
@@ -1329,7 +1487,7 @@ import {
1329
1487
  uuid,
1330
1488
  varchar
1331
1489
  } from "drizzle-orm/pg-core";
1332
- var gamePlatformEnum, gameBootModeEnum, gameTypeEnum, games, gameSessions, gameStates;
1490
+ var gamePlatformEnum, gameBootModeEnum, gameTypeEnum, games, gameSessions, gameStates, deploymentProviderEnum, gameBackendDeployments;
1333
1491
  var init_table = __esm(() => {
1334
1492
  init_table3();
1335
1493
  init_table4();
@@ -1368,12 +1526,23 @@ var init_table = __esm(() => {
1368
1526
  data: jsonb("data").default("{}"),
1369
1527
  updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow()
1370
1528
  }, (table) => [uniqueIndex("unique_user_game_idx").on(table.userId, table.gameId)]);
1529
+ deploymentProviderEnum = pgEnum("deployment_provider", ["cloudflare", "aws"]);
1530
+ gameBackendDeployments = pgTable("game_backend_deployments", {
1531
+ id: uuid("id").primaryKey().defaultRandom(),
1532
+ gameId: uuid("game_id").notNull().references(() => games.id, { onDelete: "cascade" }),
1533
+ deploymentId: text("deployment_id").notNull(),
1534
+ provider: deploymentProviderEnum("provider").notNull(),
1535
+ url: text("url").notNull(),
1536
+ codeHash: text("code_hash"),
1537
+ isActive: boolean("is_active").notNull().default(false),
1538
+ deployedAt: timestamp("deployed_at", { withTimezone: true }).notNull().defaultNow()
1539
+ });
1371
1540
  });
1372
1541
 
1373
1542
  // ../data/src/domains/inventory/table.ts
1374
1543
  import { relations, sql } from "drizzle-orm";
1375
1544
  import {
1376
- boolean,
1545
+ boolean as boolean2,
1377
1546
  integer,
1378
1547
  jsonb as jsonb2,
1379
1548
  pgEnum as pgEnum2,
@@ -1408,7 +1577,7 @@ var init_table2 = __esm(() => {
1408
1577
  displayName: text2("display_name").notNull(),
1409
1578
  description: text2("description"),
1410
1579
  type: itemTypeEnum("type").notNull().default("other"),
1411
- isPlaceable: boolean("is_placeable").default(false).notNull(),
1580
+ isPlaceable: boolean2("is_placeable").default(false).notNull(),
1412
1581
  imageUrl: text2("image_url"),
1413
1582
  metadata: jsonb2("metadata").default({}),
1414
1583
  createdAt: timestamp2("created_at").defaultNow().notNull()
@@ -1427,7 +1596,7 @@ var init_table2 = __esm(() => {
1427
1596
  id: uuid2("id").primaryKey().defaultRandom(),
1428
1597
  itemId: uuid2("item_id").notNull().references(() => items.id, { onDelete: "cascade" }),
1429
1598
  symbol: text2("symbol"),
1430
- isPrimary: boolean("is_primary").default(false).notNull(),
1599
+ isPrimary: boolean2("is_primary").default(false).notNull(),
1431
1600
  createdAt: timestamp2("created_at").defaultNow().notNull(),
1432
1601
  updatedAt: timestamp2("updated_at", { withTimezone: true }).defaultNow().$onUpdate(() => new Date)
1433
1602
  }, (table) => [uniqueIndex2("currency_item_id_idx").on(table.itemId)]);
@@ -1438,7 +1607,7 @@ var init_table2 = __esm(() => {
1438
1607
  price: integer("price").notNull(),
1439
1608
  sellBackPercentage: integer("sell_back_percentage"),
1440
1609
  stock: integer("stock"),
1441
- isActive: boolean("is_active").default(true).notNull(),
1610
+ isActive: boolean2("is_active").default(true).notNull(),
1442
1611
  availableFrom: timestamp2("available_from", { withTimezone: true }),
1443
1612
  availableUntil: timestamp2("available_until", { withTimezone: true }),
1444
1613
  createdAt: timestamp2("created_at").defaultNow().notNull(),
@@ -1572,7 +1741,7 @@ var init_table3 = __esm(() => {
1572
1741
 
1573
1742
  // ../data/src/domains/user/table.ts
1574
1743
  import { relations as relations3 } from "drizzle-orm";
1575
- import { boolean as boolean2, pgEnum as pgEnum4, pgTable as pgTable4, text as text4, timestamp as timestamp4, uniqueIndex as uniqueIndex4 } from "drizzle-orm/pg-core";
1744
+ import { boolean as boolean3, pgEnum as pgEnum4, pgTable as pgTable4, text as text4, timestamp as timestamp4, uniqueIndex as uniqueIndex4 } from "drizzle-orm/pg-core";
1576
1745
  var userRoleEnum, developerStatusEnum, users, accounts, sessions, verification, ssoProvider, usersRelations;
1577
1746
  var init_table4 = __esm(() => {
1578
1747
  init_table3();
@@ -1584,11 +1753,11 @@ var init_table4 = __esm(() => {
1584
1753
  username: text4("username").unique(),
1585
1754
  email: text4("email").notNull().unique(),
1586
1755
  timebackId: text4("timeback_id").unique(),
1587
- emailVerified: boolean2("email_verified").notNull().default(false),
1756
+ emailVerified: boolean3("email_verified").notNull().default(false),
1588
1757
  image: text4("image"),
1589
1758
  role: userRoleEnum("role").notNull().default("player"),
1590
1759
  developerStatus: developerStatusEnum("developer_status").notNull().default("none"),
1591
- characterCreated: boolean2("character_created").notNull().default(false),
1760
+ characterCreated: boolean3("character_created").notNull().default(false),
1592
1761
  createdAt: timestamp4("created_at", {
1593
1762
  mode: "date",
1594
1763
  withTimezone: true
@@ -1677,16 +1846,32 @@ var init_table4 = __esm(() => {
1677
1846
  });
1678
1847
 
1679
1848
  // ../data/src/domains/developer/table.ts
1680
- import { pgTable as pgTable5, text as text5, timestamp as timestamp5, uuid as uuid4, varchar as varchar3 } from "drizzle-orm/pg-core";
1681
- var developerKeys;
1849
+ import { boolean as boolean4, integer as integer3, pgTable as pgTable5, text as text5, timestamp as timestamp5, uuid as uuid4 } from "drizzle-orm/pg-core";
1850
+ var apikey;
1682
1851
  var init_table5 = __esm(() => {
1683
1852
  init_table4();
1684
- developerKeys = pgTable5("developer_keys", {
1853
+ apikey = pgTable5("api_key", {
1685
1854
  id: uuid4("id").primaryKey().defaultRandom(),
1855
+ name: text5("name"),
1856
+ start: text5("start"),
1857
+ prefix: text5("prefix"),
1858
+ key: text5("key").notNull(),
1686
1859
  userId: text5("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
1687
- label: varchar3("label", { length: 255 }),
1688
- keyHash: text5("key_hash").notNull().unique(),
1689
- createdAt: timestamp5("created_at", { withTimezone: true }).notNull().defaultNow()
1860
+ refillInterval: integer3("refill_interval"),
1861
+ refillAmount: integer3("refill_amount"),
1862
+ lastRefillAt: timestamp5("last_refill_at", { withTimezone: true }),
1863
+ enabled: boolean4("enabled").notNull().default(true),
1864
+ rateLimitEnabled: boolean4("rate_limit_enabled").notNull().default(false),
1865
+ rateLimitTimeWindow: integer3("rate_limit_time_window"),
1866
+ rateLimitMax: integer3("rate_limit_max"),
1867
+ requestCount: integer3("request_count").notNull().default(0),
1868
+ remaining: integer3("remaining"),
1869
+ lastRequest: timestamp5("last_request", { withTimezone: true }),
1870
+ expiresAt: timestamp5("expires_at", { withTimezone: true }),
1871
+ createdAt: timestamp5("created_at", { withTimezone: true }).notNull().defaultNow(),
1872
+ updatedAt: timestamp5("updated_at", { withTimezone: true }).notNull().defaultNow(),
1873
+ permissions: text5("permissions"),
1874
+ metadata: text5("metadata")
1690
1875
  });
1691
1876
  });
1692
1877
 
@@ -1694,7 +1879,7 @@ var init_table5 = __esm(() => {
1694
1879
  import { relations as relations4 } from "drizzle-orm";
1695
1880
  import {
1696
1881
  doublePrecision as doublePrecision2,
1697
- integer as integer3,
1882
+ integer as integer4,
1698
1883
  pgTable as pgTable6,
1699
1884
  text as text6,
1700
1885
  timestamp as timestamp6,
@@ -1706,7 +1891,7 @@ var init_table6 = __esm(() => {
1706
1891
  init_table4();
1707
1892
  userLevels = pgTable6("user_levels", {
1708
1893
  userId: text6("user_id").primaryKey().references(() => users.id, { onDelete: "cascade" }),
1709
- currentLevel: integer3("current_level").notNull().default(1),
1894
+ currentLevel: integer4("current_level").notNull().default(1),
1710
1895
  currentXp: doublePrecision2("current_xp").notNull().default(0),
1711
1896
  totalXP: doublePrecision2("total_xp").notNull().default(0),
1712
1897
  lastLevelUpAt: timestamp6("last_level_up_at", { withTimezone: true }),
@@ -1715,9 +1900,9 @@ var init_table6 = __esm(() => {
1715
1900
  });
1716
1901
  levelConfigs = pgTable6("level_configs", {
1717
1902
  id: uuid5("id").primaryKey().defaultRandom(),
1718
- level: integer3("level").notNull().unique(),
1719
- xpRequired: integer3("xp_required").notNull(),
1720
- creditsReward: integer3("credits_reward").notNull().default(0),
1903
+ level: integer4("level").notNull().unique(),
1904
+ xpRequired: integer4("xp_required").notNull(),
1905
+ creditsReward: integer4("credits_reward").notNull().default(0),
1721
1906
  createdAt: timestamp6("created_at").defaultNow().notNull()
1722
1907
  }, (table) => [uniqueIndex5("unique_level_config_idx").on(table.level)]);
1723
1908
  userLevelsRelations = relations4(userLevels, ({ one }) => ({
@@ -1730,7 +1915,7 @@ var init_table6 = __esm(() => {
1730
1915
 
1731
1916
  // ../data/src/domains/leaderboard/table.ts
1732
1917
  import { relations as relations5 } from "drizzle-orm";
1733
- import { index as index2, integer as integer4, jsonb as jsonb4, pgTable as pgTable7, text as text7, timestamp as timestamp7, uuid as uuid6 } from "drizzle-orm/pg-core";
1918
+ import { index as index2, integer as integer5, jsonb as jsonb4, pgTable as pgTable7, text as text7, timestamp as timestamp7, uuid as uuid6 } from "drizzle-orm/pg-core";
1734
1919
  var gameScores, gameScoresRelations;
1735
1920
  var init_table7 = __esm(() => {
1736
1921
  init_table();
@@ -1739,7 +1924,7 @@ var init_table7 = __esm(() => {
1739
1924
  id: uuid6("id").primaryKey().defaultRandom(),
1740
1925
  userId: text7("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
1741
1926
  gameId: uuid6("game_id").notNull().references(() => games.id, { onDelete: "cascade" }),
1742
- score: integer4("score").notNull(),
1927
+ score: integer5("score").notNull(),
1743
1928
  metadata: jsonb4("metadata").default("{}"),
1744
1929
  achievedAt: timestamp7("achieved_at", { withTimezone: true }).defaultNow().notNull(),
1745
1930
  sessionId: uuid6("session_id").references(() => gameSessions.id, { onDelete: "set null" })
@@ -1766,22 +1951,22 @@ var init_table7 = __esm(() => {
1766
1951
 
1767
1952
  // ../data/src/domains/sprite/table.ts
1768
1953
  import { relations as relations6 } from "drizzle-orm";
1769
- import { integer as integer5, pgTable as pgTable8, timestamp as timestamp8, uuid as uuid7, varchar as varchar4 } from "drizzle-orm/pg-core";
1954
+ import { integer as integer6, pgTable as pgTable8, timestamp as timestamp8, uuid as uuid7, varchar as varchar3 } from "drizzle-orm/pg-core";
1770
1955
  var spriteTemplates, spriteSheets, spriteTemplatesRelations, spriteSheetsRelations;
1771
1956
  var init_table8 = __esm(() => {
1772
1957
  spriteTemplates = pgTable8("sprite_templates", {
1773
1958
  id: uuid7("id").primaryKey().defaultRandom(),
1774
- slug: varchar4("slug", { length: 64 }).notNull().unique(),
1775
- url: varchar4("url", { length: 255 }).notNull(),
1959
+ slug: varchar3("slug", { length: 64 }).notNull().unique(),
1960
+ url: varchar3("url", { length: 255 }).notNull(),
1776
1961
  createdAt: timestamp8("created_at", { withTimezone: true }).notNull().defaultNow(),
1777
1962
  updatedAt: timestamp8("updated_at", { withTimezone: true }).notNull().defaultNow()
1778
1963
  });
1779
1964
  spriteSheets = pgTable8("sprite_sheets", {
1780
1965
  id: uuid7("id").primaryKey().defaultRandom(),
1781
1966
  templateId: uuid7("template_id").notNull().references(() => spriteTemplates.id, { onDelete: "cascade" }),
1782
- width: integer5("width").notNull(),
1783
- height: integer5("height").notNull(),
1784
- url: varchar4("url", { length: 255 }).notNull(),
1967
+ width: integer6("width").notNull(),
1968
+ height: integer6("height").notNull(),
1969
+ url: varchar3("url", { length: 255 }).notNull(),
1785
1970
  createdAt: timestamp8("created_at", { withTimezone: true }).notNull().defaultNow(),
1786
1971
  updatedAt: timestamp8("updated_at", { withTimezone: true }).notNull().defaultNow()
1787
1972
  });
@@ -1799,14 +1984,14 @@ var init_table8 = __esm(() => {
1799
1984
  // ../data/src/domains/character/table.ts
1800
1985
  import { relations as relations7 } from "drizzle-orm";
1801
1986
  import {
1802
- integer as integer6,
1987
+ integer as integer7,
1803
1988
  pgEnum as pgEnum5,
1804
1989
  pgTable as pgTable9,
1805
1990
  text as text8,
1806
1991
  timestamp as timestamp9,
1807
1992
  uniqueIndex as uniqueIndex6,
1808
1993
  uuid as uuid8,
1809
- varchar as varchar5
1994
+ varchar as varchar4
1810
1995
  } from "drizzle-orm/pg-core";
1811
1996
  var characterComponentTypeEnum, characterComponents, playerCharacters, playerCharacterAccessories, characterComponentsRelations, playerCharactersRelations, playerCharacterAccessoriesRelations;
1812
1997
  var init_table9 = __esm(() => {
@@ -1822,12 +2007,12 @@ var init_table9 = __esm(() => {
1822
2007
  characterComponents = pgTable9("character_components", {
1823
2008
  id: uuid8("id").primaryKey().defaultRandom(),
1824
2009
  componentType: characterComponentTypeEnum("component_type").notNull(),
1825
- slug: varchar5("slug", { length: 128 }).notNull().unique(),
1826
- displayName: varchar5("display_name", { length: 128 }).notNull(),
1827
- slot: varchar5("slot", { length: 64 }).notNull(),
2010
+ slug: varchar4("slug", { length: 128 }).notNull().unique(),
2011
+ displayName: varchar4("display_name", { length: 128 }).notNull(),
2012
+ slot: varchar4("slot", { length: 64 }).notNull(),
1828
2013
  spriteSheetId: uuid8("sprite_sheet_id").notNull().references(() => spriteSheets.id, { onDelete: "cascade" }),
1829
- unlockLevel: integer6("unlock_level").notNull().default(0),
1830
- variant: integer6("variant").notNull().default(0),
2014
+ unlockLevel: integer7("unlock_level").notNull().default(0),
2015
+ variant: integer7("variant").notNull().default(0),
1831
2016
  createdAt: timestamp9("created_at", { withTimezone: true }).notNull().defaultNow(),
1832
2017
  updatedAt: timestamp9("updated_at", { withTimezone: true }).notNull().defaultNow()
1833
2018
  });
@@ -1845,7 +2030,7 @@ var init_table9 = __esm(() => {
1845
2030
  id: uuid8("id").primaryKey().defaultRandom(),
1846
2031
  playerCharacterId: uuid8("player_character_id").notNull().references(() => playerCharacters.id, { onDelete: "cascade" }),
1847
2032
  accessoryComponentId: uuid8("accessory_component_id").notNull().references(() => characterComponents.id, { onDelete: "cascade" }),
1848
- slot: varchar5("slot", { length: 64 }).notNull(),
2033
+ slot: varchar4("slot", { length: 64 }).notNull(),
1849
2034
  equippedAt: timestamp9("equipped_at", { withTimezone: true }).notNull().defaultNow(),
1850
2035
  updatedAt: timestamp9("updated_at", { withTimezone: true }).notNull().defaultNow()
1851
2036
  }, (table) => [
@@ -1895,8 +2080,9 @@ var init_table9 = __esm(() => {
1895
2080
 
1896
2081
  // ../data/src/domains/timeback/table.ts
1897
2082
  import { doublePrecision as doublePrecision3, pgTable as pgTable10, text as text9, timestamp as timestamp10, uniqueIndex as uniqueIndex7, uuid as uuid9 } from "drizzle-orm/pg-core";
1898
- var timebackDailyXp, timebackXpEvents;
2083
+ var timebackDailyXp, timebackXpEvents, gameTimebackIntegrations;
1899
2084
  var init_table10 = __esm(() => {
2085
+ init_table();
1900
2086
  init_table4();
1901
2087
  timebackDailyXp = pgTable10("timeback_daily_xp", {
1902
2088
  userId: text9("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
@@ -1917,14 +2103,22 @@ var init_table10 = __esm(() => {
1917
2103
  createdAt: timestamp10("created_at", { withTimezone: true }).notNull().defaultNow(),
1918
2104
  updatedAt: timestamp10("updated_at", { withTimezone: true }).notNull().defaultNow()
1919
2105
  }, (table) => [uniqueIndex7("timeback_xp_events_source_id_idx").on(table.source, table.sourceId)]);
2106
+ gameTimebackIntegrations = pgTable10("game_timeback_integrations", {
2107
+ id: uuid9("id").primaryKey().defaultRandom(),
2108
+ gameId: uuid9("game_id").notNull().unique().references(() => games.id, { onDelete: "cascade" }),
2109
+ courseId: text9("course_id").notNull(),
2110
+ lastVerifiedAt: timestamp10("last_verified_at", { withTimezone: true }),
2111
+ createdAt: timestamp10("created_at", { withTimezone: true }).notNull().defaultNow(),
2112
+ updatedAt: timestamp10("updated_at", { withTimezone: true }).notNull().defaultNow()
2113
+ });
1920
2114
  });
1921
2115
 
1922
2116
  // ../data/src/domains/achievement/table.ts
1923
2117
  import { relations as relations8 } from "drizzle-orm";
1924
2118
  import {
1925
- boolean as boolean3,
2119
+ boolean as boolean5,
1926
2120
  index as index3,
1927
- integer as integer7,
2121
+ integer as integer8,
1928
2122
  jsonb as jsonb5,
1929
2123
  pgEnum as pgEnum6,
1930
2124
  pgTable as pgTable11,
@@ -1932,45 +2126,55 @@ import {
1932
2126
  timestamp as timestamp11,
1933
2127
  uniqueIndex as uniqueIndex8,
1934
2128
  uuid as uuid10,
1935
- varchar as varchar6
2129
+ varchar as varchar5
1936
2130
  } from "drizzle-orm/pg-core";
1937
- var achievementIntervalEnum, achievements, userAchievementProgress, userAchievementClaims, userAchievementProgressRelations, userAchievementClaimsRelations;
2131
+ var achievementScopeEnum, achievements, userAchievementProgress, userAchievementClaims, userAchievementProgressRelations, userAchievementClaimsRelations;
1938
2132
  var init_table11 = __esm(() => {
1939
2133
  init_table4();
1940
- achievementIntervalEnum = pgEnum6("achievement_interval", ["daily", "weekly"]);
2134
+ achievementScopeEnum = pgEnum6("achievement_scope", [
2135
+ "daily",
2136
+ "weekly",
2137
+ "monthly",
2138
+ "yearly",
2139
+ "game",
2140
+ "global",
2141
+ "map",
2142
+ "level",
2143
+ "event"
2144
+ ]);
1941
2145
  achievements = pgTable11("achievements", {
1942
- id: varchar6("id", { length: 255 }).primaryKey(),
1943
- title: varchar6("title", { length: 255 }).notNull(),
2146
+ id: varchar5("id", { length: 255 }).primaryKey(),
2147
+ title: varchar5("title", { length: 255 }).notNull(),
1944
2148
  description: text10("description"),
1945
- intervalType: achievementIntervalEnum("interval_type").notNull(),
1946
- rewardCredits: integer7("reward_credits").notNull().default(0),
1947
- limitPerInterval: integer7("limit_per_interval").notNull().default(1),
1948
- completionType: varchar6("completion_type", { length: 50 }).notNull(),
2149
+ scope: achievementScopeEnum("scope").notNull(),
2150
+ rewardCredits: integer8("reward_credits").notNull().default(0),
2151
+ limit: integer8("limit").notNull().default(1),
2152
+ completionType: varchar5("completion_type", { length: 50 }).notNull(),
1949
2153
  completionConfig: jsonb5("completion_config").notNull().default({}),
1950
- scope: jsonb5("scope").notNull().default({}),
1951
- active: boolean3("active").notNull().default(true),
2154
+ target: jsonb5("target").notNull().default({}),
2155
+ active: boolean5("active").notNull().default(true),
1952
2156
  createdAt: timestamp11("created_at", { withTimezone: true }).defaultNow(),
1953
2157
  updatedAt: timestamp11("updated_at", { withTimezone: true }).defaultNow()
1954
2158
  });
1955
2159
  userAchievementProgress = pgTable11("user_achievement_progress", {
1956
2160
  id: uuid10("id").primaryKey().defaultRandom(),
1957
2161
  userId: text10("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
1958
- achievementId: varchar6("achievement_id", { length: 255 }).notNull().references(() => achievements.id, { onDelete: "cascade" }),
1959
- intervalKey: text10("interval_key").notNull(),
2162
+ achievementId: varchar5("achievement_id", { length: 255 }).notNull().references(() => achievements.id, { onDelete: "cascade" }),
2163
+ scopeKey: text10("scope_key").notNull(),
1960
2164
  progress: jsonb5("progress").notNull().default({}),
1961
2165
  updatedAt: timestamp11("updated_at", { withTimezone: true }).defaultNow().notNull()
1962
2166
  }, (table) => [
1963
- index3("user_achievement_progress_idx").on(table.userId, table.achievementId, table.intervalKey)
2167
+ index3("user_achievement_progress_idx").on(table.userId, table.achievementId, table.scopeKey)
1964
2168
  ]);
1965
2169
  userAchievementClaims = pgTable11("user_achievement_claims", {
1966
2170
  id: uuid10("id").primaryKey().defaultRandom(),
1967
2171
  userId: text10("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
1968
- achievementId: varchar6("achievement_id", { length: 255 }).notNull().references(() => achievements.id, { onDelete: "cascade" }),
1969
- intervalKey: text10("interval_key").notNull(),
1970
- rewardCredits: integer7("reward_credits").notNull(),
2172
+ achievementId: varchar5("achievement_id", { length: 255 }).notNull().references(() => achievements.id, { onDelete: "cascade" }),
2173
+ scopeKey: text10("scope_key").notNull(),
2174
+ rewardCredits: integer8("reward_credits").notNull(),
1971
2175
  createdAt: timestamp11("created_at", { withTimezone: true }).defaultNow().notNull()
1972
2176
  }, (table) => [
1973
- uniqueIndex8("user_achievement_claims_unique").on(table.userId, table.achievementId, table.intervalKey)
2177
+ uniqueIndex8("user_achievement_claims_unique").on(table.userId, table.achievementId, table.scopeKey)
1974
2178
  ]);
1975
2179
  userAchievementProgressRelations = relations8(userAchievementProgress, ({ one }) => ({
1976
2180
  user: one(users, {
@@ -1994,6 +2198,58 @@ var init_table11 = __esm(() => {
1994
2198
  }));
1995
2199
  });
1996
2200
 
2201
+ // ../data/src/domains/notification/table.ts
2202
+ import { relations as relations9 } from "drizzle-orm";
2203
+ import { index as index4, jsonb as jsonb6, pgEnum as pgEnum7, pgTable as pgTable12, text as text11, timestamp as timestamp12, uuid as uuid11, varchar as varchar6 } from "drizzle-orm/pg-core";
2204
+ var notificationPriorityEnum, notificationStatusEnum, notifications, notificationsRelations;
2205
+ var init_table12 = __esm(() => {
2206
+ init_table4();
2207
+ notificationPriorityEnum = pgEnum7("notification_priority", [
2208
+ "low",
2209
+ "normal",
2210
+ "high",
2211
+ "urgent"
2212
+ ]);
2213
+ notificationStatusEnum = pgEnum7("notification_status", [
2214
+ "pending",
2215
+ "delivered",
2216
+ "seen",
2217
+ "clicked",
2218
+ "dismissed",
2219
+ "expired"
2220
+ ]);
2221
+ notifications = pgTable12("notifications", {
2222
+ id: uuid11("id").primaryKey().defaultRandom(),
2223
+ userId: text11("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
2224
+ type: varchar6("type", { length: 50 }).notNull(),
2225
+ title: varchar6("title", { length: 255 }).notNull(),
2226
+ message: text11("message").notNull(),
2227
+ data: jsonb6("data").notNull().default({}),
2228
+ priority: notificationPriorityEnum("priority").notNull().default("normal"),
2229
+ status: notificationStatusEnum("status").notNull().default("pending"),
2230
+ createdAt: timestamp12("created_at", { withTimezone: true }).defaultNow().notNull(),
2231
+ deliveredAt: timestamp12("delivered_at", { withTimezone: true }),
2232
+ seenAt: timestamp12("seen_at", { withTimezone: true }),
2233
+ clickedAt: timestamp12("clicked_at", { withTimezone: true }),
2234
+ expiresAt: timestamp12("expires_at", { withTimezone: true }),
2235
+ method: varchar6("method", { length: 50 }),
2236
+ clickUrl: text11("click_url"),
2237
+ metadata: jsonb6("metadata").notNull().default({})
2238
+ }, (table) => [
2239
+ index4("notifications_user_id_idx").on(table.userId),
2240
+ index4("notifications_status_idx").on(table.status),
2241
+ index4("notifications_type_idx").on(table.type),
2242
+ index4("notifications_created_at_idx").on(table.createdAt),
2243
+ index4("notifications_user_status_idx").on(table.userId, table.status)
2244
+ ]);
2245
+ notificationsRelations = relations9(notifications, ({ one }) => ({
2246
+ user: one(users, {
2247
+ fields: [notifications.userId],
2248
+ references: [users.id]
2249
+ })
2250
+ }));
2251
+ });
2252
+
1997
2253
  // ../data/src/tables.index.ts
1998
2254
  var init_tables_index = __esm(() => {
1999
2255
  init_table4();
@@ -2007,10 +2263,11 @@ var init_tables_index = __esm(() => {
2007
2263
  init_table9();
2008
2264
  init_table10();
2009
2265
  init_table11();
2266
+ init_table12();
2010
2267
  });
2011
2268
 
2012
2269
  // ../data/src/constants.ts
2013
- var ITEM_SLUGS, CURRENCIES, BADGES, ACHIEVEMENT_COMPLETION_TYPES, ACHIEVEMENT_COMPLETION_TYPE, INTERACTION_TYPE;
2270
+ var ITEM_SLUGS, CURRENCIES, BADGES, INTERACTION_TYPE;
2014
2271
  var init_constants = __esm(() => {
2015
2272
  init_tables_index();
2016
2273
  ITEM_SLUGS = {
@@ -2035,12 +2292,6 @@ var init_constants = __esm(() => {
2035
2292
  EARLY_ADOPTER: ITEM_SLUGS.EARLY_ADOPTER_BADGE,
2036
2293
  FIRST_GAME: ITEM_SLUGS.FIRST_GAME_BADGE
2037
2294
  };
2038
- ACHIEVEMENT_COMPLETION_TYPES = [
2039
- "time_played_session",
2040
- "interaction",
2041
- "leaderboard_rank"
2042
- ];
2043
- ACHIEVEMENT_COMPLETION_TYPE = Object.fromEntries(ACHIEVEMENT_COMPLETION_TYPES.map((value) => [value, value]));
2044
2295
  INTERACTION_TYPE = Object.fromEntries(interactionTypeEnum.enumValues.map((value) => [value, value]));
2045
2296
  });
2046
2297
 
@@ -2514,9 +2765,60 @@ function createAchievementsNamespace(client) {
2514
2765
  }
2515
2766
  var init_achievements = () => {};
2516
2767
 
2768
+ // src/constants.ts
2769
+ var CURRENCIES2, BADGES2, AuthProvider, TIMEBACK_ROUTES;
2770
+ var init_constants2 = __esm(() => {
2771
+ CURRENCIES2 = {
2772
+ PRIMARY: "PLAYCADEMY_CREDITS",
2773
+ XP: "PLAYCADEMY_XP"
2774
+ };
2775
+ BADGES2 = {
2776
+ FOUNDING_MEMBER: "FOUNDING_MEMBER_BADGE",
2777
+ EARLY_ADOPTER: "EARLY_ADOPTER_BADGE",
2778
+ FIRST_GAME: "FIRST_GAME_BADGE"
2779
+ };
2780
+ AuthProvider = {
2781
+ TIMEBACK: "TIMEBACK"
2782
+ };
2783
+ TIMEBACK_ROUTES = {
2784
+ PROGRESS: "/api/integrations/timeback/progress",
2785
+ SESSION_END: "/api/integrations/timeback/session-end",
2786
+ AWARD_XP: "/api/integrations/timeback/award-xp"
2787
+ };
2788
+ });
2789
+
2517
2790
  // src/core/namespaces/timeback.ts
2518
2791
  function createTimebackNamespace(client) {
2519
2792
  return {
2793
+ recordProgress: (progressData) => {
2794
+ return client["requestGameBackend"](TIMEBACK_ROUTES.PROGRESS, "POST", { progressData });
2795
+ },
2796
+ recordSessionEnd: (sessionData) => {
2797
+ return client["requestGameBackend"](TIMEBACK_ROUTES.SESSION_END, "POST", { sessionData });
2798
+ },
2799
+ awardXP: (xpAmount, metadata) => {
2800
+ return client["requestGameBackend"](TIMEBACK_ROUTES.AWARD_XP, "POST", {
2801
+ xpAmount,
2802
+ metadata
2803
+ });
2804
+ },
2805
+ management: {
2806
+ setup: (request2) => {
2807
+ return client["request"]("/timeback/setup", "POST", request2);
2808
+ },
2809
+ verify: (gameId) => {
2810
+ return client["request"](`/timeback/verify/${gameId}`, "GET");
2811
+ },
2812
+ cleanup: (gameId) => {
2813
+ return client["request"](`/timeback/integrations/${gameId}`, "DELETE");
2814
+ },
2815
+ get: (gameId) => {
2816
+ return client["request"](`/timeback/integrations/${gameId}`, "GET");
2817
+ },
2818
+ getConfig: (gameId) => {
2819
+ return client["request"](`/timeback/config/${gameId}`, "GET");
2820
+ }
2821
+ },
2520
2822
  xp: {
2521
2823
  today: async (options) => {
2522
2824
  const params = new URLSearchParams;
@@ -2559,6 +2861,104 @@ function createTimebackNamespace(client) {
2559
2861
  }
2560
2862
  };
2561
2863
  }
2864
+ var init_timeback = __esm(() => {
2865
+ init_constants2();
2866
+ });
2867
+
2868
+ // src/core/namespaces/notifications.ts
2869
+ function createNotificationsNamespace(client) {
2870
+ const notificationsListCache = createTTLCache({
2871
+ ttl: 5 * 1000,
2872
+ keyPrefix: "notifications.list"
2873
+ });
2874
+ const notificationStatsCache = createTTLCache({
2875
+ ttl: 30 * 1000,
2876
+ keyPrefix: "notifications.stats"
2877
+ });
2878
+ return {
2879
+ list: async (queryOptions, cacheOptions) => {
2880
+ const params = new URLSearchParams;
2881
+ if (queryOptions?.status)
2882
+ params.append("status", queryOptions.status);
2883
+ if (queryOptions?.type)
2884
+ params.append("type", queryOptions.type);
2885
+ if (queryOptions?.limit)
2886
+ params.append("limit", String(queryOptions.limit));
2887
+ if (queryOptions?.offset)
2888
+ params.append("offset", String(queryOptions.offset));
2889
+ const qs = params.toString();
2890
+ const path = qs ? `/notifications?${qs}` : "/notifications";
2891
+ const cacheKey = qs ? `list-${qs}` : "list";
2892
+ return notificationsListCache.get(cacheKey, () => client["request"](path, "GET"), cacheOptions);
2893
+ },
2894
+ markAsSeen: async (notificationId) => {
2895
+ const result = await client["request"](`/notifications/${notificationId}/status`, "PATCH", {
2896
+ id: notificationId,
2897
+ status: "seen"
2898
+ });
2899
+ notificationsListCache.clear();
2900
+ return result;
2901
+ },
2902
+ markAsClicked: async (notificationId) => {
2903
+ const result = await client["request"](`/notifications/${notificationId}/status`, "PATCH", {
2904
+ id: notificationId,
2905
+ status: "clicked"
2906
+ });
2907
+ notificationsListCache.clear();
2908
+ return result;
2909
+ },
2910
+ dismiss: async (notificationId) => {
2911
+ const result = await client["request"](`/notifications/${notificationId}/status`, "PATCH", {
2912
+ id: notificationId,
2913
+ status: "dismissed"
2914
+ });
2915
+ notificationsListCache.clear();
2916
+ return result;
2917
+ },
2918
+ stats: {
2919
+ get: async (queryOptions, cacheOptions) => {
2920
+ const user = await client.users.me();
2921
+ const params = new URLSearchParams;
2922
+ if (queryOptions?.from)
2923
+ params.append("from", queryOptions.from);
2924
+ if (queryOptions?.to)
2925
+ params.append("to", queryOptions.to);
2926
+ const qs = params.toString();
2927
+ const path = qs ? `/notifications/stats/${user.id}?${qs}` : `/notifications/stats/${user.id}`;
2928
+ const cacheKey = qs ? `stats-${qs}` : "stats";
2929
+ return notificationStatsCache.get(cacheKey, () => client["request"](path, "GET"), cacheOptions);
2930
+ }
2931
+ }
2932
+ };
2933
+ }
2934
+ var init_notifications = () => {};
2935
+
2936
+ // src/core/namespaces/backend.ts
2937
+ function createBackendNamespace(client) {
2938
+ function normalizePath(path) {
2939
+ return path.startsWith("/") ? path : `/${path}`;
2940
+ }
2941
+ return {
2942
+ async get(path, headers) {
2943
+ return client["requestGameBackend"](normalizePath(path), "GET", undefined, headers);
2944
+ },
2945
+ async post(path, body, headers) {
2946
+ return client["requestGameBackend"](normalizePath(path), "POST", body, headers);
2947
+ },
2948
+ async put(path, body, headers) {
2949
+ return client["requestGameBackend"](normalizePath(path), "PUT", body, headers);
2950
+ },
2951
+ async patch(path, body, headers) {
2952
+ return client["requestGameBackend"](normalizePath(path), "PATCH", body, headers);
2953
+ },
2954
+ async delete(path, headers) {
2955
+ return client["requestGameBackend"](normalizePath(path), "DELETE", undefined, headers);
2956
+ },
2957
+ async request(path, method, body, headers) {
2958
+ return client["requestGameBackend"](normalizePath(path), method, body, headers);
2959
+ }
2960
+ };
2961
+ }
2562
2962
 
2563
2963
  // src/core/namespaces/index.ts
2564
2964
  var init_namespaces = __esm(() => {
@@ -2566,12 +2966,15 @@ var init_namespaces = __esm(() => {
2566
2966
  init_runtime();
2567
2967
  init_games();
2568
2968
  init_users();
2969
+ init_maps();
2569
2970
  init_levels();
2570
2971
  init_credits();
2571
2972
  init_character();
2572
2973
  init_sprites();
2573
2974
  init_realtime();
2574
2975
  init_achievements();
2976
+ init_timeback();
2977
+ init_notifications();
2575
2978
  });
2576
2979
 
2577
2980
  // src/core/static/init.ts
@@ -2600,6 +3003,9 @@ function buildAllowedOrigins(explicit) {
2600
3003
  return ref ? [ref] : [];
2601
3004
  }
2602
3005
  function isOriginAllowed(origin, allowlist) {
3006
+ if (window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1") {
3007
+ return true;
3008
+ }
2603
3009
  if (!allowlist || allowlist.length === 0) {
2604
3010
  console.error("[Playcademy SDK] No allowed origins configured. Consider passing allowedParentOrigins explicitly to init().");
2605
3011
  return false;
@@ -2643,7 +3049,8 @@ async function waitForPlaycademyInit(allowedParentOrigins) {
2643
3049
  function createStandaloneConfig() {
2644
3050
  console.warn("[Playcademy SDK] Standalone mode detected, creating mock context for local development");
2645
3051
  const mockConfig = {
2646
- baseUrl: "/api",
3052
+ baseUrl: "http://localhost:4321",
3053
+ gameUrl: "http://localhost:8788",
2647
3054
  token: "mock-game-token-for-local-dev",
2648
3055
  gameId: "mock-game-id-from-template",
2649
3056
  realtimeUrl: undefined
@@ -2662,6 +3069,7 @@ async function init(options) {
2662
3069
  }
2663
3070
  const client = new PlaycademyClient({
2664
3071
  baseUrl: config.baseUrl,
3072
+ gameUrl: config.gameUrl,
2665
3073
  token: config.token,
2666
3074
  gameId: config.gameId,
2667
3075
  autoStartSession: window.self !== window.top
@@ -2739,7 +3147,8 @@ var init_client = __esm(() => {
2739
3147
  init_static();
2740
3148
  PlaycademyClient = class PlaycademyClient {
2741
3149
  baseUrl;
2742
- token;
3150
+ gameUrl;
3151
+ authStrategy;
2743
3152
  gameId;
2744
3153
  config;
2745
3154
  listeners = {};
@@ -2747,16 +3156,11 @@ var init_client = __esm(() => {
2747
3156
  authContext;
2748
3157
  initPayload;
2749
3158
  constructor(config) {
2750
- if (!config) {
2751
- this.baseUrl = "/api";
2752
- this.token = undefined;
2753
- this.gameId = undefined;
2754
- } else {
2755
- this.baseUrl = config.baseUrl || "/api";
2756
- this.token = config.token;
2757
- this.gameId = config.gameId;
2758
- }
3159
+ this.baseUrl = config?.baseUrl?.endsWith("/api") ? config.baseUrl : `${config?.baseUrl}/api`;
3160
+ this.gameUrl = config?.gameUrl;
3161
+ this.gameId = config?.gameId;
2759
3162
  this.config = config || {};
3163
+ this.authStrategy = createAuthStrategy(config?.token ?? null, config?.tokenType);
2760
3164
  this._detectAuthContext();
2761
3165
  this._initializeInternalSession().catch(() => {});
2762
3166
  }
@@ -2765,18 +3169,30 @@ var init_client = __esm(() => {
2765
3169
  const isBrowser2 = typeof window !== "undefined";
2766
3170
  return isRelative && isBrowser2 ? `${window.location.origin}${this.baseUrl}` : this.baseUrl;
2767
3171
  }
3172
+ getGameBackendUrl() {
3173
+ if (!this.gameUrl) {
3174
+ throw new PlaycademyError("Game backend URL not configured. gameUrl must be set to use game backend features.");
3175
+ }
3176
+ const isRelative = this.gameUrl.startsWith("/");
3177
+ const isBrowser2 = typeof window !== "undefined";
3178
+ const effectiveGameUrl = isRelative && isBrowser2 ? `${window.location.origin}${this.gameUrl}` : this.gameUrl;
3179
+ return `${effectiveGameUrl}/api`;
3180
+ }
2768
3181
  ping() {
2769
3182
  return "pong";
2770
3183
  }
2771
- setToken(token) {
2772
- this.token = token ?? undefined;
2773
- this.emit("authChange", { token: this.token ?? null });
3184
+ setToken(token, tokenType) {
3185
+ this.authStrategy = createAuthStrategy(token, tokenType);
3186
+ this.emit("authChange", { token });
3187
+ }
3188
+ getTokenType() {
3189
+ return this.authStrategy.getType();
2774
3190
  }
2775
3191
  getToken() {
2776
- return this.token ?? null;
3192
+ return this.authStrategy.getToken();
2777
3193
  }
2778
3194
  isAuthenticated() {
2779
- return !!this.token;
3195
+ return this.authStrategy.getToken() !== null;
2780
3196
  }
2781
3197
  onAuthChange(callback) {
2782
3198
  this.on("authChange", (payload) => callback(payload.token));
@@ -2794,13 +3210,28 @@ var init_client = __esm(() => {
2794
3210
  });
2795
3211
  }
2796
3212
  async request(path, method, body, headers) {
2797
- const effectiveHeaders = { ...headers };
3213
+ const effectiveHeaders = {
3214
+ ...headers,
3215
+ ...this.authStrategy.getHeaders()
3216
+ };
2798
3217
  return request({
2799
3218
  path,
2800
3219
  method,
2801
3220
  body,
2802
3221
  baseUrl: this.baseUrl,
2803
- token: this.token,
3222
+ extraHeaders: effectiveHeaders
3223
+ });
3224
+ }
3225
+ async requestGameBackend(path, method, body, headers) {
3226
+ const effectiveHeaders = {
3227
+ ...headers,
3228
+ ...this.authStrategy.getHeaders()
3229
+ };
3230
+ return request({
3231
+ path,
3232
+ method,
3233
+ body,
3234
+ baseUrl: this.getGameBackendUrl(),
2804
3235
  extraHeaders: effectiveHeaders
2805
3236
  });
2806
3237
  }
@@ -2852,6 +3283,8 @@ var init_client = __esm(() => {
2852
3283
  sprites = createSpritesNamespace(this);
2853
3284
  realtime = createRealtimeNamespace(this);
2854
3285
  achievements = createAchievementsNamespace(this);
3286
+ notifications = createNotificationsNamespace(this);
3287
+ backend = createBackendNamespace(this);
2855
3288
  static init = init;
2856
3289
  static login = login2;
2857
3290
  static identity = identity;
@@ -2861,20 +3294,7 @@ var init_client = __esm(() => {
2861
3294
  // src/index.ts
2862
3295
  init_client();
2863
3296
  init_messaging();
2864
-
2865
- // src/constants.ts
2866
- var CURRENCIES2 = {
2867
- PRIMARY: "PLAYCADEMY_CREDITS",
2868
- XP: "PLAYCADEMY_XP"
2869
- };
2870
- var BADGES2 = {
2871
- FOUNDING_MEMBER: "FOUNDING_MEMBER_BADGE",
2872
- EARLY_ADOPTER: "EARLY_ADOPTER_BADGE",
2873
- FIRST_GAME: "FIRST_GAME_BADGE"
2874
- };
2875
- var AuthProvider = {
2876
- TIMEBACK: "TIMEBACK"
2877
- };
3297
+ init_constants2();
2878
3298
  export {
2879
3299
  messaging,
2880
3300
  PlaycademyClient,