@superbuilders/primer-tives 1.0.0 → 1.1.0

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/README.md CHANGED
@@ -19,7 +19,7 @@ Dependency note: `@superbuilders/errors` is installed automatically and is used
19
19
 
20
20
  | Import | Runs on | Wraps |
21
21
  |---|---|---|
22
- | `@superbuilders/primer-tives/server` | your backend | `POST /api/v0/students`, `POST /api/v0/auth/exchange`, `POST /api/v0/auth/exchange/timeback` |
22
+ | `@superbuilders/primer-tives/server` | your backend | `POST /api/v0/students`, `PATCH /api/v0/students/:id/hints`, `POST /api/v0/auth/exchange`, `POST /api/v0/auth/exchange/timeback` |
23
23
  | `@superbuilders/primer-tives/client` | the browser | `POST /api/v0/advance` |
24
24
 
25
25
  ## Supported student flows
@@ -32,8 +32,9 @@ Use this when **your system** owns the learner identity.
32
32
 
33
33
  1. Call `createStudent()` once to mint a stable Primer `studentId`.
34
34
  2. Persist that `studentId` in your own database alongside your user record.
35
- 3. At each session start, call `exchangeStudentForAccessToken(studentId)`.
36
- 4. Hand the returned `accessToken` to the browser SDK.
35
+ 3. **Before the student's first session**, call `setStudentHints(studentId, { gradeLevel })` at least once. Native students are created hint-less, and the first `/advance` call fails without a `gradeLevel` on record.
36
+ 4. At each session start, call `exchangeStudentForAccessToken(studentId)`.
37
+ 5. Hand the returned `accessToken` to the browser SDK.
37
38
 
38
39
  ### 2. Live-authoritative Timeback students
39
40
 
@@ -56,6 +57,7 @@ Persisting the returned `studentId` is fine if you want a stable local foreign k
56
57
  import * as errors from "@superbuilders/errors"
57
58
  import {
58
59
  createPrimerServer,
60
+ ErrBadRequest,
59
61
  ErrConflict,
60
62
  ErrInvalidSecretKey,
61
63
  ErrStudentNotFound
@@ -63,7 +65,7 @@ import {
63
65
 
64
66
  const primer = createPrimerServer({
65
67
  origin: "https://sb-primer.vercel.app",
66
- secretKey: process.env.PRIMER_CLIENT_SECRET_KEY_DEV!,
68
+ secretKey: process.env.PRIMER_DEMO_SECRET_KEY!,
67
69
  logger: console
68
70
  })
69
71
 
@@ -81,6 +83,20 @@ if (createResult.error) {
81
83
  const studentId = createResult.data
82
84
  // persist `studentId` alongside your own user record
83
85
 
86
+ // Required before the first session for native students.
87
+ const hintsResult = await errors.try(
88
+ primer.setStudentHints(studentId, { gradeLevel: "3" })
89
+ )
90
+ if (hintsResult.error) {
91
+ if (errors.is(hintsResult.error, ErrStudentNotFound)) {
92
+ throw new Error("Stored Primer student id no longer exists on this frontend")
93
+ }
94
+ if (errors.is(hintsResult.error, ErrBadRequest)) {
95
+ throw new Error("Invalid hint payload (e.g. unsupported gradeLevel)")
96
+ }
97
+ throw hintsResult.error
98
+ }
99
+
84
100
  // Every session start.
85
101
  const tokenResult = await errors.try(
86
102
  primer.exchangeStudentForAccessToken(studentId)
@@ -114,7 +130,7 @@ import {
114
130
 
115
131
  const primer = createPrimerServer({
116
132
  origin: "https://sb-primer.vercel.app",
117
- secretKey: process.env.PRIMER_CLIENT_SECRET_KEY_DEV!,
133
+ secretKey: process.env.PRIMER_DEMO_SECRET_KEY!,
118
134
  logger: console
119
135
  })
120
136
 
@@ -201,6 +217,10 @@ import {
201
217
  type PrimerServerConfig,
202
218
  type SessionToken,
203
219
  type TimebackSession,
220
+ type PlacementHints,
221
+ type PlacementHintsResult,
222
+ type GradeLevel,
223
+ GRADE_LEVELS,
204
224
  type PrimerLogger,
205
225
  ErrBadRequest,
206
226
  ErrConflict,
@@ -230,11 +250,12 @@ interface PrimerServerConfig {
230
250
  function createPrimerServer(config: PrimerServerConfig): PrimerServer
231
251
  ```
232
252
 
233
- Returns a `PrimerServer` with exactly **three** methods:
253
+ Returns a `PrimerServer` with exactly **four** methods:
234
254
 
235
255
  ```ts
236
256
  interface PrimerServer {
237
257
  createStudent(): Promise<string>
258
+ setStudentHints(studentId: string, hints: PlacementHints): Promise<PlacementHintsResult>
238
259
  exchangeStudentForAccessToken(studentId: string): Promise<SessionToken>
239
260
  exchangeTimebackStudentForAccessToken(sourcedId: string): Promise<TimebackSession>
240
261
  }
@@ -256,9 +277,26 @@ Notes:
256
277
 
257
278
  - Call this when your system is the source of truth for learner identity.
258
279
  - Persist the returned `studentId` in your own database.
280
+ - **Before the first session**, call `setStudentHints(studentId, { gradeLevel })`. Native students are created without a `gradeLevel`, and `/advance` requires one.
259
281
  - Use that stored `studentId` with `exchangeStudentForAccessToken(studentId)` at each session start.
260
282
  - `logger` is required. Pass any object implementing `debug/info/warn/error`; `console` is acceptable for basic integrations.
261
283
 
284
+ ### `setStudentHints(studentId, hints): Promise<PlacementHintsResult>`
285
+
286
+ Partial upsert of a native/manual student's placement-routing hints. Omitted fields are left untouched; the server returns the persisted state after upsert.
287
+
288
+ ```ts
289
+ const { gradeLevel } = await primer.setStudentHints(studentId, {
290
+ gradeLevel: "3"
291
+ })
292
+ ```
293
+
294
+ - **Required** for native/manual students before their first session. Without a `gradeLevel` on record, the first `/advance` call fails server-side.
295
+ - `gradeLevel` is the only hint today; future hint kinds (raw context, interests, etc.) will appear as additional optional fields on `PlacementHints`.
296
+ - Safe to call repeatedly — each call is a partial upsert.
297
+ - Not needed for Timeback-linked students: `exchangeTimebackStudentForAccessToken` syncs the grade level from the authority on every call.
298
+ - Throws `ErrStudentNotFound` if `studentId` is unknown; `ErrBadRequest` if the payload fails validation (e.g., unsupported `gradeLevel`).
299
+
262
300
  ### `exchangeStudentForAccessToken(studentId): Promise<SessionToken>`
263
301
 
264
302
  Mint a short-lived access token for an existing **native/manual** Primer student.
@@ -320,6 +358,27 @@ interface TimebackSession {
320
358
  }
321
359
  ```
322
360
 
361
+ ### `PlacementHints`
362
+
363
+ ```ts
364
+ interface PlacementHints {
365
+ readonly gradeLevel?: GradeLevel
366
+ }
367
+ ```
368
+
369
+ Partial shape: pass only the fields you want to set. Today the only hint is `gradeLevel` (the learner's current grade). Re-export `GRADE_LEVELS` enumerates the supported values.
370
+
371
+ ### `PlacementHintsResult`
372
+
373
+ ```ts
374
+ interface PlacementHintsResult {
375
+ readonly studentId: string
376
+ readonly gradeLevel: GradeLevel | null
377
+ }
378
+ ```
379
+
380
+ The persisted state after upsert. `gradeLevel` is `null` only on native students who have never had one set.
381
+
323
382
  ## Error sentinels (`/server`)
324
383
 
325
384
  All `/server` methods throw sentinel-wrapped `Error`s. Use `errors.try()` and `errors.is()` from `@superbuilders/errors`.
@@ -351,7 +410,7 @@ import {
351
410
 
352
411
  const primer = createPrimerServer({
353
412
  origin: "https://sb-primer.vercel.app",
354
- secretKey: process.env.PRIMER_CLIENT_SECRET_KEY_DEV!,
413
+ secretKey: process.env.PRIMER_DEMO_SECRET_KEY!,
355
414
  logger: console
356
415
  })
357
416
 
@@ -1 +1 @@
1
- {"version":3,"file":"choice-state.d.ts","sourceRoot":"","sources":["../../src/client/choice-state.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oDAAoD,CAAA;AACxF,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,wCAAwC,CAAA;AACnE,OAAO,KAAK,EACX,WAAW,EACX,cAAc,EACd,mBAAmB,EACnB,gBAAgB,EAChB,MAAM,0CAA0C,CAAA;AA6CjD,iBAAS,WAAW,CAAC,IAAI,SAAS,KAAK,EACtC,GAAG,EAAE,cAAc,CAAC,IAAI,CAAC,EACzB,QAAQ,EAAE,gBAAgB,GAAG,IAAI,EACjC,WAAW,EAAE,OAAO,CAAC,mBAAmB,CAAC,IAAI,CAAC,EAAE;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAC,EACnE,OAAO,EAAE,cAAc,EAAE,EACzB,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,GAChB,WAAW,CAAC,IAAI,CAAC,CAsFnB;AAED,OAAO,EAAE,WAAW,EAAE,CAAA"}
1
+ {"version":3,"file":"choice-state.d.ts","sourceRoot":"","sources":["../../src/client/choice-state.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oDAAoD,CAAA;AACxF,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,wCAAwC,CAAA;AACnE,OAAO,KAAK,EACX,WAAW,EACX,cAAc,EACd,mBAAmB,EACnB,gBAAgB,EAChB,MAAM,0CAA0C,CAAA;AAGjD,iBAAS,WAAW,CAAC,IAAI,SAAS,KAAK,EACtC,GAAG,EAAE,cAAc,CAAC,IAAI,CAAC,EACzB,QAAQ,EAAE,gBAAgB,GAAG,IAAI,EACjC,WAAW,EAAE,OAAO,CAAC,mBAAmB,CAAC,IAAI,CAAC,EAAE;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAC,EACnE,OAAO,EAAE,cAAc,EAAE,EACzB,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,GAChB,WAAW,CAAC,IAAI,CAAC,CAqFnB;AAED,OAAO,EAAE,WAAW,EAAE,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"extended-text-state.d.ts","sourceRoot":"","sources":["../../src/client/extended-text-state.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oDAAoD,CAAA;AACxF,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,wCAAwC,CAAA;AACnE,OAAO,KAAK,EACX,WAAW,EACX,gBAAgB,EAChB,2BAA2B,EAC3B,MAAM,0CAA0C,CAAA;AAoBjD,iBAAS,iBAAiB,CAAC,IAAI,SAAS,KAAK,EAC5C,GAAG,EAAE,cAAc,CAAC,IAAI,CAAC,EACzB,QAAQ,EAAE,gBAAgB,GAAG,IAAI,EACjC,WAAW,EAAE,OAAO,CAAC,2BAA2B,EAAE;IAAE,IAAI,EAAE,eAAe,CAAA;CAAE,CAAC,GAC1E,WAAW,CAAC,IAAI,CAAC,CA0JnB;AAED,OAAO,EAAE,iBAAiB,EAAE,CAAA"}
1
+ {"version":3,"file":"extended-text-state.d.ts","sourceRoot":"","sources":["../../src/client/extended-text-state.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oDAAoD,CAAA;AACxF,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,wCAAwC,CAAA;AACnE,OAAO,KAAK,EACX,WAAW,EACX,gBAAgB,EAChB,2BAA2B,EAC3B,MAAM,0CAA0C,CAAA;AAGjD,iBAAS,iBAAiB,CAAC,IAAI,SAAS,KAAK,EAC5C,GAAG,EAAE,cAAc,CAAC,IAAI,CAAC,EACzB,QAAQ,EAAE,gBAAgB,GAAG,IAAI,EACjC,WAAW,EAAE,OAAO,CAAC,2BAA2B,EAAE;IAAE,IAAI,EAAE,eAAe,CAAA;CAAE,CAAC,GAC1E,WAAW,CAAC,IAAI,CAAC,CAqKnB;AAED,OAAO,EAAE,iBAAiB,EAAE,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAA;AAClE,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAA;AAE/E,YAAY,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAA;AAEtE,OAAO,EAAE,QAAQ,EAAE,MAAM,qCAAqC,CAAA;AAC9D,YAAY,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,qCAAqC,CAAA;AAEhF,OAAO,EACN,aAAa,EACb,WAAW,EACX,YAAY,EACZ,qBAAqB,EACrB,oBAAoB,EACpB,YAAY,EACZ,uBAAuB,EACvB,UAAU,EACV,WAAW,EACX,kBAAkB,EAClB,cAAc,EACd,cAAc,EACd,qBAAqB,EACrB,UAAU,EACV,eAAe,EACf,iBAAiB,EACjB,MAAM,oCAAoC,CAAA;AAE3C,YAAY,EACX,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,cAAc,EACd,YAAY,EACZ,yBAAyB,EACzB,kBAAkB,EAClB,uBAAuB,EACvB,iBAAiB,EACjB,UAAU,EACV,aAAa,EACb,aAAa,EACb,iBAAiB,EACjB,gBAAgB,EAChB,SAAS,EACT,WAAW,EACX,UAAU,EACV,eAAe,EACf,gBAAgB,EAChB,WAAW,EACX,UAAU,EACV,cAAc,EACd,mBAAmB,EACnB,qBAAqB,EACrB,cAAc,EACd,SAAS,EACT,aAAa,EACb,uBAAuB,EACvB,WAAW,EACX,cAAc,EACd,mBAAmB,EACnB,mBAAmB,EACnB,gBAAgB,EAChB,kBAAkB,EAClB,iBAAiB,EACjB,yBAAyB,EACzB,iBAAiB,EACjB,2BAA2B,EAC3B,eAAe,EACf,cAAc,EACd,MAAM,0CAA0C,CAAA;AAEjD,YAAY,EACX,YAAY,EACZ,aAAa,EACb,WAAW,EACX,MAAM,4CAA4C,CAAA;AACnD,OAAO,EACN,iBAAiB,EACjB,kBAAkB,EAClB,MAAM,4CAA4C,CAAA;AAEnD,YAAY,EACX,sBAAsB,EACtB,2BAA2B,EAC3B,qBAAqB,EACrB,0BAA0B,EAC1B,KAAK,EACL,QAAQ,EACR,WAAW,EACX,MAAM,EACN,QAAQ,EACR,MAAM,wCAAwC,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAA;AAClE,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAA;AAE/E,YAAY,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAA;AAEtE,OAAO,EAAE,QAAQ,EAAE,MAAM,qCAAqC,CAAA;AAC9D,YAAY,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,qCAAqC,CAAA;AAEhF,OAAO,EACN,aAAa,EACb,WAAW,EACX,YAAY,EACZ,qBAAqB,EACrB,oBAAoB,EACpB,YAAY,EACZ,uBAAuB,EACvB,UAAU,EACV,WAAW,EACX,kBAAkB,EAClB,cAAc,EACd,cAAc,EACd,qBAAqB,EACrB,UAAU,EACV,eAAe,EACf,iBAAiB,EACjB,MAAM,oCAAoC,CAAA;AAE3C,YAAY,EACX,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,cAAc,EACd,YAAY,EACZ,yBAAyB,EACzB,kBAAkB,EAClB,uBAAuB,EACvB,iBAAiB,EACjB,UAAU,EACV,aAAa,EACb,aAAa,EACb,iBAAiB,EACjB,gBAAgB,EAChB,SAAS,EACT,WAAW,EACX,UAAU,EACV,eAAe,EACf,gBAAgB,EAChB,WAAW,EACX,UAAU,EACV,cAAc,EACd,mBAAmB,EACnB,qBAAqB,EACrB,cAAc,EACd,SAAS,EACT,aAAa,EACb,uBAAuB,EACvB,WAAW,EACX,cAAc,EACd,mBAAmB,EACnB,mBAAmB,EACnB,gBAAgB,EAChB,kBAAkB,EAClB,iBAAiB,EACjB,yBAAyB,EACzB,iBAAiB,EACjB,2BAA2B,EAC3B,eAAe,EACf,cAAc,EACd,MAAM,0CAA0C,CAAA;AAEjD,YAAY,EACX,YAAY,EACZ,aAAa,EACb,WAAW,EACX,MAAM,4CAA4C,CAAA;AAEnD,OAAO,EACN,iBAAiB,EACjB,kBAAkB,EAClB,MAAM,4CAA4C,CAAA;AAEnD,YAAY,EACX,sBAAsB,EACtB,2BAA2B,EAC3B,qBAAqB,EACrB,0BAA0B,EAC1B,KAAK,EACL,QAAQ,EACR,WAAW,EACX,MAAM,EACN,QAAQ,EACR,MAAM,wCAAwC,CAAA"}