@zintrust/core 0.1.15 → 0.1.16

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 (97) hide show
  1. package/package.json +1 -1
  2. package/src/cli/CLI.d.ts.map +1 -1
  3. package/src/cli/CLI.js +6 -0
  4. package/src/cli/commands/BroadcastWorkCommand.d.ts +10 -0
  5. package/src/cli/commands/BroadcastWorkCommand.d.ts.map +1 -0
  6. package/src/cli/commands/BroadcastWorkCommand.js +16 -0
  7. package/src/cli/commands/NotificationWorkCommand.d.ts +10 -0
  8. package/src/cli/commands/NotificationWorkCommand.d.ts.map +1 -0
  9. package/src/cli/commands/NotificationWorkCommand.js +16 -0
  10. package/src/cli/commands/QueueCommand.d.ts +10 -0
  11. package/src/cli/commands/QueueCommand.d.ts.map +1 -0
  12. package/src/cli/commands/QueueCommand.js +63 -0
  13. package/src/cli/commands/QueueWorkCommandUtils.d.ts +10 -0
  14. package/src/cli/commands/QueueWorkCommandUtils.d.ts.map +1 -0
  15. package/src/cli/commands/QueueWorkCommandUtils.js +43 -0
  16. package/src/cli/commands/createKindWorkCommand.d.ts +9 -0
  17. package/src/cli/commands/createKindWorkCommand.d.ts.map +1 -0
  18. package/src/cli/commands/createKindWorkCommand.js +33 -0
  19. package/src/cli/commands/index.d.ts +3 -0
  20. package/src/cli/commands/index.d.ts.map +1 -1
  21. package/src/cli/commands/index.js +3 -0
  22. package/src/cli/scaffolding/ModelGenerator.d.ts.map +1 -1
  23. package/src/cli/scaffolding/ModelGenerator.js +1 -0
  24. package/src/cli/workers/QueueWorkRunner.d.ts +23 -0
  25. package/src/cli/workers/QueueWorkRunner.d.ts.map +1 -0
  26. package/src/cli/workers/QueueWorkRunner.js +142 -0
  27. package/src/collections/Collection.d.ts +30 -0
  28. package/src/collections/Collection.d.ts.map +1 -0
  29. package/src/collections/Collection.js +146 -0
  30. package/src/collections/index.d.ts +3 -0
  31. package/src/collections/index.d.ts.map +1 -0
  32. package/src/collections/index.js +1 -0
  33. package/src/events/EventDispatcher.d.ts +16 -0
  34. package/src/events/EventDispatcher.d.ts.map +1 -0
  35. package/src/events/EventDispatcher.js +90 -0
  36. package/src/events/index.d.ts +3 -0
  37. package/src/events/index.d.ts.map +1 -0
  38. package/src/events/index.js +1 -0
  39. package/src/features/Queue.js +1 -1
  40. package/src/http/Response.d.ts +2 -2
  41. package/src/http/Response.d.ts.map +1 -1
  42. package/src/index.d.ts +11 -0
  43. package/src/index.d.ts.map +1 -1
  44. package/src/index.js +11 -0
  45. package/src/middleware/CsrfMiddleware.d.ts.map +1 -1
  46. package/src/middleware/CsrfMiddleware.js +20 -25
  47. package/src/middleware/SessionMiddleware.d.ts +8 -0
  48. package/src/middleware/SessionMiddleware.d.ts.map +1 -0
  49. package/src/middleware/SessionMiddleware.js +15 -0
  50. package/src/orm/Model.d.ts +15 -0
  51. package/src/orm/Model.d.ts.map +1 -1
  52. package/src/orm/Model.js +57 -8
  53. package/src/orm/QueryBuilder.d.ts +9 -1
  54. package/src/orm/QueryBuilder.d.ts.map +1 -1
  55. package/src/orm/QueryBuilder.js +54 -2
  56. package/src/scripts/TemplateSync.js +23 -1
  57. package/src/security/PasswordResetTokenBroker.d.ts +39 -0
  58. package/src/security/PasswordResetTokenBroker.d.ts.map +1 -0
  59. package/src/security/PasswordResetTokenBroker.js +131 -0
  60. package/src/session/SessionManager.d.ts +39 -0
  61. package/src/session/SessionManager.d.ts.map +1 -0
  62. package/src/session/SessionManager.js +149 -0
  63. package/src/session/index.d.ts +3 -0
  64. package/src/session/index.d.ts.map +1 -0
  65. package/src/session/index.js +1 -0
  66. package/src/templates/features/Queue.ts.tpl +4 -3
  67. package/src/templates/project/basic/config/FileLogWriter.ts.tpl +4 -3
  68. package/src/templates/project/basic/config/SecretsManager.ts.tpl +1 -1
  69. package/src/templates/project/basic/config/broadcast.ts.tpl +2 -2
  70. package/src/templates/project/basic/config/cache.ts.tpl +2 -2
  71. package/src/templates/project/basic/config/database.ts.tpl +2 -2
  72. package/src/templates/project/basic/config/features.ts.tpl +2 -2
  73. package/src/templates/project/basic/config/logger.ts.tpl +0 -2
  74. package/src/templates/project/basic/config/logging/HttpLogger.ts.tpl +1 -1
  75. package/src/templates/project/basic/config/logging/SlackLogger.ts.tpl +1 -1
  76. package/src/templates/project/basic/config/mail.ts.tpl +2 -2
  77. package/src/templates/project/basic/config/microservices.ts.tpl +1 -1
  78. package/src/templates/project/basic/config/middleware.ts.tpl +6 -9
  79. package/src/templates/project/basic/config/notification.ts.tpl +2 -2
  80. package/src/templates/project/basic/config/security.ts.tpl +1 -2
  81. package/src/templates/project/basic/config/storage.ts.tpl +2 -2
  82. package/src/templates/project/basic/config/type.ts.tpl +2 -2
  83. package/src/tools/broadcast/Broadcast.d.ts +8 -0
  84. package/src/tools/broadcast/Broadcast.d.ts.map +1 -1
  85. package/src/tools/broadcast/Broadcast.js +23 -0
  86. package/src/tools/notification/Notification.d.ts +10 -0
  87. package/src/tools/notification/Notification.d.ts.map +1 -1
  88. package/src/tools/notification/Notification.js +21 -0
  89. package/src/workers/BroadcastWorker.d.ts +22 -0
  90. package/src/workers/BroadcastWorker.d.ts.map +1 -0
  91. package/src/workers/BroadcastWorker.js +24 -0
  92. package/src/workers/NotificationWorker.d.ts +22 -0
  93. package/src/workers/NotificationWorker.d.ts.map +1 -0
  94. package/src/workers/NotificationWorker.js +23 -0
  95. package/src/workers/createQueueWorker.d.ts +24 -0
  96. package/src/workers/createQueueWorker.d.ts.map +1 -0
  97. package/src/workers/createQueueWorker.js +114 -0
package/src/orm/Model.js CHANGED
@@ -43,7 +43,27 @@ const castAttribute = (config, key, value) => {
43
43
  const fillAttributes = (config, attrs, newAttrs) => {
44
44
  for (const [key, value] of Object.entries(newAttrs)) {
45
45
  if (config.fillable.length === 0 || config.fillable.includes(key)) {
46
- attrs[key] = castAttribute(config, key, value);
46
+ const mutator = config.mutators?.[key];
47
+ const nextValue = mutator ? mutator(value, attrs) : value;
48
+ attrs[key] = castAttribute(config, key, nextValue);
49
+ }
50
+ }
51
+ };
52
+ const applyAccessor = (config, key, attrs) => {
53
+ const raw = attrs[key];
54
+ const accessor = config.accessors?.[key];
55
+ return accessor ? accessor(raw, attrs) : raw;
56
+ };
57
+ const runObservers = async (config, hook, model) => {
58
+ const observers = config.observers;
59
+ if (observers === undefined || observers.length === 0)
60
+ return;
61
+ for (const observer of observers) {
62
+ const fn = observer[hook];
63
+ if (typeof fn === 'function') {
64
+ // Observers intentionally run sequentially.
65
+ // eslint-disable-next-line no-await-in-loop
66
+ await fn(model);
47
67
  }
48
68
  }
49
69
  };
@@ -85,29 +105,36 @@ export const createModel = (config, attributes = {}) => {
85
105
  return model;
86
106
  },
87
107
  setAttribute: (key, value) => {
88
- attrs[key] = castAttribute(config, key, value);
108
+ const mutator = config.mutators?.[key];
109
+ const nextValue = mutator ? mutator(value, attrs) : value;
110
+ attrs[key] = castAttribute(config, key, nextValue);
89
111
  return model;
90
112
  },
91
- getAttribute: (key) => attrs[key],
113
+ getAttribute: (key) => applyAccessor(config, key, attrs),
92
114
  getAttributes: () => ({ ...attrs }),
93
115
  // remove in production - use saveChanges pattern
94
- // eslint-disable-next-line @typescript-eslint/require-await
95
116
  async save() {
96
117
  if (db === undefined)
97
118
  throw ErrorFactory.createDatabaseError('Database not initialized');
119
+ const isCreate = isExists === false;
120
+ await runObservers(config, 'saving', model);
121
+ await runObservers(config, isCreate ? 'creating' : 'updating', model);
98
122
  if (config.timestamps) {
99
123
  attrs['created_at'] = attrs['created_at'] ?? new Date().toISOString();
100
124
  attrs['updated_at'] = new Date().toISOString();
101
125
  }
102
126
  isExists = true;
103
127
  original = { ...attrs };
128
+ await runObservers(config, isCreate ? 'created' : 'updated', model);
129
+ await runObservers(config, 'saved', model);
104
130
  return true;
105
131
  },
106
132
  // remove in production - use delete pattern
107
- // eslint-disable-next-line @typescript-eslint/require-await
108
133
  async delete() {
109
134
  if (!isExists || db === undefined)
110
135
  return false;
136
+ await runObservers(config, 'deleting', model);
137
+ await runObservers(config, 'deleted', model);
111
138
  return true;
112
139
  },
113
140
  toJSON: () => createModelJSON(config, attrs),
@@ -130,11 +157,17 @@ export const query = (table, connection) => {
130
157
  const db = useDatabase(undefined, connection ?? DEFAULTS.CONNECTION);
131
158
  return QueryBuilder.create(table, db);
132
159
  };
160
+ const buildSoftDeleteOptions = (config) => {
161
+ if (config.softDeletes !== true)
162
+ return undefined;
163
+ return { softDeleteColumn: 'deleted_at', softDeleteMode: 'exclude' };
164
+ };
133
165
  /**
134
166
  * Find a model by ID
135
167
  */
136
168
  export const find = async (config, id) => {
137
- const builder = query(config.table, config.connection);
169
+ const db = useDatabase(undefined, config.connection ?? DEFAULTS.CONNECTION);
170
+ const builder = QueryBuilder.create(config.table, db, buildSoftDeleteOptions(config));
138
171
  builder.where('id', '=', String(id)).limit(1);
139
172
  const result = await builder.first();
140
173
  if (result === null)
@@ -147,7 +180,8 @@ export const find = async (config, id) => {
147
180
  * Get all records for a model
148
181
  */
149
182
  export const all = async (config) => {
150
- const builder = query(config.table, config.connection);
183
+ const db = useDatabase(undefined, config.connection ?? DEFAULTS.CONNECTION);
184
+ const builder = QueryBuilder.create(config.table, db, buildSoftDeleteOptions(config));
151
185
  const results = await builder.get();
152
186
  return results.map((result) => {
153
187
  const model = createModel(config, result);
@@ -187,7 +221,22 @@ export function define(config, methodsOrPlan) {
187
221
  const models = await all(cfg);
188
222
  return models.map((m) => attach(m));
189
223
  },
190
- query: () => query(cfg.table, cfg.connection),
224
+ query: () => {
225
+ const db = useDatabase(undefined, cfg.connection ?? DEFAULTS.CONNECTION);
226
+ return QueryBuilder.create(cfg.table, db, buildSoftDeleteOptions(cfg));
227
+ },
228
+ scope: (name, ...args) => {
229
+ const scopes = cfg.scopes;
230
+ const fn = scopes?.[name];
231
+ if (typeof fn !== 'function') {
232
+ throw ErrorFactory.createConfigError(`Unknown query scope: ${name}`);
233
+ }
234
+ const builder = (() => {
235
+ const db = useDatabase(undefined, cfg.connection ?? DEFAULTS.CONNECTION);
236
+ return QueryBuilder.create(cfg.table, db, buildSoftDeleteOptions(cfg));
237
+ })();
238
+ return fn(builder, ...args);
239
+ },
191
240
  getTable: () => cfg.table,
192
241
  db: (connection) => createDefinedModel({ ...cfg, connection }),
193
242
  });
@@ -8,11 +8,19 @@ export interface WhereClause {
8
8
  operator: string;
9
9
  value: unknown;
10
10
  }
11
+ export type SoftDeleteMode = 'exclude' | 'include' | 'only';
12
+ export interface QueryBuilderOptions {
13
+ softDeleteColumn?: string;
14
+ softDeleteMode?: SoftDeleteMode;
15
+ }
11
16
  export interface IQueryBuilder {
12
17
  select(...columns: string[]): IQueryBuilder;
13
18
  where(column: string, operator: string | number | boolean | null, value?: unknown): IQueryBuilder;
14
19
  andWhere(column: string, operator: string, value?: unknown): IQueryBuilder;
15
20
  orWhere(column: string, operator: string, value?: unknown): IQueryBuilder;
21
+ withTrashed(): IQueryBuilder;
22
+ onlyTrashed(): IQueryBuilder;
23
+ withoutTrashed(): IQueryBuilder;
16
24
  join(table: string, on: string): IQueryBuilder;
17
25
  leftJoin(table: string, on: string): IQueryBuilder;
18
26
  orderBy(column: string, direction?: 'ASC' | 'DESC'): IQueryBuilder;
@@ -47,7 +55,7 @@ export declare const QueryBuilder: Readonly<{
47
55
  /**
48
56
  * Create a new query builder instance
49
57
  */
50
- create(tableOrDb: string | IDatabase, db?: IDatabase): IQueryBuilder;
58
+ create(tableOrDb: string | IDatabase, db?: IDatabase, options?: QueryBuilderOptions): IQueryBuilder;
51
59
  /**
52
60
  * Ping the database connection.
53
61
  *
@@ -1 +1 @@
1
- {"version":3,"file":"QueryBuilder.d.ts","sourceRoot":"","sources":["../../../src/orm/QueryBuilder.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE1C,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,GAAG,OAAO,EAAE,MAAM,EAAE,GAAG,aAAa,CAAC;IAC5C,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,EAAE,KAAK,CAAC,EAAE,OAAO,GAAG,aAAa,CAAC;IAClG,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,GAAG,aAAa,CAAC;IAC3E,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,GAAG,aAAa,CAAC;IAC1E,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,aAAa,CAAC;IAC/C,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,aAAa,CAAC;IACnD,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,aAAa,CAAC;IACnE,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,aAAa,CAAC;IACpC,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,aAAa,CAAC;IACrC,eAAe,IAAI,WAAW,EAAE,CAAC;IACjC,gBAAgB,IAAI,MAAM,EAAE,CAAC;IAC7B,QAAQ,IAAI,MAAM,CAAC;IACnB,QAAQ,IAAI,MAAM,GAAG,SAAS,CAAC;IAC/B,SAAS,IAAI,MAAM,GAAG,SAAS,CAAC;IAChC,UAAU,IAAI;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,KAAK,GAAG,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;IACxE,QAAQ,IAAI,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjD,eAAe,IAAI,OAAO,CAAC;IAC3B,KAAK,IAAI,MAAM,CAAC;IAChB,aAAa,IAAI,OAAO,EAAE,CAAC;IAC3B,KAAK,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAC9B,GAAG,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;CACxB;AAoTD;;;;;GAKG;AACH,eAAO,MAAM,YAAY;IACvB;;OAEG;sBACe,MAAM,GAAG,SAAS,OAAO,SAAS,GAAG,aAAa;IAkBpE;;;;;OAKG;aACY,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;EAIxC,CAAC"}
1
+ {"version":3,"file":"QueryBuilder.d.ts","sourceRoot":"","sources":["../../../src/orm/QueryBuilder.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE1C,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,SAAS,GAAG,MAAM,CAAC;AAE5D,MAAM,WAAW,mBAAmB;IAClC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,cAAc,CAAC,EAAE,cAAc,CAAC;CACjC;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,GAAG,OAAO,EAAE,MAAM,EAAE,GAAG,aAAa,CAAC;IAC5C,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,EAAE,KAAK,CAAC,EAAE,OAAO,GAAG,aAAa,CAAC;IAClG,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,GAAG,aAAa,CAAC;IAC3E,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,GAAG,aAAa,CAAC;IAC1E,WAAW,IAAI,aAAa,CAAC;IAC7B,WAAW,IAAI,aAAa,CAAC;IAC7B,cAAc,IAAI,aAAa,CAAC;IAChC,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,aAAa,CAAC;IAC/C,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,aAAa,CAAC;IACnD,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,aAAa,CAAC;IACnE,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,aAAa,CAAC;IACpC,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,aAAa,CAAC;IACrC,eAAe,IAAI,WAAW,EAAE,CAAC;IACjC,gBAAgB,IAAI,MAAM,EAAE,CAAC;IAC7B,QAAQ,IAAI,MAAM,CAAC;IACnB,QAAQ,IAAI,MAAM,GAAG,SAAS,CAAC;IAC/B,SAAS,IAAI,MAAM,GAAG,SAAS,CAAC;IAChC,UAAU,IAAI;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,KAAK,GAAG,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;IACxE,QAAQ,IAAI,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjD,eAAe,IAAI,OAAO,CAAC;IAC3B,KAAK,IAAI,MAAM,CAAC;IAChB,aAAa,IAAI,OAAO,EAAE,CAAC;IAC3B,KAAK,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAC9B,GAAG,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;CACxB;AAgWD;;;;;GAKG;AACH,eAAO,MAAM,YAAY;IACvB;;OAEG;sBAEU,MAAM,GAAG,SAAS,OACxB,SAAS,YACL,mBAAmB,GAC3B,aAAa;IAyBhB;;;;;OAKG;aACY,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;EAIxC,CAAC"}
@@ -117,6 +117,25 @@ const compileWhere = (conditions) => {
117
117
  });
118
118
  return { sql: ` WHERE ${clauses.join(' AND ')}`, parameters };
119
119
  };
120
+ const buildSoftDeleteWhereClause = (column, mode) => {
121
+ const col = column.trim();
122
+ if (col.length === 0)
123
+ return null;
124
+ assertSafeIdentifierPath(col, 'soft delete column');
125
+ if (mode === 'include')
126
+ return null;
127
+ if (mode === 'only')
128
+ return { column: col, operator: 'IS NOT', value: null };
129
+ return { column: col, operator: 'IS', value: null };
130
+ };
131
+ const getEffectiveWhereConditions = (state) => {
132
+ if (state.softDelete === undefined)
133
+ return state.whereConditions;
134
+ const clause = buildSoftDeleteWhereClause(state.softDelete.column, state.softDelete.mode);
135
+ if (clause === null)
136
+ return state.whereConditions;
137
+ return [...state.whereConditions, clause];
138
+ };
120
139
  /**
121
140
  * Build ORDER BY clause
122
141
  */
@@ -152,7 +171,7 @@ const buildSelectQuery = (state) => {
152
171
  }
153
172
  const columns = buildSelectClause(state.selectColumns);
154
173
  const fromClause = state.tableName.length > 0 ? ` FROM ${escapeIdentifier(state.tableName)}` : '';
155
- const where = compileWhere(state.whereConditions);
174
+ const where = compileWhere(getEffectiveWhereConditions(state));
156
175
  const sql = `SELECT ${columns}${fromClause}${where.sql}${buildOrderByClause(state.orderByClause)}${buildLimitOffsetClause(state.limitValue, state.offsetValue)}`;
157
176
  return { sql, parameters: where.parameters };
158
177
  };
@@ -224,6 +243,33 @@ function createBuilder(state, db) {
224
243
  },
225
244
  andWhere: (column, operator, value) => builder.where(column, operator, value),
226
245
  orWhere: (column, operator, value) => builder.where(column, operator, value),
246
+ withTrashed: () => {
247
+ if (state.softDelete === undefined) {
248
+ state.softDelete = { column: 'deleted_at', mode: 'include' };
249
+ }
250
+ else {
251
+ state.softDelete.mode = 'include';
252
+ }
253
+ return builder;
254
+ },
255
+ onlyTrashed: () => {
256
+ if (state.softDelete === undefined) {
257
+ state.softDelete = { column: 'deleted_at', mode: 'only' };
258
+ }
259
+ else {
260
+ state.softDelete.mode = 'only';
261
+ }
262
+ return builder;
263
+ },
264
+ withoutTrashed: () => {
265
+ if (state.softDelete === undefined) {
266
+ state.softDelete = { column: 'deleted_at', mode: 'exclude' };
267
+ }
268
+ else {
269
+ state.softDelete.mode = 'exclude';
270
+ }
271
+ return builder;
272
+ },
227
273
  join: (tableJoin, on) => {
228
274
  state.joins.push({ table: tableJoin, on });
229
275
  return builder;
@@ -266,7 +312,7 @@ export const QueryBuilder = Object.freeze({
266
312
  /**
267
313
  * Create a new query builder instance
268
314
  */
269
- create(tableOrDb, db) {
315
+ create(tableOrDb, db, options = {}) {
270
316
  const hasTable = typeof tableOrDb === 'string';
271
317
  const tableName = hasTable ? String(tableOrDb).trim() : '';
272
318
  const database = hasTable ? db : tableOrDb;
@@ -279,6 +325,12 @@ export const QueryBuilder = Object.freeze({
279
325
  selectColumns: ['*'],
280
326
  joins: [],
281
327
  };
328
+ if (options.softDeleteColumn !== undefined && options.softDeleteColumn.trim().length > 0) {
329
+ state.softDelete = {
330
+ column: options.softDeleteColumn.trim(),
331
+ mode: options.softDeleteMode ?? 'exclude',
332
+ };
333
+ }
282
334
  return createBuilder(state, database);
283
335
  },
284
336
  /**
@@ -10,6 +10,7 @@ import { ErrorFactory } from '../exceptions/ZintrustError.js';
10
10
  import * as crypto from '../node-singletons/crypto.js';
11
11
  import fs from '../node-singletons/fs.js';
12
12
  import * as path from '../node-singletons/path.js';
13
+ import * as nodePath from 'node:path';
13
14
  const __dirname = esmDirname(import.meta.url);
14
15
  const ROOT_DIR = path.resolve(__dirname, '../../');
15
16
  /**
@@ -115,9 +116,30 @@ const rewriteStarterTemplateImports = (relPath, content) => {
115
116
  if (!relPath.endsWith('.ts') && !relPath.endsWith('.tsx') && !relPath.endsWith('.mts')) {
116
117
  return content;
117
118
  }
119
+ const rewriteConfigAlias = (aliasSuffix) => {
120
+ const currentDir = nodePath.posix.dirname(relPath);
121
+ const from = currentDir === '.' ? '' : currentDir;
122
+ const target = aliasSuffix;
123
+ const relative = nodePath.posix.relative(from, target);
124
+ return relative.startsWith('.') ? relative : `./${relative}`;
125
+ };
118
126
  // Starter templates should import framework APIs from the public package surface,
119
127
  // not from internal path-alias modules that only exist in the framework repo.
120
128
  return (content
129
+ // Node-singletons are internal to this repo; starter templates should use Node built-ins.
130
+ .replaceAll("'@node-singletons/fs'", "'node:fs'")
131
+ .replaceAll('"@node-singletons/fs"', '"node:fs"')
132
+ .replaceAll("'@node-singletons/path'", "'node:path'")
133
+ .replaceAll('"@node-singletons/path"', '"node:path"')
134
+ // Starter project config/* should reference sibling config modules via relative imports.
135
+ .replaceAll(/(['"])@config\/([^'"]+)\1/g, (_m, quote, suffix) => {
136
+ const rewritten = rewriteConfigAlias(suffix);
137
+ return `${quote}${rewritten}${quote}`;
138
+ })
139
+ // Middleware imports are framework APIs; they must come from the public package.
140
+ .replaceAll(/(['"])@middleware\/[^'"]+\1/g, (_m, quote) => {
141
+ return `${quote}@zintrust/core${quote}`;
142
+ })
121
143
  .replaceAll("'@routing/Router'", "'@zintrust/core'")
122
144
  .replaceAll("'@orm/Database'", "'@zintrust/core'")
123
145
  .replaceAll("'@orm/QueryBuilder'", "'@zintrust/core'")
@@ -217,7 +239,7 @@ const syncStarterProjectTemplates = (params) => {
217
239
  templateDirRel: `${params.projectRoot}/config`,
218
240
  description: 'Starter project config/* (from src/config/*)',
219
241
  transformContent: rewriteStarterTemplateImports,
220
- checksumSalt: 'starter-imports-v1',
242
+ checksumSalt: 'starter-imports-v4',
221
243
  });
222
244
  const s3 = syncProjectTemplateDir({
223
245
  checksums: params.checksums,
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Password Reset Token Broker
3
+ *
4
+ * Framework-agnostic, storage-pluggable password reset token flow.
5
+ *
6
+ * - Generates high-entropy tokens for a given identifier (usually an email).
7
+ * - Stores only a SHA-256 hash of the token (one active token per identifier).
8
+ * - Supports verification and one-time consumption.
9
+ */
10
+ export interface PasswordResetTokenRecord {
11
+ identifier: string;
12
+ tokenHash: string;
13
+ createdAt: Date;
14
+ expiresAt: Date;
15
+ }
16
+ export interface IPasswordResetTokenStore {
17
+ set(record: PasswordResetTokenRecord): void | Promise<void>;
18
+ get(identifier: string): PasswordResetTokenRecord | null | Promise<PasswordResetTokenRecord | null>;
19
+ delete(identifier: string): void | Promise<void>;
20
+ cleanup?(now?: Date): number | Promise<number>;
21
+ clear?(): void | Promise<void>;
22
+ }
23
+ export interface IPasswordResetTokenBroker {
24
+ createToken(identifier: string): Promise<string>;
25
+ verifyToken(identifier: string, token: string): Promise<boolean>;
26
+ consumeToken(identifier: string, token: string): Promise<boolean>;
27
+ }
28
+ export interface PasswordResetTokenBrokerOptions {
29
+ store?: IPasswordResetTokenStore;
30
+ ttlMs?: number;
31
+ tokenBytes?: number;
32
+ now?: () => Date;
33
+ }
34
+ export interface PasswordResetTokenBrokerType {
35
+ create(options?: PasswordResetTokenBrokerOptions): IPasswordResetTokenBroker;
36
+ createInMemoryStore(): IPasswordResetTokenStore;
37
+ }
38
+ export declare const PasswordResetTokenBroker: PasswordResetTokenBrokerType;
39
+ //# sourceMappingURL=PasswordResetTokenBroker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PasswordResetTokenBroker.d.ts","sourceRoot":"","sources":["../../../src/security/PasswordResetTokenBroker.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,MAAM,WAAW,wBAAwB;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,wBAAwB;IACvC,GAAG,CAAC,MAAM,EAAE,wBAAwB,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5D,GAAG,CACD,UAAU,EAAE,MAAM,GACjB,wBAAwB,GAAG,IAAI,GAAG,OAAO,CAAC,wBAAwB,GAAG,IAAI,CAAC,CAAC;IAC9E,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjD,OAAO,CAAC,CAAC,GAAG,CAAC,EAAE,IAAI,GAAG,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/C,KAAK,CAAC,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAChC;AAED,MAAM,WAAW,yBAAyB;IACxC,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACjD,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACjE,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACnE;AAED,MAAM,WAAW,+BAA+B;IAC9C,KAAK,CAAC,EAAE,wBAAwB,CAAC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAClB;AAED,MAAM,WAAW,4BAA4B;IAC3C,MAAM,CAAC,OAAO,CAAC,EAAE,+BAA+B,GAAG,yBAAyB,CAAC;IAC7E,mBAAmB,IAAI,wBAAwB,CAAC;CACjD;AAmFD,eAAO,MAAM,wBAAwB,EAAE,4BAGrC,CAAC"}
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Password Reset Token Broker
3
+ *
4
+ * Framework-agnostic, storage-pluggable password reset token flow.
5
+ *
6
+ * - Generates high-entropy tokens for a given identifier (usually an email).
7
+ * - Stores only a SHA-256 hash of the token (one active token per identifier).
8
+ * - Supports verification and one-time consumption.
9
+ */
10
+ import { ErrorFactory } from '../exceptions/ZintrustError.js';
11
+ import { createHash, randomBytes } from '../node-singletons/crypto.js';
12
+ const DEFAULT_TTL_MS = 30 * 60 * 1000;
13
+ const DEFAULT_TOKEN_BYTES = 32; // 256 bits
14
+ const createInMemoryStore = () => {
15
+ const map = new Map();
16
+ return {
17
+ set(record) {
18
+ map.set(record.identifier, record);
19
+ },
20
+ get(identifier) {
21
+ return map.get(identifier) ?? null;
22
+ },
23
+ delete(identifier) {
24
+ map.delete(identifier);
25
+ },
26
+ cleanup(now = new Date()) {
27
+ let removed = 0;
28
+ for (const [identifier, record] of map.entries()) {
29
+ if (now.getTime() > record.expiresAt.getTime()) {
30
+ map.delete(identifier);
31
+ removed++;
32
+ }
33
+ }
34
+ return removed;
35
+ },
36
+ clear() {
37
+ map.clear();
38
+ },
39
+ };
40
+ };
41
+ const create = (options = {}) => {
42
+ const store = options.store ?? createInMemoryStore();
43
+ const ttlMs = normalizeTtlMs(options.ttlMs ?? DEFAULT_TTL_MS);
44
+ const tokenBytes = normalizeTokenBytes(options.tokenBytes ?? DEFAULT_TOKEN_BYTES);
45
+ const now = options.now ?? (() => new Date());
46
+ return {
47
+ async createToken(identifier) {
48
+ const normalizedIdentifier = normalizeIdentifier(identifier);
49
+ const token = randomBytes(tokenBytes).toString('hex');
50
+ const tokenHash = sha256Hex(token);
51
+ const createdAt = now();
52
+ const expiresAt = new Date(createdAt.getTime() + ttlMs);
53
+ await store.set({ identifier: normalizedIdentifier, tokenHash, createdAt, expiresAt });
54
+ return token;
55
+ },
56
+ async verifyToken(identifier, token) {
57
+ const normalizedIdentifier = normalizeIdentifier(identifier);
58
+ const normalizedToken = normalizeToken(token);
59
+ const record = await store.get(normalizedIdentifier);
60
+ if (record === null)
61
+ return false;
62
+ if (isExpired(record, now())) {
63
+ await store.delete(normalizedIdentifier);
64
+ return false;
65
+ }
66
+ const computed = sha256Hex(normalizedToken);
67
+ return timingSafeEquals(record.tokenHash, computed);
68
+ },
69
+ async consumeToken(identifier, token) {
70
+ const normalizedIdentifier = normalizeIdentifier(identifier);
71
+ const ok = await this.verifyToken(normalizedIdentifier, token);
72
+ if (!ok)
73
+ return false;
74
+ await store.delete(normalizedIdentifier);
75
+ return true;
76
+ },
77
+ };
78
+ };
79
+ export const PasswordResetTokenBroker = Object.freeze({
80
+ create,
81
+ createInMemoryStore,
82
+ });
83
+ function normalizeIdentifier(identifier) {
84
+ if (typeof identifier !== 'string') {
85
+ throw ErrorFactory.createValidationError('Invalid identifier');
86
+ }
87
+ const trimmed = identifier.trim();
88
+ if (trimmed.length === 0) {
89
+ throw ErrorFactory.createValidationError('Invalid identifier');
90
+ }
91
+ return trimmed;
92
+ }
93
+ function normalizeToken(token) {
94
+ if (typeof token !== 'string') {
95
+ throw ErrorFactory.createValidationError('Invalid token');
96
+ }
97
+ const trimmed = token.trim();
98
+ if (trimmed.length === 0) {
99
+ throw ErrorFactory.createValidationError('Invalid token');
100
+ }
101
+ return trimmed;
102
+ }
103
+ function normalizeTtlMs(ttlMs) {
104
+ const value = Number.isFinite(ttlMs) ? Math.trunc(ttlMs) : 0;
105
+ if (value <= 0) {
106
+ throw ErrorFactory.createConfigError('Invalid password reset TTL', { ttlMs });
107
+ }
108
+ return value;
109
+ }
110
+ function normalizeTokenBytes(tokenBytes) {
111
+ const value = Number.isFinite(tokenBytes) ? Math.trunc(tokenBytes) : 0;
112
+ if (value <= 0) {
113
+ throw ErrorFactory.createConfigError('Invalid password reset token bytes', { tokenBytes });
114
+ }
115
+ return value;
116
+ }
117
+ function isExpired(record, now) {
118
+ return now.getTime() > record.expiresAt.getTime();
119
+ }
120
+ function sha256Hex(value) {
121
+ return createHash('sha256').update(value).digest('hex');
122
+ }
123
+ function timingSafeEquals(a, b) {
124
+ if (a.length !== b.length)
125
+ return false;
126
+ let result = 0;
127
+ for (let i = 0; i < a.length; i++) {
128
+ result |= (a.codePointAt(i) ?? 0) ^ (b.codePointAt(i) ?? 0);
129
+ }
130
+ return result === 0;
131
+ }
@@ -0,0 +1,39 @@
1
+ export type SessionData = Record<string, unknown>;
2
+ export interface ISession {
3
+ id: string;
4
+ get<T = unknown>(key: string): T | undefined;
5
+ set(key: string, value: unknown): void;
6
+ has(key: string): boolean;
7
+ forget(key: string): void;
8
+ all(): SessionData;
9
+ clear(): void;
10
+ }
11
+ export interface ISessionManager {
12
+ getIdFromCookieHeader(cookieHeader: string | undefined): string | undefined;
13
+ getIdFromRequest(req: {
14
+ getHeader: (name: string) => unknown;
15
+ sessionId?: unknown;
16
+ context?: Record<string, unknown>;
17
+ }): string | undefined;
18
+ ensureSessionId(req: {
19
+ getHeader: (name: string) => unknown;
20
+ sessionId?: unknown;
21
+ context: Record<string, unknown>;
22
+ }, res: {
23
+ getHeader: (name: string) => unknown;
24
+ setHeader: (name: string, value: string | string[]) => unknown;
25
+ }): Promise<string>;
26
+ get(sessionId: string): ISession;
27
+ destroy(sessionId: string): void;
28
+ cleanup(): number;
29
+ }
30
+ export interface SessionManagerOptions {
31
+ cookieName?: string;
32
+ headerName?: string;
33
+ ttlMs?: number;
34
+ }
35
+ export declare const SessionManager: Readonly<{
36
+ create(options?: SessionManagerOptions): ISessionManager;
37
+ }>;
38
+ export default SessionManager;
39
+ //# sourceMappingURL=SessionManager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SessionManager.d.ts","sourceRoot":"","sources":["../../../src/session/SessionManager.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAElD,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS,CAAC;IAC7C,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;IACvC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAC1B,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,GAAG,IAAI,WAAW,CAAC;IACnB,KAAK,IAAI,IAAI,CAAC;CACf;AAED,MAAM,WAAW,eAAe;IAC9B,qBAAqB,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAAC;IAC5E,gBAAgB,CAAC,GAAG,EAAE;QACpB,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;QACrC,SAAS,CAAC,EAAE,OAAO,CAAC;QACpB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACnC,GAAG,MAAM,GAAG,SAAS,CAAC;IACvB,eAAe,CACb,GAAG,EAAE;QACH,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;QACrC,SAAS,CAAC,EAAE,OAAO,CAAC;QACpB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KAClC,EACD,GAAG,EAAE;QACH,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;QACrC,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,KAAK,OAAO,CAAC;KAChE,GACA,OAAO,CAAC,MAAM,CAAC,CAAC;IACnB,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,QAAQ,CAAC;IACjC,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,OAAO,IAAI,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,qBAAqB;IACpC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAyHD,eAAO,MAAM,cAAc;qBACT,qBAAqB,GAAQ,eAAe;EA2E5D,CAAC;AAEH,eAAe,cAAc,CAAC"}
@@ -0,0 +1,149 @@
1
+ import { generateSecureJobId } from '../common/uuid.js';
2
+ const DEFAULT_OPTIONS = {
3
+ cookieName: 'ZIN_SESSION_ID',
4
+ headerName: 'x-session-id',
5
+ ttlMs: 7 * 24 * 60 * 60 * 1000,
6
+ };
7
+ function parseCookies(cookieHeader) {
8
+ const list = {};
9
+ if (cookieHeader.length === 0)
10
+ return list;
11
+ cookieHeader.split(';').forEach((cookie) => {
12
+ const parts = cookie.split('=');
13
+ const name = parts.shift()?.trim();
14
+ const value = parts.join('=');
15
+ if (name !== null && name !== undefined)
16
+ list[name] = decodeURIComponent(value);
17
+ });
18
+ return list;
19
+ }
20
+ function appendSetCookie(res, cookie) {
21
+ const existing = res.getHeader('Set-Cookie');
22
+ if (existing === undefined) {
23
+ res.setHeader('Set-Cookie', cookie);
24
+ return;
25
+ }
26
+ if (Array.isArray(existing)) {
27
+ const existingCookies = existing.map(String);
28
+ res.setHeader('Set-Cookie', [...existingCookies, cookie]);
29
+ return;
30
+ }
31
+ if (typeof existing === 'string') {
32
+ res.setHeader('Set-Cookie', [existing, cookie]);
33
+ return;
34
+ }
35
+ res.setHeader('Set-Cookie', cookie);
36
+ }
37
+ function buildSessionCookie(cookieName, sessionId) {
38
+ // Keep this minimal; callers can override behavior later.
39
+ // HttpOnly prevents JS access; SameSite=Lax is a reasonable default for app sessions.
40
+ return `${cookieName}=${encodeURIComponent(sessionId)}; Path=/; HttpOnly; SameSite=Lax`;
41
+ }
42
+ function createSessionApi(sessions, sessionId, ttlMs) {
43
+ const withoutKey = (data, key) => {
44
+ if (!Object.prototype.hasOwnProperty.call(data, key))
45
+ return data;
46
+ const record = data;
47
+ const { [key]: _removed, ...rest } = record;
48
+ return rest;
49
+ };
50
+ const ensureStored = () => {
51
+ const existing = sessions.get(sessionId);
52
+ const now = Date.now();
53
+ if (existing !== undefined && existing.expiresAt > now) {
54
+ return existing;
55
+ }
56
+ const created = { data: {}, expiresAt: now + ttlMs };
57
+ sessions.set(sessionId, created);
58
+ return created;
59
+ };
60
+ return {
61
+ id: sessionId,
62
+ get(key) {
63
+ return ensureStored().data[key];
64
+ },
65
+ set(key, value) {
66
+ const stored = ensureStored();
67
+ stored.data[key] = value;
68
+ stored.expiresAt = Date.now() + ttlMs;
69
+ },
70
+ has(key) {
71
+ return Object.prototype.hasOwnProperty.call(ensureStored().data, key);
72
+ },
73
+ forget(key) {
74
+ const stored = ensureStored();
75
+ stored.data = withoutKey(stored.data, key);
76
+ stored.expiresAt = Date.now() + ttlMs;
77
+ },
78
+ all() {
79
+ return { ...ensureStored().data };
80
+ },
81
+ clear() {
82
+ const stored = ensureStored();
83
+ stored.data = {};
84
+ stored.expiresAt = Date.now() + ttlMs;
85
+ },
86
+ };
87
+ }
88
+ export const SessionManager = Object.freeze({
89
+ create(options = {}) {
90
+ const config = { ...DEFAULT_OPTIONS, ...options };
91
+ const sessions = new Map();
92
+ return {
93
+ getIdFromCookieHeader(cookieHeader) {
94
+ if (cookieHeader === undefined || cookieHeader.length === 0)
95
+ return undefined;
96
+ const cookies = parseCookies(cookieHeader);
97
+ return cookies[config.cookieName];
98
+ },
99
+ getIdFromRequest(req) {
100
+ const cookieHeader = req.getHeader('cookie');
101
+ if (typeof cookieHeader === 'string') {
102
+ const fromCookie = this.getIdFromCookieHeader(cookieHeader);
103
+ if (fromCookie !== undefined)
104
+ return fromCookie;
105
+ }
106
+ const fromHeader = req.getHeader(config.headerName);
107
+ if (typeof fromHeader === 'string' && fromHeader.length > 0)
108
+ return fromHeader;
109
+ if (typeof req.sessionId === 'string' && req.sessionId.length > 0)
110
+ return req.sessionId;
111
+ const fromContext = req.context?.['sessionId'];
112
+ if (typeof fromContext === 'string' && fromContext.length > 0)
113
+ return fromContext;
114
+ return undefined;
115
+ },
116
+ async ensureSessionId(req, res) {
117
+ const existing = this.getIdFromRequest(req);
118
+ const sessionId = existing ??
119
+ (await Promise.resolve(generateSecureJobId('SessionManager: secure crypto API not available to generate a session id')));
120
+ req.context['sessionId'] = sessionId;
121
+ // If the cookie is missing, set it.
122
+ const cookieHeader = req.getHeader('cookie');
123
+ const fromCookie = typeof cookieHeader === 'string' ? this.getIdFromCookieHeader(cookieHeader) : undefined;
124
+ if (fromCookie === undefined) {
125
+ appendSetCookie(res, buildSessionCookie(config.cookieName, sessionId));
126
+ }
127
+ return sessionId;
128
+ },
129
+ get(sessionId) {
130
+ return createSessionApi(sessions, sessionId, config.ttlMs);
131
+ },
132
+ destroy(sessionId) {
133
+ sessions.delete(sessionId);
134
+ },
135
+ cleanup() {
136
+ const now = Date.now();
137
+ let removed = 0;
138
+ for (const [id, stored] of sessions.entries()) {
139
+ if (stored.expiresAt <= now) {
140
+ sessions.delete(id);
141
+ removed++;
142
+ }
143
+ }
144
+ return removed;
145
+ },
146
+ };
147
+ },
148
+ });
149
+ export default SessionManager;
@@ -0,0 +1,3 @@
1
+ export { SessionManager } from './SessionManager';
2
+ export type { ISession, ISessionManager, SessionData, SessionManagerOptions } from './SessionManager';
3
+ //# sourceMappingURL=index.d.ts.map