@timeback/sdk 0.1.5 → 0.1.7

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 (123) hide show
  1. package/README.md +21 -22
  2. package/dist/client/adapters/react/hooks/types.d.ts +15 -0
  3. package/dist/client/adapters/react/hooks/types.d.ts.map +1 -0
  4. package/dist/client/adapters/react/hooks/useTimebackVerification.d.ts +18 -0
  5. package/dist/client/adapters/react/hooks/useTimebackVerification.d.ts.map +1 -0
  6. package/dist/client/adapters/react/index.d.ts +2 -0
  7. package/dist/client/adapters/react/index.d.ts.map +1 -1
  8. package/dist/client/adapters/react/index.js +139 -9
  9. package/dist/client/auth/bearer.d.ts +17 -0
  10. package/dist/client/auth/bearer.d.ts.map +1 -0
  11. package/dist/client/auth/index.d.ts +3 -0
  12. package/dist/client/auth/index.d.ts.map +1 -0
  13. package/dist/client/auth/types.d.ts +39 -0
  14. package/dist/client/auth/types.d.ts.map +1 -0
  15. package/dist/client/index.d.ts +2 -0
  16. package/dist/client/index.d.ts.map +1 -1
  17. package/dist/client/lib/fetch.d.ts +19 -0
  18. package/dist/client/lib/fetch.d.ts.map +1 -0
  19. package/dist/client/namespaces/user.d.ts +25 -2
  20. package/dist/client/namespaces/user.d.ts.map +1 -1
  21. package/dist/client/timeback-client.class.d.ts +15 -0
  22. package/dist/client/timeback-client.class.d.ts.map +1 -1
  23. package/dist/client/timeback-client.d.ts +3 -0
  24. package/dist/client/timeback-client.d.ts.map +1 -1
  25. package/dist/client.d.ts +2 -1
  26. package/dist/client.d.ts.map +1 -1
  27. package/dist/client.js +69 -6
  28. package/dist/edge.d.ts +1 -1
  29. package/dist/edge.js +85291 -169
  30. package/dist/identity.js +85186 -74
  31. package/dist/index.js +1289 -840
  32. package/dist/server/adapters/express.d.ts.map +1 -1
  33. package/dist/server/adapters/express.js +489 -388
  34. package/dist/server/adapters/native.d.ts.map +1 -1
  35. package/dist/server/adapters/native.js +32 -1
  36. package/dist/server/adapters/nextjs.js +32 -1
  37. package/dist/server/adapters/nuxt.d.ts.map +1 -1
  38. package/dist/server/adapters/nuxt.js +493 -388
  39. package/dist/server/adapters/solid-start.d.ts.map +1 -1
  40. package/dist/server/adapters/solid-start.js +493 -388
  41. package/dist/server/adapters/svelte-kit.d.ts.map +1 -1
  42. package/dist/server/adapters/svelte-kit.js +37 -1
  43. package/dist/server/adapters/tanstack-start.d.ts.map +1 -1
  44. package/dist/server/adapters/tanstack-start.js +488 -388
  45. package/dist/server/adapters/utils.d.ts +1 -1
  46. package/dist/server/adapters/utils.d.ts.map +1 -1
  47. package/dist/server/{lib/build-activity-events.d.ts → handlers/activity/caliper.d.ts} +29 -4
  48. package/dist/server/handlers/activity/caliper.d.ts.map +1 -0
  49. package/dist/server/handlers/activity/gradebook.d.ts +56 -0
  50. package/dist/server/handlers/activity/gradebook.d.ts.map +1 -0
  51. package/dist/server/handlers/activity/handler.d.ts +15 -0
  52. package/dist/server/handlers/activity/handler.d.ts.map +1 -0
  53. package/dist/server/handlers/activity/index.d.ts +9 -0
  54. package/dist/server/handlers/activity/index.d.ts.map +1 -0
  55. package/dist/server/handlers/activity/resolve.d.ts +39 -0
  56. package/dist/server/handlers/activity/resolve.d.ts.map +1 -0
  57. package/dist/server/handlers/activity/schema.d.ts +52 -0
  58. package/dist/server/handlers/activity/schema.d.ts.map +1 -0
  59. package/dist/server/handlers/activity/types.d.ts +52 -0
  60. package/dist/server/handlers/activity/types.d.ts.map +1 -0
  61. package/dist/server/handlers/identity/handler.d.ts +14 -0
  62. package/dist/server/handlers/identity/handler.d.ts.map +1 -0
  63. package/dist/server/handlers/identity/index.d.ts +8 -0
  64. package/dist/server/handlers/identity/index.d.ts.map +1 -0
  65. package/dist/server/handlers/identity/oidc.d.ts +43 -0
  66. package/dist/server/handlers/identity/oidc.d.ts.map +1 -0
  67. package/dist/server/handlers/identity/types.d.ts +24 -0
  68. package/dist/server/handlers/identity/types.d.ts.map +1 -0
  69. package/dist/server/handlers/identity-only/handler.d.ts +15 -0
  70. package/dist/server/handlers/identity-only/handler.d.ts.map +1 -0
  71. package/dist/server/handlers/identity-only/index.d.ts +8 -0
  72. package/dist/server/handlers/identity-only/index.d.ts.map +1 -0
  73. package/dist/server/handlers/identity-only/oidc.d.ts +26 -0
  74. package/dist/server/handlers/identity-only/oidc.d.ts.map +1 -0
  75. package/dist/server/handlers/identity-only/types.d.ts +19 -0
  76. package/dist/server/handlers/identity-only/types.d.ts.map +1 -0
  77. package/dist/server/handlers/index.d.ts +5 -2
  78. package/dist/server/handlers/index.d.ts.map +1 -1
  79. package/dist/server/{lib/build-user-profile.d.ts → handlers/user/enrollments.d.ts} +7 -2
  80. package/dist/server/handlers/user/enrollments.d.ts.map +1 -0
  81. package/dist/server/handlers/user/handler.d.ts +17 -0
  82. package/dist/server/handlers/user/handler.d.ts.map +1 -0
  83. package/dist/server/handlers/user/index.d.ts +10 -0
  84. package/dist/server/handlers/user/index.d.ts.map +1 -0
  85. package/dist/server/handlers/user/profile.d.ts +22 -0
  86. package/dist/server/handlers/user/profile.d.ts.map +1 -0
  87. package/dist/server/handlers/user/types.d.ts +35 -0
  88. package/dist/server/handlers/user/types.d.ts.map +1 -0
  89. package/dist/server/handlers/user/verify.d.ts +25 -0
  90. package/dist/server/handlers/user/verify.d.ts.map +1 -0
  91. package/dist/server/index.d.ts +1 -1
  92. package/dist/server/index.d.ts.map +1 -1
  93. package/dist/server/lib/index.d.ts +4 -5
  94. package/dist/server/lib/index.d.ts.map +1 -1
  95. package/dist/server/lib/resolve.d.ts +4 -42
  96. package/dist/server/lib/resolve.d.ts.map +1 -1
  97. package/dist/server/lib/sso.d.ts +86 -0
  98. package/dist/server/lib/sso.d.ts.map +1 -0
  99. package/dist/server/lib/utils.d.ts +32 -1
  100. package/dist/server/lib/utils.d.ts.map +1 -1
  101. package/dist/server/timeback-identity.d.ts +2 -2
  102. package/dist/server/timeback-identity.d.ts.map +1 -1
  103. package/dist/server/timeback.d.ts.map +1 -1
  104. package/dist/server/types.d.ts +19 -12
  105. package/dist/server/types.d.ts.map +1 -1
  106. package/dist/shared/constants.d.ts +1 -0
  107. package/dist/shared/constants.d.ts.map +1 -1
  108. package/dist/shared/types.d.ts +18 -3
  109. package/dist/shared/types.d.ts.map +1 -1
  110. package/package.json +7 -7
  111. package/dist/config.d.ts +0 -20
  112. package/dist/config.d.ts.map +0 -1
  113. package/dist/config.js +0 -0
  114. package/dist/server/handlers/activity.d.ts +0 -25
  115. package/dist/server/handlers/activity.d.ts.map +0 -1
  116. package/dist/server/handlers/identity-full.d.ts +0 -28
  117. package/dist/server/handlers/identity-full.d.ts.map +0 -1
  118. package/dist/server/handlers/identity-only.d.ts +0 -22
  119. package/dist/server/handlers/identity-only.d.ts.map +0 -1
  120. package/dist/server/handlers/user.d.ts +0 -31
  121. package/dist/server/handlers/user.d.ts.map +0 -1
  122. package/dist/server/lib/build-activity-events.d.ts.map +0 -1
  123. package/dist/server/lib/build-user-profile.d.ts.map +0 -1
@@ -434,12 +434,32 @@ async function getUserInfo(params) {
434
434
  return response.json();
435
435
  }
436
436
  // src/server/lib/utils.ts
437
+ function safeIdSegment(value) {
438
+ return encodeURIComponent(value).replace(/%/g, "_");
439
+ }
440
+ function hashSuffix64Base36(value) {
441
+ let hash = 0xcbf29ce484222325n;
442
+ const prime = 0x100000001b3n;
443
+ const mod64 = 0xffffffffffffffffn;
444
+ for (let i = 0;i < value.length; i++) {
445
+ hash ^= BigInt(value.charCodeAt(i));
446
+ hash = hash * prime & mod64;
447
+ }
448
+ const base36 = hash.toString(36);
449
+ return base36.length > 12 ? base36.slice(-12) : base36;
450
+ }
437
451
  function mapEnvForApi(env) {
438
452
  if (env === "local" || env === "staging") {
439
453
  return "staging";
440
454
  }
441
455
  return "production";
442
456
  }
457
+ function normalizeEnv(env) {
458
+ if (env === "production" || env === "local" || env === "staging") {
459
+ return env;
460
+ }
461
+ return "staging";
462
+ }
443
463
  function jsonResponse(data, status = 200, headers) {
444
464
  const responseHeaders = new Headers(headers);
445
465
  responseHeaders.set("Content-Type", "application/json");
@@ -15391,7 +15411,7 @@ var TimebackSubject = exports_external.enum([
15391
15411
  "Math",
15392
15412
  "None",
15393
15413
  "Other"
15394
- ]);
15414
+ ]).meta({ id: "TimebackSubject", description: "Subject area" });
15395
15415
  var TimebackGrade = exports_external.union([
15396
15416
  exports_external.literal(-1),
15397
15417
  exports_external.literal(0),
@@ -15408,7 +15428,10 @@ var TimebackGrade = exports_external.union([
15408
15428
  exports_external.literal(11),
15409
15429
  exports_external.literal(12),
15410
15430
  exports_external.literal(13)
15411
- ]);
15431
+ ]).meta({
15432
+ id: "TimebackGrade",
15433
+ description: "Grade level (-1 = Pre-K, 0 = K, 1-12 = grades, 13 = AP)"
15434
+ });
15412
15435
  var ScoreStatus = exports_external.enum([
15413
15436
  "exempt",
15414
15437
  "fully graded",
@@ -15638,62 +15661,84 @@ var CaliperListEventsParams = exports_external.object({
15638
15661
  actorEmail: exports_external.email().optional()
15639
15662
  }).strict();
15640
15663
  var CourseIds = exports_external.object({
15641
- staging: exports_external.string().optional(),
15642
- production: exports_external.string().optional()
15643
- });
15644
- var CourseType = exports_external.enum(["base", "hole-filling", "optional"]);
15645
- var PublishStatus = exports_external.enum(["draft", "testing", "published", "deactivated"]);
15664
+ staging: exports_external.string().meta({ description: "Course ID in staging environment" }).optional(),
15665
+ production: exports_external.string().meta({ description: "Course ID in production environment" }).optional()
15666
+ }).meta({ id: "CourseIds", description: "Environment-specific course IDs (populated by sync)" });
15667
+ var CourseType = exports_external.enum(["base", "hole-filling", "optional"]).meta({ id: "CourseType", description: "Course classification type" });
15668
+ var PublishStatus = exports_external.enum(["draft", "testing", "published", "deactivated"]).meta({ id: "PublishStatus", description: "Course publication status" });
15646
15669
  var CourseGoals = exports_external.object({
15647
- dailyXp: exports_external.number().int().positive().optional(),
15648
- dailyLessons: exports_external.number().int().positive().optional(),
15649
- dailyActiveMinutes: exports_external.number().int().positive().optional(),
15650
- dailyAccuracy: exports_external.number().int().min(0).max(100).optional(),
15651
- dailyMasteredUnits: exports_external.number().int().positive().optional()
15652
- });
15670
+ dailyXp: exports_external.number().int().positive().meta({ description: "Target XP to earn per day" }).optional(),
15671
+ dailyLessons: exports_external.number().int().positive().meta({ description: "Target lessons to complete per day" }).optional(),
15672
+ dailyActiveMinutes: exports_external.number().int().positive().meta({ description: "Target active learning minutes per day" }).optional(),
15673
+ dailyAccuracy: exports_external.number().int().min(0).max(100).meta({ description: "Target accuracy percentage (0-100)" }).optional(),
15674
+ dailyMasteredUnits: exports_external.number().int().positive().meta({ description: "Target units to master per day" }).optional()
15675
+ }).meta({ id: "CourseGoals", description: "Daily learning goals for a course" });
15653
15676
  var CourseMetrics = exports_external.object({
15654
- totalXp: exports_external.number().int().positive().optional(),
15655
- totalLessons: exports_external.number().int().positive().optional(),
15656
- totalGrades: exports_external.number().int().positive().optional()
15657
- });
15677
+ totalXp: exports_external.number().int().positive().meta({ description: "Total XP available in the course" }).optional(),
15678
+ totalLessons: exports_external.number().int().positive().meta({ description: "Total number of lessons/activities" }).optional(),
15679
+ totalGrades: exports_external.number().int().positive().meta({ description: "Total grade levels covered" }).optional()
15680
+ }).meta({ id: "CourseMetrics", description: "Aggregate metrics for a course" });
15658
15681
  var CourseMetadata = exports_external.object({
15659
15682
  courseType: CourseType.optional(),
15660
- isSupplemental: exports_external.boolean().optional(),
15661
- isCustom: exports_external.boolean().optional(),
15683
+ isSupplemental: exports_external.boolean().meta({ description: "Whether this is supplemental to a base course" }).optional(),
15684
+ isCustom: exports_external.boolean().meta({ description: "Whether this is a custom course for an individual student" }).optional(),
15662
15685
  publishStatus: PublishStatus.optional(),
15663
- contactEmail: exports_external.email().optional(),
15664
- primaryApp: exports_external.string().optional(),
15686
+ contactEmail: exports_external.email().meta({ description: "Contact email for course issues" }).optional(),
15687
+ primaryApp: exports_external.string().meta({ description: "Primary application identifier" }).optional(),
15665
15688
  goals: CourseGoals.optional(),
15666
15689
  metrics: CourseMetrics.optional()
15667
- });
15690
+ }).meta({ id: "CourseMetadata", description: "Course metadata (matches API metadata object)" });
15668
15691
  var CourseDefaults = exports_external.object({
15669
- courseCode: exports_external.string().optional(),
15670
- level: exports_external.string().optional(),
15692
+ courseCode: exports_external.string().meta({ description: "Course code (e.g., 'MATH101')" }).optional(),
15693
+ level: exports_external.string().meta({ description: "Course level (e.g., 'AP', 'Honors')" }).optional(),
15671
15694
  metadata: CourseMetadata.optional()
15695
+ }).meta({
15696
+ id: "CourseDefaults",
15697
+ description: "Default properties that apply to all courses unless overridden"
15672
15698
  });
15673
15699
  var CourseEnvOverrides = exports_external.object({
15674
- level: exports_external.string().optional(),
15675
- sensor: exports_external.string().url().optional(),
15676
- launchUrl: exports_external.string().url().optional(),
15700
+ level: exports_external.string().meta({ description: "Course level for this environment" }).optional(),
15701
+ sensor: exports_external.url().meta({ description: "Caliper sensor endpoint URL for this environment" }).optional(),
15702
+ launchUrl: exports_external.url().meta({ description: "LTI launch URL for this environment" }).optional(),
15677
15703
  metadata: CourseMetadata.optional()
15704
+ }).meta({
15705
+ id: "CourseEnvOverrides",
15706
+ description: "Environment-specific course overrides (non-identity fields)"
15678
15707
  });
15679
15708
  var CourseOverrides = exports_external.object({
15680
- staging: CourseEnvOverrides.optional(),
15681
- production: CourseEnvOverrides.optional()
15682
- });
15709
+ staging: CourseEnvOverrides.meta({
15710
+ description: "Overrides for staging environment"
15711
+ }).optional(),
15712
+ production: CourseEnvOverrides.meta({
15713
+ description: "Overrides for production environment"
15714
+ }).optional()
15715
+ }).meta({ id: "CourseOverrides", description: "Per-environment course overrides" });
15683
15716
  var CourseConfig = CourseDefaults.extend({
15684
- subject: TimebackSubject,
15685
- grade: TimebackGrade.optional(),
15717
+ subject: TimebackSubject.meta({ description: "Subject area for this course" }),
15718
+ grade: TimebackGrade.meta({
15719
+ description: "Grade level (-1 = Pre-K, 0 = K, 1-12 = grades, 13 = AP)"
15720
+ }).optional(),
15686
15721
  ids: CourseIds.nullable().optional(),
15687
- sensor: exports_external.string().url().optional(),
15688
- launchUrl: exports_external.string().url().optional(),
15722
+ sensor: exports_external.url().meta({ description: "Caliper sensor endpoint URL for this course" }).optional(),
15723
+ launchUrl: exports_external.url().meta({ description: "LTI launch URL for this course" }).optional(),
15689
15724
  overrides: CourseOverrides.optional()
15725
+ }).meta({
15726
+ id: "CourseConfig",
15727
+ description: "Configuration for a single course. Must have either grade or courseCode (or both)."
15690
15728
  });
15691
15729
  var TimebackConfig = exports_external.object({
15692
- name: exports_external.string().min(1, "App name is required"),
15693
- defaults: CourseDefaults.optional(),
15694
- courses: exports_external.array(CourseConfig).min(1, "At least one course is required"),
15695
- sensor: exports_external.string().url().optional(),
15696
- launchUrl: exports_external.string().url().optional()
15730
+ $schema: exports_external.string().meta({ description: "JSON Schema reference for editor support" }).optional(),
15731
+ name: exports_external.string().min(1, "App name is required").meta({ description: "Display name for your app" }),
15732
+ defaults: CourseDefaults.meta({
15733
+ description: "Default properties applied to all courses"
15734
+ }).optional(),
15735
+ courses: exports_external.array(CourseConfig).min(1, "At least one course is required").meta({ description: "Courses available in this app" }),
15736
+ sensor: exports_external.url().meta({ description: "Default Caliper sensor endpoint URL for all courses" }).optional(),
15737
+ launchUrl: exports_external.url().meta({ description: "Default LTI launch URL for all courses" }).optional()
15738
+ }).meta({
15739
+ id: "TimebackConfig",
15740
+ title: "Timeback Config",
15741
+ description: "Configuration schema for timeback.config.json files"
15697
15742
  }).refine((config2) => {
15698
15743
  return config2.courses.every((c) => c.grade !== undefined || c.courseCode !== undefined);
15699
15744
  }, {
@@ -15714,14 +15759,20 @@ var TimebackConfig = exports_external.object({
15714
15759
  message: "Duplicate courseCode found; each must be unique",
15715
15760
  path: ["courses"]
15716
15761
  }).refine((config2) => {
15717
- return config2.courses.every((c) => c.sensor !== undefined || config2.sensor !== undefined);
15718
- }, {
15719
- message: "Each course must have an effective sensor; set a top-level `sensor` or per-course `sensor`",
15720
- path: ["courses"]
15721
- }).refine((config2) => {
15722
- return config2.courses.every((c) => c.launchUrl !== undefined || config2.launchUrl !== undefined);
15762
+ return config2.courses.every((c) => {
15763
+ if (c.sensor !== undefined || config2.sensor !== undefined) {
15764
+ return true;
15765
+ }
15766
+ const launchUrls = [
15767
+ c.launchUrl,
15768
+ config2.launchUrl,
15769
+ c.overrides?.staging?.launchUrl,
15770
+ c.overrides?.production?.launchUrl
15771
+ ].filter(Boolean);
15772
+ return launchUrls.length > 0;
15773
+ });
15723
15774
  }, {
15724
- message: "Each course must have an effective launchUrl; set a top-level `launchUrl` or per-course `launchUrl`",
15775
+ 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.",
15725
15776
  path: ["courses"]
15726
15777
  });
15727
15778
  var EdubridgeDateString = exports_external.union([IsoDateString, IsoDateTimeString]);
@@ -31769,7 +31820,7 @@ var TimebackSubject2 = exports_external2.enum([
31769
31820
  "Math",
31770
31821
  "None",
31771
31822
  "Other"
31772
- ]);
31823
+ ]).meta({ id: "TimebackSubject", description: "Subject area" });
31773
31824
  var TimebackGrade2 = exports_external2.union([
31774
31825
  exports_external2.literal(-1),
31775
31826
  exports_external2.literal(0),
@@ -31786,7 +31837,10 @@ var TimebackGrade2 = exports_external2.union([
31786
31837
  exports_external2.literal(11),
31787
31838
  exports_external2.literal(12),
31788
31839
  exports_external2.literal(13)
31789
- ]);
31840
+ ]).meta({
31841
+ id: "TimebackGrade",
31842
+ description: "Grade level (-1 = Pre-K, 0 = K, 1-12 = grades, 13 = AP)"
31843
+ });
31790
31844
  var ScoreStatus2 = exports_external2.enum([
31791
31845
  "exempt",
31792
31846
  "fully graded",
@@ -32016,62 +32070,84 @@ var CaliperListEventsParams2 = exports_external2.object({
32016
32070
  actorEmail: exports_external2.email().optional()
32017
32071
  }).strict();
32018
32072
  var CourseIds2 = exports_external2.object({
32019
- staging: exports_external2.string().optional(),
32020
- production: exports_external2.string().optional()
32021
- });
32022
- var CourseType2 = exports_external2.enum(["base", "hole-filling", "optional"]);
32023
- var PublishStatus2 = exports_external2.enum(["draft", "testing", "published", "deactivated"]);
32073
+ staging: exports_external2.string().meta({ description: "Course ID in staging environment" }).optional(),
32074
+ production: exports_external2.string().meta({ description: "Course ID in production environment" }).optional()
32075
+ }).meta({ id: "CourseIds", description: "Environment-specific course IDs (populated by sync)" });
32076
+ var CourseType2 = exports_external2.enum(["base", "hole-filling", "optional"]).meta({ id: "CourseType", description: "Course classification type" });
32077
+ var PublishStatus2 = exports_external2.enum(["draft", "testing", "published", "deactivated"]).meta({ id: "PublishStatus", description: "Course publication status" });
32024
32078
  var CourseGoals2 = exports_external2.object({
32025
- dailyXp: exports_external2.number().int().positive().optional(),
32026
- dailyLessons: exports_external2.number().int().positive().optional(),
32027
- dailyActiveMinutes: exports_external2.number().int().positive().optional(),
32028
- dailyAccuracy: exports_external2.number().int().min(0).max(100).optional(),
32029
- dailyMasteredUnits: exports_external2.number().int().positive().optional()
32030
- });
32079
+ dailyXp: exports_external2.number().int().positive().meta({ description: "Target XP to earn per day" }).optional(),
32080
+ dailyLessons: exports_external2.number().int().positive().meta({ description: "Target lessons to complete per day" }).optional(),
32081
+ dailyActiveMinutes: exports_external2.number().int().positive().meta({ description: "Target active learning minutes per day" }).optional(),
32082
+ dailyAccuracy: exports_external2.number().int().min(0).max(100).meta({ description: "Target accuracy percentage (0-100)" }).optional(),
32083
+ dailyMasteredUnits: exports_external2.number().int().positive().meta({ description: "Target units to master per day" }).optional()
32084
+ }).meta({ id: "CourseGoals", description: "Daily learning goals for a course" });
32031
32085
  var CourseMetrics2 = exports_external2.object({
32032
- totalXp: exports_external2.number().int().positive().optional(),
32033
- totalLessons: exports_external2.number().int().positive().optional(),
32034
- totalGrades: exports_external2.number().int().positive().optional()
32035
- });
32086
+ totalXp: exports_external2.number().int().positive().meta({ description: "Total XP available in the course" }).optional(),
32087
+ totalLessons: exports_external2.number().int().positive().meta({ description: "Total number of lessons/activities" }).optional(),
32088
+ totalGrades: exports_external2.number().int().positive().meta({ description: "Total grade levels covered" }).optional()
32089
+ }).meta({ id: "CourseMetrics", description: "Aggregate metrics for a course" });
32036
32090
  var CourseMetadata2 = exports_external2.object({
32037
32091
  courseType: CourseType2.optional(),
32038
- isSupplemental: exports_external2.boolean().optional(),
32039
- isCustom: exports_external2.boolean().optional(),
32092
+ isSupplemental: exports_external2.boolean().meta({ description: "Whether this is supplemental to a base course" }).optional(),
32093
+ isCustom: exports_external2.boolean().meta({ description: "Whether this is a custom course for an individual student" }).optional(),
32040
32094
  publishStatus: PublishStatus2.optional(),
32041
- contactEmail: exports_external2.email().optional(),
32042
- primaryApp: exports_external2.string().optional(),
32095
+ contactEmail: exports_external2.email().meta({ description: "Contact email for course issues" }).optional(),
32096
+ primaryApp: exports_external2.string().meta({ description: "Primary application identifier" }).optional(),
32043
32097
  goals: CourseGoals2.optional(),
32044
32098
  metrics: CourseMetrics2.optional()
32045
- });
32099
+ }).meta({ id: "CourseMetadata", description: "Course metadata (matches API metadata object)" });
32046
32100
  var CourseDefaults2 = exports_external2.object({
32047
- courseCode: exports_external2.string().optional(),
32048
- level: exports_external2.string().optional(),
32101
+ courseCode: exports_external2.string().meta({ description: "Course code (e.g., 'MATH101')" }).optional(),
32102
+ level: exports_external2.string().meta({ description: "Course level (e.g., 'AP', 'Honors')" }).optional(),
32049
32103
  metadata: CourseMetadata2.optional()
32104
+ }).meta({
32105
+ id: "CourseDefaults",
32106
+ description: "Default properties that apply to all courses unless overridden"
32050
32107
  });
32051
32108
  var CourseEnvOverrides2 = exports_external2.object({
32052
- level: exports_external2.string().optional(),
32053
- sensor: exports_external2.string().url().optional(),
32054
- launchUrl: exports_external2.string().url().optional(),
32109
+ level: exports_external2.string().meta({ description: "Course level for this environment" }).optional(),
32110
+ sensor: exports_external2.url().meta({ description: "Caliper sensor endpoint URL for this environment" }).optional(),
32111
+ launchUrl: exports_external2.url().meta({ description: "LTI launch URL for this environment" }).optional(),
32055
32112
  metadata: CourseMetadata2.optional()
32113
+ }).meta({
32114
+ id: "CourseEnvOverrides",
32115
+ description: "Environment-specific course overrides (non-identity fields)"
32056
32116
  });
32057
32117
  var CourseOverrides2 = exports_external2.object({
32058
- staging: CourseEnvOverrides2.optional(),
32059
- production: CourseEnvOverrides2.optional()
32060
- });
32118
+ staging: CourseEnvOverrides2.meta({
32119
+ description: "Overrides for staging environment"
32120
+ }).optional(),
32121
+ production: CourseEnvOverrides2.meta({
32122
+ description: "Overrides for production environment"
32123
+ }).optional()
32124
+ }).meta({ id: "CourseOverrides", description: "Per-environment course overrides" });
32061
32125
  var CourseConfig2 = CourseDefaults2.extend({
32062
- subject: TimebackSubject2,
32063
- grade: TimebackGrade2.optional(),
32126
+ subject: TimebackSubject2.meta({ description: "Subject area for this course" }),
32127
+ grade: TimebackGrade2.meta({
32128
+ description: "Grade level (-1 = Pre-K, 0 = K, 1-12 = grades, 13 = AP)"
32129
+ }).optional(),
32064
32130
  ids: CourseIds2.nullable().optional(),
32065
- sensor: exports_external2.string().url().optional(),
32066
- launchUrl: exports_external2.string().url().optional(),
32131
+ sensor: exports_external2.url().meta({ description: "Caliper sensor endpoint URL for this course" }).optional(),
32132
+ launchUrl: exports_external2.url().meta({ description: "LTI launch URL for this course" }).optional(),
32067
32133
  overrides: CourseOverrides2.optional()
32134
+ }).meta({
32135
+ id: "CourseConfig",
32136
+ description: "Configuration for a single course. Must have either grade or courseCode (or both)."
32068
32137
  });
32069
32138
  var TimebackConfig2 = exports_external2.object({
32070
- name: exports_external2.string().min(1, "App name is required"),
32071
- defaults: CourseDefaults2.optional(),
32072
- courses: exports_external2.array(CourseConfig2).min(1, "At least one course is required"),
32073
- sensor: exports_external2.string().url().optional(),
32074
- launchUrl: exports_external2.string().url().optional()
32139
+ $schema: exports_external2.string().meta({ description: "JSON Schema reference for editor support" }).optional(),
32140
+ name: exports_external2.string().min(1, "App name is required").meta({ description: "Display name for your app" }),
32141
+ defaults: CourseDefaults2.meta({
32142
+ description: "Default properties applied to all courses"
32143
+ }).optional(),
32144
+ courses: exports_external2.array(CourseConfig2).min(1, "At least one course is required").meta({ description: "Courses available in this app" }),
32145
+ sensor: exports_external2.url().meta({ description: "Default Caliper sensor endpoint URL for all courses" }).optional(),
32146
+ launchUrl: exports_external2.url().meta({ description: "Default LTI launch URL for all courses" }).optional()
32147
+ }).meta({
32148
+ id: "TimebackConfig",
32149
+ title: "Timeback Config",
32150
+ description: "Configuration schema for timeback.config.json files"
32075
32151
  }).refine((config22) => {
32076
32152
  return config22.courses.every((c) => c.grade !== undefined || c.courseCode !== undefined);
32077
32153
  }, {
@@ -32092,14 +32168,20 @@ var TimebackConfig2 = exports_external2.object({
32092
32168
  message: "Duplicate courseCode found; each must be unique",
32093
32169
  path: ["courses"]
32094
32170
  }).refine((config22) => {
32095
- return config22.courses.every((c) => c.sensor !== undefined || config22.sensor !== undefined);
32096
- }, {
32097
- message: "Each course must have an effective sensor; set a top-level `sensor` or per-course `sensor`",
32098
- path: ["courses"]
32099
- }).refine((config22) => {
32100
- return config22.courses.every((c) => c.launchUrl !== undefined || config22.launchUrl !== undefined);
32171
+ return config22.courses.every((c) => {
32172
+ if (c.sensor !== undefined || config22.sensor !== undefined) {
32173
+ return true;
32174
+ }
32175
+ const launchUrls = [
32176
+ c.launchUrl,
32177
+ config22.launchUrl,
32178
+ c.overrides?.staging?.launchUrl,
32179
+ c.overrides?.production?.launchUrl
32180
+ ].filter(Boolean);
32181
+ return launchUrls.length > 0;
32182
+ });
32101
32183
  }, {
32102
- message: "Each course must have an effective launchUrl; set a top-level `launchUrl` or per-course `launchUrl`",
32184
+ 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.",
32103
32185
  path: ["courses"]
32104
32186
  });
32105
32187
  var EdubridgeDateString2 = exports_external2.union([IsoDateString2, IsoDateTimeString2]);
@@ -49177,7 +49259,7 @@ var TimebackSubject3 = exports_external3.enum([
49177
49259
  "Math",
49178
49260
  "None",
49179
49261
  "Other"
49180
- ]);
49262
+ ]).meta({ id: "TimebackSubject", description: "Subject area" });
49181
49263
  var TimebackGrade3 = exports_external3.union([
49182
49264
  exports_external3.literal(-1),
49183
49265
  exports_external3.literal(0),
@@ -49194,7 +49276,10 @@ var TimebackGrade3 = exports_external3.union([
49194
49276
  exports_external3.literal(11),
49195
49277
  exports_external3.literal(12),
49196
49278
  exports_external3.literal(13)
49197
- ]);
49279
+ ]).meta({
49280
+ id: "TimebackGrade",
49281
+ description: "Grade level (-1 = Pre-K, 0 = K, 1-12 = grades, 13 = AP)"
49282
+ });
49198
49283
  var ScoreStatus3 = exports_external3.enum([
49199
49284
  "exempt",
49200
49285
  "fully graded",
@@ -49424,62 +49509,84 @@ var CaliperListEventsParams3 = exports_external3.object({
49424
49509
  actorEmail: exports_external3.email().optional()
49425
49510
  }).strict();
49426
49511
  var CourseIds3 = exports_external3.object({
49427
- staging: exports_external3.string().optional(),
49428
- production: exports_external3.string().optional()
49429
- });
49430
- var CourseType3 = exports_external3.enum(["base", "hole-filling", "optional"]);
49431
- var PublishStatus3 = exports_external3.enum(["draft", "testing", "published", "deactivated"]);
49512
+ staging: exports_external3.string().meta({ description: "Course ID in staging environment" }).optional(),
49513
+ production: exports_external3.string().meta({ description: "Course ID in production environment" }).optional()
49514
+ }).meta({ id: "CourseIds", description: "Environment-specific course IDs (populated by sync)" });
49515
+ var CourseType3 = exports_external3.enum(["base", "hole-filling", "optional"]).meta({ id: "CourseType", description: "Course classification type" });
49516
+ var PublishStatus3 = exports_external3.enum(["draft", "testing", "published", "deactivated"]).meta({ id: "PublishStatus", description: "Course publication status" });
49432
49517
  var CourseGoals3 = exports_external3.object({
49433
- dailyXp: exports_external3.number().int().positive().optional(),
49434
- dailyLessons: exports_external3.number().int().positive().optional(),
49435
- dailyActiveMinutes: exports_external3.number().int().positive().optional(),
49436
- dailyAccuracy: exports_external3.number().int().min(0).max(100).optional(),
49437
- dailyMasteredUnits: exports_external3.number().int().positive().optional()
49438
- });
49518
+ dailyXp: exports_external3.number().int().positive().meta({ description: "Target XP to earn per day" }).optional(),
49519
+ dailyLessons: exports_external3.number().int().positive().meta({ description: "Target lessons to complete per day" }).optional(),
49520
+ dailyActiveMinutes: exports_external3.number().int().positive().meta({ description: "Target active learning minutes per day" }).optional(),
49521
+ dailyAccuracy: exports_external3.number().int().min(0).max(100).meta({ description: "Target accuracy percentage (0-100)" }).optional(),
49522
+ dailyMasteredUnits: exports_external3.number().int().positive().meta({ description: "Target units to master per day" }).optional()
49523
+ }).meta({ id: "CourseGoals", description: "Daily learning goals for a course" });
49439
49524
  var CourseMetrics3 = exports_external3.object({
49440
- totalXp: exports_external3.number().int().positive().optional(),
49441
- totalLessons: exports_external3.number().int().positive().optional(),
49442
- totalGrades: exports_external3.number().int().positive().optional()
49443
- });
49525
+ totalXp: exports_external3.number().int().positive().meta({ description: "Total XP available in the course" }).optional(),
49526
+ totalLessons: exports_external3.number().int().positive().meta({ description: "Total number of lessons/activities" }).optional(),
49527
+ totalGrades: exports_external3.number().int().positive().meta({ description: "Total grade levels covered" }).optional()
49528
+ }).meta({ id: "CourseMetrics", description: "Aggregate metrics for a course" });
49444
49529
  var CourseMetadata3 = exports_external3.object({
49445
49530
  courseType: CourseType3.optional(),
49446
- isSupplemental: exports_external3.boolean().optional(),
49447
- isCustom: exports_external3.boolean().optional(),
49531
+ isSupplemental: exports_external3.boolean().meta({ description: "Whether this is supplemental to a base course" }).optional(),
49532
+ isCustom: exports_external3.boolean().meta({ description: "Whether this is a custom course for an individual student" }).optional(),
49448
49533
  publishStatus: PublishStatus3.optional(),
49449
- contactEmail: exports_external3.email().optional(),
49450
- primaryApp: exports_external3.string().optional(),
49534
+ contactEmail: exports_external3.email().meta({ description: "Contact email for course issues" }).optional(),
49535
+ primaryApp: exports_external3.string().meta({ description: "Primary application identifier" }).optional(),
49451
49536
  goals: CourseGoals3.optional(),
49452
49537
  metrics: CourseMetrics3.optional()
49453
- });
49538
+ }).meta({ id: "CourseMetadata", description: "Course metadata (matches API metadata object)" });
49454
49539
  var CourseDefaults3 = exports_external3.object({
49455
- courseCode: exports_external3.string().optional(),
49456
- level: exports_external3.string().optional(),
49540
+ courseCode: exports_external3.string().meta({ description: "Course code (e.g., 'MATH101')" }).optional(),
49541
+ level: exports_external3.string().meta({ description: "Course level (e.g., 'AP', 'Honors')" }).optional(),
49457
49542
  metadata: CourseMetadata3.optional()
49543
+ }).meta({
49544
+ id: "CourseDefaults",
49545
+ description: "Default properties that apply to all courses unless overridden"
49458
49546
  });
49459
49547
  var CourseEnvOverrides3 = exports_external3.object({
49460
- level: exports_external3.string().optional(),
49461
- sensor: exports_external3.string().url().optional(),
49462
- launchUrl: exports_external3.string().url().optional(),
49548
+ level: exports_external3.string().meta({ description: "Course level for this environment" }).optional(),
49549
+ sensor: exports_external3.url().meta({ description: "Caliper sensor endpoint URL for this environment" }).optional(),
49550
+ launchUrl: exports_external3.url().meta({ description: "LTI launch URL for this environment" }).optional(),
49463
49551
  metadata: CourseMetadata3.optional()
49552
+ }).meta({
49553
+ id: "CourseEnvOverrides",
49554
+ description: "Environment-specific course overrides (non-identity fields)"
49464
49555
  });
49465
49556
  var CourseOverrides3 = exports_external3.object({
49466
- staging: CourseEnvOverrides3.optional(),
49467
- production: CourseEnvOverrides3.optional()
49468
- });
49557
+ staging: CourseEnvOverrides3.meta({
49558
+ description: "Overrides for staging environment"
49559
+ }).optional(),
49560
+ production: CourseEnvOverrides3.meta({
49561
+ description: "Overrides for production environment"
49562
+ }).optional()
49563
+ }).meta({ id: "CourseOverrides", description: "Per-environment course overrides" });
49469
49564
  var CourseConfig3 = CourseDefaults3.extend({
49470
- subject: TimebackSubject3,
49471
- grade: TimebackGrade3.optional(),
49565
+ subject: TimebackSubject3.meta({ description: "Subject area for this course" }),
49566
+ grade: TimebackGrade3.meta({
49567
+ description: "Grade level (-1 = Pre-K, 0 = K, 1-12 = grades, 13 = AP)"
49568
+ }).optional(),
49472
49569
  ids: CourseIds3.nullable().optional(),
49473
- sensor: exports_external3.string().url().optional(),
49474
- launchUrl: exports_external3.string().url().optional(),
49570
+ sensor: exports_external3.url().meta({ description: "Caliper sensor endpoint URL for this course" }).optional(),
49571
+ launchUrl: exports_external3.url().meta({ description: "LTI launch URL for this course" }).optional(),
49475
49572
  overrides: CourseOverrides3.optional()
49573
+ }).meta({
49574
+ id: "CourseConfig",
49575
+ description: "Configuration for a single course. Must have either grade or courseCode (or both)."
49476
49576
  });
49477
49577
  var TimebackConfig3 = exports_external3.object({
49478
- name: exports_external3.string().min(1, "App name is required"),
49479
- defaults: CourseDefaults3.optional(),
49480
- courses: exports_external3.array(CourseConfig3).min(1, "At least one course is required"),
49481
- sensor: exports_external3.string().url().optional(),
49482
- launchUrl: exports_external3.string().url().optional()
49578
+ $schema: exports_external3.string().meta({ description: "JSON Schema reference for editor support" }).optional(),
49579
+ name: exports_external3.string().min(1, "App name is required").meta({ description: "Display name for your app" }),
49580
+ defaults: CourseDefaults3.meta({
49581
+ description: "Default properties applied to all courses"
49582
+ }).optional(),
49583
+ courses: exports_external3.array(CourseConfig3).min(1, "At least one course is required").meta({ description: "Courses available in this app" }),
49584
+ sensor: exports_external3.url().meta({ description: "Default Caliper sensor endpoint URL for all courses" }).optional(),
49585
+ launchUrl: exports_external3.url().meta({ description: "Default LTI launch URL for all courses" }).optional()
49586
+ }).meta({
49587
+ id: "TimebackConfig",
49588
+ title: "Timeback Config",
49589
+ description: "Configuration schema for timeback.config.json files"
49483
49590
  }).refine((config22) => {
49484
49591
  return config22.courses.every((c) => c.grade !== undefined || c.courseCode !== undefined);
49485
49592
  }, {
@@ -49500,14 +49607,20 @@ var TimebackConfig3 = exports_external3.object({
49500
49607
  message: "Duplicate courseCode found; each must be unique",
49501
49608
  path: ["courses"]
49502
49609
  }).refine((config22) => {
49503
- return config22.courses.every((c) => c.sensor !== undefined || config22.sensor !== undefined);
49504
- }, {
49505
- message: "Each course must have an effective sensor; set a top-level `sensor` or per-course `sensor`",
49506
- path: ["courses"]
49507
- }).refine((config22) => {
49508
- return config22.courses.every((c) => c.launchUrl !== undefined || config22.launchUrl !== undefined);
49610
+ return config22.courses.every((c) => {
49611
+ if (c.sensor !== undefined || config22.sensor !== undefined) {
49612
+ return true;
49613
+ }
49614
+ const launchUrls = [
49615
+ c.launchUrl,
49616
+ config22.launchUrl,
49617
+ c.overrides?.staging?.launchUrl,
49618
+ c.overrides?.production?.launchUrl
49619
+ ].filter(Boolean);
49620
+ return launchUrls.length > 0;
49621
+ });
49509
49622
  }, {
49510
- message: "Each course must have an effective launchUrl; set a top-level `launchUrl` or per-course `launchUrl`",
49623
+ 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.",
49511
49624
  path: ["courses"]
49512
49625
  });
49513
49626
  var EdubridgeDateString3 = exports_external3.union([IsoDateString3, IsoDateTimeString3]);
@@ -66784,7 +66897,7 @@ var TimebackSubject4 = exports_external4.enum([
66784
66897
  "Math",
66785
66898
  "None",
66786
66899
  "Other"
66787
- ]);
66900
+ ]).meta({ id: "TimebackSubject", description: "Subject area" });
66788
66901
  var TimebackGrade4 = exports_external4.union([
66789
66902
  exports_external4.literal(-1),
66790
66903
  exports_external4.literal(0),
@@ -66801,7 +66914,10 @@ var TimebackGrade4 = exports_external4.union([
66801
66914
  exports_external4.literal(11),
66802
66915
  exports_external4.literal(12),
66803
66916
  exports_external4.literal(13)
66804
- ]);
66917
+ ]).meta({
66918
+ id: "TimebackGrade",
66919
+ description: "Grade level (-1 = Pre-K, 0 = K, 1-12 = grades, 13 = AP)"
66920
+ });
66805
66921
  var ScoreStatus4 = exports_external4.enum([
66806
66922
  "exempt",
66807
66923
  "fully graded",
@@ -67031,62 +67147,84 @@ var CaliperListEventsParams4 = exports_external4.object({
67031
67147
  actorEmail: exports_external4.email().optional()
67032
67148
  }).strict();
67033
67149
  var CourseIds4 = exports_external4.object({
67034
- staging: exports_external4.string().optional(),
67035
- production: exports_external4.string().optional()
67036
- });
67037
- var CourseType4 = exports_external4.enum(["base", "hole-filling", "optional"]);
67038
- var PublishStatus4 = exports_external4.enum(["draft", "testing", "published", "deactivated"]);
67150
+ staging: exports_external4.string().meta({ description: "Course ID in staging environment" }).optional(),
67151
+ production: exports_external4.string().meta({ description: "Course ID in production environment" }).optional()
67152
+ }).meta({ id: "CourseIds", description: "Environment-specific course IDs (populated by sync)" });
67153
+ var CourseType4 = exports_external4.enum(["base", "hole-filling", "optional"]).meta({ id: "CourseType", description: "Course classification type" });
67154
+ var PublishStatus4 = exports_external4.enum(["draft", "testing", "published", "deactivated"]).meta({ id: "PublishStatus", description: "Course publication status" });
67039
67155
  var CourseGoals4 = exports_external4.object({
67040
- dailyXp: exports_external4.number().int().positive().optional(),
67041
- dailyLessons: exports_external4.number().int().positive().optional(),
67042
- dailyActiveMinutes: exports_external4.number().int().positive().optional(),
67043
- dailyAccuracy: exports_external4.number().int().min(0).max(100).optional(),
67044
- dailyMasteredUnits: exports_external4.number().int().positive().optional()
67045
- });
67156
+ dailyXp: exports_external4.number().int().positive().meta({ description: "Target XP to earn per day" }).optional(),
67157
+ dailyLessons: exports_external4.number().int().positive().meta({ description: "Target lessons to complete per day" }).optional(),
67158
+ dailyActiveMinutes: exports_external4.number().int().positive().meta({ description: "Target active learning minutes per day" }).optional(),
67159
+ dailyAccuracy: exports_external4.number().int().min(0).max(100).meta({ description: "Target accuracy percentage (0-100)" }).optional(),
67160
+ dailyMasteredUnits: exports_external4.number().int().positive().meta({ description: "Target units to master per day" }).optional()
67161
+ }).meta({ id: "CourseGoals", description: "Daily learning goals for a course" });
67046
67162
  var CourseMetrics4 = exports_external4.object({
67047
- totalXp: exports_external4.number().int().positive().optional(),
67048
- totalLessons: exports_external4.number().int().positive().optional(),
67049
- totalGrades: exports_external4.number().int().positive().optional()
67050
- });
67163
+ totalXp: exports_external4.number().int().positive().meta({ description: "Total XP available in the course" }).optional(),
67164
+ totalLessons: exports_external4.number().int().positive().meta({ description: "Total number of lessons/activities" }).optional(),
67165
+ totalGrades: exports_external4.number().int().positive().meta({ description: "Total grade levels covered" }).optional()
67166
+ }).meta({ id: "CourseMetrics", description: "Aggregate metrics for a course" });
67051
67167
  var CourseMetadata4 = exports_external4.object({
67052
67168
  courseType: CourseType4.optional(),
67053
- isSupplemental: exports_external4.boolean().optional(),
67054
- isCustom: exports_external4.boolean().optional(),
67169
+ isSupplemental: exports_external4.boolean().meta({ description: "Whether this is supplemental to a base course" }).optional(),
67170
+ isCustom: exports_external4.boolean().meta({ description: "Whether this is a custom course for an individual student" }).optional(),
67055
67171
  publishStatus: PublishStatus4.optional(),
67056
- contactEmail: exports_external4.email().optional(),
67057
- primaryApp: exports_external4.string().optional(),
67172
+ contactEmail: exports_external4.email().meta({ description: "Contact email for course issues" }).optional(),
67173
+ primaryApp: exports_external4.string().meta({ description: "Primary application identifier" }).optional(),
67058
67174
  goals: CourseGoals4.optional(),
67059
67175
  metrics: CourseMetrics4.optional()
67060
- });
67176
+ }).meta({ id: "CourseMetadata", description: "Course metadata (matches API metadata object)" });
67061
67177
  var CourseDefaults4 = exports_external4.object({
67062
- courseCode: exports_external4.string().optional(),
67063
- level: exports_external4.string().optional(),
67178
+ courseCode: exports_external4.string().meta({ description: "Course code (e.g., 'MATH101')" }).optional(),
67179
+ level: exports_external4.string().meta({ description: "Course level (e.g., 'AP', 'Honors')" }).optional(),
67064
67180
  metadata: CourseMetadata4.optional()
67181
+ }).meta({
67182
+ id: "CourseDefaults",
67183
+ description: "Default properties that apply to all courses unless overridden"
67065
67184
  });
67066
67185
  var CourseEnvOverrides4 = exports_external4.object({
67067
- level: exports_external4.string().optional(),
67068
- sensor: exports_external4.string().url().optional(),
67069
- launchUrl: exports_external4.string().url().optional(),
67186
+ level: exports_external4.string().meta({ description: "Course level for this environment" }).optional(),
67187
+ sensor: exports_external4.url().meta({ description: "Caliper sensor endpoint URL for this environment" }).optional(),
67188
+ launchUrl: exports_external4.url().meta({ description: "LTI launch URL for this environment" }).optional(),
67070
67189
  metadata: CourseMetadata4.optional()
67190
+ }).meta({
67191
+ id: "CourseEnvOverrides",
67192
+ description: "Environment-specific course overrides (non-identity fields)"
67071
67193
  });
67072
67194
  var CourseOverrides4 = exports_external4.object({
67073
- staging: CourseEnvOverrides4.optional(),
67074
- production: CourseEnvOverrides4.optional()
67075
- });
67195
+ staging: CourseEnvOverrides4.meta({
67196
+ description: "Overrides for staging environment"
67197
+ }).optional(),
67198
+ production: CourseEnvOverrides4.meta({
67199
+ description: "Overrides for production environment"
67200
+ }).optional()
67201
+ }).meta({ id: "CourseOverrides", description: "Per-environment course overrides" });
67076
67202
  var CourseConfig4 = CourseDefaults4.extend({
67077
- subject: TimebackSubject4,
67078
- grade: TimebackGrade4.optional(),
67203
+ subject: TimebackSubject4.meta({ description: "Subject area for this course" }),
67204
+ grade: TimebackGrade4.meta({
67205
+ description: "Grade level (-1 = Pre-K, 0 = K, 1-12 = grades, 13 = AP)"
67206
+ }).optional(),
67079
67207
  ids: CourseIds4.nullable().optional(),
67080
- sensor: exports_external4.string().url().optional(),
67081
- launchUrl: exports_external4.string().url().optional(),
67208
+ sensor: exports_external4.url().meta({ description: "Caliper sensor endpoint URL for this course" }).optional(),
67209
+ launchUrl: exports_external4.url().meta({ description: "LTI launch URL for this course" }).optional(),
67082
67210
  overrides: CourseOverrides4.optional()
67211
+ }).meta({
67212
+ id: "CourseConfig",
67213
+ description: "Configuration for a single course. Must have either grade or courseCode (or both)."
67083
67214
  });
67084
67215
  var TimebackConfig4 = exports_external4.object({
67085
- name: exports_external4.string().min(1, "App name is required"),
67086
- defaults: CourseDefaults4.optional(),
67087
- courses: exports_external4.array(CourseConfig4).min(1, "At least one course is required"),
67088
- sensor: exports_external4.string().url().optional(),
67089
- launchUrl: exports_external4.string().url().optional()
67216
+ $schema: exports_external4.string().meta({ description: "JSON Schema reference for editor support" }).optional(),
67217
+ name: exports_external4.string().min(1, "App name is required").meta({ description: "Display name for your app" }),
67218
+ defaults: CourseDefaults4.meta({
67219
+ description: "Default properties applied to all courses"
67220
+ }).optional(),
67221
+ courses: exports_external4.array(CourseConfig4).min(1, "At least one course is required").meta({ description: "Courses available in this app" }),
67222
+ sensor: exports_external4.url().meta({ description: "Default Caliper sensor endpoint URL for all courses" }).optional(),
67223
+ launchUrl: exports_external4.url().meta({ description: "Default LTI launch URL for all courses" }).optional()
67224
+ }).meta({
67225
+ id: "TimebackConfig",
67226
+ title: "Timeback Config",
67227
+ description: "Configuration schema for timeback.config.json files"
67090
67228
  }).refine((config22) => {
67091
67229
  return config22.courses.every((c) => c.grade !== undefined || c.courseCode !== undefined);
67092
67230
  }, {
@@ -67107,14 +67245,20 @@ var TimebackConfig4 = exports_external4.object({
67107
67245
  message: "Duplicate courseCode found; each must be unique",
67108
67246
  path: ["courses"]
67109
67247
  }).refine((config22) => {
67110
- return config22.courses.every((c) => c.sensor !== undefined || config22.sensor !== undefined);
67111
- }, {
67112
- message: "Each course must have an effective sensor; set a top-level `sensor` or per-course `sensor`",
67113
- path: ["courses"]
67114
- }).refine((config22) => {
67115
- return config22.courses.every((c) => c.launchUrl !== undefined || config22.launchUrl !== undefined);
67248
+ return config22.courses.every((c) => {
67249
+ if (c.sensor !== undefined || config22.sensor !== undefined) {
67250
+ return true;
67251
+ }
67252
+ const launchUrls = [
67253
+ c.launchUrl,
67254
+ config22.launchUrl,
67255
+ c.overrides?.staging?.launchUrl,
67256
+ c.overrides?.production?.launchUrl
67257
+ ].filter(Boolean);
67258
+ return launchUrls.length > 0;
67259
+ });
67116
67260
  }, {
67117
- message: "Each course must have an effective launchUrl; set a top-level `launchUrl` or per-course `launchUrl`",
67261
+ 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.",
67118
67262
  path: ["courses"]
67119
67263
  });
67120
67264
  var EdubridgeDateString4 = exports_external4.union([IsoDateString4, IsoDateTimeString4]);
@@ -83421,7 +83565,7 @@ var TimebackSubject5 = exports_external5.enum([
83421
83565
  "Math",
83422
83566
  "None",
83423
83567
  "Other"
83424
- ]);
83568
+ ]).meta({ id: "TimebackSubject", description: "Subject area" });
83425
83569
  var TimebackGrade5 = exports_external5.union([
83426
83570
  exports_external5.literal(-1),
83427
83571
  exports_external5.literal(0),
@@ -83438,7 +83582,10 @@ var TimebackGrade5 = exports_external5.union([
83438
83582
  exports_external5.literal(11),
83439
83583
  exports_external5.literal(12),
83440
83584
  exports_external5.literal(13)
83441
- ]);
83585
+ ]).meta({
83586
+ id: "TimebackGrade",
83587
+ description: "Grade level (-1 = Pre-K, 0 = K, 1-12 = grades, 13 = AP)"
83588
+ });
83442
83589
  var ScoreStatus5 = exports_external5.enum([
83443
83590
  "exempt",
83444
83591
  "fully graded",
@@ -83668,62 +83815,84 @@ var CaliperListEventsParams5 = exports_external5.object({
83668
83815
  actorEmail: exports_external5.email().optional()
83669
83816
  }).strict();
83670
83817
  var CourseIds5 = exports_external5.object({
83671
- staging: exports_external5.string().optional(),
83672
- production: exports_external5.string().optional()
83673
- });
83674
- var CourseType5 = exports_external5.enum(["base", "hole-filling", "optional"]);
83675
- var PublishStatus5 = exports_external5.enum(["draft", "testing", "published", "deactivated"]);
83818
+ staging: exports_external5.string().meta({ description: "Course ID in staging environment" }).optional(),
83819
+ production: exports_external5.string().meta({ description: "Course ID in production environment" }).optional()
83820
+ }).meta({ id: "CourseIds", description: "Environment-specific course IDs (populated by sync)" });
83821
+ var CourseType5 = exports_external5.enum(["base", "hole-filling", "optional"]).meta({ id: "CourseType", description: "Course classification type" });
83822
+ var PublishStatus5 = exports_external5.enum(["draft", "testing", "published", "deactivated"]).meta({ id: "PublishStatus", description: "Course publication status" });
83676
83823
  var CourseGoals5 = exports_external5.object({
83677
- dailyXp: exports_external5.number().int().positive().optional(),
83678
- dailyLessons: exports_external5.number().int().positive().optional(),
83679
- dailyActiveMinutes: exports_external5.number().int().positive().optional(),
83680
- dailyAccuracy: exports_external5.number().int().min(0).max(100).optional(),
83681
- dailyMasteredUnits: exports_external5.number().int().positive().optional()
83682
- });
83824
+ dailyXp: exports_external5.number().int().positive().meta({ description: "Target XP to earn per day" }).optional(),
83825
+ dailyLessons: exports_external5.number().int().positive().meta({ description: "Target lessons to complete per day" }).optional(),
83826
+ dailyActiveMinutes: exports_external5.number().int().positive().meta({ description: "Target active learning minutes per day" }).optional(),
83827
+ dailyAccuracy: exports_external5.number().int().min(0).max(100).meta({ description: "Target accuracy percentage (0-100)" }).optional(),
83828
+ dailyMasteredUnits: exports_external5.number().int().positive().meta({ description: "Target units to master per day" }).optional()
83829
+ }).meta({ id: "CourseGoals", description: "Daily learning goals for a course" });
83683
83830
  var CourseMetrics5 = exports_external5.object({
83684
- totalXp: exports_external5.number().int().positive().optional(),
83685
- totalLessons: exports_external5.number().int().positive().optional(),
83686
- totalGrades: exports_external5.number().int().positive().optional()
83687
- });
83831
+ totalXp: exports_external5.number().int().positive().meta({ description: "Total XP available in the course" }).optional(),
83832
+ totalLessons: exports_external5.number().int().positive().meta({ description: "Total number of lessons/activities" }).optional(),
83833
+ totalGrades: exports_external5.number().int().positive().meta({ description: "Total grade levels covered" }).optional()
83834
+ }).meta({ id: "CourseMetrics", description: "Aggregate metrics for a course" });
83688
83835
  var CourseMetadata5 = exports_external5.object({
83689
83836
  courseType: CourseType5.optional(),
83690
- isSupplemental: exports_external5.boolean().optional(),
83691
- isCustom: exports_external5.boolean().optional(),
83837
+ isSupplemental: exports_external5.boolean().meta({ description: "Whether this is supplemental to a base course" }).optional(),
83838
+ isCustom: exports_external5.boolean().meta({ description: "Whether this is a custom course for an individual student" }).optional(),
83692
83839
  publishStatus: PublishStatus5.optional(),
83693
- contactEmail: exports_external5.email().optional(),
83694
- primaryApp: exports_external5.string().optional(),
83840
+ contactEmail: exports_external5.email().meta({ description: "Contact email for course issues" }).optional(),
83841
+ primaryApp: exports_external5.string().meta({ description: "Primary application identifier" }).optional(),
83695
83842
  goals: CourseGoals5.optional(),
83696
83843
  metrics: CourseMetrics5.optional()
83697
- });
83844
+ }).meta({ id: "CourseMetadata", description: "Course metadata (matches API metadata object)" });
83698
83845
  var CourseDefaults5 = exports_external5.object({
83699
- courseCode: exports_external5.string().optional(),
83700
- level: exports_external5.string().optional(),
83846
+ courseCode: exports_external5.string().meta({ description: "Course code (e.g., 'MATH101')" }).optional(),
83847
+ level: exports_external5.string().meta({ description: "Course level (e.g., 'AP', 'Honors')" }).optional(),
83701
83848
  metadata: CourseMetadata5.optional()
83849
+ }).meta({
83850
+ id: "CourseDefaults",
83851
+ description: "Default properties that apply to all courses unless overridden"
83702
83852
  });
83703
83853
  var CourseEnvOverrides5 = exports_external5.object({
83704
- level: exports_external5.string().optional(),
83705
- sensor: exports_external5.string().url().optional(),
83706
- launchUrl: exports_external5.string().url().optional(),
83854
+ level: exports_external5.string().meta({ description: "Course level for this environment" }).optional(),
83855
+ sensor: exports_external5.url().meta({ description: "Caliper sensor endpoint URL for this environment" }).optional(),
83856
+ launchUrl: exports_external5.url().meta({ description: "LTI launch URL for this environment" }).optional(),
83707
83857
  metadata: CourseMetadata5.optional()
83858
+ }).meta({
83859
+ id: "CourseEnvOverrides",
83860
+ description: "Environment-specific course overrides (non-identity fields)"
83708
83861
  });
83709
83862
  var CourseOverrides5 = exports_external5.object({
83710
- staging: CourseEnvOverrides5.optional(),
83711
- production: CourseEnvOverrides5.optional()
83712
- });
83863
+ staging: CourseEnvOverrides5.meta({
83864
+ description: "Overrides for staging environment"
83865
+ }).optional(),
83866
+ production: CourseEnvOverrides5.meta({
83867
+ description: "Overrides for production environment"
83868
+ }).optional()
83869
+ }).meta({ id: "CourseOverrides", description: "Per-environment course overrides" });
83713
83870
  var CourseConfig5 = CourseDefaults5.extend({
83714
- subject: TimebackSubject5,
83715
- grade: TimebackGrade5.optional(),
83871
+ subject: TimebackSubject5.meta({ description: "Subject area for this course" }),
83872
+ grade: TimebackGrade5.meta({
83873
+ description: "Grade level (-1 = Pre-K, 0 = K, 1-12 = grades, 13 = AP)"
83874
+ }).optional(),
83716
83875
  ids: CourseIds5.nullable().optional(),
83717
- sensor: exports_external5.string().url().optional(),
83718
- launchUrl: exports_external5.string().url().optional(),
83876
+ sensor: exports_external5.url().meta({ description: "Caliper sensor endpoint URL for this course" }).optional(),
83877
+ launchUrl: exports_external5.url().meta({ description: "LTI launch URL for this course" }).optional(),
83719
83878
  overrides: CourseOverrides5.optional()
83879
+ }).meta({
83880
+ id: "CourseConfig",
83881
+ description: "Configuration for a single course. Must have either grade or courseCode (or both)."
83720
83882
  });
83721
83883
  var TimebackConfig5 = exports_external5.object({
83722
- name: exports_external5.string().min(1, "App name is required"),
83723
- defaults: CourseDefaults5.optional(),
83724
- courses: exports_external5.array(CourseConfig5).min(1, "At least one course is required"),
83725
- sensor: exports_external5.string().url().optional(),
83726
- launchUrl: exports_external5.string().url().optional()
83884
+ $schema: exports_external5.string().meta({ description: "JSON Schema reference for editor support" }).optional(),
83885
+ name: exports_external5.string().min(1, "App name is required").meta({ description: "Display name for your app" }),
83886
+ defaults: CourseDefaults5.meta({
83887
+ description: "Default properties applied to all courses"
83888
+ }).optional(),
83889
+ courses: exports_external5.array(CourseConfig5).min(1, "At least one course is required").meta({ description: "Courses available in this app" }),
83890
+ sensor: exports_external5.url().meta({ description: "Default Caliper sensor endpoint URL for all courses" }).optional(),
83891
+ launchUrl: exports_external5.url().meta({ description: "Default LTI launch URL for all courses" }).optional()
83892
+ }).meta({
83893
+ id: "TimebackConfig",
83894
+ title: "Timeback Config",
83895
+ description: "Configuration schema for timeback.config.json files"
83727
83896
  }).refine((config22) => {
83728
83897
  return config22.courses.every((c) => c.grade !== undefined || c.courseCode !== undefined);
83729
83898
  }, {
@@ -83744,14 +83913,20 @@ var TimebackConfig5 = exports_external5.object({
83744
83913
  message: "Duplicate courseCode found; each must be unique",
83745
83914
  path: ["courses"]
83746
83915
  }).refine((config22) => {
83747
- return config22.courses.every((c) => c.sensor !== undefined || config22.sensor !== undefined);
83748
- }, {
83749
- message: "Each course must have an effective sensor; set a top-level `sensor` or per-course `sensor`",
83750
- path: ["courses"]
83751
- }).refine((config22) => {
83752
- return config22.courses.every((c) => c.launchUrl !== undefined || config22.launchUrl !== undefined);
83916
+ return config22.courses.every((c) => {
83917
+ if (c.sensor !== undefined || config22.sensor !== undefined) {
83918
+ return true;
83919
+ }
83920
+ const launchUrls = [
83921
+ c.launchUrl,
83922
+ config22.launchUrl,
83923
+ c.overrides?.staging?.launchUrl,
83924
+ c.overrides?.production?.launchUrl
83925
+ ].filter(Boolean);
83926
+ return launchUrls.length > 0;
83927
+ });
83753
83928
  }, {
83754
- message: "Each course must have an effective launchUrl; set a top-level `launchUrl` or per-course `launchUrl`",
83929
+ 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.",
83755
83930
  path: ["courses"]
83756
83931
  });
83757
83932
  var EdubridgeDateString5 = exports_external5.union([IsoDateString5, IsoDateTimeString5]);
@@ -85509,163 +85684,6 @@ async function lookupTimebackIdByEmail(params) {
85509
85684
  throw new TimebackUserResolutionError(`Failed to lookup Timeback user: ${message}`, "timeback_user_lookup_failed");
85510
85685
  }
85511
85686
  }
85512
-
85513
- class ActivityCourseResolutionError extends Error {
85514
- code;
85515
- selector;
85516
- count;
85517
- constructor(code, selector, count) {
85518
- super(code);
85519
- this.code = code;
85520
- this.selector = selector;
85521
- this.count = count;
85522
- }
85523
- get selectorDescription() {
85524
- if ("grade" in this.selector) {
85525
- return `${this.selector.subject} grade ${this.selector.grade}`;
85526
- }
85527
- return `code "${this.selector.code}"`;
85528
- }
85529
- }
85530
- function resolveActivityCourse(courses, courseRef) {
85531
- let matches;
85532
- if ("grade" in courseRef) {
85533
- matches = courses.filter((c) => c.subject === courseRef.subject && c.grade === courseRef.grade);
85534
- } else {
85535
- matches = courses.filter((c) => c.courseCode === courseRef.code);
85536
- }
85537
- if (matches.length === 0) {
85538
- throw new ActivityCourseResolutionError("unknown_course", courseRef);
85539
- }
85540
- if (matches.length > 1) {
85541
- throw new ActivityCourseResolutionError("ambiguous_course", courseRef, matches.length);
85542
- }
85543
- return matches[0];
85544
- }
85545
- // src/server/lib/build-activity-events.ts
85546
- class MissingSyncedCourseIdError extends Error {
85547
- course;
85548
- env;
85549
- constructor(course, env) {
85550
- const identifier = course.grade === undefined ? course.courseCode ?? course.subject : `${course.subject} grade ${course.grade}`;
85551
- super(`Course "${identifier}" is missing a synced ID for ${env}. Run \`timeback sync\` first.`);
85552
- this.name = "MissingSyncedCourseIdError";
85553
- this.course = course;
85554
- this.env = env;
85555
- }
85556
- }
85557
- function buildCourseId(course, apiEnv) {
85558
- const courseId = course.ids?.[apiEnv];
85559
- if (!courseId) {
85560
- throw new MissingSyncedCourseIdError(course, apiEnv);
85561
- }
85562
- return courseId;
85563
- }
85564
- function buildCourseName(course) {
85565
- if (course.courseCode) {
85566
- return course.courseCode;
85567
- }
85568
- if (course.grade !== undefined) {
85569
- return `${course.subject} G${String(course.grade)}`;
85570
- }
85571
- return course.subject;
85572
- }
85573
-
85574
- class InvalidSensorUrlError extends Error {
85575
- sensor;
85576
- constructor(sensor) {
85577
- super(`Invalid sensor URL "${sensor}". Sensor must be a valid absolute URL (e.g., "https://sensor.example.com") to support slug-based activity IDs.`);
85578
- this.name = "InvalidSensorUrlError";
85579
- this.sensor = sensor;
85580
- }
85581
- }
85582
- function buildCanonicalActivityUrl(sensor, selector, slug) {
85583
- let base;
85584
- try {
85585
- base = new URL(sensor);
85586
- } catch {
85587
- throw new InvalidSensorUrlError(sensor);
85588
- }
85589
- const pathSegment = "grade" in selector ? `${selector.subject}/g${String(selector.grade)}` : selector.code;
85590
- const basePath = base.pathname.replace(/\/+$/, "");
85591
- base.pathname = `${basePath}/activities/${pathSegment}/${encodeURIComponent(slug)}`;
85592
- return base.toString();
85593
- }
85594
- function buildActivityContext(payload, course, appName, apiEnv, sensor) {
85595
- return {
85596
- id: buildCanonicalActivityUrl(sensor, payload.course, payload.id),
85597
- type: "TimebackActivityContext",
85598
- subject: course.subject,
85599
- app: { name: appName },
85600
- activity: { name: payload.name },
85601
- course: {
85602
- id: buildCourseId(course, apiEnv),
85603
- name: buildCourseName(course)
85604
- }
85605
- };
85606
- }
85607
- function buildActivityMetrics(metrics) {
85608
- const result = [];
85609
- if (metrics.totalQuestions !== undefined) {
85610
- result.push({ type: "totalQuestions", value: metrics.totalQuestions });
85611
- }
85612
- if (metrics.correctQuestions !== undefined) {
85613
- result.push({ type: "correctQuestions", value: metrics.correctQuestions });
85614
- }
85615
- if (metrics.xpEarned !== undefined) {
85616
- result.push({ type: "xpEarned", value: metrics.xpEarned });
85617
- }
85618
- if (metrics.masteredUnits !== undefined) {
85619
- result.push({ type: "masteredUnits", value: metrics.masteredUnits });
85620
- }
85621
- return result;
85622
- }
85623
- function buildTimeSpentMetrics(elapsedMs, pausedMs) {
85624
- const result = [{ type: "active", value: Math.max(0, elapsedMs) / 1000 }];
85625
- if (pausedMs > 0) {
85626
- result.push({ type: "inactive", value: Math.max(0, pausedMs) / 1000 });
85627
- }
85628
- return result;
85629
- }
85630
- async function sendCaliperEnvelope(client, sensor, activityEvent, timeSpentEvent) {
85631
- await client.caliper.events.send(sensor, [activityEvent, timeSpentEvent]);
85632
- }
85633
- // src/server/lib/build-user-profile.ts
85634
- function buildCourseLookup(courses, apiEnv) {
85635
- const courseById = new Map;
85636
- for (const course of courses) {
85637
- const courseId = course.ids?.[apiEnv];
85638
- if (courseId) {
85639
- courseById.set(courseId, course);
85640
- }
85641
- }
85642
- return courseById;
85643
- }
85644
- function mapEnrollmentsToCourses(enrollments, courseById) {
85645
- return enrollments.map((enrollment) => {
85646
- const configuredCourse = courseById.get(enrollment.course.id);
85647
- return {
85648
- id: enrollment.course.id,
85649
- code: configuredCourse?.courseCode ?? enrollment.course.id,
85650
- name: enrollment.course.title
85651
- };
85652
- });
85653
- }
85654
- function pickGoalsFromEnrollments(enrollments) {
85655
- return enrollments.map((enrollment) => enrollment.metadata?.goals).find(Boolean);
85656
- }
85657
- function getUtcDayRange(date6) {
85658
- const start = new Date(Date.UTC(date6.getUTCFullYear(), date6.getUTCMonth(), date6.getUTCDate()));
85659
- const end = new Date(Date.UTC(date6.getUTCFullYear(), date6.getUTCMonth(), date6.getUTCDate(), 23, 59, 59, 999));
85660
- return { start, end };
85661
- }
85662
- function sumXp(facts) {
85663
- return Object.values(facts).reduce((dateTotal, subjects) => {
85664
- return dateTotal + Object.values(subjects).reduce((subjectTotal, metrics) => {
85665
- return subjectTotal + (metrics.activityMetrics?.xpEarned ?? 0);
85666
- }, 0);
85667
- }, 0);
85668
- }
85669
85687
  // src/shared/constants.ts
85670
85688
  var ROUTES = {
85671
85689
  ACTIVITY: "/activity",
@@ -85675,10 +85693,83 @@ var ROUTES = {
85675
85693
  CALLBACK: "/identity/callback"
85676
85694
  },
85677
85695
  USER: {
85678
- ME: "/user/me"
85696
+ ME: "/user/me",
85697
+ VERIFY: "/user/verify"
85679
85698
  }
85680
85699
  };
85681
85700
 
85701
+ // src/server/lib/sso.ts
85702
+ function buildErrorContext(error57, errorCode, state, req) {
85703
+ return {
85704
+ error: error57,
85705
+ errorCode,
85706
+ state,
85707
+ req,
85708
+ redirect: redirectResponse,
85709
+ json: jsonResponse
85710
+ };
85711
+ }
85712
+ function tryDecodeState(stateParam) {
85713
+ try {
85714
+ return decodeBase64Url(stateParam);
85715
+ } catch {
85716
+ ssoLog.warn("Failed to decode state");
85717
+ return;
85718
+ }
85719
+ }
85720
+ function handleIdpError(errorParam, url6, state, req, onCallbackError) {
85721
+ const errorDesc = url6.searchParams.get("error_description");
85722
+ ssoLog.error("IdP returned error", { error: errorParam, description: errorDesc });
85723
+ const error57 = new Error(errorDesc ?? errorParam);
85724
+ if (onCallbackError) {
85725
+ return onCallbackError(buildErrorContext(error57, errorParam, state, req));
85726
+ }
85727
+ return jsonResponse({ error: errorParam }, 400);
85728
+ }
85729
+ function handleMissingCode(state, req, onCallbackError) {
85730
+ ssoLog.error("Missing authorization code in callback");
85731
+ const error57 = new Error("Missing authorization code");
85732
+ if (onCallbackError) {
85733
+ return onCallbackError(buildErrorContext(error57, "missing_code", state, req));
85734
+ }
85735
+ return jsonResponse({ error: "Missing authorization code" }, 400);
85736
+ }
85737
+ async function initiateSignIn(params) {
85738
+ const { req, env, clientId, buildState } = params;
85739
+ const issuer = params.issuer ?? getIssuer(env);
85740
+ const url6 = new URL(req.url);
85741
+ let redirectUri = params.redirectUri;
85742
+ if (!redirectUri) {
85743
+ const basePath = url6.pathname.replace(ROUTES.IDENTITY.SIGNIN, "");
85744
+ redirectUri = `${url6.origin}${basePath}${ROUTES.IDENTITY.CALLBACK}`;
85745
+ }
85746
+ ssoLog.debug("SSO sign-in initiated", { env, issuer, clientId, redirectUri });
85747
+ const stateData = buildState ? buildState({ req, url: url6 }) : {};
85748
+ const state = encodeBase64Url(stateData);
85749
+ const authUrl = await buildAuthorizationUrl({
85750
+ issuer,
85751
+ clientId,
85752
+ redirectUri,
85753
+ state
85754
+ });
85755
+ return redirectResponse(authUrl);
85756
+ }
85757
+ function parseCallback(req) {
85758
+ const url6 = new URL(req.url);
85759
+ const code = url6.searchParams.get("code");
85760
+ const errorParam = url6.searchParams.get("error");
85761
+ const stateParam = url6.searchParams.get("state");
85762
+ ssoLog.debug("Received callback from IdP", { hasCode: !!code, error: errorParam });
85763
+ const state = stateParam ? tryDecodeState(stateParam) : undefined;
85764
+ return { url: url6, code, errorParam, state };
85765
+ }
85766
+ function computeRedirectUri(url6, configuredRedirectUri) {
85767
+ if (configuredRedirectUri) {
85768
+ return configuredRedirectUri;
85769
+ }
85770
+ const basePath = url6.pathname.replace(ROUTES.IDENTITY.CALLBACK, "");
85771
+ return `${url6.origin}${basePath}${ROUTES.IDENTITY.CALLBACK}`;
85772
+ }
85682
85773
  // src/server/adapters/utils.ts
85683
85774
  function normalizePathname(path) {
85684
85775
  const raw = path.trim();
@@ -85728,6 +85819,8 @@ function matchTimebackRoute(params) {
85728
85819
  return "identity.signOut";
85729
85820
  if (relative === ROUTES.USER.ME)
85730
85821
  return "user.me";
85822
+ if (relative === ROUTES.USER.VERIFY)
85823
+ return "user.verify";
85731
85824
  }
85732
85825
  if (method === "POST") {
85733
85826
  if (relative === ROUTES.ACTIVITY)
@@ -85753,6 +85846,8 @@ function matchTimebackRoute(params) {
85753
85846
  return "identity.signOut";
85754
85847
  if (pathname.endsWith(ROUTES.USER.ME))
85755
85848
  return "user.me";
85849
+ if (pathname.endsWith(ROUTES.USER.VERIFY))
85850
+ return "user.verify";
85756
85851
  }
85757
85852
  if (method === "POST") {
85758
85853
  if (pathname.endsWith(ROUTES.ACTIVITY))
@@ -85791,6 +85886,11 @@ function toSolidStartHandler(input) {
85791
85886
  return jsonResponse({ error: "Not found" }, 404);
85792
85887
  return handle.user.me(event.request);
85793
85888
  }
85889
+ if (route === "user.verify") {
85890
+ if (!hasUserHandler(handle))
85891
+ return jsonResponse({ error: "Not found" }, 404);
85892
+ return handle.user.verify(event.request);
85893
+ }
85794
85894
  if (route === "activity") {
85795
85895
  if (!hasActivityHandler(handle))
85796
85896
  return jsonResponse({ error: "Not found" }, 404);
@@ -85827,6 +85927,11 @@ function solidStartHandler(options) {
85827
85927
  return;
85828
85928
  return handle.user.me(request);
85829
85929
  }
85930
+ if (route === "user.verify") {
85931
+ if (!hasUserHandler(handle))
85932
+ return;
85933
+ return handle.user.verify(request);
85934
+ }
85830
85935
  if (route === "activity") {
85831
85936
  if (!hasActivityHandler(handle))
85832
85937
  return;