playcademy 0.14.3 → 0.14.4-alpha.2

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.
@@ -8,6 +8,61 @@ export { GAME_WORKER_DOMAINS, PLAYCADEMY_BASE_URLS, PLAYCADEMY_DOMAINS } from '@
8
8
  * Used when integrations.customRoutes.directory is not specified in config
9
9
  */
10
10
  declare const DEFAULT_API_ROUTES_DIRECTORY = "server/api";
11
+ /**
12
+ * Server root directory (fixed location)
13
+ */
14
+ declare const SERVER_ROOT_DIRECTORY = "server";
15
+ /**
16
+ * Server library/utilities directory (fixed location)
17
+ */
18
+ declare const SERVER_LIB_DIRECTORY = "server/lib";
19
+ /**
20
+ * Auth routes subdirectory name within API directory (fixed structure)
21
+ */
22
+ declare const AUTH_API_SUBDIRECTORY = "auth";
23
+ /**
24
+ * Sample routes subdirectory name within API directory (fixed structure)
25
+ */
26
+ declare const SAMPLE_API_SUBDIRECTORY = "sample";
27
+ /**
28
+ * Sample route filenames
29
+ */
30
+ declare const SAMPLE_CUSTOM_FILENAME = "custom.ts";
31
+ declare const SAMPLE_DATABASE_FILENAME = "database.ts";
32
+ declare const SAMPLE_KV_FILENAME = "kv.ts";
33
+ declare const SAMPLE_BUCKET_FILENAME = "bucket.ts";
34
+
35
+ /**
36
+ * Auth-related constants and dependency versions
37
+ *
38
+ * Versions are read from workspace catalog at build time
39
+ */
40
+ /**
41
+ * Better Auth version to install in user projects
42
+ * Read from workspace catalog at CLI build time
43
+ */
44
+ declare const BETTER_AUTH_VERSION: string;
45
+ /**
46
+ * @playcademy/auth version to install in user projects
47
+ */
48
+ declare const PLAYCADEMY_AUTH_VERSION = "latest";
49
+ /**
50
+ * Auth provider display names
51
+ */
52
+ declare const AUTH_PROVIDER_NAMES: Record<string, string>;
53
+ /**
54
+ * OAuth callback URL pattern
55
+ * Replace {gameUrl} with actual game URL and {provider} with provider name
56
+ */
57
+ declare const OAUTH_CALLBACK_URL_PATTERN = "{gameUrl}/api/auth/callback/{provider}";
58
+ /**
59
+ * Placeholder game URL for setup instructions
60
+ */
61
+ declare const PLACEHOLDER_GAME_URL = "https://your-game.playcademy.gg";
62
+ /**
63
+ * Auth configuration filename (fixed structure)
64
+ */
65
+ declare const AUTH_CONFIG_FILE = "auth.ts";
11
66
 
12
67
  /**
13
68
  * Bucket-related constants
@@ -66,6 +121,11 @@ declare const MINIFLARE_D1_DIRECTORY = "miniflare-D1DatabaseObject";
66
121
  * This matches the pattern used by Vite, Bun, and other modern tools.
67
122
  */
68
123
  declare const ENV_FILES: readonly [".env", ".env.development", ".env.local"];
124
+ /**
125
+ * Environment example file (template for required environment variables)
126
+ * Safe to commit to version control with placeholder values
127
+ */
128
+ declare const ENV_EXAMPLE_FILE = ".env.example";
69
129
  /**
70
130
  * TypeScript config files to check (in priority order)
71
131
  *
@@ -163,4 +223,4 @@ declare const DEFAULT_PORTS: {
163
223
  */
164
224
  declare const CONFIG_FILE_NAMES: string[];
165
225
 
166
- export { 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, DEFAULT_API_ROUTES_DIRECTORY, DEFAULT_DATABASE_DIRECTORY, DEFAULT_PORTS, DEFAULT_SEED_FILE_NAME, ENV_FILES, MINIFLARE_D1_DIRECTORY, SCHEMA_INDEX_FILE, SCHEMA_SUBDIRECTORY, SSO_AUTH_TIMEOUT_MS, TSCONFIG_FILES, WORKSPACE_NAME };
226
+ 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, CLOUDFLARE_COMPATIBILITY_DATE, CONFIG_FILE_NAMES, DEFAULT_API_ROUTES_DIRECTORY, DEFAULT_DATABASE_DIRECTORY, DEFAULT_PORTS, DEFAULT_SEED_FILE_NAME, ENV_EXAMPLE_FILE, ENV_FILES, MINIFLARE_D1_DIRECTORY, OAUTH_CALLBACK_URL_PATTERN, PLACEHOLDER_GAME_URL, PLAYCADEMY_AUTH_VERSION, SAMPLE_API_SUBDIRECTORY, SAMPLE_BUCKET_FILENAME, SAMPLE_CUSTOM_FILENAME, SAMPLE_DATABASE_FILENAME, SAMPLE_KV_FILENAME, SCHEMA_INDEX_FILE, SCHEMA_SUBDIRECTORY, SERVER_LIB_DIRECTORY, SERVER_ROOT_DIRECTORY, SSO_AUTH_TIMEOUT_MS, TSCONFIG_FILES, WORKSPACE_NAME };
package/dist/constants.js CHANGED
@@ -1,5 +1,137 @@
1
1
  // src/constants/api.ts
2
2
  var DEFAULT_API_ROUTES_DIRECTORY = "server/api";
3
+ var SERVER_ROOT_DIRECTORY = "server";
4
+ var SERVER_LIB_DIRECTORY = "server/lib";
5
+ var AUTH_API_SUBDIRECTORY = "auth";
6
+ var SAMPLE_API_SUBDIRECTORY = "sample";
7
+ var SAMPLE_CUSTOM_FILENAME = "custom.ts";
8
+ var SAMPLE_DATABASE_FILENAME = "database.ts";
9
+ var SAMPLE_KV_FILENAME = "kv.ts";
10
+ var SAMPLE_BUCKET_FILENAME = "bucket.ts";
11
+
12
+ // ../../package.json
13
+ var package_default = {
14
+ name: "playcademy",
15
+ devDependencies: {
16
+ "@aws-sdk/client-s3": "^3.787.0",
17
+ "@eslint/js": "^9.24.0",
18
+ "@ianvs/prettier-plugin-sort-imports": "^4.4.2",
19
+ "@pulumi/pulumi": "^3.195.0",
20
+ "@types/bun": "latest",
21
+ commander: "^14.0.0",
22
+ eslint: "^9.24.0",
23
+ globals: "^16.0.0",
24
+ husky: "^9.1.7",
25
+ jscodeshift: "^17.3.0",
26
+ "lint-staged": "^15.5.1",
27
+ prettier: "3.5.3",
28
+ "prettier-plugin-svelte": "^3.3.3",
29
+ rimraf: "^6.0.1",
30
+ sharp: "^0.34.2",
31
+ typedoc: "^0.28.5",
32
+ "typedoc-plugin-markdown": "^4.7.0",
33
+ "typedoc-vitepress-theme": "^1.1.2",
34
+ "typescript-eslint": "^8.30.1",
35
+ "yocto-spinner": "^0.2.2"
36
+ },
37
+ peerDependencies: {
38
+ typescript: "^5"
39
+ },
40
+ private: true,
41
+ scripts: {
42
+ build: "bun run scripts/build.ts",
43
+ "build:types": "bun tsc -b packages/data",
44
+ clean: "bun scripts/clean.ts && bun i",
45
+ "docs:commit": "bun scripts/docs-commit.ts",
46
+ dev: "bunx sst dev",
47
+ "dev:reset": "sst shell -- bun run scripts/dev-reset.ts",
48
+ "db:reseed": "sst shell -- bun run scripts/dev-reseed-db.ts",
49
+ "db:sync": "sst shell -- bun run scripts/dev-sync-db.ts",
50
+ "db:studio": "sst shell -- bun run --filter @playcademy/data studio",
51
+ "upload-games": "sst shell -- bun run scripts/upload-games.ts",
52
+ "upload-items": "sst shell -- bun run scripts/upload-items.ts",
53
+ "upload-sprites": "sst shell -- bun run scripts/upload-sprites.ts",
54
+ "upload-static-assets": "sst shell -- bun run scripts/upload-static-assets.ts",
55
+ "upload:all": "sst shell -- bun run scripts/upload-all.ts",
56
+ "sync-engine-assets": "bun scripts/sync-engine-assets.ts",
57
+ "sync-vite-templates": "bun scripts/sync-vite-templates.ts",
58
+ "sync-godot-template": "bun scripts/sync-godot-template.ts",
59
+ "sync:all": "bun scripts/sync-all.ts",
60
+ "setup-cloudflare": "bun scripts/setup-cloudflare-dispatch.ts",
61
+ "list-s3-bucket": "sst shell -- bun scripts/list-s3-bucket.ts",
62
+ doctor: "bunx sst shell -- bun scripts/doctor.ts",
63
+ format: "bun run --filter '*' format",
64
+ lint: "bun run --filter '*' lint",
65
+ prepare: "husky",
66
+ sort: "bunx sort-package-json **/package.json",
67
+ test: "bun test",
68
+ "test:unit": "bun scripts/test-unit.ts",
69
+ "test:integration": "bun scripts/test-integration.ts",
70
+ "test:watch": "bun test --watch",
71
+ "docker:relay": "bun run scripts/docker-relay.ts",
72
+ "debug:leaderboard-notifications": "bunx sst shell -- bun scripts/debug/leaderboard-notifications.ts",
73
+ "timeback:caliper": "sst shell -- bun scripts/caliper-cli.ts",
74
+ notify: "NODE_ENV=productionbunx sst shell -- bun scripts/notifications-cli.ts"
75
+ },
76
+ type: "module",
77
+ workspaces: {
78
+ packages: [
79
+ "apps/*",
80
+ "packages/*",
81
+ "games/*",
82
+ "templates/*"
83
+ ],
84
+ catalog: {
85
+ sst: "^3.14.11",
86
+ dedent: "^1.6.0",
87
+ typescript: "^5.7.2",
88
+ vite: "^6.3.5",
89
+ eslint: "^9.24.0",
90
+ prettier: "3.5.3",
91
+ "@types/bun": "latest",
92
+ jose: "^5.2.3",
93
+ zod: "^3.25.53",
94
+ "better-auth": "1.3.33"
95
+ },
96
+ catalogs: {
97
+ svelte: {
98
+ svelte: "^5.23.1",
99
+ "@sveltejs/adapter-auto": "^6.0.0",
100
+ "@sveltejs/kit": "^2.16.0",
101
+ "@sveltejs/vite-plugin-svelte": "^5.0.3",
102
+ "svelte-check": "^4.2.1",
103
+ "prettier-plugin-svelte": "^3.3.3",
104
+ "eslint-plugin-svelte": "^3.0.0"
105
+ },
106
+ database: {
107
+ "drizzle-orm": "^0.42.0",
108
+ "drizzle-kit": "^0.31.0",
109
+ "drizzle-zod": "^0.7.1"
110
+ },
111
+ aws: {
112
+ "@aws-sdk/client-s3": "^3.787.0",
113
+ "@aws-sdk/s3-request-presigner": "^3.787.0"
114
+ },
115
+ linting: {
116
+ "typescript-eslint": "^8.30.1",
117
+ "@eslint/js": "^9.24.0",
118
+ "eslint-config-prettier": "^10.0.1"
119
+ }
120
+ }
121
+ }
122
+ };
123
+
124
+ // src/constants/auth.ts
125
+ var BETTER_AUTH_VERSION = package_default.workspaces.catalog["better-auth"];
126
+ var PLAYCADEMY_AUTH_VERSION = "latest";
127
+ var AUTH_PROVIDER_NAMES = {
128
+ email: "Email/password",
129
+ github: "GitHub",
130
+ google: "Google"
131
+ };
132
+ var OAUTH_CALLBACK_URL_PATTERN = "{gameUrl}/api/auth/callback/{provider}";
133
+ var PLACEHOLDER_GAME_URL = "https://your-game.playcademy.gg";
134
+ var AUTH_CONFIG_FILE = "auth.ts";
3
135
 
4
136
  // src/constants/config.ts
5
137
  var ENV_FILES = [
@@ -10,6 +142,7 @@ var ENV_FILES = [
10
142
  ".env.local"
11
143
  // Overrides all (highest priority)
12
144
  ];
145
+ var ENV_EXAMPLE_FILE = ".env.example";
13
146
  var TSCONFIG_FILES = [
14
147
  "tsconfig.app.json",
15
148
  // Modern tooling (try first)
@@ -137,6 +270,10 @@ var BADGES = {
137
270
  FIRST_GAME: ITEM_SLUGS.FIRST_GAME_BADGE
138
271
  };
139
272
  export {
273
+ AUTH_API_SUBDIRECTORY,
274
+ AUTH_CONFIG_FILE,
275
+ AUTH_PROVIDER_NAMES,
276
+ BETTER_AUTH_VERSION,
140
277
  BUCKET_ALWAYS_SKIP,
141
278
  CALLBACK_PATH,
142
279
  CALLBACK_PORT,
@@ -151,13 +288,24 @@ export {
151
288
  DEFAULT_DATABASE_DIRECTORY,
152
289
  DEFAULT_PORTS,
153
290
  DEFAULT_SEED_FILE_NAME,
291
+ ENV_EXAMPLE_FILE,
154
292
  ENV_FILES,
155
293
  GAME_WORKER_DOMAINS,
156
294
  MINIFLARE_D1_DIRECTORY,
295
+ OAUTH_CALLBACK_URL_PATTERN,
296
+ PLACEHOLDER_GAME_URL,
297
+ PLAYCADEMY_AUTH_VERSION,
157
298
  PLAYCADEMY_BASE_URLS,
158
299
  PLAYCADEMY_DOMAINS,
300
+ SAMPLE_API_SUBDIRECTORY,
301
+ SAMPLE_BUCKET_FILENAME,
302
+ SAMPLE_CUSTOM_FILENAME,
303
+ SAMPLE_DATABASE_FILENAME,
304
+ SAMPLE_KV_FILENAME,
159
305
  SCHEMA_INDEX_FILE,
160
306
  SCHEMA_SUBDIRECTORY,
307
+ SERVER_LIB_DIRECTORY,
308
+ SERVER_ROOT_DIRECTORY,
161
309
  SSO_AUTH_TIMEOUT_MS,
162
310
  TSCONFIG_FILES,
163
311
  WORKSPACE_NAME
package/dist/db.js CHANGED
@@ -22,6 +22,121 @@ var init_file_loader = __esm({
22
22
  import { copyFileSync, existsSync, mkdirSync, readdirSync, unlinkSync } from "fs";
23
23
  import { join as join2 } from "path";
24
24
 
25
+ // ../../package.json
26
+ var package_default = {
27
+ name: "playcademy",
28
+ devDependencies: {
29
+ "@aws-sdk/client-s3": "^3.787.0",
30
+ "@eslint/js": "^9.24.0",
31
+ "@ianvs/prettier-plugin-sort-imports": "^4.4.2",
32
+ "@pulumi/pulumi": "^3.195.0",
33
+ "@types/bun": "latest",
34
+ commander: "^14.0.0",
35
+ eslint: "^9.24.0",
36
+ globals: "^16.0.0",
37
+ husky: "^9.1.7",
38
+ jscodeshift: "^17.3.0",
39
+ "lint-staged": "^15.5.1",
40
+ prettier: "3.5.3",
41
+ "prettier-plugin-svelte": "^3.3.3",
42
+ rimraf: "^6.0.1",
43
+ sharp: "^0.34.2",
44
+ typedoc: "^0.28.5",
45
+ "typedoc-plugin-markdown": "^4.7.0",
46
+ "typedoc-vitepress-theme": "^1.1.2",
47
+ "typescript-eslint": "^8.30.1",
48
+ "yocto-spinner": "^0.2.2"
49
+ },
50
+ peerDependencies: {
51
+ typescript: "^5"
52
+ },
53
+ private: true,
54
+ scripts: {
55
+ build: "bun run scripts/build.ts",
56
+ "build:types": "bun tsc -b packages/data",
57
+ clean: "bun scripts/clean.ts && bun i",
58
+ "docs:commit": "bun scripts/docs-commit.ts",
59
+ dev: "bunx sst dev",
60
+ "dev:reset": "sst shell -- bun run scripts/dev-reset.ts",
61
+ "db:reseed": "sst shell -- bun run scripts/dev-reseed-db.ts",
62
+ "db:sync": "sst shell -- bun run scripts/dev-sync-db.ts",
63
+ "db:studio": "sst shell -- bun run --filter @playcademy/data studio",
64
+ "upload-games": "sst shell -- bun run scripts/upload-games.ts",
65
+ "upload-items": "sst shell -- bun run scripts/upload-items.ts",
66
+ "upload-sprites": "sst shell -- bun run scripts/upload-sprites.ts",
67
+ "upload-static-assets": "sst shell -- bun run scripts/upload-static-assets.ts",
68
+ "upload:all": "sst shell -- bun run scripts/upload-all.ts",
69
+ "sync-engine-assets": "bun scripts/sync-engine-assets.ts",
70
+ "sync-vite-templates": "bun scripts/sync-vite-templates.ts",
71
+ "sync-godot-template": "bun scripts/sync-godot-template.ts",
72
+ "sync:all": "bun scripts/sync-all.ts",
73
+ "setup-cloudflare": "bun scripts/setup-cloudflare-dispatch.ts",
74
+ "list-s3-bucket": "sst shell -- bun scripts/list-s3-bucket.ts",
75
+ doctor: "bunx sst shell -- bun scripts/doctor.ts",
76
+ format: "bun run --filter '*' format",
77
+ lint: "bun run --filter '*' lint",
78
+ prepare: "husky",
79
+ sort: "bunx sort-package-json **/package.json",
80
+ test: "bun test",
81
+ "test:unit": "bun scripts/test-unit.ts",
82
+ "test:integration": "bun scripts/test-integration.ts",
83
+ "test:watch": "bun test --watch",
84
+ "docker:relay": "bun run scripts/docker-relay.ts",
85
+ "debug:leaderboard-notifications": "bunx sst shell -- bun scripts/debug/leaderboard-notifications.ts",
86
+ "timeback:caliper": "sst shell -- bun scripts/caliper-cli.ts",
87
+ notify: "NODE_ENV=productionbunx sst shell -- bun scripts/notifications-cli.ts"
88
+ },
89
+ type: "module",
90
+ workspaces: {
91
+ packages: [
92
+ "apps/*",
93
+ "packages/*",
94
+ "games/*",
95
+ "templates/*"
96
+ ],
97
+ catalog: {
98
+ sst: "^3.14.11",
99
+ dedent: "^1.6.0",
100
+ typescript: "^5.7.2",
101
+ vite: "^6.3.5",
102
+ eslint: "^9.24.0",
103
+ prettier: "3.5.3",
104
+ "@types/bun": "latest",
105
+ jose: "^5.2.3",
106
+ zod: "^3.25.53",
107
+ "better-auth": "1.3.33"
108
+ },
109
+ catalogs: {
110
+ svelte: {
111
+ svelte: "^5.23.1",
112
+ "@sveltejs/adapter-auto": "^6.0.0",
113
+ "@sveltejs/kit": "^2.16.0",
114
+ "@sveltejs/vite-plugin-svelte": "^5.0.3",
115
+ "svelte-check": "^4.2.1",
116
+ "prettier-plugin-svelte": "^3.3.3",
117
+ "eslint-plugin-svelte": "^3.0.0"
118
+ },
119
+ database: {
120
+ "drizzle-orm": "^0.42.0",
121
+ "drizzle-kit": "^0.31.0",
122
+ "drizzle-zod": "^0.7.1"
123
+ },
124
+ aws: {
125
+ "@aws-sdk/client-s3": "^3.787.0",
126
+ "@aws-sdk/s3-request-presigner": "^3.787.0"
127
+ },
128
+ linting: {
129
+ "typescript-eslint": "^8.30.1",
130
+ "@eslint/js": "^9.24.0",
131
+ "eslint-config-prettier": "^10.0.1"
132
+ }
133
+ }
134
+ }
135
+ };
136
+
137
+ // src/constants/auth.ts
138
+ var BETTER_AUTH_VERSION = package_default.workspaces.catalog["better-auth"];
139
+
25
140
  // src/constants/config.ts
26
141
  var ENV_FILES = [
27
142
  ".env",
@@ -690,6 +805,9 @@ var logger = {
690
805
  }
691
806
  };
692
807
 
808
+ // src/lib/secrets/env.ts
809
+ init_file_loader();
810
+
693
811
  // src/lib/config/loader.ts
694
812
  init_file_loader();
695
813
 
@@ -6,7 +6,7 @@
6
6
 
7
7
  import { cors } from 'hono/cors'
8
8
 
9
- import { PlaycademyClient } from '@playcademy/sdk/server'
9
+ import { PlaycademyClient, verifyGameToken } from '@playcademy/sdk/server'
10
10
 
11
11
  import { populateProcessEnv, reconstructSecrets } from './setup'
12
12
 
@@ -17,19 +17,23 @@ import type { RuntimeConfig } from './types'
17
17
  /**
18
18
  * Register CORS middleware
19
19
  *
20
+ * Allows cross-origin requests with credentials support.
21
+ * This is required for authentication systems like Better Auth that use cookies.
22
+ *
20
23
  * TODO: Harden CORS in production - restrict to trusted origins:
21
- * - Game's assetBundleBase (for hosted games)
24
+ * - Game's deploymentUrl (for hosted games)
22
25
  * - Game's externalUrl (for external games)
23
- * - Platform frontend domains (hub.playcademy.com, hub.dev.playcademy.net)
24
- * This would require passing game metadata through env bindings during deployment
26
+ * - Platform domains (hub.playcademy.com, hub.dev.playcademy.net)
25
27
  */
26
28
  export function registerCors(app: Hono<HonoEnv>): void {
27
29
  app.use(
28
30
  '*',
29
31
  cors({
30
- origin: '*', // Permissive for now
32
+ origin: origin => origin, // Echo back the origin (permissive but allows credentials)
33
+ credentials: true, // Required for cookies/sessions (e.g., Better Auth)
31
34
  allowMethods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
32
35
  allowHeaders: ['Content-Type', 'Authorization'],
36
+ // exposeHeaders: ['set-auth-token'],
33
37
  }),
34
38
  )
35
39
  }
@@ -72,3 +76,96 @@ export function registerSdkInit(app: Hono<HonoEnv>, config: RuntimeConfig): void
72
76
  await next()
73
77
  })
74
78
  }
79
+
80
+ /**
81
+ * Register Playcademy user middleware
82
+ *
83
+ * Verifies platform game tokens and populates c.get('playcademyUser')
84
+ * if a valid token is present in the Authorization header
85
+ */
86
+ export function registerPlaycademyUser(app: Hono<HonoEnv>): void {
87
+ app.use('*', async (c, next) => {
88
+ const authHeader = c.req.header('Authorization')
89
+
90
+ if (authHeader?.startsWith('Bearer ')) {
91
+ const token = authHeader.slice(7)
92
+
93
+ try {
94
+ const result = await verifyGameToken(token)
95
+
96
+ c.set('playcademyUser', result.user)
97
+ } catch {
98
+ // Invalid/expired token - that's fine, just don't set user
99
+ // Routes can decide if they require authentication
100
+ }
101
+ }
102
+
103
+ await next()
104
+ })
105
+ }
106
+
107
+ /**
108
+ * Register API 404 handler
109
+ *
110
+ * Returns a helpful JSON response for unmatched API routes.
111
+ * Should be registered after all API routes but before asset fallback.
112
+ */
113
+ export function registerApiNotFoundHandler(app: Hono<HonoEnv>): void {
114
+ app.all('/api/*', async c => {
115
+ return c.json(
116
+ {
117
+ error: 'Not Found',
118
+ message: `Route ${c.req.method} ${c.req.path} not found`,
119
+ path: c.req.path,
120
+ method: c.req.method,
121
+ },
122
+ 404,
123
+ )
124
+ })
125
+ }
126
+
127
+ /**
128
+ * Register asset fallback handler
129
+ *
130
+ * Serves static assets from Workers Assets binding.
131
+ * MUST be registered last as it's a catch-all for non-API routes.
132
+ *
133
+ * SPA Routing Support:
134
+ * - First tries to fetch the requested path (e.g., /assets/main.js)
135
+ * - If 404 and NOT an API route, falls back to /index.html
136
+ * - This allows client-side routing (React Router) to work on refresh
137
+ */
138
+ export function registerAssetFallback(app: Hono<HonoEnv>): void {
139
+ app.get('*', async c => {
140
+ if (!c.env.ASSETS) {
141
+ return c.json(
142
+ {
143
+ error: 'Not Found',
144
+ message: 'Asset not found',
145
+ path: c.req.path,
146
+ },
147
+ 404,
148
+ )
149
+ }
150
+
151
+ // Try to fetch the requested asset
152
+ const response = await c.env.ASSETS.fetch(c.req.raw)
153
+
154
+ // If found, return it
155
+ if (response.status !== 404) {
156
+ return response
157
+ }
158
+
159
+ // If not found and it's a file request (has extension), return 404
160
+ const path = new URL(c.req.url).pathname
161
+ if (path.includes('.')) {
162
+ return response
163
+ }
164
+
165
+ // Otherwise, fall back to index.html for predictable routing
166
+ const indexUrl = new URL(c.req.url)
167
+ indexUrl.pathname = '/index.html'
168
+
169
+ return await c.env.ASSETS.fetch(new Request(indexUrl.toString()))
170
+ })
171
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Session Middleware Factory
3
+ *
4
+ * Provides a factory function for creating Better Auth session middleware.
5
+ * Games using Better Auth can use this to populate c.get('user') automatically.
6
+ *
7
+ * This is a factory (not a direct middleware) because edge-play can't import
8
+ * game-specific code. Games pass their getAuth function to create the middleware.
9
+ */
10
+
11
+ import type { Context } from 'hono'
12
+ import type { HonoEnv } from '../types'
13
+
14
+ /**
15
+ * Create session middleware that populates c.get('user') from Better Auth sessions
16
+ *
17
+ * @param getAuth - Function that returns the game's Better Auth instance
18
+ * @returns Middleware function
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * // In your bundled entry point (auto-injected by CLI)
23
+ * import { createSessionMiddleware } from '@playcademy/edge-play'
24
+ * import { getAuth } from './server/lib/auth'
25
+ *
26
+ * app.use('*', createSessionMiddleware(getAuth))
27
+ * ```
28
+ */
29
+ export function createSessionMiddleware(
30
+ getAuth: (c: Context<HonoEnv>) => {
31
+ api: { getSession: (options: { headers: Headers }) => Promise<{ user: unknown } | null> }
32
+ },
33
+ ) {
34
+ return async (c: Context<HonoEnv>, next: () => Promise<void>) => {
35
+ const auth = getAuth(c)
36
+ const session = await auth.api.getSession({ headers: c.req.raw.headers })
37
+
38
+ if (session?.user) {
39
+ c.set('user', session.user)
40
+ }
41
+
42
+ await next()
43
+ }
44
+ }
@@ -6,17 +6,29 @@
6
6
  *
7
7
  * Bundled with esbuild and deployed to Cloudflare Workers (or AWS Lambda).
8
8
  * Config is injected at build time via esbuild's `define` option.
9
+ *
10
+ * DO NOT REMOVE any code wrapped by ⚠️ BUILD_MARKER: <marker> ⚠️
9
11
  */
10
12
 
11
13
  import { Hono } from 'hono'
12
14
 
13
- import { registerCors, registerEnvSetup, registerSdkInit } from './entry/middleware'
15
+ import {
16
+ registerApiNotFoundHandler,
17
+ registerAssetFallback,
18
+ registerCors,
19
+ registerEnvSetup,
20
+ registerPlaycademyUser,
21
+ registerSdkInit,
22
+ } from './entry/middleware'
14
23
  import { setupProcessGlobal } from './entry/setup'
15
24
  import { registerBuiltinRoutes } from './register-routes'
16
25
 
17
26
  import type { RuntimeConfig } from './entry/types'
18
27
  import type { HonoEnv } from './types'
19
28
 
29
+ // DO NOT REMOVE THE BELOW COMMENT
30
+ // ⚠️ BUILD_MARKER: CUSTOM_ROUTE_IMPORTS ⚠️
31
+
20
32
  /**
21
33
  * Config injected at build time by esbuild
22
34
  *
@@ -43,6 +55,10 @@ const app = new Hono<HonoEnv>()
43
55
  registerCors(app)
44
56
  registerEnvSetup(app, PLAYCADEMY_CONFIG)
45
57
  registerSdkInit(app, PLAYCADEMY_CONFIG)
58
+ registerPlaycademyUser(app)
59
+
60
+ // DO NOT REMOVE THE BELOW COMMENT
61
+ // ⚠️ BUILD_MARKER: SESSION_MIDDLEWARE ⚠️
46
62
 
47
63
  // Register built-in integration routes based on enabled integrations
48
64
  // This function conditionally imports and registers routes like:
@@ -54,4 +70,20 @@ registerSdkInit(app, PLAYCADEMY_CONFIG)
54
70
  // its route code is completely removed from the bundle.
55
71
  await registerBuiltinRoutes(app, PLAYCADEMY_CONFIG.integrations)
56
72
 
73
+ // DO NOT REMOVE THE BELOW COMMENT
74
+ // ⚠️ BUILD_MARKER: CUSTOM_ROUTES ⚠️
75
+
76
+ // Register API 404 handler
77
+ // Returns JSON error for unmatched /api/* routes
78
+ // Must be registered after all API routes
79
+ registerApiNotFoundHandler(app)
80
+
81
+ // Register static asset fallback handler
82
+ // Serves frontend assets from Workers Assets binding
83
+ // MUST be registered last as it uses a wildcard GET route (app.get('*', ...))
84
+ //
85
+ // In production: Serves frontend assets from Workers Assets binding
86
+ // In local dev: Returns 404 (Vite serves the frontend separately)
87
+ registerAssetFallback(app)
88
+
57
89
  export default app
@@ -1,2 +1,4 @@
1
- export { registerBuiltinRoutes } from './register-routes'
2
1
  export { ROUTES } from './constants'
2
+
3
+ export { registerBuiltinRoutes } from './register-routes'
4
+ export { createSessionMiddleware } from './entry/session'
@@ -17,10 +17,6 @@ import type { HonoEnv, Integrations } from './types'
17
17
  * @param integrations - Enabled integrations from config
18
18
  */
19
19
  export async function registerBuiltinRoutes(app: Hono<HonoEnv>, integrations?: Integrations) {
20
- // Root page (always included)
21
- const root = await import('./routes/root')
22
- app.get('/', root.GET)
23
-
24
20
  // Route discovery (always included)
25
21
  const routesIndex = await import('./routes/index')
26
22
  app.get(ROUTES.INDEX, routesIndex.GET)
@@ -1,10 +1,8 @@
1
1
  /**
2
2
  * Type definitions for game backend runtime
3
3
  *
4
- * Defines types for:
5
- * - Cloudflare Worker environment bindings
6
- * - Hono context with typed env
7
- * - Route handler signatures
4
+ * Base types that can be augmented by game-specific generated types.
5
+ * Games extend these via playcademy-env.d.ts module augmentation.
8
6
  */
9
7
 
10
8
  /// <reference types="@cloudflare/workers-types" />
@@ -17,7 +15,7 @@ import type { RouteMetadata } from './entry/types'
17
15
  */
18
16
  export interface Integrations {
19
17
  timeback?: unknown
20
- auth?: { enabled: boolean }
18
+ auth?: boolean
21
19
  storage?: { enabled: boolean }
22
20
  realtime?: { enabled: boolean }
23
21
  }
@@ -39,6 +37,9 @@ export interface ServerEnv {
39
37
  /** Game-specific secrets (optional) */
40
38
  secrets?: Record<string, string>
41
39
 
40
+ /** Workers Assets binding for static files (Cloudflare-specific) */
41
+ ASSETS?: { fetch(request: Request): Promise<Response> }
42
+
42
43
  /** KV namespace binding (optional, Cloudflare-specific) */
43
44
  KV?: KVNamespace
44
45
 
@@ -59,6 +60,7 @@ export interface HonoVariables {
59
60
  sdk: PlaycademyClient
60
61
  config: PlaycademyConfig
61
62
  routeMetadata: Array<RouteMetadata>
63
+ [key: string]: unknown
62
64
  }
63
65
 
64
66
  /**