@zintrust/core 0.1.15 → 0.1.17

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 (120) hide show
  1. package/README.md +2 -2
  2. package/package.json +1 -1
  3. package/public/index.html +1 -1
  4. package/src/cli/CLI.d.ts.map +1 -1
  5. package/src/cli/CLI.js +6 -0
  6. package/src/cli/commands/BroadcastWorkCommand.d.ts +10 -0
  7. package/src/cli/commands/BroadcastWorkCommand.d.ts.map +1 -0
  8. package/src/cli/commands/BroadcastWorkCommand.js +16 -0
  9. package/src/cli/commands/NotificationWorkCommand.d.ts +10 -0
  10. package/src/cli/commands/NotificationWorkCommand.d.ts.map +1 -0
  11. package/src/cli/commands/NotificationWorkCommand.js +16 -0
  12. package/src/cli/commands/QueueCommand.d.ts +10 -0
  13. package/src/cli/commands/QueueCommand.d.ts.map +1 -0
  14. package/src/cli/commands/QueueCommand.js +63 -0
  15. package/src/cli/commands/QueueWorkCommandUtils.d.ts +10 -0
  16. package/src/cli/commands/QueueWorkCommandUtils.d.ts.map +1 -0
  17. package/src/cli/commands/QueueWorkCommandUtils.js +43 -0
  18. package/src/cli/commands/createKindWorkCommand.d.ts +9 -0
  19. package/src/cli/commands/createKindWorkCommand.d.ts.map +1 -0
  20. package/src/cli/commands/createKindWorkCommand.js +33 -0
  21. package/src/cli/commands/index.d.ts +3 -0
  22. package/src/cli/commands/index.d.ts.map +1 -1
  23. package/src/cli/commands/index.js +3 -0
  24. package/src/cli/scaffolding/ModelGenerator.d.ts.map +1 -1
  25. package/src/cli/scaffolding/ModelGenerator.js +1 -0
  26. package/src/cli/scaffolding/ProjectScaffolder.d.ts.map +1 -1
  27. package/src/cli/scaffolding/ProjectScaffolder.js +2 -1
  28. package/src/cli/workers/QueueWorkRunner.d.ts +23 -0
  29. package/src/cli/workers/QueueWorkRunner.d.ts.map +1 -0
  30. package/src/cli/workers/QueueWorkRunner.js +142 -0
  31. package/src/collections/Collection.d.ts +30 -0
  32. package/src/collections/Collection.d.ts.map +1 -0
  33. package/src/collections/Collection.js +146 -0
  34. package/src/collections/index.d.ts +3 -0
  35. package/src/collections/index.d.ts.map +1 -0
  36. package/src/collections/index.js +1 -0
  37. package/src/config/env.d.ts +2 -0
  38. package/src/config/env.d.ts.map +1 -1
  39. package/src/config/env.js +4 -0
  40. package/src/config/index.d.ts +3 -0
  41. package/src/config/index.d.ts.map +1 -1
  42. package/src/config/security.d.ts +4 -1
  43. package/src/config/security.d.ts.map +1 -1
  44. package/src/config/security.js +9 -1
  45. package/src/events/EventDispatcher.d.ts +16 -0
  46. package/src/events/EventDispatcher.d.ts.map +1 -0
  47. package/src/events/EventDispatcher.js +90 -0
  48. package/src/events/index.d.ts +3 -0
  49. package/src/events/index.d.ts.map +1 -0
  50. package/src/events/index.js +1 -0
  51. package/src/features/Queue.d.ts +1 -1
  52. package/src/features/Queue.d.ts.map +1 -1
  53. package/src/features/Queue.js +2 -2
  54. package/src/http/Response.d.ts +2 -2
  55. package/src/http/Response.d.ts.map +1 -1
  56. package/src/index.d.ts +12 -0
  57. package/src/index.d.ts.map +1 -1
  58. package/src/index.js +12 -0
  59. package/src/middleware/CsrfMiddleware.d.ts.map +1 -1
  60. package/src/middleware/CsrfMiddleware.js +20 -25
  61. package/src/middleware/SessionMiddleware.d.ts +8 -0
  62. package/src/middleware/SessionMiddleware.d.ts.map +1 -0
  63. package/src/middleware/SessionMiddleware.js +15 -0
  64. package/src/node-singletons/crypto.d.ts +1 -1
  65. package/src/node-singletons/crypto.d.ts.map +1 -1
  66. package/src/node-singletons/crypto.js +1 -1
  67. package/src/orm/Model.d.ts +15 -0
  68. package/src/orm/Model.d.ts.map +1 -1
  69. package/src/orm/Model.js +57 -8
  70. package/src/orm/QueryBuilder.d.ts +9 -1
  71. package/src/orm/QueryBuilder.d.ts.map +1 -1
  72. package/src/orm/QueryBuilder.js +54 -2
  73. package/src/scripts/TemplateSync.js +23 -1
  74. package/src/security/EncryptedEnvelope.d.ts +77 -0
  75. package/src/security/EncryptedEnvelope.d.ts.map +1 -0
  76. package/src/security/EncryptedEnvelope.js +256 -0
  77. package/src/security/PasswordResetTokenBroker.d.ts +39 -0
  78. package/src/security/PasswordResetTokenBroker.d.ts.map +1 -0
  79. package/src/security/PasswordResetTokenBroker.js +131 -0
  80. package/src/security/StartupSecretValidation.d.ts.map +1 -1
  81. package/src/security/StartupSecretValidation.js +72 -0
  82. package/src/session/SessionManager.d.ts +39 -0
  83. package/src/session/SessionManager.d.ts.map +1 -0
  84. package/src/session/SessionManager.js +149 -0
  85. package/src/session/index.d.ts +3 -0
  86. package/src/session/index.d.ts.map +1 -0
  87. package/src/session/index.js +1 -0
  88. package/src/templates/features/Queue.ts.tpl +5 -4
  89. package/src/templates/project/basic/config/FileLogWriter.ts.tpl +4 -3
  90. package/src/templates/project/basic/config/SecretsManager.ts.tpl +1 -1
  91. package/src/templates/project/basic/config/broadcast.ts.tpl +2 -2
  92. package/src/templates/project/basic/config/cache.ts.tpl +2 -2
  93. package/src/templates/project/basic/config/database.ts.tpl +2 -2
  94. package/src/templates/project/basic/config/env.ts.tpl +5 -0
  95. package/src/templates/project/basic/config/features.ts.tpl +2 -2
  96. package/src/templates/project/basic/config/logger.ts.tpl +0 -2
  97. package/src/templates/project/basic/config/logging/HttpLogger.ts.tpl +1 -1
  98. package/src/templates/project/basic/config/logging/SlackLogger.ts.tpl +1 -1
  99. package/src/templates/project/basic/config/mail.ts.tpl +2 -2
  100. package/src/templates/project/basic/config/microservices.ts.tpl +1 -1
  101. package/src/templates/project/basic/config/middleware.ts.tpl +6 -9
  102. package/src/templates/project/basic/config/notification.ts.tpl +2 -2
  103. package/src/templates/project/basic/config/security.ts.tpl +12 -3
  104. package/src/templates/project/basic/config/storage.ts.tpl +2 -2
  105. package/src/templates/project/basic/config/type.ts.tpl +2 -2
  106. package/src/tools/broadcast/Broadcast.d.ts +8 -0
  107. package/src/tools/broadcast/Broadcast.d.ts.map +1 -1
  108. package/src/tools/broadcast/Broadcast.js +23 -0
  109. package/src/tools/notification/Notification.d.ts +10 -0
  110. package/src/tools/notification/Notification.d.ts.map +1 -1
  111. package/src/tools/notification/Notification.js +21 -0
  112. package/src/workers/BroadcastWorker.d.ts +22 -0
  113. package/src/workers/BroadcastWorker.d.ts.map +1 -0
  114. package/src/workers/BroadcastWorker.js +24 -0
  115. package/src/workers/NotificationWorker.d.ts +22 -0
  116. package/src/workers/NotificationWorker.d.ts.map +1 -0
  117. package/src/workers/NotificationWorker.js +23 -0
  118. package/src/workers/createQueueWorker.d.ts +24 -0
  119. package/src/workers/createQueueWorker.d.ts.map +1 -0
  120. package/src/workers/createQueueWorker.js +114 -0
@@ -3,9 +3,9 @@
3
3
  * Protects against Cross-Site Request Forgery attacks
4
4
  * Uses CsrfTokenManager for token generation and validation
5
5
  */
6
- import { generateSecureJobId } from '../common/uuid.js';
7
6
  import { Logger } from '../config/logger.js';
8
7
  import { CsrfTokenManager } from '../security/CsrfTokenManager.js';
8
+ import { SessionManager } from '../session/SessionManager.js';
9
9
  const DEFAULT_OPTIONS = {
10
10
  cookieName: 'XSRF-TOKEN',
11
11
  headerName: 'X-CSRF-Token',
@@ -19,6 +19,7 @@ export const CsrfMiddleware = Object.freeze({
19
19
  create(options = {}) {
20
20
  const config = { ...DEFAULT_OPTIONS, ...options };
21
21
  const manager = CsrfTokenManager.create();
22
+ const sessions = SessionManager.create();
22
23
  // Periodic cleanup to prevent memory leaks
23
24
  // Run every hour (matching default token TTL)
24
25
  const cleanupTimer = setInterval(() => {
@@ -29,35 +30,17 @@ export const CsrfMiddleware = Object.freeze({
29
30
  cleanupTimer.unref();
30
31
  }
31
32
  return async (req, res, next) => {
32
- // We need a session ID to bind the token to.
33
- // Assuming a session middleware has run before this and populated req.context.sessionId
34
- // or we use a cookie for the session ID.
35
- // For now, we'll try to get it from a cookie or generate a temporary one if missing (stateless fallback)
36
- // Note: In a real scenario, this MUST be tied to the user's authenticated session.
37
- // Here we check for a specific session cookie or header.
38
- const cookies = parseCookies(req.getHeader('cookie') || '');
39
- let sessionId = cookies['ZIN_SESSION_ID'] || req.context['sessionId'];
40
- if (!sessionId) {
41
- // If no session exists, we can't effectively bind CSRF to a session.
42
- // However, for the sake of the middleware functioning in a stateless way (Double Submit Cookie pattern),
43
- // we can generate a pseudo-session-id if one doesn't exist, but it's less secure.
44
- // Ideally, this middleware should throw if session is missing.
45
- // For this implementation, we'll skip if no session is found, but log a warning.
46
- // Logger.warn('CSRF Middleware: No session ID found. Skipping CSRF check.');
47
- // await next();
48
- // return;
49
- // Better approach: Generate a session ID if missing (Double Submit Cookie foundation)
50
- // IMPORTANT: use a cryptographically secure generator (Sonar S2245).
51
- sessionId = await generateSecureJobId('CSRF Middleware: secure crypto API not available to generate a session id');
52
- // We would need to set this session cookie, but we can't easily do that without a SessionManager.
53
- // We'll assume the SessionMiddleware handles session creation.
54
- }
33
+ const cookieHeader = req.getHeader('cookie');
34
+ const cookies = parseCookies(typeof cookieHeader === 'string' ? cookieHeader : '');
35
+ // Guarantee a session id exists and a session cookie is set if missing.
36
+ // This allows CSRF tokens to be bound to a stable session identifier.
37
+ const sessionId = await sessions.ensureSessionId(req, res);
55
38
  const method = req.getMethod();
56
39
  // 1. Token Generation (for safe methods)
57
40
  if (config.ignoreMethods?.includes(method) ?? false) {
58
41
  const token = manager.generateToken(sessionId);
59
42
  // Set cookie for Double Submit Cookie pattern (readable by frontend)
60
- res.setHeader('Set-Cookie', `${config.cookieName}=${token}; Path=/; SameSite=Strict`);
43
+ appendSetCookie(res, `${config.cookieName}=${token}; Path=/; SameSite=Strict`);
61
44
  // Also expose in locals for server-side rendering
62
45
  res.locals['csrfToken'] = token;
63
46
  await next();
@@ -81,6 +64,18 @@ export const CsrfMiddleware = Object.freeze({
81
64
  };
82
65
  },
83
66
  });
67
+ function appendSetCookie(res, cookie) {
68
+ const existing = res.getHeader('Set-Cookie');
69
+ if (existing === undefined) {
70
+ res.setHeader('Set-Cookie', cookie);
71
+ return;
72
+ }
73
+ if (Array.isArray(existing)) {
74
+ res.setHeader('Set-Cookie', [...existing, cookie]);
75
+ return;
76
+ }
77
+ res.setHeader('Set-Cookie', [existing, cookie]);
78
+ }
84
79
  /**
85
80
  * Helper to parse cookies
86
81
  */
@@ -0,0 +1,8 @@
1
+ import { Middleware } from './MiddlewareStack';
2
+ import { type SessionManagerOptions } from '../session/SessionManager';
3
+ export type SessionOptions = SessionManagerOptions;
4
+ export declare const SessionMiddleware: Readonly<{
5
+ create(options?: SessionOptions): Middleware;
6
+ }>;
7
+ export default SessionMiddleware;
8
+ //# sourceMappingURL=SessionMiddleware.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SessionMiddleware.d.ts","sourceRoot":"","sources":["../../../src/middleware/SessionMiddleware.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AACzD,OAAO,EAAkB,KAAK,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAErF,MAAM,MAAM,cAAc,GAAG,qBAAqB,CAAC;AAEnD,eAAO,MAAM,iBAAiB;qBACZ,cAAc,GAAQ,UAAU;EAehD,CAAC;AAEH,eAAe,iBAAiB,CAAC"}
@@ -0,0 +1,15 @@
1
+ import { SessionManager } from '../session/SessionManager.js';
2
+ export const SessionMiddleware = Object.freeze({
3
+ create(options = {}) {
4
+ const manager = SessionManager.create(options);
5
+ return async (req, res, next) => {
6
+ const sessionId = await manager.ensureSessionId(req, res);
7
+ // Keep both request state + context aligned.
8
+ req.sessionId = sessionId;
9
+ req.context['sessionId'] = sessionId;
10
+ res.locals['sessionId'] = sessionId;
11
+ await next();
12
+ };
13
+ },
14
+ });
15
+ export default SessionMiddleware;
@@ -3,5 +3,5 @@
3
3
  * Safe to import in both API and CLI code
4
4
  * Exported from node:crypto built-in
5
5
  */
6
- export { createHash, createHmac, createSign, createVerify, generateKeyPairSync, pbkdf2Sync, randomBytes, randomInt, } from 'node:crypto';
6
+ export { createCipheriv, createDecipheriv, createHash, createHmac, createSign, createVerify, generateKeyPairSync, pbkdf2Sync, randomBytes, randomInt, timingSafeEqual, } from 'node:crypto';
7
7
  //# sourceMappingURL=crypto.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../../../src/node-singletons/crypto.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,UAAU,EACV,UAAU,EACV,UAAU,EACV,YAAY,EACZ,mBAAmB,EACnB,UAAU,EACV,WAAW,EACX,SAAS,GACV,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../../../src/node-singletons/crypto.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,UAAU,EACV,UAAU,EACV,UAAU,EACV,YAAY,EACZ,mBAAmB,EACnB,UAAU,EACV,WAAW,EACX,SAAS,EACT,eAAe,GAChB,MAAM,aAAa,CAAC"}
@@ -3,4 +3,4 @@
3
3
  * Safe to import in both API and CLI code
4
4
  * Exported from node:crypto built-in
5
5
  */
6
- export { createHash, createHmac, createSign, createVerify, generateKeyPairSync, pbkdf2Sync, randomBytes, randomInt, } from 'node:crypto';
6
+ export { createCipheriv, createDecipheriv, createHash, createHmac, createSign, createVerify, generateKeyPairSync, pbkdf2Sync, randomBytes, randomInt, timingSafeEqual, } from 'node:crypto';
@@ -10,6 +10,20 @@ export interface ModelConfig {
10
10
  hidden: string[];
11
11
  timestamps: boolean;
12
12
  casts: Record<string, string>;
13
+ softDeletes?: boolean;
14
+ accessors?: Record<string, (value: unknown, attrs: Record<string, unknown>) => unknown>;
15
+ mutators?: Record<string, (value: unknown, attrs: Record<string, unknown>) => unknown>;
16
+ scopes?: Record<string, (builder: IQueryBuilder, ...args: unknown[]) => IQueryBuilder>;
17
+ observers?: Array<{
18
+ saving?: (model: IModel) => void | Promise<void>;
19
+ saved?: (model: IModel) => void | Promise<void>;
20
+ creating?: (model: IModel) => void | Promise<void>;
21
+ created?: (model: IModel) => void | Promise<void>;
22
+ updating?: (model: IModel) => void | Promise<void>;
23
+ updated?: (model: IModel) => void | Promise<void>;
24
+ deleting?: (model: IModel) => void | Promise<void>;
25
+ deleted?: (model: IModel) => void | Promise<void>;
26
+ }>;
13
27
  connection?: string;
14
28
  }
15
29
  export interface ModelStatic {
@@ -60,6 +74,7 @@ export type DefinedModel<T extends BoundModelMethods> = {
60
74
  find: (id: unknown) => Promise<(IModel & T) | null>;
61
75
  all: () => Promise<Array<IModel & T>>;
62
76
  query: () => IQueryBuilder;
77
+ scope: (name: string, ...args: unknown[]) => IQueryBuilder;
63
78
  getTable: () => string;
64
79
  db: (connection: string) => DefinedModel<T>;
65
80
  };
@@ -1 +1 @@
1
- {"version":3,"file":"Model.d.ts","sourceRoot":"","sources":["../../../src/orm/Model.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Model.d.ts","sourceRoot":"","sources":["../../../src/orm/Model.ts"],"names":[],"mappings":"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"}
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,77 @@
1
+ /**
2
+ * EncryptedEnvelope
3
+ *
4
+ * Framework-compatible encrypted payload envelope (PHP-style envelope).
5
+ *
6
+ * Format: base64(JSON({ iv, value, mac, tag }))
7
+ * - iv: base64
8
+ * - value: base64 ciphertext
9
+ * - mac: hex string (AES-CBC envelopes)
10
+ * - tag: base64 auth tag (AES-GCM envelopes)
11
+ */
12
+ export type EncryptedEnvelopeCipher = 'aes-256-cbc' | 'aes-256-gcm';
13
+ export type EncryptedEnvelopeCipherInput = EncryptedEnvelopeCipher | 'AES-256-CBC' | 'AES-256-GCM';
14
+ export type EncryptedEnvelopePayload = {
15
+ iv: string;
16
+ value: string;
17
+ mac?: string;
18
+ tag?: string;
19
+ };
20
+ export type EncryptedEnvelopeSerializer<T> = {
21
+ serialize: (value: T) => string;
22
+ deserialize: (value: string) => T;
23
+ };
24
+ export type EncryptedEnvelopeKeyring = {
25
+ primaryKey: Uint8Array;
26
+ previousKeys: Uint8Array[];
27
+ };
28
+ export type EncryptedEnvelopeEnv = {
29
+ APP_KEY?: string;
30
+ APP_PREVIOUS_KEYS?: string;
31
+ ENCRYPTION_CIPHER?: string;
32
+ };
33
+ export declare const EncryptedEnvelope: Readonly<{
34
+ normalizeCipher: (cipher: EncryptedEnvelopeCipherInput) => EncryptedEnvelopeCipher;
35
+ /**
36
+ * Build a keyring from environment variables.
37
+ * - Uses APP_KEY
38
+ * - Supports APP_PREVIOUS_KEYS (comma-separated or JSON array)
39
+ */
40
+ keyringFromEnv(env?: EncryptedEnvelopeEnv): EncryptedEnvelopeKeyring;
41
+ /**
42
+ * Encrypt a UTF-8 string and return a framework-compatible base64(JSON) envelope.
43
+ */
44
+ encryptString(plaintext: string, options: {
45
+ cipher: EncryptedEnvelopeCipherInput;
46
+ key: string;
47
+ }): string;
48
+ /**
49
+ * Decrypt a framework-compatible base64(JSON) envelope to a UTF-8 string.
50
+ * Tries the primary key first, then previous keys.
51
+ */
52
+ decryptString(encrypted: string, options: {
53
+ cipher: EncryptedEnvelopeCipherInput;
54
+ key: string;
55
+ previousKeys?: string[];
56
+ }): string;
57
+ /**
58
+ * Encrypt arbitrary values using a caller-provided serializer.
59
+ * This supports encrypted payloads for frameworks that store serialized values.
60
+ */
61
+ encrypt<T>(value: T, options: {
62
+ cipher: EncryptedEnvelopeCipherInput;
63
+ key: string;
64
+ serializer: EncryptedEnvelopeSerializer<T>;
65
+ }): string;
66
+ /**
67
+ * Decrypt into an arbitrary value using a caller-provided serializer.
68
+ */
69
+ decrypt<T>(encrypted: string, options: {
70
+ cipher: EncryptedEnvelopeCipherInput;
71
+ key: string;
72
+ previousKeys?: string[];
73
+ serializer: EncryptedEnvelopeSerializer<T>;
74
+ }): T;
75
+ }>;
76
+ export default EncryptedEnvelope;
77
+ //# sourceMappingURL=EncryptedEnvelope.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EncryptedEnvelope.d.ts","sourceRoot":"","sources":["../../../src/security/EncryptedEnvelope.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAYH,MAAM,MAAM,uBAAuB,GAAG,aAAa,GAAG,aAAa,CAAC;AACpE,MAAM,MAAM,4BAA4B,GAAG,uBAAuB,GAAG,aAAa,GAAG,aAAa,CAAC;AAEnG,MAAM,MAAM,wBAAwB,GAAG;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,2BAA2B,CAAC,CAAC,IAAI;IAC3C,SAAS,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,MAAM,CAAC;IAChC,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,CAAC,CAAC;CACnC,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACrC,UAAU,EAAE,UAAU,CAAC;IACvB,YAAY,EAAE,UAAU,EAAE,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC;AA6LF,eAAO,MAAM,iBAAiB;8BA3LG,4BAA4B,KAAG,uBAAuB;IA8LrF;;;;OAIG;yBAEI,oBAAoB,GACxB,wBAAwB;IAe3B;;OAEG;6BAEU,MAAM,WACR;QAAE,MAAM,EAAE,4BAA4B,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,GAC7D,MAAM;IAgCT;;;OAGG;6BAEU,MAAM,WACR;QAAE,MAAM,EAAE,4BAA4B,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,GACtF,MAAM;IA0BT;;;OAGG;YACK,CAAC,SACA,CAAC,WACC;QACP,MAAM,EAAE,4BAA4B,CAAC;QACrC,GAAG,EAAE,MAAM,CAAC;QACZ,UAAU,EAAE,2BAA2B,CAAC,CAAC,CAAC,CAAC;KAC5C,GACA,MAAM;IAQT;;OAEG;YACK,CAAC,aACI,MAAM,WACR;QACP,MAAM,EAAE,4BAA4B,CAAC;QACrC,GAAG,EAAE,MAAM,CAAC;QACZ,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;QACxB,UAAU,EAAE,2BAA2B,CAAC,CAAC,CAAC,CAAC;KAC5C,GACA,CAAC;EASJ,CAAC;AAEH,eAAe,iBAAiB,CAAC"}