@timeback/core 0.1.3 → 0.1.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.
package/dist/utils.js CHANGED
@@ -699,7 +699,17 @@ class TimebackProvider {
699
699
  }
700
700
  function getEnv(key) {
701
701
  try {
702
- return typeof process === "undefined" ? undefined : process.env[key];
702
+ if (typeof process === "undefined")
703
+ return;
704
+ if (typeof key === "string") {
705
+ return process.env[key];
706
+ }
707
+ for (const k of key) {
708
+ const value = process.env[k];
709
+ if (value !== undefined)
710
+ return value;
711
+ }
712
+ return;
703
713
  } catch {
704
714
  return;
705
715
  }
@@ -730,6 +740,18 @@ var DEFAULT_PROVIDER_REGISTRY = {
730
740
  }
731
741
  }
732
742
  };
743
+ function primaryEnvVar(key) {
744
+ if (typeof key === "string") {
745
+ return key;
746
+ }
747
+ if (key.length === 0) {
748
+ throw new Error(`Missing env var key: ${key}`);
749
+ }
750
+ return key[0];
751
+ }
752
+ function formatEnvVarKey(key) {
753
+ return primaryEnvVar(key);
754
+ }
733
755
  function validateEnv(env) {
734
756
  if (env !== "staging" && env !== "production") {
735
757
  throw new Error(`Invalid env "${env}": must be "staging" or "production"`);
@@ -740,10 +762,10 @@ function validateAuth(auth, envVars) {
740
762
  const clientId = auth?.clientId ?? getEnv(envVars.clientId);
741
763
  const clientSecret = auth?.clientSecret ?? getEnv(envVars.clientSecret);
742
764
  if (!clientId) {
743
- throw new Error(`Missing clientId: provide in config or set ${envVars.clientId}`);
765
+ throw new Error(`Missing clientId: provide in config or set ${formatEnvVarKey(envVars.clientId)}`);
744
766
  }
745
767
  if (!clientSecret) {
746
- throw new Error(`Missing clientSecret: provide in config or set ${envVars.clientSecret}`);
768
+ throw new Error(`Missing clientSecret: provide in config or set ${formatEnvVarKey(envVars.clientSecret)}`);
747
769
  }
748
770
  return { clientId, clientSecret };
749
771
  }
@@ -759,21 +781,21 @@ function buildMissingEnvError(envVars) {
759
781
  const clientId = getEnv(envVars.clientId);
760
782
  const clientSecret = getEnv(envVars.clientSecret);
761
783
  if (baseUrl === undefined && clientId === undefined) {
762
- const hint = envVars.env ?? envVars.baseUrl;
784
+ const hint = formatEnvVarKey(envVars.env ?? envVars.baseUrl);
763
785
  return `Missing env: provide in config or set ${hint}`;
764
786
  }
765
787
  const missing = [];
766
788
  if (baseUrl === undefined) {
767
- missing.push(envVars.env ?? envVars.baseUrl);
789
+ missing.push(formatEnvVarKey(envVars.env ?? envVars.baseUrl));
768
790
  }
769
791
  if (baseUrl !== undefined && authUrl === undefined) {
770
- missing.push(envVars.authUrl);
792
+ missing.push(formatEnvVarKey(envVars.authUrl));
771
793
  }
772
794
  if (clientId === undefined) {
773
- missing.push(envVars.clientId);
795
+ missing.push(formatEnvVarKey(envVars.clientId));
774
796
  }
775
797
  if (clientSecret === undefined) {
776
- missing.push(envVars.clientSecret);
798
+ missing.push(formatEnvVarKey(envVars.clientSecret));
777
799
  }
778
800
  return `Missing environment variables: ${missing.join(", ")}`;
779
801
  }
@@ -1264,10 +1286,10 @@ function validateNonEmptyString(value, name) {
1264
1286
  }
1265
1287
  }
1266
1288
  var EDUBRIDGE_ENV_VARS = {
1267
- baseUrl: "EDUBRIDGE_BASE_URL",
1268
- clientId: "EDUBRIDGE_CLIENT_ID",
1269
- clientSecret: "EDUBRIDGE_CLIENT_SECRET",
1270
- authUrl: "EDUBRIDGE_TOKEN_URL"
1289
+ baseUrl: ["TIMEBACK_API_BASE_URL", "TIMEBACK_BASE_URL", "EDUBRIDGE_BASE_URL"],
1290
+ clientId: ["TIMEBACK_API_CLIENT_ID", "TIMEBACK_CLIENT_ID", "EDUBRIDGE_CLIENT_ID"],
1291
+ clientSecret: ["TIMEBACK_API_CLIENT_SECRET", "TIMEBACK_CLIENT_SECRET", "EDUBRIDGE_CLIENT_SECRET"],
1292
+ authUrl: ["TIMEBACK_API_AUTH_URL", "TIMEBACK_AUTH_URL", "EDUBRIDGE_TOKEN_URL"]
1271
1293
  };
1272
1294
  function resolveToProvider2(config, registry = DEFAULT_PROVIDER_REGISTRY) {
1273
1295
  return resolveToProvider(config, EDUBRIDGE_ENV_VARS, registry);
@@ -3595,7 +3617,7 @@ class Doc {
3595
3617
  var version = {
3596
3618
  major: 4,
3597
3619
  minor: 3,
3598
- patch: 5
3620
+ patch: 6
3599
3621
  };
3600
3622
  var $ZodType = /* @__PURE__ */ $constructor("$ZodType", (inst, def) => {
3601
3623
  var _a;
@@ -4879,7 +4901,7 @@ var $ZodRecord = /* @__PURE__ */ $constructor("$ZodRecord", (inst, def) => {
4879
4901
  if (keyResult instanceof Promise) {
4880
4902
  throw new Error("Async schemas not supported in object keys currently");
4881
4903
  }
4882
- const checkNumericKey = typeof key === "string" && number.test(key) && keyResult.issues.length && keyResult.issues.some((iss) => iss.code === "invalid_type" && iss.expected === "number");
4904
+ const checkNumericKey = typeof key === "string" && number.test(key) && keyResult.issues.length;
4883
4905
  if (checkNumericKey) {
4884
4906
  const retryResult = def.keyType._zod.run({ value: Number(key), issues: [] }, ctx);
4885
4907
  if (retryResult instanceof Promise) {
@@ -12196,7 +12218,7 @@ function finalize(ctx, schema) {
12196
12218
  }
12197
12219
  }
12198
12220
  }
12199
- if (refSchema.$ref) {
12221
+ if (refSchema.$ref && refSeen.def) {
12200
12222
  for (const key in schema2) {
12201
12223
  if (key === "$ref" || key === "allOf")
12202
12224
  continue;
@@ -14828,7 +14850,7 @@ var TimebackSubject = exports_external.enum([
14828
14850
  "Math",
14829
14851
  "None",
14830
14852
  "Other"
14831
- ]);
14853
+ ]).meta({ id: "TimebackSubject", description: "Subject area" });
14832
14854
  var TimebackGrade = exports_external.union([
14833
14855
  exports_external.literal(-1),
14834
14856
  exports_external.literal(0),
@@ -14845,7 +14867,10 @@ var TimebackGrade = exports_external.union([
14845
14867
  exports_external.literal(11),
14846
14868
  exports_external.literal(12),
14847
14869
  exports_external.literal(13)
14848
- ]);
14870
+ ]).meta({
14871
+ id: "TimebackGrade",
14872
+ description: "Grade level (-1 = Pre-K, 0 = K, 1-12 = grades, 13 = AP)"
14873
+ });
14849
14874
  var ScoreStatus = exports_external.enum([
14850
14875
  "exempt",
14851
14876
  "fully graded",
@@ -15075,62 +15100,84 @@ var CaliperListEventsParams = exports_external.object({
15075
15100
  actorEmail: exports_external.email().optional()
15076
15101
  }).strict();
15077
15102
  var CourseIds = exports_external.object({
15078
- staging: exports_external.string().optional(),
15079
- production: exports_external.string().optional()
15080
- });
15081
- var CourseType = exports_external.enum(["base", "hole-filling", "optional"]);
15082
- var PublishStatus = exports_external.enum(["draft", "testing", "published", "deactivated"]);
15103
+ staging: exports_external.string().meta({ description: "Course ID in staging environment" }).optional(),
15104
+ production: exports_external.string().meta({ description: "Course ID in production environment" }).optional()
15105
+ }).meta({ id: "CourseIds", description: "Environment-specific course IDs (populated by sync)" });
15106
+ var CourseType = exports_external.enum(["base", "hole-filling", "optional"]).meta({ id: "CourseType", description: "Course classification type" });
15107
+ var PublishStatus = exports_external.enum(["draft", "testing", "published", "deactivated"]).meta({ id: "PublishStatus", description: "Course publication status" });
15083
15108
  var CourseGoals = exports_external.object({
15084
- dailyXp: exports_external.number().int().positive().optional(),
15085
- dailyLessons: exports_external.number().int().positive().optional(),
15086
- dailyActiveMinutes: exports_external.number().int().positive().optional(),
15087
- dailyAccuracy: exports_external.number().int().min(0).max(100).optional(),
15088
- dailyMasteredUnits: exports_external.number().int().positive().optional()
15089
- });
15109
+ dailyXp: exports_external.number().int().positive().meta({ description: "Target XP to earn per day" }).optional(),
15110
+ dailyLessons: exports_external.number().int().positive().meta({ description: "Target lessons to complete per day" }).optional(),
15111
+ dailyActiveMinutes: exports_external.number().int().positive().meta({ description: "Target active learning minutes per day" }).optional(),
15112
+ dailyAccuracy: exports_external.number().int().min(0).max(100).meta({ description: "Target accuracy percentage (0-100)" }).optional(),
15113
+ dailyMasteredUnits: exports_external.number().int().positive().meta({ description: "Target units to master per day" }).optional()
15114
+ }).meta({ id: "CourseGoals", description: "Daily learning goals for a course" });
15090
15115
  var CourseMetrics = exports_external.object({
15091
- totalXp: exports_external.number().int().positive().optional(),
15092
- totalLessons: exports_external.number().int().positive().optional(),
15093
- totalGrades: exports_external.number().int().positive().optional()
15094
- });
15116
+ totalXp: exports_external.number().int().positive().meta({ description: "Total XP available in the course" }).optional(),
15117
+ totalLessons: exports_external.number().int().positive().meta({ description: "Total number of lessons/activities" }).optional(),
15118
+ totalGrades: exports_external.number().int().positive().meta({ description: "Total grade levels covered" }).optional()
15119
+ }).meta({ id: "CourseMetrics", description: "Aggregate metrics for a course" });
15095
15120
  var CourseMetadata = exports_external.object({
15096
15121
  courseType: CourseType.optional(),
15097
- isSupplemental: exports_external.boolean().optional(),
15098
- isCustom: exports_external.boolean().optional(),
15122
+ isSupplemental: exports_external.boolean().meta({ description: "Whether this is supplemental to a base course" }).optional(),
15123
+ isCustom: exports_external.boolean().meta({ description: "Whether this is a custom course for an individual student" }).optional(),
15099
15124
  publishStatus: PublishStatus.optional(),
15100
- contactEmail: exports_external.email().optional(),
15101
- primaryApp: exports_external.string().optional(),
15125
+ contactEmail: exports_external.email().meta({ description: "Contact email for course issues" }).optional(),
15126
+ primaryApp: exports_external.string().meta({ description: "Primary application identifier" }).optional(),
15102
15127
  goals: CourseGoals.optional(),
15103
15128
  metrics: CourseMetrics.optional()
15104
- });
15129
+ }).meta({ id: "CourseMetadata", description: "Course metadata (matches API metadata object)" });
15105
15130
  var CourseDefaults = exports_external.object({
15106
- courseCode: exports_external.string().optional(),
15107
- level: exports_external.string().optional(),
15131
+ courseCode: exports_external.string().meta({ description: "Course code (e.g., 'MATH101')" }).optional(),
15132
+ level: exports_external.string().meta({ description: "Course level (e.g., 'AP', 'Honors')" }).optional(),
15108
15133
  metadata: CourseMetadata.optional()
15134
+ }).meta({
15135
+ id: "CourseDefaults",
15136
+ description: "Default properties that apply to all courses unless overridden"
15109
15137
  });
15110
15138
  var CourseEnvOverrides = exports_external.object({
15111
- level: exports_external.string().optional(),
15112
- sensor: exports_external.string().url().optional(),
15113
- launchUrl: exports_external.string().url().optional(),
15139
+ level: exports_external.string().meta({ description: "Course level for this environment" }).optional(),
15140
+ sensor: exports_external.url().meta({ description: "Caliper sensor endpoint URL for this environment" }).optional(),
15141
+ launchUrl: exports_external.url().meta({ description: "LTI launch URL for this environment" }).optional(),
15114
15142
  metadata: CourseMetadata.optional()
15143
+ }).meta({
15144
+ id: "CourseEnvOverrides",
15145
+ description: "Environment-specific course overrides (non-identity fields)"
15115
15146
  });
15116
15147
  var CourseOverrides = exports_external.object({
15117
- staging: CourseEnvOverrides.optional(),
15118
- production: CourseEnvOverrides.optional()
15119
- });
15148
+ staging: CourseEnvOverrides.meta({
15149
+ description: "Overrides for staging environment"
15150
+ }).optional(),
15151
+ production: CourseEnvOverrides.meta({
15152
+ description: "Overrides for production environment"
15153
+ }).optional()
15154
+ }).meta({ id: "CourseOverrides", description: "Per-environment course overrides" });
15120
15155
  var CourseConfig = CourseDefaults.extend({
15121
- subject: TimebackSubject,
15122
- grade: TimebackGrade.optional(),
15156
+ subject: TimebackSubject.meta({ description: "Subject area for this course" }),
15157
+ grade: TimebackGrade.meta({
15158
+ description: "Grade level (-1 = Pre-K, 0 = K, 1-12 = grades, 13 = AP)"
15159
+ }).optional(),
15123
15160
  ids: CourseIds.nullable().optional(),
15124
- sensor: exports_external.string().url().optional(),
15125
- launchUrl: exports_external.string().url().optional(),
15161
+ sensor: exports_external.url().meta({ description: "Caliper sensor endpoint URL for this course" }).optional(),
15162
+ launchUrl: exports_external.url().meta({ description: "LTI launch URL for this course" }).optional(),
15126
15163
  overrides: CourseOverrides.optional()
15164
+ }).meta({
15165
+ id: "CourseConfig",
15166
+ description: "Configuration for a single course. Must have either grade or courseCode (or both)."
15127
15167
  });
15128
15168
  var TimebackConfig = exports_external.object({
15129
- name: exports_external.string().min(1, "App name is required"),
15130
- defaults: CourseDefaults.optional(),
15131
- courses: exports_external.array(CourseConfig).min(1, "At least one course is required"),
15132
- sensor: exports_external.string().url().optional(),
15133
- launchUrl: exports_external.string().url().optional()
15169
+ $schema: exports_external.string().meta({ description: "JSON Schema reference for editor support" }).optional(),
15170
+ name: exports_external.string().min(1, "App name is required").meta({ description: "Display name for your app" }),
15171
+ defaults: CourseDefaults.meta({
15172
+ description: "Default properties applied to all courses"
15173
+ }).optional(),
15174
+ courses: exports_external.array(CourseConfig).min(1, "At least one course is required").meta({ description: "Courses available in this app" }),
15175
+ sensor: exports_external.url().meta({ description: "Default Caliper sensor endpoint URL for all courses" }).optional(),
15176
+ launchUrl: exports_external.url().meta({ description: "Default LTI launch URL for all courses" }).optional()
15177
+ }).meta({
15178
+ id: "TimebackConfig",
15179
+ title: "Timeback Config",
15180
+ description: "Configuration schema for timeback.config.json files"
15134
15181
  }).refine((config2) => {
15135
15182
  return config2.courses.every((c) => c.grade !== undefined || c.courseCode !== undefined);
15136
15183
  }, {
@@ -15151,17 +15198,23 @@ var TimebackConfig = exports_external.object({
15151
15198
  message: "Duplicate courseCode found; each must be unique",
15152
15199
  path: ["courses"]
15153
15200
  }).refine((config2) => {
15154
- return config2.courses.every((c) => c.sensor !== undefined || config2.sensor !== undefined);
15155
- }, {
15156
- message: "Each course must have an effective sensor; set a top-level `sensor` or per-course `sensor`",
15157
- path: ["courses"]
15158
- }).refine((config2) => {
15159
- return config2.courses.every((c) => c.launchUrl !== undefined || config2.launchUrl !== undefined);
15201
+ return config2.courses.every((c) => {
15202
+ if (c.sensor !== undefined || config2.sensor !== undefined) {
15203
+ return true;
15204
+ }
15205
+ const launchUrls = [
15206
+ c.launchUrl,
15207
+ config2.launchUrl,
15208
+ c.overrides?.staging?.launchUrl,
15209
+ c.overrides?.production?.launchUrl
15210
+ ].filter(Boolean);
15211
+ return launchUrls.length > 0;
15212
+ });
15160
15213
  }, {
15161
- message: "Each course must have an effective launchUrl; set a top-level `launchUrl` or per-course `launchUrl`",
15214
+ message: "Each course must have an effective sensor. Either set `sensor` explicitly (top-level or per-course), or provide a `launchUrl` so sensor can be derived from its origin.",
15162
15215
  path: ["courses"]
15163
15216
  });
15164
- var EdubridgeDateString = exports_external.union([IsoDateString, IsoDateTimeString]);
15217
+ var EdubridgeDateString = IsoDateTimeString;
15165
15218
  var EduBridgeEnrollment = exports_external.object({
15166
15219
  id: exports_external.string(),
15167
15220
  role: exports_external.string(),
@@ -15225,13 +15278,11 @@ var EmailOrStudentId = exports_external.object({
15225
15278
  });
15226
15279
  var SubjectTrackInput = exports_external.object({
15227
15280
  subject: NonEmptyString,
15228
- gradeLevel: NonEmptyString,
15229
- targetCourseId: NonEmptyString,
15230
- metadata: exports_external.record(exports_external.string(), exports_external.unknown()).optional()
15231
- });
15232
- var SubjectTrackUpsertInput = SubjectTrackInput.extend({
15233
- id: NonEmptyString
15281
+ grade: NonEmptyString,
15282
+ courseId: NonEmptyString,
15283
+ orgSourcedId: NonEmptyString.optional()
15234
15284
  });
15285
+ var SubjectTrackUpsertInput = SubjectTrackInput;
15235
15286
  var EdubridgeListEnrollmentsParams = exports_external.object({
15236
15287
  userId: NonEmptyString
15237
15288
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timeback/core",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -30,14 +30,15 @@
30
30
  "test": "bun test --no-env-file unit.test.ts"
31
31
  },
32
32
  "devDependencies": {
33
- "@timeback/caliper": "0.1.2",
34
- "@timeback/edubridge": "0.1.1",
33
+ "@timeback/caliper": "0.1.3",
34
+ "@timeback/edubridge": "0.1.3",
35
35
  "@timeback/internal-client-infra": "0.0.0",
36
36
  "@timeback/internal-constants": "0.0.0",
37
+ "@timeback/internal-test": "0.0.0",
37
38
  "@timeback/internal-utils": "0.0.0",
38
- "@timeback/oneroster": "0.1.3",
39
- "@timeback/powerpath": "0.1.1",
40
- "@timeback/qti": "0.1.1",
39
+ "@timeback/oneroster": "0.1.5",
40
+ "@timeback/powerpath": "0.1.2",
41
+ "@timeback/qti": "0.1.2",
41
42
  "@types/bun": "latest"
42
43
  },
43
44
  "peerDependencies": {