@constructive-io/graphql-server 4.33.0 → 4.34.1
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.
- package/esm/middleware/api.js +45 -402
- package/esm/middleware/llm-api.js +39 -75
- package/esm/server.js +3 -0
- package/middleware/api.js +45 -402
- package/middleware/llm-api.d.ts +3 -0
- package/middleware/llm-api.js +39 -75
- package/package.json +15 -14
- package/server.js +3 -0
- package/types.d.ts +1 -124
package/middleware/llm-api.js
CHANGED
|
@@ -20,6 +20,9 @@
|
|
|
20
20
|
*
|
|
21
21
|
* Auth: JWT from the auth middleware (req.token) → pg SET LOCAL context for RLS
|
|
22
22
|
* Metering: check_billing_quota → LLM call → record_usage with real token counts
|
|
23
|
+
*
|
|
24
|
+
* Context: Uses `req.constructive` from @constructive-io/express-context
|
|
25
|
+
* for tenant-scoped database access, pgSettings, and withPgClient.
|
|
23
26
|
*/
|
|
24
27
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
25
28
|
if (k2 === undefined) k2 = k;
|
|
@@ -58,45 +61,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
58
61
|
exports.createLlmApiRouter = createLlmApiRouter;
|
|
59
62
|
const express_1 = __importStar(require("express"));
|
|
60
63
|
const logger_1 = require("@pgpmjs/logger");
|
|
61
|
-
const pg_cache_1 = require("pg-cache");
|
|
62
64
|
const ollama_1 = require("@agentic-kit/ollama");
|
|
63
65
|
const graphile_cache_1 = require("graphile-cache");
|
|
64
66
|
const graphile_llm_1 = require("graphile-llm");
|
|
65
67
|
const log = new logger_1.Logger('llm-api');
|
|
66
68
|
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
67
|
-
function getPgSettings(req) {
|
|
68
|
-
const settings = {};
|
|
69
|
-
if (req.token?.user_id) {
|
|
70
|
-
settings['jwt.claims.user_id'] = req.token.user_id;
|
|
71
|
-
settings['role'] = 'authenticated';
|
|
72
|
-
}
|
|
73
|
-
if (req.databaseId) {
|
|
74
|
-
settings['jwt.claims.database_id'] = req.databaseId;
|
|
75
|
-
}
|
|
76
|
-
if (req.requestId) {
|
|
77
|
-
settings['request.id'] = req.requestId;
|
|
78
|
-
}
|
|
79
|
-
return settings;
|
|
80
|
-
}
|
|
81
|
-
async function withRlsClient(pool, pgSettings, fn) {
|
|
82
|
-
const client = await pool.connect();
|
|
83
|
-
try {
|
|
84
|
-
await client.query('BEGIN');
|
|
85
|
-
for (const [key, value] of Object.entries(pgSettings)) {
|
|
86
|
-
await client.query('SELECT set_config($1, $2, true)', [key, value]);
|
|
87
|
-
}
|
|
88
|
-
const result = await fn(client);
|
|
89
|
-
await client.query('COMMIT');
|
|
90
|
-
return result;
|
|
91
|
-
}
|
|
92
|
-
catch (err) {
|
|
93
|
-
await client.query('ROLLBACK').catch(() => { });
|
|
94
|
-
throw err;
|
|
95
|
-
}
|
|
96
|
-
finally {
|
|
97
|
-
client.release();
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
69
|
function resolveOllamaAdapter() {
|
|
101
70
|
const { chat } = (0, graphile_llm_1.getLlmEnvOptions)();
|
|
102
71
|
if (chat.provider === 'ollama') {
|
|
@@ -109,9 +78,9 @@ function resolveOllamaAdapter() {
|
|
|
109
78
|
return null;
|
|
110
79
|
}
|
|
111
80
|
// ─── Billing Helpers ────────────────────────────────────────────────────────
|
|
112
|
-
async function checkQuota(
|
|
81
|
+
async function checkQuota(ctx, billing, entityId, meterSlug) {
|
|
113
82
|
try {
|
|
114
|
-
return await
|
|
83
|
+
return await ctx.withPgClient(async (client) => {
|
|
115
84
|
const sql = `SELECT "${billing.privateSchema}"."${billing.checkBillingQuotaFunction}"($1, $2::uuid, $3) AS allowed`;
|
|
116
85
|
const result = await client.query(sql, [meterSlug, entityId, 1]);
|
|
117
86
|
return result.rows[0]?.allowed !== false;
|
|
@@ -123,9 +92,9 @@ async function checkQuota(pool, pgSettings, billing, entityId, meterSlug) {
|
|
|
123
92
|
return true;
|
|
124
93
|
}
|
|
125
94
|
}
|
|
126
|
-
async function recordUsage(
|
|
95
|
+
async function recordUsage(ctx, billing, entityId, meterSlug, amount, metadata) {
|
|
127
96
|
try {
|
|
128
|
-
await
|
|
97
|
+
await ctx.withPgClient(async (client) => {
|
|
129
98
|
const sql = `SELECT "${billing.privateSchema}"."${billing.recordUsageFunction}"($1, $2::uuid, $3, $4::jsonb)`;
|
|
130
99
|
await client.query(sql, [meterSlug, entityId, amount, JSON.stringify(metadata)]);
|
|
131
100
|
});
|
|
@@ -135,11 +104,13 @@ async function recordUsage(pool, pgSettings, billing, entityId, meterSlug, amoun
|
|
|
135
104
|
log.warn(`[llm-api] record_usage failed (non-fatal): ${message}`);
|
|
136
105
|
}
|
|
137
106
|
}
|
|
138
|
-
async function resolveBilling(
|
|
107
|
+
async function resolveBilling(ctx) {
|
|
108
|
+
if (!ctx.databaseId)
|
|
109
|
+
return null;
|
|
139
110
|
try {
|
|
140
111
|
let billing = null;
|
|
141
|
-
await
|
|
142
|
-
const entry = await (0, graphile_llm_1.getLlmBillingConfig)(client, databaseId);
|
|
112
|
+
await ctx.withPgClient(async (client) => {
|
|
113
|
+
const entry = await (0, graphile_llm_1.getLlmBillingConfig)(client, ctx.databaseId);
|
|
143
114
|
billing = entry.billing;
|
|
144
115
|
});
|
|
145
116
|
return billing;
|
|
@@ -178,9 +149,9 @@ async function getInferenceLogInfo(pool, dbname) {
|
|
|
178
149
|
inferenceLogCache.set(dbname, info);
|
|
179
150
|
return info;
|
|
180
151
|
}
|
|
181
|
-
async function logInference(
|
|
152
|
+
async function logInference(ctx, logInfo, data) {
|
|
182
153
|
try {
|
|
183
|
-
await
|
|
154
|
+
await ctx.withPgClient(async (client) => {
|
|
184
155
|
await client.query(`INSERT INTO "${logInfo.schemaName}"."${logInfo.tableName}"
|
|
185
156
|
(entity_id, actor_id, model, provider, service, operation, input_tokens, output_tokens, total_tokens, latency_ms, status)
|
|
186
157
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)`, [
|
|
@@ -205,31 +176,29 @@ async function logInference(pool, pgSettings, logInfo, data) {
|
|
|
205
176
|
}
|
|
206
177
|
// ─── Route Handlers ─────────────────────────────────────────────────────────
|
|
207
178
|
async function handleCreateThread(req, res, entityId) {
|
|
208
|
-
|
|
179
|
+
const ctx = req.constructive;
|
|
180
|
+
if (!ctx?.userId) {
|
|
209
181
|
res.status(401).json({ error: 'Authentication required' });
|
|
210
182
|
return;
|
|
211
183
|
}
|
|
212
|
-
|
|
213
|
-
if (!dbname) {
|
|
184
|
+
if (!ctx.api.dbname) {
|
|
214
185
|
res.status(400).json({ error: 'Database not resolved' });
|
|
215
186
|
return;
|
|
216
187
|
}
|
|
217
|
-
const
|
|
218
|
-
const discovery = await (0, graphile_llm_1.getAgentDiscovery)(pool, dbname);
|
|
188
|
+
const discovery = await (0, graphile_llm_1.getAgentDiscovery)(ctx.pool, ctx.api.dbname);
|
|
219
189
|
if (!discovery?.thread) {
|
|
220
190
|
res.status(404).json({ error: 'Agent module not provisioned for this database' });
|
|
221
191
|
return;
|
|
222
192
|
}
|
|
223
193
|
const body = req.body || {};
|
|
224
194
|
const { thread } = discovery;
|
|
225
|
-
const
|
|
226
|
-
const result = await withRlsClient(pool, pgSettings, async (client) => {
|
|
195
|
+
const result = await ctx.withPgClient(async (client) => {
|
|
227
196
|
const { rows } = await client.query(`INSERT INTO "${thread.schemaName}"."${thread.tableName}"
|
|
228
197
|
(entity_id, owner_id, mode, model, system_prompt, title)
|
|
229
198
|
VALUES ($1, $2, $3, $4, $5, $6)
|
|
230
199
|
RETURNING id, mode, model, system_prompt, status, created_at`, [
|
|
231
200
|
entityId,
|
|
232
|
-
|
|
201
|
+
ctx.userId,
|
|
233
202
|
body.mode ?? 'ask',
|
|
234
203
|
body.model ?? null,
|
|
235
204
|
body.system_prompt ?? null,
|
|
@@ -247,17 +216,16 @@ async function handleCreateThread(req, res, entityId) {
|
|
|
247
216
|
});
|
|
248
217
|
}
|
|
249
218
|
async function handleSendMessage(req, res, entityId) {
|
|
250
|
-
|
|
219
|
+
const ctx = req.constructive;
|
|
220
|
+
if (!ctx?.userId) {
|
|
251
221
|
res.status(401).json({ error: 'Authentication required' });
|
|
252
222
|
return;
|
|
253
223
|
}
|
|
254
|
-
|
|
255
|
-
if (!dbname) {
|
|
224
|
+
if (!ctx.api.dbname) {
|
|
256
225
|
res.status(400).json({ error: 'Database not resolved' });
|
|
257
226
|
return;
|
|
258
227
|
}
|
|
259
|
-
const
|
|
260
|
-
const discovery = await (0, graphile_llm_1.getAgentDiscovery)(pool, dbname);
|
|
228
|
+
const discovery = await (0, graphile_llm_1.getAgentDiscovery)(ctx.pool, ctx.api.dbname);
|
|
261
229
|
if (!discovery?.thread || !discovery?.message) {
|
|
262
230
|
res.status(404).json({ error: 'Agent module not provisioned for this database' });
|
|
263
231
|
return;
|
|
@@ -268,12 +236,10 @@ async function handleSendMessage(req, res, entityId) {
|
|
|
268
236
|
return;
|
|
269
237
|
}
|
|
270
238
|
const { thread, message: msgTable } = discovery;
|
|
271
|
-
const pgSettings = getPgSettings(req);
|
|
272
239
|
const threadId = req.params.thread_id;
|
|
273
|
-
const userId =
|
|
274
|
-
const databaseId = req.databaseId;
|
|
240
|
+
const userId = ctx.userId;
|
|
275
241
|
// 1. Verify thread exists and user owns it (RLS enforced)
|
|
276
|
-
const threadRow = await
|
|
242
|
+
const threadRow = await ctx.withPgClient(async (client) => {
|
|
277
243
|
const { rows } = await client.query(`SELECT id, mode, model, system_prompt, status
|
|
278
244
|
FROM "${thread.schemaName}"."${thread.tableName}"
|
|
279
245
|
WHERE id = $1`, [threadId]);
|
|
@@ -284,10 +250,8 @@ async function handleSendMessage(req, res, entityId) {
|
|
|
284
250
|
return;
|
|
285
251
|
}
|
|
286
252
|
// 2. Resolve billing config + inference log discovery
|
|
287
|
-
const billing =
|
|
288
|
-
|
|
289
|
-
: null;
|
|
290
|
-
const inferenceLog = await getInferenceLogInfo(pool, dbname);
|
|
253
|
+
const billing = await resolveBilling(ctx);
|
|
254
|
+
const inferenceLog = await getInferenceLogInfo(ctx.pool, ctx.api.dbname);
|
|
291
255
|
const ollama = resolveOllamaAdapter();
|
|
292
256
|
if (!ollama) {
|
|
293
257
|
res.status(503).json({ error: 'No LLM provider configured' });
|
|
@@ -296,7 +260,7 @@ async function handleSendMessage(req, res, entityId) {
|
|
|
296
260
|
const model = body.model ?? threadRow.model ?? ollama.model;
|
|
297
261
|
const meterSlug = model;
|
|
298
262
|
if (billing) {
|
|
299
|
-
const allowed = await checkQuota(
|
|
263
|
+
const allowed = await checkQuota(ctx, billing, entityId, meterSlug);
|
|
300
264
|
if (!allowed) {
|
|
301
265
|
res.status(429).json({
|
|
302
266
|
error: 'Token quota exceeded',
|
|
@@ -307,7 +271,7 @@ async function handleSendMessage(req, res, entityId) {
|
|
|
307
271
|
}
|
|
308
272
|
}
|
|
309
273
|
// 3. Persist user message(s)
|
|
310
|
-
await
|
|
274
|
+
await ctx.withPgClient(async (client) => {
|
|
311
275
|
for (const msg of body.messages) {
|
|
312
276
|
if (msg.role === 'user') {
|
|
313
277
|
await client.query(`INSERT INTO "${msgTable.schemaName}"."${msgTable.tableName}"
|
|
@@ -322,7 +286,7 @@ async function handleSendMessage(req, res, entityId) {
|
|
|
322
286
|
}
|
|
323
287
|
});
|
|
324
288
|
// 4. Load full thread history for context
|
|
325
|
-
const history = await
|
|
289
|
+
const history = await ctx.withPgClient(async (client) => {
|
|
326
290
|
const { rows } = await client.query(`SELECT author_role, parts, created_at
|
|
327
291
|
FROM "${msgTable.schemaName}"."${msgTable.tableName}"
|
|
328
292
|
WHERE thread_id = $1
|
|
@@ -408,7 +372,7 @@ async function handleSendMessage(req, res, entityId) {
|
|
|
408
372
|
res.end();
|
|
409
373
|
// 6. Persist assistant message with model (fire-and-forget)
|
|
410
374
|
if (content) {
|
|
411
|
-
|
|
375
|
+
ctx.withPgClient(async (client) => {
|
|
412
376
|
await client.query(`INSERT INTO "${msgTable.schemaName}"."${msgTable.tableName}"
|
|
413
377
|
(thread_id, owner_id, entity_id, author_role, parts, model)
|
|
414
378
|
VALUES ($1, $2, (SELECT entity_id FROM "${thread.schemaName}"."${thread.tableName}" WHERE id = $1), $3, $4, $5)`, [
|
|
@@ -424,7 +388,7 @@ async function handleSendMessage(req, res, entityId) {
|
|
|
424
388
|
}
|
|
425
389
|
// 7. Record billing usage (fire-and-forget)
|
|
426
390
|
if (billing && usage.totalTokens > 0) {
|
|
427
|
-
recordUsage(
|
|
391
|
+
recordUsage(ctx, billing, entityId, meterSlug, usage.totalTokens, {
|
|
428
392
|
input_tokens: usage.input,
|
|
429
393
|
output_tokens: usage.output,
|
|
430
394
|
cache_read_tokens: usage.cacheRead,
|
|
@@ -436,7 +400,7 @@ async function handleSendMessage(req, res, entityId) {
|
|
|
436
400
|
}
|
|
437
401
|
// 8. Inference log (fire-and-forget)
|
|
438
402
|
if (inferenceLog) {
|
|
439
|
-
logInference(
|
|
403
|
+
logInference(ctx, inferenceLog, {
|
|
440
404
|
entityId,
|
|
441
405
|
actorId: userId,
|
|
442
406
|
model,
|
|
@@ -492,7 +456,7 @@ async function handleSendMessage(req, res, entityId) {
|
|
|
492
456
|
totalTokens: result.usage.totalTokens,
|
|
493
457
|
};
|
|
494
458
|
// Persist assistant message with model
|
|
495
|
-
await
|
|
459
|
+
await ctx.withPgClient(async (client) => {
|
|
496
460
|
await client.query(`INSERT INTO "${msgTable.schemaName}"."${msgTable.tableName}"
|
|
497
461
|
(thread_id, owner_id, entity_id, author_role, parts, model)
|
|
498
462
|
VALUES ($1, $2, (SELECT entity_id FROM "${thread.schemaName}"."${thread.tableName}" WHERE id = $1), $3, $4, $5)`, [
|
|
@@ -505,7 +469,7 @@ async function handleSendMessage(req, res, entityId) {
|
|
|
505
469
|
});
|
|
506
470
|
// Record billing usage
|
|
507
471
|
if (billing && usage.totalTokens > 0) {
|
|
508
|
-
recordUsage(
|
|
472
|
+
recordUsage(ctx, billing, entityId, meterSlug, usage.totalTokens, {
|
|
509
473
|
input_tokens: usage.input,
|
|
510
474
|
output_tokens: usage.output,
|
|
511
475
|
cache_read_tokens: usage.cacheRead,
|
|
@@ -517,7 +481,7 @@ async function handleSendMessage(req, res, entityId) {
|
|
|
517
481
|
}
|
|
518
482
|
// Inference log
|
|
519
483
|
if (inferenceLog) {
|
|
520
|
-
logInference(
|
|
484
|
+
logInference(ctx, inferenceLog, {
|
|
521
485
|
entityId,
|
|
522
486
|
actorId: userId,
|
|
523
487
|
model,
|
|
@@ -577,7 +541,7 @@ function createLlmApiRouter() {
|
|
|
577
541
|
// ── Global routes (no entity_id — bills to actor_id from JWT) ────────────
|
|
578
542
|
router.post('/v1/threads', async (req, res) => {
|
|
579
543
|
try {
|
|
580
|
-
const userId = req.
|
|
544
|
+
const userId = req.constructive?.userId;
|
|
581
545
|
if (!userId) {
|
|
582
546
|
res.status(401).json({ error: 'Authentication required' });
|
|
583
547
|
return;
|
|
@@ -593,7 +557,7 @@ function createLlmApiRouter() {
|
|
|
593
557
|
});
|
|
594
558
|
router.post('/v1/threads/:thread_id/messages', async (req, res) => {
|
|
595
559
|
try {
|
|
596
|
-
const userId = req.
|
|
560
|
+
const userId = req.constructive?.userId;
|
|
597
561
|
if (!userId) {
|
|
598
562
|
res.status(401).json({ error: 'Authentication required' });
|
|
599
563
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@constructive-io/graphql-server",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.34.1",
|
|
4
4
|
"author": "Constructive <developers@constructive.io>",
|
|
5
5
|
"description": "Constructive GraphQL Server",
|
|
6
6
|
"main": "index.js",
|
|
@@ -43,16 +43,17 @@
|
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@agentic-kit/ollama": "^2.0.0",
|
|
45
45
|
"@constructive-io/csrf": "^0.14.0",
|
|
46
|
-
"@constructive-io/
|
|
47
|
-
"@constructive-io/graphql-
|
|
46
|
+
"@constructive-io/express-context": "^0.2.1",
|
|
47
|
+
"@constructive-io/graphql-env": "^3.12.0",
|
|
48
|
+
"@constructive-io/graphql-types": "^3.11.0",
|
|
48
49
|
"@constructive-io/s3-utils": "^2.17.1",
|
|
49
50
|
"@constructive-io/upload-names": "^2.16.0",
|
|
50
51
|
"@constructive-io/url-domains": "^2.16.0",
|
|
51
52
|
"@graphile-contrib/pg-many-to-many": "2.0.0-rc.2",
|
|
52
|
-
"@pgpmjs/env": "^2.
|
|
53
|
+
"@pgpmjs/env": "^2.24.0",
|
|
53
54
|
"@pgpmjs/logger": "^2.11.0",
|
|
54
|
-
"@pgpmjs/server-utils": "^3.
|
|
55
|
-
"@pgpmjs/types": "^2.
|
|
55
|
+
"@pgpmjs/server-utils": "^3.12.0",
|
|
56
|
+
"@pgpmjs/types": "^2.28.0",
|
|
56
57
|
"@pgsql/quotes": "^17.1.0",
|
|
57
58
|
"cors": "^2.8.6",
|
|
58
59
|
"deepmerge": "^4.3.1",
|
|
@@ -62,19 +63,19 @@
|
|
|
62
63
|
"grafserv": "1.0.0",
|
|
63
64
|
"graphile-build": "5.0.2",
|
|
64
65
|
"graphile-build-pg": "5.0.2",
|
|
65
|
-
"graphile-cache": "^3.
|
|
66
|
+
"graphile-cache": "^3.12.0",
|
|
66
67
|
"graphile-config": "1.0.1",
|
|
67
|
-
"graphile-llm": "^0.
|
|
68
|
-
"graphile-settings": "^5.
|
|
68
|
+
"graphile-llm": "^0.10.0",
|
|
69
|
+
"graphile-settings": "^5.3.0",
|
|
69
70
|
"graphile-utils": "5.0.1",
|
|
70
71
|
"graphql": "16.13.0",
|
|
71
72
|
"graphql-upload": "^13.0.0",
|
|
72
73
|
"lru-cache": "^11.2.7",
|
|
73
74
|
"multer": "^2.1.1",
|
|
74
75
|
"pg": "^8.21.0",
|
|
75
|
-
"pg-cache": "^3.
|
|
76
|
-
"pg-env": "^1.
|
|
77
|
-
"pg-query-context": "^2.
|
|
76
|
+
"pg-cache": "^3.11.0",
|
|
77
|
+
"pg-env": "^1.15.0",
|
|
78
|
+
"pg-query-context": "^2.16.0",
|
|
78
79
|
"pg-sql2": "5.0.1",
|
|
79
80
|
"postgraphile": "5.0.3",
|
|
80
81
|
"request-ip": "^3.3.0"
|
|
@@ -89,10 +90,10 @@
|
|
|
89
90
|
"@types/pg": "^8.20.0",
|
|
90
91
|
"@types/request-ip": "^0.0.41",
|
|
91
92
|
"cookie-parser": "^1.4.7",
|
|
92
|
-
"graphile-test": "4.
|
|
93
|
+
"graphile-test": "4.16.0",
|
|
93
94
|
"makage": "^0.3.0",
|
|
94
95
|
"nodemon": "^3.1.14",
|
|
95
96
|
"ts-node": "^10.9.2"
|
|
96
97
|
},
|
|
97
|
-
"gitHead": "
|
|
98
|
+
"gitHead": "ccc303b2a2d1d9701648efc124ea1d9f2714711f"
|
|
98
99
|
}
|
package/server.js
CHANGED
|
@@ -34,6 +34,7 @@ const captcha_1 = require("./middleware/captcha");
|
|
|
34
34
|
const cookie_1 = require("./middleware/cookie");
|
|
35
35
|
const upload_1 = require("./middleware/upload");
|
|
36
36
|
const llm_api_1 = require("./middleware/llm-api");
|
|
37
|
+
const express_context_1 = require("@constructive-io/express-context");
|
|
37
38
|
const debug_sampler_1 = require("./diagnostics/debug-sampler");
|
|
38
39
|
const log = new logger_1.Logger('server');
|
|
39
40
|
/**
|
|
@@ -142,10 +143,12 @@ class Server {
|
|
|
142
143
|
app.use('/graphql', multipart_bridge_1.multipartBridge);
|
|
143
144
|
app.use((0, url_domains_1.middleware)());
|
|
144
145
|
app.use(request_ip_1.default.mw());
|
|
146
|
+
app.use((0, express_context_1.requestIdMiddleware)());
|
|
145
147
|
app.use(requestLogger);
|
|
146
148
|
app.use(api);
|
|
147
149
|
app.post('/upload', uploadAuthenticate, ...upload_1.uploadRoute);
|
|
148
150
|
app.use(authenticate);
|
|
151
|
+
app.use((0, express_context_1.createContextMiddleware)({ pg: effectiveOpts.pg }));
|
|
149
152
|
app.use((0, captcha_1.createCaptchaMiddleware)());
|
|
150
153
|
// CSRF protection for cookie-authenticated requests
|
|
151
154
|
// Skip CSRF for Bearer token auth (not vulnerable to CSRF) and anonymous requests
|
package/types.d.ts
CHANGED
|
@@ -1,129 +1,6 @@
|
|
|
1
1
|
import type { PgpmOptions } from '@pgpmjs/types';
|
|
2
2
|
import type { ApiOptions as ApiConfig } from '@constructive-io/graphql-types';
|
|
3
|
-
export
|
|
4
|
-
urls: string[];
|
|
5
|
-
}
|
|
6
|
-
export interface PublicKeyChallengeData {
|
|
7
|
-
schema: string;
|
|
8
|
-
crypto_network: string;
|
|
9
|
-
sign_up_with_key: string;
|
|
10
|
-
sign_in_request_challenge: string;
|
|
11
|
-
sign_in_record_failure: string;
|
|
12
|
-
sign_in_with_challenge: string;
|
|
13
|
-
}
|
|
14
|
-
export interface GenericModuleData {
|
|
15
|
-
[key: string]: unknown;
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Resolved feature flags from database_settings + api_settings cascade.
|
|
19
|
-
* api_settings values (when non-null) override database_settings defaults.
|
|
20
|
-
*/
|
|
21
|
-
export interface DatabaseSettings {
|
|
22
|
-
enableAggregates: boolean;
|
|
23
|
-
enablePostgis: boolean;
|
|
24
|
-
enableSearch: boolean;
|
|
25
|
-
enableDirectUploads: boolean;
|
|
26
|
-
enablePresignedUploads: boolean;
|
|
27
|
-
enableManyToMany: boolean;
|
|
28
|
-
enableConnectionFilter: boolean;
|
|
29
|
-
enableLtree: boolean;
|
|
30
|
-
enableLlm: boolean;
|
|
31
|
-
enableRealtime: boolean;
|
|
32
|
-
enableBulk: boolean;
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Resolved pubkey challenge config from pubkey_settings typed table.
|
|
36
|
-
* Matches the shape expected by the PublicKeySignature Graphile plugin.
|
|
37
|
-
*/
|
|
38
|
-
export interface PubkeyChallengeSettings {
|
|
39
|
-
schema: string;
|
|
40
|
-
cryptoNetwork: string;
|
|
41
|
-
signUpWithKey: string;
|
|
42
|
-
signInRequestChallenge: string;
|
|
43
|
-
signInRecordFailure: string;
|
|
44
|
-
signInWithChallenge: string;
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Resolved WebAuthn config from webauthn_settings typed table.
|
|
48
|
-
* Stored on ApiStructure for future server-side WebAuthn wiring.
|
|
49
|
-
*/
|
|
50
|
-
export interface WebauthnSettings {
|
|
51
|
-
schema: string;
|
|
52
|
-
credentialsSchema: string;
|
|
53
|
-
sessionsSchema: string;
|
|
54
|
-
sessionSecretsSchema: string;
|
|
55
|
-
rpId: string;
|
|
56
|
-
rpName: string;
|
|
57
|
-
originAllowlist: string[];
|
|
58
|
-
attestationType: string;
|
|
59
|
-
requireUserVerification: boolean;
|
|
60
|
-
residentKey: string;
|
|
61
|
-
challengeExpirySeconds: number;
|
|
62
|
-
}
|
|
63
|
-
export type ApiModule = {
|
|
64
|
-
name: 'cors';
|
|
65
|
-
data: CorsModuleData;
|
|
66
|
-
} | {
|
|
67
|
-
name: 'pubkey_challenge';
|
|
68
|
-
data: PublicKeyChallengeData;
|
|
69
|
-
} | {
|
|
70
|
-
name: string;
|
|
71
|
-
data?: GenericModuleData;
|
|
72
|
-
};
|
|
73
|
-
export interface RlsModule {
|
|
74
|
-
authenticate: string;
|
|
75
|
-
authenticateStrict: string;
|
|
76
|
-
privateSchema: {
|
|
77
|
-
schemaName: string;
|
|
78
|
-
};
|
|
79
|
-
publicSchema: {
|
|
80
|
-
schemaName: string;
|
|
81
|
-
};
|
|
82
|
-
currentRole: string;
|
|
83
|
-
currentRoleId: string;
|
|
84
|
-
currentIpAddress: string;
|
|
85
|
-
currentUserAgent: string;
|
|
86
|
-
}
|
|
87
|
-
/**
|
|
88
|
-
* Server-visible subset of app_auth_settings (lives in the tenant DB private schema).
|
|
89
|
-
* Discovered dynamically via metaschema_modules_public.sessions_module.
|
|
90
|
-
* Loaded once per API resolution and cached alongside the ApiStructure.
|
|
91
|
-
*/
|
|
92
|
-
export interface AuthSettings {
|
|
93
|
-
/** Cookie configuration */
|
|
94
|
-
cookieSecure?: boolean;
|
|
95
|
-
cookieSamesite?: string;
|
|
96
|
-
cookieDomain?: string | null;
|
|
97
|
-
cookieHttponly?: boolean;
|
|
98
|
-
cookieMaxAge?: string | null;
|
|
99
|
-
cookiePath?: string;
|
|
100
|
-
/** Remember me duration (seconds) for extended session cookies */
|
|
101
|
-
rememberMeDuration?: string | null;
|
|
102
|
-
/** reCAPTCHA / CAPTCHA */
|
|
103
|
-
enableCaptcha?: boolean;
|
|
104
|
-
captchaSiteKey?: string | null;
|
|
105
|
-
}
|
|
106
|
-
export interface ApiStructure {
|
|
107
|
-
apiId?: string;
|
|
108
|
-
dbname: string;
|
|
109
|
-
anonRole: string;
|
|
110
|
-
roleName: string;
|
|
111
|
-
schema: string[];
|
|
112
|
-
apiModules: ApiModule[];
|
|
113
|
-
rlsModule?: RlsModule;
|
|
114
|
-
domains?: string[];
|
|
115
|
-
databaseId?: string;
|
|
116
|
-
isPublic?: boolean;
|
|
117
|
-
authSettings?: AuthSettings;
|
|
118
|
-
corsOrigins?: string[];
|
|
119
|
-
databaseSettings?: DatabaseSettings;
|
|
120
|
-
pubkeyChallengeSettings?: PubkeyChallengeSettings;
|
|
121
|
-
webauthnSettings?: WebauthnSettings;
|
|
122
|
-
}
|
|
123
|
-
export type ApiError = {
|
|
124
|
-
errorHtml: string;
|
|
125
|
-
};
|
|
126
|
-
export type ApiConfigResult = ApiStructure | ApiError | null;
|
|
3
|
+
export type { ApiConfigResult, ApiError, ApiModule, ApiStructure, AuthSettings, CorsModuleData, DatabaseSettings, GenericModuleData, PubkeyChallengeSettings, PublicKeyChallengeData, RlsModule, WebauthnSettings, } from '@constructive-io/express-context';
|
|
127
4
|
export type ApiOptions = PgpmOptions & {
|
|
128
5
|
api?: ApiConfig;
|
|
129
6
|
};
|