@c9up/ream 0.2.27 → 0.2.28

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 +1 -0
  2. package/dist/Ignitor.d.ts +3 -1
  3. package/dist/Ignitor.d.ts.map +1 -1
  4. package/dist/Ignitor.js +7 -0
  5. package/dist/Ignitor.js.map +1 -1
  6. package/dist/index.d.ts +3 -3
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +2 -2
  9. package/dist/index.js.map +1 -1
  10. package/dist/native/generated.d.ts +34 -0
  11. package/dist/native/generated.d.ts.map +1 -1
  12. package/dist/server/Server.d.ts +37 -0
  13. package/dist/server/Server.d.ts.map +1 -1
  14. package/dist/server/Server.js +30 -0
  15. package/dist/server/Server.js.map +1 -1
  16. package/dist/session/Session.d.ts +33 -0
  17. package/dist/session/Session.d.ts.map +1 -1
  18. package/dist/session/Session.js +31 -2
  19. package/dist/session/Session.js.map +1 -1
  20. package/dist/session/SessionMiddleware.d.ts.map +1 -1
  21. package/dist/session/SessionMiddleware.js +24 -4
  22. package/dist/session/SessionMiddleware.js.map +1 -1
  23. package/dist/session/drivers/CookieDriver.d.ts +12 -3
  24. package/dist/session/drivers/CookieDriver.d.ts.map +1 -1
  25. package/dist/session/drivers/CookieDriver.js +20 -6
  26. package/dist/session/drivers/CookieDriver.js.map +1 -1
  27. package/dist/session/drivers/DatabaseDriver.d.ts +2 -0
  28. package/dist/session/drivers/DatabaseDriver.d.ts.map +1 -1
  29. package/dist/session/drivers/DatabaseDriver.js +6 -0
  30. package/dist/session/drivers/DatabaseDriver.js.map +1 -1
  31. package/dist/session/drivers/FileDriver.d.ts +8 -0
  32. package/dist/session/drivers/FileDriver.d.ts.map +1 -1
  33. package/dist/session/drivers/FileDriver.js +85 -11
  34. package/dist/session/drivers/FileDriver.js.map +1 -1
  35. package/dist/session/drivers/MemoryDriver.d.ts +2 -0
  36. package/dist/session/drivers/MemoryDriver.d.ts.map +1 -1
  37. package/dist/session/drivers/MemoryDriver.js +9 -0
  38. package/dist/session/drivers/MemoryDriver.js.map +1 -1
  39. package/dist/session/drivers/RedisDriver.d.ts +2 -0
  40. package/dist/session/drivers/RedisDriver.d.ts.map +1 -1
  41. package/dist/session/drivers/RedisDriver.js +6 -0
  42. package/dist/session/drivers/RedisDriver.js.map +1 -1
  43. package/dist/session/errors.d.ts +10 -0
  44. package/dist/session/errors.d.ts.map +1 -1
  45. package/dist/session/errors.js +12 -0
  46. package/dist/session/errors.js.map +1 -1
  47. package/events.darwin-arm64.node +0 -0
  48. package/events.darwin-x64.node +0 -0
  49. package/events.linux-arm64-gnu.node +0 -0
  50. package/events.linux-x64-gnu.node +0 -0
  51. package/events.win32-x64-msvc.node +0 -0
  52. package/index.darwin-arm64.node +0 -0
  53. package/index.darwin-x64.node +0 -0
  54. package/index.linux-arm64-gnu.node +0 -0
  55. package/index.linux-x64-gnu.node +0 -0
  56. package/index.win32-x64-msvc.node +0 -0
  57. package/package.json +1 -1
  58. package/scheduler.darwin-arm64.node +0 -0
  59. package/scheduler.darwin-x64.node +0 -0
  60. package/scheduler.linux-arm64-gnu.node +0 -0
  61. package/scheduler.linux-x64-gnu.node +0 -0
  62. package/scheduler.win32-x64-msvc.node +0 -0
  63. package/src/Ignitor.ts +14 -1
  64. package/src/index.ts +5 -1
  65. package/src/native/generated.ts +38 -0
  66. package/src/server/Server.ts +49 -0
  67. package/src/session/Session.ts +56 -2
  68. package/src/session/SessionMiddleware.ts +23 -4
  69. package/src/session/drivers/CookieDriver.ts +21 -6
  70. package/src/session/drivers/DatabaseDriver.ts +10 -0
  71. package/src/session/drivers/FileDriver.ts +91 -11
  72. package/src/session/drivers/MemoryDriver.ts +9 -0
  73. package/src/session/drivers/RedisDriver.ts +7 -0
  74. package/src/session/errors.ts +17 -0
@@ -46,6 +46,15 @@ export class MemoryDriver {
46
46
  this.#store.delete(oldest.value);
47
47
  }
48
48
  }
49
+ /** Atomic by construction: nothing else runs between the check and the set. */
50
+ async update(sessionId, data, ttl) {
51
+ const entry = this.#store.get(sessionId);
52
+ if (!entry || entry.expiresAt < Date.now())
53
+ return false;
54
+ this.#store.delete(sessionId);
55
+ this.#store.set(sessionId, { data, expiresAt: Date.now() + ttl * 1000 });
56
+ return true;
57
+ }
49
58
  async destroy(sessionId) {
50
59
  this.#store.delete(sessionId);
51
60
  // Drop the tags too: a destroyed session left in the index would be
@@ -1 +1 @@
1
- {"version":3,"file":"MemoryDriver.js","sourceRoot":"","sources":["../../../src/session/drivers/MemoryDriver.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"MemoryDriver.js","sourceRoot":"","sources":["../../../src/session/drivers/MemoryDriver.ts"],"names":[],"mappings":"AAEA,8DAA8D;AAC9D,MAAM,eAAe,GAAG,IAAI,CAAA;AAE5B;;;;;;;;GAQG;AACH,MAAM,mBAAmB,GAAG,MAAM,CAAA;AAOlC,MAAM,OAAO,YAAY;IACvB,MAAM,GAAsE,IAAI,GAAG,EAAE,CAAA;IACrF,6CAA6C;IAC7C,KAAK,GAA6B,IAAI,GAAG,EAAE,CAAA;IAClC,WAAW,CAAQ;IAE5B,YAAY,UAA+B,EAAE;QAC3C,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,UAAU,IAAI,mBAAmB,CAAC,CAAA;IAC3E,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,SAAiB;QAC1B,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;QACxC,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;YAC3C,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;YAC7B,OAAO,IAAI,CAAA;QACb,CAAC;QACD,OAAO,EAAE,GAAG,KAAK,CAAC,IAAI,EAAE,CAAA;IAC1B,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,SAAiB,EAAE,IAA6B,EAAE,GAAW;QACvE,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,eAAe,EAAE,CAAC;YACxC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;YACtB,KAAK,MAAM,CAAC,EAAE,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;gBACtC,IAAI,KAAK,CAAC,SAAS,GAAG,GAAG;oBAAE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;YACnD,CAAC;QACH,CAAC;QACD,yEAAyE;QACzE,iDAAiD;QACjD,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;QAC7B,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG,IAAI,EAAE,CAAC,CAAA;QACxE,6EAA6E;QAC7E,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;YAC3C,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAA;YACxC,IAAI,MAAM,CAAC,IAAI;gBAAE,MAAK;YACtB,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;QAClC,CAAC;IACH,CAAC;IAED,+EAA+E;IAC/E,KAAK,CAAC,MAAM,CAAC,SAAiB,EAAE,IAA6B,EAAE,GAAW;QACxE,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;QACxC,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE;YAAE,OAAO,KAAK,CAAA;QACxD,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;QAC7B,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG,IAAI,EAAE,CAAC,CAAA;QACxE,OAAO,IAAI,CAAA;IACb,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,SAAiB;QAC7B,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;QAC7B,oEAAoE;QACpE,wCAAwC;QACxC,KAAK,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACvC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC;gBAAE,SAAQ;YACpC,IAAI,GAAG,CAAC,IAAI,KAAK,CAAC;gBAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;QAC/C,CAAC;IACH,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,SAAiB,EAAE,GAAW;QACxC,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;QACxC,IAAI,KAAK,EAAE,CAAC;YACV,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG,IAAI,CAAA;QAC3C,CAAC;IACH,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,SAAiB,EAAE,MAAuB;QAClD,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,CAAA;QAC1B,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,GAAG,EAAU,CAAA;QACpD,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;QAClB,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;IAC1B,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,SAAiB,EAAE,MAAuB;QACpD,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,CAAA;QAC1B,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAC/B,IAAI,CAAC,GAAG;YAAE,OAAM;QAChB,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;QACrB,IAAI,GAAG,CAAC,IAAI,KAAK,CAAC;YAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;IAC5C,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,MAAuB;QAClC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAA;QAC1C,IAAI,CAAC,GAAG;YAAE,OAAO,EAAE,CAAA;QACnB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QACtB,MAAM,QAAQ,GAAoB,EAAE,CAAA;QACpC,KAAK,MAAM,EAAE,IAAI,CAAC,GAAG,GAAG,CAAC,EAAE,CAAC;YAC1B,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;YACjC,wEAAwE;YACxE,uEAAuE;YACvE,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,SAAS,GAAG,GAAG,EAAE,CAAC;gBACpC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;gBACd,SAAQ;YACV,CAAC;YACD,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,GAAG,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC,CAAA;QAChD,CAAC;QACD,IAAI,GAAG,CAAC,IAAI,KAAK,CAAC;YAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAA;QACrD,OAAO,QAAQ,CAAA;IACjB,CAAC;CACF"}
@@ -36,6 +36,8 @@ export declare class RedisDriver implements SessionDriverWithTagging {
36
36
  */
37
37
  read(sessionId: string): Promise<Record<string, unknown> | null>;
38
38
  write(sessionId: string, data: Record<string, unknown>, ttl: number): Promise<void>;
39
+ /** `XX` makes the existence check and the write one command. */
40
+ update(sessionId: string, data: Record<string, unknown>, ttl: number): Promise<boolean>;
39
41
  destroy(sessionId: string): Promise<void>;
40
42
  /** Slide the expiry — a session must not die while its owner is active. */
41
43
  touch(sessionId: string, ttl: number): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"RedisDriver.d.ts","sourceRoot":"","sources":["../../../src/session/drivers/RedisDriver.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,wBAAwB,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAE5E,4EAA4E;AAC5E,MAAM,WAAW,kBAAkB;IACjC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IACxC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IACrE,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAC5C,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IACrD,iFAAiF;IACjF,IAAI,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IACzD,IAAI,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IACzD,QAAQ,CAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;IACzC,MAAM,CAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;CACtC;AAaD,uFAAuF;AACvF,MAAM,MAAM,wBAAwB,GAChC,kBAAkB,GAClB,CAAC,MAAM,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC,CAAA;AAE5D,MAAM,WAAW,mBAAmB;IAClC,kFAAkF;IAClF,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,qBAAa,WAAY,YAAW,wBAAwB;;gBAM9C,MAAM,EAAE,wBAAwB,EAAE,OAAO,GAAE,mBAAwB;IAmE/E;;;;OAIG;IACG,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAahE,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOnF,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAK/C,2EAA2E;IACrE,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKpD,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAK9D,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKhE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;CA0BhE"}
1
+ {"version":3,"file":"RedisDriver.d.ts","sourceRoot":"","sources":["../../../src/session/drivers/RedisDriver.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,wBAAwB,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAE5E,4EAA4E;AAC5E,MAAM,WAAW,kBAAkB;IACjC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IACxC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IACrE,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAC5C,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IACrD,iFAAiF;IACjF,IAAI,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IACzD,IAAI,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IACzD,QAAQ,CAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;IACzC,MAAM,CAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;CACtC;AAaD,uFAAuF;AACvF,MAAM,MAAM,wBAAwB,GAChC,kBAAkB,GAClB,CAAC,MAAM,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC,CAAA;AAE5D,MAAM,WAAW,mBAAmB;IAClC,kFAAkF;IAClF,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,qBAAa,WAAY,YAAW,wBAAwB;;gBAM9C,MAAM,EAAE,wBAAwB,EAAE,OAAO,GAAE,mBAAwB;IAmE/E;;;;OAIG;IACG,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAahE,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOzF,gEAAgE;IAC1D,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAMvF,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAK/C,2EAA2E;IACrE,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKpD,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAK9D,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKhE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;CA0BhE"}
@@ -102,6 +102,12 @@ export class RedisDriver {
102
102
  // if the process dies between the two.
103
103
  await client.set(this.#key(sessionId), JSON.stringify(data), 'EX', ttl);
104
104
  }
105
+ /** `XX` makes the existence check and the write one command. */
106
+ async update(sessionId, data, ttl) {
107
+ const client = await this.#client();
108
+ const reply = await client.set(this.#key(sessionId), JSON.stringify(data), 'EX', ttl, 'XX');
109
+ return reply !== null;
110
+ }
105
111
  async destroy(sessionId) {
106
112
  const client = await this.#client();
107
113
  await client.del(this.#key(sessionId));
@@ -1 +1 @@
1
- {"version":3,"file":"RedisDriver.js","sourceRoot":"","sources":["../../../src/session/drivers/RedisDriver.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"RedisDriver.js","sourceRoot":"","sources":["../../../src/session/drivers/RedisDriver.ts"],"names":[],"mappings":"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"}
@@ -16,6 +16,16 @@ export declare class E_SESSION_NOT_READY extends Exception {
16
16
  static code: string;
17
17
  constructor(method: string);
18
18
  }
19
+ /**
20
+ * A store with no `update()`: it cannot write a session back only while its
21
+ * entry still exists, so a request running across a logout could sign the
22
+ * user in again. Refused when the store is attached, not at the first race.
23
+ */
24
+ export declare class E_SESSION_STORE_NOT_CONDITIONAL extends Exception {
25
+ static status: number;
26
+ static code: string;
27
+ constructor();
28
+ }
19
29
  /** The configured store cannot tag sessions (cookie and file stores cannot). */
20
30
  export declare class E_SESSION_TAGGING_NOT_SUPPORTED extends Exception {
21
31
  static status: number;
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/session/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAEhD,8DAA8D;AAC9D,qBAAa,mBAAoB,SAAQ,SAAS;IAChD,OAAgB,MAAM,SAAM;IAC5B,OAAgB,IAAI,SAAwB;gBAEhC,MAAM,EAAE,MAAM;CAM3B;AAED,gFAAgF;AAChF,qBAAa,+BAAgC,SAAQ,SAAS;IAC5D,OAAgB,MAAM,SAAM;IAC5B,OAAgB,IAAI,SAAoC;gBAE5C,MAAM,EAAE,MAAM;CAM3B"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/session/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAEhD,8DAA8D;AAC9D,qBAAa,mBAAoB,SAAQ,SAAS;IAChD,OAAgB,MAAM,SAAM;IAC5B,OAAgB,IAAI,SAAwB;gBAEhC,MAAM,EAAE,MAAM;CAM3B;AAED;;;;GAIG;AACH,qBAAa,+BAAgC,SAAQ,SAAS;IAC5D,OAAgB,MAAM,SAAM;IAC5B,OAAgB,IAAI,SAAoC;;CAQzD;AAED,gFAAgF;AAChF,qBAAa,+BAAgC,SAAQ,SAAS;IAC5D,OAAgB,MAAM,SAAM;IAC5B,OAAgB,IAAI,SAAoC;gBAE5C,MAAM,EAAE,MAAM;CAM3B"}
@@ -21,6 +21,18 @@ export class E_SESSION_NOT_READY extends Exception {
21
21
  });
22
22
  }
23
23
  }
24
+ /**
25
+ * A store with no `update()`: it cannot write a session back only while its
26
+ * entry still exists, so a request running across a logout could sign the
27
+ * user in again. Refused when the store is attached, not at the first race.
28
+ */
29
+ export class E_SESSION_STORE_NOT_CONDITIONAL extends Exception {
30
+ static status = 500;
31
+ static code = 'E_SESSION_STORE_NOT_CONDITIONAL';
32
+ constructor() {
33
+ super('This session store has no update(): it must write a session only over an entry that still exists, in one step. See "Writing a session store".', { status: 500, code: 'E_SESSION_STORE_NOT_CONDITIONAL' });
34
+ }
35
+ }
24
36
  /** The configured store cannot tag sessions (cookie and file stores cannot). */
25
37
  export class E_SESSION_TAGGING_NOT_SUPPORTED extends Exception {
26
38
  static status = 500;
@@ -1 +1 @@
1
- {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/session/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAEhD,8DAA8D;AAC9D,MAAM,OAAO,mBAAoB,SAAQ,SAAS;IAChD,MAAM,CAAU,MAAM,GAAG,GAAG,CAAA;IAC5B,MAAM,CAAU,IAAI,GAAG,qBAAqB,CAAA;IAE5C,YAAY,MAAc;QACxB,KAAK,CAAC,WAAW,MAAM,gEAAgE,EAAE;YACvF,MAAM,EAAE,GAAG;YACX,IAAI,EAAE,qBAAqB;SAC5B,CAAC,CAAA;IACJ,CAAC;;AAGH,gFAAgF;AAChF,MAAM,OAAO,+BAAgC,SAAQ,SAAS;IAC5D,MAAM,CAAU,MAAM,GAAG,GAAG,CAAA;IAC5B,MAAM,CAAU,IAAI,GAAG,iCAAiC,CAAA;IAExD,YAAY,MAAc;QACxB,KAAK,CACH,WAAW,MAAM,gGAAgG,EACjH,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,iCAAiC,EAAE,CACzD,CAAA;IACH,CAAC"}
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/session/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAEhD,8DAA8D;AAC9D,MAAM,OAAO,mBAAoB,SAAQ,SAAS;IAChD,MAAM,CAAU,MAAM,GAAG,GAAG,CAAA;IAC5B,MAAM,CAAU,IAAI,GAAG,qBAAqB,CAAA;IAE5C,YAAY,MAAc;QACxB,KAAK,CAAC,WAAW,MAAM,gEAAgE,EAAE;YACvF,MAAM,EAAE,GAAG;YACX,IAAI,EAAE,qBAAqB;SAC5B,CAAC,CAAA;IACJ,CAAC;;AAGH;;;;GAIG;AACH,MAAM,OAAO,+BAAgC,SAAQ,SAAS;IAC5D,MAAM,CAAU,MAAM,GAAG,GAAG,CAAA;IAC5B,MAAM,CAAU,IAAI,GAAG,iCAAiC,CAAA;IAExD;QACE,KAAK,CACH,+IAA+I,EAC/I,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,iCAAiC,EAAE,CACzD,CAAA;IACH,CAAC;;AAGH,gFAAgF;AAChF,MAAM,OAAO,+BAAgC,SAAQ,SAAS;IAC5D,MAAM,CAAU,MAAM,GAAG,GAAG,CAAA;IAC5B,MAAM,CAAU,IAAI,GAAG,iCAAiC,CAAA;IAExD,YAAY,MAAc;QACxB,KAAK,CACH,WAAW,MAAM,gGAAgG,EACjH,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,iCAAiC,EAAE,CACzD,CAAA;IACH,CAAC"}
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@c9up/ream",
3
- "version": "0.2.27",
3
+ "version": "0.2.28",
4
4
  "description": "Ream — Rust-powered Node.js application framework",
5
5
  "license": "MIT",
6
6
  "type": "module",
Binary file
Binary file
Binary file
Binary file
Binary file
package/src/Ignitor.ts CHANGED
@@ -51,7 +51,7 @@ import {
51
51
  import { Router } from './router/Router.js'
52
52
  import { CookieSigner } from './security/CookieSigner.js'
53
53
  import { SignedUrl } from './security/SignedUrl.js'
54
- import { Server } from './server/Server.js'
54
+ import { type GrpcHandler, Server } from './server/Server.js'
55
55
  import { clearApp, setApp } from './services/app.js'
56
56
  import { clearEncryption, setEncryption } from './services/encryption.js'
57
57
  import { clearLogger, setLogger } from './services/logger.js'
@@ -268,6 +268,8 @@ export function defineConfig(config: ReamrcConfig): ReamrcConfig {
268
268
  /** Minimal interface for the HTTP server (NAPI or mock). */
269
269
  export interface HyperServerLike {
270
270
  onRequest(callback: (request: HttpKernelRequest) => Promise<HttpKernelResponse>): void
271
+ /** Optional — present on the real `HyperServer`; required once `server.grpc()` is used. */
272
+ onGrpc?(callback: GrpcHandler): void
271
273
  /**
272
274
  * Optional — present on the real `HyperServer` (Rust-backed), absent on
273
275
  * trivial test mocks. Ignitor only calls it when a shield config is set.
@@ -903,6 +905,17 @@ export class Ignitor {
903
905
  this.#config.host ?? process.env.HOST ?? (this.#app.inProduction ? '0.0.0.0' : 'localhost')
904
906
  this.#_httpServer = this.#config.serverFactory(availablePort, this.#host)
905
907
  this.#_httpServer.onRequest(kernel)
908
+ const grpc = this.#server.getGrpcHandler()
909
+ if (grpc) {
910
+ if (typeof this.#_httpServer.onGrpc !== 'function') {
911
+ throw new ReamError(
912
+ 'E_IGNITOR_GRPC_UNSUPPORTED',
913
+ 'A gRPC handler is registered, but this server cannot serve gRPC.',
914
+ { hint: 'Use the native HyperServer, or a test server that implements onGrpc().' },
915
+ )
916
+ }
917
+ this.#_httpServer.onGrpc(grpc)
918
+ }
906
919
  // Pre-resolve client IPs in Rust from the trusted-proxy CIDRs before
907
920
  // listen. Security filtering itself lives in @c9up/blackhole.
908
921
  const trustedProxies = this.#server.getTrustedProxies()
package/src/index.ts CHANGED
@@ -91,6 +91,7 @@ export type {
91
91
  export { RedisDriver as SessionRedisDriver } from './session/drivers/RedisDriver.js'
92
92
  export {
93
93
  E_SESSION_NOT_READY,
94
+ E_SESSION_STORE_NOT_CONDITIONAL,
94
95
  E_SESSION_TAGGING_NOT_SUPPORTED,
95
96
  } from './session/errors.js'
96
97
  export type { ValuePath } from './session/ReadOnlyValuesStore.js'
@@ -220,6 +221,9 @@ export { createHttpKernel } from './HttpKernel.js'
220
221
 
221
222
  export type {
222
223
  ErrorHandlerClass,
224
+ GrpcCall,
225
+ GrpcHandler,
226
+ GrpcReply,
223
227
  LazyImport,
224
228
  MiddlewareClass,
225
229
  MiddlewareEntry,
@@ -405,4 +409,4 @@ export {
405
409
  StatsTracker,
406
410
  } from './scheduler/index.js'
407
411
 
408
- export const VERSION = '0.2.27'
412
+ export const VERSION = '0.2.28'
@@ -23,6 +23,35 @@ export interface NapiResponse {
23
23
  streamId?: string
24
24
  }
25
25
 
26
+ /**
27
+ * One gRPC call, handed to the JS handler registered with `onGrpc`.
28
+ *
29
+ * The message crosses as BYTES — never through the `String` (base64 for
30
+ * binary) body of the HTTP path.
31
+ */
32
+
33
+ export interface NapiGrpcCall {
34
+ /** `/package.Service/Method`. */
35
+ path: string
36
+ /** Request metadata, header names lowercased. */
37
+ metadata: Record<string, string>
38
+ /** The request message, still encoded. */
39
+ message: Uint8Array
40
+ /** Resolved client IP, by the same trusted-proxy rules as `request.ip`. */
41
+ ip: string
42
+ }
43
+
44
+ /**
45
+ * What the gRPC handler answers: a reply `message`, or a status `code`
46
+ * (with its `details`) instead of one.
47
+ */
48
+
49
+ export interface NapiGrpcReply {
50
+ message?: Uint8Array
51
+ code?: number
52
+ details?: string
53
+ }
54
+
26
55
  /**
27
56
  * Configuration for the wire-level shield filter. Mirrors the Rust
28
57
  * `ShieldConfig` shape; the JS layer constructs it from `ShieldMiddleware`
@@ -95,6 +124,15 @@ export declare class HyperServer {
95
124
  * this comment described until the boundary stopped carrying them.
96
125
  */
97
126
  onRequest(callback: (request: Record<string, unknown>) => Promise<NapiResponse>): void
127
+ /**
128
+ * Register the gRPC handler. From `listen()` on, `application/grpc`
129
+ * requests on this port are gRPC calls handed to it; without it they stay
130
+ * HTTP requests. Must be called BEFORE `listen()`.
131
+ *
132
+ * A rejected promise is answered `INTERNAL`, with nothing of the error
133
+ * sent to the client.
134
+ */
135
+ onGrpc(callback: (arg: NapiGrpcCall) => Promise<NapiGrpcReply>): void
98
136
  /**
99
137
  * Configure the trusted-proxy CIDR list used by `request.ip` resolution.
100
138
  * Must be called BEFORE `listen()`. Empty list = legacy permissive mode
@@ -6,6 +6,7 @@
6
6
  * - server.errorHandler(() => import('#exceptions/handler')) — custom exception handler
7
7
  */
8
8
 
9
+ import { ReamError } from '../errors/ReamError.js'
9
10
  import type { ExceptionHandler } from '../http/Exception.js'
10
11
  import type { HttpContext } from '../http/HttpContext.js'
11
12
  import type { MiddlewareFunction } from '../middleware/Pipeline.js'
@@ -32,12 +33,30 @@ export type MiddlewareClassConstructor = new (...args: never[]) => MiddlewareCla
32
33
  /** What server.use() and router.use() accept: lazy imports or direct functions. */
33
34
  export type MiddlewareEntry = LazyImport<MiddlewareClassConstructor> | MiddlewareFunction
34
35
 
36
+ /** One unary gRPC call, as the native server hands it over. */
37
+ export interface GrpcCall {
38
+ /** `/package.Service/Method`, as it arrived. */
39
+ path: string
40
+ /** Request metadata, header names lowercased. */
41
+ metadata: Record<string, string>
42
+ /** The request message, still encoded. */
43
+ message: Uint8Array
44
+ /** Resolved client IP, by the same trusted-proxy rules as `request.ip()`. */
45
+ ip: string
46
+ }
47
+
48
+ /** A reply message, still encoded — or a gRPC status code instead of one. */
49
+ export type GrpcReply = { message: Uint8Array } | { code: number; details: string }
50
+
51
+ export type GrpcHandler = (call: GrpcCall) => Promise<GrpcReply>
52
+
35
53
  export class Server {
36
54
  #router: Router
37
55
  #serverMiddleware: MiddlewareFunction[] = []
38
56
  #errorHandlerImporter?: LazyImport<ErrorHandlerClass>
39
57
  #resolvedErrorHandler?: ExceptionHandler
40
58
  #trustedProxies: string[] = []
59
+ #grpcHandler?: GrpcHandler
41
60
 
42
61
  constructor(router: Router) {
43
62
  this.#router = router
@@ -65,6 +84,36 @@ export class Server {
65
84
  return this.#trustedProxies
66
85
  }
67
86
 
87
+ /**
88
+ * Serve gRPC on the HTTP port. Once a handler is set, a request whose
89
+ * content-type is `application/grpc*` is a gRPC call handed to it — ahead
90
+ * of the router and the middleware, which never see it. The native server
91
+ * still applies its rate limiter and security filter first. Without a
92
+ * handler such a request stays an ordinary HTTP request.
93
+ *
94
+ * No AdonisJS counterpart: it serves on Node's `http.Server`, where gRPC
95
+ * would attach a library of its own. Here the native server owns the
96
+ * socket, so the gRPC branch is part of it, and this is how a package
97
+ * reaches it — `@c9up/neutrino` registers its dispatcher here.
98
+ *
99
+ * One handler per server: a second one throws. Two packages both serving
100
+ * gRPC is a conflict to resolve, not to settle by load order.
101
+ */
102
+ grpc(handler: GrpcHandler): this {
103
+ if (this.#grpcHandler) {
104
+ throw new ReamError('E_SERVER_GRPC_ALREADY_SET', 'A gRPC handler is already registered.', {
105
+ hint: 'Only one package can serve gRPC on the HTTP port.',
106
+ })
107
+ }
108
+ this.#grpcHandler = handler
109
+ return this
110
+ }
111
+
112
+ /** The gRPC handler, if one is set (consumed by Ignitor at boot). */
113
+ getGrpcHandler(): GrpcHandler | undefined {
114
+ return this.#grpcHandler
115
+ }
116
+
68
117
  /**
69
118
  * Register global server middleware.
70
119
  * Accepts lazy imports of middleware classes (AdonisJS pattern):
@@ -11,7 +11,11 @@
11
11
  */
12
12
 
13
13
  import type { SessionRedisClientSource } from './drivers/RedisDriver.js'
14
- import { E_SESSION_NOT_READY, E_SESSION_TAGGING_NOT_SUPPORTED } from './errors.js'
14
+ import {
15
+ E_SESSION_NOT_READY,
16
+ E_SESSION_STORE_NOT_CONDITIONAL,
17
+ E_SESSION_TAGGING_NOT_SUPPORTED,
18
+ } from './errors.js'
15
19
  import { ReadOnlyValuesStore } from './ReadOnlyValuesStore.js'
16
20
 
17
21
  export interface SessionDriver {
@@ -26,8 +30,27 @@ export interface SessionDriver {
26
30
  */
27
31
  read(sessionId: string): Promise<Record<string, unknown> | null>
28
32
  write(sessionId: string, data: Record<string, unknown>, ttl: number): Promise<void>
33
+ /**
34
+ * Write only over an entry that is still there, in one step; `false` when it
35
+ * is gone, and nothing written.
36
+ *
37
+ * What `commit()` uses for a session that existed when the request came in.
38
+ * A request still running when the same session was logged out, rotated or
39
+ * destroyed everywhere would otherwise write its stale copy back — the user
40
+ * id included — and undo the logout. Upstream writes unconditionally and has
41
+ * that race; this is the named deviation that closes it.
42
+ *
43
+ * Required, and checked when the store is attached: a read followed by a
44
+ * write leaves the race open between the two calls.
45
+ */
46
+ update(sessionId: string, data: Record<string, unknown>, ttl: number): Promise<boolean>
29
47
  destroy(sessionId: string): Promise<void>
30
48
  touch(sessionId: string, ttl: number): Promise<void>
49
+ /**
50
+ * The session lives with the client, not here: nothing on the server can
51
+ * delete a copy of it. Only the cookie store sets it.
52
+ */
53
+ readonly stateless?: boolean
31
54
  }
32
55
 
33
56
  /** One session belonging to a tagged user (AdonisJS `TaggedSession`). */
@@ -179,6 +202,8 @@ export class Session {
179
202
  #ttl = 0
180
203
  /** Whether {@link initiate} already ran, so a second call is a no-op. */
181
204
  #initiated = false
205
+ /** The entry vanished while this request ran — see {@link wasLost}. */
206
+ #lost = false
182
207
 
183
208
  constructor(sessionId: string, data: Record<string, unknown> = {}) {
184
209
  this.#sessionId = sessionId
@@ -228,6 +253,15 @@ export class Session {
228
253
  return this.#regenerated
229
254
  }
230
255
 
256
+ /**
257
+ * @internal — the stored entry was gone by the time {@link commit} ran: it
258
+ * was logged out, rotated or destroyed by another request meanwhile. Nothing
259
+ * was written, and the middleware must not hand the browser this id again.
260
+ */
261
+ wasLost(): boolean {
262
+ return this.#lost
263
+ }
264
+
231
265
  /** @internal — the id captured at request arrival; used to destroy the old driver entry post-regenerate. */
232
266
  originalSessionId(): string {
233
267
  return this.#originalSessionId
@@ -554,6 +588,11 @@ export class Session {
554
588
  * {@link initiate} / {@link commit} then have nothing to do.
555
589
  */
556
590
  setStore(driver: SessionDriver, options: { fresh: boolean; ttl: number }): void {
591
+ // A store written in JavaScript, or against the older contract, reaches
592
+ // here without the method the type requires.
593
+ if (typeof Reflect.get(driver, 'update') !== 'function') {
594
+ throw new E_SESSION_STORE_NOT_CONDITIONAL()
595
+ }
557
596
  this.#driver = driver
558
597
  this.#fresh = options.fresh
559
598
  this.#ttl = options.ttl
@@ -600,12 +639,27 @@ export class Session {
600
639
  return
601
640
  }
602
641
  if (this.#dirty) {
603
- await driver.write(this.#sessionId, this.toJSON(), this.#ttl)
642
+ if (this.#fresh) {
643
+ await driver.write(this.#sessionId, this.toJSON(), this.#ttl)
644
+ return
645
+ }
646
+ this.#lost = !(await driver.update(this.#sessionId, this.toJSON(), this.#ttl))
604
647
  return
605
648
  }
606
649
  if (!this.#fresh) await driver.touch(this.#sessionId, this.#ttl)
607
650
  }
608
651
 
652
+ /**
653
+ * Whether ending this session on the server ends every copy of it.
654
+ *
655
+ * False under the cookie store, where the session travels with the client:
656
+ * a logout replaces the cookie in one browser and cannot reach a copy. What
657
+ * an auth guard reads to refuse signing in on a store it cannot revoke.
658
+ */
659
+ get revocable(): boolean {
660
+ return this.#driver !== undefined && this.#driver.stateless !== true
661
+ }
662
+
609
663
  /**
610
664
  * Whether the store behind this session can tag sessions with a user
611
665
  * (AdonisJS `supportsTagging`). False under the cookie and file drivers.
@@ -79,7 +79,7 @@ export default class SessionMiddleware {
79
79
  'Cookie session driver requires a secret. Set session.secret in your config.',
80
80
  )
81
81
  }
82
- this.#cookieDriver = new CookieDriver(config.secret)
82
+ this.#cookieDriver = new CookieDriver(config.secret, this.#config.cookieName)
83
83
  this.#driver = this.#cookieDriver
84
84
  } else if (this.#config.driver === 'memory') {
85
85
  this.#driver = new MemoryDriver()
@@ -136,6 +136,9 @@ export default class SessionMiddleware {
136
136
 
137
137
  // Cookie driver: the cookie value IS the session data (encrypted)
138
138
  if (this.#cookieDriver) {
139
+ // When the data this request carries was last vouched for. See the
140
+ // re-emission below.
141
+ const readAt = Date.now()
139
142
  const data = await this.#driver.read(sessionId ?? '')
140
143
  sessionId = sessionId ?? generateSessionId()
141
144
  // `null` means the cookie carried nothing readable — an empty session,
@@ -162,8 +165,20 @@ export default class SessionMiddleware {
162
165
  // the cookie maxAge window slides forward (active users don't time out
163
166
  // mid-session). Caller opts in via SessionConfig.rolling.
164
167
  const shouldEmit = session.isDirty() || (rolling && hadIncomingCookie)
165
- if (shouldEmit) {
166
- const encoded = this.#cookieDriver.encode(session.toJSON())
168
+ // The re-emitted cookie lives `maxAge` from when this request READ the
169
+ // session, not from its response. Counted from the response, a request
170
+ // that outlasted a logout re-issued the signed-in data with a full new
171
+ // lifetime — past the window a revocation is kept for, after which the
172
+ // sign-in it carried was accepted again. Counted from the read, no cookie
173
+ // carrying a sign-in outlives the logout by more than `maxAge`. A session
174
+ // regenerated during the request (a login) is new data, so it counts from
175
+ // now; so does a session that came in with no cookie.
176
+ const issuedAt = hadIncomingCookie && !session.wasRegenerated() ? readAt : Date.now()
177
+ const remaining = issuedAt + maxAge * 1000 - Date.now()
178
+ // Expired while the request ran: nothing to hand back, and the browser
179
+ // keeps whatever cookie it was given since.
180
+ if (shouldEmit && remaining > 0) {
181
+ const encoded = this.#cookieDriver.encode(session.toJSON(), remaining / 1000)
167
182
  if (encoded.length > 4096) {
168
183
  process.stderr.write(
169
184
  `[session] Cookie session exceeds 4096 bytes (${encoded.length}). ` +
@@ -174,7 +189,7 @@ export default class SessionMiddleware {
174
189
  // Already encrypted by the cookie driver; packing it again would
175
190
  // only make the cookie bigger and change nothing.
176
191
  encode: false,
177
- maxAge: this.#config.clearWithBrowser ? undefined : maxAge,
192
+ maxAge: this.#config.clearWithBrowser ? undefined : Math.ceil(remaining / 1000),
178
193
  path: '/',
179
194
  httpOnly: true,
180
195
  sameSite: 'lax',
@@ -216,6 +231,10 @@ export default class SessionMiddleware {
216
231
  // no server-side row (touch() is a no-op without a backing entry),
217
232
  // defeats HTTP caching, and leaks a Set-Cookie to CDNs. This mirrors
218
233
  // the cookie-driver path above (which already omits `isNew`).
234
+ // A session logged out or rotated by another request while this one ran
235
+ // was not written back (see `Session.wasLost`); handing the browser its id
236
+ // again would only point it at nothing, over the cookie it got since.
237
+ if (session.wasLost()) return
219
238
  if (session.isDirty() || regenerated || (rolling && hadIncomingCookie)) {
220
239
  // SIGNED, as AdonisJS signs it (`response.cookie(cookieName, id, …)`).
221
240
  // The id is 192 bits of entropy, so it was never guessable — what the
@@ -5,23 +5,33 @@ import type { SessionDriver } from '../Session.js'
5
5
  * Cookie session driver — stores session data in an encrypted cookie (AES-256-GCM).
6
6
  * Stateless on the server. Limited by cookie size (~4KB).
7
7
  * Requires a secret for encryption.
8
+ *
9
+ * Sealed for the cookie's name, as upstream seals every encrypted cookie, so a
10
+ * value encrypted for another cookie under the same key cannot be presented as
11
+ * the session. And sealed with its expiry — the named deviation: upstream
12
+ * leaves expiry to the browser's `maxAge`, which a copied cookie does not
13
+ * obey, so a session lifted from a browser stayed valid for as long as the key
14
+ * did. Here a value older than the session's lifetime opens as nothing.
8
15
  */
9
16
  export class CookieDriver implements SessionDriver {
10
17
  #signer: CookieSigner
18
+ readonly #purpose: string
19
+ readonly stateless = true
11
20
 
12
- constructor(secret: string) {
21
+ constructor(secret: string, cookieName = 'ream_session') {
13
22
  if (!secret) {
14
23
  throw new Error(
15
24
  'CookieDriver requires a secret for session encryption. Set session.secret in your config.',
16
25
  )
17
26
  }
18
27
  this.#signer = new CookieSigner(secret)
28
+ this.#purpose = cookieName
19
29
  }
20
30
 
21
31
  async read(sessionId: string): Promise<Record<string, unknown> | null> {
22
32
  if (!sessionId) return null
23
- const decrypted = this.#signer.decrypt(sessionId)
24
- if (!decrypted) return null
33
+ const decrypted = this.#signer.decrypt(sessionId, this.#purpose)
34
+ if (typeof decrypted !== 'string') return null
25
35
  try {
26
36
  return JSON.parse(decrypted)
27
37
  } catch {
@@ -33,6 +43,11 @@ export class CookieDriver implements SessionDriver {
33
43
  // Cookie driver writes via the response cookie — handled by middleware
34
44
  }
35
45
 
46
+ async update(_sessionId: string, _data: Record<string, unknown>, _ttl: number): Promise<boolean> {
47
+ // Cookie driver writes via the response cookie — handled by middleware
48
+ return true
49
+ }
50
+
36
51
  async destroy(_sessionId: string): Promise<void> {
37
52
  // Cookie deletion handled by middleware setting maxAge=0
38
53
  }
@@ -41,8 +56,8 @@ export class CookieDriver implements SessionDriver {
41
56
  // Cookie renewal handled by middleware
42
57
  }
43
58
 
44
- /** Encode session data for cookie storage (encrypted). */
45
- encode(data: Record<string, unknown>): string {
46
- return this.#signer.encrypt(JSON.stringify(data))
59
+ /** Encode session data for cookie storage (encrypted), valid for `ttl` seconds. */
60
+ encode(data: Record<string, unknown>, ttl: number): string {
61
+ return this.#signer.encrypt(JSON.stringify(data), ttl * 1000, this.#purpose)
47
62
  }
48
63
  }
@@ -143,6 +143,16 @@ export class DatabaseDriver implements SessionDriverWithTagging {
143
143
  }
144
144
  }
145
145
 
146
+ /** One UPDATE: a row deleted or expired meanwhile matches nothing. */
147
+ async update(sessionId: string, data: Record<string, unknown>, ttl: number): Promise<boolean> {
148
+ const now = Date.now()
149
+ const updated = await this.#db.execute(
150
+ `UPDATE ${this.#table} SET data = ?, expires_at = ? WHERE id = ? AND expires_at >= ?`,
151
+ [JSON.stringify(data), now + ttl * 1000, sessionId, now],
152
+ )
153
+ return updated.rowsAffected > 0
154
+ }
155
+
146
156
  async destroy(sessionId: string): Promise<void> {
147
157
  await this.#db.execute(`DELETE FROM ${this.#table} WHERE id = ?`, [sessionId])
148
158
  }