@playcademy/sdk 0.9.0 → 0.9.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.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
+ import { UserRole, AUTH_PROVIDER_IDS } from '@playcademy/constants';
1
2
  import * as drizzle_orm_pg_core from 'drizzle-orm/pg-core';
2
- import { AUTH_PROVIDER_IDS } from '@playcademy/constants';
3
3
 
4
4
  /**
5
5
  * Base error class for Cademy SDK specific errors.
@@ -498,6 +498,21 @@ interface AdvanceCourseResponse {
498
498
  promotion: TimebackPromotionResult;
499
499
  }
500
500
 
501
+ /**
502
+ * Cache configuration types for runtime customization
503
+ */
504
+ /**
505
+ * Runtime configuration for TTL cache behavior
506
+ */
507
+ interface TTLCacheConfig {
508
+ /** Time-to-live in milliseconds. Set to 0 to disable caching for this call. */
509
+ ttl?: number;
510
+ /** Force refresh, bypassing cache */
511
+ force?: boolean;
512
+ /** Skip cache and fetch fresh data (alias for force) */
513
+ skipCache?: boolean;
514
+ }
515
+
501
516
  /**
502
517
  * User Types
503
518
  *
@@ -505,13 +520,18 @@ interface AdvanceCourseResponse {
505
520
  *
506
521
  * @module types/user
507
522
  */
508
- type UserRoleEnumType = 'admin' | 'player' | 'developer' | 'teacher';
523
+
524
+ type UserRoleEnumType = UserRole;
509
525
  type DeveloperStatusEnumType = 'none' | 'pending' | 'approved';
510
526
  type TimebackUserRole = 'administrator' | 'aide' | 'guardian' | 'parent' | 'proctor' | 'relative' | 'student' | 'teacher';
511
527
  type TimebackOrgType = 'department' | 'school' | 'district' | 'local' | 'state' | 'national';
512
528
  interface UserEnrollment {
513
529
  gameId?: string;
514
530
  courseId: string;
531
+ enrollmentIds?: {
532
+ active: string;
533
+ inactive?: string[];
534
+ };
515
535
  grade: number;
516
536
  subject: string;
517
537
  orgId?: string;
@@ -1819,6 +1839,7 @@ declare class PlaycademyClient extends PlaycademyBaseClient {
1819
1839
  * User context (cached from init, refreshable):
1820
1840
  * - `user.role` - User's role (student, parent, teacher, etc.)
1821
1841
  * - `user.enrollments` - Courses the player is enrolled in for this game
1842
+ * - `user.refresh({ only: ['enrollments'] })` - Refresh enrollments from server
1822
1843
  * - `user.organizations` - Schools/districts the player belongs to
1823
1844
  * - `user.fetch()` - Refresh user context from server
1824
1845
  *
@@ -1920,7 +1941,9 @@ declare class PlaycademyClient extends PlaycademyBaseClient {
1920
1941
 
1921
1942
  /**
1922
1943
  * A TimeBack enrollment for the current game session.
1923
- * Alias for UserEnrollment without the optional gameId.
1944
+ * Alias for UserEnrollment without the optional gameId. Active enrollment IDs
1945
+ * are available at `enrollment.enrollmentIds?.active` when supplied by the
1946
+ * platform.
1924
1947
  */
1925
1948
  type TimebackEnrollment = Omit<UserEnrollment, 'gameId'>;
1926
1949
  /**
@@ -1956,6 +1979,14 @@ interface TimebackUserContext {
1956
1979
  /** User's organizations (schools/districts) */
1957
1980
  organizations: TimebackOrganization[];
1958
1981
  }
1982
+ /**
1983
+ * Slice options for refreshing the cached TimeBack user context.
1984
+ */
1985
+ type TimebackUserRefreshField = 'enrollments';
1986
+ interface TimebackUserRefreshOptions extends TTLCacheConfig {
1987
+ /** Refresh only these user data fields */
1988
+ only: readonly TimebackUserRefreshField[];
1989
+ }
1959
1990
  /**
1960
1991
  * XP data access for the current user.
1961
1992
  * Results are cached for 5 seconds to avoid redundant network requests.
@@ -1996,7 +2027,7 @@ interface TimebackUserXp {
1996
2027
  fetch(options?: GetXpOptions): Promise<XpResponse>;
1997
2028
  }
1998
2029
  /**
1999
- * TimeBack user object with both cached getters and fetch method.
2030
+ * TimeBack user object with cached getters, fetch, and targeted refresh methods.
2000
2031
  */
2001
2032
  interface TimebackUser extends TimebackUserContext {
2002
2033
  /**
@@ -2005,9 +2036,14 @@ interface TimebackUser extends TimebackUserContext {
2005
2036
  * @param options - Cache options (pass { force: true } to bypass cache)
2006
2037
  * @returns Promise resolving to fresh user context
2007
2038
  */
2008
- fetch(options?: {
2009
- force?: boolean;
2010
- }): Promise<TimebackUserContext>;
2039
+ fetch(options?: TTLCacheConfig): Promise<TimebackUserContext>;
2040
+ /**
2041
+ * Refresh selected TimeBack user data from the server.
2042
+ * Updates the cached user snapshot used by the synchronous getters.
2043
+ * @param options - Refresh fields and cache options
2044
+ * @returns Promise resolving to the updated user context
2045
+ */
2046
+ refresh(options: TimebackUserRefreshOptions): Promise<TimebackUserContext>;
2011
2047
  /**
2012
2048
  * XP data for the current user.
2013
2049
  * Call `xp.fetch()` to get XP from the server.
package/dist/index.js CHANGED
@@ -2095,6 +2095,15 @@ function createTimebackUserStore(client) {
2095
2095
  enrollments: response.enrollments,
2096
2096
  organizations: response.organizations
2097
2097
  };
2098
+ },
2099
+ setEnrollments(enrollments) {
2100
+ const base = override ?? client["initPayload"]?.timeback;
2101
+ override = base ? { ...base, enrollments } : {
2102
+ id: undefined,
2103
+ role: undefined,
2104
+ enrollments,
2105
+ organizations: []
2106
+ };
2098
2107
  }
2099
2108
  };
2100
2109
  }
@@ -2110,20 +2119,50 @@ function createTimebackEngine(client) {
2110
2119
  ttl: 5000,
2111
2120
  keyPrefix: "game.timeback.xp"
2112
2121
  });
2122
+ const enrollmentsCache = createTTLCache({
2123
+ ttl: 5 * 60 * 1000,
2124
+ keyPrefix: "game.timeback.enrollments"
2125
+ });
2113
2126
  async function applyPromotion(promotion) {
2114
2127
  if (promotion.status !== "promoted" && promotion.status !== "already-promoted") {
2115
2128
  return;
2116
2129
  }
2117
2130
  userCache.clear("current");
2131
+ enrollmentsCache.clear("current");
2118
2132
  try {
2119
2133
  await userStore.refresh();
2120
2134
  } catch {}
2121
2135
  }
2122
2136
  const activityTracker = createTimebackActivityTracker(client);
2137
+ async function refreshUserContext() {
2138
+ const context = await userStore.refresh();
2139
+ enrollmentsCache.clear("current");
2140
+ return context;
2141
+ }
2142
+ async function refreshEnrollmentSlice(options) {
2143
+ const enrollments = await enrollmentsCache.get("current", async () => {
2144
+ const response = await client["request"]("/timeback/user/enrollments", "GET");
2145
+ userStore.setEnrollments(response);
2146
+ userCache.clear("current");
2147
+ return response;
2148
+ }, options);
2149
+ userStore.setEnrollments(enrollments);
2150
+ return userStore.snapshot();
2151
+ }
2123
2152
  return {
2124
2153
  user: {
2125
2154
  snapshot: () => userStore.snapshot(),
2126
- fetch: (options) => userCache.get("current", () => userStore.refresh(), options),
2155
+ fetch: (options) => userCache.get("current", refreshUserContext, options),
2156
+ refresh: (options) => {
2157
+ if (!options?.only.includes("enrollments")) {
2158
+ throw new Error("At least one TimeBack user refresh field must be selected");
2159
+ }
2160
+ return refreshEnrollmentSlice({
2161
+ force: options.force,
2162
+ skipCache: options.skipCache,
2163
+ ttl: options.ttl
2164
+ });
2165
+ },
2127
2166
  xp: {
2128
2167
  fetch: (options) => {
2129
2168
  const cacheKey = [
@@ -2184,6 +2223,7 @@ function createTimebackNamespace(client) {
2184
2223
  return engine.user.snapshot()?.organizations ?? [];
2185
2224
  },
2186
2225
  fetch: (options) => engine.user.fetch(options),
2226
+ refresh: (options) => engine.user.refresh(options),
2187
2227
  xp: {
2188
2228
  fetch: async (options = {}) => {
2189
2229
  const hasGrade = options.grade !== undefined;
@@ -1,9 +1,9 @@
1
1
  import { SchemaInfo } from '@playcademy/cloudflare';
2
+ import { UserRole, AUTH_PROVIDER_IDS } from '@playcademy/constants';
2
3
  import { InferSelectModel } from 'drizzle-orm';
3
4
  import * as drizzle_orm_pg_core from 'drizzle-orm/pg-core';
4
5
  import * as drizzle_zod from 'drizzle-zod';
5
6
  import { z } from 'zod';
6
- import { AUTH_PROVIDER_IDS } from '@playcademy/constants';
7
7
 
8
8
  /** Permitted HTTP verbs */
9
9
  type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
@@ -918,7 +918,8 @@ interface NotificationStats {
918
918
  *
919
919
  * @module types/user
920
920
  */
921
- type UserRoleEnumType = 'admin' | 'player' | 'developer' | 'teacher';
921
+
922
+ type UserRoleEnumType = UserRole;
922
923
  type DeveloperStatusEnumType = 'none' | 'pending' | 'approved';
923
924
  type DeveloperStatusValue = DeveloperStatusEnumType;
924
925
  type TimebackUserRole = 'administrator' | 'aide' | 'guardian' | 'parent' | 'proctor' | 'relative' | 'student' | 'teacher';
@@ -926,6 +927,10 @@ type TimebackOrgType = 'department' | 'school' | 'district' | 'local' | 'state'
926
927
  interface UserEnrollment {
927
928
  gameId?: string;
928
929
  courseId: string;
930
+ enrollmentIds?: {
931
+ active: string;
932
+ inactive?: string[];
933
+ };
929
934
  grade: number;
930
935
  subject: string;
931
936
  orgId?: string;
@@ -998,6 +1003,7 @@ interface GameUser {
998
1003
  *
999
1004
  * @module types/game
1000
1005
  */
1006
+
1001
1007
  type GameType = 'hosted' | 'external';
1002
1008
  type GamePlatform = 'web' | 'godot' | 'unity' | (string & {});
1003
1009
  /**
@@ -1846,6 +1852,98 @@ declare const games: drizzle_orm_pg_core.PgTableWithColumns<{
1846
1852
  };
1847
1853
  dialect: 'pg';
1848
1854
  }>;
1855
+ declare const gameMembers: drizzle_orm_pg_core.PgTableWithColumns<{
1856
+ name: "game_members";
1857
+ schema: undefined;
1858
+ columns: {
1859
+ id: drizzle_orm_pg_core.PgColumn<{
1860
+ name: "id";
1861
+ tableName: "game_members";
1862
+ dataType: "string";
1863
+ columnType: "PgUUID";
1864
+ data: string;
1865
+ driverParam: string;
1866
+ notNull: true;
1867
+ hasDefault: true;
1868
+ isPrimaryKey: true;
1869
+ isAutoincrement: false;
1870
+ hasRuntimeDefault: false;
1871
+ enumValues: undefined;
1872
+ baseColumn: never;
1873
+ identity: undefined;
1874
+ generated: undefined;
1875
+ }, {}, {}>;
1876
+ gameId: drizzle_orm_pg_core.PgColumn<{
1877
+ name: "game_id";
1878
+ tableName: "game_members";
1879
+ dataType: "string";
1880
+ columnType: "PgUUID";
1881
+ data: string;
1882
+ driverParam: string;
1883
+ notNull: true;
1884
+ hasDefault: false;
1885
+ isPrimaryKey: false;
1886
+ isAutoincrement: false;
1887
+ hasRuntimeDefault: false;
1888
+ enumValues: undefined;
1889
+ baseColumn: never;
1890
+ identity: undefined;
1891
+ generated: undefined;
1892
+ }, {}, {}>;
1893
+ userId: drizzle_orm_pg_core.PgColumn<{
1894
+ name: "user_id";
1895
+ tableName: "game_members";
1896
+ dataType: "string";
1897
+ columnType: "PgText";
1898
+ data: string;
1899
+ driverParam: string;
1900
+ notNull: true;
1901
+ hasDefault: false;
1902
+ isPrimaryKey: false;
1903
+ isAutoincrement: false;
1904
+ hasRuntimeDefault: false;
1905
+ enumValues: [string, ...string[]];
1906
+ baseColumn: never;
1907
+ identity: undefined;
1908
+ generated: undefined;
1909
+ }, {}, {}>;
1910
+ role: drizzle_orm_pg_core.PgColumn<{
1911
+ name: "role";
1912
+ tableName: "game_members";
1913
+ dataType: "string";
1914
+ columnType: "PgEnumColumn";
1915
+ data: "collaborator" | "owner";
1916
+ driverParam: string;
1917
+ notNull: true;
1918
+ hasDefault: true;
1919
+ isPrimaryKey: false;
1920
+ isAutoincrement: false;
1921
+ hasRuntimeDefault: false;
1922
+ enumValues: ["owner", "collaborator"];
1923
+ baseColumn: never;
1924
+ identity: undefined;
1925
+ generated: undefined;
1926
+ }, {}, {}>;
1927
+ createdAt: drizzle_orm_pg_core.PgColumn<{
1928
+ name: "created_at";
1929
+ tableName: "game_members";
1930
+ dataType: "date";
1931
+ columnType: "PgTimestamp";
1932
+ data: Date;
1933
+ driverParam: string;
1934
+ notNull: true;
1935
+ hasDefault: true;
1936
+ isPrimaryKey: false;
1937
+ isAutoincrement: false;
1938
+ hasRuntimeDefault: false;
1939
+ enumValues: undefined;
1940
+ baseColumn: never;
1941
+ identity: undefined;
1942
+ generated: undefined;
1943
+ }, {}, {}>;
1944
+ };
1945
+ dialect: 'pg';
1946
+ }>;
1849
1947
  declare const gameSessions: drizzle_orm_pg_core.PgTableWithColumns<{
1850
1948
  name: "game_sessions";
1851
1949
  schema: undefined;
@@ -4177,7 +4275,7 @@ declare const UpsertGameMetadataSchema: z.ZodEffects<z.ZodObject<{
4177
4275
  displayName: z.ZodString;
4178
4276
  mapElementId: z.ZodNullable<z.ZodOptional<z.ZodString>>;
4179
4277
  platform: z.ZodEnum<["web", "godot", "unity"]>;
4180
- metadata: z.ZodDefault<z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
4278
+ metadata: z.ZodDefault<z.ZodOptional<z.ZodEffects<z.ZodRecord<z.ZodString, z.ZodUnknown>, Record<string, unknown>, Record<string, unknown>>>>;
4181
4279
  gameType: z.ZodDefault<z.ZodOptional<z.ZodEnum<["hosted", "external"]>>>;
4182
4280
  visibility: z.ZodOptional<z.ZodEnum<["visible", "unlisted", "internal"]>>;
4183
4281
  externalUrl: z.ZodOptional<z.ZodString>;
@@ -4214,6 +4312,54 @@ declare const UpsertGameMetadataSchema: z.ZodEffects<z.ZodObject<{
4214
4312
  visibility?: "internal" | "unlisted" | "visible" | undefined;
4215
4313
  externalUrl?: string | undefined;
4216
4314
  }>;
4315
+ declare const PatchGameMetadataSchema: z.ZodObject<{
4316
+ displayName: z.ZodOptional<z.ZodString>;
4317
+ visibility: z.ZodOptional<z.ZodEnum<["visible", "unlisted", "internal"]>>;
4318
+ platform: z.ZodOptional<z.ZodEnum<["web", "godot", "unity"]>>;
4319
+ metadata: z.ZodOptional<z.ZodObject<{
4320
+ description: z.ZodOptional<z.ZodString>;
4321
+ emoji: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
4322
+ }, "strip", z.ZodTypeAny, {
4323
+ description?: string | undefined;
4324
+ emoji?: string | undefined;
4325
+ }, {
4326
+ description?: string | undefined;
4327
+ emoji?: string | undefined;
4328
+ }>>;
4329
+ }, "strip", z.ZodTypeAny, {
4330
+ displayName?: string | undefined;
4331
+ visibility?: "internal" | "unlisted" | "visible" | undefined;
4332
+ platform?: "godot" | "unity" | "web" | undefined;
4333
+ metadata?: {
4334
+ description?: string | undefined;
4335
+ emoji?: string | undefined;
4336
+ } | undefined;
4337
+ }, {
4338
+ displayName?: string | undefined;
4339
+ visibility?: "internal" | "unlisted" | "visible" | undefined;
4340
+ platform?: "godot" | "unity" | "web" | undefined;
4341
+ metadata?: {
4342
+ description?: string | undefined;
4343
+ emoji?: string | undefined;
4344
+ } | undefined;
4345
+ }>;
4346
+ declare const AddGameMemberSchema: z.ZodObject<{
4347
+ userId: z.ZodString;
4348
+ role: z.ZodDefault<z.ZodOptional<z.ZodLiteral<"collaborator">>>;
4349
+ }, "strict", z.ZodTypeAny, {
4350
+ userId: string;
4351
+ role: "collaborator";
4352
+ }, {
4353
+ userId: string;
4354
+ role?: "collaborator" | undefined;
4355
+ }>;
4356
+ declare const UpdateGameMemberRoleSchema: z.ZodObject<{
4357
+ role: z.ZodEnum<["owner", "collaborator"]>;
4358
+ }, "strict", z.ZodTypeAny, {
4359
+ role: "collaborator" | "owner";
4360
+ }, {
4361
+ role: "collaborator" | "owner";
4362
+ }>;
4217
4363
 
4218
4364
  declare const InsertItemSchema: drizzle_zod.BuildSchema<"insert", {
4219
4365
  id: drizzle_orm_pg_core.PgColumn<{
@@ -4792,9 +4938,27 @@ type ExternalGame = BaseGame & {
4792
4938
  };
4793
4939
  type Game = HostedGame | ExternalGame;
4794
4940
  type GameSessionRow = typeof gameSessions.$inferSelect;
4941
+ type GameMemberRow = typeof gameMembers.$inferSelect;
4942
+ type GameMemberWithUser = GameMemberRow & {
4943
+ user: {
4944
+ id: string;
4945
+ name: string;
4946
+ email: string;
4947
+ image: string | null;
4948
+ };
4949
+ };
4950
+ interface GameMemberSearchResult {
4951
+ id: string;
4952
+ name: string;
4953
+ email: string;
4954
+ image: string | null;
4955
+ }
4795
4956
  type GameCustomHostnameRow = typeof gameCustomHostnames.$inferSelect;
4796
4957
 
4797
4958
  type UpsertGameMetadataInput = z.infer<typeof UpsertGameMetadataSchema>;
4959
+ type PatchGameMetadataInput = z.infer<typeof PatchGameMetadataSchema>;
4960
+ type AddGameMemberInput = z.infer<typeof AddGameMemberSchema>;
4961
+ type UpdateGameMemberRoleInput = z.infer<typeof UpdateGameMemberRoleSchema>;
4798
4962
 
4799
4963
  type ItemRow = typeof items.$inferSelect;
4800
4964
  type InventoryItemRow = typeof inventoryItems.$inferSelect;
@@ -6271,6 +6435,30 @@ declare function init<T extends PlaycademyBaseClient = PlaycademyBaseClient>(thi
6271
6435
  */
6272
6436
  declare function login(baseUrl: string, email: string, password: string): Promise<LoginResponse>;
6273
6437
 
6438
+ /**
6439
+ * Cache configuration types for runtime customization
6440
+ */
6441
+ /**
6442
+ * Runtime configuration for TTL cache behavior
6443
+ */
6444
+ interface TTLCacheConfig {
6445
+ /** Time-to-live in milliseconds. Set to 0 to disable caching for this call. */
6446
+ ttl?: number;
6447
+ /** Force refresh, bypassing cache */
6448
+ force?: boolean;
6449
+ /** Skip cache and fetch fresh data (alias for force) */
6450
+ skipCache?: boolean;
6451
+ }
6452
+ /**
6453
+ * Runtime configuration for cooldown cache behavior
6454
+ */
6455
+ interface CooldownCacheConfig {
6456
+ /** Cooldown period in milliseconds. Set to 0 to disable cooldown for this call. */
6457
+ cooldown?: number;
6458
+ /** Force refresh, bypassing cooldown */
6459
+ force?: boolean;
6460
+ }
6461
+
6274
6462
  /**
6275
6463
  * Type definitions for the game timeback namespace.
6276
6464
  *
@@ -6280,7 +6468,9 @@ declare function login(baseUrl: string, email: string, password: string): Promis
6280
6468
 
6281
6469
  /**
6282
6470
  * A TimeBack enrollment for the current game session.
6283
- * Alias for UserEnrollment without the optional gameId.
6471
+ * Alias for UserEnrollment without the optional gameId. Active enrollment IDs
6472
+ * are available at `enrollment.enrollmentIds?.active` when supplied by the
6473
+ * platform.
6284
6474
  */
6285
6475
  type TimebackEnrollment = Omit<UserEnrollment, 'gameId'>;
6286
6476
  /**
@@ -6316,6 +6506,14 @@ interface TimebackUserContext {
6316
6506
  /** User's organizations (schools/districts) */
6317
6507
  organizations: TimebackOrganization[];
6318
6508
  }
6509
+ /**
6510
+ * Slice options for refreshing the cached TimeBack user context.
6511
+ */
6512
+ type TimebackUserRefreshField = 'enrollments';
6513
+ interface TimebackUserRefreshOptions extends TTLCacheConfig {
6514
+ /** Refresh only these user data fields */
6515
+ only: readonly TimebackUserRefreshField[];
6516
+ }
6319
6517
  /**
6320
6518
  * XP data access for the current user.
6321
6519
  * Results are cached for 5 seconds to avoid redundant network requests.
@@ -6356,7 +6554,7 @@ interface TimebackUserXp {
6356
6554
  fetch(options?: GetXpOptions): Promise<XpResponse>;
6357
6555
  }
6358
6556
  /**
6359
- * TimeBack user object with both cached getters and fetch method.
6557
+ * TimeBack user object with cached getters, fetch, and targeted refresh methods.
6360
6558
  */
6361
6559
  interface TimebackUser extends TimebackUserContext {
6362
6560
  /**
@@ -6365,9 +6563,14 @@ interface TimebackUser extends TimebackUserContext {
6365
6563
  * @param options - Cache options (pass { force: true } to bypass cache)
6366
6564
  * @returns Promise resolving to fresh user context
6367
6565
  */
6368
- fetch(options?: {
6369
- force?: boolean;
6370
- }): Promise<TimebackUserContext>;
6566
+ fetch(options?: TTLCacheConfig): Promise<TimebackUserContext>;
6567
+ /**
6568
+ * Refresh selected TimeBack user data from the server.
6569
+ * Updates the cached user snapshot used by the synchronous getters.
6570
+ * @param options - Refresh fields and cache options
6571
+ * @returns Promise resolving to the updated user context
6572
+ */
6573
+ refresh(options: TimebackUserRefreshOptions): Promise<TimebackUserContext>;
6371
6574
  /**
6372
6575
  * XP data for the current user.
6373
6576
  * Call `xp.fetch()` to get XP from the server.
@@ -7179,30 +7382,6 @@ declare function parseOAuthState(state: string): {
7179
7382
  data?: Record<string, string>;
7180
7383
  };
7181
7384
 
7182
- /**
7183
- * Cache configuration types for runtime customization
7184
- */
7185
- /**
7186
- * Runtime configuration for TTL cache behavior
7187
- */
7188
- interface TTLCacheConfig {
7189
- /** Time-to-live in milliseconds. Set to 0 to disable caching for this call. */
7190
- ttl?: number;
7191
- /** Force refresh, bypassing cache */
7192
- force?: boolean;
7193
- /** Skip cache and fetch fresh data (alias for force) */
7194
- skipCache?: boolean;
7195
- }
7196
- /**
7197
- * Runtime configuration for cooldown cache behavior
7198
- */
7199
- interface CooldownCacheConfig {
7200
- /** Cooldown period in milliseconds. Set to 0 to disable cooldown for this call. */
7201
- cooldown?: number;
7202
- /** Force refresh, bypassing cooldown */
7203
- force?: boolean;
7204
- }
7205
-
7206
7385
  /**
7207
7386
  * Internal Playcademy SDK client with all namespaces.
7208
7387
  * For use by Cademy platform, CLI, and admin tools.
@@ -7572,6 +7751,14 @@ declare class PlaycademyInternalClient extends PlaycademyBaseClient {
7572
7751
  games: {
7573
7752
  fetch: (gameIdOrSlug: string, options?: TTLCacheConfig) => Promise<FetchedGame>;
7574
7753
  list: (options?: TTLCacheConfig) => Promise<Game[]>;
7754
+ patchMetadata: (gameId: string, metadata: PatchGameMetadataInput) => Promise<Game>;
7755
+ members: {
7756
+ list: (gameId: string) => Promise<GameMemberWithUser[]>;
7757
+ add: (gameId: string, body: AddGameMemberInput) => Promise<GameMemberWithUser>;
7758
+ update: (gameId: string, userId: string, body: UpdateGameMemberRoleInput) => Promise<GameMemberWithUser>;
7759
+ remove: (gameId: string, userId: string) => Promise<void>;
7760
+ search: (gameId: string, query: string) => Promise<GameMemberSearchResult[]>;
7761
+ };
7575
7762
  getSubjects: () => Promise<Record<string, string | null>>;
7576
7763
  startSession: (gameId?: string) => Promise<StartSessionResponse>;
7577
7764
  endSession: (sessionId: string, gameId?: string) => Promise<void>;
@@ -7855,4 +8042,4 @@ declare class PlaycademyInternalClient extends PlaycademyBaseClient {
7855
8042
  }
7856
8043
 
7857
8044
  export { AchievementCompletionType, ApiError, ConnectionManager, ConnectionMonitor, MessageEvents, NotificationStatus, NotificationType, PlaycademyInternalClient as PlaycademyClient, PlaycademyError, PlaycademyInternalClient, extractApiErrorInfo, messaging };
7858
- export type { AchievementCurrent, AchievementHistoryEntry, AchievementProgressResponse, AchievementScopeType, AchievementWithStatus, ApiErrorCode, ApiErrorInfo, AssessmentBankStatus, AssessmentRow, AssessmentSummary, AuthCallbackPayload, AuthOptions, AuthProviderType, AuthResult, AuthServerMessage, AuthStateChangePayload, AuthStateUpdate, AuthenticatedUser, BetterAuthApiKey, BetterAuthApiKeyResponse, BetterAuthSignInResponse, BucketFile, CharacterComponentRow as CharacterComponent, CharacterComponentType, CharacterComponentWithSpriteUrl, CharacterComponentsOptions, ClientConfig, ClientEvents, ConnectionMonitorConfig, ConnectionState, ConnectionStatePayload, CourseXp, CreateCharacterData, CreateMapObjectData, CurrencyRow as Currency, DemoEndOptions, DemoEndPayload, DevUploadEvent, DevUploadHooks, DeveloperStatusEnumType, DeveloperStatusResponse, DeveloperStatusValue, DisconnectContext, DisconnectHandler, DisplayAlertPayload, ErrorResponseBody, 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, MessageEventMap, 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 };
8045
+ export type { AchievementCurrent, AchievementHistoryEntry, AchievementProgressResponse, AchievementScopeType, AchievementWithStatus, ApiErrorCode, ApiErrorInfo, AssessmentBankStatus, AssessmentRow, AssessmentSummary, AuthCallbackPayload, AuthOptions, AuthProviderType, AuthResult, AuthServerMessage, AuthStateChangePayload, AuthStateUpdate, AuthenticatedUser, BetterAuthApiKey, BetterAuthApiKeyResponse, BetterAuthSignInResponse, BucketFile, CharacterComponentRow as CharacterComponent, CharacterComponentType, CharacterComponentWithSpriteUrl, CharacterComponentsOptions, ClientConfig, ClientEvents, ConnectionMonitorConfig, ConnectionState, ConnectionStatePayload, CourseXp, CreateCharacterData, CreateMapObjectData, CurrencyRow as Currency, DemoEndOptions, DemoEndPayload, DevUploadEvent, DevUploadHooks, DeveloperStatusEnumType, DeveloperStatusResponse, DeveloperStatusValue, DisconnectContext, DisconnectHandler, DisplayAlertPayload, ErrorResponseBody, 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, MessageEventMap, 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, 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 };
package/dist/internal.js CHANGED
@@ -2095,6 +2095,15 @@ function createTimebackUserStore(client) {
2095
2095
  enrollments: response.enrollments,
2096
2096
  organizations: response.organizations
2097
2097
  };
2098
+ },
2099
+ setEnrollments(enrollments) {
2100
+ const base = override ?? client["initPayload"]?.timeback;
2101
+ override = base ? { ...base, enrollments } : {
2102
+ id: undefined,
2103
+ role: undefined,
2104
+ enrollments,
2105
+ organizations: []
2106
+ };
2098
2107
  }
2099
2108
  };
2100
2109
  }
@@ -2110,20 +2119,50 @@ function createTimebackEngine(client) {
2110
2119
  ttl: 5000,
2111
2120
  keyPrefix: "game.timeback.xp"
2112
2121
  });
2122
+ const enrollmentsCache = createTTLCache({
2123
+ ttl: 5 * 60 * 1000,
2124
+ keyPrefix: "game.timeback.enrollments"
2125
+ });
2113
2126
  async function applyPromotion(promotion) {
2114
2127
  if (promotion.status !== "promoted" && promotion.status !== "already-promoted") {
2115
2128
  return;
2116
2129
  }
2117
2130
  userCache.clear("current");
2131
+ enrollmentsCache.clear("current");
2118
2132
  try {
2119
2133
  await userStore.refresh();
2120
2134
  } catch {}
2121
2135
  }
2122
2136
  const activityTracker = createTimebackActivityTracker(client);
2137
+ async function refreshUserContext() {
2138
+ const context = await userStore.refresh();
2139
+ enrollmentsCache.clear("current");
2140
+ return context;
2141
+ }
2142
+ async function refreshEnrollmentSlice(options) {
2143
+ const enrollments = await enrollmentsCache.get("current", async () => {
2144
+ const response = await client["request"]("/timeback/user/enrollments", "GET");
2145
+ userStore.setEnrollments(response);
2146
+ userCache.clear("current");
2147
+ return response;
2148
+ }, options);
2149
+ userStore.setEnrollments(enrollments);
2150
+ return userStore.snapshot();
2151
+ }
2123
2152
  return {
2124
2153
  user: {
2125
2154
  snapshot: () => userStore.snapshot(),
2126
- fetch: (options) => userCache.get("current", () => userStore.refresh(), options),
2155
+ fetch: (options) => userCache.get("current", refreshUserContext, options),
2156
+ refresh: (options) => {
2157
+ if (!options?.only.includes("enrollments")) {
2158
+ throw new Error("At least one TimeBack user refresh field must be selected");
2159
+ }
2160
+ return refreshEnrollmentSlice({
2161
+ force: options.force,
2162
+ skipCache: options.skipCache,
2163
+ ttl: options.ttl
2164
+ });
2165
+ },
2127
2166
  xp: {
2128
2167
  fetch: (options) => {
2129
2168
  const cacheKey = [
@@ -2184,6 +2223,7 @@ function createTimebackNamespace(client) {
2184
2223
  return engine.user.snapshot()?.organizations ?? [];
2185
2224
  },
2186
2225
  fetch: (options) => engine.user.fetch(options),
2226
+ refresh: (options) => engine.user.refresh(options),
2187
2227
  xp: {
2188
2228
  fetch: async (options = {}) => {
2189
2229
  const hasGrade = options.grade !== undefined;
@@ -2348,7 +2388,7 @@ class DeployPipeline {
2348
2388
  return this.fetchGameWithRetry(slug);
2349
2389
  }
2350
2390
  async buildRequestBody(args) {
2351
- const game = await this.resolveGame(args.slug, args.game);
2391
+ const game2 = await this.resolveGame(args.slug, args.game);
2352
2392
  const requestBody = {};
2353
2393
  if (args.uploadToken) {
2354
2394
  requestBody.uploadToken = args.uploadToken;
@@ -2357,7 +2397,7 @@ class DeployPipeline {
2357
2397
  requestBody.metadata = args.metadata;
2358
2398
  }
2359
2399
  if (!args.backend) {
2360
- return { game, requestBody };
2400
+ return { game: game2, requestBody };
2361
2401
  }
2362
2402
  const backendFields = {
2363
2403
  config: args.backend.config,
@@ -2372,7 +2412,7 @@ class DeployPipeline {
2372
2412
  code: args.backend.code
2373
2413
  };
2374
2414
  if (this.serializedSize(inlineBody) <= DeployPipeline.MAX_INLINE_REQUEST_BYTES) {
2375
- return { game, requestBody: inlineBody };
2415
+ return { game: game2, requestBody: inlineBody };
2376
2416
  }
2377
2417
  const skeletonBody = {
2378
2418
  ...requestBody,
@@ -2382,8 +2422,8 @@ class DeployPipeline {
2382
2422
  if (this.serializedSize(skeletonBody) > DeployPipeline.MAX_INLINE_REQUEST_BYTES) {
2383
2423
  throw new Error("Deploy request is too large even after uploading backend code");
2384
2424
  }
2385
- skeletonBody.codeUploadToken = await this.uploadCode(game.id, args.backend.code);
2386
- return { game, requestBody: skeletonBody };
2425
+ skeletonBody.codeUploadToken = await this.uploadCode(game2.id, args.backend.code);
2426
+ return { game: game2, requestBody: skeletonBody };
2387
2427
  }
2388
2428
  serializedSize(body) {
2389
2429
  return DeployPipeline.textEncoder.encode(JSON.stringify(body)).length;
@@ -2471,8 +2511,8 @@ class DeployPipeline {
2471
2511
  await new Promise((resolve) => setTimeout(resolve, DeployPipeline.POLL_INTERVAL_MS));
2472
2512
  }
2473
2513
  }
2474
- async resolveGame(slug, game) {
2475
- return game ?? await this.client["request"](`/games/${slug}`, "GET");
2514
+ async resolveGame(slug, game2) {
2515
+ return game2 ?? await this.client["request"](`/games/${slug}`, "GET");
2476
2516
  }
2477
2517
  async fetchGameWithRetry(slug) {
2478
2518
  const { GAME_FETCH_RETRIES } = DeployPipeline;
@@ -2505,21 +2545,21 @@ function createDevNamespace(client) {
2505
2545
  deploy: async (slug, options) => {
2506
2546
  const { metadata, file, backend, hooks } = options;
2507
2547
  hooks?.onEvent?.({ type: "init" });
2508
- let game;
2548
+ let game2;
2509
2549
  if (metadata) {
2510
- game = await client["request"](`/games/${slug}`, "PUT", {
2550
+ game2 = await client["request"](`/games/${slug}`, "PUT", {
2511
2551
  body: metadata
2512
2552
  });
2513
2553
  if (metadata.gameType === "external" && !file && !backend) {
2514
- return game;
2554
+ return game2;
2515
2555
  }
2516
2556
  }
2517
- const uploadToken = file ? await deploy.uploadFile(file, game?.id || slug, hooks) : undefined;
2557
+ const uploadToken = file ? await deploy.uploadFile(file, game2?.id || slug, hooks) : undefined;
2518
2558
  if (uploadToken || backend) {
2519
- return deploy.submit({ slug, game, uploadToken, metadata, backend, hooks });
2559
+ return deploy.submit({ slug, game: game2, uploadToken, metadata, backend, hooks });
2520
2560
  }
2521
- if (game) {
2522
- return game;
2561
+ if (game2) {
2562
+ return game2;
2523
2563
  }
2524
2564
  throw new Error("No deployment actions specified (need metadata, file, or backend)");
2525
2565
  },
@@ -2675,6 +2715,24 @@ function createGamesNamespace(client) {
2675
2715
  }, options);
2676
2716
  },
2677
2717
  list: (options) => gamesListCache.get("all", () => client["request"]("/games", "GET"), options),
2718
+ patchMetadata: async (gameId, metadata) => {
2719
+ const updatedGame = await client["request"](`/games/${gameId}`, "PATCH", {
2720
+ body: metadata
2721
+ });
2722
+ gamesListCache.clear("all");
2723
+ gameFetchCache.clear(gameId);
2724
+ gameFetchCache.clear(updatedGame.slug);
2725
+ return updatedGame;
2726
+ },
2727
+ members: {
2728
+ list: (gameId) => client["request"](`/games/${gameId}/members`, "GET"),
2729
+ add: (gameId, body) => client["request"](`/games/${gameId}/members`, "POST", {
2730
+ body
2731
+ }),
2732
+ update: (gameId, userId, body) => client["request"](`/games/${gameId}/members/${encodeURIComponent(userId)}`, "PATCH", { body }),
2733
+ remove: (gameId, userId) => client["request"](`/games/${gameId}/members/${encodeURIComponent(userId)}`, "DELETE"),
2734
+ search: (gameId, query) => client["request"](`/games/${gameId}/members/search?q=${encodeURIComponent(query)}`, "GET")
2735
+ },
2678
2736
  getSubjects: () => client["request"]("/games/subjects", "GET"),
2679
2737
  startSession: async (gameId) => {
2680
2738
  const idToUse = gameId ?? client["_ensureGameId"]();
@@ -553,6 +553,14 @@ interface BackendDeploymentBundle {
553
553
  compatibilityFlags?: string[];
554
554
  }
555
555
 
556
+ /**
557
+ * User Types
558
+ *
559
+ * Enums, DTOs and API response types. Database row types are in @playcademy/data/types.
560
+ *
561
+ * @module types/user
562
+ */
563
+
556
564
  /**
557
565
  * OpenID Connect UserInfo claims (NOT a database row).
558
566
  */
package/dist/server.d.ts CHANGED
@@ -553,6 +553,14 @@ interface BackendDeploymentBundle {
553
553
  compatibilityFlags?: string[];
554
554
  }
555
555
 
556
+ /**
557
+ * User Types
558
+ *
559
+ * Enums, DTOs and API response types. Database row types are in @playcademy/data/types.
560
+ *
561
+ * @module types/user
562
+ */
563
+
556
564
  /**
557
565
  * OpenID Connect UserInfo claims (NOT a database row).
558
566
  */
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
@@ -450,6 +450,21 @@ type GameMetricsProxyResponse = {
450
450
  details?: string;
451
451
  };
452
452
 
453
+ /**
454
+ * Cache configuration types for runtime customization
455
+ */
456
+ /**
457
+ * Runtime configuration for TTL cache behavior
458
+ */
459
+ interface TTLCacheConfig {
460
+ /** Time-to-live in milliseconds. Set to 0 to disable caching for this call. */
461
+ ttl?: number;
462
+ /** Force refresh, bypassing cache */
463
+ force?: boolean;
464
+ /** Skip cache and fetch fresh data (alias for force) */
465
+ skipCache?: boolean;
466
+ }
467
+
453
468
  /**
454
469
  * @fileoverview Server SDK Type Definitions
455
470
  *
@@ -648,7 +663,8 @@ interface PlaycademyServerClientState {
648
663
  *
649
664
  * @module types/user
650
665
  */
651
- type UserRoleEnumType = 'admin' | 'player' | 'developer' | 'teacher';
666
+
667
+ type UserRoleEnumType = UserRole;
652
668
  type DeveloperStatusEnumType = 'none' | 'pending' | 'approved';
653
669
  type DeveloperStatusValue = DeveloperStatusEnumType;
654
670
  type TimebackUserRole = 'administrator' | 'aide' | 'guardian' | 'parent' | 'proctor' | 'relative' | 'student' | 'teacher';
@@ -656,6 +672,10 @@ type TimebackOrgType = 'department' | 'school' | 'district' | 'local' | 'state'
656
672
  interface UserEnrollment {
657
673
  gameId?: string;
658
674
  courseId: string;
675
+ enrollmentIds?: {
676
+ active: string;
677
+ inactive?: string[];
678
+ };
659
679
  grade: number;
660
680
  subject: string;
661
681
  orgId?: string;
@@ -745,6 +765,7 @@ interface GameUser {
745
765
  *
746
766
  * @module types/game
747
767
  */
768
+
748
769
  type GameType = 'hosted' | 'external';
749
770
  type GamePlatform = 'web' | 'godot' | 'unity' | (string & {});
750
771
  /**
@@ -3970,7 +3991,7 @@ declare const UpsertGameMetadataSchema: z.ZodEffects<z.ZodObject<{
3970
3991
  displayName: z.ZodString;
3971
3992
  mapElementId: z.ZodNullable<z.ZodOptional<z.ZodString>>;
3972
3993
  platform: z.ZodEnum<["web", "godot", "unity"]>;
3973
- metadata: z.ZodDefault<z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
3994
+ metadata: z.ZodDefault<z.ZodOptional<z.ZodEffects<z.ZodRecord<z.ZodString, z.ZodUnknown>, Record<string, unknown>, Record<string, unknown>>>>;
3974
3995
  gameType: z.ZodDefault<z.ZodOptional<z.ZodEnum<["hosted", "external"]>>>;
3975
3996
  visibility: z.ZodOptional<z.ZodEnum<["visible", "unlisted", "internal"]>>;
3976
3997
  externalUrl: z.ZodOptional<z.ZodString>;
@@ -5404,6 +5425,7 @@ declare class PlaycademyClient extends PlaycademyBaseClient {
5404
5425
  * User context (cached from init, refreshable):
5405
5426
  * - `user.role` - User's role (student, parent, teacher, etc.)
5406
5427
  * - `user.enrollments` - Courses the player is enrolled in for this game
5428
+ * - `user.refresh({ only: ['enrollments'] })` - Refresh enrollments from server
5407
5429
  * - `user.organizations` - Schools/districts the player belongs to
5408
5430
  * - `user.fetch()` - Refresh user context from server
5409
5431
  *
@@ -5505,7 +5527,9 @@ declare class PlaycademyClient extends PlaycademyBaseClient {
5505
5527
 
5506
5528
  /**
5507
5529
  * A TimeBack enrollment for the current game session.
5508
- * Alias for UserEnrollment without the optional gameId.
5530
+ * Alias for UserEnrollment without the optional gameId. Active enrollment IDs
5531
+ * are available at `enrollment.enrollmentIds?.active` when supplied by the
5532
+ * platform.
5509
5533
  */
5510
5534
  type TimebackEnrollment = Omit<UserEnrollment, 'gameId'>;
5511
5535
  /**
@@ -5541,6 +5565,14 @@ interface TimebackUserContext {
5541
5565
  /** User's organizations (schools/districts) */
5542
5566
  organizations: TimebackOrganization[];
5543
5567
  }
5568
+ /**
5569
+ * Slice options for refreshing the cached TimeBack user context.
5570
+ */
5571
+ type TimebackUserRefreshField = 'enrollments';
5572
+ interface TimebackUserRefreshOptions extends TTLCacheConfig {
5573
+ /** Refresh only these user data fields */
5574
+ only: readonly TimebackUserRefreshField[];
5575
+ }
5544
5576
  /**
5545
5577
  * XP data access for the current user.
5546
5578
  * Results are cached for 5 seconds to avoid redundant network requests.
@@ -5581,7 +5613,7 @@ interface TimebackUserXp {
5581
5613
  fetch(options?: GetXpOptions): Promise<XpResponse>;
5582
5614
  }
5583
5615
  /**
5584
- * TimeBack user object with both cached getters and fetch method.
5616
+ * TimeBack user object with cached getters, fetch, and targeted refresh methods.
5585
5617
  */
5586
5618
  interface TimebackUser extends TimebackUserContext {
5587
5619
  /**
@@ -5590,9 +5622,14 @@ interface TimebackUser extends TimebackUserContext {
5590
5622
  * @param options - Cache options (pass { force: true } to bypass cache)
5591
5623
  * @returns Promise resolving to fresh user context
5592
5624
  */
5593
- fetch(options?: {
5594
- force?: boolean;
5595
- }): Promise<TimebackUserContext>;
5625
+ fetch(options?: TTLCacheConfig): Promise<TimebackUserContext>;
5626
+ /**
5627
+ * Refresh selected TimeBack user data from the server.
5628
+ * Updates the cached user snapshot used by the synchronous getters.
5629
+ * @param options - Refresh fields and cache options
5630
+ * @returns Promise resolving to the updated user context
5631
+ */
5632
+ refresh(options: TimebackUserRefreshOptions): Promise<TimebackUserContext>;
5596
5633
  /**
5597
5634
  * XP data for the current user.
5598
5635
  * Call `xp.fetch()` to get XP from the server.
@@ -6167,4 +6204,4 @@ interface AssessmentBankStatus {
6167
6204
  }
6168
6205
 
6169
6206
  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 };
6207
+ 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, 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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playcademy/sdk",
3
- "version": "0.9.0",
3
+ "version": "0.9.1-beta.1",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {