@spinajs/session-provider-db 2.0.488 → 2.0.490

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 (47) hide show
  1. package/lib/cjs/index.d.ts +2 -0
  2. package/lib/cjs/index.d.ts.map +1 -1
  3. package/lib/cjs/index.js +33 -1
  4. package/lib/cjs/index.js.map +1 -1
  5. package/lib/cjs/migration-support.d.ts +43 -0
  6. package/lib/cjs/migration-support.d.ts.map +1 -0
  7. package/lib/cjs/migration-support.js +97 -0
  8. package/lib/cjs/migration-support.js.map +1 -0
  9. package/lib/cjs/migrations/UserSessionDBSqlMigration_2022_06_28_01_20_00.d.ts.map +1 -1
  10. package/lib/cjs/migrations/UserSessionDBSqlMigration_2022_06_28_01_20_00.js +8 -0
  11. package/lib/cjs/migrations/UserSessionDBSqlMigration_2022_06_28_01_20_00.js.map +1 -1
  12. package/lib/cjs/migrations/UserSessionDataJson_2026_07_31_00_00_00.d.ts +101 -0
  13. package/lib/cjs/migrations/UserSessionDataJson_2026_07_31_00_00_00.d.ts.map +1 -0
  14. package/lib/cjs/migrations/UserSessionDataJson_2026_07_31_00_00_00.js +144 -0
  15. package/lib/cjs/migrations/UserSessionDataJson_2026_07_31_00_00_00.js.map +1 -0
  16. package/lib/cjs/migrations/UserSessionTimestamps_2026_07_31_00_00_01.d.ts +68 -0
  17. package/lib/cjs/migrations/UserSessionTimestamps_2026_07_31_00_00_01.d.ts.map +1 -0
  18. package/lib/cjs/migrations/UserSessionTimestamps_2026_07_31_00_00_01.js +95 -0
  19. package/lib/cjs/migrations/UserSessionTimestamps_2026_07_31_00_00_01.js.map +1 -0
  20. package/lib/cjs/models/DbSession.d.ts +15 -0
  21. package/lib/cjs/models/DbSession.d.ts.map +1 -1
  22. package/lib/cjs/models/DbSession.js.map +1 -1
  23. package/lib/mjs/index.d.ts +2 -0
  24. package/lib/mjs/index.d.ts.map +1 -1
  25. package/lib/mjs/index.js +33 -1
  26. package/lib/mjs/index.js.map +1 -1
  27. package/lib/mjs/migration-support.d.ts +43 -0
  28. package/lib/mjs/migration-support.d.ts.map +1 -0
  29. package/lib/mjs/migration-support.js +91 -0
  30. package/lib/mjs/migration-support.js.map +1 -0
  31. package/lib/mjs/migrations/UserSessionDBSqlMigration_2022_06_28_01_20_00.d.ts.map +1 -1
  32. package/lib/mjs/migrations/UserSessionDBSqlMigration_2022_06_28_01_20_00.js +8 -0
  33. package/lib/mjs/migrations/UserSessionDBSqlMigration_2022_06_28_01_20_00.js.map +1 -1
  34. package/lib/mjs/migrations/UserSessionDataJson_2026_07_31_00_00_00.d.ts +101 -0
  35. package/lib/mjs/migrations/UserSessionDataJson_2026_07_31_00_00_00.d.ts.map +1 -0
  36. package/lib/mjs/migrations/UserSessionDataJson_2026_07_31_00_00_00.js +141 -0
  37. package/lib/mjs/migrations/UserSessionDataJson_2026_07_31_00_00_00.js.map +1 -0
  38. package/lib/mjs/migrations/UserSessionTimestamps_2026_07_31_00_00_01.d.ts +68 -0
  39. package/lib/mjs/migrations/UserSessionTimestamps_2026_07_31_00_00_01.d.ts.map +1 -0
  40. package/lib/mjs/migrations/UserSessionTimestamps_2026_07_31_00_00_01.js +92 -0
  41. package/lib/mjs/migrations/UserSessionTimestamps_2026_07_31_00_00_01.js.map +1 -0
  42. package/lib/mjs/models/DbSession.d.ts +15 -0
  43. package/lib/mjs/models/DbSession.d.ts.map +1 -1
  44. package/lib/mjs/models/DbSession.js.map +1 -1
  45. package/lib/tsconfig.cjs.tsbuildinfo +1 -1
  46. package/lib/tsconfig.mjs.tsbuildinfo +1 -1
  47. package/package.json +10 -10
@@ -0,0 +1,141 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { Migration, OrmMigration } from '@spinajs/orm';
8
+ import { columnNativeType, isMySqlDialect, isTextFamily } from '../migration-support.js';
9
+ /**
10
+ * Converts an EXISTING text-typed `user_sessions.Data` to a native MySQL `json`
11
+ * column, so the read path receives a parsed object graph instead of text.
12
+ *
13
+ * Databases created before the create-path migration
14
+ * (`UserSessionDBSqlMigration_2022_06_28_01_20_00`) declared `json` - i.e. every
15
+ * deployment - carry `Data text NOT NULL`. The create migration returns early
16
+ * when the table is already there, so the type it declares has never reached
17
+ * them. Fresh installs already get `json`. Two shapes of the same table exist in
18
+ * the wild and nothing detected which one you had; this migration ends that.
19
+ *
20
+ * mysql2 parses a json column on the way out, so the read path receives the
21
+ * payload as a live object graph and rebuilds the session `Map` from it directly
22
+ * (`decodeSessionData`). The write path is unchanged - `encodeSessionData` still
23
+ * produces JSON text, which a json column stores verbatim.
24
+ *
25
+ * Everything below is a guard. Read them in order.
26
+ *
27
+ * 1. Table missing: nothing to converge
28
+ * ---------------------------------------
29
+ * It normally IS there - the create migration carries an earlier timestamp and
30
+ * runs first in the same batch - but a connection that hosts only part of the
31
+ * schema must not blow up here.
32
+ *
33
+ * 2. Column state probe: skip unless it is text-family
34
+ * ------------------------------------------------------
35
+ * The previous version of this migration was unconditional, on the argument that
36
+ * `MODIFY` to a type a column already has is a harmless rewrite. True on MySQL,
37
+ * and irrelevant everywhere else: it reached the schema builder's `alterTable()`,
38
+ * which resolves `AlterTableQueryCompiler` - and `orm-mssql` registers no
39
+ * implementation for it, so DI hands back the ABSTRACT class, whose `compile()`
40
+ * does not exist. That throw is recorded as a FAILED migration, and
41
+ * `assertNoFailed()` then refuses to run ANY migration on that connection ever
42
+ * again: the application never boots without manual surgery on the tracking
43
+ * table. Probing the target state first and returning early is the same shape
44
+ * `@spinajs/rbac`'s `RBACRegisteredAt_2026_07_28_00_00_00` already uses, and it
45
+ * makes the no-op case genuinely free rather than merely survivable.
46
+ *
47
+ * An unreadable probe skips too - see `columnNativeType`.
48
+ *
49
+ * 3. Dialect gate: MySQL only
50
+ * ------------------------------
51
+ * `MODIFY` and the `JSON` type are MySQL's. A text-family `Data` on MSSQL
52
+ * (`nvarchar`) or sqlite (`TEXT`) passes guard 2 and would then be handed
53
+ * syntax its server rejects - the same failed-migration outage guard 2 exists to
54
+ * prevent. Those dialects keep a text column and the read path keeps decoding a
55
+ * string, which is exactly what `decodeSessionData` does with it.
56
+ *
57
+ * 4. The wipe
58
+ * -----------
59
+ * Reached only when an actual `text -> json` conversion is about to happen.
60
+ * MySQL validates EVERY existing row during that conversion, and a single
61
+ * malformed / truncated / empty payload (a row from a crashed write, a row
62
+ * written by an older codec, a row truncated by TEXT's 64 KiB limit) fails the
63
+ * statement:
64
+ *
65
+ * ERROR 3140 (22032): Invalid JSON text: "Invalid value." at position 1
66
+ *
67
+ * Emptying the table first makes the conversion unconditionally safe. Sessions
68
+ * are disposable state: the worst a user sees is one forced re-login, and that
69
+ * is now paid ONLY by the deployments actually being converted - a fresh install
70
+ * skips at guard 2 and never deletes anything.
71
+ *
72
+ * `DELETE`, not `TRUNCATE`: truncate is not transactional, is refused outright
73
+ * by some engines for a table involved in foreign keys, and there is no volume
74
+ * here to make the difference worth having.
75
+ *
76
+ * 5. ONE statement, not three
77
+ * ----------------------------
78
+ * `alterTable()` compiles one `ICompilerOutput` per modified column and
79
+ * `SqlDriver.execute` wraps the array in `Promise.all` - three concurrent
80
+ * `ALTER TABLE`s on three pooled connections against one table. A partial
81
+ * failure leaves the table half-converted AND the migration marked failed. The
82
+ * raw escape hatch (`schema().raw`, compiled by `SqlRawSchemaQueryCompiler`,
83
+ * registered by every SQL driver via `orm-sql`) sends exactly one statement.
84
+ * The `CreatedAt`/`Expiration` widening that used to ride along here now lives
85
+ * in its own migration (`UserSessionTimestamps_2026_07_31_00_00_01`), because it
86
+ * is a cast that cannot fail and must stay independently applicable.
87
+ */
88
+ let UserSessionDataJson_2026_07_31_00_00_00 = class UserSessionDataJson_2026_07_31_00_00_00 extends OrmMigration {
89
+ async up(connection) {
90
+ if (!(await connection.schema().tableExists('user_sessions'))) {
91
+ return;
92
+ }
93
+ const dataType = await columnNativeType(connection, 'user_sessions', 'Data');
94
+ // already `json` (fresh install / already converged), unreadable, or a type
95
+ // nobody anticipated - all three mean "leave it alone"
96
+ if (dataType === null || !isTextFamily(dataType)) {
97
+ return;
98
+ }
99
+ if (!isMySqlDialect(connection)) {
100
+ return;
101
+ }
102
+ await connection.schema().raw('DELETE FROM user_sessions');
103
+ // Identifiers are backtick-quoted: this statement is MySQL-only by
104
+ // construction (guard 3), so portability of the quoting style is not a
105
+ // constraint, and quoting removes any question about `Data` being a MySQL
106
+ // keyword. `NOT NULL` is restated because MODIFY replaces the WHOLE column
107
+ // definition - omitting it would silently make the column nullable.
108
+ await connection.schema().raw('ALTER TABLE `user_sessions` MODIFY `Data` JSON NOT NULL');
109
+ }
110
+ /**
111
+ * Restores the text column. The sessions `up()` deleted are gone for good -
112
+ * that part is not reversible and nothing pretends otherwise - but the SHAPE
113
+ * is, and a package rollback that leaves a `json` column behind for a
114
+ * `2.0.488` reader is its own outage.
115
+ *
116
+ * Guarded by the inverse probe: only a column that is currently `json` is
117
+ * touched, so running `down()` twice, or against a database this migration
118
+ * never converted, does nothing.
119
+ *
120
+ * `TEXT` because `TEXT` is what the deployed databases had. Note this is a
121
+ * NARROWING cast - a stored payload above TEXT's 64 KiB limit would fail the
122
+ * statement in strict mode. That is acceptable here in a way it would not be
123
+ * in `up()`: `down()` runs only on an explicit, interactive rollback, never at
124
+ * boot, so a failure is loud and immediate rather than an unbootable service.
125
+ */
126
+ async down(connection) {
127
+ if (!(await connection.schema().tableExists('user_sessions'))) {
128
+ return;
129
+ }
130
+ const dataType = await columnNativeType(connection, 'user_sessions', 'Data');
131
+ if (dataType !== 'json' || !isMySqlDialect(connection)) {
132
+ return;
133
+ }
134
+ await connection.schema().raw('ALTER TABLE `user_sessions` MODIFY `Data` TEXT NOT NULL');
135
+ }
136
+ };
137
+ UserSessionDataJson_2026_07_31_00_00_00 = __decorate([
138
+ Migration('session-provider-connection')
139
+ ], UserSessionDataJson_2026_07_31_00_00_00);
140
+ export { UserSessionDataJson_2026_07_31_00_00_00 };
141
+ //# sourceMappingURL=UserSessionDataJson_2026_07_31_00_00_00.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UserSessionDataJson_2026_07_31_00_00_00.js","sourceRoot":"","sources":["../../../src/migrations/UserSessionDataJson_2026_07_31_00_00_00.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,SAAS,EAAE,YAAY,EAAa,MAAM,cAAc,CAAC;AAClE,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAEzF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8EG;AAEI,IAAM,uCAAuC,GAA7C,MAAM,uCAAwC,SAAQ,YAAY;IAChE,KAAK,CAAC,EAAE,CAAC,UAAqB;QACnC,IAAI,CAAC,CAAC,MAAM,UAAU,CAAC,MAAM,EAAE,CAAC,WAAW,CAAC,eAAe,CAAC,CAAC,EAAE,CAAC;YAC9D,OAAO;QACT,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,gBAAgB,CAAC,UAAU,EAAE,eAAe,EAAE,MAAM,CAAC,CAAC;QAE7E,4EAA4E;QAC5E,uDAAuD;QACvD,IAAI,QAAQ,KAAK,IAAI,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,EAAE,CAAC;YACjD,OAAO;QACT,CAAC;QAED,IAAI,CAAC,cAAc,CAAC,UAAU,CAAC,EAAE,CAAC;YAChC,OAAO;QACT,CAAC;QAED,MAAM,UAAU,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,2BAA2B,CAAC,CAAC;QAE3D,mEAAmE;QACnE,uEAAuE;QACvE,0EAA0E;QAC1E,2EAA2E;QAC3E,oEAAoE;QACpE,MAAM,UAAU,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,yDAAyD,CAAC,CAAC;IAC3F,CAAC;IAED;;;;;;;;;;;;;;;OAeG;IACI,KAAK,CAAC,IAAI,CAAC,UAAqB;QACrC,IAAI,CAAC,CAAC,MAAM,UAAU,CAAC,MAAM,EAAE,CAAC,WAAW,CAAC,eAAe,CAAC,CAAC,EAAE,CAAC;YAC9D,OAAO;QACT,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,gBAAgB,CAAC,UAAU,EAAE,eAAe,EAAE,MAAM,CAAC,CAAC;QAE7E,IAAI,QAAQ,KAAK,MAAM,IAAI,CAAC,cAAc,CAAC,UAAU,CAAC,EAAE,CAAC;YACvD,OAAO;QACT,CAAC;QAED,MAAM,UAAU,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,yDAAyD,CAAC,CAAC;IAC3F,CAAC;CACF,CAAA;AAzDY,uCAAuC;IADnD,SAAS,CAAC,6BAA6B,CAAC;GAC5B,uCAAuC,CAyDnD"}
@@ -0,0 +1,68 @@
1
+ import { OrmMigration, OrmDriver } from '@spinajs/orm';
2
+ /**
3
+ * Widens `user_sessions.CreatedAt` / `Expiration` from `date` to `datetime`.
4
+ *
5
+ * !! This is a correctness fix in its own right and has nothing to do with the
6
+ * JSON conversion it used to be bundled with. It is a separate migration
7
+ * precisely so it can be applied, skipped, reviewed and reverted on its own.
8
+ *
9
+ * The bug
10
+ * -------
11
+ * A MySQL `date` column stores only the calendar day - MySQL truncates the time
12
+ * component on write, silently:
13
+ *
14
+ * INSERT INTO probe VALUES ('2026-07-31 23:45:12', '2026-07-31 23:45:12');
15
+ * asDate asDateTime
16
+ * 2026-07-31 2026-07-31 23:45:12
17
+ *
18
+ * So on a deployed database a session's `Expiration` has no time of day: a
19
+ * session created at 23:45 with a 60-minute sliding TTL, due to expire at
20
+ * 00:45:12, is stored as expiring at 00:00:00 and the cleanup sweep kills it
21
+ * ~45 minutes early. Sliding expiration, `SessionProvider.isExpired`,
22
+ * `cleanupExpired` and `touch` all operate on that truncated value. `DbSession`
23
+ * declares both fields as luxon `DateTime` and the create path has declared both
24
+ * as `dateTime` since 2024; `datetime` is the type the code has always assumed
25
+ * it had.
26
+ *
27
+ * Why this one is safe where the JSON one was not
28
+ * -----------------------------------------------
29
+ * `date -> datetime` is a WIDENING cast. Every existing value is representable
30
+ * in the target type (midnight of the stored day), so unlike the `text -> json`
31
+ * conversion it cannot fail on row content and needs no wipe. Nothing is
32
+ * deleted here.
33
+ *
34
+ * Guards mirror the JSON migration: skip when the table is absent, skip unless
35
+ * `CreatedAt` is currently exactly `date`, skip on any non-MySQL dialect
36
+ * (`MODIFY` is MySQL syntax; a failed migration at boot bricks every later
37
+ * migration on the connection through `assertNoFailed()`). `CreatedAt` alone is
38
+ * probed - the two columns were created by the same statement in every database
39
+ * that has the old shape, and a `date`/`datetime` split between them has never
40
+ * existed.
41
+ *
42
+ * ONE statement with two `MODIFY` clauses, via the raw escape hatch, rather than
43
+ * `alterTable()`: the builder compiles one statement per column and
44
+ * `SqlDriver.execute` runs them through `Promise.all`, i.e. two concurrent
45
+ * `ALTER TABLE`s on the same table from two pooled connections.
46
+ *
47
+ * Nullability is restated verbatim from the create path - `MODIFY` replaces the
48
+ * whole column definition, and `CreatedAt` is `notNull()` there while
49
+ * `Expiration` is nullable (a never-expiring session persists a NULL).
50
+ */
51
+ export declare class UserSessionTimestamps_2026_07_31_00_00_01 extends OrmMigration {
52
+ up(connection: OrmDriver): Promise<void>;
53
+ /**
54
+ * Deliberately a no-op, and this is a decision rather than an omission.
55
+ *
56
+ * `up()` widened `date -> datetime`. Reversing it would be a NARROWING cast
57
+ * that silently truncates the time-of-day off every stored row - it destroys
58
+ * data, and it re-introduces the exact expiry bug this migration exists to
59
+ * fix, on a schema whose model and create path have declared `dateTime` for
60
+ * years. A `datetime` column is also perfectly readable by every version of
61
+ * this package, so a rollback has nothing to gain from shrinking it.
62
+ *
63
+ * (The JSON migration's `down()` is implemented, because there the old readers
64
+ * genuinely cannot cope with the new column type. That is not the case here.)
65
+ */
66
+ down(_connection: OrmDriver): Promise<void>;
67
+ }
68
+ //# sourceMappingURL=UserSessionTimestamps_2026_07_31_00_00_01.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UserSessionTimestamps_2026_07_31_00_00_01.d.ts","sourceRoot":"","sources":["../../../src/migrations/UserSessionTimestamps_2026_07_31_00_00_01.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,YAAY,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAGlE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,qBACa,yCAA0C,SAAQ,YAAY;IAC5D,EAAE,CAAC,UAAU,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAiBrD;;;;;;;;;;;;OAYG;IAEU,IAAI,CAAC,WAAW,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;CACzD"}
@@ -0,0 +1,92 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { Migration, OrmMigration } from '@spinajs/orm';
8
+ import { columnNativeType, isMySqlDialect } from '../migration-support.js';
9
+ /**
10
+ * Widens `user_sessions.CreatedAt` / `Expiration` from `date` to `datetime`.
11
+ *
12
+ * !! This is a correctness fix in its own right and has nothing to do with the
13
+ * JSON conversion it used to be bundled with. It is a separate migration
14
+ * precisely so it can be applied, skipped, reviewed and reverted on its own.
15
+ *
16
+ * The bug
17
+ * -------
18
+ * A MySQL `date` column stores only the calendar day - MySQL truncates the time
19
+ * component on write, silently:
20
+ *
21
+ * INSERT INTO probe VALUES ('2026-07-31 23:45:12', '2026-07-31 23:45:12');
22
+ * asDate asDateTime
23
+ * 2026-07-31 2026-07-31 23:45:12
24
+ *
25
+ * So on a deployed database a session's `Expiration` has no time of day: a
26
+ * session created at 23:45 with a 60-minute sliding TTL, due to expire at
27
+ * 00:45:12, is stored as expiring at 00:00:00 and the cleanup sweep kills it
28
+ * ~45 minutes early. Sliding expiration, `SessionProvider.isExpired`,
29
+ * `cleanupExpired` and `touch` all operate on that truncated value. `DbSession`
30
+ * declares both fields as luxon `DateTime` and the create path has declared both
31
+ * as `dateTime` since 2024; `datetime` is the type the code has always assumed
32
+ * it had.
33
+ *
34
+ * Why this one is safe where the JSON one was not
35
+ * -----------------------------------------------
36
+ * `date -> datetime` is a WIDENING cast. Every existing value is representable
37
+ * in the target type (midnight of the stored day), so unlike the `text -> json`
38
+ * conversion it cannot fail on row content and needs no wipe. Nothing is
39
+ * deleted here.
40
+ *
41
+ * Guards mirror the JSON migration: skip when the table is absent, skip unless
42
+ * `CreatedAt` is currently exactly `date`, skip on any non-MySQL dialect
43
+ * (`MODIFY` is MySQL syntax; a failed migration at boot bricks every later
44
+ * migration on the connection through `assertNoFailed()`). `CreatedAt` alone is
45
+ * probed - the two columns were created by the same statement in every database
46
+ * that has the old shape, and a `date`/`datetime` split between them has never
47
+ * existed.
48
+ *
49
+ * ONE statement with two `MODIFY` clauses, via the raw escape hatch, rather than
50
+ * `alterTable()`: the builder compiles one statement per column and
51
+ * `SqlDriver.execute` runs them through `Promise.all`, i.e. two concurrent
52
+ * `ALTER TABLE`s on the same table from two pooled connections.
53
+ *
54
+ * Nullability is restated verbatim from the create path - `MODIFY` replaces the
55
+ * whole column definition, and `CreatedAt` is `notNull()` there while
56
+ * `Expiration` is nullable (a never-expiring session persists a NULL).
57
+ */
58
+ let UserSessionTimestamps_2026_07_31_00_00_01 = class UserSessionTimestamps_2026_07_31_00_00_01 extends OrmMigration {
59
+ async up(connection) {
60
+ if (!(await connection.schema().tableExists('user_sessions'))) {
61
+ return;
62
+ }
63
+ const createdAtType = await columnNativeType(connection, 'user_sessions', 'CreatedAt');
64
+ // Only the one known-bad state is converted. `datetime` is the target,
65
+ // `timestamp` needs no help, `null` means the probe could not read the
66
+ // table, and anything else is a shape this migration was not written for.
67
+ if (createdAtType !== 'date' || !isMySqlDialect(connection)) {
68
+ return;
69
+ }
70
+ await connection.schema().raw('ALTER TABLE `user_sessions` MODIFY `CreatedAt` DATETIME NOT NULL, MODIFY `Expiration` DATETIME');
71
+ }
72
+ /**
73
+ * Deliberately a no-op, and this is a decision rather than an omission.
74
+ *
75
+ * `up()` widened `date -> datetime`. Reversing it would be a NARROWING cast
76
+ * that silently truncates the time-of-day off every stored row - it destroys
77
+ * data, and it re-introduces the exact expiry bug this migration exists to
78
+ * fix, on a schema whose model and create path have declared `dateTime` for
79
+ * years. A `datetime` column is also perfectly readable by every version of
80
+ * this package, so a rollback has nothing to gain from shrinking it.
81
+ *
82
+ * (The JSON migration's `down()` is implemented, because there the old readers
83
+ * genuinely cannot cope with the new column type. That is not the case here.)
84
+ */
85
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
86
+ async down(_connection) { }
87
+ };
88
+ UserSessionTimestamps_2026_07_31_00_00_01 = __decorate([
89
+ Migration('session-provider-connection')
90
+ ], UserSessionTimestamps_2026_07_31_00_00_01);
91
+ export { UserSessionTimestamps_2026_07_31_00_00_01 };
92
+ //# sourceMappingURL=UserSessionTimestamps_2026_07_31_00_00_01.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UserSessionTimestamps_2026_07_31_00_00_01.js","sourceRoot":"","sources":["../../../src/migrations/UserSessionTimestamps_2026_07_31_00_00_01.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,SAAS,EAAE,YAAY,EAAa,MAAM,cAAc,CAAC;AAClE,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAE3E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AAEI,IAAM,yCAAyC,GAA/C,MAAM,yCAA0C,SAAQ,YAAY;IAClE,KAAK,CAAC,EAAE,CAAC,UAAqB;QACnC,IAAI,CAAC,CAAC,MAAM,UAAU,CAAC,MAAM,EAAE,CAAC,WAAW,CAAC,eAAe,CAAC,CAAC,EAAE,CAAC;YAC9D,OAAO;QACT,CAAC;QAED,MAAM,aAAa,GAAG,MAAM,gBAAgB,CAAC,UAAU,EAAE,eAAe,EAAE,WAAW,CAAC,CAAC;QAEvF,uEAAuE;QACvE,uEAAuE;QACvE,0EAA0E;QAC1E,IAAI,aAAa,KAAK,MAAM,IAAI,CAAC,cAAc,CAAC,UAAU,CAAC,EAAE,CAAC;YAC5D,OAAO;QACT,CAAC;QAED,MAAM,UAAU,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,gGAAgG,CAAC,CAAC;IAClI,CAAC;IAED;;;;;;;;;;;;OAYG;IACH,gEAAgE;IACzD,KAAK,CAAC,IAAI,CAAC,WAAsB,IAAkB,CAAC;CAC5D,CAAA;AAjCY,yCAAyC;IADrD,SAAS,CAAC,6BAA6B,CAAC;GAC5B,yCAAyC,CAiCrD"}
@@ -4,6 +4,21 @@ export declare class DbSession extends ModelBase {
4
4
  SessionId: string;
5
5
  CreatedAt: DateTime;
6
6
  Expiration: DateTime;
7
+ /**
8
+ * Serialized session payload - see `encodeSessionData` / `decodeSessionData`.
9
+ *
10
+ * Declared as a string because that is what the WRITE path assigns:
11
+ * `encodeSessionData` produces JSON text and a MySQL `json` column stores it
12
+ * verbatim. What comes BACK depends on the driver - mysql2 parses a json
13
+ * column into an object, sqlite returns the text - so the read path accepts
14
+ * both. The declared type is deliberately not widened to `string | object`:
15
+ * this model is exported, the object shape never escapes the provider, and
16
+ * widening would force every consumer to narrow for a case it cannot observe.
17
+ *
18
+ * No `@Json()` decorator on purpose: the ORM's JsonValueConverter would
19
+ * `JSON.stringify` the already-encoded string on write and double-encode
20
+ * every session.
21
+ */
7
22
  Data: string;
8
23
  UserId: number;
9
24
  }
@@ -1 +1 @@
1
- {"version":3,"file":"DbSession.d.ts","sourceRoot":"","sources":["../../../src/models/DbSession.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAyD,MAAM,cAAc,CAAC;AAChG,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACjC,qBAEa,SAAU,SAAQ,SAAS;IAE/B,SAAS,EAAE,MAAM,CAAC;IAGlB,SAAS,EAAE,QAAQ,CAAC;IAGpB,UAAU,EAAE,QAAQ,CAAC;IAErB,IAAI,EAAE,MAAM,CAAC;IAEb,MAAM,EAAE,MAAM,CAAC;CACvB"}
1
+ {"version":3,"file":"DbSession.d.ts","sourceRoot":"","sources":["../../../src/models/DbSession.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAyD,MAAM,cAAc,CAAC;AAChG,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACjC,qBAEa,SAAU,SAAQ,SAAS;IAE/B,SAAS,EAAE,MAAM,CAAC;IAGlB,SAAS,EAAE,QAAQ,CAAC;IAGpB,UAAU,EAAE,QAAQ,CAAC;IAE5B;;;;;;;;;;;;;;OAcG;IACI,IAAI,EAAE,MAAM,CAAC;IAEb,MAAM,EAAE,MAAM,CAAC;CACvB"}
@@ -1 +1 @@
1
- {"version":3,"file":"DbSession.js","sourceRoot":"","sources":["../../../src/models/DbSession.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,cAAc,CAAC;AAChG,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAG1B,IAAM,SAAS,GAAf,MAAM,SAAU,SAAQ,SAAS;CAavC,CAAA;AAXQ;IADN,OAAO,EAAE;;4CACe;AAGlB;IADN,SAAS,EAAE;8BACM,QAAQ;4CAAC;AAGpB;IADN,EAAE,EAAE;8BACc,QAAQ;6CAAC;AARjB,SAAS;IAFrB,UAAU,CAAC,6BAA6B,CAAC;IACzC,KAAK,CAAC,eAAe,CAAC;GACV,SAAS,CAarB"}
1
+ {"version":3,"file":"DbSession.js","sourceRoot":"","sources":["../../../src/models/DbSession.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,cAAc,CAAC;AAChG,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAG1B,IAAM,SAAS,GAAf,MAAM,SAAU,SAAQ,SAAS;CA4BvC,CAAA;AA1BQ;IADN,OAAO,EAAE;;4CACe;AAGlB;IADN,SAAS,EAAE;8BACM,QAAQ;4CAAC;AAGpB;IADN,EAAE,EAAE;8BACc,QAAQ;6CAAC;AARjB,SAAS;IAFrB,UAAU,CAAC,6BAA6B,CAAC;IACzC,KAAK,CAAC,eAAe,CAAC;GACV,SAAS,CA4BrB"}