@playcademy/sdk 0.9.0 → 0.9.1-beta.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.
package/dist/types.d.ts CHANGED
@@ -1,8 +1,8 @@
1
+ import { UserRole, AUTH_PROVIDER_IDS } from '@playcademy/constants';
1
2
  import { InferSelectModel } from 'drizzle-orm';
2
3
  import * as drizzle_orm_pg_core from 'drizzle-orm/pg-core';
3
4
  import * as drizzle_zod from 'drizzle-zod';
4
5
  import { z } from 'zod';
5
- import { AUTH_PROVIDER_IDS } from '@playcademy/constants';
6
6
 
7
7
  /**
8
8
  * OAuth 2.0 implementation for the Playcademy SDK
@@ -26,105 +26,378 @@ interface RetryPolicy {
26
26
  }
27
27
 
28
28
  /**
29
- * TimeBack Enums & Literal Types
29
+ * User Types
30
30
  *
31
- * Basic type definitions used throughout the TimeBack integration.
31
+ * Enums, DTOs and API response types. Database row types are in @playcademy/data/types.
32
32
  *
33
- * @module types/timeback/types
34
- */
35
- /**
36
- * Valid TimeBack subject values for course configuration.
37
- * These are the supported subject values for OneRoster courses.
38
- */
39
- type TimebackSubject = 'Reading' | 'Language' | 'Vocabulary' | 'Social Studies' | 'Writing' | 'Science' | 'FastMath' | 'Math' | 'None';
40
- /**
41
- * Grade levels per AE OneRoster GradeEnum.
42
- * -1 = Pre-K, 0 = Kindergarten, 1-12 = Grades 1-12, 13 = AP
33
+ * @module types/user
43
34
  */
44
- type TimebackGrade = -1 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13;
35
+
36
+ type UserRoleEnumType = UserRole;
37
+ type DeveloperStatusEnumType = 'none' | 'pending' | 'approved';
38
+ type DeveloperStatusValue = DeveloperStatusEnumType;
39
+ type TimebackUserRole = 'administrator' | 'aide' | 'guardian' | 'parent' | 'proctor' | 'relative' | 'student' | 'teacher';
40
+ type TimebackOrgType = 'department' | 'school' | 'district' | 'local' | 'state' | 'national';
41
+ interface UserEnrollment {
42
+ gameId?: string;
43
+ courseId: string;
44
+ enrollmentIds?: {
45
+ active: string;
46
+ inactive?: string[];
47
+ };
48
+ grade: number;
49
+ subject: string;
50
+ orgId?: string;
51
+ }
52
+ interface UserOrganization {
53
+ id: string;
54
+ name: string | null;
55
+ type: TimebackOrgType | string;
56
+ isPrimary: boolean;
57
+ }
58
+ interface TimebackStudentProfile {
59
+ role: TimebackUserRole;
60
+ organizations: UserOrganization[];
61
+ }
62
+ interface UserTimebackData extends TimebackStudentProfile {
63
+ id: string;
64
+ enrollments: UserEnrollment[];
65
+ }
45
66
  /**
46
- * Valid Caliper subject values.
47
- * Matches OneRoster subjects, with "None" as a Caliper-specific fallback.
67
+ * OpenID Connect UserInfo claims (NOT a database row).
48
68
  */
49
- type CaliperSubject = 'Reading' | 'Language' | 'Vocabulary' | 'Social Studies' | 'Writing' | 'Science' | 'FastMath' | 'Math' | 'None';
69
+ interface UserInfo {
70
+ sub: string;
71
+ email: string;
72
+ name: string | null;
73
+ email_verified?: boolean;
74
+ given_name?: string;
75
+ family_name?: string;
76
+ issuer?: string;
77
+ lti_roles?: unknown;
78
+ lti_context?: unknown;
79
+ lti_resource_link?: unknown;
80
+ timeback_id?: string;
81
+ }
82
+ interface DeveloperStatusResponse {
83
+ status: DeveloperStatusEnumType;
84
+ }
85
+ interface DemoProfile {
86
+ displayName: string;
87
+ isDefault: boolean;
88
+ }
50
89
  /**
51
- * OneRoster organization types.
90
+ * Update shape for `client.demo.profile.update(...)`.
91
+ *
92
+ * Kept as a named type so callers typed against it pick up new fields
93
+ * automatically, but `displayName` is the only updatable field today and
94
+ * the server's `DemoProfileSchema` requires it — a no-field payload would
95
+ * 400 at runtime, so we model that at the type level too. When additional
96
+ * fields land, make them required/optional individually based on server
97
+ * validation, rather than blanket-optional.
52
98
  */
53
- type OrganizationType = 'department' | 'school' | 'district' | 'local' | 'state' | 'national';
99
+ interface DemoProfileUpdate {
100
+ displayName: string;
101
+ }
54
102
  /**
55
- * Lesson types for PowerPath integration.
103
+ * Authenticated user for API responses.
104
+ * Differs from UserRow: omits timebackId, adds hasTimebackAccount and timeback.
56
105
  */
57
- type LessonType = 'powerpath-100' | 'quiz' | 'test-out' | 'placement' | 'unit-test' | 'alpha-read-article' | null;
106
+ interface AuthenticatedUser {
107
+ id: string;
108
+ email: string;
109
+ emailVerified: boolean;
110
+ name: string | null;
111
+ image: string | null;
112
+ username: string | null;
113
+ role: UserRoleEnumType;
114
+ developerStatus: DeveloperStatusEnumType;
115
+ characterCreated: boolean;
116
+ createdAt: Date;
117
+ updatedAt: Date;
118
+ hasTimebackAccount: boolean;
119
+ timeback?: UserTimebackData;
120
+ }
121
+ interface GameUser {
122
+ id: string;
123
+ name: string | null;
124
+ role: UserRoleEnumType;
125
+ username: string | null;
126
+ email: string | null;
127
+ timeback?: UserTimebackData;
128
+ }
58
129
 
59
130
  /**
60
- * TimeBack Configuration Types
131
+ * Game Types
61
132
  *
62
- * Configuration interfaces for Organization, Course, Component,
63
- * Resource, and complete TimeBack setup.
133
+ * Literal types and API DTOs. Database row types are in @playcademy/data/types.
64
134
  *
65
- * @module types/timeback/config
135
+ * @module types/game
66
136
  */
67
137
 
138
+ type GameType = 'hosted' | 'external';
139
+ type GamePlatform = 'web' | 'godot' | 'unity' | (string & {});
68
140
  /**
69
- * Organization configuration for TimeBack (user input - optionals allowed)
141
+ * Game manifest file format (manifest.json).
142
+ * Note: createdAt is a string here because it's parsed from JSON file.
70
143
  */
71
- interface OrganizationConfig {
72
- /** Display name for your organization */
73
- name?: string;
74
- /** Organization type */
75
- type?: OrganizationType;
76
- /** Unique identifier (defaults to Playcademy's org) */
77
- identifier?: string;
144
+ interface ManifestV1 {
145
+ version: '1';
146
+ platform: string;
147
+ createdAt: string;
148
+ }
149
+ interface ManifestVersions {
150
+ vitePlugin?: string;
151
+ sdk?: string;
152
+ cli?: string;
153
+ vite?: string;
154
+ godotSdk?: string;
155
+ godot?: string;
156
+ }
157
+ interface ManifestV2 {
158
+ version: '2';
159
+ platform: string;
160
+ createdAt: string;
161
+ versions: ManifestVersions;
78
162
  }
163
+ type GameManifest = ManifestV1 | ManifestV2;
164
+ interface DomainValidationRecords {
165
+ ownership?: {
166
+ name?: string;
167
+ value?: string;
168
+ type?: string;
169
+ };
170
+ ssl?: {
171
+ txt_name?: string;
172
+ txt_value?: string;
173
+ }[];
174
+ }
175
+
79
176
  /**
80
- * Course goals for daily student targets
177
+ * Leaderboard Types
178
+ *
179
+ * @module types/leaderboard
81
180
  */
82
- interface CourseGoals {
83
- /** Target XP students should earn per day */
84
- dailyXp?: number;
85
- /** Target lessons per day */
86
- dailyLessons?: number;
87
- /** Target active minutes per day */
88
- dailyActiveMinutes?: number;
89
- /** Target accuracy percentage */
90
- dailyAccuracy?: number;
91
- /** Target mastered units per day */
92
- dailyMasteredUnits?: number;
181
+ type LeaderboardTimeframe = 'all_time' | 'monthly' | 'weekly' | 'daily';
182
+ interface LeaderboardOptions {
183
+ timeframe?: LeaderboardTimeframe;
184
+ limit?: number;
185
+ offset?: number;
186
+ gameId?: string;
187
+ }
188
+ interface LeaderboardEntry {
189
+ rank: number;
190
+ userId: string;
191
+ username: string;
192
+ userImage?: string | null;
193
+ score: number;
194
+ achievedAt: Date;
195
+ metadata?: Record<string, unknown>;
196
+ gameId?: string;
197
+ gameTitle?: string;
198
+ gameSlug?: string;
199
+ }
200
+ interface UserRank {
201
+ rank: number;
202
+ totalPlayers: number;
203
+ score: number;
204
+ percentile: number;
205
+ }
206
+ interface UserRankResponse {
207
+ rank: number;
208
+ score: number;
209
+ userId: string;
210
+ }
211
+ interface UserScore {
212
+ id: string;
213
+ score: number;
214
+ achievedAt: Date;
215
+ metadata?: Record<string, unknown>;
216
+ gameId: string;
217
+ gameTitle: string;
218
+ gameSlug: string;
93
219
  }
94
220
  /**
95
- * Course metrics and totals
221
+ * Leaderboard entry with required game context.
222
+ * Used when fetching leaderboards for a specific game.
96
223
  */
97
- interface CourseMetrics {
98
- /** Total XP available in the course */
99
- totalXp?: number;
100
- /** Total lessons/activities in the course */
101
- totalLessons?: number;
102
- /** Total number of grade levels covered by this course */
103
- totalGrades?: number;
104
- /** The type of course (e.g. 'optional', 'hole-filling', 'base') */
105
- courseType?: 'base' | 'hole-filling' | 'optional' | 'Base' | 'Hole-Filling' | 'Optional';
106
- /** Indicates whether the course is supplemental content */
107
- isSupplemental?: boolean;
224
+ interface GameLeaderboardEntry {
225
+ rank: number;
226
+ userId: string;
227
+ username: string;
228
+ userImage?: string | null;
229
+ score: number;
230
+ achievedAt: Date;
231
+ metadata?: Record<string, unknown>;
232
+ gameId: string;
233
+ gameTitle: string;
234
+ gameSlug: string;
108
235
  }
236
+
109
237
  /**
110
- * Complete course metadata structure
238
+ * Achievement Types
239
+ *
240
+ * @module types/achievement
111
241
  */
112
- interface CourseMetadata {
113
- /** Define the type of course and priority for the student */
114
- courseType?: 'base' | 'hole-filling' | 'optional';
115
- /** Boolean value to determine if a course is supplemental to a base course */
116
- isSupplemental?: boolean;
117
- /** Boolean value to determine if a course is custom to an individual student */
118
- isCustom?: boolean;
119
- /** Signals whether a course is in production with students */
120
- publishStatus?: 'draft' | 'testing' | 'published' | 'deactivated';
121
- /** Whether this course appears in the TimeBack catalog for teachers and parents */
122
- timebackVisible?: boolean;
123
- /** Who to contact when issues reported with questions */
124
- contactEmail?: string;
125
- /** Primary app identifier */
126
- primaryApp?: string;
127
- /** Learning goals for students */
242
+ type AchievementScopeType = 'daily' | 'weekly' | 'monthly' | 'yearly' | 'game' | 'global' | 'map' | 'level' | 'event';
243
+ declare enum AchievementCompletionType {
244
+ TIME_PLAYED_SESSION = "time_played_session",
245
+ INTERACTION = "interaction",
246
+ LEADERBOARD_RANK = "leaderboard_rank",
247
+ FIRST_SCORE = "first_score",
248
+ PERSONAL_BEST = "personal_best"
249
+ }
250
+ interface AchievementCurrent {
251
+ id: string;
252
+ title: string;
253
+ description?: string | null;
254
+ scope: AchievementScopeType;
255
+ rewardCredits: number;
256
+ limit: number;
257
+ completionType: string;
258
+ completionConfig: unknown;
259
+ target: unknown;
260
+ active: boolean;
261
+ createdAt?: Date | null;
262
+ updatedAt?: Date | null;
263
+ status: 'available' | 'completed';
264
+ scopeKey: string;
265
+ windowStart: string;
266
+ windowEnd: string;
267
+ }
268
+ interface AchievementWithStatus {
269
+ id: string;
270
+ title: string;
271
+ description: string | null;
272
+ scope: AchievementScopeType;
273
+ rewardCredits: number;
274
+ limit: number;
275
+ completionType: string;
276
+ completionConfig: unknown;
277
+ target: unknown;
278
+ active: boolean;
279
+ createdAt: Date | null;
280
+ updatedAt: Date | null;
281
+ status: 'available' | 'completed';
282
+ scopeKey: string;
283
+ windowStart?: string;
284
+ windowEnd?: string;
285
+ }
286
+ interface AchievementHistoryEntry {
287
+ achievementId: string;
288
+ title: string;
289
+ rewardCredits: number;
290
+ createdAt: Date;
291
+ scopeKey: string;
292
+ }
293
+ interface AchievementProgressResponse {
294
+ achievementId: string;
295
+ status: 'completed' | 'already_completed';
296
+ rewardCredits: number;
297
+ scopeKey: string;
298
+ createdAt: Date;
299
+ }
300
+
301
+ /**
302
+ * TimeBack Enums & Literal Types
303
+ *
304
+ * Basic type definitions used throughout the TimeBack integration.
305
+ *
306
+ * @module types/timeback/types
307
+ */
308
+ /**
309
+ * Valid TimeBack subject values for course configuration.
310
+ * These are the supported subject values for OneRoster courses.
311
+ */
312
+ type TimebackSubject = 'Reading' | 'Language' | 'Vocabulary' | 'Social Studies' | 'Writing' | 'Science' | 'FastMath' | 'Math' | 'None';
313
+ /**
314
+ * Grade levels per AE OneRoster GradeEnum.
315
+ * -1 = Pre-K, 0 = Kindergarten, 1-12 = Grades 1-12, 13 = AP
316
+ */
317
+ type TimebackGrade = -1 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13;
318
+ /**
319
+ * Valid Caliper subject values.
320
+ * Matches OneRoster subjects, with "None" as a Caliper-specific fallback.
321
+ */
322
+ type CaliperSubject = 'Reading' | 'Language' | 'Vocabulary' | 'Social Studies' | 'Writing' | 'Science' | 'FastMath' | 'Math' | 'None';
323
+ /**
324
+ * OneRoster organization types.
325
+ */
326
+ type OrganizationType = 'department' | 'school' | 'district' | 'local' | 'state' | 'national';
327
+ /**
328
+ * Lesson types for PowerPath integration.
329
+ */
330
+ type LessonType = 'powerpath-100' | 'quiz' | 'test-out' | 'placement' | 'unit-test' | 'alpha-read-article' | null;
331
+
332
+ /**
333
+ * TimeBack Configuration Types
334
+ *
335
+ * Configuration interfaces for Organization, Course, Component,
336
+ * Resource, and complete TimeBack setup.
337
+ *
338
+ * @module types/timeback/config
339
+ */
340
+
341
+ /**
342
+ * Organization configuration for TimeBack (user input - optionals allowed)
343
+ */
344
+ interface OrganizationConfig {
345
+ /** Display name for your organization */
346
+ name?: string;
347
+ /** Organization type */
348
+ type?: OrganizationType;
349
+ /** Unique identifier (defaults to Playcademy's org) */
350
+ identifier?: string;
351
+ }
352
+ /**
353
+ * Course goals for daily student targets
354
+ */
355
+ interface CourseGoals {
356
+ /** Target XP students should earn per day */
357
+ dailyXp?: number;
358
+ /** Target lessons per day */
359
+ dailyLessons?: number;
360
+ /** Target active minutes per day */
361
+ dailyActiveMinutes?: number;
362
+ /** Target accuracy percentage */
363
+ dailyAccuracy?: number;
364
+ /** Target mastered units per day */
365
+ dailyMasteredUnits?: number;
366
+ }
367
+ /**
368
+ * Course metrics and totals
369
+ */
370
+ interface CourseMetrics {
371
+ /** Total XP available in the course */
372
+ totalXp?: number;
373
+ /** Total lessons/activities in the course */
374
+ totalLessons?: number;
375
+ /** Total number of grade levels covered by this course */
376
+ totalGrades?: number;
377
+ /** The type of course (e.g. 'optional', 'hole-filling', 'base') */
378
+ courseType?: 'base' | 'hole-filling' | 'optional' | 'Base' | 'Hole-Filling' | 'Optional';
379
+ /** Indicates whether the course is supplemental content */
380
+ isSupplemental?: boolean;
381
+ }
382
+ /**
383
+ * Complete course metadata structure
384
+ */
385
+ interface CourseMetadata {
386
+ /** Define the type of course and priority for the student */
387
+ courseType?: 'base' | 'hole-filling' | 'optional';
388
+ /** Boolean value to determine if a course is supplemental to a base course */
389
+ isSupplemental?: boolean;
390
+ /** Boolean value to determine if a course is custom to an individual student */
391
+ isCustom?: boolean;
392
+ /** Signals whether a course is in production with students */
393
+ publishStatus?: 'draft' | 'testing' | 'published' | 'deactivated';
394
+ /** Whether this course appears in the TimeBack catalog for teachers and parents */
395
+ timebackVisible?: boolean;
396
+ /** Who to contact when issues reported with questions */
397
+ contactEmail?: string;
398
+ /** Primary app identifier */
399
+ primaryApp?: string;
400
+ /** Learning goals for students */
128
401
  goals?: CourseGoals;
129
402
  /** Course metrics and totals */
130
403
  metrics?: CourseMetrics;
@@ -451,705 +724,804 @@ type GameMetricsProxyResponse = {
451
724
  };
452
725
 
453
726
  /**
454
- * @fileoverview Server SDK Type Definitions
727
+ * Inventory & Shop Types
455
728
  *
456
- * TypeScript type definitions for the server-side Playcademy SDK.
457
- * Includes configuration types, client state, and re-exported TimeBack types.
729
+ * Literal types for inventory system. Database row types are in @playcademy/data/types.
730
+ *
731
+ * @module types/inventory
732
+ */
733
+ /**
734
+ * Item categories available on the platform.
458
735
  */
736
+ type ItemType = 'currency' | 'badge' | 'trophy' | 'collectible' | 'consumable' | 'unlock' | 'upgrade' | 'accessory' | 'other';
459
737
 
460
738
  /**
461
- * Base configuration for TimeBack integration (shared across all courses).
462
- * References upstream TimeBack types from @playcademy/timeback.
739
+ * Map & Placement Types
463
740
  *
464
- * All fields are optional and support template variables: {grade}, {subject}, {gameSlug}
741
+ * Literal types and JSON shapes for maps. Database row types are in @playcademy/data/types.
742
+ *
743
+ * @module types/map
465
744
  */
466
- interface TimebackBaseConfig {
467
- /** Organization configuration (shared across all courses) */
468
- organization?: Partial<OrganizationConfig>;
469
- /** Course defaults (can be overridden per-course) */
470
- course?: Partial<CourseConfig>;
471
- /** Component defaults */
472
- component?: Partial<ComponentConfig>;
473
- /** Resource defaults */
474
- resource?: Partial<ResourceConfig>;
475
- /** ComponentResource defaults */
476
- componentResource?: Partial<ComponentResourceConfig>;
477
- }
478
745
  /**
479
- * Extended course configuration that merges TimebackCourseConfig with per-course overrides.
480
- * Used in playcademy.config.* to allow per-course customization.
746
+ * Allowed map interaction types.
481
747
  */
482
- interface TimebackCourseConfigWithOverrides extends TimebackCourseConfig {
748
+ type InteractionType = 'game_entry' | 'game_registry' | 'info' | 'teleport' | 'door_in' | 'door_out' | 'npc_interaction' | 'quest_trigger';
749
+ /**
750
+ * Metadata for interactive map elements.
751
+ */
752
+ interface MapElementMetadata {
753
+ description?: string;
483
754
  title?: string;
484
- courseCode?: string;
485
- level?: string;
486
- metadata?: CourseConfig['metadata'];
487
- totalXp?: number | null;
488
- masterableUnits?: number | null;
755
+ targetMapIdentifier?: string;
756
+ targetSpawnTileX?: number;
757
+ targetSpawnTileY?: number;
758
+ sourceTiledObjects?: Record<string, unknown>[];
759
+ [key: string]: unknown;
489
760
  }
490
761
  /**
491
- * TimeBack integration configuration for Playcademy config file.
492
- *
493
- * Supports two levels of customization:
494
- * 1. `base`: Shared defaults for all courses (organization, course, component, resource, componentResource)
495
- * 2. Per-course overrides in the `courses` array (title, courseCode, level, gradingScheme, metadata)
496
- *
497
- * Template variables ({grade}, {subject}, {gameSlug}) can be used in string fields.
762
+ * Metadata describing how an item should be placed on the map.
498
763
  */
499
- interface TimebackIntegrationConfig {
500
- /** Multi-grade course configuration (array of grade/subject/totalXp with optional per-course overrides) */
501
- courses: TimebackCourseConfigWithOverrides[];
502
- /** Optional base configuration (shared across all courses, can be overridden per-course) */
503
- base?: TimebackBaseConfig;
764
+ interface PlaceableItemMetadata {
765
+ tilesWide?: number;
766
+ tilesHigh?: number;
767
+ flippable?: boolean;
768
+ [key: string]: unknown;
504
769
  }
505
770
  /**
506
- * Custom API routes integration
771
+ * Simplified map data for API responses.
507
772
  */
508
- interface CustomRoutesIntegration {
509
- /** Directory for custom API routes (defaults to 'server/api') */
510
- directory?: string;
773
+ interface MapData {
774
+ id: string;
775
+ identifier: string;
776
+ displayName: string;
777
+ description?: string | null;
778
+ metadata?: Record<string, unknown> | null;
511
779
  }
512
780
  /**
513
- * Database integration
781
+ * Input for creating a map object.
514
782
  */
515
- interface DatabaseIntegration {
516
- /** Database directory (defaults to 'db') */
517
- directory?: string;
518
- /** Schema strategy: 'push' uses drizzle-kit push-style diffing, 'migrate' uses migration files.
519
- * When omitted, auto-detects based on presence of a migrations directory with _journal.json. */
520
- strategy?: 'push' | 'migrate';
521
- }
522
- interface QueueConfig {
523
- maxBatchSize?: number;
524
- maxRetries?: number;
525
- maxBatchTimeout?: number;
526
- maxConcurrency?: number;
527
- retryDelay?: number;
528
- deadLetterQueue?: string;
783
+ interface CreateMapObjectData {
784
+ itemId: string;
785
+ worldX: number;
786
+ worldY: number;
787
+ width?: number;
788
+ height?: number;
789
+ rotation?: number;
790
+ scale?: number;
791
+ metadata?: Record<string, unknown>;
529
792
  }
793
+
530
794
  /**
531
- * Integrations configuration
532
- * All backend features (database, custom routes, external services) are configured here
795
+ * Character & Avatar Types
796
+ *
797
+ * Literal types for character system. Database row types are in @playcademy/data/types.
798
+ *
799
+ * @module types/character
533
800
  */
534
- interface IntegrationsConfig {
535
- /** TimeBack integration (optional) */
536
- timeback?: TimebackIntegrationConfig | null;
537
- /** Custom API routes (optional) */
538
- customRoutes?: CustomRoutesIntegration | boolean;
539
- /** Database (optional) */
540
- database?: DatabaseIntegration | boolean;
541
- /** Key-Value storage (optional) */
542
- kv?: boolean;
543
- /** Bucket storage (optional) */
544
- bucket?: boolean;
545
- /** Authentication (optional) */
546
- auth?: boolean;
547
- /** Queues (optional) */
548
- queues?: Record<string, QueueConfig | boolean>;
549
- }
550
801
  /**
551
- * Unified Playcademy configuration
552
- * Used for playcademy.config.{js,json}
802
+ * Character component categories.
553
803
  */
554
- interface PlaycademyConfig {
555
- /** Game name */
556
- name: string;
557
- /** Game description */
558
- description?: string;
559
- /** Game emoji icon */
560
- emoji?: string;
561
- /** Build command to run before deployment */
562
- buildCommand?: string[];
563
- /** Path to build output */
564
- buildPath?: string;
565
- /** Game type */
566
- gameType?: 'hosted' | 'external';
567
- /** External URL (for external games) */
568
- externalUrl?: string;
569
- /** Game platform */
570
- platform?: 'web' | 'unity' | 'godot';
571
- /** Integrations (database, custom routes, external services) */
572
- integrations?: IntegrationsConfig;
573
- }
804
+ type CharacterComponentType = 'body' | 'outfit' | 'hairstyle' | 'eyes' | 'accessory';
574
805
 
575
806
  /**
576
- * Configuration options for initializing a PlaycademyClient instance.
807
+ * Level & Progression Types
577
808
  *
578
- * @example
579
- * ```typescript
580
- * const config: PlaycademyServerClientConfig = {
581
- * apiKey: process.env.PLAYCADEMY_API_KEY!,
582
- * gameId: 'my-math-game',
583
- * configPath: './playcademy.config.js'
584
- * }
585
- * ```
809
+ * API response DTOs for level system. Database row types are in @playcademy/data/types.
810
+ *
811
+ * @module types/level
586
812
  */
587
- interface PlaycademyServerClientConfig {
588
- /**
589
- * Playcademy API key for server-to-server authentication.
590
- * Obtain from the Playcademy developer dashboard.
591
- */
592
- apiKey: string;
593
- /**
594
- * Optional path to playcademy.config.js file.
595
- * If not provided, searches current directory and up to 3 parent directories.
596
- * Ignored if `config` is provided directly.
597
- *
598
- * @example './config/playcademy.config.js'
599
- */
600
- configPath?: string;
601
- /**
602
- * Optional config object (for edge environments without filesystem).
603
- * If provided, skips filesystem-based config loading.
604
- *
605
- * @example { name: 'My Game', integrations: { timeback: {...} } }
606
- */
607
- config?: PlaycademyConfig;
608
- /**
609
- * Optional base URL for Playcademy API.
610
- * Defaults to environment variables or 'https://hub.playcademy.net'.
611
- *
612
- * @example 'http://localhost:3000' for local development
613
- */
614
- baseUrl?: string;
615
- /**
616
- * Optional game ID.
617
- * If not provided, will attempt to fetch from API using the API token.
618
- *
619
- * @example 'my-math-game'
620
- */
621
- gameId?: string;
622
- }
623
813
  /**
624
- * Internal state maintained by the PlaycademyClient instance.
625
- *
626
- * @internal
814
+ * Result of adding XP to a user.
627
815
  */
628
- interface PlaycademyServerClientState {
629
- /** API key for authentication */
630
- apiKey: string;
631
- /** Base URL for API requests */
632
- baseUrl: string;
633
- /** Game identifier */
634
- gameId: string;
635
- /** Loaded game configuration from playcademy.config.js */
636
- config: PlaycademyConfig;
637
- /**
638
- * TimeBack course ID fetched from the Playcademy API.
639
- * Used for all TimeBack event recording.
640
- */
641
- courseId?: string;
816
+ interface XPAddResult {
817
+ totalXP: number;
818
+ newLevel: number;
819
+ leveledUp: boolean;
820
+ creditsAwarded: number;
821
+ xpToNextLevel: number;
642
822
  }
643
-
644
823
  /**
645
- * User Types
646
- *
647
- * Enums, DTOs and API response types. Database row types are in @playcademy/data/types.
648
- *
649
- * @module types/user
824
+ * Result of checking whether a level up occurred.
650
825
  */
651
- type UserRoleEnumType = 'admin' | 'player' | 'developer' | 'teacher';
652
- type DeveloperStatusEnumType = 'none' | 'pending' | 'approved';
653
- type DeveloperStatusValue = DeveloperStatusEnumType;
654
- type TimebackUserRole = 'administrator' | 'aide' | 'guardian' | 'parent' | 'proctor' | 'relative' | 'student' | 'teacher';
655
- type TimebackOrgType = 'department' | 'school' | 'district' | 'local' | 'state' | 'national';
656
- interface UserEnrollment {
657
- gameId?: string;
658
- courseId: string;
659
- grade: number;
660
- subject: string;
661
- orgId?: string;
662
- }
663
- interface UserOrganization {
664
- id: string;
665
- name: string | null;
666
- type: TimebackOrgType | string;
667
- isPrimary: boolean;
668
- }
669
- interface TimebackStudentProfile {
670
- role: TimebackUserRole;
671
- organizations: UserOrganization[];
672
- }
673
- interface UserTimebackData extends TimebackStudentProfile {
674
- id: string;
675
- enrollments: UserEnrollment[];
826
+ interface LevelUpCheckResult {
827
+ newLevel: number;
828
+ remainingXp: number;
829
+ leveledUp: boolean;
830
+ creditsAwarded: number;
831
+ xpToNextLevel: number;
676
832
  }
677
833
  /**
678
- * OpenID Connect UserInfo claims (NOT a database row).
834
+ * Level progress API response.
679
835
  */
680
- interface UserInfo {
681
- sub: string;
682
- email: string;
683
- name: string | null;
684
- email_verified?: boolean;
685
- given_name?: string;
686
- family_name?: string;
687
- issuer?: string;
688
- lti_roles?: unknown;
689
- lti_context?: unknown;
690
- lti_resource_link?: unknown;
691
- timeback_id?: string;
692
- }
693
- interface DeveloperStatusResponse {
694
- status: DeveloperStatusEnumType;
695
- }
696
- interface DemoProfile {
697
- displayName: string;
698
- isDefault: boolean;
836
+ interface LevelProgressResponse {
837
+ level: number;
838
+ currentXp: number;
839
+ xpToNextLevel: number;
840
+ totalXP: number;
699
841
  }
842
+
700
843
  /**
701
- * Update shape for `client.demo.profile.update(...)`.
844
+ * Notification Types
702
845
  *
703
- * Kept as a named type so callers typed against it pick up new fields
704
- * automatically, but `displayName` is the only updatable field today and
705
- * the server's `DemoProfileSchema` requires it — a no-field payload would
706
- * 400 at runtime, so we model that at the type level too. When additional
707
- * fields land, make them required/optional individually based on server
708
- * validation, rather than blanket-optional.
846
+ * @module types/notification
709
847
  */
710
- interface DemoProfileUpdate {
711
- displayName: string;
848
+
849
+ declare enum NotificationType {
850
+ ACHIEVEMENT = "achievement",
851
+ SYSTEM = "system",
852
+ PROMO = "promo"
712
853
  }
713
- /**
714
- * Authenticated user for API responses.
715
- * Differs from UserRow: omits timebackId, adds hasTimebackAccount and timeback.
716
- */
717
- interface AuthenticatedUser {
718
- id: string;
719
- email: string;
720
- emailVerified: boolean;
721
- name: string | null;
722
- image: string | null;
723
- username: string | null;
724
- role: UserRoleEnumType;
725
- developerStatus: DeveloperStatusEnumType;
726
- characterCreated: boolean;
727
- createdAt: Date;
728
- updatedAt: Date;
729
- hasTimebackAccount: boolean;
730
- timeback?: UserTimebackData;
854
+ declare enum NotificationStatus {
855
+ PENDING = "pending",
856
+ DELIVERED = "delivered",
857
+ SEEN = "seen",
858
+ CLICKED = "clicked",
859
+ DISMISSED = "dismissed",
860
+ EXPIRED = "expired"
731
861
  }
732
- interface GameUser {
733
- id: string;
734
- name: string | null;
735
- role: UserRoleEnumType;
736
- username: string | null;
737
- email: string | null;
738
- timeback?: UserTimebackData;
862
+ interface NotificationStats {
863
+ total: number;
864
+ delivered: number;
865
+ seen: number;
866
+ clicked: number;
867
+ dismissed: number;
868
+ expired: number;
869
+ clickThroughRate: number;
739
870
  }
740
871
 
741
872
  /**
742
- * Game Types
743
- *
744
- * Literal types and API DTOs. Database row types are in @playcademy/data/types.
873
+ * Shop Types
745
874
  *
746
- * @module types/game
875
+ * @module types/shop
747
876
  */
748
- type GameType = 'hosted' | 'external';
749
- type GamePlatform = 'web' | 'godot' | 'unity' | (string & {});
750
877
  /**
751
- * Game manifest file format (manifest.json).
752
- * Note: createdAt is a string here because it's parsed from JSON file.
878
+ * Currency display info for shop UI.
753
879
  */
754
- interface ManifestV1 {
755
- version: '1';
756
- platform: string;
757
- createdAt: string;
758
- }
759
- interface ManifestVersions {
760
- vitePlugin?: string;
761
- sdk?: string;
762
- cli?: string;
763
- vite?: string;
764
- godotSdk?: string;
765
- godot?: string;
766
- }
767
- interface ManifestV2 {
768
- version: '2';
769
- platform: string;
770
- createdAt: string;
771
- versions: ManifestVersions;
772
- }
773
- type GameManifest = ManifestV1 | ManifestV2;
774
- interface DomainValidationRecords {
775
- ownership?: {
776
- name?: string;
777
- value?: string;
778
- type?: string;
779
- };
780
- ssl?: {
781
- txt_name?: string;
782
- txt_value?: string;
783
- }[];
880
+ interface ShopCurrency {
881
+ id: string;
882
+ symbol: string | null;
883
+ isPrimary: boolean;
884
+ displayName?: string | null;
885
+ imageUrl?: string | null;
784
886
  }
785
-
786
887
  /**
787
- * Leaderboard Types
788
- *
789
- * @module types/leaderboard
888
+ * Shop item for display.
889
+ * Combines Item fields (excluding createdAt) with shop listing data.
790
890
  */
791
- type LeaderboardTimeframe = 'all_time' | 'monthly' | 'weekly' | 'daily';
792
- interface LeaderboardOptions {
793
- timeframe?: LeaderboardTimeframe;
794
- limit?: number;
795
- offset?: number;
796
- gameId?: string;
797
- }
798
- interface LeaderboardEntry {
799
- rank: number;
800
- userId: string;
801
- username: string;
802
- userImage?: string | null;
803
- score: number;
804
- achievedAt: Date;
805
- metadata?: Record<string, unknown>;
806
- gameId?: string;
807
- gameTitle?: string;
808
- gameSlug?: string;
809
- }
810
- interface UserRank {
811
- rank: number;
812
- totalPlayers: number;
813
- score: number;
814
- percentile: number;
815
- }
816
- interface UserRankResponse {
817
- rank: number;
818
- score: number;
819
- userId: string;
820
- }
821
- interface UserScore {
891
+ interface ShopDisplayItem {
822
892
  id: string;
823
- score: number;
824
- achievedAt: Date;
825
- metadata?: Record<string, unknown>;
826
- gameId: string;
827
- gameTitle: string;
828
- gameSlug: string;
893
+ slug: string;
894
+ gameId?: string | null;
895
+ displayName: string;
896
+ description?: string | null;
897
+ type: string;
898
+ isPlaceable: boolean;
899
+ imageUrl?: string | null;
900
+ metadata?: unknown;
901
+ listingId: string;
902
+ shopPrice: number;
903
+ currencyId: string;
904
+ currencySymbol?: string | null;
905
+ currencyDisplayName?: string | null;
906
+ currencyImageUrl?: string | null;
907
+ stock?: number | null;
908
+ sellBackPercentage?: number | null;
829
909
  }
830
910
  /**
831
- * Leaderboard entry with required game context.
832
- * Used when fetching leaderboards for a specific game.
911
+ * Complete shop view response.
833
912
  */
834
- interface GameLeaderboardEntry {
835
- rank: number;
836
- userId: string;
837
- username: string;
838
- userImage?: string | null;
839
- score: number;
840
- achievedAt: Date;
841
- metadata?: Record<string, unknown>;
842
- gameId: string;
843
- gameTitle: string;
844
- gameSlug: string;
913
+ interface ShopViewResponse {
914
+ shopItems: ShopDisplayItem[];
915
+ currencies: ShopCurrency[];
845
916
  }
846
917
 
847
918
  /**
848
- * Achievement Types
919
+ * Sprite Types
849
920
  *
850
- * @module types/achievement
921
+ * JSON shapes for sprite configuration. Database row types are in @playcademy/data/types.
922
+ *
923
+ * @module types/sprite
851
924
  */
852
- type AchievementScopeType = 'daily' | 'weekly' | 'monthly' | 'yearly' | 'game' | 'global' | 'map' | 'level' | 'event';
853
- declare enum AchievementCompletionType {
854
- TIME_PLAYED_SESSION = "time_played_session",
855
- INTERACTION = "interaction",
856
- LEADERBOARD_RANK = "leaderboard_rank",
857
- FIRST_SCORE = "first_score",
858
- PERSONAL_BEST = "personal_best"
859
- }
860
- interface AchievementCurrent {
861
- id: string;
862
- title: string;
863
- description?: string | null;
864
- scope: AchievementScopeType;
865
- rewardCredits: number;
866
- limit: number;
867
- completionType: string;
868
- completionConfig: unknown;
869
- target: unknown;
870
- active: boolean;
871
- createdAt?: Date | null;
872
- updatedAt?: Date | null;
873
- status: 'available' | 'completed';
874
- scopeKey: string;
875
- windowStart: string;
876
- windowEnd: string;
877
- }
878
- interface AchievementWithStatus {
879
- id: string;
880
- title: string;
881
- description: string | null;
882
- scope: AchievementScopeType;
883
- rewardCredits: number;
884
- limit: number;
885
- completionType: string;
886
- completionConfig: unknown;
887
- target: unknown;
888
- active: boolean;
889
- createdAt: Date | null;
890
- updatedAt: Date | null;
891
- status: 'available' | 'completed';
892
- scopeKey: string;
893
- windowStart?: string;
894
- windowEnd?: string;
925
+ /**
926
+ * Animation frame configuration.
927
+ */
928
+ interface SpriteAnimationFrame {
929
+ row: number;
930
+ frameStart: number;
931
+ numFrames: number;
932
+ fps: number;
895
933
  }
896
- interface AchievementHistoryEntry {
897
- achievementId: string;
898
- title: string;
899
- rewardCredits: number;
900
- createdAt: Date;
901
- scopeKey: string;
934
+ /**
935
+ * Sprite template data structure (stored in JSONB).
936
+ */
937
+ interface SpriteTemplateData {
938
+ tileSize: number;
939
+ tileHeight: number;
940
+ columns: number;
941
+ rows: number;
942
+ spacing: number;
943
+ animations: {
944
+ base_right: SpriteAnimationFrame;
945
+ base_up: SpriteAnimationFrame;
946
+ base_left: SpriteAnimationFrame;
947
+ base_down: SpriteAnimationFrame;
948
+ idle_right: SpriteAnimationFrame;
949
+ walk_right: SpriteAnimationFrame;
950
+ idle_up: SpriteAnimationFrame;
951
+ walk_up: SpriteAnimationFrame;
952
+ idle_left: SpriteAnimationFrame;
953
+ walk_left: SpriteAnimationFrame;
954
+ idle_down: SpriteAnimationFrame;
955
+ walk_down: SpriteAnimationFrame;
956
+ };
902
957
  }
903
- interface AchievementProgressResponse {
904
- achievementId: string;
905
- status: 'completed' | 'already_completed';
906
- rewardCredits: number;
907
- scopeKey: string;
908
- createdAt: Date;
958
+ /**
959
+ * Sprite sheet configuration with precomputed dimensions.
960
+ */
961
+ interface SpriteConfigWithDimensions {
962
+ textureUrl: string;
963
+ columns: number;
964
+ rows: number;
965
+ spriteWidth: number;
966
+ spriteHeight: number;
967
+ animations: Record<string, SpriteAnimationFrame>;
909
968
  }
910
969
 
911
970
  /**
912
- * Inventory & Shop Types
913
- *
914
- * Literal types for inventory system. Database row types are in @playcademy/data/types.
971
+ * Connection monitoring types
915
972
  *
916
- * @module types/inventory
973
+ * Type definitions for connection state, configuration, and callbacks.
917
974
  */
918
975
  /**
919
- * Item categories available on the platform.
976
+ * Possible connection states.
977
+ *
978
+ * - **online**: Connection is stable and healthy
979
+ * - **offline**: Complete loss of network connectivity
980
+ * - **degraded**: Connection is slow or experiencing intermittent issues
920
981
  */
921
- type ItemType = 'currency' | 'badge' | 'trophy' | 'collectible' | 'consumable' | 'unlock' | 'upgrade' | 'accessory' | 'other';
982
+ type ConnectionState = 'online' | 'offline' | 'degraded';
922
983
 
923
984
  /**
924
- * Map & Placement Types
985
+ * Connection Manager
925
986
  *
926
- * Literal types and JSON shapes for maps. Database row types are in @playcademy/data/types.
987
+ * Manages connection monitoring and integrates it with the Playcademy client.
988
+ * Handles event wiring, state management, and disconnect callbacks.
927
989
  *
928
- * @module types/map
990
+ * In iframe mode, disables local monitoring and listens to platform connection
991
+ * state broadcasts instead (avoids duplicate heartbeats).
929
992
  */
993
+
930
994
  /**
931
- * Allowed map interaction types.
995
+ * Configuration for the ConnectionManager.
932
996
  */
933
- type InteractionType = 'game_entry' | 'game_registry' | 'info' | 'teleport' | 'door_in' | 'door_out' | 'npc_interaction' | 'quest_trigger';
997
+ interface ConnectionManagerConfig {
998
+ /** Base URL for API requests (used for heartbeat pings) */
999
+ baseUrl: string;
1000
+ /** Authentication context (iframe vs standalone) for alert routing */
1001
+ authContext?: {
1002
+ isInIframe: boolean;
1003
+ };
1004
+ /** Handler to call when connection issues are detected */
1005
+ onDisconnect?: DisconnectHandler;
1006
+ /** Callback to emit connection change events to the client */
1007
+ onConnectionChange?: (state: ConnectionState, reason: string) => void;
1008
+ }
934
1009
  /**
935
- * Metadata for interactive map elements.
936
- */
937
- interface MapElementMetadata {
938
- description?: string;
939
- title?: string;
940
- targetMapIdentifier?: string;
941
- targetSpawnTileX?: number;
942
- targetSpawnTileY?: number;
943
- sourceTiledObjects?: Record<string, unknown>[];
944
- [key: string]: unknown;
945
- }
946
- /**
947
- * Metadata describing how an item should be placed on the map.
948
- */
949
- interface PlaceableItemMetadata {
950
- tilesWide?: number;
951
- tilesHigh?: number;
952
- flippable?: boolean;
953
- [key: string]: unknown;
954
- }
955
- /**
956
- * Simplified map data for API responses.
957
- */
958
- interface MapData {
959
- id: string;
960
- identifier: string;
961
- displayName: string;
962
- description?: string | null;
963
- metadata?: Record<string, unknown> | null;
964
- }
965
- /**
966
- * Input for creating a map object.
1010
+ * Manages connection monitoring for the Playcademy client.
1011
+ *
1012
+ * The ConnectionManager serves as an integration layer between the low-level
1013
+ * ConnectionMonitor and the PlaycademyClient. It handles:
1014
+ * - Event wiring and coordination
1015
+ * - Disconnect callbacks with context
1016
+ * - Platform-level alert integration
1017
+ * - Request success/failure tracking
1018
+ *
1019
+ * This class is used internally by PlaycademyClient and typically not
1020
+ * instantiated directly by game developers.
1021
+ *
1022
+ * @see {@link ConnectionMonitor} for the underlying monitoring implementation
1023
+ * @see {@link PlaycademyClient.onDisconnect} for the public API
967
1024
  */
968
- interface CreateMapObjectData {
969
- itemId: string;
970
- worldX: number;
971
- worldY: number;
972
- width?: number;
973
- height?: number;
974
- rotation?: number;
975
- scale?: number;
976
- metadata?: Record<string, unknown>;
1025
+ declare class ConnectionManager {
1026
+ private monitor?;
1027
+ private authContext?;
1028
+ private disconnectHandler?;
1029
+ private connectionChangeCallback?;
1030
+ private currentState;
1031
+ private additionalDisconnectHandlers;
1032
+ /**
1033
+ * Creates a new ConnectionManager instance.
1034
+ *
1035
+ * @param config - Configuration options for the manager
1036
+ *
1037
+ * @example
1038
+ * ```typescript
1039
+ * const manager = new ConnectionManager({
1040
+ * baseUrl: 'https://api.playcademy.com',
1041
+ * authContext: { isInIframe: false },
1042
+ * onDisconnect: (context) => {
1043
+ * console.log(`Disconnected: ${context.state}`)
1044
+ * },
1045
+ * onConnectionChange: (state, reason) => {
1046
+ * console.log(`Connection changed: ${state}`)
1047
+ * }
1048
+ * })
1049
+ * ```
1050
+ */
1051
+ constructor(config: ConnectionManagerConfig);
1052
+ /**
1053
+ * Gets the current connection state.
1054
+ *
1055
+ * @returns The current connection state ('online', 'offline', or 'degraded')
1056
+ *
1057
+ * @example
1058
+ * ```typescript
1059
+ * const state = manager.getState()
1060
+ * if (state === 'offline') {
1061
+ * console.log('No connection')
1062
+ * }
1063
+ * ```
1064
+ */
1065
+ getState(): ConnectionState;
1066
+ /**
1067
+ * Manually triggers a connection check immediately.
1068
+ *
1069
+ * Forces a heartbeat ping to verify the current connection status.
1070
+ * Useful when you need to check connectivity before a critical operation.
1071
+ *
1072
+ * In iframe mode, this returns the last known state from platform.
1073
+ *
1074
+ * @returns Promise resolving to the current connection state
1075
+ *
1076
+ * @example
1077
+ * ```typescript
1078
+ * const state = await manager.checkNow()
1079
+ * if (state === 'online') {
1080
+ * await performCriticalOperation()
1081
+ * }
1082
+ * ```
1083
+ */
1084
+ checkNow(): Promise<ConnectionState>;
1085
+ /**
1086
+ * Reports a successful API request to the connection monitor.
1087
+ *
1088
+ * This resets the consecutive failure counter and transitions from
1089
+ * 'degraded' to 'online' state if applicable.
1090
+ *
1091
+ * Typically called automatically by the SDK's request wrapper.
1092
+ * No-op in iframe mode (platform handles monitoring).
1093
+ */
1094
+ reportRequestSuccess(): void;
1095
+ /**
1096
+ * Reports a failed API request to the connection monitor.
1097
+ *
1098
+ * Only network errors are tracked (not 4xx/5xx HTTP responses).
1099
+ * After consecutive failures exceed the threshold, the state transitions
1100
+ * to 'degraded' or 'offline'.
1101
+ *
1102
+ * Typically called automatically by the SDK's request wrapper.
1103
+ * No-op in iframe mode (platform handles monitoring).
1104
+ *
1105
+ * @param error - The error from the failed request
1106
+ */
1107
+ reportRequestFailure(error: unknown): void;
1108
+ /**
1109
+ * Registers a callback to be called when connection issues are detected.
1110
+ *
1111
+ * The callback only fires for 'offline' and 'degraded' states, not when
1112
+ * recovering to 'online'. This provides a clean API for games to handle
1113
+ * disconnect scenarios without being notified of every state change.
1114
+ *
1115
+ * Works in both iframe and standalone modes transparently.
1116
+ *
1117
+ * @param callback - Function to call when connection degrades
1118
+ * @returns Cleanup function to unregister the callback
1119
+ *
1120
+ * @example
1121
+ * ```typescript
1122
+ * const cleanup = manager.onDisconnect(({ state, reason, displayAlert }) => {
1123
+ * if (state === 'offline') {
1124
+ * displayAlert?.('Connection lost. Saving your progress...', { type: 'error' })
1125
+ * saveGameState()
1126
+ * }
1127
+ * })
1128
+ *
1129
+ * // Later: cleanup() to unregister
1130
+ * ```
1131
+ */
1132
+ onDisconnect(callback: DisconnectHandler): () => void;
1133
+ /**
1134
+ * Stops connection monitoring and performs cleanup.
1135
+ *
1136
+ * Removes event listeners and clears heartbeat intervals.
1137
+ * Should be called when the client is being destroyed.
1138
+ */
1139
+ stop(): void;
1140
+ /**
1141
+ * Sets up listener for platform connection state broadcasts (iframe mode only).
1142
+ */
1143
+ private _setupPlatformListener;
1144
+ /**
1145
+ * Handles connection state changes from the monitor or platform.
1146
+ *
1147
+ * Coordinates between:
1148
+ * 1. Emitting events to the client (for client.on('connectionChange'))
1149
+ * 2. Calling the disconnect handler if configured
1150
+ * 3. Calling any additional handlers registered via onDisconnect()
1151
+ *
1152
+ * @param state - The new connection state
1153
+ * @param reason - Human-readable reason for the state change
1154
+ */
1155
+ private _handleConnectionChange;
977
1156
  }
978
1157
 
979
1158
  /**
980
- * Character & Avatar Types
1159
+ * @fileoverview Playcademy Messaging System
981
1160
  *
982
- * Literal types for character system. Database row types are in @playcademy/data/types.
1161
+ * This file implements a unified messaging system for the Playcademy platform that handles
1162
+ * communication between different contexts:
983
1163
  *
984
- * @module types/character
985
- */
986
- /**
987
- * Character component categories.
988
- */
989
- type CharacterComponentType = 'body' | 'outfit' | 'hairstyle' | 'eyes' | 'accessory';
990
-
991
- /**
992
- * Level & Progression Types
1164
+ * 1. **Iframe-to-Parent Communication**: When games run inside iframes (production/development),
1165
+ * they need to communicate with the parent window using postMessage API
993
1166
  *
994
- * API response DTOs for level system. Database row types are in @playcademy/data/types.
1167
+ * 2. **Local Communication**: When games run in the same context (local development),
1168
+ * they use CustomEvents for internal messaging
995
1169
  *
996
- * @module types/level
997
- */
998
- /**
999
- * Result of adding XP to a user.
1000
- */
1001
- interface XPAddResult {
1002
- totalXP: number;
1003
- newLevel: number;
1004
- leveledUp: boolean;
1005
- creditsAwarded: number;
1006
- xpToNextLevel: number;
1007
- }
1008
- /**
1009
- * Result of checking whether a level up occurred.
1010
- */
1011
- interface LevelUpCheckResult {
1012
- newLevel: number;
1013
- remainingXp: number;
1014
- leveledUp: boolean;
1015
- creditsAwarded: number;
1016
- xpToNextLevel: number;
1017
- }
1018
- /**
1019
- * Level progress API response.
1170
+ * The system automatically detects the runtime environment and chooses the appropriate
1171
+ * transport method, abstracting this complexity from the developer.
1172
+ *
1173
+ * **Architecture Overview**:
1174
+ * - Games run in iframes for security and isolation
1175
+ * - Parent window (Playcademy shell) manages game lifecycle
1176
+ * - Messages flow bidirectionally between parent and iframe
1177
+ * - Local development mode simulates this architecture without iframes
1020
1178
  */
1021
- interface LevelProgressResponse {
1022
- level: number;
1023
- currentXp: number;
1024
- xpToNextLevel: number;
1025
- totalXP: number;
1026
- }
1027
1179
 
1028
1180
  /**
1029
- * Notification Types
1181
+ * Enumeration of all message types used in the Playcademy messaging system.
1030
1182
  *
1031
- * @module types/notification
1032
- */
1033
-
1034
- declare enum NotificationType {
1035
- ACHIEVEMENT = "achievement",
1036
- SYSTEM = "system",
1037
- PROMO = "promo"
1038
- }
1039
- declare enum NotificationStatus {
1040
- PENDING = "pending",
1041
- DELIVERED = "delivered",
1042
- SEEN = "seen",
1043
- CLICKED = "clicked",
1044
- DISMISSED = "dismissed",
1045
- EXPIRED = "expired"
1046
- }
1047
- interface NotificationStats {
1048
- total: number;
1049
- delivered: number;
1050
- seen: number;
1051
- clicked: number;
1052
- dismissed: number;
1053
- expired: number;
1054
- clickThroughRate: number;
1183
+ * **Message Flow Patterns**:
1184
+ *
1185
+ * **Parent → Game (Overworld → Game)**:
1186
+ * - INIT: Provides game with authentication token and configuration
1187
+ * - TOKEN_REFRESH: Updates game's authentication token before expiry
1188
+ * - PAUSE/RESUME: Controls game execution state
1189
+ * - FORCE_EXIT: Immediately terminates the game
1190
+ * - OVERLAY: Shows/hides UI overlays over the game
1191
+ *
1192
+ * **Game → Parent (Game → Overworld)**:
1193
+ * - READY: Game has loaded and is ready to receive messages
1194
+ * - EXIT: Game requests to be closed (user clicked exit, game ended, etc.)
1195
+ * - TELEMETRY: Game reports performance metrics (FPS, memory usage, etc.)
1196
+ */
1197
+ declare enum MessageEvents {
1198
+ /**
1199
+ * Initializes the game with authentication context and configuration.
1200
+ * Sent immediately after game iframe loads.
1201
+ * Payload:
1202
+ * - `baseUrl`: string
1203
+ * - `token`: string
1204
+ * - `gameId`: string
1205
+ */
1206
+ INIT = "PLAYCADEMY_INIT",
1207
+ /**
1208
+ * Updates the game's authentication token before it expires.
1209
+ * Sent periodically to maintain valid authentication.
1210
+ * Payload:
1211
+ * - `token`: string
1212
+ * - `exp`: number
1213
+ */
1214
+ TOKEN_REFRESH = "PLAYCADEMY_TOKEN_REFRESH",
1215
+ /**
1216
+ * Pauses game execution (e.g., when user switches tabs).
1217
+ * Game should pause timers, animations, and user input.
1218
+ * Payload: void
1219
+ */
1220
+ PAUSE = "PLAYCADEMY_PAUSE",
1221
+ /**
1222
+ * Resumes game execution after being paused.
1223
+ * Game should restore timers, animations, and user input.
1224
+ * Payload: void
1225
+ */
1226
+ RESUME = "PLAYCADEMY_RESUME",
1227
+ /**
1228
+ * Forces immediate game termination (emergency exit).
1229
+ * Game should clean up resources and exit immediately.
1230
+ * Payload: void
1231
+ */
1232
+ FORCE_EXIT = "PLAYCADEMY_FORCE_EXIT",
1233
+ /**
1234
+ * Shows or hides UI overlays over the game.
1235
+ * Game may need to pause or adjust rendering accordingly.
1236
+ * Payload: boolean (true = show overlay, false = hide overlay)
1237
+ */
1238
+ OVERLAY = "PLAYCADEMY_OVERLAY",
1239
+ /**
1240
+ * Broadcasts connection state changes to games.
1241
+ * Sent by platform when network connectivity changes.
1242
+ * Payload:
1243
+ * - `state`: 'online' | 'offline' | 'degraded'
1244
+ * - `reason`: string
1245
+ */
1246
+ CONNECTION_STATE = "PLAYCADEMY_CONNECTION_STATE",
1247
+ /**
1248
+ * Game has finished loading and is ready to receive messages.
1249
+ * Sent once after game initialization is complete.
1250
+ * Payload: void
1251
+ */
1252
+ READY = "PLAYCADEMY_READY",
1253
+ /**
1254
+ * Game SDK initialization failed.
1255
+ * Sent when the game iframe fails to complete SDK init (e.g.
1256
+ * origin validation failure, INIT timeout, client creation error).
1257
+ * Payload:
1258
+ * - `reason`: string — human-readable failure description
1259
+ */
1260
+ INIT_ERROR = "PLAYCADEMY_INIT_ERROR",
1261
+ /**
1262
+ * Game requests to be closed/exited.
1263
+ * Sent when user clicks exit button or game naturally ends.
1264
+ * Payload: void
1265
+ */
1266
+ EXIT = "PLAYCADEMY_EXIT",
1267
+ /**
1268
+ * Game reports performance telemetry data.
1269
+ * Sent periodically for monitoring and analytics.
1270
+ * Payload:
1271
+ * - `fps`: number
1272
+ * - `mem`: number
1273
+ */
1274
+ TELEMETRY = "PLAYCADEMY_TELEMETRY",
1275
+ /**
1276
+ * Game reports key events to parent.
1277
+ * Sent when certain keys are pressed within the game iframe.
1278
+ * Payload:
1279
+ * - `key`: string
1280
+ * - `code?`: string
1281
+ * - `type`: 'keydown' | 'keyup'
1282
+ */
1283
+ KEY_EVENT = "PLAYCADEMY_KEY_EVENT",
1284
+ /**
1285
+ * Game requests platform to display an alert.
1286
+ * Sent when connection issues are detected or other important events occur.
1287
+ * Payload:
1288
+ * - `message`: string
1289
+ * - `options`: `{ type?: 'info' | 'warning' | 'error', duration?: number }`
1290
+ */
1291
+ DISPLAY_ALERT = "PLAYCADEMY_DISPLAY_ALERT",
1292
+ /**
1293
+ * Game signals that demo mode has ended.
1294
+ * Sent when a demo experience reaches its CTA/upgrade boundary.
1295
+ * Payload:
1296
+ * - `score?`: number
1297
+ * - `durationMs?`: number
1298
+ * - `metadata?`: `Record<string, unknown>`
1299
+ */
1300
+ DEMO_END = "PLAYCADEMY_DEMO_END",
1301
+ /**
1302
+ * Notifies about authentication state changes.
1303
+ * Can be sent in both directions depending on auth flow.
1304
+ * Payload:
1305
+ * - `authenticated`: boolean
1306
+ * - `user`: UserInfo | null
1307
+ * - `error`: Error | null
1308
+ */
1309
+ AUTH_STATE_CHANGE = "PLAYCADEMY_AUTH_STATE_CHANGE",
1310
+ /**
1311
+ * OAuth callback data from popup/new-tab windows.
1312
+ * Sent from popup window back to parent after OAuth completes.
1313
+ * Payload:
1314
+ * - `code`: string (OAuth authorization code)
1315
+ * - `state`: string (OAuth state for CSRF protection)
1316
+ * - `error`: string | null (OAuth error if any)
1317
+ */
1318
+ AUTH_CALLBACK = "PLAYCADEMY_AUTH_CALLBACK"
1055
1319
  }
1056
1320
 
1057
1321
  /**
1058
- * Shop Types
1059
- *
1060
- * @module types/shop
1322
+ * Cache configuration types for runtime customization
1061
1323
  */
1062
1324
  /**
1063
- * Currency display info for shop UI.
1325
+ * Runtime configuration for TTL cache behavior
1064
1326
  */
1065
- interface ShopCurrency {
1066
- id: string;
1067
- symbol: string | null;
1068
- isPrimary: boolean;
1069
- displayName?: string | null;
1070
- imageUrl?: string | null;
1327
+ interface TTLCacheConfig {
1328
+ /** Time-to-live in milliseconds. Set to 0 to disable caching for this call. */
1329
+ ttl?: number;
1330
+ /** Force refresh, bypassing cache */
1331
+ force?: boolean;
1332
+ /** Skip cache and fetch fresh data (alias for force) */
1333
+ skipCache?: boolean;
1071
1334
  }
1335
+
1072
1336
  /**
1073
- * Shop item for display.
1074
- * Combines Item fields (excluding createdAt) with shop listing data.
1337
+ * @fileoverview Server SDK Type Definitions
1338
+ *
1339
+ * TypeScript type definitions for the server-side Playcademy SDK.
1340
+ * Includes configuration types, client state, and re-exported TimeBack types.
1075
1341
  */
1076
- interface ShopDisplayItem {
1077
- id: string;
1078
- slug: string;
1079
- gameId?: string | null;
1080
- displayName: string;
1081
- description?: string | null;
1082
- type: string;
1083
- isPlaceable: boolean;
1084
- imageUrl?: string | null;
1085
- metadata?: unknown;
1086
- listingId: string;
1087
- shopPrice: number;
1088
- currencyId: string;
1089
- currencySymbol?: string | null;
1090
- currencyDisplayName?: string | null;
1091
- currencyImageUrl?: string | null;
1092
- stock?: number | null;
1093
- sellBackPercentage?: number | null;
1342
+
1343
+ /**
1344
+ * Base configuration for TimeBack integration (shared across all courses).
1345
+ * References upstream TimeBack types from @playcademy/timeback.
1346
+ *
1347
+ * All fields are optional and support template variables: {grade}, {subject}, {gameSlug}
1348
+ */
1349
+ interface TimebackBaseConfig {
1350
+ /** Organization configuration (shared across all courses) */
1351
+ organization?: Partial<OrganizationConfig>;
1352
+ /** Course defaults (can be overridden per-course) */
1353
+ course?: Partial<CourseConfig>;
1354
+ /** Component defaults */
1355
+ component?: Partial<ComponentConfig>;
1356
+ /** Resource defaults */
1357
+ resource?: Partial<ResourceConfig>;
1358
+ /** ComponentResource defaults */
1359
+ componentResource?: Partial<ComponentResourceConfig>;
1094
1360
  }
1095
1361
  /**
1096
- * Complete shop view response.
1362
+ * Extended course configuration that merges TimebackCourseConfig with per-course overrides.
1363
+ * Used in playcademy.config.* to allow per-course customization.
1097
1364
  */
1098
- interface ShopViewResponse {
1099
- shopItems: ShopDisplayItem[];
1100
- currencies: ShopCurrency[];
1365
+ interface TimebackCourseConfigWithOverrides extends TimebackCourseConfig {
1366
+ title?: string;
1367
+ courseCode?: string;
1368
+ level?: string;
1369
+ metadata?: CourseConfig['metadata'];
1370
+ totalXp?: number | null;
1371
+ masterableUnits?: number | null;
1101
1372
  }
1102
-
1103
1373
  /**
1104
- * Sprite Types
1374
+ * TimeBack integration configuration for Playcademy config file.
1105
1375
  *
1106
- * JSON shapes for sprite configuration. Database row types are in @playcademy/data/types.
1376
+ * Supports two levels of customization:
1377
+ * 1. `base`: Shared defaults for all courses (organization, course, component, resource, componentResource)
1378
+ * 2. Per-course overrides in the `courses` array (title, courseCode, level, gradingScheme, metadata)
1107
1379
  *
1108
- * @module types/sprite
1380
+ * Template variables ({grade}, {subject}, {gameSlug}) can be used in string fields.
1109
1381
  */
1382
+ interface TimebackIntegrationConfig {
1383
+ /** Multi-grade course configuration (array of grade/subject/totalXp with optional per-course overrides) */
1384
+ courses: TimebackCourseConfigWithOverrides[];
1385
+ /** Optional base configuration (shared across all courses, can be overridden per-course) */
1386
+ base?: TimebackBaseConfig;
1387
+ }
1110
1388
  /**
1111
- * Animation frame configuration.
1389
+ * Custom API routes integration
1112
1390
  */
1113
- interface SpriteAnimationFrame {
1114
- row: number;
1115
- frameStart: number;
1116
- numFrames: number;
1117
- fps: number;
1391
+ interface CustomRoutesIntegration {
1392
+ /** Directory for custom API routes (defaults to 'server/api') */
1393
+ directory?: string;
1118
1394
  }
1119
1395
  /**
1120
- * Sprite template data structure (stored in JSONB).
1396
+ * Database integration
1121
1397
  */
1122
- interface SpriteTemplateData {
1123
- tileSize: number;
1124
- tileHeight: number;
1125
- columns: number;
1126
- rows: number;
1127
- spacing: number;
1128
- animations: {
1129
- base_right: SpriteAnimationFrame;
1130
- base_up: SpriteAnimationFrame;
1131
- base_left: SpriteAnimationFrame;
1132
- base_down: SpriteAnimationFrame;
1133
- idle_right: SpriteAnimationFrame;
1134
- walk_right: SpriteAnimationFrame;
1135
- idle_up: SpriteAnimationFrame;
1136
- walk_up: SpriteAnimationFrame;
1137
- idle_left: SpriteAnimationFrame;
1138
- walk_left: SpriteAnimationFrame;
1139
- idle_down: SpriteAnimationFrame;
1140
- walk_down: SpriteAnimationFrame;
1141
- };
1398
+ interface DatabaseIntegration {
1399
+ /** Database directory (defaults to 'db') */
1400
+ directory?: string;
1401
+ /** Schema strategy: 'push' uses drizzle-kit push-style diffing, 'migrate' uses migration files.
1402
+ * When omitted, auto-detects based on presence of a migrations directory with _journal.json. */
1403
+ strategy?: 'push' | 'migrate';
1404
+ }
1405
+ interface QueueConfig {
1406
+ maxBatchSize?: number;
1407
+ maxRetries?: number;
1408
+ maxBatchTimeout?: number;
1409
+ maxConcurrency?: number;
1410
+ retryDelay?: number;
1411
+ deadLetterQueue?: string;
1142
1412
  }
1143
1413
  /**
1144
- * Sprite sheet configuration with precomputed dimensions.
1414
+ * Integrations configuration
1415
+ * All backend features (database, custom routes, external services) are configured here
1145
1416
  */
1146
- interface SpriteConfigWithDimensions {
1147
- textureUrl: string;
1148
- columns: number;
1149
- rows: number;
1150
- spriteWidth: number;
1151
- spriteHeight: number;
1152
- animations: Record<string, SpriteAnimationFrame>;
1417
+ interface IntegrationsConfig {
1418
+ /** TimeBack integration (optional) */
1419
+ timeback?: TimebackIntegrationConfig | null;
1420
+ /** Custom API routes (optional) */
1421
+ customRoutes?: CustomRoutesIntegration | boolean;
1422
+ /** Database (optional) */
1423
+ database?: DatabaseIntegration | boolean;
1424
+ /** Key-Value storage (optional) */
1425
+ kv?: boolean;
1426
+ /** Bucket storage (optional) */
1427
+ bucket?: boolean;
1428
+ /** Authentication (optional) */
1429
+ auth?: boolean;
1430
+ /** Queues (optional) */
1431
+ queues?: Record<string, QueueConfig | boolean>;
1432
+ }
1433
+ /**
1434
+ * Unified Playcademy configuration
1435
+ * Used for playcademy.config.{js,json}
1436
+ */
1437
+ interface PlaycademyConfig {
1438
+ /** Game name */
1439
+ name: string;
1440
+ /** Game description */
1441
+ description?: string;
1442
+ /** Game emoji icon */
1443
+ emoji?: string;
1444
+ /** Build command to run before deployment */
1445
+ buildCommand?: string[];
1446
+ /** Path to build output */
1447
+ buildPath?: string;
1448
+ /** Game type */
1449
+ gameType?: 'hosted' | 'external';
1450
+ /** External URL (for external games) */
1451
+ externalUrl?: string;
1452
+ /** Game platform */
1453
+ platform?: 'web' | 'unity' | 'godot';
1454
+ /** Integrations (database, custom routes, external services) */
1455
+ integrations?: IntegrationsConfig;
1456
+ }
1457
+
1458
+ /**
1459
+ * Configuration options for initializing a PlaycademyClient instance.
1460
+ *
1461
+ * @example
1462
+ * ```typescript
1463
+ * const config: PlaycademyServerClientConfig = {
1464
+ * apiKey: process.env.PLAYCADEMY_API_KEY!,
1465
+ * gameId: 'my-math-game',
1466
+ * configPath: './playcademy.config.js'
1467
+ * }
1468
+ * ```
1469
+ */
1470
+ interface PlaycademyServerClientConfig {
1471
+ /**
1472
+ * Playcademy API key for server-to-server authentication.
1473
+ * Obtain from the Playcademy developer dashboard.
1474
+ */
1475
+ apiKey: string;
1476
+ /**
1477
+ * Optional path to playcademy.config.js file.
1478
+ * If not provided, searches current directory and up to 3 parent directories.
1479
+ * Ignored if `config` is provided directly.
1480
+ *
1481
+ * @example './config/playcademy.config.js'
1482
+ */
1483
+ configPath?: string;
1484
+ /**
1485
+ * Optional config object (for edge environments without filesystem).
1486
+ * If provided, skips filesystem-based config loading.
1487
+ *
1488
+ * @example { name: 'My Game', integrations: { timeback: {...} } }
1489
+ */
1490
+ config?: PlaycademyConfig;
1491
+ /**
1492
+ * Optional base URL for Playcademy API.
1493
+ * Defaults to environment variables or 'https://hub.playcademy.net'.
1494
+ *
1495
+ * @example 'http://localhost:3000' for local development
1496
+ */
1497
+ baseUrl?: string;
1498
+ /**
1499
+ * Optional game ID.
1500
+ * If not provided, will attempt to fetch from API using the API token.
1501
+ *
1502
+ * @example 'my-math-game'
1503
+ */
1504
+ gameId?: string;
1505
+ }
1506
+ /**
1507
+ * Internal state maintained by the PlaycademyClient instance.
1508
+ *
1509
+ * @internal
1510
+ */
1511
+ interface PlaycademyServerClientState {
1512
+ /** API key for authentication */
1513
+ apiKey: string;
1514
+ /** Base URL for API requests */
1515
+ baseUrl: string;
1516
+ /** Game identifier */
1517
+ gameId: string;
1518
+ /** Loaded game configuration from playcademy.config.js */
1519
+ config: PlaycademyConfig;
1520
+ /**
1521
+ * TimeBack course ID fetched from the Playcademy API.
1522
+ * Used for all TimeBack event recording.
1523
+ */
1524
+ courseId?: string;
1153
1525
  }
1154
1526
 
1155
1527
  declare const users: drizzle_orm_pg_core.PgTableWithColumns<{
@@ -3970,7 +4342,7 @@ declare const UpsertGameMetadataSchema: z.ZodEffects<z.ZodObject<{
3970
4342
  displayName: z.ZodString;
3971
4343
  mapElementId: z.ZodNullable<z.ZodOptional<z.ZodString>>;
3972
4344
  platform: z.ZodEnum<["web", "godot", "unity"]>;
3973
- metadata: z.ZodDefault<z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
4345
+ metadata: z.ZodDefault<z.ZodOptional<z.ZodEffects<z.ZodRecord<z.ZodString, z.ZodUnknown>, Record<string, unknown>, Record<string, unknown>>>>;
3974
4346
  gameType: z.ZodDefault<z.ZodOptional<z.ZodEnum<["hosted", "external"]>>>;
3975
4347
  visibility: z.ZodOptional<z.ZodEnum<["visible", "unlisted", "internal"]>>;
3976
4348
  externalUrl: z.ZodOptional<z.ZodString>;
@@ -4648,480 +5020,64 @@ interface PlayerInventoryItem {
4648
5020
  inventoryItemEntryId: InventoryItemRow['id'];
4649
5021
  item: ItemRow;
4650
5022
  quantity: InventoryItemRow['quantity'];
4651
- updatedAt: InventoryItemRow['updatedAt'];
4652
- }
4653
- interface PlayerLocation {
4654
- mapIdentifier: MapRow['identifier'];
4655
- mapDisplayName: MapRow['displayName'];
4656
- tileX?: number;
4657
- tileY?: number;
4658
- worldX?: number;
4659
- worldY?: number;
4660
- direction?: 'up' | 'down' | 'left' | 'right';
4661
- }
4662
- interface PlayerSessionPayload {
4663
- profile: PlayerProfile;
4664
- currencies: PlayerCurrency[];
4665
- inventory: PlayerInventoryItem[];
4666
- ownedGameIds: Game['id'][];
4667
- currentLocation?: PlayerLocation;
4668
- characterCreated?: UserRow['characterCreated'];
4669
- playerCharacter?: PlayerCharacterRow | null;
4670
- }
4671
- /**
4672
- * Map-related Composite Types
4673
- * DB row + joined game/item data
4674
- */
4675
- type MapElementWithGame = MapElementRow & {
4676
- game: {
4677
- id: string;
4678
- displayName: string;
4679
- } | null;
4680
- };
4681
- type MapObjectWithItem = MapObjectRow & {
4682
- item: {
4683
- id: string;
4684
- slug: string;
4685
- displayName: string;
4686
- description?: string | null;
4687
- imageUrl?: string | null;
4688
- isPlaceable: boolean;
4689
- metadata?: PlaceableItemMetadata | null;
4690
- };
4691
- };
4692
- /**
4693
- * Game custom hostname with validation records
4694
- */
4695
- type GameCustomHostname = GameCustomHostnameRow & {
4696
- validationRecords?: DomainValidationRecords;
4697
- };
4698
-
4699
- type UserLevelRow = typeof userLevels.$inferSelect;
4700
- type LevelConfigRow = typeof levelConfigs.$inferSelect;
4701
- type UserLevelWithConfig = UserLevelRow & {
4702
- xpToNextLevel: number;
4703
- nextLevelConfig?: LevelConfigRow;
4704
- };
4705
-
4706
- type SpriteTemplateRow = typeof spriteTemplates.$inferSelect;
4707
-
4708
- type NotificationRow = InferSelectModel<typeof notifications>;
4709
-
4710
- /**
4711
- * Connection monitoring types
4712
- *
4713
- * Type definitions for connection state, configuration, and callbacks.
4714
- */
4715
- /**
4716
- * Possible connection states.
4717
- *
4718
- * - **online**: Connection is stable and healthy
4719
- * - **offline**: Complete loss of network connectivity
4720
- * - **degraded**: Connection is slow or experiencing intermittent issues
4721
- */
4722
- type ConnectionState = 'online' | 'offline' | 'degraded';
4723
-
4724
- /**
4725
- * Connection Manager
4726
- *
4727
- * Manages connection monitoring and integrates it with the Playcademy client.
4728
- * Handles event wiring, state management, and disconnect callbacks.
4729
- *
4730
- * In iframe mode, disables local monitoring and listens to platform connection
4731
- * state broadcasts instead (avoids duplicate heartbeats).
4732
- */
4733
-
4734
- /**
4735
- * Configuration for the ConnectionManager.
4736
- */
4737
- interface ConnectionManagerConfig {
4738
- /** Base URL for API requests (used for heartbeat pings) */
4739
- baseUrl: string;
4740
- /** Authentication context (iframe vs standalone) for alert routing */
4741
- authContext?: {
4742
- isInIframe: boolean;
4743
- };
4744
- /** Handler to call when connection issues are detected */
4745
- onDisconnect?: DisconnectHandler;
4746
- /** Callback to emit connection change events to the client */
4747
- onConnectionChange?: (state: ConnectionState, reason: string) => void;
4748
- }
4749
- /**
4750
- * Manages connection monitoring for the Playcademy client.
4751
- *
4752
- * The ConnectionManager serves as an integration layer between the low-level
4753
- * ConnectionMonitor and the PlaycademyClient. It handles:
4754
- * - Event wiring and coordination
4755
- * - Disconnect callbacks with context
4756
- * - Platform-level alert integration
4757
- * - Request success/failure tracking
4758
- *
4759
- * This class is used internally by PlaycademyClient and typically not
4760
- * instantiated directly by game developers.
4761
- *
4762
- * @see {@link ConnectionMonitor} for the underlying monitoring implementation
4763
- * @see {@link PlaycademyClient.onDisconnect} for the public API
4764
- */
4765
- declare class ConnectionManager {
4766
- private monitor?;
4767
- private authContext?;
4768
- private disconnectHandler?;
4769
- private connectionChangeCallback?;
4770
- private currentState;
4771
- private additionalDisconnectHandlers;
4772
- /**
4773
- * Creates a new ConnectionManager instance.
4774
- *
4775
- * @param config - Configuration options for the manager
4776
- *
4777
- * @example
4778
- * ```typescript
4779
- * const manager = new ConnectionManager({
4780
- * baseUrl: 'https://api.playcademy.com',
4781
- * authContext: { isInIframe: false },
4782
- * onDisconnect: (context) => {
4783
- * console.log(`Disconnected: ${context.state}`)
4784
- * },
4785
- * onConnectionChange: (state, reason) => {
4786
- * console.log(`Connection changed: ${state}`)
4787
- * }
4788
- * })
4789
- * ```
4790
- */
4791
- constructor(config: ConnectionManagerConfig);
4792
- /**
4793
- * Gets the current connection state.
4794
- *
4795
- * @returns The current connection state ('online', 'offline', or 'degraded')
4796
- *
4797
- * @example
4798
- * ```typescript
4799
- * const state = manager.getState()
4800
- * if (state === 'offline') {
4801
- * console.log('No connection')
4802
- * }
4803
- * ```
4804
- */
4805
- getState(): ConnectionState;
4806
- /**
4807
- * Manually triggers a connection check immediately.
4808
- *
4809
- * Forces a heartbeat ping to verify the current connection status.
4810
- * Useful when you need to check connectivity before a critical operation.
4811
- *
4812
- * In iframe mode, this returns the last known state from platform.
4813
- *
4814
- * @returns Promise resolving to the current connection state
4815
- *
4816
- * @example
4817
- * ```typescript
4818
- * const state = await manager.checkNow()
4819
- * if (state === 'online') {
4820
- * await performCriticalOperation()
4821
- * }
4822
- * ```
4823
- */
4824
- checkNow(): Promise<ConnectionState>;
4825
- /**
4826
- * Reports a successful API request to the connection monitor.
4827
- *
4828
- * This resets the consecutive failure counter and transitions from
4829
- * 'degraded' to 'online' state if applicable.
4830
- *
4831
- * Typically called automatically by the SDK's request wrapper.
4832
- * No-op in iframe mode (platform handles monitoring).
4833
- */
4834
- reportRequestSuccess(): void;
4835
- /**
4836
- * Reports a failed API request to the connection monitor.
4837
- *
4838
- * Only network errors are tracked (not 4xx/5xx HTTP responses).
4839
- * After consecutive failures exceed the threshold, the state transitions
4840
- * to 'degraded' or 'offline'.
4841
- *
4842
- * Typically called automatically by the SDK's request wrapper.
4843
- * No-op in iframe mode (platform handles monitoring).
4844
- *
4845
- * @param error - The error from the failed request
4846
- */
4847
- reportRequestFailure(error: unknown): void;
4848
- /**
4849
- * Registers a callback to be called when connection issues are detected.
4850
- *
4851
- * The callback only fires for 'offline' and 'degraded' states, not when
4852
- * recovering to 'online'. This provides a clean API for games to handle
4853
- * disconnect scenarios without being notified of every state change.
4854
- *
4855
- * Works in both iframe and standalone modes transparently.
4856
- *
4857
- * @param callback - Function to call when connection degrades
4858
- * @returns Cleanup function to unregister the callback
4859
- *
4860
- * @example
4861
- * ```typescript
4862
- * const cleanup = manager.onDisconnect(({ state, reason, displayAlert }) => {
4863
- * if (state === 'offline') {
4864
- * displayAlert?.('Connection lost. Saving your progress...', { type: 'error' })
4865
- * saveGameState()
4866
- * }
4867
- * })
4868
- *
4869
- * // Later: cleanup() to unregister
4870
- * ```
4871
- */
4872
- onDisconnect(callback: DisconnectHandler): () => void;
4873
- /**
4874
- * Stops connection monitoring and performs cleanup.
4875
- *
4876
- * Removes event listeners and clears heartbeat intervals.
4877
- * Should be called when the client is being destroyed.
4878
- */
4879
- stop(): void;
4880
- /**
4881
- * Sets up listener for platform connection state broadcasts (iframe mode only).
4882
- */
4883
- private _setupPlatformListener;
4884
- /**
4885
- * Handles connection state changes from the monitor or platform.
4886
- *
4887
- * Coordinates between:
4888
- * 1. Emitting events to the client (for client.on('connectionChange'))
4889
- * 2. Calling the disconnect handler if configured
4890
- * 3. Calling any additional handlers registered via onDisconnect()
4891
- *
4892
- * @param state - The new connection state
4893
- * @param reason - Human-readable reason for the state change
4894
- */
4895
- private _handleConnectionChange;
4896
- }
4897
-
4898
- /**
4899
- * @fileoverview Playcademy Messaging System
4900
- *
4901
- * This file implements a unified messaging system for the Playcademy platform that handles
4902
- * communication between different contexts:
4903
- *
4904
- * 1. **Iframe-to-Parent Communication**: When games run inside iframes (production/development),
4905
- * they need to communicate with the parent window using postMessage API
4906
- *
4907
- * 2. **Local Communication**: When games run in the same context (local development),
4908
- * they use CustomEvents for internal messaging
4909
- *
4910
- * The system automatically detects the runtime environment and chooses the appropriate
4911
- * transport method, abstracting this complexity from the developer.
4912
- *
4913
- * **Architecture Overview**:
4914
- * - Games run in iframes for security and isolation
4915
- * - Parent window (Playcademy shell) manages game lifecycle
4916
- * - Messages flow bidirectionally between parent and iframe
4917
- * - Local development mode simulates this architecture without iframes
4918
- */
4919
-
4920
- /**
4921
- * Enumeration of all message types used in the Playcademy messaging system.
4922
- *
4923
- * **Message Flow Patterns**:
4924
- *
4925
- * **Parent → Game (Overworld → Game)**:
4926
- * - INIT: Provides game with authentication token and configuration
4927
- * - TOKEN_REFRESH: Updates game's authentication token before expiry
4928
- * - PAUSE/RESUME: Controls game execution state
4929
- * - FORCE_EXIT: Immediately terminates the game
4930
- * - OVERLAY: Shows/hides UI overlays over the game
4931
- *
4932
- * **Game → Parent (Game → Overworld)**:
4933
- * - READY: Game has loaded and is ready to receive messages
4934
- * - EXIT: Game requests to be closed (user clicked exit, game ended, etc.)
4935
- * - TELEMETRY: Game reports performance metrics (FPS, memory usage, etc.)
4936
- */
4937
- declare enum MessageEvents {
4938
- /**
4939
- * Initializes the game with authentication context and configuration.
4940
- * Sent immediately after game iframe loads.
4941
- * Payload:
4942
- * - `baseUrl`: string
4943
- * - `token`: string
4944
- * - `gameId`: string
4945
- */
4946
- INIT = "PLAYCADEMY_INIT",
4947
- /**
4948
- * Updates the game's authentication token before it expires.
4949
- * Sent periodically to maintain valid authentication.
4950
- * Payload:
4951
- * - `token`: string
4952
- * - `exp`: number
4953
- */
4954
- TOKEN_REFRESH = "PLAYCADEMY_TOKEN_REFRESH",
4955
- /**
4956
- * Pauses game execution (e.g., when user switches tabs).
4957
- * Game should pause timers, animations, and user input.
4958
- * Payload: void
4959
- */
4960
- PAUSE = "PLAYCADEMY_PAUSE",
4961
- /**
4962
- * Resumes game execution after being paused.
4963
- * Game should restore timers, animations, and user input.
4964
- * Payload: void
4965
- */
4966
- RESUME = "PLAYCADEMY_RESUME",
4967
- /**
4968
- * Forces immediate game termination (emergency exit).
4969
- * Game should clean up resources and exit immediately.
4970
- * Payload: void
4971
- */
4972
- FORCE_EXIT = "PLAYCADEMY_FORCE_EXIT",
4973
- /**
4974
- * Shows or hides UI overlays over the game.
4975
- * Game may need to pause or adjust rendering accordingly.
4976
- * Payload: boolean (true = show overlay, false = hide overlay)
4977
- */
4978
- OVERLAY = "PLAYCADEMY_OVERLAY",
4979
- /**
4980
- * Broadcasts connection state changes to games.
4981
- * Sent by platform when network connectivity changes.
4982
- * Payload:
4983
- * - `state`: 'online' | 'offline' | 'degraded'
4984
- * - `reason`: string
4985
- */
4986
- CONNECTION_STATE = "PLAYCADEMY_CONNECTION_STATE",
4987
- /**
4988
- * Game has finished loading and is ready to receive messages.
4989
- * Sent once after game initialization is complete.
4990
- * Payload: void
4991
- */
4992
- READY = "PLAYCADEMY_READY",
4993
- /**
4994
- * Game SDK initialization failed.
4995
- * Sent when the game iframe fails to complete SDK init (e.g.
4996
- * origin validation failure, INIT timeout, client creation error).
4997
- * Payload:
4998
- * - `reason`: string — human-readable failure description
4999
- */
5000
- INIT_ERROR = "PLAYCADEMY_INIT_ERROR",
5001
- /**
5002
- * Game requests to be closed/exited.
5003
- * Sent when user clicks exit button or game naturally ends.
5004
- * Payload: void
5005
- */
5006
- EXIT = "PLAYCADEMY_EXIT",
5007
- /**
5008
- * Game reports performance telemetry data.
5009
- * Sent periodically for monitoring and analytics.
5010
- * Payload:
5011
- * - `fps`: number
5012
- * - `mem`: number
5013
- */
5014
- TELEMETRY = "PLAYCADEMY_TELEMETRY",
5015
- /**
5016
- * Game reports key events to parent.
5017
- * Sent when certain keys are pressed within the game iframe.
5018
- * Payload:
5019
- * - `key`: string
5020
- * - `code?`: string
5021
- * - `type`: 'keydown' | 'keyup'
5022
- */
5023
- KEY_EVENT = "PLAYCADEMY_KEY_EVENT",
5024
- /**
5025
- * Game requests platform to display an alert.
5026
- * Sent when connection issues are detected or other important events occur.
5027
- * Payload:
5028
- * - `message`: string
5029
- * - `options`: `{ type?: 'info' | 'warning' | 'error', duration?: number }`
5030
- */
5031
- DISPLAY_ALERT = "PLAYCADEMY_DISPLAY_ALERT",
5032
- /**
5033
- * Game signals that demo mode has ended.
5034
- * Sent when a demo experience reaches its CTA/upgrade boundary.
5035
- * Payload:
5036
- * - `score?`: number
5037
- * - `durationMs?`: number
5038
- * - `metadata?`: `Record<string, unknown>`
5039
- */
5040
- DEMO_END = "PLAYCADEMY_DEMO_END",
5041
- /**
5042
- * Notifies about authentication state changes.
5043
- * Can be sent in both directions depending on auth flow.
5044
- * Payload:
5045
- * - `authenticated`: boolean
5046
- * - `user`: UserInfo | null
5047
- * - `error`: Error | null
5048
- */
5049
- AUTH_STATE_CHANGE = "PLAYCADEMY_AUTH_STATE_CHANGE",
5050
- /**
5051
- * OAuth callback data from popup/new-tab windows.
5052
- * Sent from popup window back to parent after OAuth completes.
5053
- * Payload:
5054
- * - `code`: string (OAuth authorization code)
5055
- * - `state`: string (OAuth state for CSRF protection)
5056
- * - `error`: string | null (OAuth error if any)
5057
- */
5058
- AUTH_CALLBACK = "PLAYCADEMY_AUTH_CALLBACK"
5059
- }
5060
-
5061
- /**
5062
- * Auto-initializes a PlaycademyClient with context from the environment.
5063
- * Works in both iframe mode (production/development) and standalone mode (local dev).
5064
- *
5065
- * This is the recommended way to initialize the SDK as it automatically:
5066
- * - Detects the runtime environment (iframe vs standalone)
5067
- * - Configures the client with the appropriate context
5068
- * - Sets up event listeners for token refresh
5069
- * - Exposes the client for debugging in development mode
5070
- *
5071
- * @param options - Optional configuration overrides
5072
- * @param options.baseUrl - Override the base URL for API requests
5073
- * @returns Promise resolving to a fully initialized PlaycademyClient
5074
- * @throws Error if not running in a browser context
5075
- *
5076
- * @example
5077
- * ```typescript
5078
- * // Default initialization
5079
- * const client = await PlaycademyClient.init()
5080
- *
5081
- * // With custom base URL
5082
- * const client = await PlaycademyClient.init({ baseUrl: 'https://custom.api.com' })
5083
- * ```
5023
+ updatedAt: InventoryItemRow['updatedAt'];
5024
+ }
5025
+ interface PlayerLocation {
5026
+ mapIdentifier: MapRow['identifier'];
5027
+ mapDisplayName: MapRow['displayName'];
5028
+ tileX?: number;
5029
+ tileY?: number;
5030
+ worldX?: number;
5031
+ worldY?: number;
5032
+ direction?: 'up' | 'down' | 'left' | 'right';
5033
+ }
5034
+ interface PlayerSessionPayload {
5035
+ profile: PlayerProfile;
5036
+ currencies: PlayerCurrency[];
5037
+ inventory: PlayerInventoryItem[];
5038
+ ownedGameIds: Game['id'][];
5039
+ currentLocation?: PlayerLocation;
5040
+ characterCreated?: UserRow['characterCreated'];
5041
+ playerCharacter?: PlayerCharacterRow | null;
5042
+ }
5043
+ /**
5044
+ * Map-related Composite Types
5045
+ * DB row + joined game/item data
5084
5046
  */
5085
- declare function init<T extends PlaycademyBaseClient = PlaycademyBaseClient>(this: new (config?: Partial<ClientConfig>) => T, options?: {
5086
- baseUrl?: string;
5087
- allowedParentOrigins?: string[];
5088
- onDisconnect?: DisconnectHandler;
5089
- enableConnectionMonitoring?: boolean;
5090
- }): Promise<T>;
5091
-
5047
+ type MapElementWithGame = MapElementRow & {
5048
+ game: {
5049
+ id: string;
5050
+ displayName: string;
5051
+ } | null;
5052
+ };
5053
+ type MapObjectWithItem = MapObjectRow & {
5054
+ item: {
5055
+ id: string;
5056
+ slug: string;
5057
+ displayName: string;
5058
+ description?: string | null;
5059
+ imageUrl?: string | null;
5060
+ isPlaceable: boolean;
5061
+ metadata?: PlaceableItemMetadata | null;
5062
+ };
5063
+ };
5092
5064
  /**
5093
- * Authenticates a user with email and password.
5094
- *
5095
- * This is a standalone authentication method that doesn't require an initialized client.
5096
- * Use this for login flows before creating a client instance.
5097
- *
5098
- * @deprecated Use client.auth.login() instead for better error handling and automatic token management
5099
- *
5100
- * @param baseUrl - The base URL of the Playcademy API
5101
- * @param email - User's email address
5102
- * @param password - User's password
5103
- * @returns Promise resolving to authentication response with token
5104
- * @throws PlaycademyError if authentication fails or network error occurs
5105
- *
5106
- * @example
5107
- * ```typescript
5108
- * // Preferred approach:
5109
- * const client = new PlaycademyClient({ baseUrl: '/api' })
5110
- * const result = await client.auth.login({
5111
- * email: 'user@example.com',
5112
- * password: 'password'
5113
- * })
5114
- *
5115
- * // Legacy approach (still works):
5116
- * try {
5117
- * const response = await PlaycademyClient.login('/api', 'user@example.com', 'password')
5118
- * const client = new PlaycademyClient({ token: response.token })
5119
- * } catch (error) {
5120
- * console.error('Login failed:', error.message)
5121
- * }
5122
- * ```
5065
+ * Game custom hostname with validation records
5123
5066
  */
5124
- declare function login(baseUrl: string, email: string, password: string): Promise<LoginResponse>;
5067
+ type GameCustomHostname = GameCustomHostnameRow & {
5068
+ validationRecords?: DomainValidationRecords;
5069
+ };
5070
+
5071
+ type UserLevelRow = typeof userLevels.$inferSelect;
5072
+ type LevelConfigRow = typeof levelConfigs.$inferSelect;
5073
+ type UserLevelWithConfig = UserLevelRow & {
5074
+ xpToNextLevel: number;
5075
+ nextLevelConfig?: LevelConfigRow;
5076
+ };
5077
+
5078
+ type SpriteTemplateRow = typeof spriteTemplates.$inferSelect;
5079
+
5080
+ type NotificationRow = InferSelectModel<typeof notifications>;
5125
5081
 
5126
5082
  /**
5127
5083
  * @fileoverview Authentication Strategy Pattern
@@ -5295,52 +5251,69 @@ declare abstract class PlaycademyBaseClient {
5295
5251
  }
5296
5252
 
5297
5253
  /**
5298
- * Options for configuring activity tracking behavior.
5254
+ * Auto-initializes a PlaycademyClient with context from the environment.
5255
+ * Works in both iframe mode (production/development) and standalone mode (local dev).
5256
+ *
5257
+ * This is the recommended way to initialize the SDK as it automatically:
5258
+ * - Detects the runtime environment (iframe vs standalone)
5259
+ * - Configures the client with the appropriate context
5260
+ * - Sets up event listeners for token refresh
5261
+ * - Exposes the client for debugging in development mode
5262
+ *
5263
+ * @param options - Optional configuration overrides
5264
+ * @param options.baseUrl - Override the base URL for API requests
5265
+ * @returns Promise resolving to a fully initialized PlaycademyClient
5266
+ * @throws Error if not running in a browser context
5267
+ *
5268
+ * @example
5269
+ * ```typescript
5270
+ * // Default initialization
5271
+ * const client = await PlaycademyClient.init()
5272
+ *
5273
+ * // With custom base URL
5274
+ * const client = await PlaycademyClient.init({ baseUrl: 'https://custom.api.com' })
5275
+ * ```
5299
5276
  */
5300
- interface StartActivityOptions {
5301
- /**
5302
- * How long heartbeats continue after the activity is automatically paused
5303
- * because the tab is hidden or the player is inactive while visible.
5304
- * Defaults to 10 minutes. Set to `Infinity` to keep heartbeats running
5305
- * indefinitely during automatic pauses. Invalid values fall back to the
5306
- * 10-minute default.
5307
- */
5308
- pausedHeartbeatTimeoutMs?: number;
5309
- /**
5310
- * @deprecated Use `pausedHeartbeatTimeoutMs` instead.
5311
- *
5312
- * Backward-compatible alias for callers that still use the old option
5313
- * name from earlier SDK releases.
5314
- */
5315
- hiddenTimeoutMs?: number;
5316
- /**
5317
- * How often to flush periodic heartbeats with accumulated time data.
5318
- * Defaults to 15 seconds. Set to `Infinity` to disable the interval;
5319
- * final unload/endActivity flushes still run. Values must be greater than
5320
- * 0 or `Infinity`; invalid values fall back to the 15-second default.
5321
- */
5322
- heartbeatIntervalMs?: number;
5323
- /**
5324
- * How long the tab can remain visible without keyboard or mouse activity
5325
- * before the activity is marked inactive. Defaults to 10 minutes. Set to
5326
- * `Infinity` to disable keyboard/mouse inactivity tracking. Invalid values
5327
- * fall back to the 10-minute default.
5328
- */
5329
- inactivityTimeoutMs?: number;
5330
- /**
5331
- * Stable identifier for this activity run. When provided, it is used on
5332
- * every heartbeat and on endActivity instead of a freshly-generated UUID.
5333
- *
5334
- * Pass the same `runId` across multiple `startActivity()` calls (for
5335
- * example, after the player closes and reopens a resumable activity) so
5336
- * downstream systems can correlate related sessions into a single run.
5337
- *
5338
- * Must be a UUID (the backend validates it as such) and unique per
5339
- * logical run. If omitted, the SDK generates a new UUID on each call,
5340
- * which means every session is treated as its own run.
5341
- */
5342
- runId?: string;
5343
- }
5277
+ declare function init<T extends PlaycademyBaseClient = PlaycademyBaseClient>(this: new (config?: Partial<ClientConfig>) => T, options?: {
5278
+ baseUrl?: string;
5279
+ allowedParentOrigins?: string[];
5280
+ onDisconnect?: DisconnectHandler;
5281
+ enableConnectionMonitoring?: boolean;
5282
+ }): Promise<T>;
5283
+
5284
+ /**
5285
+ * Authenticates a user with email and password.
5286
+ *
5287
+ * This is a standalone authentication method that doesn't require an initialized client.
5288
+ * Use this for login flows before creating a client instance.
5289
+ *
5290
+ * @deprecated Use client.auth.login() instead for better error handling and automatic token management
5291
+ *
5292
+ * @param baseUrl - The base URL of the Playcademy API
5293
+ * @param email - User's email address
5294
+ * @param password - User's password
5295
+ * @returns Promise resolving to authentication response with token
5296
+ * @throws PlaycademyError if authentication fails or network error occurs
5297
+ *
5298
+ * @example
5299
+ * ```typescript
5300
+ * // Preferred approach:
5301
+ * const client = new PlaycademyClient({ baseUrl: '/api' })
5302
+ * const result = await client.auth.login({
5303
+ * email: 'user@example.com',
5304
+ * password: 'password'
5305
+ * })
5306
+ *
5307
+ * // Legacy approach (still works):
5308
+ * try {
5309
+ * const response = await PlaycademyClient.login('/api', 'user@example.com', 'password')
5310
+ * const client = new PlaycademyClient({ token: response.token })
5311
+ * } catch (error) {
5312
+ * console.error('Login failed:', error.message)
5313
+ * }
5314
+ * ```
5315
+ */
5316
+ declare function login(baseUrl: string, email: string, password: string): Promise<LoginResponse>;
5344
5317
 
5345
5318
  /**
5346
5319
  * Playcademy SDK client for game developers.
@@ -5404,19 +5377,22 @@ declare class PlaycademyClient extends PlaycademyBaseClient {
5404
5377
  * User context (cached from init, refreshable):
5405
5378
  * - `user.role` - User's role (student, parent, teacher, etc.)
5406
5379
  * - `user.enrollments` - Courses the player is enrolled in for this game
5380
+ * - `user.refresh({ only: ['enrollments'] })` - Refresh enrollments from server
5407
5381
  * - `user.organizations` - Schools/districts the player belongs to
5408
5382
  * - `user.fetch()` - Refresh user context from server
5409
5383
  *
5410
5384
  * Activity tracking:
5411
- * - `startActivity(metadata)` - Begin tracking an activity with automatic
5412
- * hidden-tab and visible-tab inactivity handling, plus configurable
5413
- * paused-heartbeat timeout behavior
5385
+ * - `currentRunId` - Current activity run ID, or undefined when inactive
5386
+ * - `startActivity(metadata)` - Begin tracking an activity, return its run
5387
+ * ID, and automatically handle hidden-tab and visible-tab inactivity
5388
+ * with configurable paused-heartbeat timeout behavior
5414
5389
  * - `pauseActivity()` / `resumeActivity()` - Pause/resume timer
5415
5390
  * - `endActivity(scoreData)` - Submit activity results to TimeBack
5416
5391
  */
5417
5392
  timeback: {
5418
5393
  readonly user: TimebackUser;
5419
- startActivity: (metadata: ActivityData, options?: StartActivityOptions) => void;
5394
+ readonly currentRunId: string | undefined;
5395
+ startActivity: (metadata: ActivityData, options?: StartActivityOptions) => StartActivityResult;
5420
5396
  pauseActivity: () => void;
5421
5397
  resumeActivity: () => void;
5422
5398
  endActivity: (data: EndActivityScoreData) => Promise<EndActivityResponse>;
@@ -5496,6 +5472,57 @@ declare class PlaycademyClient extends PlaycademyBaseClient {
5496
5472
  };
5497
5473
  }
5498
5474
 
5475
+ /**
5476
+ * Options for configuring activity tracking behavior.
5477
+ */
5478
+ interface StartActivityOptions {
5479
+ /**
5480
+ * How long heartbeats continue after the activity is automatically paused
5481
+ * because the tab is hidden or the player is inactive while visible.
5482
+ * Defaults to 10 minutes. Set to `Infinity` to keep heartbeats running
5483
+ * indefinitely during automatic pauses. Invalid values fall back to the
5484
+ * 10-minute default.
5485
+ */
5486
+ pausedHeartbeatTimeoutMs?: number;
5487
+ /**
5488
+ * @deprecated Use `pausedHeartbeatTimeoutMs` instead.
5489
+ *
5490
+ * Backward-compatible alias for callers that still use the old option
5491
+ * name from earlier SDK releases.
5492
+ */
5493
+ hiddenTimeoutMs?: number;
5494
+ /**
5495
+ * How often to flush periodic heartbeats with accumulated time data.
5496
+ * Defaults to 15 seconds. Set to `Infinity` to disable the interval;
5497
+ * final unload/endActivity flushes still run. Values must be greater than
5498
+ * 0 or `Infinity`; invalid values fall back to the 15-second default.
5499
+ */
5500
+ heartbeatIntervalMs?: number;
5501
+ /**
5502
+ * How long the tab can remain visible without keyboard or mouse activity
5503
+ * before the activity is marked inactive. Defaults to 10 minutes. Set to
5504
+ * `Infinity` to disable keyboard/mouse inactivity tracking. Invalid values
5505
+ * fall back to the 10-minute default.
5506
+ */
5507
+ inactivityTimeoutMs?: number;
5508
+ /**
5509
+ * Stable identifier for this activity run. When provided, it is used on
5510
+ * every heartbeat and on endActivity instead of a freshly-generated UUID.
5511
+ *
5512
+ * Pass the same `runId` across multiple `startActivity()` calls (for
5513
+ * example, after the player closes and reopens a resumable activity) so
5514
+ * downstream systems can correlate related sessions into a single run.
5515
+ *
5516
+ * Must be a UUID (the backend validates it as such) and unique per
5517
+ * logical run. If omitted, the SDK generates a new UUID on each call,
5518
+ * which means every session is treated as its own run.
5519
+ */
5520
+ runId?: string;
5521
+ }
5522
+ interface StartActivityResult {
5523
+ runId: string;
5524
+ }
5525
+
5499
5526
  /**
5500
5527
  * Type definitions for the game timeback namespace.
5501
5528
  *
@@ -5505,7 +5532,9 @@ declare class PlaycademyClient extends PlaycademyBaseClient {
5505
5532
 
5506
5533
  /**
5507
5534
  * A TimeBack enrollment for the current game session.
5508
- * Alias for UserEnrollment without the optional gameId.
5535
+ * Alias for UserEnrollment without the optional gameId. Active enrollment IDs
5536
+ * are available at `enrollment.enrollmentIds?.active` when supplied by the
5537
+ * platform.
5509
5538
  */
5510
5539
  type TimebackEnrollment = Omit<UserEnrollment, 'gameId'>;
5511
5540
  /**
@@ -5541,6 +5570,14 @@ interface TimebackUserContext {
5541
5570
  /** User's organizations (schools/districts) */
5542
5571
  organizations: TimebackOrganization[];
5543
5572
  }
5573
+ /**
5574
+ * Slice options for refreshing the cached TimeBack user context.
5575
+ */
5576
+ type TimebackUserRefreshField = 'enrollments';
5577
+ interface TimebackUserRefreshOptions extends TTLCacheConfig {
5578
+ /** Refresh only these user data fields */
5579
+ only: readonly TimebackUserRefreshField[];
5580
+ }
5544
5581
  /**
5545
5582
  * XP data access for the current user.
5546
5583
  * Results are cached for 5 seconds to avoid redundant network requests.
@@ -5581,7 +5618,7 @@ interface TimebackUserXp {
5581
5618
  fetch(options?: GetXpOptions): Promise<XpResponse>;
5582
5619
  }
5583
5620
  /**
5584
- * TimeBack user object with both cached getters and fetch method.
5621
+ * TimeBack user object with cached getters, fetch, and targeted refresh methods.
5585
5622
  */
5586
5623
  interface TimebackUser extends TimebackUserContext {
5587
5624
  /**
@@ -5590,9 +5627,14 @@ interface TimebackUser extends TimebackUserContext {
5590
5627
  * @param options - Cache options (pass { force: true } to bypass cache)
5591
5628
  * @returns Promise resolving to fresh user context
5592
5629
  */
5593
- fetch(options?: {
5594
- force?: boolean;
5595
- }): Promise<TimebackUserContext>;
5630
+ fetch(options?: TTLCacheConfig): Promise<TimebackUserContext>;
5631
+ /**
5632
+ * Refresh selected TimeBack user data from the server.
5633
+ * Updates the cached user snapshot used by the synchronous getters.
5634
+ * @param options - Refresh fields and cache options
5635
+ * @returns Promise resolving to the updated user context
5636
+ */
5637
+ refresh(options: TimebackUserRefreshOptions): Promise<TimebackUserContext>;
5596
5638
  /**
5597
5639
  * XP data for the current user.
5598
5640
  * Call `xp.fetch()` to get XP from the server.
@@ -6167,4 +6209,4 @@ interface AssessmentBankStatus {
6167
6209
  }
6168
6210
 
6169
6211
  export { AchievementCompletionType, NotificationStatus, NotificationType, PlaycademyClient };
6170
- export type { AchievementCurrent, AchievementHistoryEntry, AchievementProgressResponse, AchievementScopeType, AchievementWithStatus, AssessmentBankStatus, AssessmentRow, AssessmentSummary, AuthCallbackPayload, AuthOptions, AuthProviderType, AuthResult, AuthServerMessage, AuthStateChangePayload, AuthStateUpdate, AuthenticatedUser, BetterAuthApiKey, BetterAuthApiKeyResponse, BetterAuthSignInResponse, BucketFile, CharacterComponentRow as CharacterComponent, CharacterComponentType, CharacterComponentWithSpriteUrl, CharacterComponentsOptions, ClientConfig, ClientEvents, ConnectionStatePayload, CourseXp, CreateCharacterData, CreateMapObjectData, CurrencyRow as Currency, DemoEndOptions, DemoEndPayload, DevUploadEvent, DevUploadHooks, DeveloperStatusEnumType, DeveloperStatusResponse, DeveloperStatusValue, DisconnectContext, DisconnectHandler, DisplayAlertPayload, EventListeners, ExternalGame, FetchedGame, Game, GameActivityMetrics, GameContextPayload, GameCourseMetrics, GameCustomHostname, GameInitUser, GameLeaderboardEntry, GameManifest, MapRow as GameMap, GameMetricsProxyResponse, GameMetricsResponse, GameMetricsUnsupportedReason, GamePlatform, GameRow as GameRecord, GameSessionRow as GameSession, GameTimebackIntegration, GameTokenResponse, GameType, GameUser, GetXpOptions, HostedGame, InitErrorPayload, InitPayload, InsertCurrencyInput, InsertItemInput, InsertShopListingInput, InteractionType, InventoryItemRow as InventoryItem, InventoryItemWithItem, InventoryMutationResponse, ItemRow as Item, ItemType, KVKeyEntry, KVKeyMetadata, KVSeedEntry, KVStatsResponse, KeyEventPayload, LeaderboardEntry, LeaderboardOptions, LeaderboardTimeframe, LevelConfigRow as LevelConfig, LevelProgressResponse, LevelUpCheckResult, LoginResponse, ManifestV1, ManifestV2, ManifestVersions, MapData, MapElementRow as MapElement, MapElementMetadata, MapElementWithGame, MapObjectRow as MapObject, MapObjectWithItem, NotificationRow as Notification, NotificationStats, PlaceableItemMetadata, PlatformTimebackUser, PlatformTimebackUserContext, PlaycademyMode, PlaycademyServerClientConfig, PlaycademyServerClientState, PlayerCharacterRow as PlayerCharacter, PlayerCharacterAccessoryRow as PlayerCharacterAccessory, PlayerCurrency, PlayerInventoryItem, PlayerProfile, PlayerSessionPayload, PopulateStudentResponse, QtiTestQuestionRef, QtiTestQuestionsResponse, RealtimeTokenResponse, ScoreSubmission, ShopCurrency, ShopDisplayItem, ShopListingRow as ShopListing, ShopViewResponse, SpriteAnimationFrame, SpriteConfigWithDimensions, SpriteTemplateRow as SpriteTemplate, SpriteTemplateData, StartSessionResponse, TelemetryPayload, TimebackEnrollment, TimebackInitContext, TimebackOrganization, TimebackUser, TimebackUserContext, TimebackUserXp, TodayXpResponse, TokenRefreshPayload, TokenType, TotalXpResponse, UpdateCharacterData, UpdateCurrencyInput, UpdateItemInput, UpdateShopListingInput, UpsertGameMetadataInput, UserRow as User, UserEnrollment, UserInfo, UserLevelRow as UserLevel, UserLevelWithConfig, UserOrganization, UserRank, UserRankResponse, UserRoleEnumType, UserScore, UserTimebackData, XPAddResult, XpHistoryResponse, XpResponse, XpSummaryResponse };
6212
+ export type { AchievementCurrent, AchievementHistoryEntry, AchievementProgressResponse, AchievementScopeType, AchievementWithStatus, AssessmentBankStatus, AssessmentRow, AssessmentSummary, AuthCallbackPayload, AuthOptions, AuthProviderType, AuthResult, AuthServerMessage, AuthStateChangePayload, AuthStateUpdate, AuthenticatedUser, BetterAuthApiKey, BetterAuthApiKeyResponse, BetterAuthSignInResponse, BucketFile, CharacterComponentRow as CharacterComponent, CharacterComponentType, CharacterComponentWithSpriteUrl, CharacterComponentsOptions, ClientConfig, ClientEvents, ConnectionStatePayload, CourseXp, CreateCharacterData, CreateMapObjectData, CurrencyRow as Currency, DemoEndOptions, DemoEndPayload, DevUploadEvent, DevUploadHooks, DeveloperStatusEnumType, DeveloperStatusResponse, DeveloperStatusValue, DisconnectContext, DisconnectHandler, DisplayAlertPayload, EventListeners, ExternalGame, FetchedGame, Game, GameActivityMetrics, GameContextPayload, GameCourseMetrics, GameCustomHostname, GameInitUser, GameLeaderboardEntry, GameManifest, MapRow as GameMap, GameMetricsProxyResponse, GameMetricsResponse, GameMetricsUnsupportedReason, GamePlatform, GameRow as GameRecord, GameSessionRow as GameSession, GameTimebackIntegration, GameTokenResponse, GameType, GameUser, GetXpOptions, HostedGame, InitErrorPayload, InitPayload, InsertCurrencyInput, InsertItemInput, InsertShopListingInput, InteractionType, InventoryItemRow as InventoryItem, InventoryItemWithItem, InventoryMutationResponse, ItemRow as Item, ItemType, KVKeyEntry, KVKeyMetadata, KVSeedEntry, KVStatsResponse, KeyEventPayload, LeaderboardEntry, LeaderboardOptions, LeaderboardTimeframe, LevelConfigRow as LevelConfig, LevelProgressResponse, LevelUpCheckResult, LoginResponse, ManifestV1, ManifestV2, ManifestVersions, MapData, MapElementRow as MapElement, MapElementMetadata, MapElementWithGame, MapObjectRow as MapObject, MapObjectWithItem, NotificationRow as Notification, NotificationStats, PlaceableItemMetadata, PlatformTimebackUser, PlatformTimebackUserContext, PlaycademyMode, PlaycademyServerClientConfig, PlaycademyServerClientState, PlayerCharacterRow as PlayerCharacter, PlayerCharacterAccessoryRow as PlayerCharacterAccessory, PlayerCurrency, PlayerInventoryItem, PlayerProfile, PlayerSessionPayload, PopulateStudentResponse, QtiTestQuestionRef, QtiTestQuestionsResponse, RealtimeTokenResponse, ScoreSubmission, ShopCurrency, ShopDisplayItem, ShopListingRow as ShopListing, ShopViewResponse, SpriteAnimationFrame, SpriteConfigWithDimensions, SpriteTemplateRow as SpriteTemplate, SpriteTemplateData, StartActivityOptions, StartActivityResult, StartSessionResponse, TelemetryPayload, TimebackEnrollment, TimebackInitContext, TimebackOrganization, TimebackUser, TimebackUserContext, TimebackUserRefreshField, TimebackUserRefreshOptions, TimebackUserXp, TodayXpResponse, TokenRefreshPayload, TokenType, TotalXpResponse, UpdateCharacterData, UpdateCurrencyInput, UpdateItemInput, UpdateShopListingInput, UpsertGameMetadataInput, UserRow as User, UserEnrollment, UserInfo, UserLevelRow as UserLevel, UserLevelWithConfig, UserOrganization, UserRank, UserRankResponse, UserRoleEnumType, UserScore, UserTimebackData, XPAddResult, XpHistoryResponse, XpResponse, XpSummaryResponse };