@nxgt/janus 0.18.0 → 0.20.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.
Files changed (74) hide show
  1. package/README.md +148 -12
  2. package/dist/auth/email-flows/reset-password.d.ts.map +1 -1
  3. package/dist/auth/events.d.ts +3 -3
  4. package/dist/auth/index.d.ts +1 -1
  5. package/dist/auth/index.d.ts.map +1 -1
  6. package/dist/auth/magic-link.d.ts +3 -0
  7. package/dist/auth/magic-link.d.ts.map +1 -1
  8. package/dist/auth/one-time/issue.d.ts +6 -0
  9. package/dist/auth/one-time/issue.d.ts.map +1 -1
  10. package/dist/auth/port/assert-stores.d.ts.map +1 -1
  11. package/dist/auth/port/memory/users.d.ts.map +1 -1
  12. package/dist/auth/port/types/stores.d.ts +5 -0
  13. package/dist/auth/port/types/stores.d.ts.map +1 -1
  14. package/dist/auth/port/types/users.d.ts +17 -0
  15. package/dist/auth/port/types/users.d.ts.map +1 -1
  16. package/dist/auth/prepared.d.ts +21 -0
  17. package/dist/auth/prepared.d.ts.map +1 -0
  18. package/dist/auth/proven-sign-in.d.ts +22 -0
  19. package/dist/auth/proven-sign-in.d.ts.map +1 -0
  20. package/dist/auth/sign-in-code.d.ts +4 -0
  21. package/dist/auth/sign-in-code.d.ts.map +1 -1
  22. package/dist/auth/types/email-flows.d.ts +18 -0
  23. package/dist/auth/types/email-flows.d.ts.map +1 -1
  24. package/dist/auth/types/index.d.ts +2 -0
  25. package/dist/auth/types/index.d.ts.map +1 -1
  26. package/dist/auth/types/janus.d.ts +7 -5
  27. package/dist/auth/types/janus.d.ts.map +1 -1
  28. package/dist/auth/types/magic-link.d.ts +11 -0
  29. package/dist/auth/types/magic-link.d.ts.map +1 -1
  30. package/dist/auth/types/prepared.d.ts +41 -0
  31. package/dist/auth/types/prepared.d.ts.map +1 -0
  32. package/dist/auth/types/proven-sign-in.d.ts +43 -0
  33. package/dist/auth/types/proven-sign-in.d.ts.map +1 -0
  34. package/dist/auth/types/sign-in.d.ts +2 -2
  35. package/dist/auth/types/user-type.d.ts +15 -0
  36. package/dist/auth/types/user-type.d.ts.map +1 -1
  37. package/dist/auth/users/any-type-api.d.ts +2 -2
  38. package/dist/auth/users/any-type-api.d.ts.map +1 -1
  39. package/dist/auth/users/find-many.d.ts +29 -0
  40. package/dist/auth/users/find-many.d.ts.map +1 -0
  41. package/dist/auth/users/record-flows.d.ts.map +1 -1
  42. package/dist/auth/users/type-api.d.ts +2 -2
  43. package/dist/auth/users/type-api.d.ts.map +1 -1
  44. package/dist/chunks/{index-kvjj5xkp.js → index-6bjfv4xv.js} +8 -2
  45. package/dist/chunks/{index-kvjj5xkp.js.map → index-6bjfv4xv.js.map} +3 -3
  46. package/dist/conformance/cases/outage.d.ts.map +1 -1
  47. package/dist/conformance/cases/users/batch.d.ts +8 -0
  48. package/dist/conformance/cases/users/batch.d.ts.map +1 -0
  49. package/dist/conformance/cases/users/index.d.ts.map +1 -1
  50. package/dist/conformance/describe.d.ts +2 -1
  51. package/dist/conformance/describe.d.ts.map +1 -1
  52. package/dist/conformance/index.d.ts +1 -1
  53. package/dist/conformance/index.d.ts.map +1 -1
  54. package/dist/conformance/index.js +104 -44
  55. package/dist/conformance/index.js.map +7 -6
  56. package/dist/conformance/types.d.ts +10 -2
  57. package/dist/conformance/types.d.ts.map +1 -1
  58. package/dist/index.js +150 -23
  59. package/dist/index.js.map +14 -11
  60. package/docs/README.md +1 -1
  61. package/docs/guide/adapters.md +44 -4
  62. package/docs/guide/devices.md +4 -0
  63. package/docs/guide/email-flows.md +105 -0
  64. package/docs/guide/errors.md +2 -2
  65. package/docs/guide/events.md +2 -2
  66. package/docs/guide/magic-link.md +49 -2
  67. package/docs/guide/passwords.md +5 -2
  68. package/docs/guide/second-factor.md +1 -1
  69. package/docs/guide/sessions.md +94 -1
  70. package/docs/guide/sign-in-code.md +54 -2
  71. package/docs/guide/users.md +49 -0
  72. package/docs/roadmap.md +28 -39
  73. package/docs/troubleshooting.md +120 -7
  74. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  mintId2,
3
3
  createMemoryStores2
4
- } from "../chunks/index-kvjj5xkp.js";
4
+ } from "../chunks/index-6bjfv4xv.js";
5
5
  import {
6
6
  JanusError2,
7
7
  StoreFailure2,
@@ -180,6 +180,10 @@ var seed = async ({ stores }) => {
180
180
  };
181
181
  var outageCases = [
182
182
  outage("users", "findUser", ({ stores }) => stores.users.findUser(user.id), seed),
183
+ {
184
+ ...outage("users", "findUsers", async ({ stores }) => stores.users.findUsers?.call(stores.users, [user.id]), seed),
185
+ needs: ["faults", "findUsers"]
186
+ },
183
187
  outage("users", "findUserByLogin", ({ stores }) => stores.users.findUserByLogin(user.type, user.logins[0] ?? ""), seed),
184
188
  outage("users", "listUsers", ({ stores }) => stores.users.listUsers({ type: user.type, after: null, limit: 10 }), seed),
185
189
  outage("sessions", "findSessionByTokenHash", ({ stores }) => stores.sessions.findSessionByTokenHash(session.tokenHash), seed),
@@ -755,12 +759,51 @@ var tokenStoreCases = [
755
759
  ...tokenKindCases,
756
760
  ...tokenDeletionCases
757
761
  ];
758
- // src/conformance/cases/users/deletion.ts
762
+ // src/conformance/cases/users/batch.ts
759
763
  var group13 = "users";
764
+ var byId = (a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
765
+ var userBatchCases = [
766
+ {
767
+ id: "users.findUsers",
768
+ group: group13,
769
+ name: "findUsers answers each stored user once, byte for byte and whatever its type, and leaves out an id nobody holds",
770
+ needs: "findUsers",
771
+ async run({ stores }) {
772
+ const findUsers = stores.users.findUsers;
773
+ ok(findUsers !== undefined, "findUsers should be present");
774
+ const patient = userRecord({ type: "patient" });
775
+ const staff = userRecord({ type: "staff" });
776
+ const other = userRecord();
777
+ for (const record of [patient, staff, other]) {
778
+ await stores.users.insertUser(record);
779
+ }
780
+ const answer = await findUsers?.call(stores.users, [
781
+ staff.id,
782
+ mintId2(),
783
+ patient.id
784
+ ]);
785
+ ok(Array.isArray(answer), "findUsers should answer a list — an absence is a shorter list, never null");
786
+ equal([...answer ?? []].sort(byId), [patient, staff].sort(byId), "findUsers should answer the two stored users exactly as written, and only them");
787
+ }
788
+ },
789
+ {
790
+ id: "users.findUsersNone",
791
+ group: group13,
792
+ name: "findUsers answers an empty list, not null, when no id is held",
793
+ needs: "findUsers",
794
+ async run({ stores }) {
795
+ await stores.users.insertUser(userRecord());
796
+ equal(await stores.users.findUsers?.call(stores.users, [mintId2(), mintId2()]), [], "findUsers for ids nobody holds should answer []");
797
+ }
798
+ }
799
+ ];
800
+
801
+ // src/conformance/cases/users/deletion.ts
802
+ var group14 = "users";
760
803
  var userDeletionCases = [
761
804
  {
762
805
  id: "users.delete",
763
- group: group13,
806
+ group: group14,
764
807
  name: "deletes a user and frees their logins, answers false for nobody, and touches no other user",
765
808
  async run({ stores }) {
766
809
  const record = userRecord();
@@ -780,11 +823,11 @@ var userDeletionCases = [
780
823
  ];
781
824
 
782
825
  // src/conformance/cases/users/inserts.ts
783
- var group14 = "users";
826
+ var group15 = "users";
784
827
  var userInsertCases = [
785
828
  {
786
829
  id: "users.idempotentInsert",
787
- group: group14,
830
+ group: group15,
788
831
  name: "is idempotent under retry: inserting an existing id answers the stored record and writes nothing",
789
832
  async run({ stores }) {
790
833
  const record = userRecord();
@@ -799,7 +842,7 @@ var userInsertCases = [
799
842
  },
800
843
  {
801
844
  id: "users.uniqueness",
802
- group: group14,
845
+ group: group15,
803
846
  name: "lets exactly one of twenty concurrent inserts hold a login: uniqueness is a constraint, not a read",
804
847
  async run({ stores }) {
805
848
  const records = Array.from({ length: 20 }, () => userRecord({ logins: ["contested@example.test"] }));
@@ -824,11 +867,11 @@ var userInsertCases = [
824
867
  ];
825
868
 
826
869
  // src/conformance/cases/users/listing.ts
827
- var group15 = "users";
870
+ var group16 = "users";
828
871
  var userListingCases = [
829
872
  {
830
873
  id: "users.pagination",
831
- group: group15,
874
+ group: group16,
832
875
  name: "pages 25 users of one type by 10 in ascending id order, with no gap, no repeat and no other type, and ends on a null cursor",
833
876
  async run({ stores }) {
834
877
  const records = Array.from({ length: 25 }, () => userRecord());
@@ -929,11 +972,11 @@ async function nullClearsCodes(stores, id) {
929
972
  }
930
973
 
931
974
  // src/conformance/cases/users/patches.ts
932
- var group16 = "users";
975
+ var group17 = "users";
933
976
  var userPatchCases = [
934
977
  {
935
978
  id: "users.omission",
936
- group: group16,
979
+ group: group17,
937
980
  name: "omission — the Kratos PUT trap: an update leaves every field it does not name exactly as it was",
938
981
  async run({ stores }) {
939
982
  const record = userRecord({
@@ -958,7 +1001,7 @@ var userPatchCases = [
958
1001
  },
959
1002
  {
960
1003
  id: "users.passwordSlot",
961
- group: group16,
1004
+ group: group17,
962
1005
  name: "keeps a password the patch does not name, and removes one the patch names null",
963
1006
  async run({ stores }) {
964
1007
  const record = userRecord();
@@ -972,7 +1015,7 @@ var userPatchCases = [
972
1015
  secondFactorSlotCase,
973
1016
  {
974
1017
  id: "users.loginsMove",
975
- group: group16,
1018
+ group: group17,
976
1019
  name: "moves logins on update: the old one is free, the new one found, one held elsewhere refused",
977
1020
  async run({ stores }) {
978
1021
  const record = userRecord();
@@ -993,11 +1036,11 @@ var userPatchCases = [
993
1036
  ];
994
1037
 
995
1038
  // src/conformance/cases/users/reads.ts
996
- var group17 = "users";
1039
+ var group18 = "users";
997
1040
  var userReadCases = [
998
1041
  {
999
1042
  id: "users.roundTrip",
1000
- group: group17,
1043
+ group: group18,
1001
1044
  name: 'round-trips a record byte for byte: nested fields, a 1 that stays a number, a "1" that stays a string',
1002
1045
  async run({ stores }) {
1003
1046
  const record = userRecord({
@@ -1021,7 +1064,7 @@ var userReadCases = [
1021
1064
  },
1022
1065
  {
1023
1066
  id: "users.edgeCharacters",
1024
- group: group17,
1067
+ group: group18,
1025
1068
  name: "round-trips every character the core lets through: control characters, U+FFFF and a surrogate pair",
1026
1069
  async run({ stores }) {
1027
1070
  const edge = "a\x01\x1F￿ \uD83D\uDE00 z";
@@ -1036,7 +1079,7 @@ var userReadCases = [
1036
1079
  },
1037
1080
  {
1038
1081
  id: "users.absence",
1039
- group: group17,
1082
+ group: group18,
1040
1083
  name: "answers null for an absence, never undefined, and an empty page for an empty store",
1041
1084
  async run({ stores }) {
1042
1085
  isNull(await stores.users.findUser(mintId2()), "findUser for an unknown id");
@@ -1046,7 +1089,7 @@ var userReadCases = [
1046
1089
  },
1047
1090
  {
1048
1091
  id: "users.loginBytes",
1049
- group: group17,
1092
+ group: group18,
1050
1093
  name: "compares logins as bytes, within one type: no case folding, no trimming, no collation",
1051
1094
  async run({ stores }) {
1052
1095
  const upper = userRecord({ logins: ["Ada@Example.test"] });
@@ -1063,11 +1106,11 @@ var userReadCases = [
1063
1106
  ];
1064
1107
 
1065
1108
  // src/conformance/cases/users/versions.ts
1066
- var group18 = "users";
1109
+ var group19 = "users";
1067
1110
  var userVersionCases = [
1068
1111
  {
1069
1112
  id: "users.versionIncrements",
1070
- group: group18,
1113
+ group: group19,
1071
1114
  name: "applies an update at the expected version, and answers the record written with its version one higher",
1072
1115
  async run({ stores }) {
1073
1116
  const record = userRecord();
@@ -1085,7 +1128,7 @@ var userVersionCases = [
1085
1128
  },
1086
1129
  {
1087
1130
  id: "users.versionConflict",
1088
- group: group18,
1131
+ group: group19,
1089
1132
  name: "refuses a stale version with both versions, and leaves the record identical byte for byte",
1090
1133
  async run({ stores }) {
1091
1134
  const record = userRecord();
@@ -1106,7 +1149,7 @@ var userVersionCases = [
1106
1149
  },
1107
1150
  {
1108
1151
  id: "users.unknownUpdate",
1109
- group: group18,
1152
+ group: group19,
1110
1153
  name: "refuses an update to an unknown id with NotFoundError, told apart from a version conflict",
1111
1154
  async run({ stores }) {
1112
1155
  const error = await rejects(stores.users.updateUser(mintId2(), { updatedAt: at("2026-02-01T00:00:00.000Z") }, 0), "updateUser on an unknown id should reject");
@@ -1118,6 +1161,7 @@ var userVersionCases = [
1118
1161
  // src/conformance/cases/users/index.ts
1119
1162
  var userStoreCases = [
1120
1163
  ...userReadCases,
1164
+ ...userBatchCases,
1121
1165
  ...userInsertCases,
1122
1166
  ...userVersionCases,
1123
1167
  ...userPatchCases,
@@ -1133,17 +1177,15 @@ var allCases = [
1133
1177
  ];
1134
1178
  var SKIP_REASONS = {
1135
1179
  faults: "faults not provided: the outage invariant is not proven for this adapter",
1136
- deleteExpiredSessions: "stores.sessions does not implement the optional deleteExpiredSessions"
1180
+ deleteExpiredSessions: "stores.sessions does not implement the optional deleteExpiredSessions",
1181
+ findUsers: "stores.users does not implement the optional findUsers: findMany reads with findUser instead"
1137
1182
  };
1138
1183
  async function runCase(conformanceCase, harness) {
1139
1184
  const opened = await harness.open();
1140
1185
  try {
1141
- if (conformanceCase.needs === "faults" && opened.faults === undefined) {
1142
- return { skipped: SKIP_REASONS.faults };
1143
- }
1144
- if (conformanceCase.needs === "deleteExpiredSessions" && opened.stores.sessions.deleteExpiredSessions === undefined) {
1145
- return { skipped: SKIP_REASONS.deleteExpiredSessions };
1146
- }
1186
+ const missing = needsOf(conformanceCase).find((need) => !provides(opened, need));
1187
+ if (missing !== undefined)
1188
+ return { skipped: SKIP_REASONS[missing] };
1147
1189
  await conformanceCase.run({
1148
1190
  stores: opened.stores,
1149
1191
  faults: opened.faults ?? null
@@ -1153,6 +1195,20 @@ async function runCase(conformanceCase, harness) {
1153
1195
  await opened.close?.();
1154
1196
  }
1155
1197
  }
1198
+ function needsOf(conformanceCase) {
1199
+ const { needs } = conformanceCase;
1200
+ return needs === undefined ? [] : typeof needs === "string" ? [needs] : needs;
1201
+ }
1202
+ function provides(opened, need) {
1203
+ switch (need) {
1204
+ case "faults":
1205
+ return opened.faults !== undefined;
1206
+ case "deleteExpiredSessions":
1207
+ return opened.stores.sessions.deleteExpiredSessions !== undefined;
1208
+ case "findUsers":
1209
+ return opened.stores.users.findUsers !== undefined;
1210
+ }
1211
+ }
1156
1212
  function describeJanusStores(options) {
1157
1213
  describeSuite({
1158
1214
  title: `${options.name} — @nxgt/janus conformance`,
@@ -1163,6 +1219,10 @@ function describeJanusStores(options) {
1163
1219
  faults: options.faults
1164
1220
  });
1165
1221
  }
1222
+ function needsList(described) {
1223
+ const { needs } = described;
1224
+ return needs === undefined ? [] : typeof needs === "string" ? [needs] : needs;
1225
+ }
1166
1226
  function describeSuite(options) {
1167
1227
  const { runner, skip, cases } = options;
1168
1228
  const groups = [...new Set(cases.map((c) => c.group))];
@@ -1176,7 +1236,7 @@ function describeSuite(options) {
1176
1236
  runner.it.skip(`${conformanceCase.name} — skipped: ${reason}`, async () => {});
1177
1237
  continue;
1178
1238
  }
1179
- if (conformanceCase.needs === "faults" && options.faults === false) {
1239
+ if (needsList(conformanceCase).includes("faults") && options.faults === false) {
1180
1240
  runner.it.skip(`${conformanceCase.name} — skipped: ${SKIP_REASONS.faults}`, async () => {});
1181
1241
  continue;
1182
1242
  }
@@ -1249,11 +1309,11 @@ var tuple = (object, relation, subject) => ({ object, relation, subject });
1249
1309
  var sorted = (subjects) => subjects.map(formatSubject2).sort();
1250
1310
 
1251
1311
  // src/conformance/relations/cases/deletion.ts
1252
- var group19 = "relations";
1312
+ var group20 = "relations";
1253
1313
  var relationDeletionCases = [
1254
1314
  {
1255
1315
  id: "relations.deleteEntity",
1256
- group: group19,
1316
+ group: group20,
1257
1317
  name: "deletes every tuple naming an entity — as object, as subject, as a set’s entity — and counts them",
1258
1318
  async run({ store }) {
1259
1319
  const team = entity("team");
@@ -1280,11 +1340,11 @@ var relationDeletionCases = [
1280
1340
  ];
1281
1341
 
1282
1342
  // src/conformance/relations/cases/reads.ts
1283
- var group20 = "relations";
1343
+ var group21 = "relations";
1284
1344
  var relationReadCases = [
1285
1345
  {
1286
1346
  id: "relations.roundTrip",
1287
- group: group20,
1347
+ group: group21,
1288
1348
  name: "holds exactly the tuples written, byte for byte: staff:u1 is not patient:u1, and a set is not its entity",
1289
1349
  async run({ store }) {
1290
1350
  const record = entity("record", "R:1 — ÿ");
@@ -1300,7 +1360,7 @@ var relationReadCases = [
1300
1360
  },
1301
1361
  {
1302
1362
  id: "relations.edgeCharacters",
1303
- group: group20,
1363
+ group: group21,
1304
1364
  name: "round-trips every character the core lets through in an id: control characters, U+FFFF and a surrogate pair",
1305
1365
  async run({ store }) {
1306
1366
  const edge = "a\x01\x1F￿ \uD83D\uDE00 z";
@@ -1323,7 +1383,7 @@ var relationReadCases = [
1323
1383
  },
1324
1384
  {
1325
1385
  id: "relations.undefinedRelation",
1326
- group: group20,
1386
+ group: group21,
1327
1387
  name: "reads a subject whose relation is undefined as its entity, not as a set",
1328
1388
  async run({ store }) {
1329
1389
  const record = entity("record");
@@ -1353,7 +1413,7 @@ var relationReadCases = [
1353
1413
  },
1354
1414
  {
1355
1415
  id: "relations.absence",
1356
- group: group20,
1416
+ group: group21,
1357
1417
  name: "answers false, [], an empty page and 0 for an empty store — never null or undefined",
1358
1418
  async run({ store }) {
1359
1419
  const record = entity("record");
@@ -1374,11 +1434,11 @@ var relationReadCases = [
1374
1434
  ];
1375
1435
 
1376
1436
  // src/conformance/relations/cases/traversal.ts
1377
- var group21 = "relations";
1437
+ var group22 = "relations";
1378
1438
  var relationTraversalCases = [
1379
1439
  {
1380
1440
  id: "relations.oneHop",
1381
- group: group21,
1441
+ group: group22,
1382
1442
  name: "answers one hop: the subject sets and the entities holding one relation on one object, and nothing else",
1383
1443
  async run({ store }) {
1384
1444
  const record = entity("record");
@@ -1402,7 +1462,7 @@ var relationTraversalCases = [
1402
1462
  },
1403
1463
  {
1404
1464
  id: "relations.objects",
1405
- group: group21,
1465
+ group: group22,
1406
1466
  name: "pages 25 objects by 10 in ascending id order, for one type, relation and exact subject, and ends on a null cursor",
1407
1467
  async run({ store }) {
1408
1468
  const staff = entity("staff");
@@ -1449,11 +1509,11 @@ var relationTraversalCases = [
1449
1509
  ];
1450
1510
 
1451
1511
  // src/conformance/relations/cases/writes.ts
1452
- var group22 = "relations";
1512
+ var group23 = "relations";
1453
1513
  var relationWriteCases = [
1454
1514
  {
1455
1515
  id: "relations.idempotentWrite",
1456
- group: group22,
1516
+ group: group23,
1457
1517
  name: "is idempotent under retry: adding a stored tuple and removing an absent one write nothing",
1458
1518
  async run({ store }) {
1459
1519
  const record = entity("record");
@@ -1466,7 +1526,7 @@ var relationWriteCases = [
1466
1526
  },
1467
1527
  {
1468
1528
  id: "relations.uniqueness",
1469
- group: group22,
1529
+ group: group23,
1470
1530
  name: "keeps one tuple when twenty concurrent writes add it: uniqueness is a constraint, not a read",
1471
1531
  async run({ store }) {
1472
1532
  const record = entity("record");
@@ -1477,7 +1537,7 @@ var relationWriteCases = [
1477
1537
  },
1478
1538
  {
1479
1539
  id: "relations.removeThenAdd",
1480
- group: group22,
1540
+ group: group23,
1481
1541
  name: "applies one write’s removals and additions together",
1482
1542
  async run({ store }) {
1483
1543
  const record = entity("record");
@@ -1612,5 +1672,5 @@ export {
1612
1672
  userStoreCases
1613
1673
  };
1614
1674
 
1615
- //# debugId=292F9396C913757C64756E2164756E21
1675
+ //# debugId=E36CAD1FA03131AF64756E2164756E21
1616
1676
  //# sourceMappingURL=index.js.map