@playcademy/sdk 0.9.0 → 0.9.1-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/internal.js CHANGED
@@ -1953,17 +1953,21 @@ function createTimebackActivityTracker(client) {
1953
1953
  }
1954
1954
  }
1955
1955
  return {
1956
+ currentRunId() {
1957
+ return currentActivity?.runId;
1958
+ },
1956
1959
  startActivity(metadata, options) {
1957
1960
  if (options?.runId !== undefined && !isValidUUID(options.runId)) {
1958
1961
  throw new Error(`startActivity: \`runId\` must be a UUID (received \`${JSON.stringify(options.runId)}\`). Use crypto.randomUUID() or persist a previously-generated UUID.`);
1959
1962
  }
1960
1963
  cleanupListeners();
1961
1964
  const now = Date.now();
1965
+ const runId = options?.runId ?? crypto.randomUUID();
1962
1966
  const heartbeatIntervalMs = normalizeDelayMs(options?.heartbeatIntervalMs, DEFAULT_HEARTBEAT_INTERVAL_MS, false);
1963
1967
  const pausedHeartbeatTimeoutMs = normalizeDelayMs(options?.pausedHeartbeatTimeoutMs ?? options?.hiddenTimeoutMs, DEFAULT_PAUSED_HEARTBEAT_TIMEOUT_MS, false);
1964
1968
  const inactivityTimeoutMs = normalizeDelayMs(options?.inactivityTimeoutMs, DEFAULT_INACTIVITY_TIMEOUT_MS, false);
1965
1969
  currentActivity = {
1966
- runId: options?.runId ?? crypto.randomUUID(),
1970
+ runId,
1967
1971
  resumeId: crypto.randomUUID(),
1968
1972
  startTime: now,
1969
1973
  metadata,
@@ -2006,6 +2010,7 @@ function createTimebackActivityTracker(client) {
2006
2010
  messaging.listen("PLAYCADEMY_PAUSE" /* PAUSE */, boundShellPauseHandler);
2007
2011
  messaging.listen("PLAYCADEMY_RESUME" /* RESUME */, boundShellResumeHandler);
2008
2012
  syncInactivityTracking();
2013
+ return { runId };
2009
2014
  },
2010
2015
  pauseActivity() {
2011
2016
  if (!currentActivity) {
@@ -2095,6 +2100,15 @@ function createTimebackUserStore(client) {
2095
2100
  enrollments: response.enrollments,
2096
2101
  organizations: response.organizations
2097
2102
  };
2103
+ },
2104
+ setEnrollments(enrollments) {
2105
+ const base = override ?? client["initPayload"]?.timeback;
2106
+ override = base ? { ...base, enrollments } : {
2107
+ id: undefined,
2108
+ role: undefined,
2109
+ enrollments,
2110
+ organizations: []
2111
+ };
2098
2112
  }
2099
2113
  };
2100
2114
  }
@@ -2110,20 +2124,50 @@ function createTimebackEngine(client) {
2110
2124
  ttl: 5000,
2111
2125
  keyPrefix: "game.timeback.xp"
2112
2126
  });
2127
+ const enrollmentsCache = createTTLCache({
2128
+ ttl: 5 * 60 * 1000,
2129
+ keyPrefix: "game.timeback.enrollments"
2130
+ });
2113
2131
  async function applyPromotion(promotion) {
2114
2132
  if (promotion.status !== "promoted" && promotion.status !== "already-promoted") {
2115
2133
  return;
2116
2134
  }
2117
2135
  userCache.clear("current");
2136
+ enrollmentsCache.clear("current");
2118
2137
  try {
2119
2138
  await userStore.refresh();
2120
2139
  } catch {}
2121
2140
  }
2122
2141
  const activityTracker = createTimebackActivityTracker(client);
2142
+ async function refreshUserContext() {
2143
+ const context = await userStore.refresh();
2144
+ enrollmentsCache.clear("current");
2145
+ return context;
2146
+ }
2147
+ async function refreshEnrollmentSlice(options) {
2148
+ const enrollments = await enrollmentsCache.get("current", async () => {
2149
+ const response = await client["request"]("/timeback/user/enrollments", "GET");
2150
+ userStore.setEnrollments(response);
2151
+ userCache.clear("current");
2152
+ return response;
2153
+ }, options);
2154
+ userStore.setEnrollments(enrollments);
2155
+ return userStore.snapshot();
2156
+ }
2123
2157
  return {
2124
2158
  user: {
2125
2159
  snapshot: () => userStore.snapshot(),
2126
- fetch: (options) => userCache.get("current", () => userStore.refresh(), options),
2160
+ fetch: (options) => userCache.get("current", refreshUserContext, options),
2161
+ refresh: (options) => {
2162
+ if (!options?.only.includes("enrollments")) {
2163
+ throw new Error("At least one TimeBack user refresh field must be selected");
2164
+ }
2165
+ return refreshEnrollmentSlice({
2166
+ force: options.force,
2167
+ skipCache: options.skipCache,
2168
+ ttl: options.ttl
2169
+ });
2170
+ },
2127
2171
  xp: {
2128
2172
  fetch: (options) => {
2129
2173
  const cacheKey = [
@@ -2150,6 +2194,7 @@ function createTimebackEngine(client) {
2150
2194
  }
2151
2195
  },
2152
2196
  activity: {
2197
+ currentRunId: activityTracker.currentRunId,
2153
2198
  start: activityTracker.startActivity,
2154
2199
  pause: activityTracker.pauseActivity,
2155
2200
  resume: activityTracker.resumeActivity,
@@ -2184,6 +2229,7 @@ function createTimebackNamespace(client) {
2184
2229
  return engine.user.snapshot()?.organizations ?? [];
2185
2230
  },
2186
2231
  fetch: (options) => engine.user.fetch(options),
2232
+ refresh: (options) => engine.user.refresh(options),
2187
2233
  xp: {
2188
2234
  fetch: async (options = {}) => {
2189
2235
  const hasGrade = options.grade !== undefined;
@@ -2209,9 +2255,13 @@ function createTimebackNamespace(client) {
2209
2255
  }
2210
2256
  };
2211
2257
  },
2258
+ get currentRunId() {
2259
+ assertPlatformMode(client, "timeback.currentRunId");
2260
+ return engine.activity.currentRunId();
2261
+ },
2212
2262
  startActivity: (metadata, options) => {
2213
2263
  assertPlatformMode(client, "timeback.startActivity()");
2214
- engine.activity.start(metadata, options);
2264
+ return engine.activity.start(metadata, options);
2215
2265
  },
2216
2266
  pauseActivity: () => {
2217
2267
  assertPlatformMode(client, "timeback.pauseActivity()");
@@ -2348,7 +2398,7 @@ class DeployPipeline {
2348
2398
  return this.fetchGameWithRetry(slug);
2349
2399
  }
2350
2400
  async buildRequestBody(args) {
2351
- const game = await this.resolveGame(args.slug, args.game);
2401
+ const game2 = await this.resolveGame(args.slug, args.game);
2352
2402
  const requestBody = {};
2353
2403
  if (args.uploadToken) {
2354
2404
  requestBody.uploadToken = args.uploadToken;
@@ -2357,7 +2407,7 @@ class DeployPipeline {
2357
2407
  requestBody.metadata = args.metadata;
2358
2408
  }
2359
2409
  if (!args.backend) {
2360
- return { game, requestBody };
2410
+ return { game: game2, requestBody };
2361
2411
  }
2362
2412
  const backendFields = {
2363
2413
  config: args.backend.config,
@@ -2372,7 +2422,7 @@ class DeployPipeline {
2372
2422
  code: args.backend.code
2373
2423
  };
2374
2424
  if (this.serializedSize(inlineBody) <= DeployPipeline.MAX_INLINE_REQUEST_BYTES) {
2375
- return { game, requestBody: inlineBody };
2425
+ return { game: game2, requestBody: inlineBody };
2376
2426
  }
2377
2427
  const skeletonBody = {
2378
2428
  ...requestBody,
@@ -2382,8 +2432,8 @@ class DeployPipeline {
2382
2432
  if (this.serializedSize(skeletonBody) > DeployPipeline.MAX_INLINE_REQUEST_BYTES) {
2383
2433
  throw new Error("Deploy request is too large even after uploading backend code");
2384
2434
  }
2385
- skeletonBody.codeUploadToken = await this.uploadCode(game.id, args.backend.code);
2386
- return { game, requestBody: skeletonBody };
2435
+ skeletonBody.codeUploadToken = await this.uploadCode(game2.id, args.backend.code);
2436
+ return { game: game2, requestBody: skeletonBody };
2387
2437
  }
2388
2438
  serializedSize(body) {
2389
2439
  return DeployPipeline.textEncoder.encode(JSON.stringify(body)).length;
@@ -2471,8 +2521,8 @@ class DeployPipeline {
2471
2521
  await new Promise((resolve) => setTimeout(resolve, DeployPipeline.POLL_INTERVAL_MS));
2472
2522
  }
2473
2523
  }
2474
- async resolveGame(slug, game) {
2475
- return game ?? await this.client["request"](`/games/${slug}`, "GET");
2524
+ async resolveGame(slug, game2) {
2525
+ return game2 ?? await this.client["request"](`/games/${slug}`, "GET");
2476
2526
  }
2477
2527
  async fetchGameWithRetry(slug) {
2478
2528
  const { GAME_FETCH_RETRIES } = DeployPipeline;
@@ -2505,21 +2555,21 @@ function createDevNamespace(client) {
2505
2555
  deploy: async (slug, options) => {
2506
2556
  const { metadata, file, backend, hooks } = options;
2507
2557
  hooks?.onEvent?.({ type: "init" });
2508
- let game;
2558
+ let game2;
2509
2559
  if (metadata) {
2510
- game = await client["request"](`/games/${slug}`, "PUT", {
2560
+ game2 = await client["request"](`/games/${slug}`, "PUT", {
2511
2561
  body: metadata
2512
2562
  });
2513
2563
  if (metadata.gameType === "external" && !file && !backend) {
2514
- return game;
2564
+ return game2;
2515
2565
  }
2516
2566
  }
2517
- const uploadToken = file ? await deploy.uploadFile(file, game?.id || slug, hooks) : undefined;
2567
+ const uploadToken = file ? await deploy.uploadFile(file, game2?.id || slug, hooks) : undefined;
2518
2568
  if (uploadToken || backend) {
2519
- return deploy.submit({ slug, game, uploadToken, metadata, backend, hooks });
2569
+ return deploy.submit({ slug, game: game2, uploadToken, metadata, backend, hooks });
2520
2570
  }
2521
- if (game) {
2522
- return game;
2571
+ if (game2) {
2572
+ return game2;
2523
2573
  }
2524
2574
  throw new Error("No deployment actions specified (need metadata, file, or backend)");
2525
2575
  },
@@ -2675,6 +2725,24 @@ function createGamesNamespace(client) {
2675
2725
  }, options);
2676
2726
  },
2677
2727
  list: (options) => gamesListCache.get("all", () => client["request"]("/games", "GET"), options),
2728
+ patchMetadata: async (gameId, metadata) => {
2729
+ const updatedGame = await client["request"](`/games/${gameId}`, "PATCH", {
2730
+ body: metadata
2731
+ });
2732
+ gamesListCache.clear("all");
2733
+ gameFetchCache.clear(gameId);
2734
+ gameFetchCache.clear(updatedGame.slug);
2735
+ return updatedGame;
2736
+ },
2737
+ members: {
2738
+ list: (gameId) => client["request"](`/games/${gameId}/members`, "GET"),
2739
+ add: (gameId, body) => client["request"](`/games/${gameId}/members`, "POST", {
2740
+ body
2741
+ }),
2742
+ update: (gameId, userId, body) => client["request"](`/games/${gameId}/members/${encodeURIComponent(userId)}`, "PATCH", { body }),
2743
+ remove: (gameId, userId) => client["request"](`/games/${gameId}/members/${encodeURIComponent(userId)}`, "DELETE"),
2744
+ search: (gameId, query) => client["request"](`/games/${gameId}/members/search?q=${encodeURIComponent(query)}`, "GET")
2745
+ },
2678
2746
  getSubjects: () => client["request"]("/games/subjects", "GET"),
2679
2747
  startSession: async (gameId) => {
2680
2748
  const idToUse = gameId ?? client["_ensureGameId"]();
@@ -3099,7 +3167,10 @@ function createTimebackNamespace2(client) {
3099
3167
  };
3100
3168
  },
3101
3169
  populateStudent: async (names) => client["request"]("/timeback/populate-student", "POST", names ? { body: names } : undefined),
3102
- startActivity: (_metadata) => {
3170
+ get currentRunId() {
3171
+ throw new Error(NOT_SUPPORTED);
3172
+ },
3173
+ startActivity: (_metadata, _options) => {
3103
3174
  throw new Error(NOT_SUPPORTED);
3104
3175
  },
3105
3176
  pauseActivity: () => {
@@ -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
  */