@nxgt/janus 0.11.0 → 0.13.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 +109 -15
- package/dist/auth/config/janus-config.d.ts +2 -0
- package/dist/auth/config/janus-config.d.ts.map +1 -1
- package/dist/auth/config/resolved-config.d.ts +1 -0
- package/dist/auth/config/resolved-config.d.ts.map +1 -1
- package/dist/auth/email-flows/reset-password.d.ts.map +1 -1
- package/dist/auth/index.d.ts +2 -1
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/password-written.d.ts +21 -5
- package/dist/auth/password-written.d.ts.map +1 -1
- package/dist/auth/port/assert-stores.d.ts.map +1 -1
- package/dist/auth/port/memory/sessions.d.ts.map +1 -1
- package/dist/auth/port/types/sessions.d.ts +16 -1
- package/dist/auth/port/types/sessions.d.ts.map +1 -1
- package/dist/auth/port/types/tokens.d.ts +10 -4
- package/dist/auth/port/types/tokens.d.ts.map +1 -1
- package/dist/auth/second-factor/{regenerate-attempts.d.ts → app-code-attempts.d.ts} +13 -10
- package/dist/auth/second-factor/app-code-attempts.d.ts.map +1 -0
- package/dist/auth/sessions/authenticate.d.ts.map +1 -1
- package/dist/auth/sessions/fresh.d.ts +26 -0
- package/dist/auth/sessions/fresh.d.ts.map +1 -0
- package/dist/auth/sessions/standing.d.ts +20 -0
- package/dist/auth/sessions/standing.d.ts.map +1 -0
- package/dist/auth/step-up/codes.d.ts +29 -0
- package/dist/auth/step-up/codes.d.ts.map +1 -0
- package/dist/auth/step-up/flows.d.ts +19 -0
- package/dist/auth/step-up/flows.d.ts.map +1 -0
- package/dist/auth/step-up/open.d.ts +23 -0
- package/dist/auth/step-up/open.d.ts.map +1 -0
- package/dist/auth/step-up/request.d.ts +12 -0
- package/dist/auth/step-up/request.d.ts.map +1 -0
- package/dist/auth/types/email-flows.d.ts +6 -3
- package/dist/auth/types/email-flows.d.ts.map +1 -1
- package/dist/auth/types/index.d.ts +1 -0
- package/dist/auth/types/index.d.ts.map +1 -1
- package/dist/auth/types/janus.d.ts +2 -1
- package/dist/auth/types/janus.d.ts.map +1 -1
- package/dist/auth/types/step-up.d.ts +79 -0
- package/dist/auth/types/step-up.d.ts.map +1 -0
- package/dist/auth/users/any-type-api.d.ts +2 -2
- package/dist/auth/users/any-type-api.d.ts.map +1 -1
- package/dist/auth/users/type-api.d.ts +1 -1
- package/dist/auth/users/type-api.d.ts.map +1 -1
- package/dist/chunks/{index-5e2fw7he.js → index-j2gd8fry.js} +12 -3
- package/dist/chunks/{index-5e2fw7he.js.map → index-j2gd8fry.js.map} +4 -4
- package/dist/chunks/{index-5tm04t5p.js → index-jp2kka6d.js} +2 -2
- package/dist/chunks/{index-6f6wja93.js → index-km7ffctx.js} +2 -2
- package/dist/chunks/{index-4m49cye4.js → index-rd2rddvv.js} +15 -12
- package/dist/chunks/{index-4m49cye4.js.map → index-rd2rddvv.js.map} +3 -3
- package/dist/conformance/cases/outage.d.ts.map +1 -1
- package/dist/conformance/cases/sessions/index.d.ts.map +1 -1
- package/dist/conformance/cases/sessions/reauthentication.d.ts +4 -0
- package/dist/conformance/cases/sessions/reauthentication.d.ts.map +1 -0
- package/dist/conformance/cases/tokens/index.d.ts.map +1 -1
- package/dist/conformance/cases/tokens/kinds.d.ts +4 -0
- package/dist/conformance/cases/tokens/kinds.d.ts.map +1 -0
- package/dist/conformance/index.js +147 -60
- package/dist/conformance/index.js.map +8 -6
- package/dist/errors/janus-error/codes.d.ts +7 -0
- package/dist/errors/janus-error/codes.d.ts.map +1 -1
- package/dist/errors/janus-error/index.d.ts +1 -1
- package/dist/errors/janus-error/index.d.ts.map +1 -1
- package/dist/errors/janus-error/refusals.d.ts +8 -0
- package/dist/errors/janus-error/refusals.d.ts.map +1 -1
- package/dist/errors/janus-error/status.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +185 -23
- package/dist/index.js.map +20 -14
- package/dist/permissions/index.js +3 -3
- package/docs/README.md +2 -1
- package/docs/guide/adapters.md +41 -6
- package/docs/guide/email-flows.md +40 -1
- package/docs/guide/errors.md +1 -0
- package/docs/guide/events.md +1 -1
- package/docs/guide/passwords.md +22 -0
- package/docs/guide/second-factor.md +22 -16
- package/docs/guide/sessions.md +7 -0
- package/docs/guide/step-up.md +178 -0
- package/docs/guide/users.md +8 -3
- package/docs/guide/vocabulary.md +3 -2
- package/docs/roadmap.md +19 -24
- package/docs/troubleshooting.md +193 -8
- package/package.json +1 -1
- package/dist/auth/second-factor/regenerate-attempts.d.ts.map +0 -1
- /package/dist/chunks/{index-5tm04t5p.js.map → index-jp2kka6d.js.map} +0 -0
- /package/dist/chunks/{index-6f6wja93.js.map → index-km7ffctx.js.map} +0 -0
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
2
|
mintId2,
|
|
3
3
|
createMemoryStores2
|
|
4
|
-
} from "../chunks/index-
|
|
4
|
+
} from "../chunks/index-rd2rddvv.js";
|
|
5
5
|
import {
|
|
6
6
|
JanusError2,
|
|
7
7
|
StoreFailure2,
|
|
8
8
|
StoreConflict2,
|
|
9
9
|
NotFoundError2,
|
|
10
10
|
formatSubject2
|
|
11
|
-
} from "../chunks/index-
|
|
11
|
+
} from "../chunks/index-j2gd8fry.js";
|
|
12
12
|
import {
|
|
13
13
|
createMemoryRelations2
|
|
14
|
-
} from "../chunks/index-
|
|
14
|
+
} from "../chunks/index-km7ffctx.js";
|
|
15
15
|
|
|
16
16
|
// src/conformance/assert.ts
|
|
17
17
|
function equal(actual, expected, what) {
|
|
@@ -184,6 +184,7 @@ var outageCases = [
|
|
|
184
184
|
outage("users", "listUsers", ({ stores }) => stores.users.listUsers({ type: user.type, after: null, limit: 10 }), seed),
|
|
185
185
|
outage("sessions", "findSessionByTokenHash", ({ stores }) => stores.sessions.findSessionByTokenHash(session.tokenHash), seed),
|
|
186
186
|
outage("sessions", "extendSession", ({ stores }) => stores.sessions.extendSession(session.id, at("2099-06-01T00:00:00.000Z")), seed),
|
|
187
|
+
outage("sessions", "reauthenticateSession", ({ stores }) => stores.sessions.reauthenticateSession(session.id, now), seed),
|
|
187
188
|
outage("sessions", "revokeSession", ({ stores }) => stores.sessions.revokeSession(session.id, now), seed),
|
|
188
189
|
outage("sessions", "revokeUserSessions", ({ stores }) => stores.sessions.revokeUserSessions(user.id, now), seed),
|
|
189
190
|
outage("tokens", "consumeToken", ({ stores }) => stores.tokens.consumeToken(token.tokenHash, "resetPassword", now), seed),
|
|
@@ -320,12 +321,53 @@ var sessionReadCases = [
|
|
|
320
321
|
}
|
|
321
322
|
];
|
|
322
323
|
|
|
323
|
-
// src/conformance/cases/sessions/
|
|
324
|
+
// src/conformance/cases/sessions/reauthentication.ts
|
|
324
325
|
var group5 = "sessions";
|
|
326
|
+
var sessionReauthenticationCases = [
|
|
327
|
+
{
|
|
328
|
+
id: "sessions.reauthenticate",
|
|
329
|
+
group: group5,
|
|
330
|
+
name: "moves authenticatedAt on a standing session, and never brings back a revoked one",
|
|
331
|
+
async run({ stores }) {
|
|
332
|
+
const standing = sessionRecord();
|
|
333
|
+
const revoked = sessionRecord({
|
|
334
|
+
revokedAt: at("2026-01-02T00:00:00.000Z")
|
|
335
|
+
});
|
|
336
|
+
await stores.sessions.insertSession(standing);
|
|
337
|
+
await stores.sessions.insertSession(revoked);
|
|
338
|
+
const now = at("2026-02-01T00:00:00.000Z");
|
|
339
|
+
equal(await stores.sessions.reauthenticateSession(standing.id, now), { ...standing, authenticatedAt: now }, "reauthenticateSession should answer the session with its new authenticatedAt, and every other field as it was");
|
|
340
|
+
equal(await stores.sessions.findSessionByTokenHash(standing.tokenHash), { ...standing, authenticatedAt: now }, "reauthenticateSession should have written authenticatedAt, and read back the same");
|
|
341
|
+
isNull(await stores.sessions.reauthenticateSession(revoked.id, now), "reauthenticateSession on a revoked session — a confirmation racing a revocation must never bring it back");
|
|
342
|
+
equal(await stores.sessions.findSessionByTokenHash(revoked.tokenHash), revoked, "reauthenticateSession refused on a revoked session should have written nothing");
|
|
343
|
+
isNull(await stores.sessions.reauthenticateSession(mintId2(), now), "reauthenticateSession on an unknown session");
|
|
344
|
+
}
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
id: "sessions.reauthenticateRace",
|
|
348
|
+
group: group5,
|
|
349
|
+
name: "never brings back a session whose revocation races its confirmation",
|
|
350
|
+
async run({ stores }) {
|
|
351
|
+
const now = at("2026-02-01T00:00:00.000Z");
|
|
352
|
+
for (let round = 0;round < 10; round += 1) {
|
|
353
|
+
const record = sessionRecord();
|
|
354
|
+
await stores.sessions.insertSession(record);
|
|
355
|
+
await Promise.all([
|
|
356
|
+
stores.sessions.reauthenticateSession(record.id, now),
|
|
357
|
+
stores.sessions.revokeSession(record.id, now)
|
|
358
|
+
]);
|
|
359
|
+
equal((await stores.sessions.findSessionByTokenHash(record.tokenHash))?.revokedAt, now, "reauthenticateSession racing revokeSession: the session should stay revoked — one conditional write, never a read then a write");
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
];
|
|
364
|
+
|
|
365
|
+
// src/conformance/cases/sessions/revocation.ts
|
|
366
|
+
var group6 = "sessions";
|
|
325
367
|
var sessionRevocationCases = [
|
|
326
368
|
{
|
|
327
369
|
id: "sessions.revoke",
|
|
328
|
-
group:
|
|
370
|
+
group: group6,
|
|
329
371
|
name: "revokes once and keeps the first revokedAt; answers false only when there is no session",
|
|
330
372
|
async run({ stores }) {
|
|
331
373
|
const record = sessionRecord();
|
|
@@ -339,7 +381,7 @@ var sessionRevocationCases = [
|
|
|
339
381
|
},
|
|
340
382
|
{
|
|
341
383
|
id: "sessions.revokeUser",
|
|
342
|
-
group:
|
|
384
|
+
group: group6,
|
|
343
385
|
name: "revokes every standing session of one user but the one excepted, and counts only what it revoked",
|
|
344
386
|
async run({ stores }) {
|
|
345
387
|
const userId = mintId2();
|
|
@@ -369,15 +411,16 @@ var sessionStoreCases = [
|
|
|
369
411
|
...sessionReadCases,
|
|
370
412
|
...sessionInsertCases,
|
|
371
413
|
...sessionExtensionCases,
|
|
414
|
+
...sessionReauthenticationCases,
|
|
372
415
|
...sessionRevocationCases,
|
|
373
416
|
...sessionDeletionCases
|
|
374
417
|
];
|
|
375
418
|
// src/conformance/cases/tokens/attempts.ts
|
|
376
|
-
var
|
|
419
|
+
var group7 = "tokens";
|
|
377
420
|
var tokenAttemptCases = [
|
|
378
421
|
{
|
|
379
422
|
id: "tokens.countAttempt",
|
|
380
|
-
group:
|
|
423
|
+
group: group7,
|
|
381
424
|
name: "counts an attempt at a code, and answers the token as it is after the call",
|
|
382
425
|
async run({ stores }) {
|
|
383
426
|
const record = tokenRecord({
|
|
@@ -392,7 +435,7 @@ var tokenAttemptCases = [
|
|
|
392
435
|
},
|
|
393
436
|
{
|
|
394
437
|
id: "tokens.countAttemptConcurrency",
|
|
395
|
-
group:
|
|
438
|
+
group: group7,
|
|
396
439
|
name: "gives twenty concurrent attempts twenty distinct counts — one conditional write, never a read then a write",
|
|
397
440
|
async run({ stores }) {
|
|
398
441
|
const record = tokenRecord({ kind: "secondFactor" });
|
|
@@ -403,7 +446,7 @@ var tokenAttemptCases = [
|
|
|
403
446
|
},
|
|
404
447
|
{
|
|
405
448
|
id: "tokens.countAttemptRace",
|
|
406
|
-
group:
|
|
449
|
+
group: group7,
|
|
407
450
|
name: "never counts an attempt once the token is spent, when attempts and a redemption race",
|
|
408
451
|
async run({ stores }) {
|
|
409
452
|
const record = tokenRecord({ kind: "signInCode" });
|
|
@@ -426,7 +469,7 @@ var tokenAttemptCases = [
|
|
|
426
469
|
},
|
|
427
470
|
{
|
|
428
471
|
id: "tokens.countAttemptSpent",
|
|
429
|
-
group:
|
|
472
|
+
group: group7,
|
|
430
473
|
name: "does not count an attempt at a spent token, nor at a token of another kind, nor at none",
|
|
431
474
|
async run({ stores }) {
|
|
432
475
|
const spent = tokenRecord({
|
|
@@ -448,11 +491,11 @@ var tokenAttemptCases = [
|
|
|
448
491
|
];
|
|
449
492
|
|
|
450
493
|
// src/conformance/cases/tokens/deletion.ts
|
|
451
|
-
var
|
|
494
|
+
var group8 = "tokens";
|
|
452
495
|
var tokenDeletionCases = [
|
|
453
496
|
{
|
|
454
497
|
id: "tokens.deleteUser",
|
|
455
|
-
group:
|
|
498
|
+
group: group8,
|
|
456
499
|
name: "deletes every token of one user, spent or not, of either kind, and counts them",
|
|
457
500
|
async run({ stores }) {
|
|
458
501
|
const userId = mintId2();
|
|
@@ -477,11 +520,11 @@ var tokenDeletionCases = [
|
|
|
477
520
|
];
|
|
478
521
|
|
|
479
522
|
// src/conformance/cases/tokens/inserts.ts
|
|
480
|
-
var
|
|
523
|
+
var group9 = "tokens";
|
|
481
524
|
var tokenInsertCases = [
|
|
482
525
|
{
|
|
483
526
|
id: "tokens.challenge",
|
|
484
|
-
group:
|
|
527
|
+
group: group9,
|
|
485
528
|
name: "keeps a second-factor challenge, whose address is '' — nothing was sent for it",
|
|
486
529
|
async run({ stores }) {
|
|
487
530
|
const record = tokenRecord({ kind: "secondFactor", address: "" });
|
|
@@ -492,7 +535,7 @@ var tokenInsertCases = [
|
|
|
492
535
|
},
|
|
493
536
|
{
|
|
494
537
|
id: "tokens.lapsed",
|
|
495
|
-
group:
|
|
538
|
+
group: group9,
|
|
496
539
|
name: "spends a lapsed token all the same: expiry is compared by the core, after the call",
|
|
497
540
|
async run({ stores }) {
|
|
498
541
|
const record = tokenRecord({ expiresAt: at("2020-01-01T00:00:00.000Z") });
|
|
@@ -507,7 +550,7 @@ var tokenInsertCases = [
|
|
|
507
550
|
},
|
|
508
551
|
{
|
|
509
552
|
id: "tokens.idempotentInsert",
|
|
510
|
-
group:
|
|
553
|
+
group: group9,
|
|
511
554
|
name: "is idempotent under retry: inserting an existing token hash writes nothing",
|
|
512
555
|
async run({ stores }) {
|
|
513
556
|
const record = tokenRecord();
|
|
@@ -521,12 +564,55 @@ var tokenInsertCases = [
|
|
|
521
564
|
}
|
|
522
565
|
];
|
|
523
566
|
|
|
567
|
+
// src/conformance/cases/tokens/kinds.ts
|
|
568
|
+
var group10 = "tokens";
|
|
569
|
+
var KINDS = Object.keys({
|
|
570
|
+
verifyEmail: true,
|
|
571
|
+
resetPassword: true,
|
|
572
|
+
secondFactor: true,
|
|
573
|
+
signInCode: true,
|
|
574
|
+
stepUp: true
|
|
575
|
+
});
|
|
576
|
+
var tokenKindCases = [
|
|
577
|
+
{
|
|
578
|
+
id: "tokens.everyKind",
|
|
579
|
+
group: group10,
|
|
580
|
+
name: "stores, counts and spends a token of every kind the port names",
|
|
581
|
+
async run({ stores }) {
|
|
582
|
+
const now = at("2026-02-01T00:00:00.000Z");
|
|
583
|
+
for (const kind of KINDS) {
|
|
584
|
+
const record = tokenRecord({ kind, codeHash: "c".repeat(64) });
|
|
585
|
+
await stores.tokens.insertToken(record);
|
|
586
|
+
equal(await stores.tokens.countAttempt(record.tokenHash, kind), { ...record, attempts: 1 }, `countAttempt on a ${kind} token should answer it with one attempt`);
|
|
587
|
+
equal(await stores.tokens.consumeToken(record.tokenHash, kind, now), { ...record, attempts: 1 }, `consumeToken on a ${kind} token should answer it as it was — spentAt null`);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
},
|
|
591
|
+
{
|
|
592
|
+
id: "tokens.stepUpKind",
|
|
593
|
+
group: group10,
|
|
594
|
+
name: "keeps a step-up apart from a sign-in code: neither is counted, spent nor answered as the other",
|
|
595
|
+
async run({ stores }) {
|
|
596
|
+
const userId = mintId2();
|
|
597
|
+
const stepUp = tokenRecord({ userId, kind: "stepUp" });
|
|
598
|
+
const signInCode = tokenRecord({ userId, kind: "signInCode" });
|
|
599
|
+
await stores.tokens.insertToken(stepUp);
|
|
600
|
+
await stores.tokens.insertToken(signInCode);
|
|
601
|
+
const now = at("2026-02-01T00:00:00.000Z");
|
|
602
|
+
isNull(await stores.tokens.countAttempt(stepUp.tokenHash, "signInCode"), "countAttempt for a stepUp token counted as signInCode");
|
|
603
|
+
isNull(await stores.tokens.consumeToken(signInCode.tokenHash, "stepUp", now), "consumeToken for a signInCode token redeemed as stepUp");
|
|
604
|
+
equal(await stores.tokens.spendUserTokens(userId, "stepUp", now), 1, "spendUserTokens of stepUp: the step-up only");
|
|
605
|
+
equal(await stores.tokens.consumeToken(signInCode.tokenHash, "signInCode", now), signInCode, "spendUserTokens of stepUp should not touch a sign-in code, nor anything else have counted it");
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
];
|
|
609
|
+
|
|
524
610
|
// src/conformance/cases/tokens/redemption.ts
|
|
525
|
-
var
|
|
611
|
+
var group11 = "tokens";
|
|
526
612
|
var tokenRedemptionCases = [
|
|
527
613
|
{
|
|
528
614
|
id: "tokens.consume",
|
|
529
|
-
group:
|
|
615
|
+
group: group11,
|
|
530
616
|
name: "answers the token as it was before the call: spentAt null means this call spent it",
|
|
531
617
|
async run({ stores }) {
|
|
532
618
|
const record = tokenRecord();
|
|
@@ -538,7 +624,7 @@ var tokenRedemptionCases = [
|
|
|
538
624
|
},
|
|
539
625
|
{
|
|
540
626
|
id: "tokens.concurrency",
|
|
541
|
-
group:
|
|
627
|
+
group: group11,
|
|
542
628
|
name: "lets exactly one of twenty concurrent redemptions spend a token — one conditional write, never a read then a write",
|
|
543
629
|
async run({ stores }) {
|
|
544
630
|
const record = tokenRecord();
|
|
@@ -550,7 +636,7 @@ var tokenRedemptionCases = [
|
|
|
550
636
|
},
|
|
551
637
|
{
|
|
552
638
|
id: "tokens.kind",
|
|
553
|
-
group:
|
|
639
|
+
group: group11,
|
|
554
640
|
name: "does not know, and does not spend, a token of the other kind",
|
|
555
641
|
async run({ stores }) {
|
|
556
642
|
const record = tokenRecord({ kind: "verifyEmail" });
|
|
@@ -564,11 +650,11 @@ var tokenRedemptionCases = [
|
|
|
564
650
|
];
|
|
565
651
|
|
|
566
652
|
// src/conformance/cases/tokens/spending.ts
|
|
567
|
-
var
|
|
653
|
+
var group12 = "tokens";
|
|
568
654
|
var tokenSpendingCases = [
|
|
569
655
|
{
|
|
570
656
|
id: "tokens.spendUserTokens",
|
|
571
|
-
group:
|
|
657
|
+
group: group12,
|
|
572
658
|
name: "spends every unspent token of one user and one kind, and counts them, leaving the rest as they were",
|
|
573
659
|
async run({ stores }) {
|
|
574
660
|
const userId = mintId2();
|
|
@@ -599,7 +685,7 @@ var tokenSpendingCases = [
|
|
|
599
685
|
},
|
|
600
686
|
{
|
|
601
687
|
id: "tokens.spendUserTokensExcept",
|
|
602
|
-
group:
|
|
688
|
+
group: group12,
|
|
603
689
|
name: "spares the token named by except, and spends the rest",
|
|
604
690
|
async run({ stores }) {
|
|
605
691
|
const userId = mintId2();
|
|
@@ -621,7 +707,7 @@ var tokenSpendingCases = [
|
|
|
621
707
|
},
|
|
622
708
|
{
|
|
623
709
|
id: "tokens.spendUserTokensRace",
|
|
624
|
-
group:
|
|
710
|
+
group: group12,
|
|
625
711
|
name: "spends a token once, when spending a user's tokens races a redemption",
|
|
626
712
|
async run({ stores }) {
|
|
627
713
|
const now = at("2026-02-01T00:00:00.000Z");
|
|
@@ -644,14 +730,15 @@ var tokenStoreCases = [
|
|
|
644
730
|
...tokenAttemptCases,
|
|
645
731
|
...tokenSpendingCases,
|
|
646
732
|
...tokenInsertCases,
|
|
733
|
+
...tokenKindCases,
|
|
647
734
|
...tokenDeletionCases
|
|
648
735
|
];
|
|
649
736
|
// src/conformance/cases/users/deletion.ts
|
|
650
|
-
var
|
|
737
|
+
var group13 = "users";
|
|
651
738
|
var userDeletionCases = [
|
|
652
739
|
{
|
|
653
740
|
id: "users.delete",
|
|
654
|
-
group:
|
|
741
|
+
group: group13,
|
|
655
742
|
name: "deletes a user and frees their logins, answers false for nobody, and touches no other user",
|
|
656
743
|
async run({ stores }) {
|
|
657
744
|
const record = userRecord();
|
|
@@ -671,11 +758,11 @@ var userDeletionCases = [
|
|
|
671
758
|
];
|
|
672
759
|
|
|
673
760
|
// src/conformance/cases/users/inserts.ts
|
|
674
|
-
var
|
|
761
|
+
var group14 = "users";
|
|
675
762
|
var userInsertCases = [
|
|
676
763
|
{
|
|
677
764
|
id: "users.idempotentInsert",
|
|
678
|
-
group:
|
|
765
|
+
group: group14,
|
|
679
766
|
name: "is idempotent under retry: inserting an existing id answers the stored record and writes nothing",
|
|
680
767
|
async run({ stores }) {
|
|
681
768
|
const record = userRecord();
|
|
@@ -690,7 +777,7 @@ var userInsertCases = [
|
|
|
690
777
|
},
|
|
691
778
|
{
|
|
692
779
|
id: "users.uniqueness",
|
|
693
|
-
group:
|
|
780
|
+
group: group14,
|
|
694
781
|
name: "lets exactly one of twenty concurrent inserts hold a login: uniqueness is a constraint, not a read",
|
|
695
782
|
async run({ stores }) {
|
|
696
783
|
const records = Array.from({ length: 20 }, () => userRecord({ logins: ["contested@example.test"] }));
|
|
@@ -715,11 +802,11 @@ var userInsertCases = [
|
|
|
715
802
|
];
|
|
716
803
|
|
|
717
804
|
// src/conformance/cases/users/listing.ts
|
|
718
|
-
var
|
|
805
|
+
var group15 = "users";
|
|
719
806
|
var userListingCases = [
|
|
720
807
|
{
|
|
721
808
|
id: "users.pagination",
|
|
722
|
-
group:
|
|
809
|
+
group: group15,
|
|
723
810
|
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",
|
|
724
811
|
async run({ stores }) {
|
|
725
812
|
const records = Array.from({ length: 25 }, () => userRecord());
|
|
@@ -820,11 +907,11 @@ async function nullClearsCodes(stores, id) {
|
|
|
820
907
|
}
|
|
821
908
|
|
|
822
909
|
// src/conformance/cases/users/patches.ts
|
|
823
|
-
var
|
|
910
|
+
var group16 = "users";
|
|
824
911
|
var userPatchCases = [
|
|
825
912
|
{
|
|
826
913
|
id: "users.omission",
|
|
827
|
-
group:
|
|
914
|
+
group: group16,
|
|
828
915
|
name: "omission — the Kratos PUT trap: an update leaves every field it does not name exactly as it was",
|
|
829
916
|
async run({ stores }) {
|
|
830
917
|
const record = userRecord({
|
|
@@ -849,7 +936,7 @@ var userPatchCases = [
|
|
|
849
936
|
},
|
|
850
937
|
{
|
|
851
938
|
id: "users.passwordSlot",
|
|
852
|
-
group:
|
|
939
|
+
group: group16,
|
|
853
940
|
name: "keeps a password the patch does not name, and removes one the patch names null",
|
|
854
941
|
async run({ stores }) {
|
|
855
942
|
const record = userRecord();
|
|
@@ -863,7 +950,7 @@ var userPatchCases = [
|
|
|
863
950
|
secondFactorSlotCase,
|
|
864
951
|
{
|
|
865
952
|
id: "users.loginsMove",
|
|
866
|
-
group:
|
|
953
|
+
group: group16,
|
|
867
954
|
name: "moves logins on update: the old one is free, the new one found, one held elsewhere refused",
|
|
868
955
|
async run({ stores }) {
|
|
869
956
|
const record = userRecord();
|
|
@@ -884,11 +971,11 @@ var userPatchCases = [
|
|
|
884
971
|
];
|
|
885
972
|
|
|
886
973
|
// src/conformance/cases/users/reads.ts
|
|
887
|
-
var
|
|
974
|
+
var group17 = "users";
|
|
888
975
|
var userReadCases = [
|
|
889
976
|
{
|
|
890
977
|
id: "users.roundTrip",
|
|
891
|
-
group:
|
|
978
|
+
group: group17,
|
|
892
979
|
name: 'round-trips a record byte for byte: nested fields, a 1 that stays a number, a "1" that stays a string',
|
|
893
980
|
async run({ stores }) {
|
|
894
981
|
const record = userRecord({
|
|
@@ -912,7 +999,7 @@ var userReadCases = [
|
|
|
912
999
|
},
|
|
913
1000
|
{
|
|
914
1001
|
id: "users.edgeCharacters",
|
|
915
|
-
group:
|
|
1002
|
+
group: group17,
|
|
916
1003
|
name: "round-trips every character the core lets through: control characters, U+FFFF and a surrogate pair",
|
|
917
1004
|
async run({ stores }) {
|
|
918
1005
|
const edge = "a\x01\x1F \uD83D\uDE00 z";
|
|
@@ -927,7 +1014,7 @@ var userReadCases = [
|
|
|
927
1014
|
},
|
|
928
1015
|
{
|
|
929
1016
|
id: "users.absence",
|
|
930
|
-
group:
|
|
1017
|
+
group: group17,
|
|
931
1018
|
name: "answers null for an absence, never undefined, and an empty page for an empty store",
|
|
932
1019
|
async run({ stores }) {
|
|
933
1020
|
isNull(await stores.users.findUser(mintId2()), "findUser for an unknown id");
|
|
@@ -937,7 +1024,7 @@ var userReadCases = [
|
|
|
937
1024
|
},
|
|
938
1025
|
{
|
|
939
1026
|
id: "users.loginBytes",
|
|
940
|
-
group:
|
|
1027
|
+
group: group17,
|
|
941
1028
|
name: "compares logins as bytes, within one type: no case folding, no trimming, no collation",
|
|
942
1029
|
async run({ stores }) {
|
|
943
1030
|
const upper = userRecord({ logins: ["Ada@Example.test"] });
|
|
@@ -954,11 +1041,11 @@ var userReadCases = [
|
|
|
954
1041
|
];
|
|
955
1042
|
|
|
956
1043
|
// src/conformance/cases/users/versions.ts
|
|
957
|
-
var
|
|
1044
|
+
var group18 = "users";
|
|
958
1045
|
var userVersionCases = [
|
|
959
1046
|
{
|
|
960
1047
|
id: "users.versionIncrements",
|
|
961
|
-
group:
|
|
1048
|
+
group: group18,
|
|
962
1049
|
name: "applies an update at the expected version, and answers the record written with its version one higher",
|
|
963
1050
|
async run({ stores }) {
|
|
964
1051
|
const record = userRecord();
|
|
@@ -976,7 +1063,7 @@ var userVersionCases = [
|
|
|
976
1063
|
},
|
|
977
1064
|
{
|
|
978
1065
|
id: "users.versionConflict",
|
|
979
|
-
group:
|
|
1066
|
+
group: group18,
|
|
980
1067
|
name: "refuses a stale version with both versions, and leaves the record identical byte for byte",
|
|
981
1068
|
async run({ stores }) {
|
|
982
1069
|
const record = userRecord();
|
|
@@ -997,7 +1084,7 @@ var userVersionCases = [
|
|
|
997
1084
|
},
|
|
998
1085
|
{
|
|
999
1086
|
id: "users.unknownUpdate",
|
|
1000
|
-
group:
|
|
1087
|
+
group: group18,
|
|
1001
1088
|
name: "refuses an update to an unknown id with NotFoundError, told apart from a version conflict",
|
|
1002
1089
|
async run({ stores }) {
|
|
1003
1090
|
const error = await rejects(stores.users.updateUser(mintId2(), { updatedAt: at("2026-02-01T00:00:00.000Z") }, 0), "updateUser on an unknown id should reject");
|
|
@@ -1140,11 +1227,11 @@ var tuple = (object, relation, subject) => ({ object, relation, subject });
|
|
|
1140
1227
|
var sorted = (subjects) => subjects.map(formatSubject2).sort();
|
|
1141
1228
|
|
|
1142
1229
|
// src/conformance/relations/cases/deletion.ts
|
|
1143
|
-
var
|
|
1230
|
+
var group19 = "relations";
|
|
1144
1231
|
var relationDeletionCases = [
|
|
1145
1232
|
{
|
|
1146
1233
|
id: "relations.deleteEntity",
|
|
1147
|
-
group:
|
|
1234
|
+
group: group19,
|
|
1148
1235
|
name: "deletes every tuple naming an entity — as object, as subject, as a set’s entity — and counts them",
|
|
1149
1236
|
async run({ store }) {
|
|
1150
1237
|
const team = entity("team");
|
|
@@ -1171,11 +1258,11 @@ var relationDeletionCases = [
|
|
|
1171
1258
|
];
|
|
1172
1259
|
|
|
1173
1260
|
// src/conformance/relations/cases/reads.ts
|
|
1174
|
-
var
|
|
1261
|
+
var group20 = "relations";
|
|
1175
1262
|
var relationReadCases = [
|
|
1176
1263
|
{
|
|
1177
1264
|
id: "relations.roundTrip",
|
|
1178
|
-
group:
|
|
1265
|
+
group: group20,
|
|
1179
1266
|
name: "holds exactly the tuples written, byte for byte: staff:u1 is not patient:u1, and a set is not its entity",
|
|
1180
1267
|
async run({ store }) {
|
|
1181
1268
|
const record = entity("record", "R:1 — ÿ");
|
|
@@ -1191,7 +1278,7 @@ var relationReadCases = [
|
|
|
1191
1278
|
},
|
|
1192
1279
|
{
|
|
1193
1280
|
id: "relations.edgeCharacters",
|
|
1194
|
-
group:
|
|
1281
|
+
group: group20,
|
|
1195
1282
|
name: "round-trips every character the core lets through in an id: control characters, U+FFFF and a surrogate pair",
|
|
1196
1283
|
async run({ store }) {
|
|
1197
1284
|
const edge = "a\x01\x1F \uD83D\uDE00 z";
|
|
@@ -1214,7 +1301,7 @@ var relationReadCases = [
|
|
|
1214
1301
|
},
|
|
1215
1302
|
{
|
|
1216
1303
|
id: "relations.undefinedRelation",
|
|
1217
|
-
group:
|
|
1304
|
+
group: group20,
|
|
1218
1305
|
name: "reads a subject whose relation is undefined as its entity, not as a set",
|
|
1219
1306
|
async run({ store }) {
|
|
1220
1307
|
const record = entity("record");
|
|
@@ -1244,7 +1331,7 @@ var relationReadCases = [
|
|
|
1244
1331
|
},
|
|
1245
1332
|
{
|
|
1246
1333
|
id: "relations.absence",
|
|
1247
|
-
group:
|
|
1334
|
+
group: group20,
|
|
1248
1335
|
name: "answers false, [], an empty page and 0 for an empty store — never null or undefined",
|
|
1249
1336
|
async run({ store }) {
|
|
1250
1337
|
const record = entity("record");
|
|
@@ -1265,11 +1352,11 @@ var relationReadCases = [
|
|
|
1265
1352
|
];
|
|
1266
1353
|
|
|
1267
1354
|
// src/conformance/relations/cases/traversal.ts
|
|
1268
|
-
var
|
|
1355
|
+
var group21 = "relations";
|
|
1269
1356
|
var relationTraversalCases = [
|
|
1270
1357
|
{
|
|
1271
1358
|
id: "relations.oneHop",
|
|
1272
|
-
group:
|
|
1359
|
+
group: group21,
|
|
1273
1360
|
name: "answers one hop: the subject sets and the entities holding one relation on one object, and nothing else",
|
|
1274
1361
|
async run({ store }) {
|
|
1275
1362
|
const record = entity("record");
|
|
@@ -1293,7 +1380,7 @@ var relationTraversalCases = [
|
|
|
1293
1380
|
},
|
|
1294
1381
|
{
|
|
1295
1382
|
id: "relations.objects",
|
|
1296
|
-
group:
|
|
1383
|
+
group: group21,
|
|
1297
1384
|
name: "pages 25 objects by 10 in ascending id order, for one type, relation and exact subject, and ends on a null cursor",
|
|
1298
1385
|
async run({ store }) {
|
|
1299
1386
|
const staff = entity("staff");
|
|
@@ -1340,11 +1427,11 @@ var relationTraversalCases = [
|
|
|
1340
1427
|
];
|
|
1341
1428
|
|
|
1342
1429
|
// src/conformance/relations/cases/writes.ts
|
|
1343
|
-
var
|
|
1430
|
+
var group22 = "relations";
|
|
1344
1431
|
var relationWriteCases = [
|
|
1345
1432
|
{
|
|
1346
1433
|
id: "relations.idempotentWrite",
|
|
1347
|
-
group:
|
|
1434
|
+
group: group22,
|
|
1348
1435
|
name: "is idempotent under retry: adding a stored tuple and removing an absent one write nothing",
|
|
1349
1436
|
async run({ store }) {
|
|
1350
1437
|
const record = entity("record");
|
|
@@ -1357,7 +1444,7 @@ var relationWriteCases = [
|
|
|
1357
1444
|
},
|
|
1358
1445
|
{
|
|
1359
1446
|
id: "relations.uniqueness",
|
|
1360
|
-
group:
|
|
1447
|
+
group: group22,
|
|
1361
1448
|
name: "keeps one tuple when twenty concurrent writes add it: uniqueness is a constraint, not a read",
|
|
1362
1449
|
async run({ store }) {
|
|
1363
1450
|
const record = entity("record");
|
|
@@ -1368,7 +1455,7 @@ var relationWriteCases = [
|
|
|
1368
1455
|
},
|
|
1369
1456
|
{
|
|
1370
1457
|
id: "relations.removeThenAdd",
|
|
1371
|
-
group:
|
|
1458
|
+
group: group22,
|
|
1372
1459
|
name: "applies one write’s removals and additions together",
|
|
1373
1460
|
async run({ store }) {
|
|
1374
1461
|
const record = entity("record");
|
|
@@ -1503,5 +1590,5 @@ export {
|
|
|
1503
1590
|
userStoreCases
|
|
1504
1591
|
};
|
|
1505
1592
|
|
|
1506
|
-
//# debugId=
|
|
1593
|
+
//# debugId=B2CA4E7F432AAE2464756E2164756E21
|
|
1507
1594
|
//# sourceMappingURL=index.js.map
|