mbkauthe 4.8.4 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,193 @@
1
+ import { postgresDialect } from "./dialects/postgres.js";
2
+
3
+ const isPlainObject = (value) => !!value && typeof value === "object" && !Array.isArray(value);
4
+
5
+ export class BaseRepository {
6
+ constructor({ db, dialect = postgresDialect } = {}) {
7
+ this.db = db;
8
+ this.dialect = dialect;
9
+ }
10
+
11
+ quoteIdentifier(name) {
12
+ if (name === "*") return "*";
13
+ return String(name)
14
+ .split(".")
15
+ .map((part) => (part === "*" ? "*" : this.dialect.quoteIdentifier(part)))
16
+ .join(".");
17
+ }
18
+
19
+ ident(name) {
20
+ return { kind: "ident", name };
21
+ }
22
+
23
+ value(val) {
24
+ return { kind: "param", value: val };
25
+ }
26
+
27
+ raw(text) {
28
+ return { kind: "raw", text: text ?? "" };
29
+ }
30
+
31
+ list(values) {
32
+ return { kind: "list", values };
33
+ }
34
+
35
+ table(name, alias = null) {
36
+ const tableSql = this.quoteIdentifier(name);
37
+ if (alias) {
38
+ return this.raw(`${tableSql} AS ${this.quoteIdentifier(alias)}`);
39
+ }
40
+ return this.raw(tableSql);
41
+ }
42
+
43
+ column(name, alias = null) {
44
+ const columnSql = this.quoteIdentifier(name);
45
+ if (alias) {
46
+ return `${columnSql} AS ${this.quoteIdentifier(alias)}`;
47
+ }
48
+ return columnSql;
49
+ }
50
+
51
+ columns(list) {
52
+ const items = (list || []).filter(Boolean).map((item) => {
53
+ if (isPlainObject(item) && item.kind) {
54
+ if (item.kind === "ident") return this.quoteIdentifier(item.name);
55
+ if (item.kind === "raw") return item.text;
56
+ }
57
+ return String(item);
58
+ });
59
+ return this.raw(items.join(", "));
60
+ }
61
+
62
+ star(alias = null) {
63
+ if (!alias) return this.raw("*");
64
+ return this.raw(`${this.quoteIdentifier(alias)}.*`);
65
+ }
66
+
67
+ now() {
68
+ return this.raw(this.dialect.now());
69
+ }
70
+
71
+ boolean(value) {
72
+ return this.raw(this.dialect.boolean(value));
73
+ }
74
+
75
+ returning(columns) {
76
+ if (!this.dialect.supportsReturning) return this.raw("");
77
+ if (!columns) return this.raw("");
78
+
79
+ let columnSql = "";
80
+ if (isPlainObject(columns) && columns.kind === "raw") {
81
+ columnSql = columns.text;
82
+ } else if (Array.isArray(columns)) {
83
+ columnSql = columns.join(", ");
84
+ } else {
85
+ columnSql = String(columns);
86
+ }
87
+
88
+ if (!columnSql) return this.raw("");
89
+ return this.raw(this.dialect.returningClause(columnSql));
90
+ }
91
+
92
+ limit(limit, offset) {
93
+ return this.raw(this.dialect.limitOffset({ limit, offset }));
94
+ }
95
+
96
+ sql(strings, ...exprs) {
97
+ const values = [];
98
+ let text = strings?.[0] ?? "";
99
+
100
+ for (let i = 0; i < exprs.length; i += 1) {
101
+ text += this.renderToken(exprs[i], values);
102
+ text += strings?.[i + 1] ?? "";
103
+ }
104
+
105
+ return { text, values };
106
+ }
107
+
108
+ renderToken(token, values) {
109
+ if (token == null) return "";
110
+
111
+ if (!isPlainObject(token) || !token.kind) {
112
+ return String(token);
113
+ }
114
+
115
+ switch (token.kind) {
116
+ case "raw":
117
+ return token.text;
118
+ case "ident":
119
+ return this.quoteIdentifier(token.name);
120
+ case "param":
121
+ values.push(token.value);
122
+ return this.dialect.param(values.length);
123
+ case "list":
124
+ if (!Array.isArray(token.values) || token.values.length === 0) {
125
+ return "(NULL)";
126
+ }
127
+ return `(${token.values.map((item) => {
128
+ values.push(item);
129
+ return this.dialect.param(values.length);
130
+ }).join(", ")})`;
131
+ default:
132
+ return "";
133
+ }
134
+ }
135
+
136
+ async execute(name, query) {
137
+ const { text, values } = query;
138
+ if (name) {
139
+ return this.db.query({ name, text, values });
140
+ }
141
+ return this.db.query({ text, values });
142
+ }
143
+
144
+ async executeRaw({ name, text, values }) {
145
+ if (name) {
146
+ return this.db.query({ name, text, values });
147
+ }
148
+ return this.db.query({ text, values });
149
+ }
150
+
151
+ cloneWithDb(db) {
152
+ return new this.constructor({ db, dialect: this.dialect });
153
+ }
154
+
155
+ async withTransaction(fn) {
156
+ if (!this.db || typeof this.db.connect !== "function") {
157
+ return fn(this);
158
+ }
159
+
160
+ const client = await this.db.connect();
161
+ const txRepo = this.cloneWithDb(client);
162
+
163
+ try {
164
+ await client.query("BEGIN");
165
+ const result = await fn(txRepo);
166
+ await client.query("COMMIT");
167
+ return result;
168
+ } catch (err) {
169
+ await client.query("ROLLBACK").catch(() => {});
170
+ throw err;
171
+ } finally {
172
+ client.release();
173
+ }
174
+ }
175
+
176
+ async lockTable(tableName, mode) {
177
+ if (!this.dialect.lockTable) return null;
178
+ const text = this.dialect.lockTable(this.quoteIdentifier(tableName), mode);
179
+ return this.executeRaw({
180
+ name: `lock-${tableName}`,
181
+ text,
182
+ values: []
183
+ });
184
+ }
185
+
186
+ async advisoryTransactionLock(lockKey, queryName = "advisory-transaction-lock") {
187
+ return this.executeRaw({
188
+ name: queryName,
189
+ text: "SELECT pg_advisory_xact_lock(hashtext($1))",
190
+ values: [String(lockKey ?? "")]
191
+ });
192
+ }
193
+ }
@@ -0,0 +1,18 @@
1
+ const quoteIdentifier = (name) => `"${String(name).replace(/"/g, '""')}"`;
2
+
3
+ export const postgresDialect = {
4
+ name: "postgres",
5
+ quoteIdentifier,
6
+ param: (index) => `$${index}`,
7
+ now: () => "NOW()",
8
+ boolean: (value) => (value ? "TRUE" : "FALSE"),
9
+ supportsReturning: true,
10
+ returningClause: (columns) => ` RETURNING ${columns}`,
11
+ limitOffset: ({ limit, offset } = {}) => {
12
+ const parts = [];
13
+ if (typeof limit === "number") parts.push(`LIMIT ${limit}`);
14
+ if (typeof offset === "number") parts.push(`OFFSET ${offset}`);
15
+ return parts.length ? ` ${parts.join(" ")}` : "";
16
+ },
17
+ lockTable: (tableSql, mode = "ROW EXCLUSIVE") => `LOCK TABLE ${tableSql} IN ${mode} MODE`
18
+ };
package/lib/main.js CHANGED
@@ -41,17 +41,17 @@ router.use(cookieParser());
41
41
  // CORS and security headers
42
42
  router.use(corsMiddleware);
43
43
 
44
+ // Attach request context as early as possible so session-store queries are tied to the request.
45
+ if (process.env.env === 'dev') {
46
+ router.use(requestContextMiddleware);
47
+ }
48
+
44
49
  // Session configuration
45
50
  router.use(session(sessionConfig));
46
51
 
47
52
  // Session restoration
48
53
  router.use(sessionRestorationMiddleware);
49
54
 
50
- // Attach request context for DB query logging (dev only)
51
- if (process.env.env === 'dev') {
52
- router.use(requestContextMiddleware);
53
- }
54
-
55
55
  // Initialize passport
56
56
  router.use(passport.initialize());
57
57
  router.use(passport.session());