playcademy 0.19.0 → 0.19.1-beta.10

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/cli.js CHANGED
@@ -1065,7 +1065,7 @@ var SAMPLE_BUCKET_FILENAME = "bucket.ts";
1065
1065
  // ../better-auth/package.json
1066
1066
  var package_default = {
1067
1067
  name: "@playcademy/better-auth",
1068
- version: "0.0.5",
1068
+ version: "0.0.6-beta.10",
1069
1069
  type: "module",
1070
1070
  exports: {
1071
1071
  "./server": {
@@ -3016,7 +3016,7 @@ import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as re
3016
3016
  import { join as join13 } from "node:path";
3017
3017
 
3018
3018
  // src/version.ts
3019
- var cliVersion = false ? "0.0.0-dev" : "0.19.0";
3019
+ var cliVersion = false ? "0.0.0-dev" : "0.19.1-beta.10";
3020
3020
 
3021
3021
  // src/lib/init/database.ts
3022
3022
  var drizzleConfigTemplate = loadTemplateString("database/drizzle-config.ts");
@@ -67,19 +67,12 @@ declare const BUCKET_ALWAYS_SKIP: readonly [".git", ".DS_Store", ".gitignore", "
67
67
 
68
68
  /**
69
69
  * Cloudflare-related constants
70
- */
71
- /**
72
- * Cloudflare Workers compatibility date
73
- * Must stay on or after 2024-09-23 so nodejs_compat uses the modern v2 path.
74
- */
75
- declare const CLOUDFLARE_COMPATIBILITY_DATE = "2025-10-11";
76
- /**
77
- * Compatibility flags enabled for Playcademy backend workers.
78
70
  *
79
- * Keep local dev and deployed backends aligned so libraries like Better Auth
80
- * and Sentry see the same Worker runtime capabilities.
71
+ * Worker runtime pins (`CLOUDFLARE_COMPATIBILITY_DATE`,
72
+ * `BACKEND_COMPATIBILITY_FLAGS`) live in `@playcademy/constants` so every
73
+ * package that deploys or emulates workers (CLI, cloudflare, sdk, api-core)
74
+ * agrees on a single source of truth. Import them from there directly.
81
75
  */
82
- declare const BACKEND_COMPATIBILITY_FLAGS: readonly ["nodejs_compat"];
83
76
  /**
84
77
  * Cloudflare binding names for local development (Miniflare)
85
78
  */
@@ -351,4 +344,4 @@ declare const CONFIG_FILE_NAMES: string[];
351
344
  */
352
345
  declare const DOCS_URL = "https://docs.dev.playcademy.net/platform";
353
346
 
354
- export { AUTH_API_SUBDIRECTORY, AUTH_CONFIG_FILE, AUTH_PROVIDER_NAMES, BACKEND_COMPATIBILITY_FLAGS, BETTER_AUTH_VERSION, BUCKET_ALWAYS_SKIP, CALLBACK_PATH, CALLBACK_PORT, CLI_DEFAULT_OUTPUTS, CLI_DIRECTORIES, CLI_FILES, CLI_USER_DIRECTORIES, CLOUDFLARE_BINDINGS, CLOUDFLARE_COMPATIBILITY_DATE, CONFIG_FILE_NAMES, DB_FILES, DEFAULT_API_ROUTES_DIRECTORY, DEFAULT_DATABASE_DIRECTORY, DEFAULT_PORTS, DEFAULT_TEMPLATE_REF, DOCS_URL, DRIZZLE_CONFIG_FILES, ENV_EXAMPLE_FILE, ENV_FILES, GODOT_ADDON_URL, GODOT_BUILD_DIRECTORIES, GODOT_BUILD_OUTPUTS, GODOT_EXECUTABLE_PATTERNS, GODOT_EXPORT_PRESETS_FILE, GODOT_PROJECT_FILE, GODOT_WEB_PLATFORM, HOOKS_FILE, MINIFLARE_D1_DIRECTORY, OAUTH_CALLBACK_URL_PATTERN, PLACEHOLDER_APP_URL, PLAYCADEMY_AUTH_VERSION, PUBLIC_DIRECTORY, QUEUE_NAME_PREFIX, SAMPLE_API_SUBDIRECTORY, SAMPLE_BUCKET_FILENAME, SAMPLE_CUSTOM_FILENAME, SAMPLE_DATABASE_FILENAME, SAMPLE_KV_FILENAME, SCHEMA_SUBDIRECTORY, SELF_WORKER_NAME_BINDING, SERVER_LIB_DIRECTORY, SERVER_ROOT_DIRECTORY, SSO_AUTH_TIMEOUT_MS, TEMPLATE_RENAME_FILES, TEMPLATE_REPOS, TSCONFIG_APP_JSON, TSCONFIG_FILES, TSCONFIG_JSON, TSCONFIG_REQUIRED_INCLUDES, WORKSPACE_NAME, cacheVersionDir, nativeBinaryPath };
347
+ export { AUTH_API_SUBDIRECTORY, AUTH_CONFIG_FILE, AUTH_PROVIDER_NAMES, BETTER_AUTH_VERSION, BUCKET_ALWAYS_SKIP, CALLBACK_PATH, CALLBACK_PORT, CLI_DEFAULT_OUTPUTS, CLI_DIRECTORIES, CLI_FILES, CLI_USER_DIRECTORIES, CLOUDFLARE_BINDINGS, CONFIG_FILE_NAMES, DB_FILES, DEFAULT_API_ROUTES_DIRECTORY, DEFAULT_DATABASE_DIRECTORY, DEFAULT_PORTS, DEFAULT_TEMPLATE_REF, DOCS_URL, DRIZZLE_CONFIG_FILES, ENV_EXAMPLE_FILE, ENV_FILES, GODOT_ADDON_URL, GODOT_BUILD_DIRECTORIES, GODOT_BUILD_OUTPUTS, GODOT_EXECUTABLE_PATTERNS, GODOT_EXPORT_PRESETS_FILE, GODOT_PROJECT_FILE, GODOT_WEB_PLATFORM, HOOKS_FILE, MINIFLARE_D1_DIRECTORY, OAUTH_CALLBACK_URL_PATTERN, PLACEHOLDER_APP_URL, PLAYCADEMY_AUTH_VERSION, PUBLIC_DIRECTORY, QUEUE_NAME_PREFIX, SAMPLE_API_SUBDIRECTORY, SAMPLE_BUCKET_FILENAME, SAMPLE_CUSTOM_FILENAME, SAMPLE_DATABASE_FILENAME, SAMPLE_KV_FILENAME, SCHEMA_SUBDIRECTORY, SELF_WORKER_NAME_BINDING, SERVER_LIB_DIRECTORY, SERVER_ROOT_DIRECTORY, SSO_AUTH_TIMEOUT_MS, TEMPLATE_RENAME_FILES, TEMPLATE_REPOS, TSCONFIG_APP_JSON, TSCONFIG_FILES, TSCONFIG_JSON, TSCONFIG_REQUIRED_INCLUDES, WORKSPACE_NAME, cacheVersionDir, nativeBinaryPath };
package/dist/constants.js CHANGED
@@ -20,7 +20,7 @@ var SAMPLE_BUCKET_FILENAME = "bucket.ts";
20
20
  // ../better-auth/package.json
21
21
  var package_default = {
22
22
  name: "@playcademy/better-auth",
23
- version: "0.0.5",
23
+ version: "0.0.6-beta.10",
24
24
  type: "module",
25
25
  exports: {
26
26
  "./server": {
@@ -101,8 +101,6 @@ var TSCONFIG_REQUIRED_INCLUDES = [
101
101
  var BUCKET_ALWAYS_SKIP = [".git", ".DS_Store", ".gitignore", ...ENV_FILES];
102
102
 
103
103
  // src/constants/cloudflare.ts
104
- var CLOUDFLARE_COMPATIBILITY_DATE = "2025-10-11";
105
- var BACKEND_COMPATIBILITY_FLAGS = ["nodejs_compat"];
106
104
  var CLOUDFLARE_BINDINGS = {
107
105
  /** R2 bucket binding name */
108
106
  BUCKET: "BUCKET",
@@ -390,7 +388,6 @@ export {
390
388
  AUTH_API_SUBDIRECTORY,
391
389
  AUTH_CONFIG_FILE,
392
390
  AUTH_PROVIDER_NAMES,
393
- BACKEND_COMPATIBILITY_FLAGS,
394
391
  BETTER_AUTH_VERSION,
395
392
  BUCKET_ALWAYS_SKIP,
396
393
  CALLBACK_PATH,
@@ -400,7 +397,6 @@ export {
400
397
  CLI_FILES,
401
398
  CLI_USER_DIRECTORIES,
402
399
  CLOUDFLARE_BINDINGS,
403
- CLOUDFLARE_COMPATIBILITY_DATE,
404
400
  CONFIG_FILE_NAMES,
405
401
  DB_FILES,
406
402
  DEFAULT_API_ROUTES_DIRECTORY,
package/dist/db.js CHANGED
@@ -36,7 +36,7 @@ var DEFAULT_API_ROUTES_DIRECTORY = join2(SERVER_ROOT_DIRECTORY, "api");
36
36
  // ../better-auth/package.json
37
37
  var package_default = {
38
38
  name: "@playcademy/better-auth",
39
- version: "0.0.5",
39
+ version: "0.0.6-beta.10",
40
40
  type: "module",
41
41
  exports: {
42
42
  "./server": {
package/dist/index.d.ts CHANGED
@@ -615,6 +615,8 @@ interface BackendDeploymentBundle {
615
615
  bindings?: BackendResourceBindings;
616
616
  /** Optional schema information for database setup */
617
617
  schema?: SchemaInfo;
618
+ /** Cloudflare Worker compatibility date (YYYY-MM-DD) */
619
+ compatibilityDate?: string;
618
620
  /** Optional Cloudflare Worker compatibility flags */
619
621
  compatibilityFlags?: string[];
620
622
  }
@@ -1061,6 +1063,7 @@ interface DeploymentContext {
1061
1063
  minify: boolean;
1062
1064
  verbose: boolean;
1063
1065
  debug: boolean;
1066
+ compatibilityDate?: string;
1064
1067
  }
1065
1068
  /**
1066
1069
  * Analysis of what changed in an update
@@ -1193,6 +1196,8 @@ interface BundleOptions {
1193
1196
  sourcemap?: boolean;
1194
1197
  /** Minify output (default: false) */
1195
1198
  minify?: boolean;
1199
+ /** Override the Worker compatibility date used for compat-aware bundling */
1200
+ compatibilityDate?: string;
1196
1201
  }
1197
1202
  /**
1198
1203
  * Metadata describing the sealed backend runtime artifact.
package/dist/index.js CHANGED
@@ -326,7 +326,7 @@ var SAMPLE_BUCKET_FILENAME = "bucket.ts";
326
326
  // ../better-auth/package.json
327
327
  var package_default = {
328
328
  name: "@playcademy/better-auth",
329
- version: "0.0.5",
329
+ version: "0.0.6-beta.10",
330
330
  type: "module",
331
331
  exports: {
332
332
  "./server": {
@@ -400,8 +400,6 @@ var TSCONFIG_REQUIRED_INCLUDES = [
400
400
  var BUCKET_ALWAYS_SKIP = [".git", ".DS_Store", ".gitignore", ...ENV_FILES];
401
401
 
402
402
  // src/constants/cloudflare.ts
403
- var CLOUDFLARE_COMPATIBILITY_DATE = "2025-10-11";
404
- var BACKEND_COMPATIBILITY_FLAGS = ["nodejs_compat"];
405
403
  var CLOUDFLARE_BINDINGS = {
406
404
  /** R2 bucket binding name */
407
405
  BUCKET: "BUCKET",
@@ -674,7 +672,8 @@ var BADGES = {
674
672
  // ../constants/src/timeback.ts
675
673
  var TIMEBACK_ROUTES = {
676
674
  END_ACTIVITY: "/integrations/timeback/end-activity",
677
- GET_XP: "/integrations/timeback/xp"
675
+ GET_XP: "/integrations/timeback/xp",
676
+ HEARTBEAT: "/integrations/timeback/heartbeat"
678
677
  };
679
678
  var TIMEBACK_ORG_SOURCED_ID = "PLAYCADEMY";
680
679
  var TIMEBACK_ORG_NAME = "Playcademy Studios";
@@ -724,6 +723,8 @@ var WORKER_NAMING = {
724
723
  STAGING_SUFFIX: "-staging"
725
724
  };
726
725
  var SECRETS_PREFIX = "secrets_";
726
+ var CLOUDFLARE_COMPATIBILITY_DATE = "2025-10-11";
727
+ var BACKEND_COMPATIBILITY_FLAGS = ["nodejs_compat"];
727
728
 
728
729
  // src/constants/urls.ts
729
730
  var DOCS_URL = "https://docs.dev.playcademy.net/platform";
@@ -4017,7 +4018,7 @@ import { existsSync as existsSync9, mkdirSync as mkdirSync2, readFileSync as rea
4017
4018
  import { join as join13 } from "node:path";
4018
4019
 
4019
4020
  // src/version.ts
4020
- var cliVersion = false ? "0.0.0-dev" : "0.19.0";
4021
+ var cliVersion = false ? "0.0.0-dev" : "0.19.1-beta.10";
4021
4022
 
4022
4023
  // src/lib/init/database.ts
4023
4024
  var drizzleConfigTemplate = loadTemplateString("database/drizzle-config.ts");
@@ -6401,7 +6402,8 @@ var ROUTES = {
6401
6402
  /** TimeBack integration routes */
6402
6403
  TIMEBACK: {
6403
6404
  END_ACTIVITY: `/api${TIMEBACK_ROUTES.END_ACTIVITY}`,
6404
- GET_XP: `/api${TIMEBACK_ROUTES.GET_XP}`
6405
+ GET_XP: `/api${TIMEBACK_ROUTES.GET_XP}`,
6406
+ HEARTBEAT: `/api${TIMEBACK_ROUTES.HEARTBEAT}`
6405
6407
  }
6406
6408
  };
6407
6409
 
@@ -9515,7 +9517,10 @@ function createEsbuildConfig(entryCode, paths, bundleConfig, customRoutesDir, op
9515
9517
  // └─────────────────────────────────────────────────────────────────────┘
9516
9518
  "@game-server": join32(workspace, "server")
9517
9519
  },
9518
- plugins: [createBackendNodeCompatPlugin(), createForbiddenRuntimeImportsPlugin(workspace)],
9520
+ plugins: [
9521
+ createBackendNodeCompatPlugin({ compatibilityDate: options.compatibilityDate }),
9522
+ createForbiddenRuntimeImportsPlugin(workspace)
9523
+ ],
9519
9524
  external: []
9520
9525
  // Native workerd builtins are externalized via the compat plugin
9521
9526
  };
@@ -10964,7 +10969,7 @@ import {
10964
10969
  // ../data/src/domains/user/table.ts
10965
10970
  import { relations } from "drizzle-orm";
10966
10971
  import { boolean, pgEnum, pgTable, text, timestamp, uniqueIndex } from "drizzle-orm/pg-core";
10967
- var userRoleEnum = pgEnum("user_role", ["admin", "player", "developer"]);
10972
+ var userRoleEnum = pgEnum("user_role", ["admin", "player", "developer", "teacher"]);
10968
10973
  var developerStatusEnum = pgEnum("developer_status", ["none", "pending", "approved"]);
10969
10974
  var users = pgTable("user", {
10970
10975
  id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
@@ -11504,6 +11509,7 @@ async function prepareDeploymentContext(options) {
11504
11509
  minify: options.minify ?? true,
11505
11510
  verbose: options.verbose ?? false,
11506
11511
  debug: options.debug ?? false,
11512
+ compatibilityDate: options.compatibilityDate,
11507
11513
  localSecrets,
11508
11514
  remoteSecretKeys
11509
11515
  };
@@ -11529,7 +11535,10 @@ async function detectIntegrationMetadataChanges(context2) {
11529
11535
  async function bundleAndHash(context2) {
11530
11536
  try {
11531
11537
  await ensurePlaycademyTypes();
11532
- const bundle = await bundleBackend(context2.fullConfig, { minify: context2.minify });
11538
+ const bundle = await bundleBackend(context2.fullConfig, {
11539
+ minify: context2.minify,
11540
+ compatibilityDate: context2.compatibilityDate
11541
+ });
11533
11542
  const bundleHash = hashContent(bundle.code);
11534
11543
  const [serverSize, dbSize] = await Promise.all([
11535
11544
  getDirectorySize(join37(context2.projectPath, SERVER_ROOT_DIRECTORY)),
@@ -11644,6 +11653,7 @@ async function calculateDeploymentPlan(context2, changes) {
11644
11653
  deployBackend,
11645
11654
  forceBackend
11646
11655
  } = context2;
11656
+ const compatibilityDateOverride = Boolean(context2.compatibilityDate) && deployBackend;
11647
11657
  if (!gameAlreadyDeployed) {
11648
11658
  const needsBackend3 = hasLocalCustomRoutes(projectPath, context2.fullConfig) || Boolean(context2.fullConfig?.integrations);
11649
11659
  const shouldDeployBackend2 = deployBackend && needsBackend3;
@@ -11660,7 +11670,7 @@ async function calculateDeploymentPlan(context2, changes) {
11660
11670
  const configHasChanges = changes.config;
11661
11671
  const secretsHaveChanges = changes.secrets !== void 0;
11662
11672
  const noChanges = !buildHasChanges && !backendHasChanges && !configHasChanges && !secretsHaveChanges;
11663
- if (noChanges && !forceBackend) {
11673
+ if (noChanges && !forceBackend && !compatibilityDateOverride) {
11664
11674
  return {
11665
11675
  action: "no-changes",
11666
11676
  gameType: config.gameType ?? "hosted",
@@ -11673,7 +11683,7 @@ async function calculateDeploymentPlan(context2, changes) {
11673
11683
  const shouldUpdateGame = buildHasChanges || configHasChanges;
11674
11684
  const shouldUploadBuild = buildHasChanges;
11675
11685
  const needsBackend2 = hasLocalCustomRoutes(projectPath, context2.fullConfig) || Boolean(context2.fullConfig?.integrations);
11676
- const shouldDeployBackend = deployBackend && (backendHasChanges || forceBackend || shouldUploadBuild && needsBackend2);
11686
+ const shouldDeployBackend = deployBackend && (backendHasChanges || forceBackend || compatibilityDateOverride || shouldUploadBuild && needsBackend2);
11677
11687
  return {
11678
11688
  action: "update-existing",
11679
11689
  gameType: config.gameType ?? "hosted",
@@ -11854,7 +11864,10 @@ async function deployGame(context2, shouldUploadBuild, shouldDeployBackend) {
11854
11864
  await ensurePlaycademyTypes();
11855
11865
  const bundle = await runStep(
11856
11866
  "Bundling backend",
11857
- async () => context2.currentBackendBundle ?? bundleBackend(fullConfig, { minify: context2.minify }),
11867
+ async () => context2.currentBackendBundle ?? bundleBackend(fullConfig, {
11868
+ minify: context2.minify,
11869
+ compatibilityDate: context2.compatibilityDate
11870
+ }),
11858
11871
  "Backend bundled"
11859
11872
  );
11860
11873
  const deploymentPrep = await runStep(
@@ -11898,6 +11911,7 @@ async function deployGame(context2, shouldUploadBuild, shouldDeployBackend) {
11898
11911
  ...bundle,
11899
11912
  bindings: Object.keys(deploymentPrep.bindings).length > 0 ? deploymentPrep.bindings : void 0,
11900
11913
  schema: deploymentPrep.schemaInfo || void 0,
11914
+ compatibilityDate: context2.compatibilityDate ?? CLOUDFLARE_COMPATIBILITY_DATE,
11901
11915
  compatibilityFlags: [...BACKEND_COMPATIBILITY_FLAGS]
11902
11916
  };
11903
11917
  const [serverSize, dbSize] = await Promise.all([
@@ -11927,7 +11941,8 @@ async function deployGame(context2, shouldUploadBuild, shouldDeployBackend) {
11927
11941
  );
11928
11942
  backendBundle = {
11929
11943
  code: stubCode,
11930
- config: fullConfig || { name: "stub" }
11944
+ config: fullConfig || { name: "stub" },
11945
+ ...context2.compatibilityDate ? { compatibilityDate: context2.compatibilityDate } : {}
11931
11946
  };
11932
11947
  }
11933
11948
  let game;
@@ -213,7 +213,8 @@ var init_timeback = __esm({
213
213
  "use strict";
214
214
  TIMEBACK_ROUTES = {
215
215
  END_ACTIVITY: "/integrations/timeback/end-activity",
216
- GET_XP: "/integrations/timeback/xp"
216
+ GET_XP: "/integrations/timeback/xp",
217
+ HEARTBEAT: "/integrations/timeback/heartbeat"
217
218
  };
218
219
  }
219
220
  });
@@ -257,7 +258,8 @@ var init_constants = __esm({
257
258
  /** TimeBack integration routes */
258
259
  TIMEBACK: {
259
260
  END_ACTIVITY: `/api${TIMEBACK_ROUTES.END_ACTIVITY}`,
260
- GET_XP: `/api${TIMEBACK_ROUTES.GET_XP}`
261
+ GET_XP: `/api${TIMEBACK_ROUTES.GET_XP}`,
262
+ HEARTBEAT: `/api${TIMEBACK_ROUTES.HEARTBEAT}`
261
263
  }
262
264
  };
263
265
  }
@@ -311,7 +313,11 @@ function getBuiltinRoutes() {
311
313
  }
312
314
  function getTimebackRoutes(config) {
313
315
  if (config?.integrations?.timeback) {
314
- return [{ path: ROUTES.TIMEBACK.END_ACTIVITY, methods: ["POST"] }];
316
+ return [
317
+ { path: ROUTES.TIMEBACK.END_ACTIVITY, methods: ["POST"] },
318
+ { path: ROUTES.TIMEBACK.GET_XP, methods: ["GET"] },
319
+ { path: ROUTES.TIMEBACK.HEARTBEAT, methods: ["POST"] }
320
+ ];
315
321
  }
316
322
  return [];
317
323
  }
@@ -344,9 +350,9 @@ var init_routes = __esm({
344
350
  // ../edge-play/src/entry/metadata.ts
345
351
  function getRuntimeMetadata() {
346
352
  return {
347
- cliVersion: true ? "0.19.0" : "0.0.0-dev",
348
- sdkVersion: true ? "0.4.0" : "0.0.0-dev",
349
- buildId: true ? "4086a66aa2f8" : "dev-source"
353
+ cliVersion: true ? "0.19.1-beta.10" : "0.0.0-dev",
354
+ sdkVersion: true ? "0.5.0-beta.1" : "0.0.0-dev",
355
+ buildId: true ? "68aee00c09c6" : "dev-source"
350
356
  };
351
357
  }
352
358
  var init_metadata = __esm({
@@ -546,11 +552,7 @@ var init_lib = __esm({
546
552
  }
547
553
  });
548
554
 
549
- // ../edge-play/src/routes/integrations/timeback/end-activity.ts
550
- var end_activity_exports = {};
551
- __export(end_activity_exports, {
552
- POST: () => POST
553
- });
555
+ // ../edge-play/src/routes/integrations/timeback/shared.ts
554
556
  function getConfig(c) {
555
557
  const config = c.get("config");
556
558
  const timebackConfig = config?.integrations?.timeback;
@@ -574,6 +576,17 @@ function enrichActivityData(params) {
574
576
  function isActivityDataInput(value) {
575
577
  return typeof value === "object" && value !== null && !Array.isArray(value);
576
578
  }
579
+ var init_shared = __esm({
580
+ "../edge-play/src/routes/integrations/timeback/shared.ts"() {
581
+ "use strict";
582
+ }
583
+ });
584
+
585
+ // ../edge-play/src/routes/integrations/timeback/end-activity.ts
586
+ var end_activity_exports = {};
587
+ __export(end_activity_exports, {
588
+ POST: () => POST
589
+ });
577
590
  async function POST(c) {
578
591
  try {
579
592
  const user = c.get("playcademyUser");
@@ -585,7 +598,17 @@ async function POST(c) {
585
598
  console.error("[TimeBack End Activity] Error:", message);
586
599
  return c.json(buildErrorResponse(c, message, message), 400);
587
600
  }
588
- const { activityData, scoreData, timingData, xpEarned, masteredUnits, extensions } = await c.req.json();
601
+ const {
602
+ runId,
603
+ resumeId,
604
+ activityData,
605
+ scoreData,
606
+ timingData,
607
+ sessionTimingData,
608
+ xpEarned,
609
+ masteredUnits,
610
+ extensions
611
+ } = await c.req.json();
589
612
  const config = getConfig(c);
590
613
  const structuredActivityData = isActivityDataInput(activityData) ? activityData : void 0;
591
614
  if (structuredActivityData && isValidGrade2(structuredActivityData.grade) && isValidSubject2(structuredActivityData.subject)) {
@@ -612,9 +635,12 @@ async function POST(c) {
612
635
  {
613
636
  gameId: sdk.gameId,
614
637
  studentId: user.timeback_id,
638
+ runId,
639
+ resumeId,
615
640
  activityData: enrichResult.data,
616
641
  scoreData,
617
642
  timingData,
643
+ sessionTimingData,
618
644
  xpEarned,
619
645
  masteredUnits,
620
646
  extensions
@@ -630,6 +656,7 @@ var init_end_activity = __esm({
630
656
  "../edge-play/src/routes/integrations/timeback/end-activity.ts"() {
631
657
  "use strict";
632
658
  init_lib();
659
+ init_shared();
633
660
  }
634
661
  });
635
662
 
@@ -707,6 +734,86 @@ var init_get_xp = __esm({
707
734
  }
708
735
  });
709
736
 
737
+ // ../edge-play/src/routes/integrations/timeback/heartbeat.ts
738
+ var heartbeat_exports = {};
739
+ __export(heartbeat_exports, {
740
+ POST: () => POST2
741
+ });
742
+ async function POST2(c) {
743
+ try {
744
+ const user = c.get("playcademyUser");
745
+ if (!user) {
746
+ return c.json(buildErrorResponse(c, "Unauthorized", "Unauthorized"), 401);
747
+ }
748
+ if (!user.timeback_id) {
749
+ const message = "User does not have TimeBack integration";
750
+ console.error("[TimeBack Heartbeat] Error:", message);
751
+ return c.json(buildErrorResponse(c, message, message), 400);
752
+ }
753
+ const {
754
+ activityData,
755
+ timingData,
756
+ windowStartedAtMs,
757
+ windowSequence,
758
+ isFinal,
759
+ runId,
760
+ resumeId
761
+ } = await c.req.json();
762
+ const hasWindowStartedAtMs = windowStartedAtMs !== void 0;
763
+ const hasWindowSequence = windowSequence !== void 0;
764
+ if (hasWindowStartedAtMs === hasWindowSequence) {
765
+ const message = "Provide exactly one of windowStartedAtMs or windowSequence";
766
+ console.error("[TimeBack Heartbeat] Error:", message);
767
+ return c.json(buildErrorResponse(c, message, message), 400);
768
+ }
769
+ const config = getConfig(c);
770
+ const structuredActivityData = isActivityDataInput(activityData) ? activityData : void 0;
771
+ if (structuredActivityData && isValidGrade2(structuredActivityData.grade) && isValidSubject2(structuredActivityData.subject)) {
772
+ const courseValidationError = validateCourseConfig({
773
+ grade: structuredActivityData.grade,
774
+ subject: structuredActivityData.subject,
775
+ config
776
+ });
777
+ if (courseValidationError) {
778
+ const message = courseValidationError.error;
779
+ console.error("[TimeBack Heartbeat] Error:", message);
780
+ return c.json(buildErrorResponse(c, message, message), 400);
781
+ }
782
+ }
783
+ const enrichResult = structuredActivityData ? enrichActivityData({ activityData: structuredActivityData, config, c }) : { data: activityData };
784
+ if ("error" in enrichResult) {
785
+ console.error("[TimeBack Heartbeat] Error:", enrichResult.error);
786
+ return c.json(buildErrorResponse(c, enrichResult.error, enrichResult.error), 500);
787
+ }
788
+ const sdk = c.get("sdk");
789
+ const result = await sdk.request("/api/timeback/heartbeat", "POST", {
790
+ gameId: sdk.gameId,
791
+ studentId: user.timeback_id,
792
+ runId,
793
+ resumeId,
794
+ activityData: enrichResult.data,
795
+ timingData,
796
+ ...hasWindowStartedAtMs ? { windowStartedAtMs } : {},
797
+ ...hasWindowSequence ? { windowSequence } : {},
798
+ isFinal
799
+ });
800
+ return c.json(result);
801
+ } catch (error) {
802
+ logError("TimeBack Heartbeat", error);
803
+ return c.json(
804
+ buildErrorResponse(c, error, "Failed to record heartbeat"),
805
+ getErrorStatus(error)
806
+ );
807
+ }
808
+ }
809
+ var init_heartbeat = __esm({
810
+ "../edge-play/src/routes/integrations/timeback/heartbeat.ts"() {
811
+ "use strict";
812
+ init_lib();
813
+ init_shared();
814
+ }
815
+ });
816
+
710
817
  // ../../node_modules/.bun/hono@4.10.7/node_modules/hono/dist/compose.js
711
818
  var compose = (middleware, onError, onNotFound) => {
712
819
  return (context, next) => {
@@ -3036,12 +3143,14 @@ async function registerBuiltinRoutes(app, integrations) {
3036
3143
  const health = await Promise.resolve().then(() => (init_health(), health_exports));
3037
3144
  app.get(ROUTES.HEALTH, health.GET);
3038
3145
  if (integrations?.timeback) {
3039
- const [endActivity, getXp] = await Promise.all([
3146
+ const [endActivity, getXp, heartbeat] = await Promise.all([
3040
3147
  Promise.resolve().then(() => (init_end_activity(), end_activity_exports)),
3041
- Promise.resolve().then(() => (init_get_xp(), get_xp_exports))
3148
+ Promise.resolve().then(() => (init_get_xp(), get_xp_exports)),
3149
+ Promise.resolve().then(() => (init_heartbeat(), heartbeat_exports))
3042
3150
  ]);
3043
3151
  app.post(ROUTES.TIMEBACK.END_ACTIVITY, endActivity.POST);
3044
3152
  app.get(ROUTES.TIMEBACK.GET_XP, getXp.GET);
3153
+ app.post(ROUTES.TIMEBACK.HEARTBEAT, heartbeat.POST);
3045
3154
  } else if (integrations?.timeback === null) {
3046
3155
  app.post(
3047
3156
  "/api/integrations/timeback/end-activity",
@@ -3058,6 +3167,13 @@ async function registerBuiltinRoutes(app, integrations) {
3058
3167
  __playcademyDevWarning: "timeback-not-configured"
3059
3168
  })
3060
3169
  );
3170
+ app.post(
3171
+ "/api/integrations/timeback/heartbeat",
3172
+ async (c) => c.json({
3173
+ status: "ok",
3174
+ __playcademyDevWarning: "timeback-not-configured"
3175
+ })
3176
+ );
3061
3177
  }
3062
3178
  }
3063
3179
 
@@ -1,7 +1,7 @@
1
1
  {
2
- "cliVersion": "0.19.0",
3
- "sdkVersion": "0.4.0",
4
- "runtimeBuildId": "4086a66aa2f8",
5
- "inputFingerprint": "4086a66aa2f8f08916caeed739b88de02ca5fe45e5de235b98dd1f9e3513d196",
2
+ "cliVersion": "0.19.1-beta.10",
3
+ "sdkVersion": "0.5.0-beta.1",
4
+ "runtimeBuildId": "68aee00c09c6",
5
+ "inputFingerprint": "68aee00c09c62e7838d5d93a1b93c278d33bc528fb776834d2f27abbcd75de43",
6
6
  "entry": "index.js"
7
7
  }
package/dist/utils.js CHANGED
@@ -440,7 +440,8 @@ var BADGES = {
440
440
  // ../constants/src/timeback.ts
441
441
  var TIMEBACK_ROUTES = {
442
442
  END_ACTIVITY: "/integrations/timeback/end-activity",
443
- GET_XP: "/integrations/timeback/xp"
443
+ GET_XP: "/integrations/timeback/xp",
444
+ HEARTBEAT: "/integrations/timeback/heartbeat"
444
445
  };
445
446
 
446
447
  // ../constants/src/workers.ts
@@ -451,6 +452,8 @@ var WORKER_NAMING = {
451
452
  STAGING_SUFFIX: "-staging"
452
453
  };
453
454
  var SECRETS_PREFIX = "secrets_";
455
+ var CLOUDFLARE_COMPATIBILITY_DATE = "2025-10-11";
456
+ var BACKEND_COMPATIBILITY_FLAGS = ["nodejs_compat"];
454
457
 
455
458
  // src/lib/config/loader.ts
456
459
  init_file_loader();
@@ -475,7 +478,7 @@ var DEFAULT_API_ROUTES_DIRECTORY = join2(SERVER_ROOT_DIRECTORY, "api");
475
478
  // ../better-auth/package.json
476
479
  var package_default = {
477
480
  name: "@playcademy/better-auth",
478
- version: "0.0.5",
481
+ version: "0.0.6-beta.10",
479
482
  type: "module",
480
483
  exports: {
481
484
  "./server": {
@@ -546,8 +549,6 @@ var TSCONFIG_REQUIRED_INCLUDES = [
546
549
  var BUCKET_ALWAYS_SKIP = [".git", ".DS_Store", ".gitignore", ...ENV_FILES];
547
550
 
548
551
  // src/constants/cloudflare.ts
549
- var CLOUDFLARE_COMPATIBILITY_DATE = "2025-10-11";
550
- var BACKEND_COMPATIBILITY_FLAGS = ["nodejs_compat"];
551
552
  var CLOUDFLARE_BINDINGS = {
552
553
  /** R2 bucket binding name */
553
554
  BUCKET: "BUCKET",
@@ -1961,7 +1962,8 @@ var ROUTES = {
1961
1962
  /** TimeBack integration routes */
1962
1963
  TIMEBACK: {
1963
1964
  END_ACTIVITY: `/api${TIMEBACK_ROUTES.END_ACTIVITY}`,
1964
- GET_XP: `/api${TIMEBACK_ROUTES.GET_XP}`
1965
+ GET_XP: `/api${TIMEBACK_ROUTES.GET_XP}`,
1966
+ HEARTBEAT: `/api${TIMEBACK_ROUTES.HEARTBEAT}`
1965
1967
  }
1966
1968
  };
1967
1969
 
@@ -2529,7 +2531,7 @@ import { existsSync as existsSync8, mkdirSync as mkdirSync2, writeFileSync as wr
2529
2531
  import { dirname as dirname4, join as join13 } from "node:path";
2530
2532
 
2531
2533
  // src/version.ts
2532
- var cliVersion = false ? "0.0.0-dev" : "0.19.0";
2534
+ var cliVersion = false ? "0.0.0-dev" : "0.19.1-beta.10";
2533
2535
 
2534
2536
  // src/lib/build/binary-resource.ts
2535
2537
  function writeFileTree(baseDir, files) {
@@ -5232,7 +5234,10 @@ function createEsbuildConfig(entryCode, paths, bundleConfig, customRoutesDir, op
5232
5234
  // └─────────────────────────────────────────────────────────────────────┘
5233
5235
  "@game-server": join18(workspace, "server")
5234
5236
  },
5235
- plugins: [createBackendNodeCompatPlugin(), createForbiddenRuntimeImportsPlugin(workspace)],
5237
+ plugins: [
5238
+ createBackendNodeCompatPlugin({ compatibilityDate: options.compatibilityDate }),
5239
+ createForbiddenRuntimeImportsPlugin(workspace)
5240
+ ],
5236
5241
  external: []
5237
5242
  // Native workerd builtins are externalized via the compat plugin
5238
5243
  };
package/dist/version.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/version.ts
2
- var cliVersion = false ? "0.0.0-dev" : "0.19.0";
2
+ var cliVersion = false ? "0.0.0-dev" : "0.19.1-beta.10";
3
3
  export {
4
4
  cliVersion
5
5
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "playcademy",
3
- "version": "0.19.0",
3
+ "version": "0.19.1-beta.10",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -50,7 +50,7 @@
50
50
  },
51
51
  "dependencies": {
52
52
  "@inquirer/prompts": "^7.8.6",
53
- "@playcademy/sdk": "0.4.0",
53
+ "@playcademy/sdk": "0.5.0",
54
54
  "chokidar": "^4.0.3",
55
55
  "colorette": "^2.0.20",
56
56
  "commander": "^14.0.1",