@playcademy/sdk 0.14.0 → 0.14.1-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,8 +2,8 @@ import { SchemaInfo } from '@playcademy/cloudflare';
2
2
  import { TimebackGrade, TimebackSubject, HeartbeatRequest, TimebackCourseConfig, CourseConfig, OrganizationConfig, ComponentConfig, ResourceConfig, ComponentResourceConfig } from '@playcademy/types/timeback';
3
3
  export { QtiTestQuestionRef, QtiTestQuestionsResponse } from '@playcademy/types/timeback';
4
4
  import * as _playcademy_types from '@playcademy/types';
5
- import { GameManifest } from '@playcademy/types';
6
- export { AuthenticatedUser, DeveloperStatusEnumType, DeveloperStatusResponse, DeveloperStatusValue, GameCourseMetrics, GameLeaderboardEntry, GameManifest, GameMetricComparisonKind, GameMetricComparisonMetric, GameMetricComparisonRow, GameMetricComparisonRowStatus, GameMetricsProxyResponse, GameMetricsResponse, GameMetricsUnsupportedReason, GamePlatform, GameRunMetrics, GameRunMetricsComparison, GameRunMetricsComparisonStatus, GameRunMetricsComparisonSummary, GameTimebackIntegration, GameType, GameUser, LeaderboardEntry, LeaderboardOptions, LeaderboardTimeframe, ManifestV1, ManifestV2, ManifestVersions, PopulateStudentResponse, UserEnrollment, UserInfo, UserOrganization, UserRank, UserRankResponse, UserRoleEnumType, UserScore, UserTimebackData } from '@playcademy/types';
5
+ import { GameManifest, LocalDayContext } from '@playcademy/types';
6
+ export { AuthenticatedUser, DeveloperStatusEnumType, DeveloperStatusResponse, DeveloperStatusValue, GameCourseMetrics, GameLeaderboardEntry, GameManifest, GameMetricComparisonKind, GameMetricComparisonMetric, GameMetricComparisonRow, GameMetricComparisonRowStatus, GameMetricsProxyResponse, GameMetricsResponse, GameMetricsUnsupportedReason, GamePlatform, GameRunMetrics, GameRunMetricsComparison, GameRunMetricsComparisonStatus, GameRunMetricsComparisonSummary, GameTimebackIntegration, GameType, GameUser, LeaderboardEntry, LeaderboardOptions, LeaderboardTimeframe, LocalDayContext, LocalDaySource, ManifestV1, ManifestV2, ManifestVersions, PopulateStudentResponse, UserEnrollment, UserInfo, UserOrganization, UserRank, UserRankResponse, UserRoleEnumType, UserScore, UserTimebackData } from '@playcademy/types';
7
7
  import * as drizzle_orm_pg_core from 'drizzle-orm/pg-core';
8
8
  import { z } from 'zod';
9
9
  import { DomainValidationRecords } from '@playcademy/types/game';
@@ -756,951 +756,273 @@ interface AuthStrategy {
756
756
  getHeaders(): Record<string, string>;
757
757
  }
758
758
 
759
+ declare const users: drizzle_orm_pg_core.PgTableWithColumns<{
760
+ name: "user";
761
+ schema: undefined;
762
+ columns: {
763
+ id: drizzle_orm_pg_core.PgColumn<{
764
+ name: "id";
765
+ tableName: "user";
766
+ dataType: "string";
767
+ columnType: "PgText";
768
+ data: string;
769
+ driverParam: string;
770
+ notNull: true;
771
+ hasDefault: true;
772
+ isPrimaryKey: true;
773
+ isAutoincrement: false;
774
+ hasRuntimeDefault: true;
775
+ enumValues: [string, ...string[]];
776
+ baseColumn: never;
777
+ identity: undefined;
778
+ generated: undefined;
779
+ }, {}, {}>;
780
+ name: drizzle_orm_pg_core.PgColumn<{
781
+ name: "name";
782
+ tableName: "user";
783
+ dataType: "string";
784
+ columnType: "PgText";
785
+ data: string;
786
+ driverParam: string;
787
+ notNull: true;
788
+ hasDefault: false;
789
+ isPrimaryKey: false;
790
+ isAutoincrement: false;
791
+ hasRuntimeDefault: false;
792
+ enumValues: [string, ...string[]];
793
+ baseColumn: never;
794
+ identity: undefined;
795
+ generated: undefined;
796
+ }, {}, {}>;
797
+ username: drizzle_orm_pg_core.PgColumn<{
798
+ name: "username";
799
+ tableName: "user";
800
+ dataType: "string";
801
+ columnType: "PgText";
802
+ data: string;
803
+ driverParam: string;
804
+ notNull: false;
805
+ hasDefault: false;
806
+ isPrimaryKey: false;
807
+ isAutoincrement: false;
808
+ hasRuntimeDefault: false;
809
+ enumValues: [string, ...string[]];
810
+ baseColumn: never;
811
+ identity: undefined;
812
+ generated: undefined;
813
+ }, {}, {}>;
814
+ email: drizzle_orm_pg_core.PgColumn<{
815
+ name: "email";
816
+ tableName: "user";
817
+ dataType: "string";
818
+ columnType: "PgText";
819
+ data: string;
820
+ driverParam: string;
821
+ notNull: true;
822
+ hasDefault: false;
823
+ isPrimaryKey: false;
824
+ isAutoincrement: false;
825
+ hasRuntimeDefault: false;
826
+ enumValues: [string, ...string[]];
827
+ baseColumn: never;
828
+ identity: undefined;
829
+ generated: undefined;
830
+ }, {}, {}>;
831
+ isAnonymous: drizzle_orm_pg_core.PgColumn<{
832
+ name: "is_anonymous";
833
+ tableName: "user";
834
+ dataType: "boolean";
835
+ columnType: "PgBoolean";
836
+ data: boolean;
837
+ driverParam: boolean;
838
+ notNull: true;
839
+ hasDefault: true;
840
+ isPrimaryKey: false;
841
+ isAutoincrement: false;
842
+ hasRuntimeDefault: false;
843
+ enumValues: undefined;
844
+ baseColumn: never;
845
+ identity: undefined;
846
+ generated: undefined;
847
+ }, {}, {}>;
848
+ timebackId: drizzle_orm_pg_core.PgColumn<{
849
+ name: "timeback_id";
850
+ tableName: "user";
851
+ dataType: "string";
852
+ columnType: "PgText";
853
+ data: string;
854
+ driverParam: string;
855
+ notNull: false;
856
+ hasDefault: false;
857
+ isPrimaryKey: false;
858
+ isAutoincrement: false;
859
+ hasRuntimeDefault: false;
860
+ enumValues: [string, ...string[]];
861
+ baseColumn: never;
862
+ identity: undefined;
863
+ generated: undefined;
864
+ }, {}, {}>;
865
+ learningTimeZone: drizzle_orm_pg_core.PgColumn<{
866
+ name: "learning_time_zone";
867
+ tableName: "user";
868
+ dataType: "string";
869
+ columnType: "PgText";
870
+ data: string;
871
+ driverParam: string;
872
+ notNull: false;
873
+ hasDefault: false;
874
+ isPrimaryKey: false;
875
+ isAutoincrement: false;
876
+ hasRuntimeDefault: false;
877
+ enumValues: [string, ...string[]];
878
+ baseColumn: never;
879
+ identity: undefined;
880
+ generated: undefined;
881
+ }, {}, {}>;
882
+ learningTimeZoneUpdatedAt: drizzle_orm_pg_core.PgColumn<{
883
+ name: "learning_time_zone_updated_at";
884
+ tableName: "user";
885
+ dataType: "date";
886
+ columnType: "PgTimestamp";
887
+ data: Date;
888
+ driverParam: string;
889
+ notNull: false;
890
+ hasDefault: false;
891
+ isPrimaryKey: false;
892
+ isAutoincrement: false;
893
+ hasRuntimeDefault: false;
894
+ enumValues: undefined;
895
+ baseColumn: never;
896
+ identity: undefined;
897
+ generated: undefined;
898
+ }, {}, {}>;
899
+ emailVerified: drizzle_orm_pg_core.PgColumn<{
900
+ name: "email_verified";
901
+ tableName: "user";
902
+ dataType: "boolean";
903
+ columnType: "PgBoolean";
904
+ data: boolean;
905
+ driverParam: boolean;
906
+ notNull: true;
907
+ hasDefault: true;
908
+ isPrimaryKey: false;
909
+ isAutoincrement: false;
910
+ hasRuntimeDefault: false;
911
+ enumValues: undefined;
912
+ baseColumn: never;
913
+ identity: undefined;
914
+ generated: undefined;
915
+ }, {}, {}>;
916
+ image: drizzle_orm_pg_core.PgColumn<{
917
+ name: "image";
918
+ tableName: "user";
919
+ dataType: "string";
920
+ columnType: "PgText";
921
+ data: string;
922
+ driverParam: string;
923
+ notNull: false;
924
+ hasDefault: false;
925
+ isPrimaryKey: false;
926
+ isAutoincrement: false;
927
+ hasRuntimeDefault: false;
928
+ enumValues: [string, ...string[]];
929
+ baseColumn: never;
930
+ identity: undefined;
931
+ generated: undefined;
932
+ }, {}, {}>;
933
+ role: drizzle_orm_pg_core.PgColumn<{
934
+ name: "role";
935
+ tableName: "user";
936
+ dataType: "string";
937
+ columnType: "PgEnumColumn";
938
+ data: "admin" | "developer" | "player" | "teacher";
939
+ driverParam: string;
940
+ notNull: true;
941
+ hasDefault: true;
942
+ isPrimaryKey: false;
943
+ isAutoincrement: false;
944
+ hasRuntimeDefault: false;
945
+ enumValues: ["admin", "player", "developer", "teacher"];
946
+ baseColumn: never;
947
+ identity: undefined;
948
+ generated: undefined;
949
+ }, {}, {}>;
950
+ developerStatus: drizzle_orm_pg_core.PgColumn<{
951
+ name: "developer_status";
952
+ tableName: "user";
953
+ dataType: "string";
954
+ columnType: "PgEnumColumn";
955
+ data: "approved" | "none" | "pending";
956
+ driverParam: string;
957
+ notNull: true;
958
+ hasDefault: true;
959
+ isPrimaryKey: false;
960
+ isAutoincrement: false;
961
+ hasRuntimeDefault: false;
962
+ enumValues: ["none", "pending", "approved"];
963
+ baseColumn: never;
964
+ identity: undefined;
965
+ generated: undefined;
966
+ }, {}, {}>;
967
+ createdAt: drizzle_orm_pg_core.PgColumn<{
968
+ name: "created_at";
969
+ tableName: "user";
970
+ dataType: "date";
971
+ columnType: "PgTimestamp";
972
+ data: Date;
973
+ driverParam: string;
974
+ notNull: true;
975
+ hasDefault: false;
976
+ isPrimaryKey: false;
977
+ isAutoincrement: false;
978
+ hasRuntimeDefault: false;
979
+ enumValues: undefined;
980
+ baseColumn: never;
981
+ identity: undefined;
982
+ generated: undefined;
983
+ }, {}, {}>;
984
+ updatedAt: drizzle_orm_pg_core.PgColumn<{
985
+ name: "updated_at";
986
+ tableName: "user";
987
+ dataType: "date";
988
+ columnType: "PgTimestamp";
989
+ data: Date;
990
+ driverParam: string;
991
+ notNull: true;
992
+ hasDefault: false;
993
+ isPrimaryKey: false;
994
+ isAutoincrement: false;
995
+ hasRuntimeDefault: false;
996
+ enumValues: undefined;
997
+ baseColumn: never;
998
+ identity: undefined;
999
+ generated: undefined;
1000
+ }, {}, {}>;
1001
+ };
1002
+ dialect: 'pg';
1003
+ }>;
1004
+
1005
+ interface GameMetadata {
1006
+ description?: string;
1007
+ emoji?: string;
1008
+ [key: string]: unknown;
1009
+ }
759
1010
  /**
760
- * Base Playcademy SDK client with shared infrastructure.
761
- * Provides authentication, HTTP requests, events,
762
- * and fundamental namespaces used by all clients.
763
- *
764
- * Extended by PlaycademyClient (game SDK) and PlaycademyInternalClient (platform SDK).
1011
+ * DNS validation records for custom hostname
1012
+ * Structure for the validationRecords JSON field in game_custom_hostnames table
765
1013
  */
766
- declare abstract class PlaycademyBaseClient {
767
- baseUrl: string;
768
- gameUrl?: string;
769
- mode: PlaycademyMode;
770
- protected authStrategy: AuthStrategy;
771
- protected gameId?: string;
772
- protected config: Partial<ClientConfig>;
773
- protected listeners: EventListeners;
774
- protected authContext?: {
775
- isInIframe: boolean;
776
- };
777
- protected initPayload?: InitPayload;
778
- protected launchId?: string;
779
- protected gameOrigin?: string;
780
- constructor(config?: Partial<ClientConfig>);
781
- /**
782
- * Gets the effective base URL for API requests.
783
- */
784
- getBaseUrl(): string;
785
- /**
786
- * Gets the effective game backend URL for integration requests.
787
- */
788
- protected getGameBackendUrl(): string;
789
- /**
790
- * Simple ping method for testing connectivity.
791
- */
792
- ping(): string;
793
- /**
794
- * Sets the authentication token for API requests.
795
- */
796
- setToken(token: string | null, tokenType?: TokenType): void;
797
- setLaunchId(launchId: string | null | undefined): void;
798
- /**
799
- * Gets the current token type.
800
- */
801
- getTokenType(): TokenType;
802
- /**
803
- * Gets the current authentication token.
804
- */
805
- getToken(): string | null;
806
- /**
807
- * Checks if the client has a valid API token.
808
- */
809
- isAuthenticated(): boolean;
810
- /**
811
- * Registers a callback to be called when authentication state changes.
812
- */
813
- onAuthChange(callback: (token: string | null) => void): void;
814
- /**
815
- * Sets the authentication context for the client.
816
- * @internal
817
- */
818
- _setAuthContext(context: {
819
- isInIframe: boolean;
820
- }): void;
821
- /**
822
- * Registers an event listener for client events.
823
- *
824
- * @param event - The event name to listen for.
825
- * @param callback - The handler invoked when the event fires.
826
- * @returns A cleanup function that removes this specific listener.
827
- */
828
- on<E extends keyof ClientEvents>(event: E, callback: (payload: ClientEvents[E]) => void): () => void;
829
- /**
830
- * Removes a previously registered event listener.
831
- *
832
- * @param event - The event name to stop listening for.
833
- * @param callback - The exact function reference passed to {@link on}.
834
- */
835
- off<E extends keyof ClientEvents>(event: E, callback: (payload: ClientEvents[E]) => void): void;
836
- /**
837
- * Emits an event to all registered listeners.
838
- */
839
- protected emit<E extends keyof ClientEvents>(event: E, payload: ClientEvents[E]): void;
840
- /**
841
- * Makes an authenticated HTTP request to the platform API.
842
- */
843
- protected request<T>(path: string, method: Method, options?: {
844
- body?: unknown;
845
- headers?: Record<string, string>;
846
- raw?: boolean;
847
- retryPolicy?: RetryPolicy;
848
- }): Promise<T>;
849
- /**
850
- * Makes an authenticated HTTP request to the game's backend Worker.
851
- */
852
- protected requestGameBackend<T>(path: string, method: Method, body?: unknown, headers?: Record<string, string>, options?: {
853
- raw?: boolean;
854
- retryPolicy?: RetryPolicy;
855
- }): Promise<T>;
856
- /**
857
- * Ensures a gameId is available, throwing an error if not.
858
- */
859
- protected _ensureGameId(): string;
860
- private _parseOrigin;
861
- /**
862
- * Detects and sets the authentication context (iframe vs standalone).
863
- */
864
- private _detectAuthContext;
865
- /**
866
- * Current user data.
867
- * - `me()` - Get authenticated user profile
868
- */
869
- users: {
870
- me: () => Promise<_playcademy_types.AuthenticatedUser>;
1014
+ interface CustomHostnameValidationRecords {
1015
+ /** TXT record for ownership verification */
1016
+ ownership?: {
1017
+ name?: string;
1018
+ value?: string;
1019
+ type?: string;
871
1020
  };
872
- }
873
-
874
- /**
875
- * Auto-initializes a PlaycademyClient with context from the environment.
876
- * Works in both iframe mode (production/development) and standalone mode (local dev).
877
- *
878
- * This is the recommended way to initialize the SDK as it automatically:
879
- * - Detects the runtime environment (iframe vs standalone)
880
- * - Configures the client with the appropriate context
881
- * - Sets up event listeners for token refresh
882
- * - Exposes the client for debugging in development mode
883
- *
884
- * @param options - Optional configuration overrides
885
- * @param options.baseUrl - Override the base URL for API requests
886
- * @returns Promise resolving to a fully initialized PlaycademyClient
887
- * @throws Error if not running in a browser context
888
- *
889
- * @example
890
- * ```typescript
891
- * // Default initialization
892
- * const client = await PlaycademyClient.init()
893
- *
894
- * // With custom base URL
895
- * const client = await PlaycademyClient.init({ baseUrl: 'https://custom.api.com' })
896
- * ```
897
- */
898
- declare function init<T extends PlaycademyBaseClient = PlaycademyBaseClient>(this: new (config?: Partial<ClientConfig>) => T, options?: {
899
- baseUrl?: string;
900
- allowedParentOrigins?: string[];
901
- }): Promise<T>;
902
-
903
- /**
904
- * Authenticates a user with email and password.
905
- *
906
- * This is a standalone authentication method that doesn't require an initialized client.
907
- * Use this for login flows before creating a client instance.
908
- *
909
- * @deprecated Use client.auth.login() instead for better error handling and automatic token management
910
- *
911
- * @param baseUrl - The base URL of the Playcademy API
912
- * @param email - User's email address
913
- * @param password - User's password
914
- * @returns Promise resolving to authentication response with token
915
- * @throws PlaycademyError if authentication fails or network error occurs
916
- *
917
- * @example
918
- * ```typescript
919
- * // Preferred approach:
920
- * const client = new PlaycademyClient({ baseUrl: '/api' })
921
- * const result = await client.auth.login({
922
- * email: 'user@example.com',
923
- * password: 'password'
924
- * })
925
- *
926
- * // Legacy approach (still works):
927
- * try {
928
- * const response = await PlaycademyClient.login('/api', 'user@example.com', 'password')
929
- * const client = new PlaycademyClient({ token: response.token })
930
- * } catch (error) {
931
- * console.error('Login failed:', error.message)
932
- * }
933
- * ```
934
- */
935
- declare function login(baseUrl: string, email: string, password: string): Promise<LoginResponse>;
936
-
937
- /**
938
- * Cache configuration types for runtime customization
939
- */
940
- /**
941
- * Runtime configuration for TTL cache behavior
942
- */
943
- interface TTLCacheConfig {
944
- /** Time-to-live in milliseconds. Set to 0 to disable caching for this call. */
945
- ttl?: number;
946
- /** Force refresh, bypassing cache */
947
- force?: boolean;
948
- /** Skip cache and fetch fresh data (alias for force) */
949
- skipCache?: boolean;
950
- }
951
-
952
- /**
953
- * Options for configuring activity tracking behavior.
954
- */
955
- interface StartActivityOptions {
956
- /**
957
- * How long heartbeats continue after the activity is automatically paused
958
- * because the tab is hidden or the player is inactive while visible.
959
- * Defaults to 10 minutes. Set to `Infinity` to keep heartbeats running
960
- * indefinitely during automatic pauses. Invalid values fall back to the
961
- * 10-minute default.
962
- */
963
- pausedHeartbeatTimeoutMs?: number;
964
- /**
965
- * @deprecated Use `pausedHeartbeatTimeoutMs` instead.
966
- *
967
- * Backward-compatible alias for callers that still use the old option
968
- * name from earlier SDK releases.
969
- */
970
- hiddenTimeoutMs?: number;
971
- /**
972
- * How often to flush periodic heartbeats with accumulated time data.
973
- * Defaults to 15 seconds. Set to `Infinity` to disable the interval;
974
- * final unload/endActivity flushes still run. Values must be greater than
975
- * 0 or `Infinity`; invalid values fall back to the 15-second default.
976
- */
977
- heartbeatIntervalMs?: number;
978
- /**
979
- * How long the tab can remain visible without keyboard or mouse activity
980
- * before the activity is marked inactive. Defaults to 10 minutes. Set to
981
- * `Infinity` to disable keyboard/mouse inactivity tracking. Invalid values
982
- * fall back to the 10-minute default.
983
- */
984
- inactivityTimeoutMs?: number;
985
- /**
986
- * Stable identifier for this activity run. When provided, it is used on
987
- * every heartbeat and on endActivity instead of a freshly-generated UUID.
988
- *
989
- * Pass the same `runId` across multiple `startActivity()` calls (for
990
- * example, after the player closes and reopens a resumable activity) so
991
- * downstream systems can correlate related sessions into a single run.
992
- *
993
- * Must be a UUID (the backend validates it as such) and unique per
994
- * logical run. If omitted, the SDK generates a new UUID on each call,
995
- * which means every session is treated as its own run.
996
- */
997
- runId?: string;
998
- }
999
- interface StartActivityResult {
1000
- runId: string;
1001
- }
1002
-
1003
- /**
1004
- * Type definitions for the game timeback namespace.
1005
- *
1006
- * SDK-specific types like TimebackInitContext that wrap the core types
1007
- * from @playcademy/types/user (which are re-exported via types/data.ts).
1008
- */
1009
-
1010
- /**
1011
- * A TimeBack enrollment for the current game session.
1012
- * Alias for UserEnrollment without the optional gameId.
1013
- */
1014
- type TimebackEnrollment = Omit<UserEnrollment, 'gameId'>;
1015
- /**
1016
- * A TimeBack organization (school/district) for the current user.
1017
- * Alias for UserOrganization.
1018
- */
1019
- type TimebackOrganization = UserOrganization;
1020
- /**
1021
- * TimeBack context passed during game initialization.
1022
- * This is sent from the platform (cademy) to the game iframe via postMessage.
1023
- */
1024
- interface TimebackInitContext {
1025
- /** User's TimeBack ID */
1026
- id: string;
1027
- /** User's role in TimeBack (student, parent, teacher, etc.) */
1028
- role: TimebackUserRole;
1029
- /** User's enrollments for this game (one per grade/subject combo) */
1030
- enrollments: TimebackEnrollment[];
1031
- /** User's organizations (schools/districts) */
1032
- organizations: TimebackOrganization[];
1033
- }
1034
- /**
1035
- * TimeBack user context with current data (may be stale from init).
1036
- * Use `fetch()` to get fresh data from the server.
1037
- */
1038
- interface TimebackUserContext {
1039
- /** User's TimeBack ID */
1040
- id: string | undefined;
1041
- /** User's role in TimeBack (student, parent, teacher, etc.) */
1042
- role: TimebackUserRole | undefined;
1043
- /** User's enrollments for this game */
1044
- enrollments: TimebackEnrollment[];
1045
- /** User's organizations (schools/districts) */
1046
- organizations: TimebackOrganization[];
1047
- }
1048
- /**
1049
- * Slice options for refreshing the cached TimeBack user context.
1050
- */
1051
- type TimebackUserRefreshField = 'enrollments';
1052
- interface TimebackUserRefreshOptions extends TTLCacheConfig {
1053
- /** Refresh only these user data fields */
1054
- only: readonly TimebackUserRefreshField[];
1055
- }
1056
- /**
1057
- * XP data access for the current user.
1058
- * Results are cached for 5 seconds to avoid redundant network requests.
1059
- */
1060
- interface TimebackUserXp {
1061
- /**
1062
- * Fetch XP data from the server.
1063
- * Returns XP for all courses in this game, or filter by grade/subject.
1064
- * Results are cached for 5 seconds (use `force: true` to bypass).
1065
- *
1066
- * @param options - Query options
1067
- * @param options.grade - Grade level to filter (must be used with subject)
1068
- * @param options.subject - Subject to filter (must be used with grade)
1069
- * @param options.include - Additional data to include: 'perCourse', 'today'
1070
- * @param options.force - Bypass cache and fetch fresh data (default: false)
1071
- * @returns Promise resolving to XP data
1072
- *
1073
- * @example
1074
- * ```typescript
1075
- * // Get total XP for all game courses
1076
- * const xp = await client.timeback.user.xp.fetch()
1077
- *
1078
- * // Get XP for a specific grade/subject
1079
- * const xp = await client.timeback.user.xp.fetch({
1080
- * grade: 3,
1081
- * subject: 'Math'
1082
- * })
1083
- *
1084
- * // Get XP with per-course breakdown
1085
- * const xp = await client.timeback.user.xp.fetch({
1086
- * include: ['perCourse', 'today']
1087
- * })
1088
- *
1089
- * // Force fresh data
1090
- * const xp = await client.timeback.user.xp.fetch({ force: true })
1091
- * ```
1092
- */
1093
- fetch(options?: GetXpOptions): Promise<XpResponse>;
1094
- }
1095
- /**
1096
- * TimeBack user object with cached getters, fetch, and targeted refresh methods.
1097
- */
1098
- interface TimebackUser extends TimebackUserContext {
1099
- /**
1100
- * Fetch TimeBack data from the server (cached for 5 min).
1101
- * Updates the cached values so subsequent property access returns fresh data.
1102
- * @param options - Cache options (pass { force: true } to bypass cache)
1103
- * @returns Promise resolving to fresh user context
1104
- */
1105
- fetch(options?: TTLCacheConfig): Promise<TimebackUserContext>;
1106
- /**
1107
- * Refresh selected TimeBack user data from the server.
1108
- * Updates the cached user snapshot used by the synchronous getters.
1109
- * @param options - Refresh fields and cache options
1110
- * @returns Promise resolving to the updated user context
1111
- */
1112
- refresh(options: TimebackUserRefreshOptions): Promise<TimebackUserContext>;
1113
- /**
1114
- * XP data for the current user.
1115
- * Call `xp.fetch()` to get XP from the server.
1116
- */
1117
- xp: TimebackUserXp;
1118
- /**
1119
- * Mastery data for the current user.
1120
- * Call `mastery.fetch()` to get mastery progress from the server.
1121
- */
1122
- mastery: TimebackUserMastery;
1123
- /**
1124
- * Highest-grade-mastered data for the current user.
1125
- * Call `highestGradeMastered.fetch({ subject })` to get the student's
1126
- * highest mastered grade for a subject.
1127
- */
1128
- highestGradeMastered: TimebackUserHighestGradeMastered;
1129
- }
1130
- /**
1131
- * Mastery data access for the current user.
1132
- * Results are cached for 5 seconds to avoid redundant network requests.
1133
- */
1134
- interface TimebackUserMastery {
1135
- /**
1136
- * Fetch mastery data from the server.
1137
- * Returns mastery for all courses in this game, or filter by grade/subject.
1138
- * Returned masteredUnits values are true analytics values and may exceed
1139
- * the course masterableUnits maximum when historical data violates the
1140
- * expected invariant.
1141
- * Results are cached for 5 seconds (use `force: true` to bypass).
1142
- *
1143
- * @param options - Query options
1144
- * @param options.grade - Grade level to filter (must be used with subject)
1145
- * @param options.subject - Subject to filter (must be used with grade)
1146
- * @param options.include - Additional data to include: 'perCourse'
1147
- * @param options.force - Bypass cache and fetch fresh data (default: false)
1148
- * @returns Promise resolving to mastery data
1149
- *
1150
- * @example
1151
- * ```typescript
1152
- * // Get total mastery for all game courses
1153
- * const mastery = await client.timeback.user.mastery.fetch()
1154
- *
1155
- * // Get mastery for a specific grade/subject
1156
- * const mastery = await client.timeback.user.mastery.fetch({
1157
- * grade: 3,
1158
- * subject: 'Math'
1159
- * })
1160
- *
1161
- * // Get mastery with per-course breakdown
1162
- * const mastery = await client.timeback.user.mastery.fetch({
1163
- * include: ['perCourse']
1164
- * })
1165
- *
1166
- * // Force fresh data
1167
- * const mastery = await client.timeback.user.mastery.fetch({ force: true })
1168
- * ```
1169
- */
1170
- fetch(options?: GetMasteryOptions): Promise<MasteryResponse>;
1171
- }
1172
- /**
1173
- * Highest-grade-mastered data access for the current user.
1174
- * Results are cached for 5 seconds to avoid redundant network requests.
1175
- */
1176
- interface TimebackUserHighestGradeMastered {
1177
- /**
1178
- * Fetch the highest grade the current student has mastered for a subject.
1179
- * Results are cached for 5 seconds (use `force: true` to bypass).
1180
- */
1181
- fetch(options: GetHighestGradeMasteredOptions): Promise<HighestGradeMasteredResponse>;
1182
- }
1183
- /**
1184
- * Options for querying student XP.
1185
- */
1186
- interface GetXpOptions {
1187
- /** Grade level to filter (must be used with subject) */
1188
- grade?: TimebackGrade;
1189
- /** Subject to filter (must be used with grade) */
1190
- subject?: TimebackSubject;
1191
- /** Additional data to include: 'perCourse', 'today' */
1192
- include?: ('perCourse' | 'today')[];
1193
- /** Bypass cache and fetch fresh data (default: false) */
1194
- force?: boolean;
1195
- }
1196
- /**
1197
- * Options for querying student mastery.
1198
- */
1199
- interface GetMasteryOptions {
1200
- /** Grade level to filter (must be used with subject) */
1201
- grade?: TimebackGrade;
1202
- /** Subject to filter (must be used with grade) */
1203
- subject?: TimebackSubject;
1204
- /** Additional data to include: 'perCourse' */
1205
- include?: 'perCourse'[];
1206
- /** Bypass cache and fetch fresh data (default: false) */
1207
- force?: boolean;
1208
- }
1209
- /**
1210
- * Options for querying highest grade mastered.
1211
- */
1212
- interface GetHighestGradeMasteredOptions {
1213
- /** Subject to query */
1214
- subject: TimebackSubject;
1215
- /** Bypass cache and fetch fresh data (default: false) */
1216
- force?: boolean;
1217
- }
1218
- /**
1219
- * XP data for a single course.
1220
- */
1221
- interface CourseXp {
1222
- grade: TimebackGrade;
1223
- subject: TimebackSubject;
1224
- title: string;
1225
- totalXp: number;
1226
- todayXp?: number;
1227
- }
1228
- /**
1229
- * Response from XP query.
1230
- */
1231
- interface XpResponse {
1232
- totalXp: number;
1233
- todayXp?: number;
1234
- courses?: CourseXp[];
1235
- }
1236
- interface CourseMastery {
1237
- grade: TimebackGrade;
1238
- subject: TimebackSubject;
1239
- title: string;
1240
- /** True mastered units from analytics. May exceed masterableUnits for historical data. */
1241
- masteredUnits: number;
1242
- /** Configured mastery ceiling for the course. */
1243
- masterableUnits: number;
1244
- /** Clamped course completion percentage from 0..100. */
1245
- pctComplete: number;
1246
- isComplete: boolean;
1247
- }
1248
- /**
1249
- * Response from mastery query.
1250
- */
1251
- interface MasteryResponse {
1252
- /** True mastered units across all queried courses. */
1253
- totalMasteredUnits: number;
1254
- /** Configured mastery ceiling across all queried courses. */
1255
- totalMasterableUnits: number;
1256
- courses?: CourseMastery[];
1257
- }
1258
- /**
1259
- * Response from highest-grade-mastered query.
1260
- */
1261
- interface HighestGradeMasteredResponse {
1262
- subject: TimebackSubject;
1263
- highestGradeMastered: TimebackGrade | null;
1264
- }
1265
-
1266
- /**
1267
- * Core client configuration and lifecycle types
1268
- */
1269
-
1270
- type TokenType = 'session' | 'apiKey' | 'gameJwt';
1271
- /**
1272
- * Runtime mode for a Playcademy game client.
1273
- *
1274
- * - `'platform'` — game is embedded in the platform (e.g. the cademy hub)
1275
- * with a real authenticated user and full access to SDK namespaces.
1276
- * - `'demo'` — game is embedded in the landing page demo shell with an
1277
- * anonymous user; platform-only namespaces throw, and `client.demo.*`
1278
- * is the supported surface for signaling demo lifecycle events and
1279
- * reading/updating the anonymous profile.
1280
- * - `'standalone'` — game is running outside any iframe (e.g. `bun run dev`
1281
- * or direct-deploy preview) with a mock token and no real platform
1282
- * context. API calls will not succeed; use this to branch UX locally.
1283
- */
1284
- type PlaycademyMode = 'platform' | 'demo' | 'standalone';
1285
- interface ClientConfig {
1286
- baseUrl: string;
1287
- gameUrl?: string;
1288
- token?: string;
1289
- tokenType?: TokenType;
1290
- gameId?: string;
1291
- launchId?: string;
1292
- mode?: PlaycademyMode;
1293
- }
1294
- interface InitPayload {
1295
- /** Hub API base URL */
1296
- baseUrl: string;
1297
- /** Game deployment URL (serves both frontend assets and backend API) */
1298
- gameUrl?: string;
1299
- /** Short-lived game token */
1300
- token: string;
1301
- /** Game ID */
1302
- gameId: string;
1303
- /** Timeback context (if user has a Timeback account) */
1304
- timeback?: TimebackInitContext;
1305
- /** Runtime mode for the game client */
1306
- mode?: PlaycademyMode;
1307
- /** Launch session correlation ID (UUID, set by platform on game launch) */
1308
- launchId?: string;
1309
- /** When `true`, the parent shell provides a heartbeat relay via postMessage, so the SDK can skip its own `fetch({ keepalive })` beacon on pagehide. Defaults to `false`. */
1310
- hasHeartbeatRelay?: boolean;
1311
- }
1312
- /**
1313
- * Simplified user data passed to games via InitPayload
1314
- * This is a subset of AuthenticatedUser suitable for external game consumption
1315
- *
1316
- * Note: Named GameInitUser to distinguish from the cross-game GameUser DTO
1317
- * exported from @playcademy/types
1318
- */
1319
- interface GameInitUser {
1320
- /** Playcademy user ID */
1321
- id: string;
1322
- /** Unique username */
1323
- username: string | null;
1324
- /** Display name */
1325
- name: string | null;
1326
- /** Email address */
1327
- email: string | null;
1328
- /** Profile image URL */
1329
- image: string | null;
1330
- /** Whether the user has a Timeback account */
1331
- hasTimebackAccount: boolean;
1332
- }
1333
- interface GameContextPayload {
1334
- token: string;
1335
- baseUrl: string;
1336
- gameId: string;
1337
- forwardKeys?: string[];
1338
- mode?: PlaycademyMode;
1339
- }
1340
- type EventListeners = {
1341
- [E in keyof ClientEvents]?: ((payload: ClientEvents[E]) => void)[];
1342
- };
1343
- interface ClientEvents {
1344
- authChange: {
1345
- token: string | null;
1346
- };
1347
- }
1348
-
1349
- /**
1350
- * Event and message payload types for SDK messaging system
1351
- */
1352
-
1353
- /**
1354
- * Authentication state change event payload.
1355
- * Used when authentication state changes in the application.
1356
- */
1357
- interface AuthStateChangePayload {
1358
- /** Whether the user is currently authenticated */
1359
- authenticated: boolean;
1360
- /** User information if authenticated, null otherwise */
1361
- user: UserInfo | null;
1362
- /** Error information if authentication failed */
1363
- error: Error | null;
1364
- }
1365
- /**
1366
- * OAuth callback event payload.
1367
- * Used when OAuth flow completes in popup/new-tab windows.
1368
- */
1369
- interface AuthCallbackPayload {
1370
- /** OAuth authorization code */
1371
- code: string;
1372
- /** OAuth state parameter for CSRF protection */
1373
- state: string;
1374
- /** Error message if OAuth flow failed */
1375
- error: string | null;
1376
- }
1377
- /**
1378
- * Message sent from server callback to opener window.
1379
- * This is the standardized format from our server-first architecture.
1380
- */
1381
- interface AuthServerMessage {
1382
- /** Type of the message */
1383
- type: 'PLAYCADEMY_AUTH_STATE_CHANGE';
1384
- /** Whether the user is currently authenticated */
1385
- authenticated: boolean;
1386
- /** Whether the authentication was successful */
1387
- success: boolean;
1388
- /** Timestamp of the message */
1389
- ts: number;
1390
- /** User information if authentication was successful */
1391
- user?: UserInfo;
1392
- /** Error message if authentication failed */
1393
- error?: string;
1394
- }
1395
- /**
1396
- * Token refresh event payload.
1397
- * Sent when authentication token is updated.
1398
- */
1399
- interface TokenRefreshPayload {
1400
- /** New authentication token */
1401
- token: string;
1402
- /** Token expiration timestamp */
1403
- exp: number;
1404
- }
1405
- /**
1406
- * Telemetry event payload.
1407
- * Performance metrics sent from the game.
1408
- */
1409
- interface TelemetryPayload {
1410
- /** Frames per second */
1411
- fps: number;
1412
- /** Memory usage in MB */
1413
- mem: number;
1414
- }
1415
- /**
1416
- * Keyboard event payload.
1417
- * Key events forwarded from the game.
1418
- */
1419
- interface KeyEventPayload {
1420
- /** Key value (e.g., 'Escape', 'F1') */
1421
- key: string;
1422
- /** Key code (optional) */
1423
- code?: string;
1424
- /** Event type */
1425
- type: 'keydown' | 'keyup';
1426
- }
1427
- /**
1428
- * Init error payload.
1429
- * Sent from game iframe to parent when SDK initialization fails
1430
- * (e.g. origin validation failure, INIT timeout, client creation error).
1431
- */
1432
- interface InitErrorPayload {
1433
- reason: string;
1434
- }
1435
- /**
1436
- * Options for `client.demo.end(score, options?)`.
1437
- *
1438
- * All optional. The landing-page demo shell can render a meaningful CTA
1439
- * from `score` alone, but `durationMs` enables a nicer summary and
1440
- * `metadata` lets games pass any extra context they want to surface.
1441
- */
1442
- interface DemoEndOptions {
1443
- durationMs?: number;
1444
- metadata?: Record<string, unknown>;
1445
- }
1446
- /**
1447
- * Wire payload for the `PLAYCADEMY_DEMO_END` message.
1448
- *
1449
- * `score` is required — the demo shell always needs something to display
1450
- * when the round ends. `durationMs` and `metadata` are optional.
1451
- */
1452
- interface DemoEndPayload extends DemoEndOptions {
1453
- score: number;
1454
- }
1455
- type TimebackHeartbeatRelayRequest = Omit<HeartbeatRequest, 'gameId' | 'studentId' | 'windowStartedAtMs' | 'windowSequence'> & {
1456
- windowStartedAtMs: number;
1457
- };
1458
-
1459
- /**
1460
- * SDK-specific API response types
1461
- */
1462
- interface LoginResponse {
1463
- token: string;
1464
- }
1465
- interface GameTokenResponse {
1466
- token: string;
1467
- exp: number;
1468
- baseUrl?: string;
1469
- }
1470
-
1471
- declare const users: drizzle_orm_pg_core.PgTableWithColumns<{
1472
- name: "user";
1473
- schema: undefined;
1474
- columns: {
1475
- id: drizzle_orm_pg_core.PgColumn<{
1476
- name: "id";
1477
- tableName: "user";
1478
- dataType: "string";
1479
- columnType: "PgText";
1480
- data: string;
1481
- driverParam: string;
1482
- notNull: true;
1483
- hasDefault: true;
1484
- isPrimaryKey: true;
1485
- isAutoincrement: false;
1486
- hasRuntimeDefault: true;
1487
- enumValues: [string, ...string[]];
1488
- baseColumn: never;
1489
- identity: undefined;
1490
- generated: undefined;
1491
- }, {}, {}>;
1492
- name: drizzle_orm_pg_core.PgColumn<{
1493
- name: "name";
1494
- tableName: "user";
1495
- dataType: "string";
1496
- columnType: "PgText";
1497
- data: string;
1498
- driverParam: string;
1499
- notNull: true;
1500
- hasDefault: false;
1501
- isPrimaryKey: false;
1502
- isAutoincrement: false;
1503
- hasRuntimeDefault: false;
1504
- enumValues: [string, ...string[]];
1505
- baseColumn: never;
1506
- identity: undefined;
1507
- generated: undefined;
1508
- }, {}, {}>;
1509
- username: drizzle_orm_pg_core.PgColumn<{
1510
- name: "username";
1511
- tableName: "user";
1512
- dataType: "string";
1513
- columnType: "PgText";
1514
- data: string;
1515
- driverParam: string;
1516
- notNull: false;
1517
- hasDefault: false;
1518
- isPrimaryKey: false;
1519
- isAutoincrement: false;
1520
- hasRuntimeDefault: false;
1521
- enumValues: [string, ...string[]];
1522
- baseColumn: never;
1523
- identity: undefined;
1524
- generated: undefined;
1525
- }, {}, {}>;
1526
- email: drizzle_orm_pg_core.PgColumn<{
1527
- name: "email";
1528
- tableName: "user";
1529
- dataType: "string";
1530
- columnType: "PgText";
1531
- data: string;
1532
- driverParam: string;
1533
- notNull: true;
1534
- hasDefault: false;
1535
- isPrimaryKey: false;
1536
- isAutoincrement: false;
1537
- hasRuntimeDefault: false;
1538
- enumValues: [string, ...string[]];
1539
- baseColumn: never;
1540
- identity: undefined;
1541
- generated: undefined;
1542
- }, {}, {}>;
1543
- isAnonymous: drizzle_orm_pg_core.PgColumn<{
1544
- name: "is_anonymous";
1545
- tableName: "user";
1546
- dataType: "boolean";
1547
- columnType: "PgBoolean";
1548
- data: boolean;
1549
- driverParam: boolean;
1550
- notNull: true;
1551
- hasDefault: true;
1552
- isPrimaryKey: false;
1553
- isAutoincrement: false;
1554
- hasRuntimeDefault: false;
1555
- enumValues: undefined;
1556
- baseColumn: never;
1557
- identity: undefined;
1558
- generated: undefined;
1559
- }, {}, {}>;
1560
- timebackId: drizzle_orm_pg_core.PgColumn<{
1561
- name: "timeback_id";
1562
- tableName: "user";
1563
- dataType: "string";
1564
- columnType: "PgText";
1565
- data: string;
1566
- driverParam: string;
1567
- notNull: false;
1568
- hasDefault: false;
1569
- isPrimaryKey: false;
1570
- isAutoincrement: false;
1571
- hasRuntimeDefault: false;
1572
- enumValues: [string, ...string[]];
1573
- baseColumn: never;
1574
- identity: undefined;
1575
- generated: undefined;
1576
- }, {}, {}>;
1577
- emailVerified: drizzle_orm_pg_core.PgColumn<{
1578
- name: "email_verified";
1579
- tableName: "user";
1580
- dataType: "boolean";
1581
- columnType: "PgBoolean";
1582
- data: boolean;
1583
- driverParam: boolean;
1584
- notNull: true;
1585
- hasDefault: true;
1586
- isPrimaryKey: false;
1587
- isAutoincrement: false;
1588
- hasRuntimeDefault: false;
1589
- enumValues: undefined;
1590
- baseColumn: never;
1591
- identity: undefined;
1592
- generated: undefined;
1593
- }, {}, {}>;
1594
- image: drizzle_orm_pg_core.PgColumn<{
1595
- name: "image";
1596
- tableName: "user";
1597
- dataType: "string";
1598
- columnType: "PgText";
1599
- data: string;
1600
- driverParam: string;
1601
- notNull: false;
1602
- hasDefault: false;
1603
- isPrimaryKey: false;
1604
- isAutoincrement: false;
1605
- hasRuntimeDefault: false;
1606
- enumValues: [string, ...string[]];
1607
- baseColumn: never;
1608
- identity: undefined;
1609
- generated: undefined;
1610
- }, {}, {}>;
1611
- role: drizzle_orm_pg_core.PgColumn<{
1612
- name: "role";
1613
- tableName: "user";
1614
- dataType: "string";
1615
- columnType: "PgEnumColumn";
1616
- data: "admin" | "developer" | "player" | "teacher";
1617
- driverParam: string;
1618
- notNull: true;
1619
- hasDefault: true;
1620
- isPrimaryKey: false;
1621
- isAutoincrement: false;
1622
- hasRuntimeDefault: false;
1623
- enumValues: ["admin", "player", "developer", "teacher"];
1624
- baseColumn: never;
1625
- identity: undefined;
1626
- generated: undefined;
1627
- }, {}, {}>;
1628
- developerStatus: drizzle_orm_pg_core.PgColumn<{
1629
- name: "developer_status";
1630
- tableName: "user";
1631
- dataType: "string";
1632
- columnType: "PgEnumColumn";
1633
- data: "approved" | "none" | "pending";
1634
- driverParam: string;
1635
- notNull: true;
1636
- hasDefault: true;
1637
- isPrimaryKey: false;
1638
- isAutoincrement: false;
1639
- hasRuntimeDefault: false;
1640
- enumValues: ["none", "pending", "approved"];
1641
- baseColumn: never;
1642
- identity: undefined;
1643
- generated: undefined;
1644
- }, {}, {}>;
1645
- createdAt: drizzle_orm_pg_core.PgColumn<{
1646
- name: "created_at";
1647
- tableName: "user";
1648
- dataType: "date";
1649
- columnType: "PgTimestamp";
1650
- data: Date;
1651
- driverParam: string;
1652
- notNull: true;
1653
- hasDefault: false;
1654
- isPrimaryKey: false;
1655
- isAutoincrement: false;
1656
- hasRuntimeDefault: false;
1657
- enumValues: undefined;
1658
- baseColumn: never;
1659
- identity: undefined;
1660
- generated: undefined;
1661
- }, {}, {}>;
1662
- updatedAt: drizzle_orm_pg_core.PgColumn<{
1663
- name: "updated_at";
1664
- tableName: "user";
1665
- dataType: "date";
1666
- columnType: "PgTimestamp";
1667
- data: Date;
1668
- driverParam: string;
1669
- notNull: true;
1670
- hasDefault: false;
1671
- isPrimaryKey: false;
1672
- isAutoincrement: false;
1673
- hasRuntimeDefault: false;
1674
- enumValues: undefined;
1675
- baseColumn: never;
1676
- identity: undefined;
1677
- generated: undefined;
1678
- }, {}, {}>;
1679
- };
1680
- dialect: 'pg';
1681
- }>;
1682
-
1683
- interface GameMetadata {
1684
- description?: string;
1685
- emoji?: string;
1686
- [key: string]: unknown;
1687
- }
1688
- /**
1689
- * DNS validation records for custom hostname
1690
- * Structure for the validationRecords JSON field in game_custom_hostnames table
1691
- */
1692
- interface CustomHostnameValidationRecords {
1693
- /** TXT record for ownership verification */
1694
- ownership?: {
1695
- name?: string;
1696
- value?: string;
1697
- type?: string;
1698
- };
1699
- /** TXT records for SSL certificate validation */
1700
- ssl?: {
1701
- txt_name?: string;
1702
- txt_value?: string;
1703
- }[];
1021
+ /** TXT records for SSL certificate validation */
1022
+ ssl?: {
1023
+ txt_name?: string;
1024
+ txt_value?: string;
1025
+ }[];
1704
1026
  }
1705
1027
  declare const games: drizzle_orm_pg_core.PgTableWithColumns<{
1706
1028
  name: "games";
@@ -2212,146 +1534,866 @@ declare const gameCustomHostnames: drizzle_orm_pg_core.PgTableWithColumns<{
2212
1534
  generated: undefined;
2213
1535
  }, {}, {}>;
2214
1536
  };
2215
- dialect: 'pg';
2216
- }>;
2217
- declare const UpsertGameMetadataSchema: z.ZodEffects<z.ZodObject<{
2218
- displayName: z.ZodString;
2219
- platform: z.ZodEnum<["web", "godot", "unity"]>;
2220
- metadata: z.ZodDefault<z.ZodOptional<z.ZodEffects<z.ZodRecord<z.ZodString, z.ZodUnknown>, Record<string, unknown>, Record<string, unknown>>>>;
2221
- gameType: z.ZodDefault<z.ZodOptional<z.ZodEnum<["hosted", "external"]>>>;
2222
- visibility: z.ZodOptional<z.ZodEnum<["visible", "unlisted", "internal"]>>;
2223
- externalUrl: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
2224
- }, "strip", z.ZodTypeAny, {
2225
- displayName: string;
2226
- platform: "godot" | "unity" | "web";
2227
- metadata: Record<string, unknown>;
2228
- gameType: "external" | "hosted";
2229
- visibility?: "internal" | "unlisted" | "visible" | undefined;
2230
- externalUrl?: string | undefined;
2231
- }, {
2232
- displayName: string;
2233
- platform: "godot" | "unity" | "web";
2234
- metadata?: Record<string, unknown> | undefined;
2235
- gameType?: "external" | "hosted" | undefined;
2236
- visibility?: "internal" | "unlisted" | "visible" | undefined;
2237
- externalUrl?: string | undefined;
2238
- }>, {
2239
- displayName: string;
2240
- platform: "godot" | "unity" | "web";
2241
- metadata: Record<string, unknown>;
2242
- gameType: "external" | "hosted";
2243
- visibility?: "internal" | "unlisted" | "visible" | undefined;
2244
- externalUrl?: string | undefined;
2245
- }, {
2246
- displayName: string;
2247
- platform: "godot" | "unity" | "web";
2248
- metadata?: Record<string, unknown> | undefined;
2249
- gameType?: "external" | "hosted" | undefined;
2250
- visibility?: "internal" | "unlisted" | "visible" | undefined;
2251
- externalUrl?: string | undefined;
2252
- }>;
2253
- declare const PatchGameMetadataSchema: z.ZodObject<{
2254
- displayName: z.ZodOptional<z.ZodString>;
2255
- visibility: z.ZodOptional<z.ZodEnum<["visible", "unlisted", "internal"]>>;
2256
- platform: z.ZodOptional<z.ZodEnum<["web", "godot", "unity"]>>;
2257
- metadata: z.ZodOptional<z.ZodObject<{
2258
- description: z.ZodOptional<z.ZodString>;
2259
- emoji: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
2260
- }, "strip", z.ZodTypeAny, {
2261
- description?: string | undefined;
2262
- emoji?: string | undefined;
2263
- }, {
2264
- description?: string | undefined;
2265
- emoji?: string | undefined;
2266
- }>>;
2267
- }, "strip", z.ZodTypeAny, {
2268
- displayName?: string | undefined;
2269
- visibility?: "internal" | "unlisted" | "visible" | undefined;
2270
- platform?: "godot" | "unity" | "web" | undefined;
2271
- metadata?: {
2272
- description?: string | undefined;
2273
- emoji?: string | undefined;
2274
- } | undefined;
2275
- }, {
2276
- displayName?: string | undefined;
2277
- visibility?: "internal" | "unlisted" | "visible" | undefined;
2278
- platform?: "godot" | "unity" | "web" | undefined;
2279
- metadata?: {
2280
- description?: string | undefined;
2281
- emoji?: string | undefined;
2282
- } | undefined;
2283
- }>;
2284
- declare const AddGameMemberSchema: z.ZodObject<{
2285
- userId: z.ZodString;
2286
- role: z.ZodDefault<z.ZodOptional<z.ZodLiteral<"collaborator">>>;
2287
- }, "strict", z.ZodTypeAny, {
2288
- userId: string;
2289
- role: "collaborator";
2290
- }, {
2291
- userId: string;
2292
- role?: "collaborator" | undefined;
2293
- }>;
2294
- declare const UpdateGameMemberRoleSchema: z.ZodObject<{
2295
- role: z.ZodEnum<["owner", "collaborator"]>;
2296
- }, "strict", z.ZodTypeAny, {
2297
- role: "collaborator" | "owner";
2298
- }, {
2299
- role: "collaborator" | "owner";
2300
- }>;
1537
+ dialect: 'pg';
1538
+ }>;
1539
+ declare const UpsertGameMetadataSchema: z.ZodEffects<z.ZodObject<{
1540
+ displayName: z.ZodString;
1541
+ platform: z.ZodEnum<["web", "godot", "unity"]>;
1542
+ metadata: z.ZodDefault<z.ZodOptional<z.ZodEffects<z.ZodRecord<z.ZodString, z.ZodUnknown>, Record<string, unknown>, Record<string, unknown>>>>;
1543
+ gameType: z.ZodDefault<z.ZodOptional<z.ZodEnum<["hosted", "external"]>>>;
1544
+ visibility: z.ZodOptional<z.ZodEnum<["visible", "unlisted", "internal"]>>;
1545
+ externalUrl: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
1546
+ }, "strip", z.ZodTypeAny, {
1547
+ displayName: string;
1548
+ platform: "godot" | "unity" | "web";
1549
+ metadata: Record<string, unknown>;
1550
+ gameType: "external" | "hosted";
1551
+ visibility?: "internal" | "unlisted" | "visible" | undefined;
1552
+ externalUrl?: string | undefined;
1553
+ }, {
1554
+ displayName: string;
1555
+ platform: "godot" | "unity" | "web";
1556
+ metadata?: Record<string, unknown> | undefined;
1557
+ gameType?: "external" | "hosted" | undefined;
1558
+ visibility?: "internal" | "unlisted" | "visible" | undefined;
1559
+ externalUrl?: string | undefined;
1560
+ }>, {
1561
+ displayName: string;
1562
+ platform: "godot" | "unity" | "web";
1563
+ metadata: Record<string, unknown>;
1564
+ gameType: "external" | "hosted";
1565
+ visibility?: "internal" | "unlisted" | "visible" | undefined;
1566
+ externalUrl?: string | undefined;
1567
+ }, {
1568
+ displayName: string;
1569
+ platform: "godot" | "unity" | "web";
1570
+ metadata?: Record<string, unknown> | undefined;
1571
+ gameType?: "external" | "hosted" | undefined;
1572
+ visibility?: "internal" | "unlisted" | "visible" | undefined;
1573
+ externalUrl?: string | undefined;
1574
+ }>;
1575
+ declare const PatchGameMetadataSchema: z.ZodObject<{
1576
+ displayName: z.ZodOptional<z.ZodString>;
1577
+ visibility: z.ZodOptional<z.ZodEnum<["visible", "unlisted", "internal"]>>;
1578
+ platform: z.ZodOptional<z.ZodEnum<["web", "godot", "unity"]>>;
1579
+ metadata: z.ZodOptional<z.ZodObject<{
1580
+ description: z.ZodOptional<z.ZodString>;
1581
+ emoji: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
1582
+ }, "strip", z.ZodTypeAny, {
1583
+ description?: string | undefined;
1584
+ emoji?: string | undefined;
1585
+ }, {
1586
+ description?: string | undefined;
1587
+ emoji?: string | undefined;
1588
+ }>>;
1589
+ }, "strip", z.ZodTypeAny, {
1590
+ displayName?: string | undefined;
1591
+ visibility?: "internal" | "unlisted" | "visible" | undefined;
1592
+ platform?: "godot" | "unity" | "web" | undefined;
1593
+ metadata?: {
1594
+ description?: string | undefined;
1595
+ emoji?: string | undefined;
1596
+ } | undefined;
1597
+ }, {
1598
+ displayName?: string | undefined;
1599
+ visibility?: "internal" | "unlisted" | "visible" | undefined;
1600
+ platform?: "godot" | "unity" | "web" | undefined;
1601
+ metadata?: {
1602
+ description?: string | undefined;
1603
+ emoji?: string | undefined;
1604
+ } | undefined;
1605
+ }>;
1606
+ declare const AddGameMemberSchema: z.ZodObject<{
1607
+ userId: z.ZodString;
1608
+ role: z.ZodDefault<z.ZodOptional<z.ZodLiteral<"collaborator">>>;
1609
+ }, "strict", z.ZodTypeAny, {
1610
+ userId: string;
1611
+ role: "collaborator";
1612
+ }, {
1613
+ userId: string;
1614
+ role?: "collaborator" | undefined;
1615
+ }>;
1616
+ declare const UpdateGameMemberRoleSchema: z.ZodObject<{
1617
+ role: z.ZodEnum<["owner", "collaborator"]>;
1618
+ }, "strict", z.ZodTypeAny, {
1619
+ role: "collaborator" | "owner";
1620
+ }, {
1621
+ role: "collaborator" | "owner";
1622
+ }>;
1623
+
1624
+ type GameRow = typeof games.$inferSelect;
1625
+ type BaseGame = Omit<GameRow, 'gameType' | 'deploymentUrl' | 'externalUrl'>;
1626
+ type HostedGame = BaseGame & {
1627
+ gameType: 'hosted';
1628
+ deploymentUrl: string;
1629
+ externalUrl: null;
1630
+ };
1631
+ type ExternalGame = BaseGame & {
1632
+ gameType: 'external';
1633
+ deploymentUrl: null;
1634
+ externalUrl: string;
1635
+ };
1636
+ type Game = HostedGame | ExternalGame;
1637
+ type GameMemberRow = typeof gameMembers.$inferSelect;
1638
+ type GameMemberWithUser = GameMemberRow & {
1639
+ user: {
1640
+ id: string;
1641
+ name: string;
1642
+ email: string;
1643
+ image: string | null;
1644
+ };
1645
+ };
1646
+ interface GameMemberSearchResult {
1647
+ id: string;
1648
+ name: string;
1649
+ email: string;
1650
+ image: string | null;
1651
+ }
1652
+ type GameCustomHostnameRow = typeof gameCustomHostnames.$inferSelect;
1653
+
1654
+ type UpsertGameMetadataInput = z.infer<typeof UpsertGameMetadataSchema>;
1655
+ type PatchGameMetadataInput = z.infer<typeof PatchGameMetadataSchema>;
1656
+ type AddGameMemberInput = z.infer<typeof AddGameMemberSchema>;
1657
+ type UpdateGameMemberRoleInput = z.infer<typeof UpdateGameMemberRoleSchema>;
1658
+
1659
+ type UserRow = typeof users.$inferSelect;
1660
+
1661
+ /**
1662
+ * Cross-Domain Composite Types
1663
+ */
1664
+ /**
1665
+ * Game with optional manifest metadata from build tools
1666
+ */
1667
+ type FetchedGame = (HostedGame | ExternalGame | GameRow) & {
1668
+ manifest?: GameManifest;
1669
+ };
1670
+ /**
1671
+ * Game custom hostname with validation records
1672
+ */
1673
+ type GameCustomHostname = GameCustomHostnameRow & {
1674
+ validationRecords?: DomainValidationRecords;
1675
+ };
1676
+
1677
+ /**
1678
+ * Base Playcademy SDK client with shared infrastructure.
1679
+ * Provides authentication, HTTP requests, events,
1680
+ * and fundamental namespaces used by all clients.
1681
+ *
1682
+ * Extended by PlaycademyClient (game SDK) and PlaycademyInternalClient (platform SDK).
1683
+ */
1684
+ declare abstract class PlaycademyBaseClient {
1685
+ baseUrl: string;
1686
+ gameUrl?: string;
1687
+ mode: PlaycademyMode;
1688
+ protected authStrategy: AuthStrategy;
1689
+ protected gameId?: string;
1690
+ protected config: Partial<ClientConfig>;
1691
+ protected listeners: EventListeners;
1692
+ protected authContext?: {
1693
+ isInIframe: boolean;
1694
+ };
1695
+ protected initPayload?: InitPayload;
1696
+ protected launchId?: string;
1697
+ protected gameOrigin?: string;
1698
+ private browserTimeZone?;
1699
+ constructor(config?: Partial<ClientConfig>);
1700
+ /**
1701
+ * Gets the effective base URL for API requests.
1702
+ */
1703
+ getBaseUrl(): string;
1704
+ /**
1705
+ * Gets the effective game backend URL for integration requests.
1706
+ */
1707
+ protected getGameBackendUrl(): string;
1708
+ /**
1709
+ * Simple ping method for testing connectivity.
1710
+ */
1711
+ ping(): string;
1712
+ /**
1713
+ * Local day context supplied by the platform during iframe initialization.
1714
+ */
1715
+ get localDay(): LocalDayContext | undefined;
1716
+ /**
1717
+ * Sets the authentication token for API requests.
1718
+ */
1719
+ setToken(token: string | null, tokenType?: TokenType): void;
1720
+ setLaunchId(launchId: string | null | undefined): void;
1721
+ /**
1722
+ * Gets the current token type.
1723
+ */
1724
+ getTokenType(): TokenType;
1725
+ /**
1726
+ * Gets the current authentication token.
1727
+ */
1728
+ getToken(): string | null;
1729
+ /**
1730
+ * Checks if the client has a valid API token.
1731
+ */
1732
+ isAuthenticated(): boolean;
1733
+ /**
1734
+ * Registers a callback to be called when authentication state changes.
1735
+ */
1736
+ onAuthChange(callback: (token: string | null) => void): void;
1737
+ /**
1738
+ * Sets the authentication context for the client.
1739
+ * @internal
1740
+ */
1741
+ _setAuthContext(context: {
1742
+ isInIframe: boolean;
1743
+ }): void;
1744
+ /**
1745
+ * Registers an event listener for client events.
1746
+ *
1747
+ * @param event - The event name to listen for.
1748
+ * @param callback - The handler invoked when the event fires.
1749
+ * @returns A cleanup function that removes this specific listener.
1750
+ */
1751
+ on<E extends keyof ClientEvents>(event: E, callback: (payload: ClientEvents[E]) => void): () => void;
1752
+ /**
1753
+ * Removes a previously registered event listener.
1754
+ *
1755
+ * @param event - The event name to stop listening for.
1756
+ * @param callback - The exact function reference passed to {@link on}.
1757
+ */
1758
+ off<E extends keyof ClientEvents>(event: E, callback: (payload: ClientEvents[E]) => void): void;
1759
+ /**
1760
+ * Emits an event to all registered listeners.
1761
+ */
1762
+ protected emit<E extends keyof ClientEvents>(event: E, payload: ClientEvents[E]): void;
1763
+ /**
1764
+ * Makes an authenticated HTTP request to the platform API.
1765
+ */
1766
+ protected request<T>(path: string, method: Method, options?: {
1767
+ body?: unknown;
1768
+ headers?: Record<string, string>;
1769
+ raw?: boolean;
1770
+ retryPolicy?: RetryPolicy;
1771
+ }): Promise<T>;
1772
+ private getBrowserTimeZone;
1773
+ /**
1774
+ * Makes an authenticated HTTP request to the game's backend Worker.
1775
+ */
1776
+ protected requestGameBackend<T>(path: string, method: Method, body?: unknown, headers?: Record<string, string>, options?: {
1777
+ raw?: boolean;
1778
+ retryPolicy?: RetryPolicy;
1779
+ }): Promise<T>;
1780
+ /**
1781
+ * Ensures a gameId is available, throwing an error if not.
1782
+ */
1783
+ protected _ensureGameId(): string;
1784
+ private _parseOrigin;
1785
+ /**
1786
+ * Detects and sets the authentication context (iframe vs standalone).
1787
+ */
1788
+ private _detectAuthContext;
1789
+ /**
1790
+ * Current user data.
1791
+ * - `me()` - Get authenticated user profile
1792
+ */
1793
+ users: {
1794
+ me: () => Promise<_playcademy_types.AuthenticatedUser>;
1795
+ };
1796
+ }
1797
+
1798
+ /**
1799
+ * Auto-initializes a PlaycademyClient with context from the environment.
1800
+ * Works in both iframe mode (production/development) and standalone mode (local dev).
1801
+ *
1802
+ * This is the recommended way to initialize the SDK as it automatically:
1803
+ * - Detects the runtime environment (iframe vs standalone)
1804
+ * - Configures the client with the appropriate context
1805
+ * - Sets up event listeners for token refresh
1806
+ * - Exposes the client for debugging in development mode
1807
+ *
1808
+ * @param options - Optional configuration overrides
1809
+ * @param options.baseUrl - Override the base URL for API requests
1810
+ * @returns Promise resolving to a fully initialized PlaycademyClient
1811
+ * @throws Error if not running in a browser context
1812
+ *
1813
+ * @example
1814
+ * ```typescript
1815
+ * // Default initialization
1816
+ * const client = await PlaycademyClient.init()
1817
+ *
1818
+ * // With custom base URL
1819
+ * const client = await PlaycademyClient.init({ baseUrl: 'https://custom.api.com' })
1820
+ * ```
1821
+ */
1822
+ declare function init<T extends PlaycademyBaseClient = PlaycademyBaseClient>(this: new (config?: Partial<ClientConfig>) => T, options?: {
1823
+ baseUrl?: string;
1824
+ allowedParentOrigins?: string[];
1825
+ }): Promise<T>;
1826
+
1827
+ /**
1828
+ * Authenticates a user with email and password.
1829
+ *
1830
+ * This is a standalone authentication method that doesn't require an initialized client.
1831
+ * Use this for login flows before creating a client instance.
1832
+ *
1833
+ * @deprecated Use client.auth.login() instead for better error handling and automatic token management
1834
+ *
1835
+ * @param baseUrl - The base URL of the Playcademy API
1836
+ * @param email - User's email address
1837
+ * @param password - User's password
1838
+ * @returns Promise resolving to authentication response with token
1839
+ * @throws PlaycademyError if authentication fails or network error occurs
1840
+ *
1841
+ * @example
1842
+ * ```typescript
1843
+ * // Preferred approach:
1844
+ * const client = new PlaycademyClient({ baseUrl: '/api' })
1845
+ * const result = await client.auth.login({
1846
+ * email: 'user@example.com',
1847
+ * password: 'password'
1848
+ * })
1849
+ *
1850
+ * // Legacy approach (still works):
1851
+ * try {
1852
+ * const response = await PlaycademyClient.login('/api', 'user@example.com', 'password')
1853
+ * const client = new PlaycademyClient({ token: response.token })
1854
+ * } catch (error) {
1855
+ * console.error('Login failed:', error.message)
1856
+ * }
1857
+ * ```
1858
+ */
1859
+ declare function login(baseUrl: string, email: string, password: string): Promise<LoginResponse>;
1860
+
1861
+ /**
1862
+ * Cache configuration types for runtime customization
1863
+ */
1864
+ /**
1865
+ * Runtime configuration for TTL cache behavior
1866
+ */
1867
+ interface TTLCacheConfig {
1868
+ /** Time-to-live in milliseconds. Set to 0 to disable caching for this call. */
1869
+ ttl?: number;
1870
+ /** Force refresh, bypassing cache */
1871
+ force?: boolean;
1872
+ /** Skip cache and fetch fresh data (alias for force) */
1873
+ skipCache?: boolean;
1874
+ }
1875
+
1876
+ /**
1877
+ * Options for configuring activity tracking behavior.
1878
+ */
1879
+ interface StartActivityOptions {
1880
+ /**
1881
+ * How long heartbeats continue after the activity is automatically paused
1882
+ * because the tab is hidden or the player is inactive while visible.
1883
+ * Defaults to 10 minutes. Set to `Infinity` to keep heartbeats running
1884
+ * indefinitely during automatic pauses. Invalid values fall back to the
1885
+ * 10-minute default.
1886
+ */
1887
+ pausedHeartbeatTimeoutMs?: number;
1888
+ /**
1889
+ * @deprecated Use `pausedHeartbeatTimeoutMs` instead.
1890
+ *
1891
+ * Backward-compatible alias for callers that still use the old option
1892
+ * name from earlier SDK releases.
1893
+ */
1894
+ hiddenTimeoutMs?: number;
1895
+ /**
1896
+ * How often to flush periodic heartbeats with accumulated time data.
1897
+ * Defaults to 15 seconds. Set to `Infinity` to disable the interval;
1898
+ * final unload/endActivity flushes still run. Values must be greater than
1899
+ * 0 or `Infinity`; invalid values fall back to the 15-second default.
1900
+ */
1901
+ heartbeatIntervalMs?: number;
1902
+ /**
1903
+ * How long the tab can remain visible without keyboard or mouse activity
1904
+ * before the activity is marked inactive. Defaults to 10 minutes. Set to
1905
+ * `Infinity` to disable keyboard/mouse inactivity tracking. Invalid values
1906
+ * fall back to the 10-minute default.
1907
+ */
1908
+ inactivityTimeoutMs?: number;
1909
+ /**
1910
+ * Stable identifier for this activity run. When provided, it is used on
1911
+ * every heartbeat and on endActivity instead of a freshly-generated UUID.
1912
+ *
1913
+ * Pass the same `runId` across multiple `startActivity()` calls (for
1914
+ * example, after the player closes and reopens a resumable activity) so
1915
+ * downstream systems can correlate related sessions into a single run.
1916
+ *
1917
+ * Must be a UUID (the backend validates it as such) and unique per
1918
+ * logical run. If omitted, the SDK generates a new UUID on each call,
1919
+ * which means every session is treated as its own run.
1920
+ */
1921
+ runId?: string;
1922
+ }
1923
+ interface StartActivityResult {
1924
+ runId: string;
1925
+ }
1926
+
1927
+ /**
1928
+ * Type definitions for the game timeback namespace.
1929
+ *
1930
+ * SDK-specific types like TimebackInitContext that wrap the core types
1931
+ * from @playcademy/types/user (which are re-exported via types/data.ts).
1932
+ */
1933
+
1934
+ /**
1935
+ * A TimeBack enrollment for the current game session.
1936
+ * Alias for UserEnrollment without the optional gameId.
1937
+ */
1938
+ type TimebackEnrollment = Omit<UserEnrollment, 'gameId'>;
1939
+ /**
1940
+ * A TimeBack organization (school/district) for the current user.
1941
+ * Alias for UserOrganization.
1942
+ */
1943
+ type TimebackOrganization = UserOrganization;
1944
+ /**
1945
+ * TimeBack context passed during game initialization.
1946
+ * This is sent from the platform (cademy) to the game iframe via postMessage.
1947
+ */
1948
+ interface TimebackInitContext {
1949
+ /** User's TimeBack ID */
1950
+ id: string;
1951
+ /** User's role in TimeBack (student, parent, teacher, etc.) */
1952
+ role: TimebackUserRole;
1953
+ /** User's enrollments for this game (one per grade/subject combo) */
1954
+ enrollments: TimebackEnrollment[];
1955
+ /** User's organizations (schools/districts) */
1956
+ organizations: TimebackOrganization[];
1957
+ }
1958
+ /**
1959
+ * TimeBack user context with current data (may be stale from init).
1960
+ * Use `fetch()` to get fresh data from the server.
1961
+ */
1962
+ interface TimebackUserContext {
1963
+ /** User's TimeBack ID */
1964
+ id: string | undefined;
1965
+ /** User's role in TimeBack (student, parent, teacher, etc.) */
1966
+ role: TimebackUserRole | undefined;
1967
+ /** User's enrollments for this game */
1968
+ enrollments: TimebackEnrollment[];
1969
+ /** User's organizations (schools/districts) */
1970
+ organizations: TimebackOrganization[];
1971
+ }
1972
+ /**
1973
+ * Slice options for refreshing the cached TimeBack user context.
1974
+ */
1975
+ type TimebackUserRefreshField = 'enrollments';
1976
+ interface TimebackUserRefreshOptions extends TTLCacheConfig {
1977
+ /** Refresh only these user data fields */
1978
+ only: readonly TimebackUserRefreshField[];
1979
+ }
1980
+ /**
1981
+ * XP data access for the current user.
1982
+ * Results are cached for 5 seconds to avoid redundant network requests.
1983
+ */
1984
+ interface TimebackUserXp {
1985
+ /**
1986
+ * Fetch XP data from the server.
1987
+ * Returns XP for all courses in this game, or filter by grade/subject.
1988
+ * Results are cached for 5 seconds (use `force: true` to bypass).
1989
+ *
1990
+ * @param options - Query options
1991
+ * @param options.grade - Grade level to filter (must be used with subject)
1992
+ * @param options.subject - Subject to filter (must be used with grade)
1993
+ * @param options.include - Additional data to include: 'perCourse', 'today'
1994
+ * @param options.force - Bypass cache and fetch fresh data (default: false)
1995
+ * @returns Promise resolving to XP data
1996
+ *
1997
+ * @example
1998
+ * ```typescript
1999
+ * // Get total XP for all game courses
2000
+ * const xp = await client.timeback.user.xp.fetch()
2001
+ *
2002
+ * // Get XP for a specific grade/subject
2003
+ * const xp = await client.timeback.user.xp.fetch({
2004
+ * grade: 3,
2005
+ * subject: 'Math'
2006
+ * })
2007
+ *
2008
+ * // Get XP with per-course breakdown
2009
+ * const xp = await client.timeback.user.xp.fetch({
2010
+ * include: ['perCourse', 'today']
2011
+ * })
2012
+ *
2013
+ * // Force fresh data
2014
+ * const xp = await client.timeback.user.xp.fetch({ force: true })
2015
+ * ```
2016
+ */
2017
+ fetch(options?: GetXpOptions): Promise<XpResponse>;
2018
+ }
2019
+ /**
2020
+ * TimeBack user object with cached getters, fetch, and targeted refresh methods.
2021
+ */
2022
+ interface TimebackUser extends TimebackUserContext {
2023
+ /**
2024
+ * Fetch TimeBack data from the server (cached for 5 min).
2025
+ * Updates the cached values so subsequent property access returns fresh data.
2026
+ * @param options - Cache options (pass { force: true } to bypass cache)
2027
+ * @returns Promise resolving to fresh user context
2028
+ */
2029
+ fetch(options?: TTLCacheConfig): Promise<TimebackUserContext>;
2030
+ /**
2031
+ * Refresh selected TimeBack user data from the server.
2032
+ * Updates the cached user snapshot used by the synchronous getters.
2033
+ * @param options - Refresh fields and cache options
2034
+ * @returns Promise resolving to the updated user context
2035
+ */
2036
+ refresh(options: TimebackUserRefreshOptions): Promise<TimebackUserContext>;
2037
+ /**
2038
+ * XP data for the current user.
2039
+ * Call `xp.fetch()` to get XP from the server.
2040
+ */
2041
+ xp: TimebackUserXp;
2042
+ /**
2043
+ * Mastery data for the current user.
2044
+ * Call `mastery.fetch()` to get mastery progress from the server.
2045
+ */
2046
+ mastery: TimebackUserMastery;
2047
+ /**
2048
+ * Highest-grade-mastered data for the current user.
2049
+ * Call `highestGradeMastered.fetch({ subject })` to get the student's
2050
+ * highest mastered grade for a subject.
2051
+ */
2052
+ highestGradeMastered: TimebackUserHighestGradeMastered;
2053
+ }
2054
+ /**
2055
+ * Mastery data access for the current user.
2056
+ * Results are cached for 5 seconds to avoid redundant network requests.
2057
+ */
2058
+ interface TimebackUserMastery {
2059
+ /**
2060
+ * Fetch mastery data from the server.
2061
+ * Returns mastery for all courses in this game, or filter by grade/subject.
2062
+ * Returned masteredUnits values are true analytics values and may exceed
2063
+ * the course masterableUnits maximum when historical data violates the
2064
+ * expected invariant.
2065
+ * Results are cached for 5 seconds (use `force: true` to bypass).
2066
+ *
2067
+ * @param options - Query options
2068
+ * @param options.grade - Grade level to filter (must be used with subject)
2069
+ * @param options.subject - Subject to filter (must be used with grade)
2070
+ * @param options.include - Additional data to include: 'perCourse'
2071
+ * @param options.force - Bypass cache and fetch fresh data (default: false)
2072
+ * @returns Promise resolving to mastery data
2073
+ *
2074
+ * @example
2075
+ * ```typescript
2076
+ * // Get total mastery for all game courses
2077
+ * const mastery = await client.timeback.user.mastery.fetch()
2078
+ *
2079
+ * // Get mastery for a specific grade/subject
2080
+ * const mastery = await client.timeback.user.mastery.fetch({
2081
+ * grade: 3,
2082
+ * subject: 'Math'
2083
+ * })
2084
+ *
2085
+ * // Get mastery with per-course breakdown
2086
+ * const mastery = await client.timeback.user.mastery.fetch({
2087
+ * include: ['perCourse']
2088
+ * })
2089
+ *
2090
+ * // Force fresh data
2091
+ * const mastery = await client.timeback.user.mastery.fetch({ force: true })
2092
+ * ```
2093
+ */
2094
+ fetch(options?: GetMasteryOptions): Promise<MasteryResponse>;
2095
+ }
2096
+ /**
2097
+ * Highest-grade-mastered data access for the current user.
2098
+ * Results are cached for 5 seconds to avoid redundant network requests.
2099
+ */
2100
+ interface TimebackUserHighestGradeMastered {
2101
+ /**
2102
+ * Fetch the highest grade the current student has mastered for a subject.
2103
+ * Results are cached for 5 seconds (use `force: true` to bypass).
2104
+ */
2105
+ fetch(options: GetHighestGradeMasteredOptions): Promise<HighestGradeMasteredResponse>;
2106
+ }
2107
+ /**
2108
+ * Options for querying student XP.
2109
+ */
2110
+ interface GetXpOptions {
2111
+ /** Grade level to filter (must be used with subject) */
2112
+ grade?: TimebackGrade;
2113
+ /** Subject to filter (must be used with grade) */
2114
+ subject?: TimebackSubject;
2115
+ /** Additional data to include: 'perCourse', 'today' */
2116
+ include?: ('perCourse' | 'today')[];
2117
+ /** Bypass cache and fetch fresh data (default: false) */
2118
+ force?: boolean;
2119
+ }
2120
+ /**
2121
+ * Options for querying student mastery.
2122
+ */
2123
+ interface GetMasteryOptions {
2124
+ /** Grade level to filter (must be used with subject) */
2125
+ grade?: TimebackGrade;
2126
+ /** Subject to filter (must be used with grade) */
2127
+ subject?: TimebackSubject;
2128
+ /** Additional data to include: 'perCourse' */
2129
+ include?: 'perCourse'[];
2130
+ /** Bypass cache and fetch fresh data (default: false) */
2131
+ force?: boolean;
2132
+ }
2133
+ /**
2134
+ * Options for querying highest grade mastered.
2135
+ */
2136
+ interface GetHighestGradeMasteredOptions {
2137
+ /** Subject to query */
2138
+ subject: TimebackSubject;
2139
+ /** Bypass cache and fetch fresh data (default: false) */
2140
+ force?: boolean;
2141
+ }
2142
+ /**
2143
+ * XP data for a single course.
2144
+ */
2145
+ interface CourseXp {
2146
+ grade: TimebackGrade;
2147
+ subject: TimebackSubject;
2148
+ title: string;
2149
+ totalXp: number;
2150
+ todayXp?: number;
2151
+ }
2152
+ /**
2153
+ * Response from XP query.
2154
+ */
2155
+ interface XpResponse {
2156
+ totalXp: number;
2157
+ todayXp?: number;
2158
+ courses?: CourseXp[];
2159
+ }
2160
+ interface CourseMastery {
2161
+ grade: TimebackGrade;
2162
+ subject: TimebackSubject;
2163
+ title: string;
2164
+ /** True mastered units from analytics. May exceed masterableUnits for historical data. */
2165
+ masteredUnits: number;
2166
+ /** Configured mastery ceiling for the course. */
2167
+ masterableUnits: number;
2168
+ /** Clamped course completion percentage from 0..100. */
2169
+ pctComplete: number;
2170
+ isComplete: boolean;
2171
+ }
2172
+ /**
2173
+ * Response from mastery query.
2174
+ */
2175
+ interface MasteryResponse {
2176
+ /** True mastered units across all queried courses. */
2177
+ totalMasteredUnits: number;
2178
+ /** Configured mastery ceiling across all queried courses. */
2179
+ totalMasterableUnits: number;
2180
+ courses?: CourseMastery[];
2181
+ }
2182
+ /**
2183
+ * Response from highest-grade-mastered query.
2184
+ */
2185
+ interface HighestGradeMasteredResponse {
2186
+ subject: TimebackSubject;
2187
+ highestGradeMastered: TimebackGrade | null;
2188
+ }
2301
2189
 
2302
- type GameRow = typeof games.$inferSelect;
2303
- type BaseGame = Omit<GameRow, 'gameType' | 'deploymentUrl' | 'externalUrl'>;
2304
- type HostedGame = BaseGame & {
2305
- gameType: 'hosted';
2306
- deploymentUrl: string;
2307
- externalUrl: null;
2308
- };
2309
- type ExternalGame = BaseGame & {
2310
- gameType: 'external';
2311
- deploymentUrl: null;
2312
- externalUrl: string;
2313
- };
2314
- type Game = HostedGame | ExternalGame;
2315
- type GameMemberRow = typeof gameMembers.$inferSelect;
2316
- type GameMemberWithUser = GameMemberRow & {
2317
- user: {
2318
- id: string;
2319
- name: string;
2320
- email: string;
2321
- image: string | null;
2322
- };
2323
- };
2324
- interface GameMemberSearchResult {
2190
+ /**
2191
+ * Core client configuration and lifecycle types
2192
+ */
2193
+
2194
+ type TokenType = 'session' | 'apiKey' | 'gameJwt';
2195
+ /**
2196
+ * Runtime mode for a Playcademy game client.
2197
+ *
2198
+ * - `'platform'` — game is embedded in the platform (e.g. the cademy hub)
2199
+ * with a real authenticated user and full access to SDK namespaces.
2200
+ * - `'demo'` — game is embedded in the landing page demo shell with an
2201
+ * anonymous user; platform-only namespaces throw, and `client.demo.*`
2202
+ * is the supported surface for signaling demo lifecycle events and
2203
+ * reading/updating the anonymous profile.
2204
+ * - `'standalone'` game is running outside any iframe (e.g. `bun run dev`
2205
+ * or direct-deploy preview) with a mock token and no real platform
2206
+ * context. API calls will not succeed; use this to branch UX locally.
2207
+ */
2208
+ type PlaycademyMode = 'platform' | 'demo' | 'standalone';
2209
+ interface ClientConfig {
2210
+ baseUrl: string;
2211
+ gameUrl?: string;
2212
+ token?: string;
2213
+ tokenType?: TokenType;
2214
+ gameId?: string;
2215
+ launchId?: string;
2216
+ mode?: PlaycademyMode;
2217
+ }
2218
+ interface InitPayload {
2219
+ /** Hub API base URL */
2220
+ baseUrl: string;
2221
+ /** Game deployment URL (serves both frontend assets and backend API) */
2222
+ gameUrl?: string;
2223
+ /** Short-lived game token */
2224
+ token: string;
2225
+ /** Game ID */
2226
+ gameId: string;
2227
+ /** Timeback context (if user has a Timeback account) */
2228
+ timeback?: TimebackInitContext;
2229
+ /** Playcademy context for resolving the student's local learning day */
2230
+ localDay?: LocalDayContext;
2231
+ /** Runtime mode for the game client */
2232
+ mode?: PlaycademyMode;
2233
+ /** Launch session correlation ID (UUID, set by platform on game launch) */
2234
+ launchId?: string;
2235
+ /** When `true`, the parent shell provides a heartbeat relay via postMessage, so the SDK can skip its own `fetch({ keepalive })` beacon on pagehide. Defaults to `false`. */
2236
+ hasHeartbeatRelay?: boolean;
2237
+ }
2238
+ /**
2239
+ * Simplified user data passed to games via InitPayload
2240
+ * This is a subset of AuthenticatedUser suitable for external game consumption
2241
+ *
2242
+ * Note: Named GameInitUser to distinguish from the cross-game GameUser DTO
2243
+ * exported from @playcademy/types
2244
+ */
2245
+ interface GameInitUser {
2246
+ /** Playcademy user ID */
2325
2247
  id: string;
2326
- name: string;
2327
- email: string;
2248
+ /** Unique username */
2249
+ username: string | null;
2250
+ /** Display name */
2251
+ name: string | null;
2252
+ /** Email address */
2253
+ email: string | null;
2254
+ /** Profile image URL */
2328
2255
  image: string | null;
2256
+ /** Whether the user has a Timeback account */
2257
+ hasTimebackAccount: boolean;
2258
+ }
2259
+ interface GameContextPayload {
2260
+ token: string;
2261
+ baseUrl: string;
2262
+ gameId: string;
2263
+ forwardKeys?: string[];
2264
+ mode?: PlaycademyMode;
2265
+ }
2266
+ type EventListeners = {
2267
+ [E in keyof ClientEvents]?: ((payload: ClientEvents[E]) => void)[];
2268
+ };
2269
+ interface ClientEvents {
2270
+ authChange: {
2271
+ token: string | null;
2272
+ };
2329
2273
  }
2330
- type GameCustomHostnameRow = typeof gameCustomHostnames.$inferSelect;
2331
-
2332
- type UpsertGameMetadataInput = z.infer<typeof UpsertGameMetadataSchema>;
2333
- type PatchGameMetadataInput = z.infer<typeof PatchGameMetadataSchema>;
2334
- type AddGameMemberInput = z.infer<typeof AddGameMemberSchema>;
2335
- type UpdateGameMemberRoleInput = z.infer<typeof UpdateGameMemberRoleSchema>;
2336
2274
 
2337
- type UserRow = typeof users.$inferSelect;
2275
+ /**
2276
+ * Event and message payload types for SDK messaging system
2277
+ */
2338
2278
 
2339
2279
  /**
2340
- * Cross-Domain Composite Types
2280
+ * Authentication state change event payload.
2281
+ * Used when authentication state changes in the application.
2341
2282
  */
2283
+ interface AuthStateChangePayload {
2284
+ /** Whether the user is currently authenticated */
2285
+ authenticated: boolean;
2286
+ /** User information if authenticated, null otherwise */
2287
+ user: UserInfo | null;
2288
+ /** Error information if authentication failed */
2289
+ error: Error | null;
2290
+ }
2342
2291
  /**
2343
- * Game with optional manifest metadata from build tools
2292
+ * OAuth callback event payload.
2293
+ * Used when OAuth flow completes in popup/new-tab windows.
2344
2294
  */
2345
- type FetchedGame = (HostedGame | ExternalGame | GameRow) & {
2346
- manifest?: GameManifest;
2347
- };
2295
+ interface AuthCallbackPayload {
2296
+ /** OAuth authorization code */
2297
+ code: string;
2298
+ /** OAuth state parameter for CSRF protection */
2299
+ state: string;
2300
+ /** Error message if OAuth flow failed */
2301
+ error: string | null;
2302
+ }
2348
2303
  /**
2349
- * Game custom hostname with validation records
2304
+ * Message sent from server callback to opener window.
2305
+ * This is the standardized format from our server-first architecture.
2350
2306
  */
2351
- type GameCustomHostname = GameCustomHostnameRow & {
2352
- validationRecords?: DomainValidationRecords;
2307
+ interface AuthServerMessage {
2308
+ /** Type of the message */
2309
+ type: 'PLAYCADEMY_AUTH_STATE_CHANGE';
2310
+ /** Whether the user is currently authenticated */
2311
+ authenticated: boolean;
2312
+ /** Whether the authentication was successful */
2313
+ success: boolean;
2314
+ /** Timestamp of the message */
2315
+ ts: number;
2316
+ /** User information if authentication was successful */
2317
+ user?: UserInfo;
2318
+ /** Error message if authentication failed */
2319
+ error?: string;
2320
+ }
2321
+ /**
2322
+ * Token refresh event payload.
2323
+ * Sent when authentication token is updated.
2324
+ */
2325
+ interface TokenRefreshPayload {
2326
+ /** New authentication token */
2327
+ token: string;
2328
+ /** Token expiration timestamp */
2329
+ exp: number;
2330
+ }
2331
+ /**
2332
+ * Telemetry event payload.
2333
+ * Performance metrics sent from the game.
2334
+ */
2335
+ interface TelemetryPayload {
2336
+ /** Frames per second */
2337
+ fps: number;
2338
+ /** Memory usage in MB */
2339
+ mem: number;
2340
+ }
2341
+ /**
2342
+ * Keyboard event payload.
2343
+ * Key events forwarded from the game.
2344
+ */
2345
+ interface KeyEventPayload {
2346
+ /** Key value (e.g., 'Escape', 'F1') */
2347
+ key: string;
2348
+ /** Key code (optional) */
2349
+ code?: string;
2350
+ /** Event type */
2351
+ type: 'keydown' | 'keyup';
2352
+ }
2353
+ /**
2354
+ * Init error payload.
2355
+ * Sent from game iframe to parent when SDK initialization fails
2356
+ * (e.g. origin validation failure, INIT timeout, client creation error).
2357
+ */
2358
+ interface InitErrorPayload {
2359
+ reason: string;
2360
+ }
2361
+ /**
2362
+ * Options for `client.demo.end(score, options?)`.
2363
+ *
2364
+ * All optional. The landing-page demo shell can render a meaningful CTA
2365
+ * from `score` alone, but `durationMs` enables a nicer summary and
2366
+ * `metadata` lets games pass any extra context they want to surface.
2367
+ */
2368
+ interface DemoEndOptions {
2369
+ durationMs?: number;
2370
+ metadata?: Record<string, unknown>;
2371
+ }
2372
+ /**
2373
+ * Wire payload for the `PLAYCADEMY_DEMO_END` message.
2374
+ *
2375
+ * `score` is required — the demo shell always needs something to display
2376
+ * when the round ends. `durationMs` and `metadata` are optional.
2377
+ */
2378
+ interface DemoEndPayload extends DemoEndOptions {
2379
+ score: number;
2380
+ }
2381
+ type TimebackHeartbeatRelayRequest = Omit<HeartbeatRequest, 'gameId' | 'studentId' | 'windowStartedAtMs' | 'windowSequence'> & {
2382
+ windowStartedAtMs: number;
2353
2383
  };
2354
2384
 
2385
+ /**
2386
+ * SDK-specific API response types
2387
+ */
2388
+ interface LoginResponse {
2389
+ token: string;
2390
+ }
2391
+ interface GameTokenResponse {
2392
+ token: string;
2393
+ exp: number;
2394
+ baseUrl?: string;
2395
+ }
2396
+
2355
2397
  /**
2356
2398
  * Scores namespace types
2357
2399
  */