@playcademy/sdk 0.23.1-beta.1 → 0.24.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 +146 -126
- package/dist/index.js +1 -1
- package/dist/internal.d.ts +2023 -1975
- package/dist/internal.js +3 -1
- package/dist/server/edge.js +1 -1
- package/dist/server.js +1 -1
- package/dist/types.d.ts +434 -410
- package/package.json +1 -1
package/dist/types.d.ts
CHANGED
|
@@ -96,442 +96,92 @@ declare const ASSESSMENT_PURPOSES: readonly ['end_of_course', 'diagnostic', 'rev
|
|
|
96
96
|
declare const PLAYCADEMY_ASSESSMENT_CONTRACT_VERSION: 2;
|
|
97
97
|
|
|
98
98
|
/**
|
|
99
|
-
*
|
|
99
|
+
* TimeBack Enums & Literal Types
|
|
100
100
|
*
|
|
101
|
-
*
|
|
101
|
+
* Basic type definitions used throughout the TimeBack integration. Unions
|
|
102
|
+
* derive from the canonical value lists in @playcademy/constants (type-only
|
|
103
|
+
* imports, so this package ships no runtime code).
|
|
102
104
|
*
|
|
103
|
-
* @module types/
|
|
105
|
+
* @module types/timeback/types
|
|
104
106
|
*/
|
|
105
107
|
|
|
106
|
-
type UserRoleEnumType = UserRole;
|
|
107
|
-
type DeveloperStatusEnumType = 'none' | 'pending' | 'approved';
|
|
108
|
-
type DeveloperStatusValue = DeveloperStatusEnumType;
|
|
109
|
-
type TimebackUserRole = 'administrator' | 'aide' | 'guardian' | 'parent' | 'proctor' | 'relative' | 'student' | 'teacher';
|
|
110
|
-
type TimebackOrgType = 'department' | 'school' | 'district' | 'local' | 'state' | 'national';
|
|
111
|
-
interface UserEnrollment {
|
|
112
|
-
gameId?: string;
|
|
113
|
-
courseId: string;
|
|
114
|
-
id?: string;
|
|
115
|
-
grade: number;
|
|
116
|
-
subject: string;
|
|
117
|
-
orgId?: string;
|
|
118
|
-
}
|
|
119
|
-
interface UserOrganization {
|
|
120
|
-
id: string;
|
|
121
|
-
name: string | null;
|
|
122
|
-
type: TimebackOrgType | string;
|
|
123
|
-
isPrimary: boolean;
|
|
124
|
-
}
|
|
125
|
-
interface TimebackStudentProfile {
|
|
126
|
-
role: TimebackUserRole;
|
|
127
|
-
organizations: UserOrganization[];
|
|
128
|
-
}
|
|
129
108
|
/**
|
|
130
|
-
*
|
|
131
|
-
*
|
|
109
|
+
* The TimeBack subject vocabulary. 'None' is canonical: TimeBack accepts it
|
|
110
|
+
* on courses and uses it as the fallback for unknown or missing subjects.
|
|
132
111
|
*/
|
|
133
|
-
|
|
134
|
-
id: string;
|
|
135
|
-
}
|
|
136
|
-
interface UserTimebackData extends UserTimebackProfile {
|
|
137
|
-
enrollments: UserEnrollment[];
|
|
138
|
-
}
|
|
139
|
-
type LocalDaySource = 'browser_last_seen' | 'platform_default';
|
|
140
|
-
interface LocalDayContext {
|
|
141
|
-
timeZone: string;
|
|
142
|
-
source: LocalDaySource;
|
|
143
|
-
/** ISO 8601 timestamp for the last persisted timezone observation. */
|
|
144
|
-
observedAt: string | null;
|
|
145
|
-
}
|
|
112
|
+
type TimebackSubject = (typeof TIMEBACK_SUBJECTS)[number];
|
|
146
113
|
/**
|
|
147
|
-
*
|
|
114
|
+
* Grade levels per AE OneRoster GradeEnum.
|
|
115
|
+
* -1 = Pre-K, 0 = Kindergarten, 1-12 = Grades 1-12, 13 = AP
|
|
148
116
|
*/
|
|
149
|
-
|
|
150
|
-
sub: string;
|
|
151
|
-
email: string;
|
|
152
|
-
name: string | null;
|
|
153
|
-
email_verified?: boolean;
|
|
154
|
-
given_name?: string;
|
|
155
|
-
family_name?: string;
|
|
156
|
-
issuer?: string;
|
|
157
|
-
lti_roles?: unknown;
|
|
158
|
-
lti_context?: unknown;
|
|
159
|
-
lti_resource_link?: unknown;
|
|
160
|
-
timeback_id?: string;
|
|
161
|
-
}
|
|
162
|
-
interface DeveloperStatusResponse {
|
|
163
|
-
status: DeveloperStatusEnumType;
|
|
164
|
-
}
|
|
165
|
-
interface DemoProfile {
|
|
166
|
-
displayName: string;
|
|
167
|
-
isDefault: boolean;
|
|
168
|
-
}
|
|
117
|
+
type TimebackGrade = (typeof TIMEBACK_GRADES)[number];
|
|
169
118
|
/**
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
* Kept as a named type so callers typed against it pick up new fields
|
|
173
|
-
* automatically, but `displayName` is the only updatable field today and
|
|
174
|
-
* the server's `DemoProfileSchema` requires it — a no-field payload would
|
|
175
|
-
* 400 at runtime, so we model that at the type level too. When additional
|
|
176
|
-
* fields land, make them required/optional individually based on server
|
|
177
|
-
* validation, rather than blanket-optional.
|
|
119
|
+
* Platform pedagogy levels for a single lesson of instruction.
|
|
120
|
+
* E1 teaches, E2/E3 practice and remediate, E4 is the mastery check.
|
|
178
121
|
*/
|
|
179
|
-
|
|
180
|
-
displayName: string;
|
|
181
|
-
}
|
|
122
|
+
type ELevel = 'E1' | 'E2' | 'E3' | 'E4';
|
|
182
123
|
/**
|
|
183
|
-
*
|
|
184
|
-
* Differs from UserRow: omits timebackId, adds hasTimebackAccount and timeback.
|
|
124
|
+
* Valid Caliper subject values. The same vocabulary as OneRoster subjects.
|
|
185
125
|
*/
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
createdAt: Date;
|
|
196
|
-
updatedAt: Date;
|
|
197
|
-
hasTimebackAccount: boolean;
|
|
198
|
-
localDay: LocalDayContext;
|
|
199
|
-
timeback?: UserTimebackProfile;
|
|
200
|
-
}
|
|
201
|
-
interface GameUser {
|
|
202
|
-
id: string;
|
|
203
|
-
name: string | null;
|
|
204
|
-
role: UserRoleEnumType;
|
|
205
|
-
username: string | null;
|
|
206
|
-
email: string | null;
|
|
207
|
-
localDay: LocalDayContext;
|
|
208
|
-
timeback?: UserTimebackProfile;
|
|
209
|
-
}
|
|
126
|
+
type CaliperSubject = (typeof TIMEBACK_SUBJECTS)[number];
|
|
127
|
+
/**
|
|
128
|
+
* OneRoster organization types.
|
|
129
|
+
*/
|
|
130
|
+
type OrganizationType = 'department' | 'school' | 'district' | 'local' | 'state' | 'national';
|
|
131
|
+
/**
|
|
132
|
+
* Lesson types for PowerPath integration.
|
|
133
|
+
*/
|
|
134
|
+
type LessonType = 'powerpath-100' | 'quiz' | 'test-out' | 'placement' | 'unit-test' | 'alpha-read-article' | null;
|
|
210
135
|
|
|
211
136
|
/**
|
|
212
|
-
*
|
|
137
|
+
* TimeBack Configuration Types
|
|
213
138
|
*
|
|
214
|
-
*
|
|
139
|
+
* Configuration interfaces for Organization, Course, Component,
|
|
140
|
+
* Resource, and complete TimeBack setup.
|
|
215
141
|
*
|
|
216
|
-
* @module types/
|
|
142
|
+
* @module types/timeback/config
|
|
217
143
|
*/
|
|
218
144
|
|
|
219
|
-
type GameType = 'hosted' | 'external';
|
|
220
|
-
type GamePlatform = 'web' | 'godot' | (string & {});
|
|
221
145
|
/**
|
|
222
|
-
*
|
|
223
|
-
* Note: createdAt is a string here because it's parsed from JSON file.
|
|
146
|
+
* Organization configuration for TimeBack (user input - optionals allowed)
|
|
224
147
|
*/
|
|
225
|
-
interface
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
sdk?: string;
|
|
233
|
-
cli?: string;
|
|
234
|
-
vite?: string;
|
|
235
|
-
godotSdk?: string;
|
|
236
|
-
godot?: string;
|
|
237
|
-
}
|
|
238
|
-
interface ManifestV2 {
|
|
239
|
-
version: '2';
|
|
240
|
-
platform: string;
|
|
241
|
-
createdAt: string;
|
|
242
|
-
gameVersion?: string;
|
|
243
|
-
versions: ManifestVersions;
|
|
148
|
+
interface OrganizationConfig {
|
|
149
|
+
/** Display name for your organization */
|
|
150
|
+
name?: string;
|
|
151
|
+
/** Organization type */
|
|
152
|
+
type?: OrganizationType;
|
|
153
|
+
/** Unique identifier (defaults to Playcademy's org) */
|
|
154
|
+
identifier?: string;
|
|
244
155
|
}
|
|
245
|
-
type GameManifest = ManifestV1 | ManifestV2;
|
|
246
156
|
/**
|
|
247
|
-
*
|
|
248
|
-
*
|
|
249
|
-
* Captured by the CLI at deploy time and persisted verbatim on the deploy
|
|
250
|
-
* job, so "which commit is running" is answerable from the platform record.
|
|
251
|
-
* Every field is nullable: a tree with no git, no remote, or a detached HEAD
|
|
252
|
-
* is recorded honestly rather than refused. Recorded per deploy, never
|
|
253
|
-
* pinned to the game — a fork deployed under the same slug shows the fork.
|
|
157
|
+
* Course goals for daily student targets
|
|
254
158
|
*/
|
|
255
|
-
interface
|
|
256
|
-
/**
|
|
257
|
-
|
|
258
|
-
/**
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
|
|
264
|
-
/**
|
|
265
|
-
|
|
266
|
-
* other fields describe (`--build ~/Downloads/game.zip`). The commit is
|
|
267
|
-
* still the deployer's tree, but it may not be what produced the artifact.
|
|
268
|
-
*/
|
|
269
|
-
externalBuild: boolean;
|
|
159
|
+
interface CourseGoals {
|
|
160
|
+
/** Target XP students should earn per day */
|
|
161
|
+
dailyXp?: number;
|
|
162
|
+
/** Target lessons per day */
|
|
163
|
+
dailyLessons?: number;
|
|
164
|
+
/** Target active minutes per day */
|
|
165
|
+
dailyActiveMinutes?: number;
|
|
166
|
+
/** Target accuracy percentage */
|
|
167
|
+
dailyAccuracy?: number;
|
|
168
|
+
/** Target mastered units per day */
|
|
169
|
+
dailyMasteredUnits?: number;
|
|
270
170
|
}
|
|
271
171
|
/**
|
|
272
|
-
*
|
|
273
|
-
* describes it: what is running and which commit produced it.
|
|
274
|
-
*
|
|
275
|
-
* This shape is parsed by external automation. Every field is present on
|
|
276
|
-
* every entry — nullable, never absent — and nothing from the deploy job
|
|
277
|
-
* beyond its git provenance and completion time is exposed. No deployer
|
|
278
|
-
* identity, no schema state, no secrets manifest.
|
|
172
|
+
* Course metrics and totals
|
|
279
173
|
*/
|
|
280
|
-
interface
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
deployedAt: string;
|
|
292
|
-
}
|
|
293
|
-
/** GET /api/releases — the current release of every game */
|
|
294
|
-
interface ReleasesResponse {
|
|
295
|
-
releases: Release[];
|
|
296
|
-
}
|
|
297
|
-
/** GET /api/releases/:slug — one game's current release and, on request, its recent history */
|
|
298
|
-
interface GameReleaseResponse {
|
|
299
|
-
/** The current release; null when the game has never deployed successfully */
|
|
300
|
-
current: Release | null;
|
|
301
|
-
/**
|
|
302
|
-
* The last N successful deploys, newest first, current included. Only
|
|
303
|
-
* present when `?history=N` was requested.
|
|
304
|
-
*/
|
|
305
|
-
history?: Release[];
|
|
306
|
-
}
|
|
307
|
-
interface DomainValidationRecords {
|
|
308
|
-
ownership?: {
|
|
309
|
-
name?: string;
|
|
310
|
-
value?: string;
|
|
311
|
-
type?: string;
|
|
312
|
-
};
|
|
313
|
-
ssl?: {
|
|
314
|
-
txt_name?: string;
|
|
315
|
-
txt_value?: string;
|
|
316
|
-
}[];
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
/**
|
|
320
|
-
* Leaderboard Types
|
|
321
|
-
*
|
|
322
|
-
* @module types/leaderboard
|
|
323
|
-
*/
|
|
324
|
-
type LeaderboardTimeframe = 'all_time' | 'monthly' | 'weekly' | 'daily';
|
|
325
|
-
interface LeaderboardOptions {
|
|
326
|
-
timeframe?: LeaderboardTimeframe;
|
|
327
|
-
limit?: number;
|
|
328
|
-
offset?: number;
|
|
329
|
-
gameId?: string;
|
|
330
|
-
}
|
|
331
|
-
interface LeaderboardEntry {
|
|
332
|
-
rank: number;
|
|
333
|
-
userId: string;
|
|
334
|
-
username: string;
|
|
335
|
-
userImage?: string | null;
|
|
336
|
-
score: number;
|
|
337
|
-
achievedAt: Date;
|
|
338
|
-
metadata?: Record<string, unknown>;
|
|
339
|
-
gameId?: string;
|
|
340
|
-
gameTitle?: string;
|
|
341
|
-
gameSlug?: string;
|
|
342
|
-
}
|
|
343
|
-
interface UserRank {
|
|
344
|
-
rank: number;
|
|
345
|
-
totalPlayers: number;
|
|
346
|
-
score: number;
|
|
347
|
-
percentile: number;
|
|
348
|
-
}
|
|
349
|
-
interface UserRankResponse {
|
|
350
|
-
rank: number;
|
|
351
|
-
score: number;
|
|
352
|
-
userId: string;
|
|
353
|
-
}
|
|
354
|
-
interface UserScore {
|
|
355
|
-
id: string;
|
|
356
|
-
score: number;
|
|
357
|
-
achievedAt: Date;
|
|
358
|
-
metadata?: Record<string, unknown>;
|
|
359
|
-
gameId: string;
|
|
360
|
-
gameTitle: string;
|
|
361
|
-
gameSlug: string;
|
|
362
|
-
}
|
|
363
|
-
/**
|
|
364
|
-
* Leaderboard entry with required game context.
|
|
365
|
-
* Used when fetching leaderboards for a specific game.
|
|
366
|
-
*/
|
|
367
|
-
interface GameLeaderboardEntry {
|
|
368
|
-
rank: number;
|
|
369
|
-
userId: string;
|
|
370
|
-
username: string;
|
|
371
|
-
userImage?: string | null;
|
|
372
|
-
score: number;
|
|
373
|
-
achievedAt: Date;
|
|
374
|
-
metadata?: Record<string, unknown>;
|
|
375
|
-
gameId: string;
|
|
376
|
-
gameTitle: string;
|
|
377
|
-
gameSlug: string;
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
/**
|
|
381
|
-
* Skill Status Types
|
|
382
|
-
*
|
|
383
|
-
* Contract for cross-game skill status queries. An answering game exports
|
|
384
|
-
* `getSkillStatus` from `server/lib/skills.ts`; the platform calls its
|
|
385
|
-
* reserved `/__playcademy/skills/status` route on behalf of another game.
|
|
386
|
-
*
|
|
387
|
-
* @module types/skills
|
|
388
|
-
*/
|
|
389
|
-
|
|
390
|
-
/**
|
|
391
|
-
* Current status of one skill for one student, as judged by the answering game.
|
|
392
|
-
*
|
|
393
|
-
* - `locked`: the student can't work on the skill yet in this game.
|
|
394
|
-
* - `unlocked`: available to the student, but no progress yet.
|
|
395
|
-
* - `in_progress`: started, not mastered.
|
|
396
|
-
* - `mastered`: currently meets this game's mastery bar, including by placement.
|
|
397
|
-
* - `unknown_skill`: the skill ID isn't recognized. Never report an unrecognized
|
|
398
|
-
* skill as `locked`.
|
|
399
|
-
*/
|
|
400
|
-
type SkillStatus = (typeof SKILL_STATUSES)[number];
|
|
401
|
-
/** How a `mastered` status was reached. */
|
|
402
|
-
type SkillMasterySource = (typeof SKILL_MASTERY_SOURCES)[number];
|
|
403
|
-
interface SkillStatusResult {
|
|
404
|
-
/** Skill ID, lowercase. */
|
|
405
|
-
skill: string;
|
|
406
|
-
/** Current status. */
|
|
407
|
-
status: SkillStatus;
|
|
408
|
-
/** How mastery was reached. Only allowed when `status` is `mastered`. */
|
|
409
|
-
source?: SkillMasterySource;
|
|
410
|
-
/** ISO 8601 time of the evidence behind this status. */
|
|
411
|
-
asOf?: string;
|
|
412
|
-
}
|
|
413
|
-
/** Response returned by an answering game's `getSkillStatus` handler. */
|
|
414
|
-
interface SkillStatusResponse {
|
|
415
|
-
/** Student identifier (Playcademy user ID). Must match the requested student. */
|
|
416
|
-
studentId: string;
|
|
417
|
-
/** One result per skill the handler was asked about. */
|
|
418
|
-
results: SkillStatusResult[];
|
|
419
|
-
}
|
|
420
|
-
/** The game asking for skill status, as identified by the platform. */
|
|
421
|
-
interface SkillStatusRequestingGame {
|
|
422
|
-
id: string;
|
|
423
|
-
slug: string;
|
|
424
|
-
}
|
|
425
|
-
/** Body of `POST /api/skills/status`, sent by the asking game's worker. */
|
|
426
|
-
interface SkillStatusRequest {
|
|
427
|
-
/** Slug of the game to ask. */
|
|
428
|
-
game: string;
|
|
429
|
-
/** Playcademy user ID of the student. */
|
|
430
|
-
studentId: string;
|
|
431
|
-
/** Skill IDs to ask about. Normalized (trimmed, lowercased) and de-duplicated. */
|
|
432
|
-
skills: string[];
|
|
433
|
-
}
|
|
434
|
-
/** Why the answering game couldn't answer. */
|
|
435
|
-
type SkillStatusUnsupportedReason = 'not_implemented' | 'no_active_deployment' | 'timeout' | 'fetch_failed' | 'invalid_response';
|
|
436
|
-
/**
|
|
437
|
-
* What the asking game gets back. When `supported` is false, fall back to
|
|
438
|
-
* the game's own default (usually: treat the skills as not mastered).
|
|
439
|
-
*/
|
|
440
|
-
type SkillStatusLookupResult = {
|
|
441
|
-
supported: true;
|
|
442
|
-
results: SkillStatusResult[];
|
|
443
|
-
} | {
|
|
444
|
-
supported: false;
|
|
445
|
-
reason: SkillStatusUnsupportedReason;
|
|
446
|
-
};
|
|
447
|
-
|
|
448
|
-
/**
|
|
449
|
-
* TimeBack Enums & Literal Types
|
|
450
|
-
*
|
|
451
|
-
* Basic type definitions used throughout the TimeBack integration. Unions
|
|
452
|
-
* derive from the canonical value lists in @playcademy/constants (type-only
|
|
453
|
-
* imports, so this package ships no runtime code).
|
|
454
|
-
*
|
|
455
|
-
* @module types/timeback/types
|
|
456
|
-
*/
|
|
457
|
-
|
|
458
|
-
/**
|
|
459
|
-
* The TimeBack subject vocabulary. 'None' is canonical: TimeBack accepts it
|
|
460
|
-
* on courses and uses it as the fallback for unknown or missing subjects.
|
|
461
|
-
*/
|
|
462
|
-
type TimebackSubject = (typeof TIMEBACK_SUBJECTS)[number];
|
|
463
|
-
/**
|
|
464
|
-
* Grade levels per AE OneRoster GradeEnum.
|
|
465
|
-
* -1 = Pre-K, 0 = Kindergarten, 1-12 = Grades 1-12, 13 = AP
|
|
466
|
-
*/
|
|
467
|
-
type TimebackGrade = (typeof TIMEBACK_GRADES)[number];
|
|
468
|
-
/**
|
|
469
|
-
* Platform pedagogy levels for a single lesson of instruction.
|
|
470
|
-
* E1 teaches, E2/E3 practice and remediate, E4 is the mastery check.
|
|
471
|
-
*/
|
|
472
|
-
type ELevel = 'E1' | 'E2' | 'E3' | 'E4';
|
|
473
|
-
/**
|
|
474
|
-
* Valid Caliper subject values. The same vocabulary as OneRoster subjects.
|
|
475
|
-
*/
|
|
476
|
-
type CaliperSubject = (typeof TIMEBACK_SUBJECTS)[number];
|
|
477
|
-
/**
|
|
478
|
-
* OneRoster organization types.
|
|
479
|
-
*/
|
|
480
|
-
type OrganizationType = 'department' | 'school' | 'district' | 'local' | 'state' | 'national';
|
|
481
|
-
/**
|
|
482
|
-
* Lesson types for PowerPath integration.
|
|
483
|
-
*/
|
|
484
|
-
type LessonType = 'powerpath-100' | 'quiz' | 'test-out' | 'placement' | 'unit-test' | 'alpha-read-article' | null;
|
|
485
|
-
|
|
486
|
-
/**
|
|
487
|
-
* TimeBack Configuration Types
|
|
488
|
-
*
|
|
489
|
-
* Configuration interfaces for Organization, Course, Component,
|
|
490
|
-
* Resource, and complete TimeBack setup.
|
|
491
|
-
*
|
|
492
|
-
* @module types/timeback/config
|
|
493
|
-
*/
|
|
494
|
-
|
|
495
|
-
/**
|
|
496
|
-
* Organization configuration for TimeBack (user input - optionals allowed)
|
|
497
|
-
*/
|
|
498
|
-
interface OrganizationConfig {
|
|
499
|
-
/** Display name for your organization */
|
|
500
|
-
name?: string;
|
|
501
|
-
/** Organization type */
|
|
502
|
-
type?: OrganizationType;
|
|
503
|
-
/** Unique identifier (defaults to Playcademy's org) */
|
|
504
|
-
identifier?: string;
|
|
505
|
-
}
|
|
506
|
-
/**
|
|
507
|
-
* Course goals for daily student targets
|
|
508
|
-
*/
|
|
509
|
-
interface CourseGoals {
|
|
510
|
-
/** Target XP students should earn per day */
|
|
511
|
-
dailyXp?: number;
|
|
512
|
-
/** Target lessons per day */
|
|
513
|
-
dailyLessons?: number;
|
|
514
|
-
/** Target active minutes per day */
|
|
515
|
-
dailyActiveMinutes?: number;
|
|
516
|
-
/** Target accuracy percentage */
|
|
517
|
-
dailyAccuracy?: number;
|
|
518
|
-
/** Target mastered units per day */
|
|
519
|
-
dailyMasteredUnits?: number;
|
|
520
|
-
}
|
|
521
|
-
/**
|
|
522
|
-
* Course metrics and totals
|
|
523
|
-
*/
|
|
524
|
-
interface CourseMetrics {
|
|
525
|
-
/** Total XP available in the course */
|
|
526
|
-
totalXp?: number;
|
|
527
|
-
/** Total lessons/activities in the course */
|
|
528
|
-
totalLessons?: number;
|
|
529
|
-
/** Total number of grade levels covered by this course */
|
|
530
|
-
totalGrades?: number;
|
|
531
|
-
/** The type of course (e.g. 'optional', 'hole-filling', 'base') */
|
|
532
|
-
courseType?: 'base' | 'hole-filling' | 'optional' | 'Base' | 'Hole-Filling' | 'Optional';
|
|
533
|
-
/** Indicates whether the course is supplemental content */
|
|
534
|
-
isSupplemental?: boolean;
|
|
174
|
+
interface CourseMetrics {
|
|
175
|
+
/** Total XP available in the course */
|
|
176
|
+
totalXp?: number;
|
|
177
|
+
/** Total lessons/activities in the course */
|
|
178
|
+
totalLessons?: number;
|
|
179
|
+
/** Total number of grade levels covered by this course */
|
|
180
|
+
totalGrades?: number;
|
|
181
|
+
/** The type of course (e.g. 'optional', 'hole-filling', 'base') */
|
|
182
|
+
courseType?: 'base' | 'hole-filling' | 'optional' | 'Base' | 'Hole-Filling' | 'Optional';
|
|
183
|
+
/** Indicates whether the course is supplemental content */
|
|
184
|
+
isSupplemental?: boolean;
|
|
535
185
|
}
|
|
536
186
|
/**
|
|
537
187
|
* Complete course metadata structure
|
|
@@ -838,12 +488,22 @@ interface PopulateStudentResponse {
|
|
|
838
488
|
status: string;
|
|
839
489
|
message?: string;
|
|
840
490
|
}
|
|
491
|
+
/**
|
|
492
|
+
* `base` courses form a game's grade ladder (one active per grade and subject).
|
|
493
|
+
* `hole_filling` courses are remediation courses Timeback enrolls students into
|
|
494
|
+
* after a failed test; each sits beside a base course.
|
|
495
|
+
*/
|
|
496
|
+
type GameTimebackIntegrationKind = 'base' | 'hole_filling';
|
|
841
497
|
interface GameTimebackIntegration {
|
|
842
498
|
id: string;
|
|
843
499
|
gameId: string;
|
|
844
500
|
courseId: string;
|
|
845
501
|
grade: number;
|
|
846
502
|
subject: string;
|
|
503
|
+
/** Defaults to `base` when absent (responses from older servers). */
|
|
504
|
+
kind?: GameTimebackIntegrationKind;
|
|
505
|
+
/** The base course a hole-filling course belongs to; null for base courses. */
|
|
506
|
+
baseIntegrationId?: string | null;
|
|
847
507
|
status?: 'active' | 'deactivated';
|
|
848
508
|
totalXp?: number | null;
|
|
849
509
|
lastVerifiedAt?: Date | null;
|
|
@@ -1735,6 +1395,370 @@ interface AssessmentAwardRecord extends AssessmentMasteryAward {
|
|
|
1735
1395
|
warnings?: MasteryWriteWarning[];
|
|
1736
1396
|
}
|
|
1737
1397
|
|
|
1398
|
+
/**
|
|
1399
|
+
* User Types
|
|
1400
|
+
*
|
|
1401
|
+
* Enums, DTOs and API response types. Database row types are in @playcademy/data/types.
|
|
1402
|
+
*
|
|
1403
|
+
* @module types/user
|
|
1404
|
+
*/
|
|
1405
|
+
|
|
1406
|
+
type UserRoleEnumType = UserRole;
|
|
1407
|
+
type DeveloperStatusEnumType = 'none' | 'pending' | 'approved';
|
|
1408
|
+
type DeveloperStatusValue = DeveloperStatusEnumType;
|
|
1409
|
+
type TimebackUserRole = 'administrator' | 'aide' | 'guardian' | 'parent' | 'proctor' | 'relative' | 'student' | 'teacher';
|
|
1410
|
+
type TimebackOrgType = 'department' | 'school' | 'district' | 'local' | 'state' | 'national';
|
|
1411
|
+
interface UserEnrollment {
|
|
1412
|
+
gameId?: string;
|
|
1413
|
+
courseId: string;
|
|
1414
|
+
id?: string;
|
|
1415
|
+
grade: number;
|
|
1416
|
+
subject: string;
|
|
1417
|
+
orgId?: string;
|
|
1418
|
+
/**
|
|
1419
|
+
* `base` for the game's grade courses, `hole_filling` for a remediation
|
|
1420
|
+
* course Timeback enrolled the student in. Absent means `base`.
|
|
1421
|
+
*/
|
|
1422
|
+
kind?: GameTimebackIntegrationKind;
|
|
1423
|
+
/** For hole-filling enrollments: the course ID of the base course it belongs to. */
|
|
1424
|
+
baseCourseId?: string;
|
|
1425
|
+
/** For hole-filling enrollments: when the enrollment started (ISO 8601). */
|
|
1426
|
+
beginDate?: string | null;
|
|
1427
|
+
/**
|
|
1428
|
+
* For hole-filling enrollments: enrollment metadata as set by Timeback
|
|
1429
|
+
* (e.g. `assignmentId`, `testType`). Every field is optional.
|
|
1430
|
+
*/
|
|
1431
|
+
enrollmentMetadata?: Record<string, unknown>;
|
|
1432
|
+
}
|
|
1433
|
+
interface UserOrganization {
|
|
1434
|
+
id: string;
|
|
1435
|
+
name: string | null;
|
|
1436
|
+
type: TimebackOrgType | string;
|
|
1437
|
+
isPrimary: boolean;
|
|
1438
|
+
}
|
|
1439
|
+
interface TimebackStudentProfile {
|
|
1440
|
+
role: TimebackUserRole;
|
|
1441
|
+
organizations: UserOrganization[];
|
|
1442
|
+
}
|
|
1443
|
+
/**
|
|
1444
|
+
* Timeback identity on `/users/me`: who the student is, without enrollments.
|
|
1445
|
+
* Enrollments come from `/timeback/user` (or `client.timeback.user`).
|
|
1446
|
+
*/
|
|
1447
|
+
interface UserTimebackProfile extends TimebackStudentProfile {
|
|
1448
|
+
id: string;
|
|
1449
|
+
}
|
|
1450
|
+
interface UserTimebackData extends UserTimebackProfile {
|
|
1451
|
+
enrollments: UserEnrollment[];
|
|
1452
|
+
}
|
|
1453
|
+
type LocalDaySource = 'browser_last_seen' | 'platform_default';
|
|
1454
|
+
interface LocalDayContext {
|
|
1455
|
+
timeZone: string;
|
|
1456
|
+
source: LocalDaySource;
|
|
1457
|
+
/** ISO 8601 timestamp for the last persisted timezone observation. */
|
|
1458
|
+
observedAt: string | null;
|
|
1459
|
+
}
|
|
1460
|
+
/**
|
|
1461
|
+
* OpenID Connect UserInfo claims (NOT a database row).
|
|
1462
|
+
*/
|
|
1463
|
+
interface UserInfo {
|
|
1464
|
+
sub: string;
|
|
1465
|
+
email: string;
|
|
1466
|
+
name: string | null;
|
|
1467
|
+
email_verified?: boolean;
|
|
1468
|
+
given_name?: string;
|
|
1469
|
+
family_name?: string;
|
|
1470
|
+
issuer?: string;
|
|
1471
|
+
lti_roles?: unknown;
|
|
1472
|
+
lti_context?: unknown;
|
|
1473
|
+
lti_resource_link?: unknown;
|
|
1474
|
+
timeback_id?: string;
|
|
1475
|
+
}
|
|
1476
|
+
interface DeveloperStatusResponse {
|
|
1477
|
+
status: DeveloperStatusEnumType;
|
|
1478
|
+
}
|
|
1479
|
+
interface DemoProfile {
|
|
1480
|
+
displayName: string;
|
|
1481
|
+
isDefault: boolean;
|
|
1482
|
+
}
|
|
1483
|
+
/**
|
|
1484
|
+
* Update shape for `client.demo.profile.update(...)`.
|
|
1485
|
+
*
|
|
1486
|
+
* Kept as a named type so callers typed against it pick up new fields
|
|
1487
|
+
* automatically, but `displayName` is the only updatable field today and
|
|
1488
|
+
* the server's `DemoProfileSchema` requires it — a no-field payload would
|
|
1489
|
+
* 400 at runtime, so we model that at the type level too. When additional
|
|
1490
|
+
* fields land, make them required/optional individually based on server
|
|
1491
|
+
* validation, rather than blanket-optional.
|
|
1492
|
+
*/
|
|
1493
|
+
interface DemoProfileUpdate {
|
|
1494
|
+
displayName: string;
|
|
1495
|
+
}
|
|
1496
|
+
/**
|
|
1497
|
+
* Authenticated user for API responses.
|
|
1498
|
+
* Differs from UserRow: omits timebackId, adds hasTimebackAccount and timeback.
|
|
1499
|
+
*/
|
|
1500
|
+
interface AuthenticatedUser {
|
|
1501
|
+
id: string;
|
|
1502
|
+
email: string;
|
|
1503
|
+
emailVerified: boolean;
|
|
1504
|
+
name: string | null;
|
|
1505
|
+
image: string | null;
|
|
1506
|
+
username: string | null;
|
|
1507
|
+
role: UserRoleEnumType;
|
|
1508
|
+
developerStatus: DeveloperStatusEnumType;
|
|
1509
|
+
createdAt: Date;
|
|
1510
|
+
updatedAt: Date;
|
|
1511
|
+
hasTimebackAccount: boolean;
|
|
1512
|
+
localDay: LocalDayContext;
|
|
1513
|
+
timeback?: UserTimebackProfile;
|
|
1514
|
+
}
|
|
1515
|
+
interface GameUser {
|
|
1516
|
+
id: string;
|
|
1517
|
+
name: string | null;
|
|
1518
|
+
role: UserRoleEnumType;
|
|
1519
|
+
username: string | null;
|
|
1520
|
+
email: string | null;
|
|
1521
|
+
localDay: LocalDayContext;
|
|
1522
|
+
timeback?: UserTimebackProfile;
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
/**
|
|
1526
|
+
* Game Types
|
|
1527
|
+
*
|
|
1528
|
+
* Literal types and API DTOs. Database row types are in @playcademy/data/types.
|
|
1529
|
+
*
|
|
1530
|
+
* @module types/game
|
|
1531
|
+
*/
|
|
1532
|
+
|
|
1533
|
+
type GameType = 'hosted' | 'external';
|
|
1534
|
+
type GamePlatform = 'web' | 'godot' | (string & {});
|
|
1535
|
+
/**
|
|
1536
|
+
* Game manifest file format (manifest.json).
|
|
1537
|
+
* Note: createdAt is a string here because it's parsed from JSON file.
|
|
1538
|
+
*/
|
|
1539
|
+
interface ManifestV1 {
|
|
1540
|
+
version: '1';
|
|
1541
|
+
platform: string;
|
|
1542
|
+
createdAt: string;
|
|
1543
|
+
}
|
|
1544
|
+
interface ManifestVersions {
|
|
1545
|
+
vitePlugin?: string;
|
|
1546
|
+
sdk?: string;
|
|
1547
|
+
cli?: string;
|
|
1548
|
+
vite?: string;
|
|
1549
|
+
godotSdk?: string;
|
|
1550
|
+
godot?: string;
|
|
1551
|
+
}
|
|
1552
|
+
interface ManifestV2 {
|
|
1553
|
+
version: '2';
|
|
1554
|
+
platform: string;
|
|
1555
|
+
createdAt: string;
|
|
1556
|
+
gameVersion?: string;
|
|
1557
|
+
versions: ManifestVersions;
|
|
1558
|
+
}
|
|
1559
|
+
type GameManifest = ManifestV1 | ManifestV2;
|
|
1560
|
+
/**
|
|
1561
|
+
* Git provenance of the working tree a deploy was run from (PLA-294).
|
|
1562
|
+
*
|
|
1563
|
+
* Captured by the CLI at deploy time and persisted verbatim on the deploy
|
|
1564
|
+
* job, so "which commit is running" is answerable from the platform record.
|
|
1565
|
+
* Every field is nullable: a tree with no git, no remote, or a detached HEAD
|
|
1566
|
+
* is recorded honestly rather than refused. Recorded per deploy, never
|
|
1567
|
+
* pinned to the game — a fork deployed under the same slug shows the fork.
|
|
1568
|
+
*/
|
|
1569
|
+
interface DeploySource {
|
|
1570
|
+
/** Normalized `https://github.com/<org>/<repo>`; null when no remote or unparseable */
|
|
1571
|
+
repository: string | null;
|
|
1572
|
+
/** Full 40-char SHA of HEAD; null when not in a git tree */
|
|
1573
|
+
commit: string | null;
|
|
1574
|
+
/** Current branch name; null on detached HEAD outside CI */
|
|
1575
|
+
branch: string | null;
|
|
1576
|
+
/** True when the tree had uncommitted changes — the SHA does not describe what shipped */
|
|
1577
|
+
dirty: boolean;
|
|
1578
|
+
/**
|
|
1579
|
+
* True when the uploaded build artifact lives outside the git tree the
|
|
1580
|
+
* other fields describe (`--build ~/Downloads/game.zip`). The commit is
|
|
1581
|
+
* still the deployer's tree, but it may not be what produced the artifact.
|
|
1582
|
+
*/
|
|
1583
|
+
externalBuild: boolean;
|
|
1584
|
+
}
|
|
1585
|
+
/**
|
|
1586
|
+
* One successful deploy of a game's worker, as the open releases surface
|
|
1587
|
+
* describes it: what is running and which commit produced it.
|
|
1588
|
+
*
|
|
1589
|
+
* This shape is parsed by external automation. Every field is present on
|
|
1590
|
+
* every entry — nullable, never absent — and nothing from the deploy job
|
|
1591
|
+
* beyond its git provenance and completion time is exposed. No deployer
|
|
1592
|
+
* identity, no schema state, no secrets manifest.
|
|
1593
|
+
*/
|
|
1594
|
+
interface Release {
|
|
1595
|
+
slug: string;
|
|
1596
|
+
/** Where the game is served; null when the platform has no URL for it */
|
|
1597
|
+
url: string | null;
|
|
1598
|
+
/**
|
|
1599
|
+
* Git provenance the deploying CLI captured. Null for deploys that
|
|
1600
|
+
* predate provenance capture, so the fleet list is complete on day one
|
|
1601
|
+
* and a consumer can tell "unknown" from "not in git".
|
|
1602
|
+
*/
|
|
1603
|
+
source: DeploySource | null;
|
|
1604
|
+
/** When the deploy succeeded, ISO 8601 */
|
|
1605
|
+
deployedAt: string;
|
|
1606
|
+
}
|
|
1607
|
+
/** GET /api/releases — the current release of every game */
|
|
1608
|
+
interface ReleasesResponse {
|
|
1609
|
+
releases: Release[];
|
|
1610
|
+
}
|
|
1611
|
+
/** GET /api/releases/:slug — one game's current release and, on request, its recent history */
|
|
1612
|
+
interface GameReleaseResponse {
|
|
1613
|
+
/** The current release; null when the game has never deployed successfully */
|
|
1614
|
+
current: Release | null;
|
|
1615
|
+
/**
|
|
1616
|
+
* The last N successful deploys, newest first, current included. Only
|
|
1617
|
+
* present when `?history=N` was requested.
|
|
1618
|
+
*/
|
|
1619
|
+
history?: Release[];
|
|
1620
|
+
}
|
|
1621
|
+
interface DomainValidationRecords {
|
|
1622
|
+
ownership?: {
|
|
1623
|
+
name?: string;
|
|
1624
|
+
value?: string;
|
|
1625
|
+
type?: string;
|
|
1626
|
+
};
|
|
1627
|
+
ssl?: {
|
|
1628
|
+
txt_name?: string;
|
|
1629
|
+
txt_value?: string;
|
|
1630
|
+
}[];
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
/**
|
|
1634
|
+
* Leaderboard Types
|
|
1635
|
+
*
|
|
1636
|
+
* @module types/leaderboard
|
|
1637
|
+
*/
|
|
1638
|
+
type LeaderboardTimeframe = 'all_time' | 'monthly' | 'weekly' | 'daily';
|
|
1639
|
+
interface LeaderboardOptions {
|
|
1640
|
+
timeframe?: LeaderboardTimeframe;
|
|
1641
|
+
limit?: number;
|
|
1642
|
+
offset?: number;
|
|
1643
|
+
gameId?: string;
|
|
1644
|
+
}
|
|
1645
|
+
interface LeaderboardEntry {
|
|
1646
|
+
rank: number;
|
|
1647
|
+
userId: string;
|
|
1648
|
+
username: string;
|
|
1649
|
+
userImage?: string | null;
|
|
1650
|
+
score: number;
|
|
1651
|
+
achievedAt: Date;
|
|
1652
|
+
metadata?: Record<string, unknown>;
|
|
1653
|
+
gameId?: string;
|
|
1654
|
+
gameTitle?: string;
|
|
1655
|
+
gameSlug?: string;
|
|
1656
|
+
}
|
|
1657
|
+
interface UserRank {
|
|
1658
|
+
rank: number;
|
|
1659
|
+
totalPlayers: number;
|
|
1660
|
+
score: number;
|
|
1661
|
+
percentile: number;
|
|
1662
|
+
}
|
|
1663
|
+
interface UserRankResponse {
|
|
1664
|
+
rank: number;
|
|
1665
|
+
score: number;
|
|
1666
|
+
userId: string;
|
|
1667
|
+
}
|
|
1668
|
+
interface UserScore {
|
|
1669
|
+
id: string;
|
|
1670
|
+
score: number;
|
|
1671
|
+
achievedAt: Date;
|
|
1672
|
+
metadata?: Record<string, unknown>;
|
|
1673
|
+
gameId: string;
|
|
1674
|
+
gameTitle: string;
|
|
1675
|
+
gameSlug: string;
|
|
1676
|
+
}
|
|
1677
|
+
/**
|
|
1678
|
+
* Leaderboard entry with required game context.
|
|
1679
|
+
* Used when fetching leaderboards for a specific game.
|
|
1680
|
+
*/
|
|
1681
|
+
interface GameLeaderboardEntry {
|
|
1682
|
+
rank: number;
|
|
1683
|
+
userId: string;
|
|
1684
|
+
username: string;
|
|
1685
|
+
userImage?: string | null;
|
|
1686
|
+
score: number;
|
|
1687
|
+
achievedAt: Date;
|
|
1688
|
+
metadata?: Record<string, unknown>;
|
|
1689
|
+
gameId: string;
|
|
1690
|
+
gameTitle: string;
|
|
1691
|
+
gameSlug: string;
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
/**
|
|
1695
|
+
* Skill Status Types
|
|
1696
|
+
*
|
|
1697
|
+
* Contract for cross-game skill status queries. An answering game exports
|
|
1698
|
+
* `getSkillStatus` from `server/lib/skills.ts`; the platform calls its
|
|
1699
|
+
* reserved `/__playcademy/skills/status` route on behalf of another game.
|
|
1700
|
+
*
|
|
1701
|
+
* @module types/skills
|
|
1702
|
+
*/
|
|
1703
|
+
|
|
1704
|
+
/**
|
|
1705
|
+
* Current status of one skill for one student, as judged by the answering game.
|
|
1706
|
+
*
|
|
1707
|
+
* - `locked`: the student can't work on the skill yet in this game.
|
|
1708
|
+
* - `unlocked`: available to the student, but no progress yet.
|
|
1709
|
+
* - `in_progress`: started, not mastered.
|
|
1710
|
+
* - `mastered`: currently meets this game's mastery bar, including by placement.
|
|
1711
|
+
* - `unknown_skill`: the skill ID isn't recognized. Never report an unrecognized
|
|
1712
|
+
* skill as `locked`.
|
|
1713
|
+
*/
|
|
1714
|
+
type SkillStatus = (typeof SKILL_STATUSES)[number];
|
|
1715
|
+
/** How a `mastered` status was reached. */
|
|
1716
|
+
type SkillMasterySource = (typeof SKILL_MASTERY_SOURCES)[number];
|
|
1717
|
+
interface SkillStatusResult {
|
|
1718
|
+
/** Skill ID, lowercase. */
|
|
1719
|
+
skill: string;
|
|
1720
|
+
/** Current status. */
|
|
1721
|
+
status: SkillStatus;
|
|
1722
|
+
/** How mastery was reached. Only allowed when `status` is `mastered`. */
|
|
1723
|
+
source?: SkillMasterySource;
|
|
1724
|
+
/** ISO 8601 time of the evidence behind this status. */
|
|
1725
|
+
asOf?: string;
|
|
1726
|
+
}
|
|
1727
|
+
/** Response returned by an answering game's `getSkillStatus` handler. */
|
|
1728
|
+
interface SkillStatusResponse {
|
|
1729
|
+
/** Student identifier (Playcademy user ID). Must match the requested student. */
|
|
1730
|
+
studentId: string;
|
|
1731
|
+
/** One result per skill the handler was asked about. */
|
|
1732
|
+
results: SkillStatusResult[];
|
|
1733
|
+
}
|
|
1734
|
+
/** The game asking for skill status, as identified by the platform. */
|
|
1735
|
+
interface SkillStatusRequestingGame {
|
|
1736
|
+
id: string;
|
|
1737
|
+
slug: string;
|
|
1738
|
+
}
|
|
1739
|
+
/** Body of `POST /api/skills/status`, sent by the asking game's worker. */
|
|
1740
|
+
interface SkillStatusRequest {
|
|
1741
|
+
/** Slug of the game to ask. */
|
|
1742
|
+
game: string;
|
|
1743
|
+
/** Playcademy user ID of the student. */
|
|
1744
|
+
studentId: string;
|
|
1745
|
+
/** Skill IDs to ask about. Normalized (trimmed, lowercased) and de-duplicated. */
|
|
1746
|
+
skills: string[];
|
|
1747
|
+
}
|
|
1748
|
+
/** Why the answering game couldn't answer. */
|
|
1749
|
+
type SkillStatusUnsupportedReason = 'not_implemented' | 'no_active_deployment' | 'timeout' | 'fetch_failed' | 'invalid_response';
|
|
1750
|
+
/**
|
|
1751
|
+
* What the asking game gets back. When `supported` is false, fall back to
|
|
1752
|
+
* the game's own default (usually: treat the skills as not mastered).
|
|
1753
|
+
*/
|
|
1754
|
+
type SkillStatusLookupResult = {
|
|
1755
|
+
supported: true;
|
|
1756
|
+
results: SkillStatusResult[];
|
|
1757
|
+
} | {
|
|
1758
|
+
supported: false;
|
|
1759
|
+
reason: SkillStatusUnsupportedReason;
|
|
1760
|
+
};
|
|
1761
|
+
|
|
1738
1762
|
/**
|
|
1739
1763
|
* Base error class for Cademy SDK specific errors.
|
|
1740
1764
|
*/
|