@playcademy/vite-plugin 1.1.1-beta.2 → 1.1.1-beta.4

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.
Files changed (2) hide show
  1. package/dist/index.js +1781 -1188
  2. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -23746,7 +23746,7 @@ import path from "node:path";
23746
23746
  // package.json
23747
23747
  var package_default = {
23748
23748
  name: "@playcademy/vite-plugin",
23749
- version: "1.1.1-beta.2",
23749
+ version: "1.1.1-beta.4",
23750
23750
  type: "module",
23751
23751
  exports: {
23752
23752
  ".": {
@@ -24244,6 +24244,7 @@ import { join as join2 } from "path";
24244
24244
  import { Buffer as Buffer2 } from "node:buffer";
24245
24245
  import { gzipSync } from "node:zlib";
24246
24246
  import { stdout } from "process";
24247
+ import { createHash } from "node:crypto";
24247
24248
  import { AsyncLocalStorage } from "node:async_hooks";
24248
24249
  import crypto3 from "node:crypto";
24249
24250
  import * as s3 from "fs";
@@ -24449,7 +24450,8 @@ var DEMO_DISPLAY_NAME_PLACEHOLDER = "Demo Player";
24449
24450
  var init_auth = __esm(() => {
24450
24451
  AUTH_PROVIDER_IDS = {
24451
24452
  TIMEBACK: "timeback",
24452
- TIMEBACK_LTI: "timeback-lti"
24453
+ TIMEBACK_LTI: "timeback-lti",
24454
+ PLAYCADEMY: "playcademy"
24453
24455
  };
24454
24456
  });
24455
24457
  var init_typescript = () => {};
@@ -25342,7 +25344,7 @@ var package_default2;
25342
25344
  var init_package = __esm(() => {
25343
25345
  package_default2 = {
25344
25346
  name: "@playcademy/sandbox",
25345
- version: "0.5.2-beta.2",
25347
+ version: "0.5.1",
25346
25348
  description: "Local development server for Playcademy game development",
25347
25349
  type: "module",
25348
25350
  exports: {
@@ -35608,25 +35610,34 @@ var init_table6 = __esm(() => {
35608
35610
  })
35609
35611
  }));
35610
35612
  });
35613
+ var gameTimebackIntegrationStatusEnum;
35611
35614
  var gameTimebackIntegrations;
35612
35615
  var gameTimebackAssessmentTests;
35613
35616
  var gameTimebackMetricDiscrepancyVerifications;
35614
35617
  var init_table7 = __esm(() => {
35618
+ init_drizzle_orm();
35615
35619
  init_pg_core();
35616
35620
  init_table5();
35617
35621
  init_table3();
35622
+ gameTimebackIntegrationStatusEnum = pgEnum("game_timeback_integration_status", [
35623
+ "active",
35624
+ "deactivated"
35625
+ ]);
35618
35626
  gameTimebackIntegrations = pgTable("game_timeback_integrations", {
35619
35627
  id: uuid("id").primaryKey().defaultRandom(),
35620
35628
  gameId: uuid("game_id").notNull().references(() => games.id, { onDelete: "cascade" }),
35621
35629
  courseId: text("course_id").notNull(),
35622
35630
  grade: integer("grade").notNull(),
35623
35631
  subject: text("subject").notNull(),
35632
+ status: gameTimebackIntegrationStatusEnum("status"),
35624
35633
  totalXp: integer("total_xp"),
35625
35634
  lastVerifiedAt: timestamp("last_verified_at", { withTimezone: true }),
35635
+ deactivatedAt: timestamp("deactivated_at", { withTimezone: true }),
35636
+ reactivatedAt: timestamp("reactivated_at", { withTimezone: true }),
35626
35637
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
35627
35638
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
35628
35639
  }, (table3) => [
35629
- uniqueIndex("game_timeback_integrations_game_grade_subject_idx").on(table3.gameId, table3.grade, table3.subject)
35640
+ uniqueIndex("game_timeback_integrations_game_grade_subject_idx").on(table3.gameId, table3.grade, table3.subject).where(sql`${table3.status} IS DISTINCT FROM 'deactivated'`)
35630
35641
  ]);
35631
35642
  gameTimebackAssessmentTests = pgTable("game_timeback_assessment_tests", {
35632
35643
  id: uuid("id").primaryKey().defaultRandom(),
@@ -35667,6 +35678,7 @@ __export(exports_tables_index, {
35667
35678
  gameTypeEnum: () => gameTypeEnum,
35668
35679
  gameTimebackMetricDiscrepancyVerifications: () => gameTimebackMetricDiscrepancyVerifications,
35669
35680
  gameTimebackIntegrations: () => gameTimebackIntegrations,
35681
+ gameTimebackIntegrationStatusEnum: () => gameTimebackIntegrationStatusEnum,
35670
35682
  gameTimebackAssessmentTests: () => gameTimebackAssessmentTests,
35671
35683
  gameScoresRelations: () => gameScoresRelations,
35672
35684
  gameScores: () => gameScores,
@@ -48677,6 +48689,7 @@ var init_helpers = __esm(() => {
48677
48689
  init_mime();
48678
48690
  });
48679
48691
  var init_provider = __esm(() => {
48692
+ init_src();
48680
48693
  init_src();
48681
48694
  init_core();
48682
48695
  init_constants2();
@@ -51748,21 +51761,626 @@ var init_game_member_service = __esm(() => {
51748
51761
  init_spans();
51749
51762
  init_errors();
51750
51763
  });
51764
+ function isActiveGameTimebackIntegrationStatus(statusColumn = gameTimebackIntegrations.status) {
51765
+ return sql`${statusColumn} IS DISTINCT FROM ${DEACTIVATED_GAME_TIMEBACK_INTEGRATION_STATUS}`;
51766
+ }
51767
+ var ACTIVE_GAME_TIMEBACK_INTEGRATION_STATUS = "active";
51768
+ var DEACTIVATED_GAME_TIMEBACK_INTEGRATION_STATUS = "deactivated";
51769
+ var init_helpers2 = __esm(() => {
51770
+ init_drizzle_orm();
51771
+ init_table7();
51772
+ });
51773
+ var init_helpers_index = __esm(() => {
51774
+ init_helpers2();
51775
+ });
51751
51776
  function sleep(ms) {
51752
51777
  if (ms <= 0) {
51753
51778
  return Promise.resolve();
51754
51779
  }
51755
51780
  return new Promise((resolve2) => setTimeout(resolve2, ms));
51756
51781
  }
51782
+ function isObject(value) {
51783
+ return typeof value === "object" && value !== null;
51784
+ }
51785
+ function isCourseMetadata(value) {
51786
+ return isObject(value);
51787
+ }
51788
+ function isResourceMetadata(value) {
51789
+ return isObject(value);
51790
+ }
51791
+ function isPlaycademyResourceMetadata(value) {
51792
+ if (!isObject(value)) {
51793
+ return false;
51794
+ }
51795
+ if (!("mastery" in value) || value.mastery === undefined) {
51796
+ return true;
51797
+ }
51798
+ return isObject(value.mastery);
51799
+ }
51800
+ function isTimebackSubject(value) {
51801
+ return typeof value === "string" && SUBJECT_VALUES.includes(value);
51802
+ }
51803
+ function isTimebackGrade(value) {
51804
+ return typeof value === "number" && Number.isInteger(value) && GRADE_VALUES.includes(value);
51805
+ }
51806
+ var __esm2 = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
51807
+ var TIMEBACK_API_URLS;
51808
+ var TIMEBACK_AUTH_URLS;
51809
+ var CALIPER_API_URLS;
51810
+ var ONEROSTER_ENDPOINTS;
51811
+ var QTI_ENDPOINTS;
51812
+ var CALIPER_ENDPOINTS;
51813
+ var CALIPER_CONSTANTS;
51814
+ var TIMEBACK_EVENT_TYPES;
51815
+ var TIMEBACK_ACTIONS;
51816
+ var TIMEBACK_TYPES;
51817
+ var ACTIVITY_METRIC_TYPES;
51818
+ var TIME_METRIC_TYPES;
51819
+ var TIMEBACK_SUBJECTS;
51820
+ var TIMEBACK_GRADE_LEVELS;
51821
+ var TIMEBACK_GRADE_LEVEL_LABELS;
51822
+ var CALIPER_SUBJECTS;
51823
+ var ONEROSTER_STATUS;
51824
+ var SCORE_STATUS;
51825
+ var ENV_VARS;
51826
+ var HTTP_DEFAULTS;
51827
+ var AUTH_DEFAULTS;
51828
+ var CACHE_DEFAULTS;
51829
+ var CONFIG_DEFAULTS;
51830
+ var PLAYCADEMY_DEFAULTS;
51831
+ var RESOURCE_DEFAULTS;
51832
+ var HTTP_STATUS;
51833
+ var ERROR_NAMES;
51834
+ var init_constants3;
51835
+ var SUBJECT_VALUES;
51836
+ var GRADE_VALUES;
51837
+ var init_types2 = __esm(() => {
51838
+ init_src();
51839
+ init_constants3 = __esm2(() => {
51840
+ TIMEBACK_API_URLS = {
51841
+ production: "https://api.alpha-1edtech.ai",
51842
+ staging: "https://api.staging.alpha-1edtech.com"
51843
+ };
51844
+ TIMEBACK_AUTH_URLS = {
51845
+ production: "https://prod-beyond-timeback-api-2-idp.auth.us-east-1.amazoncognito.com",
51846
+ staging: "https://alpha-auth-development-idp.auth.us-west-2.amazoncognito.com"
51847
+ };
51848
+ CALIPER_API_URLS = {
51849
+ production: "https://caliper.alpha-1edtech.ai",
51850
+ staging: "https://caliper-staging.alpha-1edtech.com"
51851
+ };
51852
+ ONEROSTER_ENDPOINTS = {
51853
+ organizations: "/ims/oneroster/rostering/v1p2/orgs",
51854
+ courses: "/ims/oneroster/rostering/v1p2/courses",
51855
+ courseComponents: "/ims/oneroster/rostering/v1p2/courses/components",
51856
+ resources: "/ims/oneroster/resources/v1p2/resources",
51857
+ componentResources: "/ims/oneroster/rostering/v1p2/courses/component-resources",
51858
+ classes: "/ims/oneroster/rostering/v1p2/classes",
51859
+ enrollments: "/ims/oneroster/rostering/v1p2/enrollments",
51860
+ assessmentLineItems: "/ims/oneroster/gradebook/v1p2/assessmentLineItems",
51861
+ assessmentResults: "/ims/oneroster/gradebook/v1p2/assessmentResults",
51862
+ users: "/ims/oneroster/rostering/v1p2/users"
51863
+ };
51864
+ QTI_ENDPOINTS = {
51865
+ assessmentTests: "/assessment-tests",
51866
+ assessmentItems: "/assessment-items"
51867
+ };
51868
+ CALIPER_ENDPOINTS = {
51869
+ event: "/caliper/event",
51870
+ events: "/caliper/events",
51871
+ validate: "/caliper/event/validate"
51872
+ };
51873
+ CALIPER_CONSTANTS = {
51874
+ context: "http://purl.imsglobal.org/ctx/caliper/v1p2",
51875
+ profile: "TimebackProfile",
51876
+ dataVersion: "http://purl.imsglobal.org/ctx/caliper/v1p2"
51877
+ };
51878
+ TIMEBACK_EVENT_TYPES = {
51879
+ activityEvent: "ActivityEvent",
51880
+ timeSpentEvent: "TimeSpentEvent"
51881
+ };
51882
+ TIMEBACK_ACTIONS = {
51883
+ completed: "Completed",
51884
+ spentTime: "SpentTime"
51885
+ };
51886
+ TIMEBACK_TYPES = {
51887
+ user: "TimebackUser",
51888
+ activityContext: "TimebackActivityContext",
51889
+ activityMetricsCollection: "TimebackActivityMetricsCollection",
51890
+ timeSpentMetricsCollection: "TimebackTimeSpentMetricsCollection"
51891
+ };
51892
+ ACTIVITY_METRIC_TYPES = {
51893
+ totalQuestions: "totalQuestions",
51894
+ correctQuestions: "correctQuestions",
51895
+ xpEarned: "xpEarned",
51896
+ masteredUnits: "masteredUnits"
51897
+ };
51898
+ TIME_METRIC_TYPES = {
51899
+ active: "active",
51900
+ inactive: "inactive",
51901
+ waste: "waste",
51902
+ unknown: "unknown",
51903
+ antiPattern: "anti-pattern"
51904
+ };
51905
+ TIMEBACK_SUBJECTS = [
51906
+ "Math",
51907
+ "FastMath",
51908
+ "Science",
51909
+ "Social Studies",
51910
+ "Language",
51911
+ "Reading",
51912
+ "Vocabulary",
51913
+ "Writing"
51914
+ ];
51915
+ TIMEBACK_GRADE_LEVELS = [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
51916
+ TIMEBACK_GRADE_LEVEL_LABELS = {
51917
+ "-1": "pre-k",
51918
+ "0": "kindergarten",
51919
+ "1": "1st grade",
51920
+ "2": "2nd grade",
51921
+ "3": "3rd grade",
51922
+ "4": "4th grade",
51923
+ "5": "5th grade",
51924
+ "6": "6th grade",
51925
+ "7": "7th grade",
51926
+ "8": "8th grade",
51927
+ "9": "9th grade",
51928
+ "10": "10th grade",
51929
+ "11": "11th grade",
51930
+ "12": "12th grade",
51931
+ "13": "AP"
51932
+ };
51933
+ CALIPER_SUBJECTS = {
51934
+ Reading: "Reading",
51935
+ Language: "Language",
51936
+ Vocabulary: "Vocabulary",
51937
+ SocialStudies: "Social Studies",
51938
+ Writing: "Writing",
51939
+ Science: "Science",
51940
+ FastMath: "FastMath",
51941
+ Math: "Math",
51942
+ None: "None"
51943
+ };
51944
+ ONEROSTER_STATUS = {
51945
+ active: "active",
51946
+ toBeDeleted: "tobedeleted"
51947
+ };
51948
+ SCORE_STATUS = {
51949
+ exempt: "exempt",
51950
+ fullyGraded: "fully graded",
51951
+ notSubmitted: "not submitted",
51952
+ partiallyGraded: "partially graded",
51953
+ submitted: "submitted"
51954
+ };
51955
+ ENV_VARS = {
51956
+ clientId: "TIMEBACK_CLIENT_ID",
51957
+ clientSecret: "TIMEBACK_CLIENT_SECRET",
51958
+ baseUrl: "TIMEBACK_BASE_URL",
51959
+ environment: "TIMEBACK_ENVIRONMENT",
51960
+ vendorResourceId: "TIMEBACK_VENDOR_RESOURCE_ID",
51961
+ launchBaseUrl: "GAME_URL",
51962
+ qtiClientId: "QTI_CLIENT_ID",
51963
+ qtiClientSecret: "QTI_CLIENT_SECRET",
51964
+ qtiAuthUrl: "QTI_AUTH_URL"
51965
+ };
51966
+ HTTP_DEFAULTS = {
51967
+ timeout: 30000,
51968
+ retries: 3,
51969
+ retryBackoffBase: 2
51970
+ };
51971
+ AUTH_DEFAULTS = {
51972
+ tokenCacheDuration: 50000
51973
+ };
51974
+ CACHE_DEFAULTS = {
51975
+ defaultTTL: 600000,
51976
+ defaultMaxSize: 500,
51977
+ defaultName: "TimebackCache",
51978
+ studentTTL: 600000,
51979
+ studentMaxSize: 500,
51980
+ assessmentTTL: 1800000,
51981
+ assessmentMaxSize: 200,
51982
+ enrollmentTTL: 5000,
51983
+ enrollmentMaxSize: 100
51984
+ };
51985
+ CONFIG_DEFAULTS = {
51986
+ fileNames: ["timeback.config.js", "timeback.config.json"]
51987
+ };
51988
+ PLAYCADEMY_DEFAULTS = {
51989
+ organization: TIMEBACK_ORG_SOURCED_ID,
51990
+ launchBaseUrls: PLAYCADEMY_BASE_URLS
51991
+ };
51992
+ RESOURCE_DEFAULTS = {
51993
+ organization: {
51994
+ name: TIMEBACK_ORG_NAME,
51995
+ type: TIMEBACK_ORG_TYPE
51996
+ },
51997
+ course: {
51998
+ gradingScheme: TIMEBACK_COURSE_DEFAULTS.gradingScheme,
51999
+ level: TIMEBACK_COURSE_DEFAULTS.level,
52000
+ metadata: {
52001
+ goals: TIMEBACK_COURSE_DEFAULTS.goals,
52002
+ metrics: TIMEBACK_COURSE_DEFAULTS.metrics
52003
+ }
52004
+ },
52005
+ component: TIMEBACK_COMPONENT_DEFAULTS,
52006
+ resource: TIMEBACK_RESOURCE_DEFAULTS,
52007
+ componentResource: TIMEBACK_COMPONENT_RESOURCE_DEFAULTS
52008
+ };
52009
+ HTTP_STATUS = {
52010
+ CLIENT_ERROR_MIN: 400,
52011
+ CLIENT_ERROR_MAX: 500,
52012
+ SERVER_ERROR_MIN: 500
52013
+ };
52014
+ ERROR_NAMES = {
52015
+ timebackAuth: "TimebackAuthError",
52016
+ timebackApi: "TimebackApiError",
52017
+ timebackConfig: "TimebackConfigError",
52018
+ timebackSdk: "TimebackSDKError"
52019
+ };
52020
+ });
52021
+ init_constants3();
52022
+ SUBJECT_VALUES = TIMEBACK_SUBJECTS;
52023
+ GRADE_VALUES = TIMEBACK_GRADE_LEVELS;
52024
+ });
52025
+ function kebabToTitleCase(kebabStr) {
52026
+ return kebabStr.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
52027
+ }
52028
+ function isRecord2(value) {
52029
+ return typeof value === "object" && value !== null;
52030
+ }
52031
+ function filterEnrollmentsByGame(enrollments, gameId) {
52032
+ return enrollments.filter((enrollment) => enrollment.gameId === gameId).map(({ gameId: _2, ...enrollment }) => enrollment);
52033
+ }
52034
+ function mapEnrollmentsToUserEnrollments(enrollments, integrations) {
52035
+ const enrollmentByCourse = new Map(enrollments.map((enrollment) => [enrollment.courseId, enrollment]));
52036
+ const courseToSchool = new Map(enrollments.filter((enrollment) => enrollment.school?.id).map((enrollment) => [enrollment.courseId, enrollment.school.id]));
52037
+ return integrations.map((integration) => {
52038
+ const enrollment = enrollmentByCourse.get(integration.courseId);
52039
+ return {
52040
+ gameId: integration.gameId,
52041
+ grade: integration.grade,
52042
+ subject: integration.subject,
52043
+ courseId: integration.courseId,
52044
+ orgId: courseToSchool.get(integration.courseId),
52045
+ ...enrollment ? { id: enrollment.sourcedId } : {}
52046
+ };
52047
+ });
52048
+ }
52049
+ function buildGameTimebackSummaries(integrations) {
52050
+ const summaries = {};
52051
+ for (const integration of integrations) {
52052
+ const summary = summaries[integration.gameId] ?? {
52053
+ subject: null,
52054
+ hasActiveIntegration: false,
52055
+ hasRemovedIntegration: false
52056
+ };
52057
+ const isRemoved = integration.status === DEACTIVATED_GAME_TIMEBACK_INTEGRATION_STATUS;
52058
+ if (isRemoved) {
52059
+ summary.hasRemovedIntegration = true;
52060
+ } else {
52061
+ summary.hasActiveIntegration = true;
52062
+ summary.subject ??= integration.subject;
52063
+ }
52064
+ summaries[integration.gameId] = summary;
52065
+ }
52066
+ return summaries;
52067
+ }
52068
+ function getStringValue(value) {
52069
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
52070
+ }
52071
+ function parseSourcedIdFromUrl(url) {
52072
+ if (!url) {
52073
+ return;
52074
+ }
52075
+ const trimmed = url.trim().replace(/\/$/, "");
52076
+ if (!trimmed) {
52077
+ return;
52078
+ }
52079
+ const segments = trimmed.split("/");
52080
+ const lastSegment = segments.at(-1);
52081
+ return lastSegment ? decodeURIComponent(lastSegment) : undefined;
52082
+ }
52083
+ function getGeneratedMetricValue(event, type) {
52084
+ const items = event.generated?.items;
52085
+ if (!Array.isArray(items)) {
52086
+ return;
52087
+ }
52088
+ const metric = items.find((item) => item?.type === type);
52089
+ if (!metric) {
52090
+ return;
52091
+ }
52092
+ const value = typeof metric.value === "number" ? metric.value : Number(metric.value);
52093
+ return Number.isFinite(value) ? value : undefined;
52094
+ }
52095
+ function getMergedCaliperExtensions(event) {
52096
+ const objectActivityExtensions = isRecord2(event.object.activity?.extensions) ? event.object.activity.extensions : undefined;
52097
+ const generatedExtensions = isRecord2(event.generated?.extensions) ? event.generated.extensions : undefined;
52098
+ const eventExtensions = isRecord2(event.extensions) ? event.extensions : undefined;
52099
+ return {
52100
+ ...objectActivityExtensions,
52101
+ ...generatedExtensions,
52102
+ ...eventExtensions
52103
+ };
52104
+ }
52105
+ function getPlaycademyMetadata(event) {
52106
+ const extensions = getMergedCaliperExtensions(event);
52107
+ return isRecord2(extensions.playcademy) ? extensions.playcademy : undefined;
52108
+ }
52109
+ function getActivityId(event, playcademy) {
52110
+ const metadataActivityId = getStringValue(playcademy?.activityId);
52111
+ if (metadataActivityId) {
52112
+ return metadataActivityId;
52113
+ }
52114
+ const activityId = getStringValue(event.object.activity?.id);
52115
+ if (activityId) {
52116
+ return activityId;
52117
+ }
52118
+ const objectId = getStringValue(event.object.id);
52119
+ if (!objectId) {
52120
+ return;
52121
+ }
52122
+ const trimmed = objectId.replace(/\/$/, "");
52123
+ const segments = trimmed.split("/");
52124
+ const activityIndex = segments.lastIndexOf("activities");
52125
+ if (activityIndex !== -1 && segments.length >= activityIndex + 3) {
52126
+ const candidate = segments[activityIndex + 2];
52127
+ return candidate ? decodeURIComponent(candidate) : undefined;
52128
+ }
52129
+ return;
52130
+ }
52131
+ function buildResourceMetadata({
52132
+ baseMetadata,
52133
+ subject,
52134
+ grade,
52135
+ totalXp,
52136
+ masterableUnits
52137
+ }) {
52138
+ const normalizedBaseMetadata = isResourceMetadata(baseMetadata) ? baseMetadata : undefined;
52139
+ const metadata2 = {
52140
+ ...normalizedBaseMetadata
52141
+ };
52142
+ metadata2.subject = subject;
52143
+ metadata2.grades = [grade];
52144
+ metadata2.xp = totalXp;
52145
+ if (masterableUnits !== undefined && masterableUnits !== null) {
52146
+ const existingPlaycademy = isPlaycademyResourceMetadata(metadata2.playcademy) ? metadata2.playcademy : undefined;
52147
+ metadata2.playcademy = {
52148
+ ...existingPlaycademy,
52149
+ mastery: {
52150
+ ...existingPlaycademy?.mastery,
52151
+ masterableUnits
52152
+ }
52153
+ };
52154
+ }
52155
+ return metadata2;
52156
+ }
52157
+ function getDurationSecondsFromExtensions(event) {
52158
+ const extensions = getMergedCaliperExtensions(event);
52159
+ const playcademy = isRecord2(extensions.playcademy) ? extensions.playcademy : undefined;
52160
+ const rawValue = extensions.durationSeconds ?? playcademy?.durationSeconds;
52161
+ const value = typeof rawValue === "number" ? rawValue : Number(rawValue);
52162
+ return Number.isFinite(value) ? value : undefined;
52163
+ }
52164
+ function getCanonicalRunId(session2) {
52165
+ const sessionId = getStringValue(session2?.id);
52166
+ if (!sessionId) {
52167
+ return;
52168
+ }
52169
+ return sessionId.replace(/^urn:uuid:/, "");
52170
+ }
52171
+ function getResumeId(event) {
52172
+ const playcademy = getPlaycademyMetadata(event);
52173
+ return getStringValue(playcademy?.resumeId);
52174
+ }
52175
+ function isCaliperRemediationOrCompletionEvent(event) {
52176
+ const playcademy = getPlaycademyMetadata(event);
52177
+ return REMEDIATION_OR_COMPLETION_EVENT_KINDS.has(getStringValue(playcademy?.eventKind) || "");
52178
+ }
52179
+ function groupCaliperEventsByRun(events) {
52180
+ const groups = new Map;
52181
+ for (const event of events) {
52182
+ const objectId = getStringValue(event.object.id) || "unknown-activity";
52183
+ const groupKey = `${objectId}::${getStringValue(event.session?.id) || event.externalId}`;
52184
+ const existing = groups.get(groupKey);
52185
+ if (existing) {
52186
+ existing.push(event);
52187
+ } else {
52188
+ groups.set(groupKey, [event]);
52189
+ }
52190
+ }
52191
+ return groups;
52192
+ }
52193
+ function findCaliperEventGroupContainingExternalId(events, externalId) {
52194
+ const targetExternalId = externalId.trim();
52195
+ if (!targetExternalId) {
52196
+ return;
52197
+ }
52198
+ return [...groupCaliperEventsByRun(events).values()].find((group) => group.some((event) => event.externalId === targetExternalId));
52199
+ }
52200
+ function mapCaliperEventGroupToActivity(events, relevantCourseIds) {
52201
+ if (events.length === 0) {
52202
+ return null;
52203
+ }
52204
+ const sortedEvents = events.toSorted((a, b) => a.eventTime.localeCompare(b.eventTime));
52205
+ const activityEvent = [...sortedEvents].toReversed().find((event) => event.type === "ActivityEvent");
52206
+ const contextSource = activityEvent || sortedEvents.at(-1);
52207
+ if (!contextSource) {
52208
+ return null;
52209
+ }
52210
+ const ctx = parseCaliperEventContext(contextSource, relevantCourseIds);
52211
+ if (!ctx) {
52212
+ return null;
52213
+ }
52214
+ const score = activityEvent !== undefined ? (() => {
52215
+ const totalQuestions = getGeneratedMetricValue(activityEvent, "totalQuestions");
52216
+ const correctQuestions = getGeneratedMetricValue(activityEvent, "correctQuestions");
52217
+ if (totalQuestions === undefined || correctQuestions === undefined || totalQuestions <= 0) {
52218
+ return;
52219
+ }
52220
+ return correctQuestions / totalQuestions * 100;
52221
+ })() : undefined;
52222
+ const xpEarned = activityEvent !== undefined ? getGeneratedMetricValue(activityEvent, "xpEarned") : undefined;
52223
+ const masteredUnits = activityEvent !== undefined ? getGeneratedMetricValue(activityEvent, "masteredUnits") : undefined;
52224
+ const timeSpentEvents = sortedEvents.filter((event) => event.type === "TimeSpentEvent");
52225
+ let totalActiveTimeSeconds;
52226
+ if (timeSpentEvents.length > 0) {
52227
+ totalActiveTimeSeconds = timeSpentEvents.reduce((sum, event) => sum + (getGeneratedMetricValue(event, "active") ?? 0), 0);
52228
+ } else if (activityEvent !== undefined) {
52229
+ totalActiveTimeSeconds = getDurationSecondsFromExtensions(activityEvent);
52230
+ }
52231
+ const fallbackActivityId = getActivityId(contextSource, getPlaycademyMetadata(contextSource));
52232
+ const occurredAt = getStringValue(activityEvent?.eventTime) || getStringValue(sortedEvents.at(-1)?.eventTime);
52233
+ const runId = getCanonicalRunId(contextSource.session);
52234
+ const resumeIds = new Set(sortedEvents.map((event) => getResumeId(event)).filter((resumeId) => resumeId !== undefined));
52235
+ const sessionCount = resumeIds.size > 0 ? resumeIds.size : 1;
52236
+ const kind = activityEvent !== undefined ? "activity" : "activity-in-progress";
52237
+ if (!occurredAt) {
52238
+ return null;
52239
+ }
52240
+ return {
52241
+ id: activityEvent?.externalId || sortedEvents.at(-1)?.externalId || events[0].externalId,
52242
+ kind,
52243
+ occurredAt,
52244
+ courseId: ctx.courseId,
52245
+ title: getStringValue(activityEvent?.object.activity?.name) || ctx.titleFromEvent || (fallbackActivityId ? kebabToTitleCase(fallbackActivityId) : "Activity completed"),
52246
+ ...ctx.activityId ? { activityId: ctx.activityId } : {},
52247
+ ...ctx.appName ? { appName: ctx.appName } : {},
52248
+ ...score !== undefined ? { score } : {},
52249
+ ...xpEarned !== undefined ? { xpDelta: xpEarned } : {},
52250
+ ...masteredUnits !== undefined ? { masteredUnitsDelta: masteredUnits } : {},
52251
+ ...totalActiveTimeSeconds !== undefined ? { timeDeltaSeconds: totalActiveTimeSeconds } : {},
52252
+ ...runId ? { runId } : {},
52253
+ ...sessionCount > 0 ? { sessionCount } : {}
52254
+ };
52255
+ }
52256
+ function parseCaliperEventContext(event, relevantCourseIds) {
52257
+ const playcademy = getPlaycademyMetadata(event);
52258
+ const courseId = getStringValue(playcademy?.courseId) || parseSourcedIdFromUrl(event.object.course?.id);
52259
+ if (!courseId || !relevantCourseIds.has(courseId)) {
52260
+ return null;
52261
+ }
52262
+ const occurredAt = getStringValue(event.eventTime);
52263
+ if (!occurredAt) {
52264
+ return null;
52265
+ }
52266
+ return {
52267
+ courseId,
52268
+ occurredAt,
52269
+ eventKind: getStringValue(playcademy?.eventKind),
52270
+ source: getStringValue(playcademy?.source),
52271
+ reason: getStringValue(playcademy?.reason),
52272
+ titleFromEvent: getStringValue(event.object.activity?.name),
52273
+ appName: getStringValue(event.object.app?.name),
52274
+ activityId: getActivityId(event, playcademy)
52275
+ };
52276
+ }
52277
+ function mapTimeSpentRemediation(event, ctx) {
52278
+ if (ctx.eventKind !== "remediation-time") {
52279
+ return null;
52280
+ }
52281
+ return {
52282
+ id: event.externalId,
52283
+ kind: "remediation-time",
52284
+ occurredAt: ctx.occurredAt,
52285
+ courseId: ctx.courseId,
52286
+ title: "Time Adjustment",
52287
+ activityId: ctx.activityId,
52288
+ appName: ctx.appName,
52289
+ reason: ctx.reason,
52290
+ timeDeltaSeconds: getGeneratedMetricValue(event, "active")
52291
+ };
52292
+ }
52293
+ function mapActivityRemediation(event, ctx) {
52294
+ if (ctx.eventKind === "remediation-xp") {
52295
+ return {
52296
+ id: event.externalId,
52297
+ kind: "remediation-xp",
52298
+ occurredAt: ctx.occurredAt,
52299
+ courseId: ctx.courseId,
52300
+ title: "XP Adjustment",
52301
+ activityId: ctx.activityId,
52302
+ appName: ctx.appName,
52303
+ reason: ctx.reason,
52304
+ xpDelta: getGeneratedMetricValue(event, "xpEarned"),
52305
+ masteredUnitsDelta: getGeneratedMetricValue(event, "masteredUnits")
52306
+ };
52307
+ }
52308
+ if (ctx.eventKind === "remediation-mastery") {
52309
+ return {
52310
+ id: event.externalId,
52311
+ kind: "remediation-mastery",
52312
+ occurredAt: ctx.occurredAt,
52313
+ courseId: ctx.courseId,
52314
+ title: "Mastery Adjustment",
52315
+ activityId: ctx.activityId,
52316
+ appName: ctx.appName,
52317
+ reason: ctx.reason,
52318
+ xpDelta: getGeneratedMetricValue(event, "xpEarned"),
52319
+ masteredUnitsDelta: getGeneratedMetricValue(event, "masteredUnits")
52320
+ };
52321
+ }
52322
+ if (ctx.eventKind === "course-completed") {
52323
+ return {
52324
+ id: event.externalId,
52325
+ kind: "course-completed",
52326
+ occurredAt: ctx.occurredAt,
52327
+ courseId: ctx.courseId,
52328
+ title: ctx.source === "admin" ? "Course marked complete" : "Course completed",
52329
+ activityId: ctx.activityId,
52330
+ appName: ctx.appName,
52331
+ reason: ctx.reason
52332
+ };
52333
+ }
52334
+ if (ctx.eventKind === "course-resumed") {
52335
+ return {
52336
+ id: event.externalId,
52337
+ kind: "course-resumed",
52338
+ occurredAt: ctx.occurredAt,
52339
+ courseId: ctx.courseId,
52340
+ title: "Course resumed",
52341
+ activityId: ctx.activityId,
52342
+ appName: ctx.appName,
52343
+ reason: ctx.reason
52344
+ };
52345
+ }
52346
+ return null;
52347
+ }
52348
+ function mapCaliperEventToRemediationActivity(event, relevantCourseIds) {
52349
+ const ctx = parseCaliperEventContext(event, relevantCourseIds);
52350
+ if (!ctx) {
52351
+ return null;
52352
+ }
52353
+ if (event.type === "TimeSpentEvent") {
52354
+ return mapTimeSpentRemediation(event, ctx);
52355
+ }
52356
+ if (event.type === "ActivityEvent") {
52357
+ return mapActivityRemediation(event, ctx);
52358
+ }
52359
+ return null;
52360
+ }
52361
+ var REMEDIATION_OR_COMPLETION_EVENT_KINDS;
52362
+ var init_timeback_util = __esm(() => {
52363
+ init_helpers_index();
52364
+ init_types2();
52365
+ REMEDIATION_OR_COMPLETION_EVENT_KINDS = new Set([
52366
+ "remediation-xp",
52367
+ "remediation-time",
52368
+ "remediation-mastery",
52369
+ "course-completed",
52370
+ "course-resumed"
52371
+ ]);
52372
+ });
51757
52373
  var inFlightManifestFetches;
51758
52374
  var GameService;
51759
52375
  var init_game_service = __esm(() => {
51760
52376
  init_drizzle_orm();
51761
52377
  init_src();
52378
+ init_helpers_index();
51762
52379
  init_tables_index();
51763
52380
  init_spans();
51764
52381
  init_errors();
51765
52382
  init_deployment_util();
52383
+ init_timeback_util();
51766
52384
  inFlightManifestFetches = new Map;
51767
52385
  GameService = class GameService2 {
51768
52386
  deps;
@@ -51906,6 +52524,7 @@ var init_game_service = __esm(() => {
51906
52524
  const db2 = this.deps.db;
51907
52525
  const integrations = await db2.query.gameTimebackIntegrations.findMany({
51908
52526
  columns: { gameId: true, subject: true },
52527
+ where: isActiveGameTimebackIntegrationStatus(),
51909
52528
  orderBy: [asc(gameTimebackIntegrations.createdAt)]
51910
52529
  });
51911
52530
  const subjectMap = {};
@@ -51916,6 +52535,26 @@ var init_game_service = __esm(() => {
51916
52535
  }
51917
52536
  return subjectMap;
51918
52537
  }
52538
+ async getTimebackSummaries(user) {
52539
+ const db2 = this.deps.db;
52540
+ const canSeeAllGames = user.role === "admin" || user.role === "teacher";
52541
+ let accessibleGameIds = null;
52542
+ if (!canSeeAllGames) {
52543
+ const accessibleGames = await this.listAccessible(user);
52544
+ accessibleGameIds = new Set(accessibleGames.map((game2) => game2.id));
52545
+ }
52546
+ if (accessibleGameIds?.size === 0) {
52547
+ return {};
52548
+ }
52549
+ const integrations = await db2.query.gameTimebackIntegrations.findMany({
52550
+ columns: { gameId: true, subject: true, status: true },
52551
+ ...accessibleGameIds && {
52552
+ where: inArray(gameTimebackIntegrations.gameId, [...accessibleGameIds])
52553
+ },
52554
+ orderBy: [asc(gameTimebackIntegrations.createdAt)]
52555
+ });
52556
+ return buildGameTimebackSummaries(integrations);
52557
+ }
51919
52558
  async getById(gameId, caller) {
51920
52559
  const db2 = this.deps.db;
51921
52560
  const game2 = await db2.query.games.findFirst({
@@ -52545,7 +53184,7 @@ class DiscordEmbedBuilder {
52545
53184
  }
52546
53185
  var init_client2 = () => {};
52547
53186
  var DiscordColors;
52548
- var init_types2 = __esm(() => {
53187
+ var init_types3 = __esm(() => {
52549
53188
  DiscordColors = {
52550
53189
  DEFAULT: 0,
52551
53190
  WHITE: 16777215,
@@ -52581,7 +53220,7 @@ var init_types2 = __esm(() => {
52581
53220
  });
52582
53221
  var init_discord = __esm(() => {
52583
53222
  init_client2();
52584
- init_types2();
53223
+ init_types3();
52585
53224
  });
52586
53225
  function truncateField(value, maxLength = DISCORD_FIELD_LIMIT) {
52587
53226
  if (value.length <= maxLength) {
@@ -53702,7 +54341,7 @@ var init_secrets_service = __esm(() => {
53702
54341
  });
53703
54342
  var ASSET_ROUTE_PREFIX = "/api/assets/";
53704
54343
  var ROUTES;
53705
- var init_constants3 = __esm(() => {
54344
+ var init_constants4 = __esm(() => {
53706
54345
  init_src();
53707
54346
  ROUTES = {
53708
54347
  INDEX: "/api",
@@ -53728,7 +54367,7 @@ function prefixSecrets(secrets) {
53728
54367
  }
53729
54368
  var init_setup2 = __esm(() => {
53730
54369
  init_src();
53731
- init_constants3();
54370
+ init_constants4();
53732
54371
  });
53733
54372
 
53734
54373
  class SeedService {
@@ -54469,11 +55108,11 @@ var init_schemas3 = __esm(() => {
54469
55108
  metadata: exports_external.record(exports_external.string(), exports_external.unknown()).optional()
54470
55109
  });
54471
55110
  });
54472
- function isTimebackGrade(value) {
55111
+ function isTimebackGrade2(value) {
54473
55112
  return Number.isInteger(value) && TIMEBACK_GRADES.includes(value);
54474
55113
  }
54475
- function isTimebackSubject(value) {
54476
- return TIMEBACK_SUBJECTS.includes(value);
55114
+ function isTimebackSubject2(value) {
55115
+ return TIMEBACK_SUBJECTS2.includes(value);
54477
55116
  }
54478
55117
  function isValidAdminAttributionDate(value) {
54479
55118
  const match = value.match(/^(\d{4})-(\d{2})-(\d{2})$/);
@@ -54488,11 +55127,12 @@ function isValidAdminAttributionDate(value) {
54488
55127
  return date3.getUTCFullYear() === year && date3.getUTCMonth() + 1 === month && date3.getUTCDate() === day;
54489
55128
  }
54490
55129
  var TIMEBACK_GRADES;
54491
- var TIMEBACK_SUBJECTS;
55130
+ var TIMEBACK_SUBJECTS2;
54492
55131
  var TimebackGradeSchema;
54493
55132
  var TimebackSubjectSchema;
54494
55133
  var CourseGoalsSchema;
54495
55134
  var UpdateGameTimebackIntegrationRequestSchema;
55135
+ var CreateGameTimebackIntegrationRequestSchema;
54496
55136
  var TimebackActivityDataSchema;
54497
55137
  var EndActivityRequestSchema;
54498
55138
  var GameRunMetricsSchema;
@@ -54527,7 +55167,7 @@ var init_schemas4 = __esm(() => {
54527
55167
  init_esm();
54528
55168
  init_table7();
54529
55169
  TIMEBACK_GRADES = [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
54530
- TIMEBACK_SUBJECTS = [
55170
+ TIMEBACK_SUBJECTS2 = [
54531
55171
  "Reading",
54532
55172
  "Language",
54533
55173
  "Vocabulary",
@@ -54541,7 +55181,7 @@ var init_schemas4 = __esm(() => {
54541
55181
  TimebackGradeSchema = exports_external.number().int().refine((val) => TIMEBACK_GRADES.includes(val), {
54542
55182
  message: `Grade must be one of: ${TIMEBACK_GRADES.join(", ")}`
54543
55183
  });
54544
- TimebackSubjectSchema = exports_external.enum(TIMEBACK_SUBJECTS);
55184
+ TimebackSubjectSchema = exports_external.enum(TIMEBACK_SUBJECTS2);
54545
55185
  CourseGoalsSchema = exports_external.object({
54546
55186
  dailyXp: exports_external.number().int().nonnegative().nullable().optional(),
54547
55187
  dailyLessons: exports_external.number().int().nonnegative().nullable().optional(),
@@ -54560,6 +55200,15 @@ var init_schemas4 = __esm(() => {
54560
55200
  isSupplemental: exports_external.boolean().optional(),
54561
55201
  timebackVisible: exports_external.boolean().nullable().optional()
54562
55202
  });
55203
+ CreateGameTimebackIntegrationRequestSchema = exports_external.object({
55204
+ title: exports_external.string().trim().min(1),
55205
+ courseCode: exports_external.string().trim().min(1),
55206
+ subject: TimebackSubjectSchema,
55207
+ grade: TimebackGradeSchema,
55208
+ totalXp: exports_external.number().int().positive(),
55209
+ masterableUnits: exports_external.number().int().nonnegative(),
55210
+ level: exports_external.string().trim().min(1).optional()
55211
+ });
54563
55212
  TimebackActivityDataSchema = exports_external.object({
54564
55213
  activityId: exports_external.string().min(1),
54565
55214
  activityName: exports_external.string().optional(),
@@ -55001,9 +55650,6 @@ var init_log = __esm(() => {
55001
55650
  init_ansi();
55002
55651
  init_spinner();
55003
55652
  });
55004
- function kebabToTitleCase(kebabStr) {
55005
- return kebabStr.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
55006
- }
55007
55653
  function formatDateYMDInTimezone(timeZone, date3 = new Date) {
55008
55654
  const parts2 = new Intl.DateTimeFormat("en-US", {
55009
55655
  timeZone,
@@ -55106,14 +55752,42 @@ var init_src4 = __esm(() => {
55106
55752
  init_pure();
55107
55753
  });
55108
55754
  function createOneRosterUrls(baseUrl) {
55109
- const effective = baseUrl || TIMEBACK_API_URLS.production;
55755
+ const effective = baseUrl || TIMEBACK_API_URLS2.production;
55110
55756
  const base = effective.replace(/\/$/, "");
55111
55757
  return {
55112
- user: (userId) => `${base}${ONEROSTER_ENDPOINTS.users}/${userId}`,
55113
- course: (courseId) => `${base}${ONEROSTER_ENDPOINTS.courses}/${courseId}`,
55114
- componentResource: (resourceId) => `${base}${ONEROSTER_ENDPOINTS.componentResources}/${resourceId}`
55758
+ user: (userId) => `${base}${ONEROSTER_ENDPOINTS2.users}/${userId}`,
55759
+ course: (courseId) => `${base}${ONEROSTER_ENDPOINTS2.courses}/${courseId}`,
55760
+ componentResource: (resourceId) => `${base}${ONEROSTER_ENDPOINTS2.componentResources}/${resourceId}`
55115
55761
  };
55116
55762
  }
55763
+ function resolveCourseIdFromCourseUrl(url2) {
55764
+ try {
55765
+ const parts2 = new URL(url2).pathname.split("/").filter(Boolean);
55766
+ const maybeCourses = parts2.at(-2);
55767
+ const maybeId = parts2.at(-1);
55768
+ if (maybeCourses !== "courses" || !maybeId) {
55769
+ return null;
55770
+ }
55771
+ if (UUID_REGEX2.test(maybeId)) {
55772
+ return maybeId;
55773
+ }
55774
+ if (maybeId.length > 2 && !maybeId.includes("/")) {
55775
+ return maybeId;
55776
+ }
55777
+ return null;
55778
+ } catch {
55779
+ return null;
55780
+ }
55781
+ }
55782
+ function computeCaliperLineItemId(objectId, courseSourcedId, courseUrl) {
55783
+ const recovered = resolveCourseIdFromCourseUrl(courseUrl);
55784
+ if (recovered !== courseSourcedId) {
55785
+ throw new ConfigurationError("courseId", `Course id "${courseSourcedId}" is not URL-safe: Timeback Platform would parse it back as ${recovered === null ? "null" : `"${recovered}"`} from the Caliper event, producing a different gradebook line item. Use a UUID or an id with no spaces, slashes, or non-ASCII characters.`);
55786
+ }
55787
+ const idParts = [objectId, courseSourcedId].join("_");
55788
+ const hashedId = createHash("sha256").update(idParts).digest("hex");
55789
+ return `caliper_${hashedId}`;
55790
+ }
55117
55791
  function deriveSourcedIds(courseId) {
55118
55792
  return {
55119
55793
  course: courseId,
@@ -55125,7 +55799,7 @@ function deriveSourcedIds(courseId) {
55125
55799
  async function fetchTimebackConfig(client, courseId) {
55126
55800
  const sourcedIds = deriveSourcedIds(courseId);
55127
55801
  const [org, course, component, resource, componentResource] = await Promise.all([
55128
- client.oneroster.organizations.get(PLAYCADEMY_DEFAULTS.organization),
55802
+ client.oneroster.organizations.get(PLAYCADEMY_DEFAULTS2.organization),
55129
55803
  client.oneroster.courses.get(sourcedIds.course),
55130
55804
  client.oneroster.courseComponents.get(sourcedIds.component),
55131
55805
  client.oneroster.resources.get(sourcedIds.resource),
@@ -55135,7 +55809,7 @@ async function fetchTimebackConfig(client, courseId) {
55135
55809
  organization: {
55136
55810
  name: org.name,
55137
55811
  type: org.type,
55138
- identifier: org.identifier || PLAYCADEMY_DEFAULTS.organization
55812
+ identifier: org.identifier || PLAYCADEMY_DEFAULTS2.organization
55139
55813
  },
55140
55814
  course: {
55141
55815
  title: course.title || "",
@@ -55195,23 +55869,23 @@ async function verifyTimebackResources(client, courseId) {
55195
55869
  }
55196
55870
  };
55197
55871
  }
55198
- function isObject(value) {
55872
+ function isObject2(value) {
55199
55873
  return typeof value === "object" && value !== null;
55200
55874
  }
55201
- function isPlaycademyResourceMetadata(value) {
55202
- if (!isObject(value)) {
55875
+ function isPlaycademyResourceMetadata2(value) {
55876
+ if (!isObject2(value)) {
55203
55877
  return false;
55204
55878
  }
55205
55879
  if (!("mastery" in value) || value.mastery === undefined) {
55206
55880
  return true;
55207
55881
  }
55208
- return isObject(value.mastery);
55882
+ return isObject2(value.mastery);
55209
55883
  }
55210
- function isTimebackSubject2(value) {
55211
- return typeof value === "string" && SUBJECT_VALUES.includes(value);
55884
+ function isTimebackSubject3(value) {
55885
+ return typeof value === "string" && SUBJECT_VALUES2.includes(value);
55212
55886
  }
55213
- function isTimebackGrade2(value) {
55214
- return typeof value === "number" && Number.isInteger(value) && GRADE_VALUES.includes(value);
55887
+ function isTimebackGrade3(value) {
55888
+ return typeof value === "number" && Number.isInteger(value) && GRADE_VALUES2.includes(value);
55215
55889
  }
55216
55890
  async function deleteTimebackResources(client, courseId) {
55217
55891
  const sourcedIds = deriveSourcedIds(courseId);
@@ -55239,17 +55913,7 @@ async function deleteTimebackResources(client, courseId) {
55239
55913
  prerequisites: config2.component.prerequisites,
55240
55914
  prerequisiteCriteria: config2.component.prerequisiteCriteria
55241
55915
  }),
55242
- client.oneroster.resources.update(sourcedIds.resource, {
55243
- sourcedId: sourcedIds.resource,
55244
- status: "tobedeleted",
55245
- title: config2.resource.title,
55246
- vendorResourceId: config2.resource.vendorResourceId,
55247
- vendorId: config2.resource.vendorId,
55248
- applicationId: config2.resource.applicationId,
55249
- roles: config2.resource.roles,
55250
- importance: config2.resource.importance,
55251
- metadata: config2.resource.metadata
55252
- }),
55916
+ client.oneroster.resources.delete(sourcedIds.resource),
55253
55917
  client.oneroster.componentResources.update(sourcedIds.componentResource, {
55254
55918
  sourcedId: sourcedIds.componentResource,
55255
55919
  status: "tobedeleted",
@@ -55262,6 +55926,15 @@ async function deleteTimebackResources(client, courseId) {
55262
55926
  ]);
55263
55927
  setAttribute("app.timeback.resources_deleted", true);
55264
55928
  }
55929
+ async function updateTimebackCourseStatus(client, courseId, status) {
55930
+ const course = await client.oneroster.courses.get(courseId);
55931
+ await client.oneroster.courses.update(courseId, {
55932
+ ...course,
55933
+ sourcedId: courseId,
55934
+ status
55935
+ });
55936
+ setAttribute("app.timeback.course_status", status);
55937
+ }
55265
55938
  async function createCourse(client, config2) {
55266
55939
  const courseData = {
55267
55940
  status: "active",
@@ -55510,14 +56183,14 @@ async function getTimebackTokenResponse(config2) {
55510
56183
  }
55511
56184
  }
55512
56185
  function getAuthUrl(environment = "production") {
55513
- return TIMEBACK_AUTH_URLS[environment];
56186
+ return TIMEBACK_AUTH_URLS2[environment];
55514
56187
  }
55515
56188
  function parseEduBridgeGrade(value) {
55516
56189
  if (value === null || value === undefined || value.trim() === "") {
55517
56190
  return null;
55518
56191
  }
55519
56192
  const parsed = Number(value);
55520
- return isTimebackGrade2(parsed) ? parsed : null;
56193
+ return isTimebackGrade3(parsed) ? parsed : null;
55521
56194
  }
55522
56195
  function normalizeHighestGradeMastered(response, subject) {
55523
56196
  const grades = {
@@ -55594,12 +56267,12 @@ async function withTimebackClientTelemetry(fn) {
55594
56267
  }
55595
56268
  function handleHttpError(res, errorBody, attempt, retries, context2) {
55596
56269
  const error = new TimebackApiError(res.status, res.statusText, errorBody);
55597
- if (res.status >= HTTP_STATUS.CLIENT_ERROR_MIN && res.status < HTTP_STATUS.CLIENT_ERROR_MAX) {
56270
+ if (res.status >= HTTP_STATUS2.CLIENT_ERROR_MIN && res.status < HTTP_STATUS2.CLIENT_ERROR_MAX) {
55598
56271
  recordTimebackHttpFailure();
55599
56272
  throw error;
55600
56273
  }
55601
56274
  if (attempt < retries) {
55602
- const delay = HTTP_DEFAULTS.retryBackoffBase ** attempt * 1000;
56275
+ const delay = HTTP_DEFAULTS2.retryBackoffBase ** attempt * 1000;
55603
56276
  recordTimebackRetry();
55604
56277
  addEvent("timeback.request_retry", {
55605
56278
  "app.timeback.attempt": attempt + 1,
@@ -55676,7 +56349,7 @@ async function request({
55676
56349
  const result = handleHttpError(res, errorBody, attempt, retries, { method, path: path2 });
55677
56350
  lastError = result.error;
55678
56351
  if (result.retry) {
55679
- const delay = HTTP_DEFAULTS.retryBackoffBase ** attempt * 1000;
56352
+ const delay = HTTP_DEFAULTS2.retryBackoffBase ** attempt * 1000;
55680
56353
  await new Promise((resolve2) => setTimeout(resolve2, delay));
55681
56354
  }
55682
56355
  } else {
@@ -55688,7 +56361,7 @@ async function request({
55688
56361
  }
55689
56362
  lastError = error instanceof Error ? error : new Error(String(error));
55690
56363
  if (attempt < retries) {
55691
- const delay = HTTP_DEFAULTS.retryBackoffBase ** attempt * 1000;
56364
+ const delay = HTTP_DEFAULTS2.retryBackoffBase ** attempt * 1000;
55692
56365
  recordTimebackRetry();
55693
56366
  addEvent("timeback.network_retry", {
55694
56367
  "app.timeback.attempt": attempt + 1,
@@ -55737,10 +56410,10 @@ function createCaliperNamespace(client) {
55737
56410
  const envelope = {
55738
56411
  sensor: sensorUrl,
55739
56412
  sendTime: new Date().toISOString(),
55740
- dataVersion: CALIPER_CONSTANTS.dataVersion,
56413
+ dataVersion: CALIPER_CONSTANTS2.dataVersion,
55741
56414
  data: [event]
55742
56415
  };
55743
- return client["requestCaliper"](CALIPER_ENDPOINTS.event, "POST", envelope);
56416
+ return client["requestCaliper"](CALIPER_ENDPOINTS2.event, "POST", envelope);
55744
56417
  },
55745
56418
  emitBatch: async (events, sensorUrl) => {
55746
56419
  if (events.length === 0) {
@@ -55749,10 +56422,10 @@ function createCaliperNamespace(client) {
55749
56422
  const envelope = {
55750
56423
  sensor: sensorUrl,
55751
56424
  sendTime: new Date().toISOString(),
55752
- dataVersion: CALIPER_CONSTANTS.dataVersion,
56425
+ dataVersion: CALIPER_CONSTANTS2.dataVersion,
55753
56426
  data: events
55754
56427
  };
55755
- return client["requestCaliper"](CALIPER_ENDPOINTS.event, "POST", envelope);
56428
+ return client["requestCaliper"](CALIPER_ENDPOINTS2.event, "POST", envelope);
55756
56429
  },
55757
56430
  events: {
55758
56431
  list: async (params = {}) => {
@@ -55782,7 +56455,7 @@ function createCaliperNamespace(client) {
55782
56455
  query.set(`extensions.${key}`, value);
55783
56456
  }
55784
56457
  }
55785
- const requestPath = `${CALIPER_ENDPOINTS.events}?${query.toString()}`;
56458
+ const requestPath = `${CALIPER_ENDPOINTS2.events}?${query.toString()}`;
55786
56459
  return client["requestCaliper"](requestPath, "GET");
55787
56460
  }
55788
56461
  },
@@ -55792,21 +56465,21 @@ function createCaliperNamespace(client) {
55792
56465
  gameId: data.gameId
55793
56466
  });
55794
56467
  const event = {
55795
- "@context": CALIPER_CONSTANTS.context,
56468
+ "@context": CALIPER_CONSTANTS2.context,
55796
56469
  id: `urn:uuid:${crypto.randomUUID()}`,
55797
- type: TIMEBACK_EVENT_TYPES.activityEvent,
56470
+ type: TIMEBACK_EVENT_TYPES2.activityEvent,
55798
56471
  eventTime: data.eventTime || new Date().toISOString(),
55799
- profile: CALIPER_CONSTANTS.profile,
56472
+ profile: CALIPER_CONSTANTS2.profile,
55800
56473
  actor: {
55801
56474
  id: urls.user(data.studentId),
55802
- type: TIMEBACK_TYPES.user,
56475
+ type: TIMEBACK_TYPES2.user,
55803
56476
  email: data.studentEmail
55804
56477
  },
55805
- action: TIMEBACK_ACTIONS.completed,
56478
+ action: TIMEBACK_ACTIONS2.completed,
55806
56479
  ...data.runId ? { session: `urn:uuid:${data.runId}` } : {},
55807
56480
  object: {
55808
56481
  id: data.objectId || caliper.buildActivityUrl(data),
55809
- type: TIMEBACK_TYPES.activityContext,
56482
+ type: TIMEBACK_TYPES2.activityContext,
55810
56483
  subject: data.subject,
55811
56484
  app: {
55812
56485
  name: data.appName
@@ -55820,25 +56493,25 @@ function createCaliperNamespace(client) {
55820
56493
  },
55821
56494
  generated: {
55822
56495
  id: data.generatedId || `urn:timeback:metrics:activity-completion-${crypto.randomUUID()}`,
55823
- type: TIMEBACK_TYPES.activityMetricsCollection,
56496
+ type: TIMEBACK_TYPES2.activityMetricsCollection,
55824
56497
  ...data.includeAttempt === false ? {} : { attempt: data.attemptNumber || 1 },
55825
56498
  items: [
55826
56499
  ...data.totalQuestions !== undefined ? [
55827
56500
  {
55828
- type: ACTIVITY_METRIC_TYPES.totalQuestions,
56501
+ type: ACTIVITY_METRIC_TYPES2.totalQuestions,
55829
56502
  value: data.totalQuestions
55830
56503
  }
55831
56504
  ] : [],
55832
56505
  ...data.correctQuestions !== undefined ? [
55833
56506
  {
55834
- type: ACTIVITY_METRIC_TYPES.correctQuestions,
56507
+ type: ACTIVITY_METRIC_TYPES2.correctQuestions,
55835
56508
  value: data.correctQuestions
55836
56509
  }
55837
56510
  ] : [],
55838
- ...data.xpEarned !== undefined ? [{ type: ACTIVITY_METRIC_TYPES.xpEarned, value: data.xpEarned }] : [],
56511
+ ...data.xpEarned !== undefined ? [{ type: ACTIVITY_METRIC_TYPES2.xpEarned, value: data.xpEarned }] : [],
55839
56512
  ...data.masteredUnits !== undefined ? [
55840
56513
  {
55841
- type: ACTIVITY_METRIC_TYPES.masteredUnits,
56514
+ type: ACTIVITY_METRIC_TYPES2.masteredUnits,
55842
56515
  value: data.masteredUnits
55843
56516
  }
55844
56517
  ] : []
@@ -55860,21 +56533,21 @@ function createCaliperNamespace(client) {
55860
56533
  gameId: data.gameId
55861
56534
  });
55862
56535
  const event = {
55863
- "@context": CALIPER_CONSTANTS.context,
56536
+ "@context": CALIPER_CONSTANTS2.context,
55864
56537
  id: `urn:uuid:${crypto.randomUUID()}`,
55865
- type: TIMEBACK_EVENT_TYPES.timeSpentEvent,
56538
+ type: TIMEBACK_EVENT_TYPES2.timeSpentEvent,
55866
56539
  eventTime: data.eventTime || new Date().toISOString(),
55867
- profile: CALIPER_CONSTANTS.profile,
56540
+ profile: CALIPER_CONSTANTS2.profile,
55868
56541
  actor: {
55869
56542
  id: urls.user(data.studentId),
55870
- type: TIMEBACK_TYPES.user,
56543
+ type: TIMEBACK_TYPES2.user,
55871
56544
  email: data.studentEmail
55872
56545
  },
55873
- action: TIMEBACK_ACTIONS.spentTime,
56546
+ action: TIMEBACK_ACTIONS2.spentTime,
55874
56547
  ...data.runId ? { session: `urn:uuid:${data.runId}` } : {},
55875
56548
  object: {
55876
56549
  id: caliper.buildActivityUrl(data),
55877
- type: TIMEBACK_TYPES.activityContext,
56550
+ type: TIMEBACK_TYPES2.activityContext,
55878
56551
  subject: data.subject,
55879
56552
  app: {
55880
56553
  name: data.appName
@@ -55887,16 +56560,16 @@ function createCaliperNamespace(client) {
55887
56560
  },
55888
56561
  generated: {
55889
56562
  id: `urn:timeback:metrics:time-spent-${crypto.randomUUID()}`,
55890
- type: TIMEBACK_TYPES.timeSpentMetricsCollection,
56563
+ type: TIMEBACK_TYPES2.timeSpentMetricsCollection,
55891
56564
  items: [
55892
- { type: TIME_METRIC_TYPES.active, value: data.activeTimeSeconds },
56565
+ { type: TIME_METRIC_TYPES2.active, value: data.activeTimeSeconds },
55893
56566
  ...data.inactiveTimeSeconds !== undefined ? [
55894
56567
  {
55895
- type: TIME_METRIC_TYPES.inactive,
56568
+ type: TIME_METRIC_TYPES2.inactive,
55896
56569
  value: data.inactiveTimeSeconds
55897
56570
  }
55898
56571
  ] : [],
55899
- ...data.wasteTimeSeconds !== undefined ? [{ type: TIME_METRIC_TYPES.waste, value: data.wasteTimeSeconds }] : []
56572
+ ...data.wasteTimeSeconds !== undefined ? [{ type: TIME_METRIC_TYPES2.waste, value: data.wasteTimeSeconds }] : []
55900
56573
  ],
55901
56574
  ...data.extensions ? { extensions: data.extensions } : {}
55902
56575
  },
@@ -55907,7 +56580,8 @@ function createCaliperNamespace(client) {
55907
56580
  buildActivityUrl: (data) => {
55908
56581
  const base = data.sensorUrl.replace(/\/$/, "");
55909
56582
  return `${base}/activities/${encodeURIComponent(data.courseId)}/${encodeURIComponent(data.activityId)}`;
55910
- }
56583
+ },
56584
+ buildCourseUrl: (courseId) => urls.course(courseId)
55911
56585
  };
55912
56586
  return caliper;
55913
56587
  }
@@ -56030,17 +56704,17 @@ async function listOneRosterCollection(client, endpoint, collectionKey, options)
56030
56704
  function createOneRosterNamespace(client) {
56031
56705
  return {
56032
56706
  classes: {
56033
- create: async (data) => client["request"](ONEROSTER_ENDPOINTS.classes, "POST", { class: data }),
56707
+ create: async (data) => client["request"](ONEROSTER_ENDPOINTS2.classes, "POST", { class: data }),
56034
56708
  get: async (sourcedId) => {
56035
- const res = await client["request"](`${ONEROSTER_ENDPOINTS.classes}/${sourcedId}`, "GET");
56709
+ const res = await client["request"](`${ONEROSTER_ENDPOINTS2.classes}/${sourcedId}`, "GET");
56036
56710
  return res.class;
56037
56711
  },
56038
56712
  update: async (sourcedId, data) => {
56039
- const res = await client["request"](`${ONEROSTER_ENDPOINTS.classes}/${sourcedId}`, "PUT", { class: data });
56713
+ const res = await client["request"](`${ONEROSTER_ENDPOINTS2.classes}/${sourcedId}`, "PUT", { class: data });
56040
56714
  return res.class;
56041
56715
  },
56042
56716
  listByCourse: async (courseSourcedId) => {
56043
- const res = await client["request"](`${ONEROSTER_ENDPOINTS.courses}/${courseSourcedId}/classes`, "GET");
56717
+ const res = await client["request"](`${ONEROSTER_ENDPOINTS2.courses}/${courseSourcedId}/classes`, "GET");
56044
56718
  return res.classes;
56045
56719
  },
56046
56720
  listByStudent: async (userSourcedId, options) => {
@@ -56051,7 +56725,7 @@ function createOneRosterNamespace(client) {
56051
56725
  if (options?.offset) {
56052
56726
  queryParams.set("offset", String(options.offset));
56053
56727
  }
56054
- const endpoint = `${ONEROSTER_ENDPOINTS.users}/${userSourcedId}/classes`;
56728
+ const endpoint = `${ONEROSTER_ENDPOINTS2.users}/${userSourcedId}/classes`;
56055
56729
  const url2 = queryParams.toString() ? `${endpoint}?${queryParams}` : endpoint;
56056
56730
  const res = await client["request"](url2, "GET");
56057
56731
  return res.classes || [];
@@ -56059,7 +56733,7 @@ function createOneRosterNamespace(client) {
56059
56733
  },
56060
56734
  enrollments: {
56061
56735
  get: async (sourcedId) => {
56062
- const response = await client["request"](`${ONEROSTER_ENDPOINTS.enrollments}/${sourcedId}`, "GET");
56736
+ const response = await client["request"](`${ONEROSTER_ENDPOINTS2.enrollments}/${sourcedId}`, "GET");
56063
56737
  return response.enrollment;
56064
56738
  },
56065
56739
  listByClass: async (classSourcedId, options) => {
@@ -56075,7 +56749,7 @@ function createOneRosterNamespace(client) {
56075
56749
  if (options?.offset) {
56076
56750
  queryParams.set("offset", String(options.offset));
56077
56751
  }
56078
- const url2 = `${ONEROSTER_ENDPOINTS.enrollments}?${queryParams}`;
56752
+ const url2 = `${ONEROSTER_ENDPOINTS2.enrollments}?${queryParams}`;
56079
56753
  try {
56080
56754
  const response = await client["request"](url2, "GET");
56081
56755
  return response.enrollments || [];
@@ -56108,7 +56782,7 @@ function createOneRosterNamespace(client) {
56108
56782
  }
56109
56783
  queryParams.set("filter", filters.join(" AND "));
56110
56784
  queryParams.set("limit", "3000");
56111
- const url2 = `${ONEROSTER_ENDPOINTS.enrollments}?${queryParams}`;
56785
+ const url2 = `${ONEROSTER_ENDPOINTS2.enrollments}?${queryParams}`;
56112
56786
  const response = await client["request"](url2, "GET");
56113
56787
  return response.enrollments || [];
56114
56788
  }));
@@ -56138,72 +56812,72 @@ function createOneRosterNamespace(client) {
56138
56812
  throw error;
56139
56813
  }
56140
56814
  },
56141
- create: async (data) => client["request"](ONEROSTER_ENDPOINTS.enrollments, "POST", { enrollment: data }),
56815
+ create: async (data) => client["request"](ONEROSTER_ENDPOINTS2.enrollments, "POST", { enrollment: data }),
56142
56816
  update: async (sourcedId, data) => {
56143
- await client["request"](`${ONEROSTER_ENDPOINTS.enrollments}/${sourcedId}`, "PUT", {
56817
+ await client["request"](`${ONEROSTER_ENDPOINTS2.enrollments}/${sourcedId}`, "PUT", {
56144
56818
  enrollment: data
56145
56819
  });
56146
56820
  },
56147
56821
  delete: async (sourcedId) => {
56148
- await client["request"](`${ONEROSTER_ENDPOINTS.enrollments}/${sourcedId}`, "DELETE");
56822
+ await client["request"](`${ONEROSTER_ENDPOINTS2.enrollments}/${sourcedId}`, "DELETE");
56149
56823
  }
56150
56824
  },
56151
56825
  organizations: {
56152
- create: async (data) => client["request"](ONEROSTER_ENDPOINTS.organizations, "POST", data),
56153
- get: async (sourcedId) => client["request"](`${ONEROSTER_ENDPOINTS.organizations}/${sourcedId}`, "GET").then((res) => res.org),
56154
- update: async (sourcedId, data) => client["request"](`${ONEROSTER_ENDPOINTS.organizations}/${sourcedId}`, "PUT", data)
56826
+ create: async (data) => client["request"](ONEROSTER_ENDPOINTS2.organizations, "POST", data),
56827
+ get: async (sourcedId) => client["request"](`${ONEROSTER_ENDPOINTS2.organizations}/${sourcedId}`, "GET").then((res) => res.org),
56828
+ update: async (sourcedId, data) => client["request"](`${ONEROSTER_ENDPOINTS2.organizations}/${sourcedId}`, "PUT", data)
56155
56829
  },
56156
56830
  courses: {
56157
- create: async (data) => client["request"](ONEROSTER_ENDPOINTS.courses, "POST", data),
56831
+ create: async (data) => client["request"](ONEROSTER_ENDPOINTS2.courses, "POST", data),
56158
56832
  get: async (sourcedId) => {
56159
- const response = await client["request"](`${ONEROSTER_ENDPOINTS.courses}/${sourcedId}`, "GET");
56833
+ const response = await client["request"](`${ONEROSTER_ENDPOINTS2.courses}/${sourcedId}`, "GET");
56160
56834
  return response.course;
56161
56835
  },
56162
56836
  update: async (sourcedId, data) => {
56163
- await client["request"](`${ONEROSTER_ENDPOINTS.courses}/${sourcedId}`, "PUT", {
56837
+ await client["request"](`${ONEROSTER_ENDPOINTS2.courses}/${sourcedId}`, "PUT", {
56164
56838
  course: data
56165
56839
  });
56166
56840
  }
56167
56841
  },
56168
56842
  courseComponents: {
56169
- create: async (data) => client["request"](ONEROSTER_ENDPOINTS.courseComponents, "POST", data),
56843
+ create: async (data) => client["request"](ONEROSTER_ENDPOINTS2.courseComponents, "POST", data),
56170
56844
  get: async (sourcedId) => {
56171
- const response = await client["request"](`${ONEROSTER_ENDPOINTS.courseComponents}/${sourcedId}`, "GET");
56845
+ const response = await client["request"](`${ONEROSTER_ENDPOINTS2.courseComponents}/${sourcedId}`, "GET");
56172
56846
  return response.courseComponent;
56173
56847
  },
56174
56848
  update: async (sourcedId, data) => {
56175
- await client["request"](`${ONEROSTER_ENDPOINTS.courseComponents}/${sourcedId}`, "PUT", { courseComponent: data });
56849
+ await client["request"](`${ONEROSTER_ENDPOINTS2.courseComponents}/${sourcedId}`, "PUT", { courseComponent: data });
56176
56850
  }
56177
56851
  },
56178
56852
  resources: {
56179
- create: async (data) => client["request"](ONEROSTER_ENDPOINTS.resources, "POST", data),
56853
+ create: async (data) => client["request"](ONEROSTER_ENDPOINTS2.resources, "POST", data),
56180
56854
  get: async (sourcedId) => {
56181
- const response = await client["request"](`${ONEROSTER_ENDPOINTS.resources}/${sourcedId}`, "GET");
56855
+ const response = await client["request"](`${ONEROSTER_ENDPOINTS2.resources}/${sourcedId}`, "GET");
56182
56856
  return response.resource;
56183
56857
  },
56184
56858
  update: async (sourcedId, data) => {
56185
- await client["request"](`${ONEROSTER_ENDPOINTS.resources}/${sourcedId}`, "PUT", {
56859
+ await client["request"](`${ONEROSTER_ENDPOINTS2.resources}/${sourcedId}`, "PUT", {
56186
56860
  resource: data
56187
56861
  });
56188
56862
  },
56189
56863
  delete: async (sourcedId) => {
56190
- await client["request"](`${ONEROSTER_ENDPOINTS.resources}/${sourcedId}`, "DELETE");
56864
+ await client["request"](`${ONEROSTER_ENDPOINTS2.resources}/${sourcedId}`, "DELETE");
56191
56865
  }
56192
56866
  },
56193
56867
  componentResources: {
56194
- create: async (data) => client["request"](ONEROSTER_ENDPOINTS.componentResources, "POST", data),
56868
+ create: async (data) => client["request"](ONEROSTER_ENDPOINTS2.componentResources, "POST", data),
56195
56869
  get: async (sourcedId) => {
56196
- const response = await client["request"](`${ONEROSTER_ENDPOINTS.componentResources}/${sourcedId}`, "GET");
56870
+ const response = await client["request"](`${ONEROSTER_ENDPOINTS2.componentResources}/${sourcedId}`, "GET");
56197
56871
  return response.componentResource;
56198
56872
  },
56199
56873
  update: async (sourcedId, data) => {
56200
- await client["request"](`${ONEROSTER_ENDPOINTS.componentResources}/${sourcedId}`, "PUT", { componentResource: data });
56874
+ await client["request"](`${ONEROSTER_ENDPOINTS2.componentResources}/${sourcedId}`, "PUT", { componentResource: data });
56201
56875
  }
56202
56876
  },
56203
56877
  assessmentLineItems: {
56204
56878
  list: async (options) => {
56205
56879
  try {
56206
- return await listOneRosterCollection(client, ONEROSTER_ENDPOINTS.assessmentLineItems, "assessmentLineItems", options);
56880
+ return await listOneRosterCollection(client, ONEROSTER_ENDPOINTS2.assessmentLineItems, "assessmentLineItems", options);
56207
56881
  } catch (error) {
56208
56882
  logTimebackError("list assessment line items", error, {
56209
56883
  filter: options?.filter
@@ -56211,14 +56885,14 @@ function createOneRosterNamespace(client) {
56211
56885
  return [];
56212
56886
  }
56213
56887
  },
56214
- create: async (data) => client["request"](ONEROSTER_ENDPOINTS.assessmentLineItems, "POST", { assessmentLineItem: data }),
56888
+ create: async (data) => client["request"](ONEROSTER_ENDPOINTS2.assessmentLineItems, "POST", { assessmentLineItem: data }),
56215
56889
  get: async (sourcedId) => {
56216
- const response = await client["request"](`${ONEROSTER_ENDPOINTS.assessmentLineItems}/${sourcedId}`, "GET");
56890
+ const response = await client["request"](`${ONEROSTER_ENDPOINTS2.assessmentLineItems}/${sourcedId}`, "GET");
56217
56891
  return response.assessmentLineItem;
56218
56892
  },
56219
56893
  findOrCreate: async (sourcedId, data) => {
56220
56894
  try {
56221
- const response = await client["request"](`${ONEROSTER_ENDPOINTS.assessmentLineItems}/${sourcedId}`, "GET");
56895
+ const response = await client["request"](`${ONEROSTER_ENDPOINTS2.assessmentLineItems}/${sourcedId}`, "GET");
56222
56896
  return response.assessmentLineItem;
56223
56897
  } catch {
56224
56898
  const createData = {
@@ -56227,7 +56901,7 @@ function createOneRosterNamespace(client) {
56227
56901
  dateLastModified: new Date().toISOString()
56228
56902
  };
56229
56903
  try {
56230
- const response = await client["request"](ONEROSTER_ENDPOINTS.assessmentLineItems, "POST", { assessmentLineItem: createData });
56904
+ const response = await client["request"](ONEROSTER_ENDPOINTS2.assessmentLineItems, "POST", { assessmentLineItem: createData });
56231
56905
  if (!response.sourcedIdPairs?.allocatedSourcedId) {
56232
56906
  throw new Error("Invalid response from OneRoster API - missing allocatedSourcedId");
56233
56907
  }
@@ -56242,7 +56916,7 @@ function createOneRosterNamespace(client) {
56242
56916
  assessmentResults: {
56243
56917
  list: async (options) => {
56244
56918
  try {
56245
- return await listOneRosterCollection(client, ONEROSTER_ENDPOINTS.assessmentResults, "assessmentResults", options);
56919
+ return await listOneRosterCollection(client, ONEROSTER_ENDPOINTS2.assessmentResults, "assessmentResults", options);
56246
56920
  } catch (error) {
56247
56921
  logTimebackError("list assessment results", error, {
56248
56922
  filter: options?.filter
@@ -56250,10 +56924,11 @@ function createOneRosterNamespace(client) {
56250
56924
  return [];
56251
56925
  }
56252
56926
  },
56253
- create: async (data) => client["request"](ONEROSTER_ENDPOINTS.assessmentResults, "POST", { assessmentResult: data }),
56927
+ listOrThrow: async (options) => await listOneRosterCollection(client, ONEROSTER_ENDPOINTS2.assessmentResults, "assessmentResults", options),
56928
+ create: async (data) => client["request"](ONEROSTER_ENDPOINTS2.assessmentResults, "POST", { assessmentResult: data }),
56254
56929
  listByStudent: async (studentSourcedId, options) => {
56255
56930
  try {
56256
- return await listOneRosterCollection(client, ONEROSTER_ENDPOINTS.assessmentResults, "assessmentResults", {
56931
+ return await listOneRosterCollection(client, ONEROSTER_ENDPOINTS2.assessmentResults, "assessmentResults", {
56257
56932
  limit: options?.limit,
56258
56933
  offset: options?.offset,
56259
56934
  fields: options?.fields,
@@ -56272,54 +56947,22 @@ function createOneRosterNamespace(client) {
56272
56947
  sourcedId,
56273
56948
  dateLastModified: new Date().toISOString()
56274
56949
  };
56275
- return client["request"](`${ONEROSTER_ENDPOINTS.assessmentResults}/${sourcedId}`, "PUT", { assessmentResult });
56950
+ return client["request"](`${ONEROSTER_ENDPOINTS2.assessmentResults}/${sourcedId}`, "PUT", { assessmentResult });
56276
56951
  },
56277
56952
  get: async (sourcedId) => {
56278
- const response = await client["request"](`${ONEROSTER_ENDPOINTS.assessmentResults}/${sourcedId}`, "GET");
56953
+ const response = await client["request"](`${ONEROSTER_ENDPOINTS2.assessmentResults}/${sourcedId}`, "GET");
56279
56954
  return response.assessmentResult;
56280
56955
  },
56281
- update: async (sourcedId, data) => client["request"](`${ONEROSTER_ENDPOINTS.assessmentResults}/${sourcedId}`, "PUT", data),
56282
- getAttemptStats: async (studentId, lineItemId) => {
56283
- try {
56284
- const filter = `student.sourcedId='${studentId}' AND assessmentLineItem.sourcedId='${lineItemId}'`;
56285
- const url2 = `${ONEROSTER_ENDPOINTS.assessmentResults}?filter=${encodeURIComponent(filter)}`;
56286
- const response = await client["request"](url2, "GET");
56287
- const results = response.assessmentResults || [];
56288
- const firstResult = results[0];
56289
- if (!firstResult) {
56290
- return null;
56291
- }
56292
- let maxAttemptResult = firstResult;
56293
- let maxAttemptNumber = maxAttemptResult.metadata?.attemptNumber || 0;
56294
- let activeAttemptCount = 0;
56295
- for (const result of results) {
56296
- const attemptNumber = result.metadata?.attemptNumber || 0;
56297
- if (attemptNumber > maxAttemptNumber) {
56298
- maxAttemptNumber = attemptNumber;
56299
- maxAttemptResult = result;
56300
- }
56301
- if (result.status === "active") {
56302
- activeAttemptCount++;
56303
- }
56304
- }
56305
- return { maxAttemptNumber, activeAttemptCount, maxAttemptResult };
56306
- } catch (error) {
56307
- logTimebackError("query attempt stats", error, {
56308
- studentId,
56309
- lineItemId
56310
- });
56311
- return null;
56312
- }
56313
- }
56956
+ update: async (sourcedId, data) => client["request"](`${ONEROSTER_ENDPOINTS2.assessmentResults}/${sourcedId}`, "PUT", data)
56314
56957
  },
56315
56958
  users: {
56316
- create: async (data) => client["request"](ONEROSTER_ENDPOINTS.users, "POST", {
56959
+ create: async (data) => client["request"](ONEROSTER_ENDPOINTS2.users, "POST", {
56317
56960
  user: data
56318
56961
  }),
56319
- get: async (sourcedId) => client["request"](`${ONEROSTER_ENDPOINTS.users}/${sourcedId}`, "GET").then((res) => res.user),
56962
+ get: async (sourcedId) => client["request"](`${ONEROSTER_ENDPOINTS2.users}/${sourcedId}`, "GET").then((res) => res.user),
56320
56963
  findByEmail: async (email) => {
56321
56964
  const params = new URLSearchParams({ filter: `email='${email}'` });
56322
- const response = await client["request"](`${ONEROSTER_ENDPOINTS.users}?${params}`, "GET");
56965
+ const response = await client["request"](`${ONEROSTER_ENDPOINTS2.users}?${params}`, "GET");
56323
56966
  if (!response || !response.users || !Array.isArray(response.users)) {
56324
56967
  throw new Error(`Invalid response format from OneRoster API when searching for user with email: ${email}. Expected { users: [...] } but received: ${JSON.stringify(response)}`);
56325
56968
  }
@@ -56338,7 +56981,7 @@ function createOneRosterNamespace(client) {
56338
56981
  const results = await Promise.all(batches.map(async (batch) => {
56339
56982
  const filter = batch.map((id) => `sourcedId='${escapeFilterValue(id)}'`).join(" OR ");
56340
56983
  const params = new URLSearchParams({ filter });
56341
- const response = await client["request"](`${ONEROSTER_ENDPOINTS.users}?${params}`, "GET");
56984
+ const response = await client["request"](`${ONEROSTER_ENDPOINTS2.users}?${params}`, "GET");
56342
56985
  return response.users || [];
56343
56986
  }));
56344
56987
  return results.flat();
@@ -56355,11 +56998,11 @@ function createOneRosterNamespace(client) {
56355
56998
  function createQtiNamespace(client) {
56356
56999
  return {
56357
57000
  items: {
56358
- create: async (data) => client["requestQti"](QTI_ENDPOINTS.assessmentItems, "POST", data),
56359
- get: async (identifier) => client["requestQti"](`${QTI_ENDPOINTS.assessmentItems}/${identifier}`, "GET"),
56360
- update: async (identifier, data) => client["requestQti"](`${QTI_ENDPOINTS.assessmentItems}/${identifier}`, "PUT", data),
57001
+ create: async (data) => client["requestQti"](QTI_ENDPOINTS2.assessmentItems, "POST", data),
57002
+ get: async (identifier) => client["requestQti"](`${QTI_ENDPOINTS2.assessmentItems}/${identifier}`, "GET"),
57003
+ update: async (identifier, data) => client["requestQti"](`${QTI_ENDPOINTS2.assessmentItems}/${identifier}`, "PUT", data),
56361
57004
  delete: async (identifier) => {
56362
- await client["requestQti"](`${QTI_ENDPOINTS.assessmentItems}/${identifier}`, "DELETE");
57005
+ await client["requestQti"](`${QTI_ENDPOINTS2.assessmentItems}/${identifier}`, "DELETE");
56363
57006
  }
56364
57007
  },
56365
57008
  tests: {
@@ -56381,25 +57024,25 @@ function createQtiNamespace(client) {
56381
57024
  params.set("order", options.order);
56382
57025
  }
56383
57026
  const query = params.toString();
56384
- return client["requestQti"](`${QTI_ENDPOINTS.assessmentTests}${query ? `?${query}` : ""}`, "GET");
57027
+ return client["requestQti"](`${QTI_ENDPOINTS2.assessmentTests}${query ? `?${query}` : ""}`, "GET");
56385
57028
  },
56386
- create: async (data) => client["requestQti"](QTI_ENDPOINTS.assessmentTests, "POST", data),
56387
- get: async (identifier) => client["requestQti"](`${QTI_ENDPOINTS.assessmentTests}/${identifier}`, "GET"),
56388
- getQuestions: async (identifier) => client["requestQti"](`${QTI_ENDPOINTS.assessmentTests}/${identifier}/questions`, "GET"),
56389
- update: async (identifier, data) => client["requestQti"](`${QTI_ENDPOINTS.assessmentTests}/${identifier}`, "PUT", data),
57029
+ create: async (data) => client["requestQti"](QTI_ENDPOINTS2.assessmentTests, "POST", data),
57030
+ get: async (identifier) => client["requestQti"](`${QTI_ENDPOINTS2.assessmentTests}/${identifier}`, "GET"),
57031
+ getQuestions: async (identifier) => client["requestQti"](`${QTI_ENDPOINTS2.assessmentTests}/${identifier}/questions`, "GET"),
57032
+ update: async (identifier, data) => client["requestQti"](`${QTI_ENDPOINTS2.assessmentTests}/${identifier}`, "PUT", data),
56390
57033
  delete: async (identifier) => {
56391
- await client["requestQti"](`${QTI_ENDPOINTS.assessmentTests}/${identifier}`, "DELETE");
57034
+ await client["requestQti"](`${QTI_ENDPOINTS2.assessmentTests}/${identifier}`, "DELETE");
56392
57035
  },
56393
- addItem: async (testId, partId, sectionId, itemIdentifier) => client["requestQti"](`${QTI_ENDPOINTS.assessmentTests}/${testId}/test-parts/${partId}/sections/${sectionId}/items`, "POST", { identifier: itemIdentifier }),
57036
+ addItem: async (testId, partId, sectionId, itemIdentifier) => client["requestQti"](`${QTI_ENDPOINTS2.assessmentTests}/${testId}/test-parts/${partId}/sections/${sectionId}/items`, "POST", { identifier: itemIdentifier }),
56394
57037
  removeItem: async (testId, partId, sectionId, itemIdentifier) => {
56395
- await client["requestQti"](`${QTI_ENDPOINTS.assessmentTests}/${testId}/test-parts/${partId}/sections/${sectionId}/items/${itemIdentifier}`, "DELETE");
57038
+ await client["requestQti"](`${QTI_ENDPOINTS2.assessmentTests}/${testId}/test-parts/${partId}/sections/${sectionId}/items/${itemIdentifier}`, "DELETE");
56396
57039
  },
56397
- reorderItems: async (testId, partId, sectionId, items) => client["requestQti"](`${QTI_ENDPOINTS.assessmentTests}/${testId}/test-parts/${partId}/sections/${sectionId}/items/order`, "PUT", { items })
57040
+ reorderItems: async (testId, partId, sectionId, items) => client["requestQti"](`${QTI_ENDPOINTS2.assessmentTests}/${testId}/test-parts/${partId}/sections/${sectionId}/items/order`, "PUT", { items })
56398
57041
  }
56399
57042
  };
56400
57043
  }
56401
57044
  function toCaliperSubject(subject) {
56402
- return isTimebackSubject2(subject) ? subject : "None";
57045
+ return isTimebackSubject3(subject) ? subject : "None";
56403
57046
  }
56404
57047
  function buildAdminEventMetadata({
56405
57048
  reason,
@@ -56463,7 +57106,7 @@ class AdminEventRecorder {
56463
57106
  defaultActivityId: "playcademy-admin-manual-xp",
56464
57107
  eventKind: "remediation-xp"
56465
57108
  });
56466
- const courseUrl = createOneRosterUrls(TIMEBACK_API_URLS[this.environment]).course(data.courseId);
57109
+ const courseUrl = createOneRosterUrls(TIMEBACK_API_URLS2[this.environment]).course(data.courseId);
56467
57110
  await this.caliper.emitActivityEvent({
56468
57111
  studentId: ctx.student.id,
56469
57112
  studentEmail: ctx.student.email,
@@ -56513,7 +57156,7 @@ class AdminEventRecorder {
56513
57156
  defaultActivityId: "playcademy-admin-mastery-adjustment",
56514
57157
  eventKind: "remediation-mastery"
56515
57158
  });
56516
- const courseUrl = createOneRosterUrls(TIMEBACK_API_URLS[this.environment]).course(data.courseId);
57159
+ const courseUrl = createOneRosterUrls(TIMEBACK_API_URLS2[this.environment]).course(data.courseId);
56517
57160
  await this.caliper.emitActivityEvent({
56518
57161
  studentId: ctx.student.id,
56519
57162
  studentEmail: ctx.student.email,
@@ -56547,9 +57190,9 @@ class TimebackCache {
56547
57190
  maxSize;
56548
57191
  name;
56549
57192
  constructor(options = {}) {
56550
- this.defaultTTL = options.defaultTTL || CACHE_DEFAULTS.defaultTTL;
56551
- this.maxSize = options.maxSize || CACHE_DEFAULTS.defaultMaxSize;
56552
- this.name = options.name || CACHE_DEFAULTS.defaultName;
57193
+ this.defaultTTL = options.defaultTTL || CACHE_DEFAULTS2.defaultTTL;
57194
+ this.maxSize = options.maxSize || CACHE_DEFAULTS2.defaultMaxSize;
57195
+ this.name = options.name || CACHE_DEFAULTS2.defaultName;
56553
57196
  }
56554
57197
  get(key) {
56555
57198
  const entry = this.cache.get(key);
@@ -56640,23 +57283,23 @@ class TimebackCacheManager {
56640
57283
  enrollmentCache;
56641
57284
  constructor() {
56642
57285
  this.studentCache = new TimebackCache({
56643
- defaultTTL: CACHE_DEFAULTS.studentTTL,
56644
- maxSize: CACHE_DEFAULTS.studentMaxSize,
57286
+ defaultTTL: CACHE_DEFAULTS2.studentTTL,
57287
+ maxSize: CACHE_DEFAULTS2.studentMaxSize,
56645
57288
  name: "StudentCache"
56646
57289
  });
56647
57290
  this.assessmentLineItemCache = new TimebackCache({
56648
- defaultTTL: CACHE_DEFAULTS.assessmentTTL,
56649
- maxSize: CACHE_DEFAULTS.assessmentMaxSize,
57291
+ defaultTTL: CACHE_DEFAULTS2.assessmentTTL,
57292
+ maxSize: CACHE_DEFAULTS2.assessmentMaxSize,
56650
57293
  name: "AssessmentLineItemCache"
56651
57294
  });
56652
57295
  this.resourceMasteryCache = new TimebackCache({
56653
- defaultTTL: CACHE_DEFAULTS.assessmentTTL,
56654
- maxSize: CACHE_DEFAULTS.assessmentMaxSize,
57296
+ defaultTTL: CACHE_DEFAULTS2.assessmentTTL,
57297
+ maxSize: CACHE_DEFAULTS2.assessmentMaxSize,
56655
57298
  name: "ResourceMasteryCache"
56656
57299
  });
56657
57300
  this.enrollmentCache = new TimebackCache({
56658
- defaultTTL: CACHE_DEFAULTS.enrollmentTTL,
56659
- maxSize: CACHE_DEFAULTS.enrollmentMaxSize,
57301
+ defaultTTL: CACHE_DEFAULTS2.enrollmentTTL,
57302
+ maxSize: CACHE_DEFAULTS2.enrollmentMaxSize,
56660
57303
  name: "EnrollmentCache"
56661
57304
  });
56662
57305
  }
@@ -56912,18 +57555,18 @@ class MasteryTracker {
56912
57555
  await this.onerosterNamespace.assessmentLineItems.findOrCreate(lineItemId, {
56913
57556
  sourcedId: lineItemId,
56914
57557
  title: "Mastery Completion",
56915
- status: ONEROSTER_STATUS.active,
57558
+ status: ONEROSTER_STATUS2.active,
56916
57559
  ...classId ? { class: { sourcedId: classId } } : { course: { sourcedId: ids.course } },
56917
57560
  ...ids.componentResource ? { componentResource: { sourcedId: ids.componentResource } } : {}
56918
57561
  });
56919
57562
  await this.onerosterNamespace.assessmentResults.upsert(resultId, {
56920
57563
  sourcedId: resultId,
56921
- status: ONEROSTER_STATUS.active,
57564
+ status: ONEROSTER_STATUS2.active,
56922
57565
  assessmentLineItem: { sourcedId: lineItemId },
56923
57566
  student: { sourcedId: studentId },
56924
57567
  score: 100,
56925
57568
  scoreDate: new Date().toISOString(),
56926
- scoreStatus: SCORE_STATUS.fullyGraded,
57569
+ scoreStatus: SCORE_STATUS2.fullyGraded,
56927
57570
  inProgress: "false",
56928
57571
  metadata: {
56929
57572
  isMasteryCompletion: true,
@@ -56935,6 +57578,7 @@ class MasteryTracker {
56935
57578
  "app.timeback.line_item_id": lineItemId,
56936
57579
  "app.timeback.result_id": resultId
56937
57580
  });
57581
+ return true;
56938
57582
  } catch (error) {
56939
57583
  addEvent("timeback.mastery_completion_failed", {
56940
57584
  "app.timeback.student_id": studentId,
@@ -56942,6 +57586,7 @@ class MasteryTracker {
56942
57586
  "exception.type": errorType(error),
56943
57587
  "app.error.message": errorMessage(error)
56944
57588
  });
57589
+ return false;
56945
57590
  }
56946
57591
  }
56947
57592
  async revokeCompletionEntry(studentId, courseId, classId, appName) {
@@ -56952,18 +57597,18 @@ class MasteryTracker {
56952
57597
  await this.onerosterNamespace.assessmentLineItems.findOrCreate(lineItemId, {
56953
57598
  sourcedId: lineItemId,
56954
57599
  title: "Mastery Completion",
56955
- status: ONEROSTER_STATUS.active,
57600
+ status: ONEROSTER_STATUS2.active,
56956
57601
  ...classId ? { class: { sourcedId: classId } } : { course: { sourcedId: ids.course } },
56957
57602
  ...ids.componentResource ? { componentResource: { sourcedId: ids.componentResource } } : {}
56958
57603
  });
56959
57604
  await this.onerosterNamespace.assessmentResults.upsert(resultId, {
56960
57605
  sourcedId: resultId,
56961
- status: ONEROSTER_STATUS.active,
57606
+ status: ONEROSTER_STATUS2.active,
56962
57607
  assessmentLineItem: { sourcedId: lineItemId },
56963
57608
  student: { sourcedId: studentId },
56964
57609
  score: 0,
56965
57610
  scoreDate: new Date().toISOString(),
56966
- scoreStatus: SCORE_STATUS.notSubmitted,
57611
+ scoreStatus: SCORE_STATUS2.notSubmitted,
56967
57612
  inProgress: "true",
56968
57613
  metadata: {
56969
57614
  isMasteryCompletion: true,
@@ -56998,7 +57643,7 @@ class MasteryTracker {
56998
57643
  if (!playcademyMetadata) {
56999
57644
  return;
57000
57645
  }
57001
- const masterableUnits = isPlaycademyResourceMetadata(playcademyMetadata) ? playcademyMetadata.mastery?.masterableUnits : undefined;
57646
+ const masterableUnits = isPlaycademyResourceMetadata2(playcademyMetadata) ? playcademyMetadata.mastery?.masterableUnits : undefined;
57002
57647
  this.cacheManager.setResourceMasterableUnits(resourceId, masterableUnits ?? null);
57003
57648
  return masterableUnits;
57004
57649
  } catch (error) {
@@ -57078,13 +57723,11 @@ function validateSessionData(sessionData) {
57078
57723
 
57079
57724
  class ProgressRecorder {
57080
57725
  studentResolver;
57081
- cacheManager;
57082
57726
  onerosterNamespace;
57083
57727
  caliperNamespace;
57084
57728
  masteryTracker;
57085
- constructor(studentResolver, cacheManager, onerosterNamespace, caliperNamespace, masteryTracker) {
57729
+ constructor(studentResolver, onerosterNamespace, caliperNamespace, masteryTracker) {
57086
57730
  this.studentResolver = studentResolver;
57087
- this.cacheManager = cacheManager;
57088
57731
  this.onerosterNamespace = onerosterNamespace;
57089
57732
  this.caliperNamespace = caliperNamespace;
57090
57733
  this.masteryTracker = masteryTracker;
@@ -57093,26 +57736,37 @@ class ProgressRecorder {
57093
57736
  validateProgressData(progressData);
57094
57737
  const { ids, activityId, activityName, courseName, student } = await this.resolveContext(courseId, studentIdentifier, progressData);
57095
57738
  const { id: studentId, email: studentEmail } = student;
57096
- const {
57097
- score,
57098
- totalQuestions,
57099
- correctQuestions,
57100
- xpEarned = 0,
57101
- attemptNumber
57102
- } = progressData;
57103
- let extensions = progressData.extensions;
57104
- const masteryProgress = await this.masteryTracker.checkProgress({
57105
- studentId,
57106
- courseId,
57107
- resourceId: ids.resource,
57108
- masteredUnits: progressData.masteredUnits ?? 0,
57109
- masteredUnitsAbsolute: progressData.masteredUnitsAbsolute
57739
+ const { totalQuestions, correctQuestions, xpEarned = 0, attemptNumber } = progressData;
57740
+ const activityUrl = this.caliperNamespace.buildActivityUrl({
57741
+ courseId: ids.course,
57742
+ activityId,
57743
+ sensorUrl: progressData.sensorUrl
57110
57744
  });
57745
+ const courseUrl = this.caliperNamespace.buildCourseUrl(ids.course);
57746
+ let caliperLineItemId;
57747
+ try {
57748
+ caliperLineItemId = computeCaliperLineItemId(activityUrl, ids.course, courseUrl);
57749
+ } catch (error) {
57750
+ setAttributes({ "app.timeback.course_id_not_url_safe": true });
57751
+ throw error;
57752
+ }
57753
+ const legacyLineItemId = `${ids.course}-${activityId}-assessment`;
57754
+ const [currentAttemptNumber, masteryProgress] = await Promise.all([
57755
+ this.resolveAttemptNumber(attemptNumber, studentId, caliperLineItemId, legacyLineItemId),
57756
+ this.masteryTracker.checkProgress({
57757
+ studentId,
57758
+ courseId,
57759
+ resourceId: ids.resource,
57760
+ masteredUnits: progressData.masteredUnits ?? 0,
57761
+ masteredUnitsAbsolute: progressData.masteredUnitsAbsolute
57762
+ })
57763
+ ]);
57764
+ setAttributes({ "app.timeback.attempt_number": currentAttemptNumber });
57765
+ let extensions = progressData.extensions;
57111
57766
  const effectiveMasteredUnits = masteryProgress ? masteryProgress.effectiveDelta : progressData.masteredUnits ?? 0;
57112
57767
  let pctCompleteApp;
57113
57768
  let masteryAchieved = false;
57114
- let scoreStatus = SCORE_STATUS.fullyGraded;
57115
- const inProgress = "false";
57769
+ let completionEntryWritten = false;
57116
57770
  const warnings = masteryProgress?.writeWarning ? [masteryProgress.writeWarning] : undefined;
57117
57771
  if (masteryProgress) {
57118
57772
  masteryAchieved = masteryProgress.masteryAchieved;
@@ -57121,32 +57775,12 @@ class ProgressRecorder {
57121
57775
  ...extensions,
57122
57776
  ...pctCompleteApp !== undefined ? { pctCompleteApp } : {}
57123
57777
  };
57124
- if (masteryAchieved) {
57125
- scoreStatus = SCORE_STATUS.fullyGraded;
57126
- }
57127
57778
  }
57128
- const actualLineItemId = await this.resolveAssessmentLineItem(activityId, activityName, progressData.classId, ids);
57129
- const currentAttemptNumber = await this.resolveAttemptNumber(attemptNumber, score, studentId, actualLineItemId);
57130
- if (score !== undefined) {
57131
- await this.createGradebookEntry({
57132
- lineItemId: actualLineItemId,
57133
- studentId,
57134
- attemptNumber: currentAttemptNumber,
57135
- score,
57136
- xp: xpEarned,
57137
- scoreStatus,
57138
- inProgress,
57139
- appName: progressData.appName,
57140
- totalQuestions,
57141
- correctQuestions,
57142
- masteredUnits: effectiveMasteredUnits || undefined,
57143
- pctCompleteApp
57144
- });
57145
- } else {
57146
- setAttribute("app.timeback.score_provided", false);
57779
+ if (masteryAchieved) {
57780
+ setAttributes({ "app.timeback.mastery_achieved": true });
57147
57781
  }
57148
57782
  if (masteryAchieved) {
57149
- await this.masteryTracker.createCompletionEntry(studentId, courseId, progressData.classId, progressData.appName);
57783
+ completionEntryWritten = await this.masteryTracker.createCompletionEntry(studentId, courseId, progressData.classId, progressData.appName);
57150
57784
  await this.emitCourseCompletionHistoryEvent({
57151
57785
  studentId,
57152
57786
  studentEmail,
@@ -57162,30 +57796,36 @@ class ProgressRecorder {
57162
57796
  if (masteryProgress?.masteryRevoked) {
57163
57797
  await this.masteryTracker.revokeCompletionEntry(studentId, courseId, progressData.classId, progressData.appName);
57164
57798
  }
57165
- await this.emitCaliperEvent({
57166
- studentId,
57167
- studentEmail,
57168
- gameId: progressData.gameId,
57169
- activityId,
57170
- activityName,
57171
- courseId: ids.course,
57172
- courseName,
57173
- totalQuestions,
57174
- correctQuestions,
57175
- xpEarned,
57176
- masteredUnits: effectiveMasteredUnits || undefined,
57177
- attemptNumber: currentAttemptNumber,
57178
- progressData,
57179
- extensions,
57180
- runId: progressData.runId
57181
- });
57799
+ try {
57800
+ await this.emitCaliperEvent({
57801
+ studentId,
57802
+ studentEmail,
57803
+ gameId: progressData.gameId,
57804
+ activityId,
57805
+ activityName,
57806
+ courseId: ids.course,
57807
+ courseName,
57808
+ totalQuestions,
57809
+ correctQuestions,
57810
+ xpEarned,
57811
+ masteredUnits: effectiveMasteredUnits || undefined,
57812
+ attemptNumber: currentAttemptNumber,
57813
+ objectId: activityUrl,
57814
+ progressData,
57815
+ extensions,
57816
+ runId: progressData.runId
57817
+ });
57818
+ } catch (error) {
57819
+ if (completionEntryWritten) {
57820
+ setAttributes({ "app.timeback.completion_orphaned": true });
57821
+ }
57822
+ throw error;
57823
+ }
57182
57824
  return {
57183
57825
  xpAwarded: xpEarned,
57184
57826
  attemptNumber: currentAttemptNumber,
57185
57827
  masteredUnitsApplied: effectiveMasteredUnits,
57186
57828
  pctCompleteApp,
57187
- scoreStatus,
57188
- inProgress,
57189
57829
  ...warnings ? { warnings } : {}
57190
57830
  };
57191
57831
  }
@@ -57197,89 +57837,48 @@ class ProgressRecorder {
57197
57837
  const student = await this.studentResolver.resolve(studentIdentifier, progressData.studentEmail);
57198
57838
  return { ids, activityId, activityName, courseName, student };
57199
57839
  }
57200
- async resolveAssessmentLineItem(activityId, activityName, classId, ids) {
57201
- const lineItemId = `${ids.course}-${activityId}-assessment`;
57202
- let actualLineItemId = this.cacheManager.getAssessmentLineItem(lineItemId);
57203
- if (!actualLineItemId) {
57204
- actualLineItemId = await this.getOrCreateLineItem(lineItemId, activityName, classId, ids);
57205
- this.cacheManager.setAssessmentLineItem(lineItemId, actualLineItemId);
57206
- }
57207
- return actualLineItemId;
57208
- }
57209
- async resolveAttemptNumber(providedAttemptNumber, score, studentId, lineItemId) {
57840
+ async resolveAttemptNumber(providedAttemptNumber, studentId, caliperLineItemId, legacyLineItemId) {
57210
57841
  if (providedAttemptNumber) {
57842
+ setAttributes({ "app.timeback.attempt_source": "provided" });
57211
57843
  return providedAttemptNumber;
57212
57844
  }
57213
- if (score !== undefined) {
57214
- return this.determineAttemptNumber(studentId, lineItemId);
57215
- }
57216
- return 1;
57217
- }
57218
- async getOrCreateLineItem(lineItemId, activityName, classId, ids) {
57219
- try {
57220
- const lineItem = await this.onerosterNamespace.assessmentLineItems.findOrCreate(lineItemId, {
57221
- sourcedId: lineItemId,
57222
- title: activityName,
57223
- status: ONEROSTER_STATUS.active,
57224
- ...classId ? { class: { sourcedId: classId } } : { course: { sourcedId: ids.course } }
57845
+ const caliperAttempt = await this.getLatestActivityAttempt(studentId, caliperLineItemId, "attempt");
57846
+ if (caliperAttempt !== null) {
57847
+ const next = caliperAttempt + 1;
57848
+ setAttributes({
57849
+ "app.timeback.attempt_source": caliperAttempt > 0 ? "caliper" : "caliper_unreadable"
57225
57850
  });
57226
- if (!lineItem.sourcedId) {
57227
- throw new TimebackError(`Assessment line item created but has no sourcedId. This should not happen and indicates an upstream API issue.`);
57228
- }
57229
- return lineItem.sourcedId;
57230
- } catch (error) {
57231
- if (error instanceof TimebackApiError && error.status === 404) {
57232
- const errorDetails = error.details;
57233
- const description = errorDetails?.imsx_description || error.message;
57234
- if (description.includes("course") && description.includes("not found") || description.includes("component") && description.includes("not found") || description.includes("resource") && description.includes("not found") || description.includes("componentResource") && description.includes("not found")) {
57235
- throw new ResourceNotFoundError("TimeBack resources", "setup-not-run");
57236
- }
57851
+ if (caliperAttempt === 0) {
57852
+ setAttributes({
57853
+ "app.timeback.attempt_regressed": true,
57854
+ "app.timeback.attempt_emitted": next
57855
+ });
57237
57856
  }
57238
- throw error;
57857
+ return next;
57239
57858
  }
57240
- }
57241
- async determineAttemptNumber(studentId, lineItemId) {
57242
- const stats = await this.onerosterNamespace.assessmentResults.getAttemptStats(studentId, lineItemId);
57243
- if (stats) {
57244
- return stats.activeAttemptCount + 1;
57859
+ const legacyAttempt = await this.getLatestActivityAttempt(studentId, legacyLineItemId, "attemptNumber");
57860
+ if (legacyAttempt !== null) {
57861
+ setAttributes({
57862
+ "app.timeback.attempt_source": "legacy_seed",
57863
+ "app.timeback.legacy_seed_attempt": legacyAttempt
57864
+ });
57865
+ return legacyAttempt + 1;
57245
57866
  }
57867
+ setAttributes({ "app.timeback.attempt_source": "first" });
57246
57868
  return 1;
57247
57869
  }
57248
- async createGradebookEntry({
57249
- lineItemId,
57250
- studentId,
57251
- attemptNumber,
57252
- score,
57253
- xp,
57254
- scoreStatus,
57255
- inProgress,
57256
- appName,
57257
- totalQuestions,
57258
- correctQuestions,
57259
- masteredUnits,
57260
- pctCompleteApp
57261
- }) {
57262
- const timestamp3 = Date.now().toString(36);
57263
- const resultId = `${lineItemId}:${studentId}:${timestamp3}`;
57264
- await this.onerosterNamespace.assessmentResults.upsert(resultId, {
57265
- sourcedId: resultId,
57266
- status: ONEROSTER_STATUS.active,
57267
- assessmentLineItem: { sourcedId: lineItemId },
57268
- student: { sourcedId: studentId },
57269
- score,
57270
- scoreDate: new Date().toISOString(),
57271
- scoreStatus,
57272
- inProgress,
57273
- metadata: {
57274
- xp,
57275
- attemptNumber,
57276
- appName,
57277
- ...totalQuestions !== undefined ? { totalQuestions } : {},
57278
- ...correctQuestions !== undefined ? { correctQuestions } : {},
57279
- ...masteredUnits !== undefined ? { masteredUnits } : {},
57280
- ...pctCompleteApp !== undefined ? { pctCompleteApp } : {}
57281
- }
57870
+ async getLatestActivityAttempt(studentId, lineItemId, metadataField) {
57871
+ const results = await this.onerosterNamespace.assessmentResults.listOrThrow({
57872
+ filter: `student.sourcedId='${escapeFilterValue(studentId)}' AND assessmentLineItem.sourcedId='${escapeFilterValue(lineItemId)}' AND status='active'`,
57873
+ sort: "dateLastModified",
57874
+ orderBy: "desc",
57875
+ limit: 1
57282
57876
  });
57877
+ const latest = results[0];
57878
+ if (!latest) {
57879
+ return null;
57880
+ }
57881
+ return latest.metadata?.[metadataField] || 0;
57283
57882
  }
57284
57883
  async emitCaliperEvent({
57285
57884
  studentId,
@@ -57294,6 +57893,7 @@ class ProgressRecorder {
57294
57893
  xpEarned,
57295
57894
  masteredUnits,
57296
57895
  attemptNumber,
57896
+ objectId,
57297
57897
  progressData,
57298
57898
  extensions,
57299
57899
  runId
@@ -57311,12 +57911,18 @@ class ProgressRecorder {
57311
57911
  xpEarned,
57312
57912
  masteredUnits,
57313
57913
  attemptNumber,
57914
+ objectId,
57915
+ process: true,
57314
57916
  subject: progressData.subject,
57315
57917
  appName: progressData.appName,
57316
57918
  sensorUrl: progressData.sensorUrl,
57317
57919
  extensions: extensions || progressData.extensions,
57318
57920
  ...runId ? { runId } : {}
57319
- }).catch(catchEvent("timeback.caliper_event_failed"));
57921
+ }).catch((error) => {
57922
+ setAttributes({ "app.timeback.caliper_emit_failed": true });
57923
+ catchEvent("timeback.caliper_event_failed")(error);
57924
+ throw error;
57925
+ });
57320
57926
  }
57321
57927
  async emitCourseCompletionHistoryEvent(data) {
57322
57928
  await this.caliperNamespace.emitActivityEvent({
@@ -57494,15 +58100,15 @@ class TimebackClient {
57494
58100
  masteryTracker;
57495
58101
  constructor(config2) {
57496
58102
  this.baseUrl = TimebackClient.resolveBaseUrl(config2?.baseUrl);
57497
- this.environment = process.env[ENV_VARS.environment] === "staging" ? "staging" : "production";
58103
+ this.environment = process.env[ENV_VARS2.environment] === "staging" ? "staging" : "production";
57498
58104
  this.caliperUrl = TimebackClient.resolveCaliperUrl(config2?.caliperUrl, this.environment);
57499
58105
  this.authUrl = config2?.credentials?.authUrl;
57500
58106
  this.credentials = config2?.credentials;
57501
58107
  this.qtiCredentials = config2?.qtiCredentials;
57502
58108
  this.options = {
57503
- retries: config2?.options?.retries ?? HTTP_DEFAULTS.retries,
57504
- cacheDuration: config2?.options?.cacheDuration ?? AUTH_DEFAULTS.tokenCacheDuration,
57505
- timeout: config2?.options?.timeout ?? HTTP_DEFAULTS.timeout,
58109
+ retries: config2?.options?.retries ?? HTTP_DEFAULTS2.retries,
58110
+ cacheDuration: config2?.options?.cacheDuration ?? AUTH_DEFAULTS2.tokenCacheDuration,
58111
+ timeout: config2?.options?.timeout ?? HTTP_DEFAULTS2.timeout,
57506
58112
  sensorUrl: config2?.options?.sensorUrl
57507
58113
  };
57508
58114
  this.oneroster = createOneRosterNamespace(this);
@@ -57512,7 +58118,7 @@ class TimebackClient {
57512
58118
  this.cacheManager = new TimebackCacheManager;
57513
58119
  this.studentResolver = new StudentResolver(this.cacheManager, this.oneroster);
57514
58120
  this.masteryTracker = new MasteryTracker(this.cacheManager, this.oneroster, this.edubridge);
57515
- this.progressRecorder = new ProgressRecorder(this.studentResolver, this.cacheManager, this.oneroster, this.caliper, this.masteryTracker);
58121
+ this.progressRecorder = new ProgressRecorder(this.studentResolver, this.oneroster, this.caliper, this.masteryTracker);
57516
58122
  this.sessionRecorder = new SessionRecorder(this.studentResolver, this.caliper);
57517
58123
  this.adminEventRecorder = new AdminEventRecorder(this.studentResolver, this.oneroster, this.caliper, this.environment);
57518
58124
  if (this.credentials) {
@@ -57521,16 +58127,16 @@ class TimebackClient {
57521
58127
  }
57522
58128
  static async init(config2) {
57523
58129
  let credentials = config2?.credentials;
57524
- if (!credentials && process.env[ENV_VARS.clientId] && process.env[ENV_VARS.clientSecret]) {
58130
+ if (!credentials && process.env[ENV_VARS2.clientId] && process.env[ENV_VARS2.clientSecret]) {
57525
58131
  credentials = {
57526
- clientId: process.env[ENV_VARS.clientId],
57527
- clientSecret: process.env[ENV_VARS.clientSecret]
58132
+ clientId: process.env[ENV_VARS2.clientId],
58133
+ clientSecret: process.env[ENV_VARS2.clientSecret]
57528
58134
  };
57529
58135
  }
57530
- const qtiCredentials = config2?.qtiCredentials ?? (process.env[ENV_VARS.qtiClientId] && process.env[ENV_VARS.qtiClientSecret] && process.env[ENV_VARS.qtiAuthUrl] ? {
57531
- clientId: process.env[ENV_VARS.qtiClientId],
57532
- clientSecret: process.env[ENV_VARS.qtiClientSecret],
57533
- authUrl: process.env[ENV_VARS.qtiAuthUrl]
58136
+ const qtiCredentials = config2?.qtiCredentials ?? (process.env[ENV_VARS2.qtiClientId] && process.env[ENV_VARS2.qtiClientSecret] && process.env[ENV_VARS2.qtiAuthUrl] ? {
58137
+ clientId: process.env[ENV_VARS2.qtiClientId],
58138
+ clientSecret: process.env[ENV_VARS2.qtiClientSecret],
58139
+ authUrl: process.env[ENV_VARS2.qtiAuthUrl]
57534
58140
  } : undefined);
57535
58141
  return new TimebackClient({
57536
58142
  ...config2,
@@ -57543,15 +58149,15 @@ class TimebackClient {
57543
58149
  if (explicit) {
57544
58150
  return explicit;
57545
58151
  }
57546
- const envName = process.env[ENV_VARS.environment] === "staging" ? "staging" : "production";
57547
- return TIMEBACK_API_URLS[envName];
58152
+ const envName = process.env[ENV_VARS2.environment] === "staging" ? "staging" : "production";
58153
+ return TIMEBACK_API_URLS2[envName];
57548
58154
  }
57549
58155
  static resolveCaliperUrl(input, environment = "production") {
57550
58156
  const explicit = (input || "").trim();
57551
58157
  if (explicit) {
57552
58158
  return explicit;
57553
58159
  }
57554
- return CALIPER_API_URLS[environment];
58160
+ return CALIPER_API_URLS2[environment];
57555
58161
  }
57556
58162
  getBaseUrl() {
57557
58163
  return this.baseUrl;
@@ -57672,14 +58278,14 @@ class TimebackClient {
57672
58278
  await this._ensureAuthenticated();
57673
58279
  return this.token;
57674
58280
  }
57675
- throw new TimebackAuthenticationError(`QTI credentials are required. Set ${ENV_VARS.qtiClientId}, ${ENV_VARS.qtiClientSecret}, and ${ENV_VARS.qtiAuthUrl}.`);
58281
+ throw new TimebackAuthenticationError(`QTI credentials are required. Set ${ENV_VARS2.qtiClientId}, ${ENV_VARS2.qtiClientSecret}, and ${ENV_VARS2.qtiAuthUrl}.`);
57676
58282
  }
57677
58283
  async ensureQtiAuthenticated() {
57678
58284
  if (this.isQtiAuthenticated()) {
57679
58285
  return;
57680
58286
  }
57681
58287
  if (!this.qtiCredentials) {
57682
- throw new TimebackAuthenticationError(`QTI credentials are required. Set ${ENV_VARS.qtiClientId}, ${ENV_VARS.qtiClientSecret}, and ${ENV_VARS.qtiAuthUrl}.`);
58288
+ throw new TimebackAuthenticationError(`QTI credentials are required. Set ${ENV_VARS2.qtiClientId}, ${ENV_VARS2.qtiClientSecret}, and ${ENV_VARS2.qtiAuthUrl}.`);
57683
58289
  }
57684
58290
  try {
57685
58291
  const tokenData = await getTimebackTokenResponse({
@@ -57691,11 +58297,11 @@ class TimebackClient {
57691
58297
  this.setQtiToken(tokenData.access_token, tokenData.expires_in);
57692
58298
  } catch (error) {
57693
58299
  const errMsg = errorMessage(error);
57694
- throw new TimebackAuthenticationError(`QTI authentication failed: ${errMsg}. Verify that ${ENV_VARS.qtiClientId}, ${ENV_VARS.qtiClientSecret}, and ${ENV_VARS.qtiAuthUrl} are correct.`);
58300
+ throw new TimebackAuthenticationError(`QTI authentication failed: ${errMsg}. Verify that ${ENV_VARS2.qtiClientId}, ${ENV_VARS2.qtiClientSecret}, and ${ENV_VARS2.qtiAuthUrl} are correct.`);
57695
58301
  }
57696
58302
  }
57697
58303
  canUseCoreCredentialsForQti() {
57698
- return this.baseUrl.replace(/\/$/, "") === TIMEBACK_API_URLS.production;
58304
+ return this.baseUrl.replace(/\/$/, "") === TIMEBACK_API_URLS2.production;
57699
58305
  }
57700
58306
  async resolveStudent(studentIdentifier, providedEmail) {
57701
58307
  return this.studentResolver.resolve(studentIdentifier, providedEmail);
@@ -57716,8 +58322,8 @@ class TimebackClient {
57716
58322
  await this._ensureAuthenticated();
57717
58323
  const edubridgeEnrollments = await this.edubridge.enrollments.listByUser(studentId);
57718
58324
  const enrollments = edubridgeEnrollments.map((enrollment) => {
57719
- const grades = enrollment.course.grades ? enrollment.course.grades.map((g) => parseInt(g, 10)).filter(isTimebackGrade2) : null;
57720
- const subjects = enrollment.course.subjects ? enrollment.course.subjects.filter(isTimebackSubject2) : null;
58325
+ const grades = enrollment.course.grades ? enrollment.course.grades.map((g) => parseInt(g, 10)).filter(isTimebackGrade3) : null;
58326
+ const subjects = enrollment.course.subjects ? enrollment.course.subjects.filter(isTimebackSubject3) : null;
57721
58327
  return {
57722
58328
  sourcedId: enrollment.id,
57723
58329
  title: enrollment.course.title,
@@ -57783,9 +58389,9 @@ class TimebackClient {
57783
58389
  if (options?.include?.perCourse) {
57784
58390
  const gradeStr = enrollment.course.grades?.[0];
57785
58391
  const parsedGrade = gradeStr ? parseInt(gradeStr, 10) : 0;
57786
- const grade = isTimebackGrade2(parsedGrade) ? parsedGrade : 0;
58392
+ const grade = isTimebackGrade3(parsedGrade) ? parsedGrade : 0;
57787
58393
  const subjectStr = enrollment.course.subjects?.[0];
57788
- const subject = subjectStr && isTimebackSubject2(subjectStr) ? subjectStr : "None";
58394
+ const subject = subjectStr && isTimebackSubject3(subjectStr) ? subjectStr : "None";
57789
58395
  courses.push({
57790
58396
  grade,
57791
58397
  subject,
@@ -57859,9 +58465,9 @@ class TimebackClient {
57859
58465
  if (options?.include?.perCourse) {
57860
58466
  const gradeStr = enrollment.course.grades?.[0];
57861
58467
  const parsedGrade = gradeStr ? parseInt(gradeStr, 10) : 0;
57862
- const grade = isTimebackGrade2(parsedGrade) ? parsedGrade : 0;
58468
+ const grade = isTimebackGrade3(parsedGrade) ? parsedGrade : 0;
57863
58469
  const subjectStr = enrollment.course.subjects?.[0];
57864
- const subject = subjectStr && isTimebackSubject2(subjectStr) ? subjectStr : "None";
58470
+ const subject = subjectStr && isTimebackSubject3(subjectStr) ? subjectStr : "None";
57865
58471
  courses.push({
57866
58472
  grade,
57867
58473
  subject,
@@ -57917,6 +58523,12 @@ class TimebackClient {
57917
58523
  async cleanup(courseId) {
57918
58524
  return deleteTimebackResources(this, courseId);
57919
58525
  }
58526
+ async deactivateCourse(courseId) {
58527
+ return updateTimebackCourseStatus(this, courseId, "tobedeleted");
58528
+ }
58529
+ async reactivateCourse(courseId) {
58530
+ return updateTimebackCourseStatus(this, courseId, "active");
58531
+ }
57920
58532
  }
57921
58533
  var __defProp22;
57922
58534
  var __export2 = (target, all) => {
@@ -57928,46 +58540,50 @@ var __export2 = (target, all) => {
57928
58540
  set: (newValue) => all[name3] = () => newValue
57929
58541
  });
57930
58542
  };
57931
- var __esm2 = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
57932
- var TIMEBACK_API_URLS;
58543
+ var __esm3 = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
58544
+ var TIMEBACK_API_URLS2;
57933
58545
  var QTI_API_URL = "https://qti.alpha-1edtech.ai/api";
57934
- var TIMEBACK_AUTH_URLS;
57935
- var CALIPER_API_URLS;
57936
- var ONEROSTER_ENDPOINTS;
57937
- var QTI_ENDPOINTS;
57938
- var CALIPER_ENDPOINTS;
57939
- var CALIPER_CONSTANTS;
57940
- var TIMEBACK_EVENT_TYPES;
57941
- var TIMEBACK_ACTIONS;
57942
- var TIMEBACK_TYPES;
57943
- var ACTIVITY_METRIC_TYPES;
57944
- var TIME_METRIC_TYPES;
57945
- var TIMEBACK_SUBJECTS2;
57946
- var TIMEBACK_GRADE_LEVELS;
57947
- var TIMEBACK_GRADE_LEVEL_LABELS;
57948
- var CALIPER_SUBJECTS;
57949
- var ONEROSTER_STATUS;
57950
- var SCORE_STATUS;
57951
- var ENV_VARS;
57952
- var HTTP_DEFAULTS;
57953
- var AUTH_DEFAULTS;
57954
- var CACHE_DEFAULTS;
57955
- var CONFIG_DEFAULTS;
57956
- var PLAYCADEMY_DEFAULTS;
57957
- var RESOURCE_DEFAULTS;
57958
- var HTTP_STATUS;
57959
- var ERROR_NAMES;
57960
- var init_constants4;
57961
- var exports_verify;
57962
- var init_verify;
58546
+ var TIMEBACK_AUTH_URLS2;
58547
+ var CALIPER_API_URLS2;
58548
+ var ONEROSTER_ENDPOINTS2;
58549
+ var QTI_ENDPOINTS2;
58550
+ var CALIPER_ENDPOINTS2;
58551
+ var CALIPER_CONSTANTS2;
58552
+ var TIMEBACK_EVENT_TYPES2;
58553
+ var TIMEBACK_ACTIONS2;
58554
+ var TIMEBACK_TYPES2;
58555
+ var ACTIVITY_METRIC_TYPES2;
58556
+ var TIME_METRIC_TYPES2;
58557
+ var TIMEBACK_SUBJECTS3;
58558
+ var TIMEBACK_GRADE_LEVELS2;
58559
+ var TIMEBACK_GRADE_LEVEL_LABELS2;
58560
+ var CALIPER_SUBJECTS2;
58561
+ var ONEROSTER_STATUS2;
58562
+ var SCORE_STATUS2;
58563
+ var ENV_VARS2;
58564
+ var HTTP_DEFAULTS2;
58565
+ var AUTH_DEFAULTS2;
58566
+ var CACHE_DEFAULTS2;
58567
+ var CONFIG_DEFAULTS2;
58568
+ var PLAYCADEMY_DEFAULTS2;
58569
+ var RESOURCE_DEFAULTS2;
58570
+ var HTTP_STATUS2;
58571
+ var ERROR_NAMES2;
58572
+ var init_constants5;
57963
58573
  var TimebackError;
57964
58574
  var TimebackApiError;
57965
58575
  var TimebackAuthenticationError;
57966
58576
  var StudentNotFoundError;
57967
58577
  var ConfigurationError;
58578
+ var ResourceAlreadyExistsError;
57968
58579
  var ResourceNotFoundError;
57969
- var SUBJECT_VALUES;
57970
- var GRADE_VALUES;
58580
+ var init_errors4;
58581
+ var UUID_REGEX2;
58582
+ var init_ids;
58583
+ var exports_verify;
58584
+ var init_verify;
58585
+ var SUBJECT_VALUES2;
58586
+ var GRADE_VALUES2;
57971
58587
  var TimebackAuthError;
57972
58588
  var UUID_PATTERN;
57973
58589
  var storage;
@@ -58000,320 +58616,7 @@ var init_dist2 = __esm(() => {
58000
58616
  init_spans();
58001
58617
  init_esm();
58002
58618
  __defProp22 = Object.defineProperty;
58003
- init_constants4 = __esm2(() => {
58004
- TIMEBACK_API_URLS = {
58005
- production: "https://api.alpha-1edtech.ai",
58006
- staging: "https://api.staging.alpha-1edtech.com"
58007
- };
58008
- TIMEBACK_AUTH_URLS = {
58009
- production: "https://prod-beyond-timeback-api-2-idp.auth.us-east-1.amazoncognito.com",
58010
- staging: "https://alpha-auth-development-idp.auth.us-west-2.amazoncognito.com"
58011
- };
58012
- CALIPER_API_URLS = {
58013
- production: "https://caliper.alpha-1edtech.ai",
58014
- staging: "https://caliper-staging.alpha-1edtech.com"
58015
- };
58016
- ONEROSTER_ENDPOINTS = {
58017
- organizations: "/ims/oneroster/rostering/v1p2/orgs",
58018
- courses: "/ims/oneroster/rostering/v1p2/courses",
58019
- courseComponents: "/ims/oneroster/rostering/v1p2/courses/components",
58020
- resources: "/ims/oneroster/resources/v1p2/resources",
58021
- componentResources: "/ims/oneroster/rostering/v1p2/courses/component-resources",
58022
- classes: "/ims/oneroster/rostering/v1p2/classes",
58023
- enrollments: "/ims/oneroster/rostering/v1p2/enrollments",
58024
- assessmentLineItems: "/ims/oneroster/gradebook/v1p2/assessmentLineItems",
58025
- assessmentResults: "/ims/oneroster/gradebook/v1p2/assessmentResults",
58026
- users: "/ims/oneroster/rostering/v1p2/users"
58027
- };
58028
- QTI_ENDPOINTS = {
58029
- assessmentTests: "/assessment-tests",
58030
- assessmentItems: "/assessment-items"
58031
- };
58032
- CALIPER_ENDPOINTS = {
58033
- event: "/caliper/event",
58034
- events: "/caliper/events",
58035
- validate: "/caliper/event/validate"
58036
- };
58037
- CALIPER_CONSTANTS = {
58038
- context: "http://purl.imsglobal.org/ctx/caliper/v1p2",
58039
- profile: "TimebackProfile",
58040
- dataVersion: "http://purl.imsglobal.org/ctx/caliper/v1p2"
58041
- };
58042
- TIMEBACK_EVENT_TYPES = {
58043
- activityEvent: "ActivityEvent",
58044
- timeSpentEvent: "TimeSpentEvent"
58045
- };
58046
- TIMEBACK_ACTIONS = {
58047
- completed: "Completed",
58048
- spentTime: "SpentTime"
58049
- };
58050
- TIMEBACK_TYPES = {
58051
- user: "TimebackUser",
58052
- activityContext: "TimebackActivityContext",
58053
- activityMetricsCollection: "TimebackActivityMetricsCollection",
58054
- timeSpentMetricsCollection: "TimebackTimeSpentMetricsCollection"
58055
- };
58056
- ACTIVITY_METRIC_TYPES = {
58057
- totalQuestions: "totalQuestions",
58058
- correctQuestions: "correctQuestions",
58059
- xpEarned: "xpEarned",
58060
- masteredUnits: "masteredUnits"
58061
- };
58062
- TIME_METRIC_TYPES = {
58063
- active: "active",
58064
- inactive: "inactive",
58065
- waste: "waste",
58066
- unknown: "unknown",
58067
- antiPattern: "anti-pattern"
58068
- };
58069
- TIMEBACK_SUBJECTS2 = [
58070
- "Math",
58071
- "FastMath",
58072
- "Science",
58073
- "Social Studies",
58074
- "Language",
58075
- "Reading",
58076
- "Vocabulary",
58077
- "Writing"
58078
- ];
58079
- TIMEBACK_GRADE_LEVELS = [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
58080
- TIMEBACK_GRADE_LEVEL_LABELS = {
58081
- "-1": "pre-k",
58082
- "0": "kindergarten",
58083
- "1": "1st grade",
58084
- "2": "2nd grade",
58085
- "3": "3rd grade",
58086
- "4": "4th grade",
58087
- "5": "5th grade",
58088
- "6": "6th grade",
58089
- "7": "7th grade",
58090
- "8": "8th grade",
58091
- "9": "9th grade",
58092
- "10": "10th grade",
58093
- "11": "11th grade",
58094
- "12": "12th grade",
58095
- "13": "AP"
58096
- };
58097
- CALIPER_SUBJECTS = {
58098
- Reading: "Reading",
58099
- Language: "Language",
58100
- Vocabulary: "Vocabulary",
58101
- SocialStudies: "Social Studies",
58102
- Writing: "Writing",
58103
- Science: "Science",
58104
- FastMath: "FastMath",
58105
- Math: "Math",
58106
- None: "None"
58107
- };
58108
- ONEROSTER_STATUS = {
58109
- active: "active",
58110
- toBeDeleted: "tobedeleted"
58111
- };
58112
- SCORE_STATUS = {
58113
- exempt: "exempt",
58114
- fullyGraded: "fully graded",
58115
- notSubmitted: "not submitted",
58116
- partiallyGraded: "partially graded",
58117
- submitted: "submitted"
58118
- };
58119
- ENV_VARS = {
58120
- clientId: "TIMEBACK_CLIENT_ID",
58121
- clientSecret: "TIMEBACK_CLIENT_SECRET",
58122
- baseUrl: "TIMEBACK_BASE_URL",
58123
- environment: "TIMEBACK_ENVIRONMENT",
58124
- vendorResourceId: "TIMEBACK_VENDOR_RESOURCE_ID",
58125
- launchBaseUrl: "GAME_URL",
58126
- qtiClientId: "QTI_CLIENT_ID",
58127
- qtiClientSecret: "QTI_CLIENT_SECRET",
58128
- qtiAuthUrl: "QTI_AUTH_URL"
58129
- };
58130
- HTTP_DEFAULTS = {
58131
- timeout: 30000,
58132
- retries: 3,
58133
- retryBackoffBase: 2
58134
- };
58135
- AUTH_DEFAULTS = {
58136
- tokenCacheDuration: 50000
58137
- };
58138
- CACHE_DEFAULTS = {
58139
- defaultTTL: 600000,
58140
- defaultMaxSize: 500,
58141
- defaultName: "TimebackCache",
58142
- studentTTL: 600000,
58143
- studentMaxSize: 500,
58144
- assessmentTTL: 1800000,
58145
- assessmentMaxSize: 200,
58146
- enrollmentTTL: 5000,
58147
- enrollmentMaxSize: 100
58148
- };
58149
- CONFIG_DEFAULTS = {
58150
- fileNames: ["timeback.config.js", "timeback.config.json"]
58151
- };
58152
- PLAYCADEMY_DEFAULTS = {
58153
- organization: TIMEBACK_ORG_SOURCED_ID,
58154
- launchBaseUrls: PLAYCADEMY_BASE_URLS
58155
- };
58156
- RESOURCE_DEFAULTS = {
58157
- organization: {
58158
- name: TIMEBACK_ORG_NAME,
58159
- type: TIMEBACK_ORG_TYPE
58160
- },
58161
- course: {
58162
- gradingScheme: TIMEBACK_COURSE_DEFAULTS.gradingScheme,
58163
- level: TIMEBACK_COURSE_DEFAULTS.level,
58164
- metadata: {
58165
- goals: TIMEBACK_COURSE_DEFAULTS.goals,
58166
- metrics: TIMEBACK_COURSE_DEFAULTS.metrics
58167
- }
58168
- },
58169
- component: TIMEBACK_COMPONENT_DEFAULTS,
58170
- resource: TIMEBACK_RESOURCE_DEFAULTS,
58171
- componentResource: TIMEBACK_COMPONENT_RESOURCE_DEFAULTS
58172
- };
58173
- HTTP_STATUS = {
58174
- CLIENT_ERROR_MIN: 400,
58175
- CLIENT_ERROR_MAX: 500,
58176
- SERVER_ERROR_MIN: 500
58177
- };
58178
- ERROR_NAMES = {
58179
- timebackAuth: "TimebackAuthError",
58180
- timebackApi: "TimebackApiError",
58181
- timebackConfig: "TimebackConfigError",
58182
- timebackSdk: "TimebackSDKError"
58183
- };
58184
- });
58185
- exports_verify = {};
58186
- __export2(exports_verify, {
58187
- verifyTimebackResources: () => verifyTimebackResources,
58188
- fetchTimebackConfig: () => fetchTimebackConfig
58189
- });
58190
- init_verify = __esm2(() => {
58191
- init_constants4();
58192
- });
58193
- init_constants4();
58194
- TimebackError = class TimebackError2 extends Error {
58195
- constructor(message) {
58196
- super(message);
58197
- this.name = ERROR_NAMES.timebackSdk;
58198
- }
58199
- };
58200
- TimebackApiError = class TimebackApiError2 extends Error {
58201
- status;
58202
- details;
58203
- constructor(status, message, details) {
58204
- super(`${status} ${message}`);
58205
- this.name = ERROR_NAMES.timebackApi;
58206
- this.status = status;
58207
- this.details = details;
58208
- Object.setPrototypeOf(this, TimebackApiError2.prototype);
58209
- }
58210
- };
58211
- TimebackAuthenticationError = class TimebackAuthenticationError2 extends TimebackError {
58212
- constructor(message) {
58213
- super(message || "Authentication failed. Please verify TIMEBACK_CLIENT_ID and TIMEBACK_CLIENT_SECRET are set correctly.");
58214
- this.name = "TimebackAuthenticationError";
58215
- Object.setPrototypeOf(this, TimebackAuthenticationError2.prototype);
58216
- }
58217
- };
58218
- StudentNotFoundError = class StudentNotFoundError2 extends TimebackError {
58219
- identifier;
58220
- identifierType;
58221
- constructor(identifier, identifierType = "email") {
58222
- super(`Student not found with ${identifierType}: ${identifier}. Ensure the student exists in OneRoster. If this is a new student, they must be created in OneRoster first.`);
58223
- this.name = "StudentNotFoundError";
58224
- this.identifier = identifier;
58225
- this.identifierType = identifierType;
58226
- Object.setPrototypeOf(this, StudentNotFoundError2.prototype);
58227
- }
58228
- };
58229
- ConfigurationError = class ConfigurationError2 extends TimebackError {
58230
- field;
58231
- constructor(field, message) {
58232
- super(message || `Missing required configuration: ${field}. Please check your timeback.config.js file.`);
58233
- this.name = "ConfigurationError";
58234
- this.field = field;
58235
- Object.setPrototypeOf(this, ConfigurationError2.prototype);
58236
- }
58237
- };
58238
- ResourceNotFoundError = class ResourceNotFoundError2 extends TimebackError {
58239
- resourceType;
58240
- sourcedId;
58241
- constructor(resourceType, sourcedId) {
58242
- const message = sourcedId === "setup-not-run" ? `TimeBack resources have not been created yet. Please run 'bunx @playcademy/timeback setup' to create the required resources (organization, course, components, etc.) before recording progress.` : `${resourceType} with ID '${sourcedId}' not found`;
58243
- super(message);
58244
- this.name = "ResourceNotFoundError";
58245
- this.resourceType = resourceType;
58246
- this.sourcedId = sourcedId;
58247
- Object.setPrototypeOf(this, ResourceNotFoundError2.prototype);
58248
- }
58249
- };
58250
- init_constants4();
58251
- SUBJECT_VALUES = TIMEBACK_SUBJECTS2;
58252
- GRADE_VALUES = TIMEBACK_GRADE_LEVELS;
58253
- init_verify();
58254
- init_constants4();
58255
- init_constants4();
58256
- if (process.env.DEBUG === "true") {
58257
- process.env.TERM = "dumb";
58258
- }
58259
- TimebackAuthError = class TimebackAuthError2 extends Error {
58260
- statusCode;
58261
- constructor(message, statusCode) {
58262
- super(message);
58263
- this.name = ERROR_NAMES.timebackAuth;
58264
- this.statusCode = statusCode;
58265
- }
58266
- };
58267
- init_constants4();
58268
- UUID_PATTERN = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi;
58269
- storage = new AsyncLocalStorage;
58270
- init_constants4();
58271
- init_constants4();
58272
- init_constants4();
58273
- init_constants4();
58274
- init_constants4();
58275
- init_constants4();
58276
- init_constants4();
58277
- init_constants4();
58278
- EmailSchema = exports_external.string().email();
58279
- StudentSourcedIdSchema = exports_external.string().min(1, {
58280
- message: "Student sourcedId must be a non-empty string"
58281
- });
58282
- StudentIdentifierSchema = exports_external.union([EmailSchema, StudentSourcedIdSchema]);
58283
- });
58284
- var __esm3 = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
58285
- var TIMEBACK_API_URLS2;
58286
- var QTI_API_URL2 = "https://qti.alpha-1edtech.ai/api";
58287
- var TIMEBACK_AUTH_URLS2;
58288
- var CALIPER_API_URLS2;
58289
- var ONEROSTER_ENDPOINTS2;
58290
- var QTI_ENDPOINTS2;
58291
- var CALIPER_ENDPOINTS2;
58292
- var CALIPER_CONSTANTS2;
58293
- var TIMEBACK_EVENT_TYPES2;
58294
- var TIMEBACK_ACTIONS2;
58295
- var TIMEBACK_TYPES2;
58296
- var ACTIVITY_METRIC_TYPES2;
58297
- var TIME_METRIC_TYPES2;
58298
- var TIMEBACK_SUBJECTS3;
58299
- var TIMEBACK_GRADE_LEVELS2;
58300
- var TIMEBACK_GRADE_LEVEL_LABELS2;
58301
- var CALIPER_SUBJECTS2;
58302
- var ONEROSTER_STATUS2;
58303
- var SCORE_STATUS2;
58304
- var ENV_VARS2;
58305
- var HTTP_DEFAULTS2;
58306
- var AUTH_DEFAULTS2;
58307
- var CACHE_DEFAULTS2;
58308
- var CONFIG_DEFAULTS2;
58309
- var PLAYCADEMY_DEFAULTS2;
58310
- var RESOURCE_DEFAULTS2;
58311
- var HTTP_STATUS2;
58312
- var ERROR_NAMES2;
58313
- var init_constants6;
58314
- var init_constants5 = __esm(() => {
58315
- init_src();
58316
- init_constants6 = __esm3(() => {
58619
+ init_constants5 = __esm3(() => {
58317
58620
  TIMEBACK_API_URLS2 = {
58318
58621
  production: "https://api.alpha-1edtech.ai",
58319
58622
  staging: "https://api.staging.alpha-1edtech.com"
@@ -58495,34 +58798,152 @@ var init_constants5 = __esm(() => {
58495
58798
  timebackSdk: "TimebackSDKError"
58496
58799
  };
58497
58800
  });
58498
- init_constants6();
58801
+ init_errors4 = __esm3(() => {
58802
+ init_constants5();
58803
+ TimebackError = class TimebackError2 extends Error {
58804
+ constructor(message) {
58805
+ super(message);
58806
+ this.name = ERROR_NAMES2.timebackSdk;
58807
+ }
58808
+ };
58809
+ TimebackApiError = class TimebackApiError2 extends Error {
58810
+ status;
58811
+ details;
58812
+ constructor(status, message, details) {
58813
+ super(`${status} ${message}`);
58814
+ this.name = ERROR_NAMES2.timebackApi;
58815
+ this.status = status;
58816
+ this.details = details;
58817
+ Object.setPrototypeOf(this, TimebackApiError2.prototype);
58818
+ }
58819
+ };
58820
+ TimebackAuthenticationError = class TimebackAuthenticationError2 extends TimebackError {
58821
+ constructor(message) {
58822
+ super(message || "Authentication failed. Please verify TIMEBACK_CLIENT_ID and TIMEBACK_CLIENT_SECRET are set correctly.");
58823
+ this.name = "TimebackAuthenticationError";
58824
+ Object.setPrototypeOf(this, TimebackAuthenticationError2.prototype);
58825
+ }
58826
+ };
58827
+ StudentNotFoundError = class StudentNotFoundError2 extends TimebackError {
58828
+ identifier;
58829
+ identifierType;
58830
+ constructor(identifier, identifierType = "email") {
58831
+ super(`Student not found with ${identifierType}: ${identifier}. Ensure the student exists in OneRoster. If this is a new student, they must be created in OneRoster first.`);
58832
+ this.name = "StudentNotFoundError";
58833
+ this.identifier = identifier;
58834
+ this.identifierType = identifierType;
58835
+ Object.setPrototypeOf(this, StudentNotFoundError2.prototype);
58836
+ }
58837
+ };
58838
+ ConfigurationError = class ConfigurationError2 extends TimebackError {
58839
+ field;
58840
+ constructor(field, message) {
58841
+ super(message || `Missing required configuration: ${field}. Please check your timeback.config.js file.`);
58842
+ this.name = "ConfigurationError";
58843
+ this.field = field;
58844
+ Object.setPrototypeOf(this, ConfigurationError2.prototype);
58845
+ }
58846
+ };
58847
+ ResourceAlreadyExistsError = class ResourceAlreadyExistsError2 extends TimebackError {
58848
+ resourceType;
58849
+ sourcedId;
58850
+ originalError;
58851
+ constructor(resourceType, sourcedId, originalError) {
58852
+ super(`${resourceType} with ID '${sourcedId}' already exists`);
58853
+ this.name = "ResourceAlreadyExistsError";
58854
+ this.resourceType = resourceType;
58855
+ this.sourcedId = sourcedId;
58856
+ this.originalError = originalError;
58857
+ Object.setPrototypeOf(this, ResourceAlreadyExistsError2.prototype);
58858
+ }
58859
+ };
58860
+ ResourceNotFoundError = class ResourceNotFoundError2 extends TimebackError {
58861
+ resourceType;
58862
+ sourcedId;
58863
+ constructor(resourceType, sourcedId) {
58864
+ const message = sourcedId === "setup-not-run" ? `TimeBack resources have not been created yet. Please run 'bunx @playcademy/timeback setup' to create the required resources (organization, course, components, etc.) before recording progress.` : `${resourceType} with ID '${sourcedId}' not found`;
58865
+ super(message);
58866
+ this.name = "ResourceNotFoundError";
58867
+ this.resourceType = resourceType;
58868
+ this.sourcedId = sourcedId;
58869
+ Object.setPrototypeOf(this, ResourceNotFoundError2.prototype);
58870
+ }
58871
+ };
58872
+ });
58873
+ init_ids = __esm3(() => {
58874
+ init_errors4();
58875
+ UUID_REGEX2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
58876
+ });
58877
+ exports_verify = {};
58878
+ __export2(exports_verify, {
58879
+ verifyTimebackResources: () => verifyTimebackResources,
58880
+ fetchTimebackConfig: () => fetchTimebackConfig
58881
+ });
58882
+ init_verify = __esm3(() => {
58883
+ init_constants5();
58884
+ init_ids();
58885
+ });
58886
+ init_constants5();
58887
+ SUBJECT_VALUES2 = TIMEBACK_SUBJECTS3;
58888
+ GRADE_VALUES2 = TIMEBACK_GRADE_LEVELS2;
58889
+ init_ids();
58890
+ init_ids();
58891
+ init_verify();
58892
+ init_ids();
58893
+ init_constants5();
58894
+ init_errors4();
58895
+ init_constants5();
58896
+ if (process.env.DEBUG === "true") {
58897
+ process.env.TERM = "dumb";
58898
+ }
58899
+ TimebackAuthError = class TimebackAuthError2 extends Error {
58900
+ statusCode;
58901
+ constructor(message, statusCode) {
58902
+ super(message);
58903
+ this.name = ERROR_NAMES2.timebackAuth;
58904
+ this.statusCode = statusCode;
58905
+ }
58906
+ };
58907
+ init_ids();
58908
+ init_constants5();
58909
+ init_errors4();
58910
+ UUID_PATTERN = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi;
58911
+ storage = new AsyncLocalStorage;
58912
+ init_constants5();
58913
+ init_constants5();
58914
+ init_errors4();
58915
+ init_constants5();
58916
+ init_constants5();
58917
+ init_constants5();
58918
+ init_constants5();
58919
+ init_constants5();
58920
+ init_ids();
58921
+ init_ids();
58922
+ init_errors4();
58923
+ init_ids();
58924
+ init_errors4();
58925
+ EmailSchema = exports_external.string().email();
58926
+ StudentSourcedIdSchema = exports_external.string().min(1, {
58927
+ message: "Student sourcedId must be a non-empty string"
58928
+ });
58929
+ StudentIdentifierSchema = exports_external.union([EmailSchema, StudentSourcedIdSchema]);
58930
+ init_ids();
58499
58931
  });
58500
- function isObject2(value) {
58501
- return typeof value === "object" && value !== null;
58502
- }
58503
- function isCourseMetadata(value) {
58504
- return isObject2(value);
58505
- }
58506
- function isResourceMetadata(value) {
58507
- return isObject2(value);
58508
- }
58509
- function isPlaycademyResourceMetadata2(value) {
58510
- if (!isObject2(value)) {
58511
- return false;
58932
+ function deriveTimebackCourseLevelFromGrade(grade) {
58933
+ if (grade === 13) {
58934
+ return TIMEBACK_COURSE_DEFAULTS.level.ap;
58512
58935
  }
58513
- if (!("mastery" in value) || value.mastery === undefined) {
58514
- return true;
58936
+ if (grade <= 5) {
58937
+ return TIMEBACK_COURSE_DEFAULTS.level.elementary;
58515
58938
  }
58516
- return isObject2(value.mastery);
58517
- }
58518
- function isTimebackSubject3(value) {
58519
- return typeof value === "string" && SUBJECT_VALUES2.includes(value);
58520
- }
58521
- function isTimebackGrade3(value) {
58522
- return typeof value === "number" && Number.isInteger(value) && GRADE_VALUES2.includes(value);
58939
+ if (grade <= 8) {
58940
+ return TIMEBACK_COURSE_DEFAULTS.level.middle;
58941
+ }
58942
+ return TIMEBACK_COURSE_DEFAULTS.level.high;
58523
58943
  }
58524
58944
  var __esm4 = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
58525
58945
  var TIMEBACK_API_URLS3;
58946
+ var QTI_API_URL2 = "https://qti.alpha-1edtech.ai/api";
58526
58947
  var TIMEBACK_AUTH_URLS3;
58527
58948
  var CALIPER_API_URLS3;
58528
58949
  var ONEROSTER_ENDPOINTS3;
@@ -58550,9 +58971,7 @@ var RESOURCE_DEFAULTS3;
58550
58971
  var HTTP_STATUS3;
58551
58972
  var ERROR_NAMES3;
58552
58973
  var init_constants7;
58553
- var SUBJECT_VALUES2;
58554
- var GRADE_VALUES2;
58555
- var init_types3 = __esm(() => {
58974
+ var init_constants6 = __esm(() => {
58556
58975
  init_src();
58557
58976
  init_constants7 = __esm4(() => {
58558
58977
  TIMEBACK_API_URLS3 = {
@@ -58737,8 +59156,6 @@ var init_types3 = __esm(() => {
58737
59156
  };
58738
59157
  });
58739
59158
  init_constants7();
58740
- SUBJECT_VALUES2 = TIMEBACK_SUBJECTS4;
58741
- GRADE_VALUES2 = TIMEBACK_GRADE_LEVELS3;
58742
59159
  });
58743
59160
  function deriveSourcedIds2(courseId) {
58744
59161
  return {
@@ -58787,6 +59204,16 @@ var RESOURCE_DEFAULTS4;
58787
59204
  var HTTP_STATUS4;
58788
59205
  var ERROR_NAMES4;
58789
59206
  var init_constants8;
59207
+ var TimebackError2;
59208
+ var TimebackApiError2;
59209
+ var TimebackAuthenticationError2;
59210
+ var StudentNotFoundError2;
59211
+ var ConfigurationError2;
59212
+ var ResourceAlreadyExistsError2;
59213
+ var ResourceNotFoundError2;
59214
+ var init_errors5;
59215
+ var UUID_REGEX3;
59216
+ var init_ids2;
58790
59217
  var init_utils6 = __esm(() => {
58791
59218
  init_src();
58792
59219
  init_constants8 = __esm5(() => {
@@ -58971,6 +59398,82 @@ var init_utils6 = __esm(() => {
58971
59398
  timebackSdk: "TimebackSDKError"
58972
59399
  };
58973
59400
  });
59401
+ init_errors5 = __esm5(() => {
59402
+ init_constants8();
59403
+ TimebackError2 = class TimebackError3 extends Error {
59404
+ constructor(message) {
59405
+ super(message);
59406
+ this.name = ERROR_NAMES4.timebackSdk;
59407
+ }
59408
+ };
59409
+ TimebackApiError2 = class TimebackApiError3 extends Error {
59410
+ status;
59411
+ details;
59412
+ constructor(status, message, details) {
59413
+ super(`${status} ${message}`);
59414
+ this.name = ERROR_NAMES4.timebackApi;
59415
+ this.status = status;
59416
+ this.details = details;
59417
+ Object.setPrototypeOf(this, TimebackApiError3.prototype);
59418
+ }
59419
+ };
59420
+ TimebackAuthenticationError2 = class TimebackAuthenticationError3 extends TimebackError2 {
59421
+ constructor(message) {
59422
+ super(message || "Authentication failed. Please verify TIMEBACK_CLIENT_ID and TIMEBACK_CLIENT_SECRET are set correctly.");
59423
+ this.name = "TimebackAuthenticationError";
59424
+ Object.setPrototypeOf(this, TimebackAuthenticationError3.prototype);
59425
+ }
59426
+ };
59427
+ StudentNotFoundError2 = class StudentNotFoundError3 extends TimebackError2 {
59428
+ identifier;
59429
+ identifierType;
59430
+ constructor(identifier, identifierType = "email") {
59431
+ super(`Student not found with ${identifierType}: ${identifier}. Ensure the student exists in OneRoster. If this is a new student, they must be created in OneRoster first.`);
59432
+ this.name = "StudentNotFoundError";
59433
+ this.identifier = identifier;
59434
+ this.identifierType = identifierType;
59435
+ Object.setPrototypeOf(this, StudentNotFoundError3.prototype);
59436
+ }
59437
+ };
59438
+ ConfigurationError2 = class ConfigurationError3 extends TimebackError2 {
59439
+ field;
59440
+ constructor(field, message) {
59441
+ super(message || `Missing required configuration: ${field}. Please check your timeback.config.js file.`);
59442
+ this.name = "ConfigurationError";
59443
+ this.field = field;
59444
+ Object.setPrototypeOf(this, ConfigurationError3.prototype);
59445
+ }
59446
+ };
59447
+ ResourceAlreadyExistsError2 = class ResourceAlreadyExistsError3 extends TimebackError2 {
59448
+ resourceType;
59449
+ sourcedId;
59450
+ originalError;
59451
+ constructor(resourceType, sourcedId, originalError) {
59452
+ super(`${resourceType} with ID '${sourcedId}' already exists`);
59453
+ this.name = "ResourceAlreadyExistsError";
59454
+ this.resourceType = resourceType;
59455
+ this.sourcedId = sourcedId;
59456
+ this.originalError = originalError;
59457
+ Object.setPrototypeOf(this, ResourceAlreadyExistsError3.prototype);
59458
+ }
59459
+ };
59460
+ ResourceNotFoundError2 = class ResourceNotFoundError3 extends TimebackError2 {
59461
+ resourceType;
59462
+ sourcedId;
59463
+ constructor(resourceType, sourcedId) {
59464
+ const message = sourcedId === "setup-not-run" ? `TimeBack resources have not been created yet. Please run 'bunx @playcademy/timeback setup' to create the required resources (organization, course, components, etc.) before recording progress.` : `${resourceType} with ID '${sourcedId}' not found`;
59465
+ super(message);
59466
+ this.name = "ResourceNotFoundError";
59467
+ this.resourceType = resourceType;
59468
+ this.sourcedId = sourcedId;
59469
+ Object.setPrototypeOf(this, ResourceNotFoundError3.prototype);
59470
+ }
59471
+ };
59472
+ });
59473
+ init_ids2 = __esm5(() => {
59474
+ init_errors5();
59475
+ UUID_REGEX3 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
59476
+ });
58974
59477
  init_constants8();
58975
59478
  init_constants8();
58976
59479
  if (process.env.DEBUG === "true") {
@@ -59130,331 +59633,6 @@ function compareEnrollmentsByRecency(a, b) {
59130
59633
  var init_timeback_admin_util = __esm(() => {
59131
59634
  init_errors();
59132
59635
  });
59133
- function isRecord2(value) {
59134
- return typeof value === "object" && value !== null;
59135
- }
59136
- function filterEnrollmentsByGame(enrollments, gameId) {
59137
- return enrollments.filter((enrollment) => enrollment.gameId === gameId).map(({ gameId: _2, ...enrollment }) => enrollment);
59138
- }
59139
- function mapEnrollmentsToUserEnrollments(enrollments, integrations) {
59140
- const enrollmentByCourse = new Map(enrollments.map((enrollment) => [enrollment.courseId, enrollment]));
59141
- const courseToSchool = new Map(enrollments.filter((enrollment) => enrollment.school?.id).map((enrollment) => [enrollment.courseId, enrollment.school.id]));
59142
- return integrations.map((integration) => {
59143
- const enrollment = enrollmentByCourse.get(integration.courseId);
59144
- return {
59145
- gameId: integration.gameId,
59146
- grade: integration.grade,
59147
- subject: integration.subject,
59148
- courseId: integration.courseId,
59149
- orgId: courseToSchool.get(integration.courseId),
59150
- ...enrollment ? { id: enrollment.sourcedId } : {}
59151
- };
59152
- });
59153
- }
59154
- function getStringValue(value) {
59155
- return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
59156
- }
59157
- function parseSourcedIdFromUrl(url2) {
59158
- if (!url2) {
59159
- return;
59160
- }
59161
- const trimmed = url2.trim().replace(/\/$/, "");
59162
- if (!trimmed) {
59163
- return;
59164
- }
59165
- const segments = trimmed.split("/");
59166
- const lastSegment = segments.at(-1);
59167
- return lastSegment ? decodeURIComponent(lastSegment) : undefined;
59168
- }
59169
- function getGeneratedMetricValue(event, type) {
59170
- const items = event.generated?.items;
59171
- if (!Array.isArray(items)) {
59172
- return;
59173
- }
59174
- const metric = items.find((item) => item?.type === type);
59175
- if (!metric) {
59176
- return;
59177
- }
59178
- const value = typeof metric.value === "number" ? metric.value : Number(metric.value);
59179
- return Number.isFinite(value) ? value : undefined;
59180
- }
59181
- function getMergedCaliperExtensions(event) {
59182
- const objectActivityExtensions = isRecord2(event.object.activity?.extensions) ? event.object.activity.extensions : undefined;
59183
- const generatedExtensions = isRecord2(event.generated?.extensions) ? event.generated.extensions : undefined;
59184
- const eventExtensions = isRecord2(event.extensions) ? event.extensions : undefined;
59185
- return {
59186
- ...objectActivityExtensions,
59187
- ...generatedExtensions,
59188
- ...eventExtensions
59189
- };
59190
- }
59191
- function getPlaycademyMetadata(event) {
59192
- const extensions = getMergedCaliperExtensions(event);
59193
- return isRecord2(extensions.playcademy) ? extensions.playcademy : undefined;
59194
- }
59195
- function getActivityId(event, playcademy) {
59196
- const metadataActivityId = getStringValue(playcademy?.activityId);
59197
- if (metadataActivityId) {
59198
- return metadataActivityId;
59199
- }
59200
- const activityId = getStringValue(event.object.activity?.id);
59201
- if (activityId) {
59202
- return activityId;
59203
- }
59204
- const objectId = getStringValue(event.object.id);
59205
- if (!objectId) {
59206
- return;
59207
- }
59208
- const trimmed = objectId.replace(/\/$/, "");
59209
- const segments = trimmed.split("/");
59210
- const activityIndex = segments.lastIndexOf("activities");
59211
- if (activityIndex !== -1 && segments.length >= activityIndex + 3) {
59212
- const candidate = segments[activityIndex + 2];
59213
- return candidate ? decodeURIComponent(candidate) : undefined;
59214
- }
59215
- return;
59216
- }
59217
- function buildResourceMetadata({
59218
- baseMetadata,
59219
- subject,
59220
- grade,
59221
- totalXp,
59222
- masterableUnits
59223
- }) {
59224
- const normalizedBaseMetadata = isResourceMetadata(baseMetadata) ? baseMetadata : undefined;
59225
- const metadata2 = {
59226
- ...normalizedBaseMetadata
59227
- };
59228
- metadata2.subject = subject;
59229
- metadata2.grades = [grade];
59230
- metadata2.xp = totalXp;
59231
- if (masterableUnits !== undefined && masterableUnits !== null) {
59232
- const existingPlaycademy = isPlaycademyResourceMetadata2(metadata2.playcademy) ? metadata2.playcademy : undefined;
59233
- metadata2.playcademy = {
59234
- ...existingPlaycademy,
59235
- mastery: {
59236
- ...existingPlaycademy?.mastery,
59237
- masterableUnits
59238
- }
59239
- };
59240
- }
59241
- return metadata2;
59242
- }
59243
- function getDurationSecondsFromExtensions(event) {
59244
- const extensions = getMergedCaliperExtensions(event);
59245
- const playcademy = isRecord2(extensions.playcademy) ? extensions.playcademy : undefined;
59246
- const rawValue = extensions.durationSeconds ?? playcademy?.durationSeconds;
59247
- const value = typeof rawValue === "number" ? rawValue : Number(rawValue);
59248
- return Number.isFinite(value) ? value : undefined;
59249
- }
59250
- function getCanonicalRunId(session2) {
59251
- const sessionId = getStringValue(session2?.id);
59252
- if (!sessionId) {
59253
- return;
59254
- }
59255
- return sessionId.replace(/^urn:uuid:/, "");
59256
- }
59257
- function getResumeId(event) {
59258
- const playcademy = getPlaycademyMetadata(event);
59259
- return getStringValue(playcademy?.resumeId);
59260
- }
59261
- function isCaliperRemediationOrCompletionEvent(event) {
59262
- const playcademy = getPlaycademyMetadata(event);
59263
- return REMEDIATION_OR_COMPLETION_EVENT_KINDS.has(getStringValue(playcademy?.eventKind) || "");
59264
- }
59265
- function groupCaliperEventsByRun(events) {
59266
- const groups = new Map;
59267
- for (const event of events) {
59268
- const objectId = getStringValue(event.object.id) || "unknown-activity";
59269
- const groupKey = `${objectId}::${getStringValue(event.session?.id) || event.externalId}`;
59270
- const existing = groups.get(groupKey);
59271
- if (existing) {
59272
- existing.push(event);
59273
- } else {
59274
- groups.set(groupKey, [event]);
59275
- }
59276
- }
59277
- return groups;
59278
- }
59279
- function findCaliperEventGroupContainingExternalId(events, externalId) {
59280
- const targetExternalId = externalId.trim();
59281
- if (!targetExternalId) {
59282
- return;
59283
- }
59284
- return [...groupCaliperEventsByRun(events).values()].find((group) => group.some((event) => event.externalId === targetExternalId));
59285
- }
59286
- function mapCaliperEventGroupToActivity(events, relevantCourseIds) {
59287
- if (events.length === 0) {
59288
- return null;
59289
- }
59290
- const sortedEvents = events.toSorted((a, b) => a.eventTime.localeCompare(b.eventTime));
59291
- const activityEvent = [...sortedEvents].toReversed().find((event) => event.type === "ActivityEvent");
59292
- const contextSource = activityEvent || sortedEvents.at(-1);
59293
- if (!contextSource) {
59294
- return null;
59295
- }
59296
- const ctx = parseCaliperEventContext(contextSource, relevantCourseIds);
59297
- if (!ctx) {
59298
- return null;
59299
- }
59300
- const score = activityEvent !== undefined ? (() => {
59301
- const totalQuestions = getGeneratedMetricValue(activityEvent, "totalQuestions");
59302
- const correctQuestions = getGeneratedMetricValue(activityEvent, "correctQuestions");
59303
- if (totalQuestions === undefined || correctQuestions === undefined || totalQuestions <= 0) {
59304
- return;
59305
- }
59306
- return correctQuestions / totalQuestions * 100;
59307
- })() : undefined;
59308
- const xpEarned = activityEvent !== undefined ? getGeneratedMetricValue(activityEvent, "xpEarned") : undefined;
59309
- const masteredUnits = activityEvent !== undefined ? getGeneratedMetricValue(activityEvent, "masteredUnits") : undefined;
59310
- const timeSpentEvents = sortedEvents.filter((event) => event.type === "TimeSpentEvent");
59311
- let totalActiveTimeSeconds;
59312
- if (timeSpentEvents.length > 0) {
59313
- totalActiveTimeSeconds = timeSpentEvents.reduce((sum, event) => sum + (getGeneratedMetricValue(event, "active") ?? 0), 0);
59314
- } else if (activityEvent !== undefined) {
59315
- totalActiveTimeSeconds = getDurationSecondsFromExtensions(activityEvent);
59316
- }
59317
- const fallbackActivityId = getActivityId(contextSource, getPlaycademyMetadata(contextSource));
59318
- const occurredAt = getStringValue(activityEvent?.eventTime) || getStringValue(sortedEvents.at(-1)?.eventTime);
59319
- const runId = getCanonicalRunId(contextSource.session);
59320
- const resumeIds = new Set(sortedEvents.map((event) => getResumeId(event)).filter((resumeId) => resumeId !== undefined));
59321
- const sessionCount = resumeIds.size > 0 ? resumeIds.size : 1;
59322
- const kind = activityEvent !== undefined ? "activity" : "activity-in-progress";
59323
- if (!occurredAt) {
59324
- return null;
59325
- }
59326
- return {
59327
- id: activityEvent?.externalId || sortedEvents.at(-1)?.externalId || events[0].externalId,
59328
- kind,
59329
- occurredAt,
59330
- courseId: ctx.courseId,
59331
- title: getStringValue(activityEvent?.object.activity?.name) || ctx.titleFromEvent || (fallbackActivityId ? kebabToTitleCase(fallbackActivityId) : "Activity completed"),
59332
- ...ctx.activityId ? { activityId: ctx.activityId } : {},
59333
- ...ctx.appName ? { appName: ctx.appName } : {},
59334
- ...score !== undefined ? { score } : {},
59335
- ...xpEarned !== undefined ? { xpDelta: xpEarned } : {},
59336
- ...masteredUnits !== undefined ? { masteredUnitsDelta: masteredUnits } : {},
59337
- ...totalActiveTimeSeconds !== undefined ? { timeDeltaSeconds: totalActiveTimeSeconds } : {},
59338
- ...runId ? { runId } : {},
59339
- ...sessionCount > 0 ? { sessionCount } : {}
59340
- };
59341
- }
59342
- function parseCaliperEventContext(event, relevantCourseIds) {
59343
- const playcademy = getPlaycademyMetadata(event);
59344
- const courseId = getStringValue(playcademy?.courseId) || parseSourcedIdFromUrl(event.object.course?.id);
59345
- if (!courseId || !relevantCourseIds.has(courseId)) {
59346
- return null;
59347
- }
59348
- const occurredAt = getStringValue(event.eventTime);
59349
- if (!occurredAt) {
59350
- return null;
59351
- }
59352
- return {
59353
- courseId,
59354
- occurredAt,
59355
- eventKind: getStringValue(playcademy?.eventKind),
59356
- source: getStringValue(playcademy?.source),
59357
- reason: getStringValue(playcademy?.reason),
59358
- titleFromEvent: getStringValue(event.object.activity?.name),
59359
- appName: getStringValue(event.object.app?.name),
59360
- activityId: getActivityId(event, playcademy)
59361
- };
59362
- }
59363
- function mapTimeSpentRemediation(event, ctx) {
59364
- if (ctx.eventKind !== "remediation-time") {
59365
- return null;
59366
- }
59367
- return {
59368
- id: event.externalId,
59369
- kind: "remediation-time",
59370
- occurredAt: ctx.occurredAt,
59371
- courseId: ctx.courseId,
59372
- title: "Time Adjustment",
59373
- activityId: ctx.activityId,
59374
- appName: ctx.appName,
59375
- reason: ctx.reason,
59376
- timeDeltaSeconds: getGeneratedMetricValue(event, "active")
59377
- };
59378
- }
59379
- function mapActivityRemediation(event, ctx) {
59380
- if (ctx.eventKind === "remediation-xp") {
59381
- return {
59382
- id: event.externalId,
59383
- kind: "remediation-xp",
59384
- occurredAt: ctx.occurredAt,
59385
- courseId: ctx.courseId,
59386
- title: "XP Adjustment",
59387
- activityId: ctx.activityId,
59388
- appName: ctx.appName,
59389
- reason: ctx.reason,
59390
- xpDelta: getGeneratedMetricValue(event, "xpEarned"),
59391
- masteredUnitsDelta: getGeneratedMetricValue(event, "masteredUnits")
59392
- };
59393
- }
59394
- if (ctx.eventKind === "remediation-mastery") {
59395
- return {
59396
- id: event.externalId,
59397
- kind: "remediation-mastery",
59398
- occurredAt: ctx.occurredAt,
59399
- courseId: ctx.courseId,
59400
- title: "Mastery Adjustment",
59401
- activityId: ctx.activityId,
59402
- appName: ctx.appName,
59403
- reason: ctx.reason,
59404
- xpDelta: getGeneratedMetricValue(event, "xpEarned"),
59405
- masteredUnitsDelta: getGeneratedMetricValue(event, "masteredUnits")
59406
- };
59407
- }
59408
- if (ctx.eventKind === "course-completed") {
59409
- return {
59410
- id: event.externalId,
59411
- kind: "course-completed",
59412
- occurredAt: ctx.occurredAt,
59413
- courseId: ctx.courseId,
59414
- title: ctx.source === "admin" ? "Course marked complete" : "Course completed",
59415
- activityId: ctx.activityId,
59416
- appName: ctx.appName,
59417
- reason: ctx.reason
59418
- };
59419
- }
59420
- if (ctx.eventKind === "course-resumed") {
59421
- return {
59422
- id: event.externalId,
59423
- kind: "course-resumed",
59424
- occurredAt: ctx.occurredAt,
59425
- courseId: ctx.courseId,
59426
- title: "Course resumed",
59427
- activityId: ctx.activityId,
59428
- appName: ctx.appName,
59429
- reason: ctx.reason
59430
- };
59431
- }
59432
- return null;
59433
- }
59434
- function mapCaliperEventToRemediationActivity(event, relevantCourseIds) {
59435
- const ctx = parseCaliperEventContext(event, relevantCourseIds);
59436
- if (!ctx) {
59437
- return null;
59438
- }
59439
- if (event.type === "TimeSpentEvent") {
59440
- return mapTimeSpentRemediation(event, ctx);
59441
- }
59442
- if (event.type === "ActivityEvent") {
59443
- return mapActivityRemediation(event, ctx);
59444
- }
59445
- return null;
59446
- }
59447
- var REMEDIATION_OR_COMPLETION_EVENT_KINDS;
59448
- var init_timeback_util = __esm(() => {
59449
- init_types3();
59450
- REMEDIATION_OR_COMPLETION_EVENT_KINDS = new Set([
59451
- "remediation-xp",
59452
- "remediation-time",
59453
- "remediation-mastery",
59454
- "course-completed",
59455
- "course-resumed"
59456
- ]);
59457
- });
59458
59636
  function parseDateInputParts(value) {
59459
59637
  const parts2 = value.split("-");
59460
59638
  return {
@@ -59823,7 +60001,7 @@ function isAllowedGradeLevelTestType(value) {
59823
60001
  }
59824
60002
  function isQualifyingGradeLevelTestResult(result, options) {
59825
60003
  const metadata2 = metadataOf(result);
59826
- return sourceIdFromRef(result.student) === options.studentId && normalizeText(result.status) === "active" && result.scoreStatus === SCORE_STATUS2.fullyGraded && normalizeText(metadata2.resultType) === "assessment" && stringField(metadata2.subject).trim() === options.subject && isAllowedGradeLevelTestType(metadata2.testType);
60004
+ return sourceIdFromRef(result.student) === options.studentId && normalizeText(result.status) === "active" && result.scoreStatus === SCORE_STATUS3.fullyGraded && normalizeText(metadata2.resultType) === "assessment" && stringField(metadata2.subject).trim() === options.subject && isAllowedGradeLevelTestType(metadata2.testType);
59827
60005
  }
59828
60006
  function mapGradeLevelTestSummary(result, options) {
59829
60007
  if (!isQualifyingGradeLevelTestResult(result, options)) {
@@ -60083,7 +60261,7 @@ function latestAssessmentResultsByLineItem(results) {
60083
60261
  const latestResultsByLineItem = new Map;
60084
60262
  for (const result of results) {
60085
60263
  const lineItemId = sourceIdFromRef(result.assessmentLineItem);
60086
- if (lineItemId && !latestResultsByLineItem.has(lineItemId) && result.scoreStatus === SCORE_STATUS2.fullyGraded) {
60264
+ if (lineItemId && !latestResultsByLineItem.has(lineItemId) && result.scoreStatus === SCORE_STATUS3.fullyGraded) {
60087
60265
  latestResultsByLineItem.set(lineItemId, result);
60088
60266
  }
60089
60267
  }
@@ -60184,7 +60362,7 @@ function qtiIdCandidates(parentLineItem, resource) {
60184
60362
  var GRADE_LEVEL_TEST_TYPES;
60185
60363
  var naturalTitleSort;
60186
60364
  var init_timeback_grade_level_results_util = __esm(() => {
60187
- init_constants5();
60365
+ init_constants6();
60188
60366
  init_utils6();
60189
60367
  GRADE_LEVEL_TEST_TYPES = [
60190
60368
  "placement",
@@ -60207,18 +60385,18 @@ async function upsertMasteryCompletionEntry(params) {
60207
60385
  await client.oneroster.assessmentLineItems.findOrCreate(lineItemId, {
60208
60386
  sourcedId: lineItemId,
60209
60387
  title: "Mastery Completion",
60210
- status: ONEROSTER_STATUS2.active,
60388
+ status: ONEROSTER_STATUS3.active,
60211
60389
  course: { sourcedId: ids.course },
60212
60390
  ...ids.componentResource ? { componentResource: { sourcedId: ids.componentResource } } : {}
60213
60391
  });
60214
60392
  await client.oneroster.assessmentResults.upsert(resultId, {
60215
60393
  sourcedId: resultId,
60216
- status: ONEROSTER_STATUS2.active,
60394
+ status: ONEROSTER_STATUS3.active,
60217
60395
  assessmentLineItem: { sourcedId: lineItemId },
60218
60396
  student: { sourcedId: studentId },
60219
60397
  score: 100,
60220
60398
  scoreDate: new Date().toISOString(),
60221
- scoreStatus: SCORE_STATUS2.fullyGraded,
60399
+ scoreStatus: SCORE_STATUS3.fullyGraded,
60222
60400
  inProgress: "false",
60223
60401
  metadata: {
60224
60402
  isMasteryCompletion: true,
@@ -60230,12 +60408,12 @@ async function upsertMasteryCompletionEntry(params) {
60230
60408
  try {
60231
60409
  await client.oneroster.assessmentResults.upsert(resultId, {
60232
60410
  sourcedId: resultId,
60233
- status: ONEROSTER_STATUS2.active,
60411
+ status: ONEROSTER_STATUS3.active,
60234
60412
  assessmentLineItem: { sourcedId: lineItemId },
60235
60413
  student: { sourcedId: studentId },
60236
60414
  score: 0,
60237
60415
  scoreDate: new Date().toISOString(),
60238
- scoreStatus: SCORE_STATUS2.notSubmitted,
60416
+ scoreStatus: SCORE_STATUS3.notSubmitted,
60239
60417
  inProgress: "true",
60240
60418
  metadata: {
60241
60419
  isMasteryCompletion: true,
@@ -60247,7 +60425,7 @@ async function upsertMasteryCompletionEntry(params) {
60247
60425
  }
60248
60426
  }
60249
60427
  var init_timeback_mastery_completion_util = __esm(() => {
60250
- init_constants5();
60428
+ init_constants6();
60251
60429
  init_utils6();
60252
60430
  });
60253
60431
 
@@ -60275,7 +60453,7 @@ class TimebackAdminService {
60275
60453
  this.deps = deps;
60276
60454
  }
60277
60455
  getGradeLevelTestCourseScope(integration) {
60278
- if (!isTimebackSubject(integration.subject) || !isTimebackGrade(integration.grade)) {
60456
+ if (!isTimebackSubject2(integration.subject) || !isTimebackGrade2(integration.grade)) {
60279
60457
  throw new ValidationError("Timeback integration has invalid grade or subject");
60280
60458
  }
60281
60459
  return {
@@ -60354,7 +60532,7 @@ class TimebackAdminService {
60354
60532
  await this.deps.validateDeveloperAccess(user, gameId);
60355
60533
  }
60356
60534
  const integration = await this.deps.db.query.gameTimebackIntegrations.findFirst({
60357
- where: and(eq(gameTimebackIntegrations.gameId, gameId), eq(gameTimebackIntegrations.courseId, courseId))
60535
+ where: and(eq(gameTimebackIntegrations.gameId, gameId), eq(gameTimebackIntegrations.courseId, courseId), isActiveGameTimebackIntegrationStatus())
60358
60536
  });
60359
60537
  if (!integration) {
60360
60538
  throw new NotFoundError("Timeback integration", `${gameId}:${courseId}`);
@@ -60452,7 +60630,7 @@ class TimebackAdminService {
60452
60630
  const ids = deriveSourcedIds2(courseId);
60453
60631
  const resource = await client.oneroster.resources.get(ids.resource);
60454
60632
  const playcademyMetadata = resource.metadata?.playcademy;
60455
- if (!isPlaycademyResourceMetadata2(playcademyMetadata)) {
60633
+ if (!isPlaycademyResourceMetadata(playcademyMetadata)) {
60456
60634
  return;
60457
60635
  }
60458
60636
  return playcademyMetadata?.mastery?.masterableUnits;
@@ -60856,7 +61034,7 @@ class TimebackAdminService {
60856
61034
  "app.timeback.include_inactive": options?.includeInactive ?? false
60857
61035
  });
60858
61036
  const integration = await this.deps.db.query.gameTimebackIntegrations.findFirst({
60859
- where: and(eq(gameTimebackIntegrations.gameId, gameId), eq(gameTimebackIntegrations.courseId, courseId))
61037
+ where: and(eq(gameTimebackIntegrations.gameId, gameId), eq(gameTimebackIntegrations.courseId, courseId), isActiveGameTimebackIntegrationStatus())
60860
61038
  });
60861
61039
  if (!integration) {
60862
61040
  throw new NotFoundError("Timeback integration", `${gameId}:${courseId}`);
@@ -60934,7 +61112,7 @@ class TimebackAdminService {
60934
61112
  columns: { id: true }
60935
61113
  }),
60936
61114
  this.deps.db.query.gameTimebackIntegrations.findMany({
60937
- where: eq(gameTimebackIntegrations.gameId, gameId)
61115
+ where: and(eq(gameTimebackIntegrations.gameId, gameId), isActiveGameTimebackIntegrationStatus())
60938
61116
  }),
60939
61117
  this.deps.db.query.games.findFirst({
60940
61118
  where: eq(games.id, gameId),
@@ -61037,7 +61215,7 @@ class TimebackAdminService {
61037
61215
  "app.timeback.course_id": courseId
61038
61216
  });
61039
61217
  const integrations = await this.deps.db.query.gameTimebackIntegrations.findMany({
61040
- where: courseId ? and(eq(gameTimebackIntegrations.gameId, gameId), eq(gameTimebackIntegrations.courseId, courseId)) : eq(gameTimebackIntegrations.gameId, gameId)
61218
+ where: courseId ? and(eq(gameTimebackIntegrations.gameId, gameId), eq(gameTimebackIntegrations.courseId, courseId), isActiveGameTimebackIntegrationStatus()) : and(eq(gameTimebackIntegrations.gameId, gameId), isActiveGameTimebackIntegrationStatus())
61041
61219
  });
61042
61220
  if (integrations.length === 0) {
61043
61221
  throw new NotFoundError("Timeback integration", gameId);
@@ -61127,7 +61305,7 @@ class TimebackAdminService {
61127
61305
  });
61128
61306
  const [integration, gameSource] = await Promise.all([
61129
61307
  this.deps.db.query.gameTimebackIntegrations.findFirst({
61130
- where: and(eq(gameTimebackIntegrations.gameId, gameId), eq(gameTimebackIntegrations.courseId, courseId))
61308
+ where: and(eq(gameTimebackIntegrations.gameId, gameId), eq(gameTimebackIntegrations.courseId, courseId), isActiveGameTimebackIntegrationStatus())
61131
61309
  }),
61132
61310
  this.getGameActivitySource(gameId)
61133
61311
  ]);
@@ -61231,7 +61409,7 @@ class TimebackAdminService {
61231
61409
  "app.timeback.course_id": courseId
61232
61410
  });
61233
61411
  const integration = await this.deps.db.query.gameTimebackIntegrations.findFirst({
61234
- where: and(eq(gameTimebackIntegrations.gameId, gameId), eq(gameTimebackIntegrations.courseId, courseId))
61412
+ where: and(eq(gameTimebackIntegrations.gameId, gameId), eq(gameTimebackIntegrations.courseId, courseId), isActiveGameTimebackIntegrationStatus())
61235
61413
  });
61236
61414
  if (!integration) {
61237
61415
  throw new NotFoundError("Timeback integration", `${gameId}:${courseId}`);
@@ -61286,7 +61464,7 @@ class TimebackAdminService {
61286
61464
  "app.timeback.assessment_result_id": resultId
61287
61465
  });
61288
61466
  const integration = await this.deps.db.query.gameTimebackIntegrations.findFirst({
61289
- where: and(eq(gameTimebackIntegrations.gameId, gameId), eq(gameTimebackIntegrations.courseId, courseId))
61467
+ where: and(eq(gameTimebackIntegrations.gameId, gameId), eq(gameTimebackIntegrations.courseId, courseId), isActiveGameTimebackIntegrationStatus())
61290
61468
  });
61291
61469
  if (!integration) {
61292
61470
  throw new NotFoundError("Timeback integration", `${gameId}:${courseId}`);
@@ -61364,7 +61542,7 @@ class TimebackAdminService {
61364
61542
  });
61365
61543
  const [integration, gameSource, roster] = await Promise.all([
61366
61544
  this.deps.db.query.gameTimebackIntegrations.findFirst({
61367
- where: and(eq(gameTimebackIntegrations.gameId, gameId), eq(gameTimebackIntegrations.courseId, courseId))
61545
+ where: and(eq(gameTimebackIntegrations.gameId, gameId), eq(gameTimebackIntegrations.courseId, courseId), isActiveGameTimebackIntegrationStatus())
61368
61546
  }),
61369
61547
  this.getGameActivitySource(gameId),
61370
61548
  client.oneroster.enrollments.listByCourse(courseId, {
@@ -61485,7 +61663,7 @@ class TimebackAdminService {
61485
61663
  });
61486
61664
  const [integration, gameSource] = await Promise.all([
61487
61665
  this.deps.db.query.gameTimebackIntegrations.findFirst({
61488
- where: and(eq(gameTimebackIntegrations.gameId, gameId), eq(gameTimebackIntegrations.courseId, courseId))
61666
+ where: and(eq(gameTimebackIntegrations.gameId, gameId), eq(gameTimebackIntegrations.courseId, courseId), isActiveGameTimebackIntegrationStatus())
61489
61667
  }),
61490
61668
  this.getGameActivitySource(gameId)
61491
61669
  ]);
@@ -61569,7 +61747,7 @@ class TimebackAdminService {
61569
61747
  });
61570
61748
  const [integration, gameSource] = await Promise.all([
61571
61749
  this.deps.db.query.gameTimebackIntegrations.findFirst({
61572
- where: and(eq(gameTimebackIntegrations.gameId, gameId), eq(gameTimebackIntegrations.courseId, courseId))
61750
+ where: and(eq(gameTimebackIntegrations.gameId, gameId), eq(gameTimebackIntegrations.courseId, courseId), isActiveGameTimebackIntegrationStatus())
61573
61751
  }),
61574
61752
  this.getGameActivitySource(gameId)
61575
61753
  ]);
@@ -61798,7 +61976,7 @@ class TimebackAdminService {
61798
61976
  "app.timeback.course_id": courseId
61799
61977
  });
61800
61978
  const integration = await this.deps.db.query.gameTimebackIntegrations.findFirst({
61801
- where: and(eq(gameTimebackIntegrations.gameId, gameId), eq(gameTimebackIntegrations.courseId, courseId))
61979
+ where: and(eq(gameTimebackIntegrations.gameId, gameId), eq(gameTimebackIntegrations.courseId, courseId), isActiveGameTimebackIntegrationStatus())
61802
61980
  });
61803
61981
  if (!integration) {
61804
61982
  throw new NotFoundError("Timeback integration", `${gameId}:${courseId}`);
@@ -61869,7 +62047,7 @@ class TimebackAdminService {
61869
62047
  "app.timeback.enrollment.operation": "enroll"
61870
62048
  });
61871
62049
  const integration = await this.deps.db.query.gameTimebackIntegrations.findFirst({
61872
- where: and(eq(gameTimebackIntegrations.gameId, data.gameId), eq(gameTimebackIntegrations.courseId, data.courseId))
62050
+ where: and(eq(gameTimebackIntegrations.gameId, data.gameId), eq(gameTimebackIntegrations.courseId, data.courseId), isActiveGameTimebackIntegrationStatus())
61873
62051
  });
61874
62052
  if (!integration) {
61875
62053
  throw new NotFoundError("Timeback integration", `${data.gameId}:${data.courseId}`);
@@ -61909,7 +62087,7 @@ class TimebackAdminService {
61909
62087
  "app.timeback.enrollment.operation": "unenroll"
61910
62088
  });
61911
62089
  const integration = await this.deps.db.query.gameTimebackIntegrations.findFirst({
61912
- where: and(eq(gameTimebackIntegrations.gameId, data.gameId), eq(gameTimebackIntegrations.courseId, data.courseId))
62090
+ where: and(eq(gameTimebackIntegrations.gameId, data.gameId), eq(gameTimebackIntegrations.courseId, data.courseId), isActiveGameTimebackIntegrationStatus())
61913
62091
  });
61914
62092
  if (!integration) {
61915
62093
  throw new NotFoundError("Timeback integration", `${data.gameId}:${data.courseId}`);
@@ -61930,7 +62108,7 @@ class TimebackAdminService {
61930
62108
  "app.timeback.enrollment.operation": "reactivate"
61931
62109
  });
61932
62110
  const integration = await this.deps.db.query.gameTimebackIntegrations.findFirst({
61933
- where: and(eq(gameTimebackIntegrations.gameId, data.gameId), eq(gameTimebackIntegrations.courseId, data.courseId))
62111
+ where: and(eq(gameTimebackIntegrations.gameId, data.gameId), eq(gameTimebackIntegrations.courseId, data.courseId), isActiveGameTimebackIntegrationStatus())
61934
62112
  });
61935
62113
  if (!integration) {
61936
62114
  throw new NotFoundError("Timeback integration", `${data.gameId}:${data.courseId}`);
@@ -61976,7 +62154,7 @@ class TimebackAdminService {
61976
62154
  const resultId = `${lineItemId}:${studentId}:completion`;
61977
62155
  try {
61978
62156
  const result = await client.oneroster.assessmentResults.get(resultId);
61979
- if (result.scoreStatus === SCORE_STATUS2.fullyGraded) {
62157
+ if (result.scoreStatus === SCORE_STATUS3.fullyGraded) {
61980
62158
  return "complete";
61981
62159
  }
61982
62160
  return "incomplete";
@@ -62012,12 +62190,13 @@ class TimebackAdminService {
62012
62190
  var init_timeback_admin_service = __esm(() => {
62013
62191
  init_drizzle_orm();
62014
62192
  init_src();
62193
+ init_helpers_index();
62015
62194
  init_schemas_index();
62016
62195
  init_tables_index();
62017
62196
  init_spans();
62018
62197
  init_dist2();
62019
- init_constants5();
62020
- init_types3();
62198
+ init_constants6();
62199
+ init_types2();
62021
62200
  init_utils6();
62022
62201
  init_src4();
62023
62202
  init_timeback3();
@@ -62059,8 +62238,15 @@ var RESOURCE_DEFAULTS5;
62059
62238
  var HTTP_STATUS5;
62060
62239
  var ERROR_NAMES5;
62061
62240
  var init_constants9;
62062
- var TimebackApiError2;
62063
- var init_errors4 = __esm(() => {
62241
+ var TimebackError3;
62242
+ var TimebackApiError3;
62243
+ var TimebackAuthenticationError3;
62244
+ var StudentNotFoundError3;
62245
+ var ConfigurationError3;
62246
+ var ResourceAlreadyExistsError3;
62247
+ var ResourceNotFoundError3;
62248
+ var init_errors7;
62249
+ var init_errors6 = __esm(() => {
62064
62250
  init_src();
62065
62251
  init_constants9 = __esm6(() => {
62066
62252
  TIMEBACK_API_URLS5 = {
@@ -62244,18 +62430,79 @@ var init_errors4 = __esm(() => {
62244
62430
  timebackSdk: "TimebackSDKError"
62245
62431
  };
62246
62432
  });
62247
- init_constants9();
62248
- TimebackApiError2 = class TimebackApiError22 extends Error {
62249
- status;
62250
- details;
62251
- constructor(status, message, details) {
62252
- super(`${status} ${message}`);
62253
- this.name = ERROR_NAMES5.timebackApi;
62254
- this.status = status;
62255
- this.details = details;
62256
- Object.setPrototypeOf(this, TimebackApiError22.prototype);
62257
- }
62258
- };
62433
+ init_errors7 = __esm6(() => {
62434
+ init_constants9();
62435
+ TimebackError3 = class TimebackError4 extends Error {
62436
+ constructor(message) {
62437
+ super(message);
62438
+ this.name = ERROR_NAMES5.timebackSdk;
62439
+ }
62440
+ };
62441
+ TimebackApiError3 = class TimebackApiError4 extends Error {
62442
+ status;
62443
+ details;
62444
+ constructor(status, message, details) {
62445
+ super(`${status} ${message}`);
62446
+ this.name = ERROR_NAMES5.timebackApi;
62447
+ this.status = status;
62448
+ this.details = details;
62449
+ Object.setPrototypeOf(this, TimebackApiError4.prototype);
62450
+ }
62451
+ };
62452
+ TimebackAuthenticationError3 = class TimebackAuthenticationError4 extends TimebackError3 {
62453
+ constructor(message) {
62454
+ super(message || "Authentication failed. Please verify TIMEBACK_CLIENT_ID and TIMEBACK_CLIENT_SECRET are set correctly.");
62455
+ this.name = "TimebackAuthenticationError";
62456
+ Object.setPrototypeOf(this, TimebackAuthenticationError4.prototype);
62457
+ }
62458
+ };
62459
+ StudentNotFoundError3 = class StudentNotFoundError4 extends TimebackError3 {
62460
+ identifier;
62461
+ identifierType;
62462
+ constructor(identifier, identifierType = "email") {
62463
+ super(`Student not found with ${identifierType}: ${identifier}. Ensure the student exists in OneRoster. If this is a new student, they must be created in OneRoster first.`);
62464
+ this.name = "StudentNotFoundError";
62465
+ this.identifier = identifier;
62466
+ this.identifierType = identifierType;
62467
+ Object.setPrototypeOf(this, StudentNotFoundError4.prototype);
62468
+ }
62469
+ };
62470
+ ConfigurationError3 = class ConfigurationError4 extends TimebackError3 {
62471
+ field;
62472
+ constructor(field, message) {
62473
+ super(message || `Missing required configuration: ${field}. Please check your timeback.config.js file.`);
62474
+ this.name = "ConfigurationError";
62475
+ this.field = field;
62476
+ Object.setPrototypeOf(this, ConfigurationError4.prototype);
62477
+ }
62478
+ };
62479
+ ResourceAlreadyExistsError3 = class ResourceAlreadyExistsError4 extends TimebackError3 {
62480
+ resourceType;
62481
+ sourcedId;
62482
+ originalError;
62483
+ constructor(resourceType, sourcedId, originalError) {
62484
+ super(`${resourceType} with ID '${sourcedId}' already exists`);
62485
+ this.name = "ResourceAlreadyExistsError";
62486
+ this.resourceType = resourceType;
62487
+ this.sourcedId = sourcedId;
62488
+ this.originalError = originalError;
62489
+ Object.setPrototypeOf(this, ResourceAlreadyExistsError4.prototype);
62490
+ }
62491
+ };
62492
+ ResourceNotFoundError3 = class ResourceNotFoundError4 extends TimebackError3 {
62493
+ resourceType;
62494
+ sourcedId;
62495
+ constructor(resourceType, sourcedId) {
62496
+ const message = sourcedId === "setup-not-run" ? `TimeBack resources have not been created yet. Please run 'bunx @playcademy/timeback setup' to create the required resources (organization, course, components, etc.) before recording progress.` : `${resourceType} with ID '${sourcedId}' not found`;
62497
+ super(message);
62498
+ this.name = "ResourceNotFoundError";
62499
+ this.resourceType = resourceType;
62500
+ this.sourcedId = sourcedId;
62501
+ Object.setPrototypeOf(this, ResourceNotFoundError4.prototype);
62502
+ }
62503
+ };
62504
+ });
62505
+ init_errors7();
62259
62506
  });
62260
62507
 
62261
62508
  class TimebackAssessmentsService {
@@ -62266,7 +62513,7 @@ class TimebackAssessmentsService {
62266
62513
  async resolveIntegrationId(gameId, courseId, user) {
62267
62514
  await this.deps.validateGameManagementAccess(user, gameId);
62268
62515
  const integration = await this.deps.db.query.gameTimebackIntegrations.findFirst({
62269
- where: and(eq(gameTimebackIntegrations.gameId, gameId), eq(gameTimebackIntegrations.courseId, courseId))
62516
+ where: and(eq(gameTimebackIntegrations.gameId, gameId), eq(gameTimebackIntegrations.courseId, courseId), isActiveGameTimebackIntegrationStatus())
62270
62517
  });
62271
62518
  if (!integration) {
62272
62519
  throw new NotFoundError(`No Timeback integration found for game ${gameId} course ${courseId}`);
@@ -62359,7 +62606,7 @@ class TimebackAssessmentsService {
62359
62606
  ]
62360
62607
  });
62361
62608
  } catch (error) {
62362
- if (error instanceof TimebackApiError2 && error.status === 409) {} else {
62609
+ if (error instanceof TimebackApiError3 && error.status === 409) {} else {
62363
62610
  throw error;
62364
62611
  }
62365
62612
  }
@@ -62659,7 +62906,7 @@ class TimebackAssessmentsService {
62659
62906
  }
62660
62907
  async requireIntegration(integrationId) {
62661
62908
  const integration = await this.deps.db.query.gameTimebackIntegrations.findFirst({
62662
- where: eq(gameTimebackIntegrations.id, integrationId)
62909
+ where: and(eq(gameTimebackIntegrations.id, integrationId), isActiveGameTimebackIntegrationStatus())
62663
62910
  });
62664
62911
  if (!integration) {
62665
62912
  throw new NotFoundError(`Integration not found: ${integrationId}`);
@@ -62690,7 +62937,7 @@ class TimebackAssessmentsService {
62690
62937
  }
62691
62938
  });
62692
62939
  } catch (error) {
62693
- if (!(error instanceof TimebackApiError2 && error.status === 409)) {
62940
+ if (!(error instanceof TimebackApiError3 && error.status === 409)) {
62694
62941
  throw error;
62695
62942
  }
62696
62943
  }
@@ -62712,7 +62959,7 @@ class TimebackAssessmentsService {
62712
62959
  }
62713
62960
  });
62714
62961
  } catch (error) {
62715
- if (!(error instanceof TimebackApiError2 && error.status === 409)) {
62962
+ if (!(error instanceof TimebackApiError3 && error.status === 409)) {
62716
62963
  throw error;
62717
62964
  }
62718
62965
  }
@@ -62730,7 +62977,7 @@ class TimebackAssessmentsService {
62730
62977
  }
62731
62978
  });
62732
62979
  } catch (error) {
62733
- if (!(error instanceof TimebackApiError2 && error.status === 409)) {
62980
+ if (!(error instanceof TimebackApiError3 && error.status === 409)) {
62734
62981
  throw error;
62735
62982
  }
62736
62983
  }
@@ -62748,13 +62995,60 @@ class TimebackAssessmentsService {
62748
62995
  }
62749
62996
  var init_timeback_assessments_service = __esm(() => {
62750
62997
  init_drizzle_orm();
62998
+ init_helpers_index();
62751
62999
  init_tables_index();
62752
63000
  init_spans();
62753
- init_constants5();
62754
- init_errors4();
63001
+ init_constants6();
63002
+ init_errors6();
62755
63003
  init_utils6();
62756
63004
  init_errors();
62757
63005
  });
63006
+ function buildTimebackBaseConfigFromExistingConfig(config2) {
63007
+ return {
63008
+ organization: config2.organization,
63009
+ component: {
63010
+ ...config2.component,
63011
+ title: ""
63012
+ },
63013
+ resource: {
63014
+ ...config2.resource,
63015
+ title: ""
63016
+ },
63017
+ componentResource: {
63018
+ ...config2.componentResource,
63019
+ title: ""
63020
+ }
63021
+ };
63022
+ }
63023
+ function getMasterableUnitsFromTimebackConfig(config2) {
63024
+ const playcademyMetadata = config2.resource.metadata?.playcademy;
63025
+ if (!isPlaycademyResourceMetadata(playcademyMetadata)) {
63026
+ return null;
63027
+ }
63028
+ return playcademyMetadata?.mastery?.masterableUnits ?? null;
63029
+ }
63030
+ function getTotalXpFromTimebackConfig(config2) {
63031
+ const courseMetadata = isCourseMetadata(config2.course.metadata) ? config2.course.metadata : undefined;
63032
+ if (typeof courseMetadata?.metrics?.totalXp === "number") {
63033
+ return courseMetadata.metrics.totalXp;
63034
+ }
63035
+ const resourceMetadata = config2.resource.metadata;
63036
+ if (isRecord2(resourceMetadata) && typeof resourceMetadata.xp === "number") {
63037
+ return resourceMetadata.xp;
63038
+ }
63039
+ return null;
63040
+ }
63041
+ function timebackConfigMatchesCreateIntegrationRequest(config2, request2) {
63042
+ const subject = config2.course.subjects[0];
63043
+ const grade = config2.course.grades[0];
63044
+ const requestedLevel = request2.level ?? deriveTimebackCourseLevelFromGrade(request2.grade);
63045
+ return subject === request2.subject && grade === request2.grade && config2.course.title === request2.title && config2.course.courseCode === request2.courseCode && config2.course.level === requestedLevel && (getTotalXpFromTimebackConfig(config2) ?? null) === request2.totalXp && (getMasterableUnitsFromTimebackConfig(config2) ?? null) === request2.masterableUnits;
63046
+ }
63047
+ var init_timeback_create_integration_util = __esm(() => {
63048
+ init_constants6();
63049
+ init_types2();
63050
+ init_timeback_util();
63051
+ });
62758
63052
  async function promoteCompletedCourse({
62759
63053
  db: db2,
62760
63054
  client,
@@ -62763,7 +63057,7 @@ async function promoteCompletedCourse({
62763
63057
  enrollments: prefetchedEnrollments
62764
63058
  }) {
62765
63059
  const subjectIntegrations = await db2.query.gameTimebackIntegrations.findMany({
62766
- where: and(eq(gameTimebackIntegrations.gameId, currentIntegration.gameId), eq(gameTimebackIntegrations.subject, currentIntegration.subject))
63060
+ where: and(eq(gameTimebackIntegrations.gameId, currentIntegration.gameId), eq(gameTimebackIntegrations.subject, currentIntegration.subject), isActiveGameTimebackIntegrationStatus())
62767
63061
  });
62768
63062
  const nextIntegration = subjectIntegrations.filter((integration) => integration.grade > currentIntegration.grade).toSorted((left, right) => left.grade - right.grade)[0];
62769
63063
  if (!nextIntegration) {
@@ -62818,19 +63112,68 @@ async function promoteCompletedCourse({
62818
63112
  }
62819
63113
  var init_timeback_promotion_util = __esm(() => {
62820
63114
  init_drizzle_orm();
63115
+ init_helpers_index();
62821
63116
  init_tables_index();
62822
63117
  init_spans();
62823
63118
  });
63119
+ function toGameTimebackIntegration(integration) {
63120
+ return {
63121
+ id: integration.id,
63122
+ gameId: integration.gameId,
63123
+ courseId: integration.courseId,
63124
+ grade: integration.grade,
63125
+ subject: integration.subject,
63126
+ totalXp: integration.totalXp ?? null,
63127
+ status: integration.status ?? ACTIVE_GAME_TIMEBACK_INTEGRATION_STATUS,
63128
+ deactivatedAt: integration.deactivatedAt ?? null,
63129
+ reactivatedAt: integration.reactivatedAt ?? null,
63130
+ createdAt: integration.createdAt,
63131
+ updatedAt: integration.updatedAt,
63132
+ lastVerifiedAt: integration.lastVerifiedAt ?? null
63133
+ };
63134
+ }
63135
+ function buildFallbackRemovedGameTimebackIntegration(integration) {
63136
+ if (!isTimebackSubject(integration.subject)) {
63137
+ throw new ValidationError(`Invalid subject "${integration.subject}"`);
63138
+ }
63139
+ if (!isTimebackGrade(integration.grade)) {
63140
+ throw new ValidationError(`Invalid grade "${integration.grade}"`);
63141
+ }
63142
+ return {
63143
+ integration: toGameTimebackIntegration(integration),
63144
+ title: `${integration.subject} ${formatGradeLabel(integration.grade)}`,
63145
+ courseCode: integration.courseId,
63146
+ subject: integration.subject,
63147
+ grade: integration.grade,
63148
+ totalXp: integration.totalXp ?? null,
63149
+ masterableUnits: null,
63150
+ removedAt: integration.deactivatedAt ?? null,
63151
+ metadata: null
63152
+ };
63153
+ }
63154
+ var init_timeback_removed_integration_util = __esm(() => {
63155
+ init_helpers_index();
63156
+ init_types2();
63157
+ init_timeback3();
63158
+ init_errors();
63159
+ });
63160
+ async function findGameTimebackIntegrationForUpdate(db2, condition) {
63161
+ const [integration] = await db2.select().from(gameTimebackIntegrations).where(condition).limit(1).for("update");
63162
+ return integration;
63163
+ }
62824
63164
  var TimebackService;
62825
63165
  var init_timeback_service = __esm(() => {
62826
63166
  init_drizzle_orm();
62827
63167
  init_src();
63168
+ init_helpers_index();
62828
63169
  init_tables_index();
62829
63170
  init_spans();
62830
63171
  init_dist2();
62831
- init_types3();
63172
+ init_types2();
62832
63173
  init_errors();
63174
+ init_timeback_create_integration_util();
62833
63175
  init_timeback_promotion_util();
63176
+ init_timeback_removed_integration_util();
62834
63177
  init_timeback_util();
62835
63178
  TimebackService = class TimebackService2 {
62836
63179
  static HEARTBEAT_DEDUPE_TTL_MS = 300000;
@@ -63060,7 +63403,7 @@ var init_timeback_service = __esm(() => {
63060
63403
  return [];
63061
63404
  }
63062
63405
  const integrations = await db2.query.gameTimebackIntegrations.findMany({
63063
- where: inArray(gameTimebackIntegrations.courseId, courseIds)
63406
+ where: and(inArray(gameTimebackIntegrations.courseId, courseIds), isActiveGameTimebackIntegrationStatus())
63064
63407
  });
63065
63408
  return mapEnrollmentsToUserEnrollments(enrollments, integrations);
63066
63409
  } catch (error) {
@@ -63085,6 +63428,7 @@ var init_timeback_service = __esm(() => {
63085
63428
  const verboseData = [];
63086
63429
  let integrationCreatedCount = 0;
63087
63430
  let integrationUpdatedCount = 0;
63431
+ let integrationReactivatedCount = 0;
63088
63432
  for (const courseConfig of courses) {
63089
63433
  let applySuffix = function(text3) {
63090
63434
  return suffix ? `${text3} ${suffix}` : text3;
@@ -63099,16 +63443,16 @@ var init_timeback_service = __esm(() => {
63099
63443
  totalXp: derivedTotalXp,
63100
63444
  masterableUnits: derivedMasterableUnits
63101
63445
  } = courseConfig;
63102
- if (!isTimebackSubject3(subjectInput)) {
63446
+ if (!isTimebackSubject(subjectInput)) {
63103
63447
  throw new ValidationError(`Invalid subject "${subjectInput}"`);
63104
63448
  }
63105
- if (!isTimebackGrade3(grade)) {
63449
+ if (!isTimebackGrade(grade)) {
63106
63450
  throw new ValidationError(`Invalid grade "${grade}"`);
63107
63451
  }
63108
63452
  const subject = subjectInput;
63109
63453
  const courseMetadata = isCourseMetadata(metadata2) ? metadata2 : undefined;
63110
63454
  const totalXp = derivedTotalXp ?? courseMetadata?.metrics?.totalXp;
63111
- const masterableUnits = derivedMasterableUnits ?? (isPlaycademyResourceMetadata2(courseMetadata?.playcademy) ? courseMetadata?.playcademy?.mastery?.masterableUnits : undefined);
63455
+ const masterableUnits = derivedMasterableUnits ?? (isPlaycademyResourceMetadata(courseMetadata?.playcademy) ? courseMetadata?.playcademy?.mastery?.masterableUnits : undefined);
63112
63456
  if (typeof totalXp !== "number") {
63113
63457
  throw new ValidationError(`Course "${title}" is missing totalXp`);
63114
63458
  }
@@ -63154,19 +63498,28 @@ var init_timeback_service = __esm(() => {
63154
63498
  title: applySuffix(baseConfig.componentResource.title || "")
63155
63499
  }
63156
63500
  };
63157
- const existingIntegration = existing.find((i2) => i2.grade === grade && i2.subject === subject);
63501
+ const matches = existing.filter((i2) => i2.grade === grade && i2.subject === subject);
63502
+ const existingIntegration = matches.find((i2) => i2.status !== DEACTIVATED_GAME_TIMEBACK_INTEGRATION_STATUS) ?? matches.toSorted((a, b) => (b.deactivatedAt?.getTime() ?? 0) - (a.deactivatedAt?.getTime() ?? 0))[0];
63158
63503
  if (existingIntegration) {
63159
- await client.update(existingIntegration.courseId, fullConfig);
63160
- const [updated] = await db2.update(gameTimebackIntegrations).set({ subject, totalXp, updatedAt: new Date }).where(eq(gameTimebackIntegrations.id, existingIntegration.id)).returning();
63504
+ const { updated, revived } = await this.updateSetupIntegration({
63505
+ client,
63506
+ existingIntegration,
63507
+ fullConfig,
63508
+ subject,
63509
+ totalXp
63510
+ });
63161
63511
  if (updated) {
63162
- integrations.push(this.toGameTimebackIntegration(updated));
63512
+ integrations.push(toGameTimebackIntegration(updated));
63163
63513
  integrationUpdatedCount++;
63514
+ if (revived) {
63515
+ integrationReactivatedCount++;
63516
+ }
63164
63517
  }
63165
63518
  } else {
63166
63519
  const result = await client.setup(fullConfig, { verbose });
63167
63520
  const [integration] = await db2.insert(gameTimebackIntegrations).values({ gameId, courseId: result.courseId, grade, subject, totalXp }).returning();
63168
63521
  if (integration) {
63169
- const dto = this.toGameTimebackIntegration(integration);
63522
+ const dto = toGameTimebackIntegration(integration);
63170
63523
  integrations.push(dto);
63171
63524
  integrationCreatedCount++;
63172
63525
  if (verbose && result.verboseData) {
@@ -63178,7 +63531,8 @@ var init_timeback_service = __esm(() => {
63178
63531
  setAttributes({
63179
63532
  "app.timeback.integration_count": integrations.length,
63180
63533
  "app.timeback.integration_created_count": integrationCreatedCount,
63181
- "app.timeback.integration_updated_count": integrationUpdatedCount
63534
+ "app.timeback.integration_updated_count": integrationUpdatedCount,
63535
+ "app.timeback.integration_reactivated_count": integrationReactivatedCount
63182
63536
  });
63183
63537
  return {
63184
63538
  integrations,
@@ -63186,19 +63540,221 @@ var init_timeback_service = __esm(() => {
63186
63540
  };
63187
63541
  });
63188
63542
  }
63543
+ async updateSetupIntegration(args2) {
63544
+ const { client, existingIntegration, fullConfig, subject, totalXp } = args2;
63545
+ const revived = existingIntegration.status === DEACTIVATED_GAME_TIMEBACK_INTEGRATION_STATUS;
63546
+ await client.update(existingIntegration.courseId, fullConfig);
63547
+ if (revived) {
63548
+ await client.reactivateCourse(existingIntegration.courseId);
63549
+ }
63550
+ const now2 = new Date;
63551
+ const [updated] = await this.deps.db.update(gameTimebackIntegrations).set({
63552
+ subject,
63553
+ totalXp,
63554
+ updatedAt: now2,
63555
+ ...revived && {
63556
+ status: ACTIVE_GAME_TIMEBACK_INTEGRATION_STATUS,
63557
+ deactivatedAt: null,
63558
+ reactivatedAt: now2
63559
+ }
63560
+ }).where(eq(gameTimebackIntegrations.id, existingIntegration.id)).returning();
63561
+ return { updated, revived };
63562
+ }
63563
+ async createIntegration(gameId, user, request2) {
63564
+ return this.withClientTelemetry(async () => {
63565
+ const client = this.requireClient();
63566
+ const db2 = this.deps.db;
63567
+ await this.deps.validateDeveloperAccess(user, gameId);
63568
+ setAttributes({
63569
+ "app.timeback.grade": request2.grade,
63570
+ "app.timeback.subject": request2.subject
63571
+ });
63572
+ const activeConflict = await db2.query.gameTimebackIntegrations.findFirst({
63573
+ where: and(eq(gameTimebackIntegrations.gameId, gameId), eq(gameTimebackIntegrations.grade, request2.grade), eq(gameTimebackIntegrations.subject, request2.subject), isActiveGameTimebackIntegrationStatus())
63574
+ });
63575
+ if (activeConflict) {
63576
+ throw new ConflictError(`A TimeBack integration already exists for ${request2.subject} Grade ${request2.grade}`);
63577
+ }
63578
+ const removedCandidates = await db2.query.gameTimebackIntegrations.findMany({
63579
+ where: and(eq(gameTimebackIntegrations.gameId, gameId), eq(gameTimebackIntegrations.grade, request2.grade), eq(gameTimebackIntegrations.subject, request2.subject), eq(gameTimebackIntegrations.status, DEACTIVATED_GAME_TIMEBACK_INTEGRATION_STATUS))
63580
+ });
63581
+ for (const candidate of removedCandidates) {
63582
+ const config2 = await client.getConfig(candidate.courseId).catch((error) => {
63583
+ addEvent("timeback.removed_integration_config_fetch_failed", {
63584
+ "app.game.id": gameId,
63585
+ "app.timeback.course_id": candidate.courseId,
63586
+ "exception.type": errorType(error),
63587
+ "app.error.message": errorMessage(error)
63588
+ });
63589
+ return null;
63590
+ });
63591
+ if (config2 && timebackConfigMatchesCreateIntegrationRequest(config2, request2)) {
63592
+ const updated = await db2.transaction(async (tx) => {
63593
+ const database = tx;
63594
+ const lockedCandidate = await findGameTimebackIntegrationForUpdate(database, eq(gameTimebackIntegrations.id, candidate.id));
63595
+ if (lockedCandidate?.status !== DEACTIVATED_GAME_TIMEBACK_INTEGRATION_STATUS) {
63596
+ throw new ConflictError(`A TimeBack integration already exists for ${request2.subject} Grade ${request2.grade}`);
63597
+ }
63598
+ const now2 = new Date;
63599
+ const [integration2] = await database.update(gameTimebackIntegrations).set({
63600
+ status: ACTIVE_GAME_TIMEBACK_INTEGRATION_STATUS,
63601
+ totalXp: request2.totalXp,
63602
+ deactivatedAt: null,
63603
+ reactivatedAt: now2,
63604
+ updatedAt: now2
63605
+ }).where(and(eq(gameTimebackIntegrations.id, candidate.id), eq(gameTimebackIntegrations.status, DEACTIVATED_GAME_TIMEBACK_INTEGRATION_STATUS))).returning();
63606
+ if (!integration2) {
63607
+ throw new NotFoundError("Timeback integration", candidate.id);
63608
+ }
63609
+ await client.reactivateCourse(candidate.courseId);
63610
+ return integration2;
63611
+ });
63612
+ setAttribute("app.timeback.course_create_mode", "reactivated-removed");
63613
+ return toGameTimebackIntegration(updated);
63614
+ }
63615
+ }
63616
+ const reference = await db2.query.gameTimebackIntegrations.findFirst({
63617
+ where: and(eq(gameTimebackIntegrations.gameId, gameId), isActiveGameTimebackIntegrationStatus()),
63618
+ orderBy: (table8, { asc: asc2 }) => [asc2(table8.createdAt)]
63619
+ }) ?? await db2.query.gameTimebackIntegrations.findFirst({
63620
+ where: eq(gameTimebackIntegrations.gameId, gameId),
63621
+ orderBy: (table8, { asc: asc2 }) => [asc2(table8.createdAt)]
63622
+ });
63623
+ if (!reference) {
63624
+ throw new ValidationError("Run `playcademy timeback setup` before adding courses from the dashboard.");
63625
+ }
63626
+ const referenceConfig = await client.getConfig(reference.courseId);
63627
+ const result = await this.setupIntegration(gameId, {
63628
+ gameId,
63629
+ courses: [
63630
+ {
63631
+ title: request2.title,
63632
+ courseCode: request2.courseCode,
63633
+ subject: request2.subject,
63634
+ grade: request2.grade,
63635
+ level: request2.level ?? deriveTimebackCourseLevelFromGrade(request2.grade),
63636
+ totalXp: request2.totalXp,
63637
+ masterableUnits: request2.masterableUnits
63638
+ }
63639
+ ],
63640
+ baseConfig: buildTimebackBaseConfigFromExistingConfig(referenceConfig)
63641
+ }, user);
63642
+ const integration = result.integrations[0];
63643
+ if (!integration) {
63644
+ throw new InternalError("TimeBack course creation did not return an integration");
63645
+ }
63646
+ setAttribute("app.timeback.course_create_mode", "created-new");
63647
+ return integration;
63648
+ });
63649
+ }
63189
63650
  async getIntegrations(gameId, user) {
63190
63651
  await this.deps.validateGameManagementAccess(user, gameId);
63191
63652
  const rows = await this.deps.db.query.gameTimebackIntegrations.findMany({
63192
- where: eq(gameTimebackIntegrations.gameId, gameId)
63653
+ where: and(eq(gameTimebackIntegrations.gameId, gameId), isActiveGameTimebackIntegrationStatus())
63654
+ });
63655
+ return rows.map((row) => toGameTimebackIntegration(row));
63656
+ }
63657
+ async getRemovedIntegrations(gameId, user) {
63658
+ return this.withClientTelemetry(async () => {
63659
+ const client = this.requireClient();
63660
+ await this.deps.validateGameManagementAccess(user, gameId);
63661
+ const rows = await this.deps.db.query.gameTimebackIntegrations.findMany({
63662
+ where: and(eq(gameTimebackIntegrations.gameId, gameId), eq(gameTimebackIntegrations.status, DEACTIVATED_GAME_TIMEBACK_INTEGRATION_STATUS)),
63663
+ orderBy: (table8, { asc: asc2 }) => [asc2(table8.subject), asc2(table8.grade)]
63664
+ });
63665
+ setAttribute("app.timeback.removed_integration_count", rows.length);
63666
+ return Promise.all(rows.map(async (row) => {
63667
+ try {
63668
+ const config2 = await client.getConfig(row.courseId);
63669
+ return this.toRemovedGameTimebackIntegration(row, config2);
63670
+ } catch (error) {
63671
+ addEvent("timeback.removed_integration_config_fetch_failed", {
63672
+ "app.game.id": gameId,
63673
+ "app.timeback.course_id": row.courseId,
63674
+ "exception.type": errorType(error),
63675
+ "app.error.message": errorMessage(error)
63676
+ });
63677
+ return buildFallbackRemovedGameTimebackIntegration(row);
63678
+ }
63679
+ }));
63680
+ });
63681
+ }
63682
+ async deactivateCourse(gameId, courseId, user) {
63683
+ return this.withClientTelemetry(async () => {
63684
+ const client = this.requireClient();
63685
+ const db2 = this.deps.db;
63686
+ await this.deps.validateDeveloperAccess(user, gameId);
63687
+ const updated = await db2.transaction(async (tx) => {
63688
+ const database = tx;
63689
+ const integration = await findGameTimebackIntegrationForUpdate(database, and(eq(gameTimebackIntegrations.gameId, gameId), eq(gameTimebackIntegrations.courseId, courseId), isActiveGameTimebackIntegrationStatus()));
63690
+ if (!integration) {
63691
+ throw new NotFoundError("Timeback course", `${gameId}:${courseId}`);
63692
+ }
63693
+ const activeEnrollments = await client.oneroster.enrollments.listByCourse(courseId, {
63694
+ role: "student",
63695
+ includeUsers: false
63696
+ });
63697
+ setAttribute("app.timeback.active_enrollment_count", activeEnrollments.length);
63698
+ if (activeEnrollments.length > 0) {
63699
+ throw new ConflictError("Cannot remove course with active enrollments", {
63700
+ courseId,
63701
+ activeEnrollmentCount: activeEnrollments.length
63702
+ });
63703
+ }
63704
+ const now2 = new Date;
63705
+ const [updatedIntegration] = await database.update(gameTimebackIntegrations).set({
63706
+ status: DEACTIVATED_GAME_TIMEBACK_INTEGRATION_STATUS,
63707
+ deactivatedAt: now2,
63708
+ updatedAt: now2
63709
+ }).where(and(eq(gameTimebackIntegrations.id, integration.id), isActiveGameTimebackIntegrationStatus())).returning();
63710
+ if (!updatedIntegration) {
63711
+ throw new NotFoundError("Timeback course", `${gameId}:${courseId}`);
63712
+ }
63713
+ await client.deactivateCourse(courseId);
63714
+ return updatedIntegration;
63715
+ });
63716
+ return toGameTimebackIntegration(updated);
63717
+ });
63718
+ }
63719
+ async reactivateCourse(gameId, courseId, user) {
63720
+ return this.withClientTelemetry(async () => {
63721
+ const client = this.requireClient();
63722
+ const db2 = this.deps.db;
63723
+ await this.deps.validateDeveloperAccess(user, gameId);
63724
+ const updated = await db2.transaction(async (tx) => {
63725
+ const database = tx;
63726
+ const integration = await findGameTimebackIntegrationForUpdate(database, and(eq(gameTimebackIntegrations.gameId, gameId), eq(gameTimebackIntegrations.courseId, courseId), eq(gameTimebackIntegrations.status, DEACTIVATED_GAME_TIMEBACK_INTEGRATION_STATUS)));
63727
+ if (!integration) {
63728
+ throw new NotFoundError("Removed Timeback course", `${gameId}:${courseId}`);
63729
+ }
63730
+ const activeConflict = await database.query.gameTimebackIntegrations.findFirst({
63731
+ where: and(eq(gameTimebackIntegrations.gameId, gameId), eq(gameTimebackIntegrations.grade, integration.grade), eq(gameTimebackIntegrations.subject, integration.subject), isActiveGameTimebackIntegrationStatus())
63732
+ });
63733
+ if (activeConflict) {
63734
+ throw new ConflictError(`An active TimeBack course already exists for ${integration.subject} Grade ${integration.grade}`);
63735
+ }
63736
+ const now2 = new Date;
63737
+ const [updatedIntegration] = await database.update(gameTimebackIntegrations).set({
63738
+ status: ACTIVE_GAME_TIMEBACK_INTEGRATION_STATUS,
63739
+ deactivatedAt: null,
63740
+ reactivatedAt: now2,
63741
+ updatedAt: now2
63742
+ }).where(and(eq(gameTimebackIntegrations.id, integration.id), eq(gameTimebackIntegrations.status, DEACTIVATED_GAME_TIMEBACK_INTEGRATION_STATUS))).returning();
63743
+ if (!updatedIntegration) {
63744
+ throw new NotFoundError("Timeback course", `${gameId}:${courseId}`);
63745
+ }
63746
+ await client.reactivateCourse(courseId);
63747
+ return updatedIntegration;
63748
+ });
63749
+ return toGameTimebackIntegration(updated);
63193
63750
  });
63194
- return rows.map((row) => this.toGameTimebackIntegration(row));
63195
63751
  }
63196
63752
  async getIntegrationConfig(gameId, courseId, user) {
63197
63753
  return this.withClientTelemetry(async () => {
63198
63754
  const client = this.requireClient();
63199
63755
  await this.deps.validateGameManagementAccess(user, gameId);
63200
63756
  const integration = await this.deps.db.query.gameTimebackIntegrations.findFirst({
63201
- where: and(eq(gameTimebackIntegrations.gameId, gameId), eq(gameTimebackIntegrations.courseId, courseId))
63757
+ where: and(eq(gameTimebackIntegrations.gameId, gameId), eq(gameTimebackIntegrations.courseId, courseId), isActiveGameTimebackIntegrationStatus())
63202
63758
  });
63203
63759
  if (!integration) {
63204
63760
  throw new NotFoundError("Timeback integration", `${gameId}:${courseId}`);
@@ -63215,15 +63771,15 @@ var init_timeback_service = __esm(() => {
63215
63771
  "app.timeback.course_id": courseId
63216
63772
  });
63217
63773
  const integration = await this.deps.db.query.gameTimebackIntegrations.findFirst({
63218
- where: and(eq(gameTimebackIntegrations.gameId, gameId), eq(gameTimebackIntegrations.courseId, courseId))
63774
+ where: and(eq(gameTimebackIntegrations.gameId, gameId), eq(gameTimebackIntegrations.courseId, courseId), isActiveGameTimebackIntegrationStatus())
63219
63775
  });
63220
63776
  if (!integration) {
63221
63777
  throw new NotFoundError("Timeback integration", `${gameId}:${courseId}`);
63222
63778
  }
63223
63779
  const timebackConfig = await client.getConfig(courseId);
63224
63780
  const liveSubject = timebackConfig.course.subjects[0];
63225
- const subject = patch.subject ?? (isTimebackSubject3(liveSubject) ? liveSubject : integration.subject);
63226
- if (!isTimebackSubject3(subject)) {
63781
+ const subject = patch.subject ?? (isTimebackSubject(liveSubject) ? liveSubject : integration.subject);
63782
+ if (!isTimebackSubject(subject)) {
63227
63783
  throw new ValidationError(`Invalid subject "${subject}"`);
63228
63784
  }
63229
63785
  setAttributes({
@@ -63233,14 +63789,14 @@ var init_timeback_service = __esm(() => {
63233
63789
  });
63234
63790
  if (subject !== integration.subject) {
63235
63791
  const subjectConflict = await this.deps.db.query.gameTimebackIntegrations.findFirst({
63236
- where: and(eq(gameTimebackIntegrations.gameId, gameId), eq(gameTimebackIntegrations.grade, integration.grade), eq(gameTimebackIntegrations.subject, subject))
63792
+ where: and(eq(gameTimebackIntegrations.gameId, gameId), eq(gameTimebackIntegrations.grade, integration.grade), eq(gameTimebackIntegrations.subject, subject), isActiveGameTimebackIntegrationStatus())
63237
63793
  });
63238
63794
  if (subjectConflict && subjectConflict.id !== integration.id) {
63239
63795
  throw new ValidationError(`A TimeBack integration already exists for ${subject} Grade ${integration.grade}`);
63240
63796
  }
63241
63797
  }
63242
- const totalXp = "totalXp" in patch ? patch.totalXp ?? null : TimebackService2.getTotalXpFromConfig(timebackConfig) ?? integration.totalXp ?? null;
63243
- const masterableUnits = "masterableUnits" in patch ? patch.masterableUnits ?? null : TimebackService2.getMasterableUnitsFromConfig(timebackConfig);
63798
+ const totalXp = "totalXp" in patch ? patch.totalXp ?? null : getTotalXpFromTimebackConfig(timebackConfig) ?? integration.totalXp ?? null;
63799
+ const masterableUnits = "masterableUnits" in patch ? patch.masterableUnits ?? null : getMasterableUnitsFromTimebackConfig(timebackConfig);
63244
63800
  setAttributes({
63245
63801
  "app.timeback.total_xp": totalXp,
63246
63802
  "app.timeback.masterable_units": masterableUnits
@@ -63260,7 +63816,7 @@ var init_timeback_service = __esm(() => {
63260
63816
  if (!updated) {
63261
63817
  throw new NotFoundError("Timeback integration", `${gameId}:${courseId}`);
63262
63818
  }
63263
- return this.toGameTimebackIntegration(updated);
63819
+ return toGameTimebackIntegration(updated);
63264
63820
  });
63265
63821
  }
63266
63822
  async verifyIntegration(gameId, user) {
@@ -63270,7 +63826,7 @@ var init_timeback_service = __esm(() => {
63270
63826
  await this.deps.validateDeveloperAccess(user, gameId);
63271
63827
  TimebackService2.recordIntegrationOperation("verify_integration");
63272
63828
  const integrations = await db2.query.gameTimebackIntegrations.findMany({
63273
- where: eq(gameTimebackIntegrations.gameId, gameId)
63829
+ where: and(eq(gameTimebackIntegrations.gameId, gameId), isActiveGameTimebackIntegrationStatus())
63274
63830
  });
63275
63831
  if (integrations.length === 0) {
63276
63832
  throw new NotFoundError("Timeback integration", gameId);
@@ -63283,7 +63839,7 @@ var init_timeback_service = __esm(() => {
63283
63839
  const errors3 = Object.entries(resources).filter(([_2, r]) => !r.found).map(([name3]) => `${name3} not found`);
63284
63840
  const status = allFound ? "success" : "error";
63285
63841
  return {
63286
- integration: this.toGameTimebackIntegration({
63842
+ integration: toGameTimebackIntegration({
63287
63843
  ...integration,
63288
63844
  lastVerifiedAt: now2
63289
63845
  }),
@@ -63292,7 +63848,7 @@ var init_timeback_service = __esm(() => {
63292
63848
  ...errors3.length > 0 && { errors: errors3 }
63293
63849
  };
63294
63850
  }));
63295
- await db2.update(gameTimebackIntegrations).set({ lastVerifiedAt: now2 }).where(eq(gameTimebackIntegrations.gameId, gameId));
63851
+ await db2.update(gameTimebackIntegrations).set({ lastVerifiedAt: now2 }).where(inArray(gameTimebackIntegrations.id, integrations.map((integration) => integration.id)));
63296
63852
  const overallStatus = results.every((r) => r.status === "success") ? "success" : "error";
63297
63853
  const missingResourceCount = results.reduce((count, result) => count + (result.errors?.length ?? 0), 0);
63298
63854
  setAttributes({
@@ -63308,7 +63864,7 @@ var init_timeback_service = __esm(() => {
63308
63864
  const client = this.requireClient();
63309
63865
  await this.deps.validateDeveloperAccess(user, gameId);
63310
63866
  const integration = await this.deps.db.query.gameTimebackIntegrations.findFirst({
63311
- where: eq(gameTimebackIntegrations.gameId, gameId)
63867
+ where: and(eq(gameTimebackIntegrations.gameId, gameId), isActiveGameTimebackIntegrationStatus())
63312
63868
  });
63313
63869
  if (!integration) {
63314
63870
  throw new NotFoundError("Timeback integration", gameId);
@@ -63335,24 +63891,6 @@ var init_timeback_service = __esm(() => {
63335
63891
  await db2.delete(gameTimebackIntegrations).where(eq(gameTimebackIntegrations.gameId, gameId));
63336
63892
  });
63337
63893
  }
63338
- static getMasterableUnitsFromConfig(config2) {
63339
- const playcademyMetadata = config2.resource.metadata?.playcademy;
63340
- if (!isPlaycademyResourceMetadata2(playcademyMetadata)) {
63341
- return null;
63342
- }
63343
- return playcademyMetadata?.mastery?.masterableUnits ?? null;
63344
- }
63345
- static getTotalXpFromConfig(config2) {
63346
- const courseMetadata = isCourseMetadata(config2.course.metadata) ? config2.course.metadata : undefined;
63347
- if (typeof courseMetadata?.metrics?.totalXp === "number") {
63348
- return courseMetadata.metrics.totalXp;
63349
- }
63350
- const resourceMetadata = config2.resource.metadata;
63351
- if (isRecord2(resourceMetadata) && typeof resourceMetadata.xp === "number") {
63352
- return resourceMetadata.xp;
63353
- }
63354
- return null;
63355
- }
63356
63894
  static patchCourseMetadata(metadata2, totalXp, options) {
63357
63895
  const nextMetadata = isRecord2(metadata2) ? { ...metadata2 } : {};
63358
63896
  const currentMetrics = isRecord2(nextMetadata.metrics) ? nextMetadata.metrics : {};
@@ -63475,31 +64013,29 @@ var init_timeback_service = __esm(() => {
63475
64013
  }
63476
64014
  };
63477
64015
  }
63478
- toGameTimebackIntegration(integration) {
64016
+ toRemovedGameTimebackIntegration(integration, config2) {
64017
+ const grade = config2.course.grades[0] ?? integration.grade;
64018
+ if (!isTimebackGrade(grade)) {
64019
+ throw new ValidationError(`Invalid grade "${grade}"`);
64020
+ }
63479
64021
  return {
63480
- id: integration.id,
63481
- gameId: integration.gameId,
63482
- courseId: integration.courseId,
63483
- grade: integration.grade,
63484
- subject: integration.subject,
63485
- totalXp: integration.totalXp ?? null,
63486
- createdAt: integration.createdAt,
63487
- updatedAt: integration.updatedAt,
63488
- lastVerifiedAt: integration.lastVerifiedAt ?? null
64022
+ ...this.toGameTimebackIntegrationConfig(integration, config2),
64023
+ grade,
64024
+ removedAt: integration.deactivatedAt ?? null
63489
64025
  };
63490
64026
  }
63491
64027
  toGameTimebackIntegrationConfig(integration, config2) {
63492
64028
  const subject = config2.course.subjects[0] ?? integration.subject;
63493
- if (!isTimebackSubject3(subject)) {
64029
+ if (!isTimebackSubject(subject)) {
63494
64030
  throw new ValidationError(`Invalid subject "${subject}"`);
63495
64031
  }
63496
64032
  return {
63497
- integration: this.toGameTimebackIntegration(integration),
64033
+ integration: toGameTimebackIntegration(integration),
63498
64034
  title: config2.course.title,
63499
64035
  courseCode: config2.course.courseCode,
63500
64036
  subject,
63501
- totalXp: TimebackService2.getTotalXpFromConfig(config2) ?? integration.totalXp ?? null,
63502
- masterableUnits: TimebackService2.getMasterableUnitsFromConfig(config2),
64037
+ totalXp: getTotalXpFromTimebackConfig(config2) ?? integration.totalXp ?? null,
64038
+ masterableUnits: getMasterableUnitsFromTimebackConfig(config2),
63503
64039
  metadata: isCourseMetadata(config2.course.metadata) ? config2.course.metadata : null
63504
64040
  };
63505
64041
  }
@@ -63531,7 +64067,7 @@ var init_timeback_service = __esm(() => {
63531
64067
  });
63532
64068
  await this.deps.validateDeveloperAccess(user, gameId);
63533
64069
  const integration = await db2.query.gameTimebackIntegrations.findFirst({
63534
- where: and(eq(gameTimebackIntegrations.gameId, gameId), eq(gameTimebackIntegrations.grade, activityData.grade), eq(gameTimebackIntegrations.subject, activityData.subject))
64070
+ where: and(eq(gameTimebackIntegrations.gameId, gameId), eq(gameTimebackIntegrations.grade, activityData.grade), eq(gameTimebackIntegrations.subject, activityData.subject), isActiveGameTimebackIntegrationStatus())
63535
64071
  });
63536
64072
  if (!integration) {
63537
64073
  throw new NotFoundError(`Timeback integration for game (grade ${activityData.grade}, subject ${activityData.subject})`);
@@ -63592,9 +64128,7 @@ var init_timeback_service = __esm(() => {
63592
64128
  "app.timeback.xp_awarded": result.xpAwarded,
63593
64129
  "app.timeback.mastered_units_requested": masteredUnits,
63594
64130
  "app.timeback.mastered_units_absolute_requested": masteredUnitsAbsolute,
63595
- "app.timeback.mastered_units_applied": result.masteredUnitsApplied,
63596
- "app.timeback.score_status": result.scoreStatus,
63597
- "app.timeback.in_progress": result.inProgress
64131
+ "app.timeback.mastered_units_applied": result.masteredUnitsApplied
63598
64132
  });
63599
64133
  return {
63600
64134
  status: "ok",
@@ -63602,8 +64136,6 @@ var init_timeback_service = __esm(() => {
63602
64136
  xpAwarded: result.xpAwarded,
63603
64137
  masteredUnits: result.masteredUnitsApplied,
63604
64138
  pctCompleteApp: result.pctCompleteApp,
63605
- scoreStatus: result.scoreStatus,
63606
- inProgress: result.inProgress,
63607
64139
  ...result.warnings ? { warnings: result.warnings } : {}
63608
64140
  };
63609
64141
  }
@@ -63616,7 +64148,7 @@ var init_timeback_service = __esm(() => {
63616
64148
  const client = this.requireClient();
63617
64149
  const db2 = this.deps.db;
63618
64150
  const integrations = await db2.query.gameTimebackIntegrations.findMany({
63619
- where: subject ? and(eq(gameTimebackIntegrations.gameId, gameId), eq(gameTimebackIntegrations.subject, subject)) : eq(gameTimebackIntegrations.gameId, gameId)
64151
+ where: subject ? and(eq(gameTimebackIntegrations.gameId, gameId), eq(gameTimebackIntegrations.subject, subject), isActiveGameTimebackIntegrationStatus()) : and(eq(gameTimebackIntegrations.gameId, gameId), isActiveGameTimebackIntegrationStatus())
63620
64152
  });
63621
64153
  if (integrations.length === 0) {
63622
64154
  throw new NotFoundError(subject ? `Timeback integrations for game (subject ${subject})` : "Timeback integrations for game");
@@ -63798,7 +64330,7 @@ var init_timeback_service = __esm(() => {
63798
64330
  }
63799
64331
  const pendingHeartbeat = (async () => {
63800
64332
  const integration = await db2.query.gameTimebackIntegrations.findFirst({
63801
- where: and(eq(gameTimebackIntegrations.gameId, gameId), eq(gameTimebackIntegrations.grade, activityData.grade), eq(gameTimebackIntegrations.subject, activityData.subject))
64333
+ where: and(eq(gameTimebackIntegrations.gameId, gameId), eq(gameTimebackIntegrations.grade, activityData.grade), eq(gameTimebackIntegrations.subject, activityData.subject), isActiveGameTimebackIntegrationStatus())
63802
64334
  });
63803
64335
  if (!integration) {
63804
64336
  throw new NotFoundError(`Timeback integration for game (grade ${activityData.grade}, subject ${activityData.subject})`);
@@ -63846,7 +64378,10 @@ var init_timeback_service = __esm(() => {
63846
64378
  let courseIds = [];
63847
64379
  if (options?.gameId) {
63848
64380
  await this.deps.validateDeveloperAccess(user, options.gameId);
63849
- const conditions2 = [eq(gameTimebackIntegrations.gameId, options.gameId)];
64381
+ const conditions2 = [
64382
+ eq(gameTimebackIntegrations.gameId, options.gameId),
64383
+ isActiveGameTimebackIntegrationStatus()
64384
+ ];
63850
64385
  if (options.grade !== undefined && options.subject) {
63851
64386
  conditions2.push(eq(gameTimebackIntegrations.grade, options.grade));
63852
64387
  conditions2.push(eq(gameTimebackIntegrations.subject, options.subject));
@@ -63873,7 +64408,10 @@ var init_timeback_service = __esm(() => {
63873
64408
  const client = this.requireClient();
63874
64409
  const db2 = this.deps.db;
63875
64410
  await this.deps.validateDeveloperAccess(user, options.gameId);
63876
- const conditions2 = [eq(gameTimebackIntegrations.gameId, options.gameId)];
64411
+ const conditions2 = [
64412
+ eq(gameTimebackIntegrations.gameId, options.gameId),
64413
+ isActiveGameTimebackIntegrationStatus()
64414
+ ];
63877
64415
  if (options.grade !== undefined && options.subject) {
63878
64416
  conditions2.push(eq(gameTimebackIntegrations.grade, options.grade));
63879
64417
  conditions2.push(eq(gameTimebackIntegrations.subject, options.subject));
@@ -63900,7 +64438,7 @@ var init_timeback_service = __esm(() => {
63900
64438
  const db2 = this.deps.db;
63901
64439
  await this.deps.validateDeveloperAccess(user, options.gameId);
63902
64440
  const integration = await db2.query.gameTimebackIntegrations.findFirst({
63903
- where: and(eq(gameTimebackIntegrations.gameId, options.gameId), eq(gameTimebackIntegrations.subject, options.subject))
64441
+ where: and(eq(gameTimebackIntegrations.gameId, options.gameId), eq(gameTimebackIntegrations.subject, options.subject), isActiveGameTimebackIntegrationStatus())
63904
64442
  });
63905
64443
  if (!integration) {
63906
64444
  throw new ValidationError(`Subject "${options.subject}" is not configured for game ${options.gameId}`);
@@ -64767,7 +65305,7 @@ class UserService {
64767
65305
  return [];
64768
65306
  }
64769
65307
  const integrations = await this.deps.db.query.gameTimebackIntegrations.findMany({
64770
- where: inArray(gameTimebackIntegrations.courseId, courseIds)
65308
+ where: and(inArray(gameTimebackIntegrations.courseId, courseIds), isActiveGameTimebackIntegrationStatus())
64771
65309
  });
64772
65310
  return mapEnrollmentsToUserEnrollments(enrollments, integrations);
64773
65311
  } catch (error) {
@@ -64789,6 +65327,7 @@ class UserService {
64789
65327
  var init_user_service = __esm(() => {
64790
65328
  init_drizzle_orm();
64791
65329
  init_src();
65330
+ init_helpers_index();
64792
65331
  init_tables_index();
64793
65332
  init_spans();
64794
65333
  init_errors();
@@ -81189,7 +81728,7 @@ function extractTablesRelationalConfig2(schema5, configHelpers) {
81189
81728
  return { tables: tablesConfig, tableNamesMap };
81190
81729
  }
81191
81730
  function relations3(table62, relations22) {
81192
- return new Relations2(table62, (helpers) => Object.fromEntries(Object.entries(relations22(helpers)).map(([key, value]) => [
81731
+ return new Relations2(table62, (helpers3) => Object.fromEntries(Object.entries(relations22(helpers3)).map(([key, value]) => [
81193
81732
  key,
81194
81733
  value.withFieldName(key)
81195
81734
  ])));
@@ -82452,7 +82991,7 @@ var errorMap2;
82452
82991
  var en_default2;
82453
82992
  var init_en2;
82454
82993
  var overrideErrorMap2;
82455
- var init_errors5;
82994
+ var init_errors8;
82456
82995
  var makeIssue2;
82457
82996
  var ParseStatus2;
82458
82997
  var INVALID2;
@@ -88689,7 +89228,7 @@ See: https://github.com/isaacs/node-glob/issues/167`);
88689
89228
  en_default2 = errorMap2;
88690
89229
  }
88691
89230
  });
88692
- init_errors5 = __esm7({
89231
+ init_errors8 = __esm7({
88693
89232
  "../node_modules/.pnpm/zod@3.25.42/node_modules/zod/dist/esm/v3/errors.js"() {
88694
89233
  init_en2();
88695
89234
  overrideErrorMap2 = en_default2;
@@ -88697,7 +89236,7 @@ See: https://github.com/isaacs/node-glob/issues/167`);
88697
89236
  });
88698
89237
  init_parseUtil2 = __esm7({
88699
89238
  "../node_modules/.pnpm/zod@3.25.42/node_modules/zod/dist/esm/v3/helpers/parseUtil.js"() {
88700
- init_errors5();
89239
+ init_errors8();
88701
89240
  init_en2();
88702
89241
  makeIssue2 = (params) => {
88703
89242
  const { data, path: path22, errorMaps, issueData } = params;
@@ -88803,7 +89342,7 @@ See: https://github.com/isaacs/node-glob/issues/167`);
88803
89342
  init_types5 = __esm7({
88804
89343
  "../node_modules/.pnpm/zod@3.25.42/node_modules/zod/dist/esm/v3/types.js"() {
88805
89344
  init_ZodError2();
88806
- init_errors5();
89345
+ init_errors8();
88807
89346
  init_errorUtil2();
88808
89347
  init_parseUtil2();
88809
89348
  init_util2();
@@ -91961,7 +92500,7 @@ See: https://github.com/isaacs/node-glob/issues/167`);
91961
92500
  });
91962
92501
  init_external2 = __esm7({
91963
92502
  "../node_modules/.pnpm/zod@3.25.42/node_modules/zod/dist/esm/v3/external.js"() {
91964
- init_errors5();
92503
+ init_errors8();
91965
92504
  init_parseUtil2();
91966
92505
  init_typeAliases2();
91967
92506
  init_util2();
@@ -122615,7 +123154,7 @@ async function seedTimebackIntegrations(db2, gameId, courses) {
122615
123154
  const courseId = course.courseId || `mock-${course.subject.toLowerCase()}-g${course.grade}`;
122616
123155
  try {
122617
123156
  const existing = await db2.query.gameTimebackIntegrations.findFirst({
122618
- where: (table9, { and: and3, eq: eq3 }) => and3(eq3(table9.gameId, gameId), eq3(table9.grade, course.grade), eq3(table9.subject, course.subject))
123157
+ where: (table9, { and: and3, eq: eq3 }) => and3(eq3(table9.gameId, gameId), eq3(table9.grade, course.grade), eq3(table9.subject, course.subject), isActiveGameTimebackIntegrationStatus(table9.status))
122619
123158
  });
122620
123159
  if (!existing) {
122621
123160
  await db2.insert(gameTimebackIntegrations).values({
@@ -122635,6 +123174,7 @@ async function seedTimebackIntegrations(db2, gameId, courses) {
122635
123174
  return seededCount;
122636
123175
  }
122637
123176
  var init_timeback5 = __esm(() => {
123177
+ init_helpers_index();
122638
123178
  init_tables_index();
122639
123179
  init_config();
122640
123180
  });
@@ -123840,6 +124380,7 @@ var init_game_member_controller = __esm(() => {
123840
124380
  var list2;
123841
124381
  var listAccessible;
123842
124382
  var getSubjects;
124383
+ var getTimebackSummaries;
123843
124384
  var getById;
123844
124385
  var getBySlug;
123845
124386
  var getManifest;
@@ -123855,6 +124396,7 @@ var init_game_controller = __esm(() => {
123855
124396
  list2 = requireNonAnonymous(async (ctx) => ctx.services.game.list(ctx.user));
123856
124397
  listAccessible = requireNonAnonymous(async (ctx) => ctx.services.game.listAccessible(ctx.user));
123857
124398
  getSubjects = requireNonAnonymous(async (ctx) => ctx.services.game.getSubjects());
124399
+ getTimebackSummaries = requireGameManagementAccess(async (ctx) => ctx.services.game.getTimebackSummaries(ctx.user));
123858
124400
  getById = requireNonAnonymous(async (ctx) => {
123859
124401
  const gameId = requireGameId(ctx.params.gameId);
123860
124402
  return ctx.services.game.getById(gameId, ctx.user);
@@ -123902,6 +124444,7 @@ var init_game_controller = __esm(() => {
123902
124444
  list: list2,
123903
124445
  listAccessible,
123904
124446
  getSubjects,
124447
+ getTimebackSummaries,
123905
124448
  getById,
123906
124449
  getManifest,
123907
124450
  getBySlug,
@@ -124305,7 +124848,11 @@ var getUserEnrollments;
124305
124848
  var getUserById;
124306
124849
  var setupIntegration;
124307
124850
  var getIntegrations;
124851
+ var getRemovedIntegrations;
124852
+ var createIntegration;
124308
124853
  var updateIntegration;
124854
+ var deactivateCourse;
124855
+ var reactivateCourse;
124309
124856
  var getIntegrationConfig;
124310
124857
  var verifyIntegration;
124311
124858
  var getConfig;
@@ -124403,6 +124950,27 @@ var init_timeback_controller = __esm(() => {
124403
124950
  }
124404
124951
  return ctx.services.timeback.getIntegrations(gameId, ctx.user);
124405
124952
  });
124953
+ getRemovedIntegrations = requireGameManagementAccess(async (ctx) => {
124954
+ const gameId = ctx.params.gameId;
124955
+ if (!gameId) {
124956
+ throw ApiError.badRequest("Missing gameId");
124957
+ }
124958
+ if (!isValidUUID(gameId)) {
124959
+ throw ApiError.unprocessableEntity("Invalid gameId format");
124960
+ }
124961
+ return ctx.services.timeback.getRemovedIntegrations(gameId, ctx.user);
124962
+ });
124963
+ createIntegration = requireDeveloper(async (ctx) => {
124964
+ const gameId = ctx.params.gameId;
124965
+ if (!gameId) {
124966
+ throw ApiError.badRequest("Missing gameId");
124967
+ }
124968
+ if (!isValidUUID(gameId)) {
124969
+ throw ApiError.unprocessableEntity("Invalid gameId format");
124970
+ }
124971
+ const body2 = await parseRequestBody(ctx.request, CreateGameTimebackIntegrationRequestSchema);
124972
+ return ctx.services.timeback.createIntegration(gameId, ctx.user, body2);
124973
+ });
124406
124974
  updateIntegration = requireDeveloper(async (ctx) => {
124407
124975
  const { gameId, courseId } = ctx.params;
124408
124976
  if (!gameId || !courseId) {
@@ -124414,6 +124982,26 @@ var init_timeback_controller = __esm(() => {
124414
124982
  const body2 = await parseRequestBody(ctx.request, UpdateGameTimebackIntegrationRequestSchema);
124415
124983
  return ctx.services.timeback.updateIntegration(gameId, courseId, ctx.user, body2);
124416
124984
  });
124985
+ deactivateCourse = requireDeveloper(async (ctx) => {
124986
+ const { gameId, courseId } = ctx.params;
124987
+ if (!gameId || !courseId) {
124988
+ throw ApiError.badRequest("Missing gameId or courseId parameter");
124989
+ }
124990
+ if (!isValidUUID(gameId)) {
124991
+ throw ApiError.unprocessableEntity("Invalid gameId format");
124992
+ }
124993
+ return ctx.services.timeback.deactivateCourse(gameId, courseId, ctx.user);
124994
+ });
124995
+ reactivateCourse = requireDeveloper(async (ctx) => {
124996
+ const { gameId, courseId } = ctx.params;
124997
+ if (!gameId || !courseId) {
124998
+ throw ApiError.badRequest("Missing gameId or courseId parameter");
124999
+ }
125000
+ if (!isValidUUID(gameId)) {
125001
+ throw ApiError.unprocessableEntity("Invalid gameId format");
125002
+ }
125003
+ return ctx.services.timeback.reactivateCourse(gameId, courseId, ctx.user);
125004
+ });
124417
125005
  getIntegrationConfig = requireGameManagementAccess(async (ctx) => {
124418
125006
  const { gameId, courseId } = ctx.params;
124419
125007
  if (!gameId || !courseId) {
@@ -124563,11 +125151,11 @@ var init_timeback_controller = __esm(() => {
124563
125151
  let subject;
124564
125152
  if (gradeParam !== null && subjectParam !== null) {
124565
125153
  const parsedGrade = parseInt(gradeParam, 10);
124566
- if (!isTimebackGrade(parsedGrade)) {
125154
+ if (!isTimebackGrade2(parsedGrade)) {
124567
125155
  throw ApiError.badRequest(`Invalid grade: ${gradeParam}. Valid grades: ${TIMEBACK_GRADES.join(", ")}`);
124568
125156
  }
124569
- if (!isTimebackSubject(subjectParam)) {
124570
- throw ApiError.badRequest(`Invalid subject: ${subjectParam}. Valid subjects: ${TIMEBACK_SUBJECTS.join(", ")}`);
125157
+ if (!isTimebackSubject2(subjectParam)) {
125158
+ throw ApiError.badRequest(`Invalid subject: ${subjectParam}. Valid subjects: ${TIMEBACK_SUBJECTS2.join(", ")}`);
124571
125159
  }
124572
125160
  grade = parsedGrade;
124573
125161
  subject = subjectParam;
@@ -124603,11 +125191,11 @@ var init_timeback_controller = __esm(() => {
124603
125191
  let subject;
124604
125192
  if (gradeParam !== null && subjectParam !== null) {
124605
125193
  const parsedGrade = parseInt(gradeParam, 10);
124606
- if (!isTimebackGrade(parsedGrade)) {
125194
+ if (!isTimebackGrade2(parsedGrade)) {
124607
125195
  throw ApiError.badRequest(`Invalid grade: ${gradeParam}. Valid grades: ${TIMEBACK_GRADES.join(", ")}`);
124608
125196
  }
124609
- if (!isTimebackSubject(subjectParam)) {
124610
- throw ApiError.badRequest(`Invalid subject: ${subjectParam}. Valid subjects: ${TIMEBACK_SUBJECTS.join(", ")}`);
125197
+ if (!isTimebackSubject2(subjectParam)) {
125198
+ throw ApiError.badRequest(`Invalid subject: ${subjectParam}. Valid subjects: ${TIMEBACK_SUBJECTS2.join(", ")}`);
124611
125199
  }
124612
125200
  grade = parsedGrade;
124613
125201
  subject = subjectParam;
@@ -124637,8 +125225,8 @@ var init_timeback_controller = __esm(() => {
124637
125225
  if (!subjectParam) {
124638
125226
  throw ApiError.badRequest("Missing required subject query parameter");
124639
125227
  }
124640
- if (!isTimebackSubject(subjectParam)) {
124641
- throw ApiError.badRequest(`Invalid subject: ${subjectParam}. Valid subjects: ${TIMEBACK_SUBJECTS.join(", ")}`);
125228
+ if (!isTimebackSubject2(subjectParam)) {
125229
+ throw ApiError.badRequest(`Invalid subject: ${subjectParam}. Valid subjects: ${TIMEBACK_SUBJECTS2.join(", ")}`);
124642
125230
  }
124643
125231
  return ctx.services.timeback.getStudentHighestGradeMastered(timebackId, ctx.user, {
124644
125232
  gameId,
@@ -124949,7 +125537,11 @@ var init_timeback_controller = __esm(() => {
124949
125537
  getUserById,
124950
125538
  setupIntegration,
124951
125539
  getIntegrations,
125540
+ getRemovedIntegrations,
125541
+ createIntegration,
124952
125542
  updateIntegration,
125543
+ deactivateCourse,
125544
+ reactivateCourse,
124953
125545
  getIntegrationConfig,
124954
125546
  verifyIntegration,
124955
125547
  getConfig,
@@ -125909,12 +126501,12 @@ var init_timeback7 = __esm(() => {
125909
126501
  }
125910
126502
  if (gradeParam !== null && subjectParam !== null) {
125911
126503
  const grade = parseInt(gradeParam, 10);
125912
- if (!Number.isFinite(grade) || !isTimebackGrade(grade)) {
126504
+ if (!Number.isFinite(grade) || !isTimebackGrade2(grade)) {
125913
126505
  const error2 = ApiError.badRequest(`Invalid grade: ${gradeParam}. Valid grades: ${TIMEBACK_GRADES.join(", ")}`);
125914
126506
  return c2.json(createErrorResponse(error2), error2.status);
125915
126507
  }
125916
- if (!isTimebackSubject(subjectParam)) {
125917
- const error2 = ApiError.badRequest(`Invalid subject: ${subjectParam}. Valid subjects: ${TIMEBACK_SUBJECTS.join(", ")}`);
126508
+ if (!isTimebackSubject2(subjectParam)) {
126509
+ const error2 = ApiError.badRequest(`Invalid subject: ${subjectParam}. Valid subjects: ${TIMEBACK_SUBJECTS2.join(", ")}`);
125918
126510
  return c2.json(createErrorResponse(error2), error2.status);
125919
126511
  }
125920
126512
  enrollments = enrollments.filter((e) => e.grade === grade && e.subject === subjectParam);
@@ -125967,12 +126559,12 @@ var init_timeback7 = __esm(() => {
125967
126559
  }
125968
126560
  if (gradeParam !== null && subjectParam !== null) {
125969
126561
  const grade = parseInt(gradeParam, 10);
125970
- if (!Number.isFinite(grade) || !isTimebackGrade(grade)) {
126562
+ if (!Number.isFinite(grade) || !isTimebackGrade2(grade)) {
125971
126563
  const error2 = ApiError.badRequest(`Invalid grade: ${gradeParam}. Valid grades: ${TIMEBACK_GRADES.join(", ")}`);
125972
126564
  return c2.json(createErrorResponse(error2), error2.status);
125973
126565
  }
125974
- if (!isTimebackSubject(subjectParam)) {
125975
- const error2 = ApiError.badRequest(`Invalid subject: ${subjectParam}. Valid subjects: ${TIMEBACK_SUBJECTS.join(", ")}`);
126566
+ if (!isTimebackSubject2(subjectParam)) {
126567
+ const error2 = ApiError.badRequest(`Invalid subject: ${subjectParam}. Valid subjects: ${TIMEBACK_SUBJECTS2.join(", ")}`);
125976
126568
  return c2.json(createErrorResponse(error2), error2.status);
125977
126569
  }
125978
126570
  enrollments = enrollments.filter((e) => e.grade === grade && e.subject === subjectParam);
@@ -126021,8 +126613,8 @@ var init_timeback7 = __esm(() => {
126021
126613
  const error2 = ApiError.badRequest("Missing required gameId query parameter");
126022
126614
  return c2.json(createErrorResponse(error2), error2.status);
126023
126615
  }
126024
- if (!isTimebackSubject(subject)) {
126025
- const error2 = ApiError.badRequest(`Invalid subject: ${subject}. Valid subjects: ${TIMEBACK_SUBJECTS.join(", ")}`);
126616
+ if (!isTimebackSubject2(subject)) {
126617
+ const error2 = ApiError.badRequest(`Invalid subject: ${subject}. Valid subjects: ${TIMEBACK_SUBJECTS2.join(", ")}`);
126026
126618
  return c2.json(createErrorResponse(error2), error2.status);
126027
126619
  }
126028
126620
  const db2 = c2.get("db");
@@ -126251,12 +126843,12 @@ import path3 from "node:path";
126251
126843
  import { loadPlaycademyConfig as loadPlaycademyConfig2 } from "playcademy/utils";
126252
126844
 
126253
126845
  // ../utils/src/uuid.ts
126254
- var UUID_REGEX2 = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
126846
+ var UUID_REGEX4 = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
126255
126847
  function isValidUUID2(value) {
126256
126848
  if (!value || typeof value !== "string") {
126257
126849
  return false;
126258
126850
  }
126259
- return UUID_REGEX2.test(value);
126851
+ return UUID_REGEX4.test(value);
126260
126852
  }
126261
126853
  // ../utils/src/ansi.ts
126262
126854
  var colors3 = {
@@ -126703,7 +127295,8 @@ var DEMO_DISPLAY_NAME_PLACEHOLDER2 = "Demo Player";
126703
127295
  var init_auth3 = __esm8(() => {
126704
127296
  AUTH_PROVIDER_IDS2 = {
126705
127297
  TIMEBACK: "timeback",
126706
- TIMEBACK_LTI: "timeback-lti"
127298
+ TIMEBACK_LTI: "timeback-lti",
127299
+ PLAYCADEMY: "playcademy"
126707
127300
  };
126708
127301
  });
126709
127302
  var init_typescript2 = () => {};