@constructive-io/graphql-server 5.14.6 → 5.15.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.
@@ -0,0 +1,486 @@
1
+ /**
2
+ * router — Express router for the agentic-server
3
+ *
4
+ * Provides REST endpoints for AI agent conversations:
5
+ *
6
+ * POST /v1/threads → create thread
7
+ * POST /v1/threads/:thread_id/messages → send message + stream response
8
+ * POST /v1/orgs/:entity_id/threads → create thread (entity-scoped)
9
+ * POST /v1/orgs/:entity_id/threads/:thread_id/messages → send message (entity-scoped)
10
+ * POST /v1/embed → generate embedding
11
+ *
12
+ * All routes require `req.constructive` (from @constructive-io/express-context).
13
+ * Billing (check_quota + record_usage) and inference logging are automatic
14
+ * when the billing/inference_log modules are provisioned.
15
+ *
16
+ * LLM provider config is resolved per-database via `ctx.useLlm()` from the
17
+ * llm_module table, falling back to env vars (EMBEDDER_*, CHAT_*) when the
18
+ * module is not provisioned.
19
+ */
20
+ import { OllamaAdapter } from '@agentic-kit/ollama';
21
+ import { getEnvOptions as getLlmEnvOptions } from '@constructive-io/llm-env';
22
+ import { Logger } from '@pgpmjs/logger';
23
+ import express, { Router } from 'express';
24
+ const log = new Logger('agentic-server');
25
+ function resolveChatAdapter(llm) {
26
+ const provider = llm?.chatProvider ?? getLlmEnvOptions().chat.provider;
27
+ const model = llm?.chatModel ?? getLlmEnvOptions().chat.model;
28
+ const baseUrl = llm?.chatBaseUrl ?? getLlmEnvOptions().chat.baseUrl;
29
+ if (provider === 'ollama') {
30
+ return { adapter: new OllamaAdapter(baseUrl), model, baseUrl, provider };
31
+ }
32
+ return null;
33
+ }
34
+ function resolveEmbeddingAdapter(llm) {
35
+ const provider = llm?.embeddingProvider ?? getLlmEnvOptions().embedding.provider;
36
+ const model = llm?.embeddingModel ?? getLlmEnvOptions().embedding.model;
37
+ const baseUrl = llm?.embeddingBaseUrl ?? getLlmEnvOptions().embedding.baseUrl;
38
+ if (provider === 'ollama') {
39
+ return { adapter: new OllamaAdapter(baseUrl), model, provider };
40
+ }
41
+ return null;
42
+ }
43
+ // ─── Route Handlers ─────────────────────────────────────────────────────────
44
+ async function handleCreateThread(req, res, entityId) {
45
+ const ctx = req.constructive;
46
+ if (!ctx?.userId) {
47
+ res.status(401).json({ error: 'Authentication required' });
48
+ return;
49
+ }
50
+ const agentChat = await ctx.useModule('agentChat');
51
+ if (!agentChat?.threadTableName) {
52
+ res.status(404).json({ error: 'Agent module not provisioned for this database' });
53
+ return;
54
+ }
55
+ const body = req.body || {};
56
+ const { schemaName, threadTableName } = agentChat;
57
+ const result = await ctx.withPgClient(async (client) => {
58
+ const { rows } = await client.query(`INSERT INTO "${schemaName}"."${threadTableName}"
59
+ (entity_id, owner_id, mode, model, system_prompt, title)
60
+ VALUES ($1, $2, $3, $4, $5, $6)
61
+ RETURNING id, mode, model, system_prompt, status, created_at`, [
62
+ entityId,
63
+ ctx.userId,
64
+ body.mode ?? 'ask',
65
+ body.model ?? null,
66
+ body.system_prompt ?? null,
67
+ body.title ?? null
68
+ ]);
69
+ return rows[0];
70
+ });
71
+ res.status(201).json({
72
+ id: result.id,
73
+ mode: result.mode,
74
+ model: result.model,
75
+ system_prompt: result.system_prompt,
76
+ status: result.status,
77
+ created_at: result.created_at
78
+ });
79
+ }
80
+ async function handleSendMessage(req, res, entityId) {
81
+ const ctx = req.constructive;
82
+ if (!ctx?.userId) {
83
+ res.status(401).json({ error: 'Authentication required' });
84
+ return;
85
+ }
86
+ const agentChat = await ctx.useModule('agentChat');
87
+ if (!agentChat?.threadTableName || !agentChat?.messageTableName) {
88
+ res.status(404).json({ error: 'Agent module not provisioned for this database' });
89
+ return;
90
+ }
91
+ const body = req.body || {};
92
+ if (!body.messages?.length) {
93
+ res.status(400).json({ error: 'messages[] is required and must not be empty' });
94
+ return;
95
+ }
96
+ const { schemaName, threadTableName, messageTableName } = agentChat;
97
+ const threadId = req.params.thread_id;
98
+ const userId = ctx.userId;
99
+ // Verify thread exists (RLS enforced)
100
+ const threadRow = await ctx.withPgClient(async (client) => {
101
+ const { rows } = await client.query(`SELECT id, mode, model, system_prompt, status
102
+ FROM "${schemaName}"."${threadTableName}"
103
+ WHERE id = $1`, [threadId]);
104
+ return rows[0];
105
+ });
106
+ if (!threadRow) {
107
+ res.status(404).json({ error: 'Thread not found' });
108
+ return;
109
+ }
110
+ // Resolve shared billing client and LLM config (lazy, cached per request)
111
+ const [billing, llm] = await Promise.all([ctx.useBilling(), ctx.useLlm()]);
112
+ const chatAdapter = resolveChatAdapter(llm);
113
+ if (!chatAdapter) {
114
+ res.status(503).json({ error: 'No LLM provider configured' });
115
+ return;
116
+ }
117
+ const model = body.model ?? threadRow.model ?? chatAdapter.model;
118
+ const meterSlug = model;
119
+ // Quota check
120
+ if (billing) {
121
+ const allowed = await billing.checkQuota(meterSlug);
122
+ if (!allowed) {
123
+ res.status(429).json({
124
+ error: 'Token quota exceeded',
125
+ meter: meterSlug,
126
+ entity_id: entityId
127
+ });
128
+ return;
129
+ }
130
+ }
131
+ // Persist user messages
132
+ await ctx.withPgClient(async (client) => {
133
+ for (const msg of body.messages) {
134
+ if (msg.role === 'user') {
135
+ await client.query(`INSERT INTO "${schemaName}"."${messageTableName}"
136
+ (thread_id, owner_id, entity_id, author_role, parts)
137
+ VALUES ($1, $2, (SELECT entity_id FROM "${schemaName}"."${threadTableName}" WHERE id = $1), $3, $4)`, [threadId, userId, 'user', JSON.stringify([{ type: 'text', text: msg.content }])]);
138
+ }
139
+ }
140
+ });
141
+ // Load full thread history
142
+ const history = await ctx.withPgClient(async (client) => {
143
+ const { rows } = await client.query(`SELECT author_role, parts, created_at
144
+ FROM "${schemaName}"."${messageTableName}"
145
+ WHERE thread_id = $1
146
+ ORDER BY created_at ASC`, [threadId]);
147
+ return rows;
148
+ });
149
+ const llmMessages = [];
150
+ if (threadRow.system_prompt) {
151
+ llmMessages.push({ role: 'system', content: threadRow.system_prompt });
152
+ }
153
+ for (const row of history) {
154
+ const parts = Array.isArray(row.parts) ? row.parts : [];
155
+ const textContent = parts
156
+ .filter((p) => p.type === 'text')
157
+ .map((p) => p.text)
158
+ .join('');
159
+ if (textContent) {
160
+ llmMessages.push({
161
+ role: row.author_role === 'user' ? 'user' : 'assistant',
162
+ content: textContent
163
+ });
164
+ }
165
+ }
166
+ const startTime = Date.now();
167
+ const shouldStream = body.stream !== false;
168
+ if (shouldStream) {
169
+ await handleStreamingResponse(req, res, {
170
+ ctx, chatAdapter, model, llmMessages, body,
171
+ entityId, userId, threadId,
172
+ schemaName, threadTableName, messageTableName,
173
+ billing, startTime, meterSlug
174
+ });
175
+ }
176
+ else {
177
+ await handleBatchResponse(req, res, {
178
+ ctx, chatAdapter, model, llmMessages, body,
179
+ entityId, userId, threadId,
180
+ schemaName, threadTableName, messageTableName,
181
+ billing, startTime, meterSlug
182
+ });
183
+ }
184
+ }
185
+ async function handleStreamingResponse(_req, res, mc) {
186
+ const { ctx, chatAdapter, model, llmMessages, body, entityId, userId, threadId, schemaName, threadTableName, messageTableName, billing, startTime, meterSlug } = mc;
187
+ res.writeHead(200, {
188
+ 'Content-Type': 'text/event-stream',
189
+ 'Cache-Control': 'no-cache',
190
+ Connection: 'keep-alive',
191
+ 'X-Accel-Buffering': 'no'
192
+ });
193
+ const messageId = `msg_${Date.now()}`;
194
+ try {
195
+ const systemMsg = llmMessages.find(m => m.role === 'system');
196
+ const nonSystem = llmMessages.filter(m => m.role !== 'system');
197
+ const modelDesc = chatAdapter.adapter.createModel(model, { maxOutputTokens: undefined });
198
+ const context = {
199
+ systemPrompt: systemMsg?.content,
200
+ messages: nonSystem.map((m) => ({
201
+ role: m.role,
202
+ content: m.content,
203
+ timestamp: Date.now()
204
+ }))
205
+ };
206
+ const stream = chatAdapter.adapter.stream(modelDesc, context, {
207
+ temperature: body.temperature
208
+ });
209
+ let streamedContent = '';
210
+ for await (const event of stream) {
211
+ if (event.type === 'text_delta') {
212
+ streamedContent += event.delta;
213
+ const sseEvent = {
214
+ id: messageId,
215
+ choices: [{
216
+ index: 0,
217
+ delta: { content: event.delta, role: 'assistant' },
218
+ finish_reason: null
219
+ }],
220
+ model
221
+ };
222
+ res.write(`data: ${JSON.stringify(sseEvent)}\n\n`);
223
+ }
224
+ }
225
+ const result = await stream.result();
226
+ const content = streamedContent;
227
+ const latencyMs = Date.now() - startTime;
228
+ const usage = {
229
+ input: result.usage.input,
230
+ output: result.usage.output,
231
+ reasoning: result.usage.reasoning,
232
+ cacheRead: result.usage.cacheRead,
233
+ cacheWrite: result.usage.cacheWrite,
234
+ totalTokens: result.usage.totalTokens
235
+ };
236
+ res.write('data: [DONE]\n\n');
237
+ res.end();
238
+ // Persist assistant message (fire-and-forget)
239
+ if (content) {
240
+ ctx.withPgClient(async (client) => {
241
+ await client.query(`INSERT INTO "${schemaName}"."${messageTableName}"
242
+ (thread_id, owner_id, entity_id, author_role, parts, model)
243
+ VALUES ($1, $2, (SELECT entity_id FROM "${schemaName}"."${threadTableName}" WHERE id = $1), $3, $4, $5)`, [threadId, userId, 'assistant', JSON.stringify([{ type: 'text', text: content }]), model]);
244
+ }).catch((err) => log.error('Failed to persist assistant message:', err));
245
+ }
246
+ // Record billing usage + inference log (fire-and-forget)
247
+ if (billing && usage.totalTokens > 0) {
248
+ billing.recordUsage(meterSlug, usage.totalTokens, {
249
+ input_tokens: usage.input,
250
+ output_tokens: usage.output,
251
+ cache_read_tokens: usage.cacheRead,
252
+ cache_write_tokens: usage.cacheWrite,
253
+ model,
254
+ latency_ms: latencyMs,
255
+ stream: true
256
+ }).catch(() => { });
257
+ billing.logInference({
258
+ entityId, actorId: userId, model, provider: chatAdapter.provider,
259
+ service: 'llm', operation: 'chat',
260
+ inputTokens: usage.input, outputTokens: usage.output,
261
+ totalTokens: usage.totalTokens, latencyMs, status: 'ok'
262
+ }).catch(() => { });
263
+ }
264
+ }
265
+ catch (streamErr) {
266
+ log.error('Streaming error:', streamErr);
267
+ const errorEvent = { error: { message: streamErr.message, type: 'stream_error' } };
268
+ res.write(`data: ${JSON.stringify(errorEvent)}\n\n`);
269
+ res.write('data: [DONE]\n\n');
270
+ res.end();
271
+ }
272
+ }
273
+ async function handleBatchResponse(_req, res, mc) {
274
+ const { ctx, chatAdapter, model, llmMessages, body, entityId, userId, threadId, schemaName, threadTableName, messageTableName, billing, startTime, meterSlug } = mc;
275
+ const systemMsg = llmMessages.find(m => m.role === 'system');
276
+ const nonSystem = llmMessages.filter(m => m.role !== 'system');
277
+ const modelDesc = chatAdapter.adapter.createModel(model, { maxOutputTokens: undefined });
278
+ const context = {
279
+ systemPrompt: systemMsg?.content,
280
+ messages: nonSystem.map((m) => ({
281
+ role: m.role,
282
+ content: m.content,
283
+ timestamp: Date.now()
284
+ }))
285
+ };
286
+ const stream = chatAdapter.adapter.stream(modelDesc, context, {
287
+ temperature: body.temperature
288
+ });
289
+ const result = await stream.result();
290
+ const content = result.content
291
+ .filter((block) => block.type === 'text')
292
+ .map((block) => block.text)
293
+ .join('');
294
+ const latencyMs = Date.now() - startTime;
295
+ const usage = {
296
+ input: result.usage.input,
297
+ output: result.usage.output,
298
+ reasoning: result.usage.reasoning,
299
+ cacheRead: result.usage.cacheRead,
300
+ cacheWrite: result.usage.cacheWrite,
301
+ totalTokens: result.usage.totalTokens
302
+ };
303
+ // Persist assistant message
304
+ await ctx.withPgClient(async (client) => {
305
+ await client.query(`INSERT INTO "${schemaName}"."${messageTableName}"
306
+ (thread_id, owner_id, entity_id, author_role, parts, model)
307
+ VALUES ($1, $2, (SELECT entity_id FROM "${schemaName}"."${threadTableName}" WHERE id = $1), $3, $4, $5)`, [threadId, userId, 'assistant', JSON.stringify([{ type: 'text', text: content }]), model]);
308
+ });
309
+ // Record billing + inference log (fire-and-forget)
310
+ if (billing && usage.totalTokens > 0) {
311
+ billing.recordUsage(meterSlug, usage.totalTokens, {
312
+ input_tokens: usage.input,
313
+ output_tokens: usage.output,
314
+ cache_read_tokens: usage.cacheRead,
315
+ cache_write_tokens: usage.cacheWrite,
316
+ model,
317
+ latency_ms: latencyMs,
318
+ stream: false
319
+ }).catch(() => { });
320
+ billing.logInference({
321
+ entityId, actorId: userId, model, provider: chatAdapter.provider,
322
+ service: 'llm', operation: 'chat',
323
+ inputTokens: usage.input, outputTokens: usage.output,
324
+ totalTokens: usage.totalTokens, latencyMs, status: 'ok'
325
+ }).catch(() => { });
326
+ }
327
+ res.json({
328
+ id: `msg_${Date.now()}`,
329
+ choices: [{
330
+ index: 0,
331
+ message: { role: 'assistant', content },
332
+ finish_reason: 'stop'
333
+ }],
334
+ model,
335
+ usage: {
336
+ prompt_tokens: usage.input,
337
+ completion_tokens: usage.output,
338
+ total_tokens: usage.totalTokens
339
+ }
340
+ });
341
+ }
342
+ // ─── Embedding Handler ──────────────────────────────────────────────────────
343
+ async function handleEmbed(req, res) {
344
+ const ctx = req.constructive;
345
+ if (!ctx?.userId) {
346
+ res.status(401).json({ error: 'Authentication required' });
347
+ return;
348
+ }
349
+ const body = req.body || {};
350
+ if (!body.input) {
351
+ res.status(400).json({ error: 'input is required' });
352
+ return;
353
+ }
354
+ const llm = await ctx.useLlm();
355
+ const embedAdapter = resolveEmbeddingAdapter(llm);
356
+ if (!embedAdapter) {
357
+ res.status(503).json({ error: 'No embedding provider configured' });
358
+ return;
359
+ }
360
+ const model = body.model ?? embedAdapter.model;
361
+ const inputs = Array.isArray(body.input) ? body.input : [body.input];
362
+ // Resolve shared billing client
363
+ const billing = await ctx.useBilling();
364
+ // Quota check
365
+ if (billing) {
366
+ const allowed = await billing.checkQuota(model);
367
+ if (!allowed) {
368
+ res.status(429).json({ error: 'Embedding quota exceeded', meter: model });
369
+ return;
370
+ }
371
+ }
372
+ const startTime = Date.now();
373
+ try {
374
+ const results = await Promise.all(inputs.map((text) => embedAdapter.adapter.embed(text, model)));
375
+ const latencyMs = Date.now() - startTime;
376
+ const totalTokens = results.reduce((sum, r) => sum + r.promptTokens, 0);
377
+ // Record usage + inference log (fire-and-forget)
378
+ if (billing && totalTokens > 0) {
379
+ billing.recordUsage(model, totalTokens, {
380
+ input_tokens: totalTokens,
381
+ model,
382
+ latency_ms: latencyMs,
383
+ batch_size: inputs.length
384
+ }).catch(() => { });
385
+ billing.logInference({
386
+ entityId: ctx.userId,
387
+ actorId: ctx.userId,
388
+ model,
389
+ provider: embedAdapter.provider,
390
+ service: 'embedding',
391
+ operation: 'embed',
392
+ inputTokens: totalTokens,
393
+ outputTokens: 0,
394
+ totalTokens,
395
+ latencyMs,
396
+ status: 'ok'
397
+ }).catch(() => { });
398
+ }
399
+ res.json({
400
+ object: 'list',
401
+ data: results.map((r, i) => ({
402
+ object: 'embedding',
403
+ index: i,
404
+ embedding: r.embedding
405
+ })),
406
+ model,
407
+ usage: {
408
+ prompt_tokens: totalTokens,
409
+ total_tokens: totalTokens
410
+ }
411
+ });
412
+ }
413
+ catch (err) {
414
+ log.error('Embedding error:', err);
415
+ res.status(500).json({ error: err.message ?? 'Embedding failed' });
416
+ }
417
+ }
418
+ // ─── Router Factory ─────────────────────────────────────────────────────────
419
+ export function createAgenticRouter() {
420
+ const router = Router();
421
+ router.use(express.json());
422
+ // Entity-scoped routes
423
+ router.post('/v1/orgs/:entity_id/threads', async (req, res) => {
424
+ try {
425
+ await handleCreateThread(req, res, req.params.entity_id);
426
+ }
427
+ catch (err) {
428
+ log.error('Error creating thread:', err);
429
+ if (!res.headersSent)
430
+ res.status(500).json({ error: 'Internal server error' });
431
+ }
432
+ });
433
+ router.post('/v1/orgs/:entity_id/threads/:thread_id/messages', async (req, res) => {
434
+ try {
435
+ await handleSendMessage(req, res, req.params.entity_id);
436
+ }
437
+ catch (err) {
438
+ log.error('Error in messages endpoint:', err);
439
+ if (!res.headersSent)
440
+ res.status(500).json({ error: 'Internal server error' });
441
+ }
442
+ });
443
+ // Global routes (entity_id = user_id from JWT)
444
+ router.post('/v1/threads', async (req, res) => {
445
+ try {
446
+ const userId = req.constructive?.userId;
447
+ if (!userId) {
448
+ res.status(401).json({ error: 'Authentication required' });
449
+ return;
450
+ }
451
+ await handleCreateThread(req, res, userId);
452
+ }
453
+ catch (err) {
454
+ log.error('Error creating thread:', err);
455
+ if (!res.headersSent)
456
+ res.status(500).json({ error: 'Internal server error' });
457
+ }
458
+ });
459
+ router.post('/v1/threads/:thread_id/messages', async (req, res) => {
460
+ try {
461
+ const userId = req.constructive?.userId;
462
+ if (!userId) {
463
+ res.status(401).json({ error: 'Authentication required' });
464
+ return;
465
+ }
466
+ await handleSendMessage(req, res, userId);
467
+ }
468
+ catch (err) {
469
+ log.error('Error in messages endpoint:', err);
470
+ if (!res.headersSent)
471
+ res.status(500).json({ error: 'Internal server error' });
472
+ }
473
+ });
474
+ // Embedding endpoint
475
+ router.post('/v1/embed', async (req, res) => {
476
+ try {
477
+ await handleEmbed(req, res);
478
+ }
479
+ catch (err) {
480
+ log.error('Error in embed endpoint:', err);
481
+ if (!res.headersSent)
482
+ res.status(500).json({ error: 'Internal server error' });
483
+ }
484
+ });
485
+ return router;
486
+ }
@@ -1,6 +1,6 @@
1
+ import { Pool } from 'pg';
1
2
  import { buildConnectionString, getPgPool } from 'pg-cache';
2
3
  import { getPgEnvOptions } from 'pg-env';
3
- import { Pool } from 'pg';
4
4
  const ACTIVE_ACTIVITY_SQL = `
5
5
  select
6
6
  pid,
@@ -1,6 +1,6 @@
1
1
  import os from 'node:os';
2
2
  import v8 from 'node:v8';
3
- import { svcCache, SVC_CACHE_TTL_MS } from '@pgpmjs/server-utils';
3
+ import { SVC_CACHE_TTL_MS, svcCache } from '@pgpmjs/server-utils';
4
4
  import { getCacheStats } from 'graphile-cache';
5
5
  import { getInFlightCount, getInFlightKeys } from '../middleware/graphile';
6
6
  import { getGraphileBuildStats } from '../middleware/observability/graphile-build-stats';
package/esm/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  export * from './server';
2
2
  // Export middleware for use in testing packages
3
- export { createApiMiddleware, getSubdomain, getApiConfig } from './middleware/api';
3
+ export { createApiMiddleware, getApiConfig, getSubdomain } from './middleware/api';
4
4
  export { createAuthenticateMiddleware } from './middleware/auth';
5
5
  export { cors } from './middleware/cors';
6
- export { graphile } from './middleware/graphile';
7
6
  export { flush, flushService } from './middleware/flush';
7
+ export { graphile } from './middleware/graphile';
@@ -1,10 +1,10 @@
1
+ import './types'; // for Request type
1
2
  import { errors } from '@constructive-io/errors';
2
3
  import { getNodeEnv } from '@pgpmjs/env';
3
4
  import { Logger } from '@pgpmjs/logger';
4
5
  import { getPgPool } from 'pg-cache';
5
6
  import pgQueryContext from 'pg-query-context';
6
7
  import { respondWithGraphQLError } from '../errors/graphql-response';
7
- import './types'; // for Request type
8
8
  const log = new Logger('auth');
9
9
  const isDev = () => getNodeEnv() === 'development';
10
10
  /** Default cookie name for session tokens. */
@@ -1,7 +1,7 @@
1
+ import './types'; // for Request type
1
2
  import { errors } from '@constructive-io/errors';
2
3
  import { Logger } from '@pgpmjs/logger';
3
4
  import { respondWithGraphQLError } from '../errors/graphql-response';
4
- import './types'; // for Request type
5
5
  const log = new Logger('captcha');
6
6
  /** Google reCAPTCHA verification endpoint */
7
7
  const RECAPTCHA_VERIFY_URL = 'https://www.google.com/recaptcha/api/siteverify';
@@ -1,6 +1,6 @@
1
+ import './types'; // for Request type
1
2
  import { parseUrl } from '@constructive-io/url-domains';
2
3
  import corsPlugin from 'cors';
3
- import './types'; // for Request type
4
4
  /**
5
5
  * Unified CORS middleware for Constructive API
6
6
  *
@@ -1,9 +1,9 @@
1
+ import './types';
1
2
  import { getNodeEnv } from '@pgpmjs/env';
2
3
  import { Logger } from '@pgpmjs/logger';
3
- import { isApiError } from '../errors/api-errors';
4
- import errorPage404Message from '../errors/404-message';
5
4
  import errorPage50x from '../errors/50x';
6
- import './types';
5
+ import errorPage404Message from '../errors/404-message';
6
+ import { isApiError } from '../errors/api-errors';
7
7
  const log = new Logger('error-handler');
8
8
  const isDevelopment = () => getNodeEnv() === 'development';
9
9
  const wantsJson = (req) => {
@@ -17,9 +17,9 @@
17
17
  * request's pgSettings (role + jwt.claims.* incl. jwt.claims.api_id) in a
18
18
  * transaction — RLS is fully enforced; no superuser or bypass path is used.
19
19
  */
20
+ import { QueryBuilder } from '@constructive-io/query-builder';
20
21
  import { Logger } from '@pgpmjs/logger';
21
22
  import { isUuid } from '@pgpmjs/server-utils';
22
- import { QueryBuilder } from '@constructive-io/query-builder';
23
23
  import express, { Router } from 'express';
24
24
  const log = new Logger('fn');
25
25
  const notFound = (res) => {
@@ -1,5 +1,5 @@
1
- import { randomUUID } from 'crypto';
2
1
  import { Logger } from '@pgpmjs/logger';
2
+ import { randomUUID } from 'crypto';
3
3
  const log = new Logger('server');
4
4
  const SAFE_REQUEST_ID = /^[a-zA-Z0-9\-_]{1,128}$/;
5
5
  export const createRequestLogger = ({ observabilityEnabled }) => {
@@ -1,6 +1,6 @@
1
- import { Logger } from '@pgpmjs/logger';
2
1
  import '../middleware/types';
3
- import { SESSION_COOKIE_NAME, DEVICE_TOKEN_COOKIE_NAME, getSessionCookieConfig, getDeviceTokenCookieConfig, } from '../middleware/cookie';
2
+ import { Logger } from '@pgpmjs/logger';
3
+ import { DEVICE_TOKEN_COOKIE_NAME, getDeviceTokenCookieConfig, getSessionCookieConfig, SESSION_COOKIE_NAME, } from '../middleware/cookie';
4
4
  const log = new Logger('auth-cookie');
5
5
  /**
6
6
  * Serialize a cookie to a Set-Cookie header value.
package/esm/server.js CHANGED
@@ -4,13 +4,13 @@ import { getEnvOptions } from '@constructive-io/graphql-env';
4
4
  import { middleware as parseDomains } from '@constructive-io/url-domains';
5
5
  import { Logger } from '@pgpmjs/logger';
6
6
  import { healthz, poweredBy, svcCache, trustProxy } from '@pgpmjs/server-utils';
7
- import { createAgenticRouter } from 'agentic-server';
8
7
  import cookieParser from 'cookie-parser';
9
8
  import express from 'express';
10
9
  import { closeAllCaches, graphileCache } from 'graphile-cache';
11
10
  import graphqlUpload from 'graphql-upload';
12
11
  import { getPgPool } from 'pg-cache';
13
12
  import requestIp from 'request-ip';
13
+ import { createAgenticRouter } from './agentic';
14
14
  import { closeDebugDatabasePools } from './diagnostics/debug-db-snapshot';
15
15
  import { startDebugSampler } from './diagnostics/debug-sampler';
16
16
  import { isDevelopmentObservabilityMode, isGraphqlObservabilityEnabled, isGraphqlObservabilityRequested, isLoopbackHost } from './diagnostics/observability';
package/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export * from './server';
2
- export { createApiMiddleware, getSubdomain, getApiConfig } from './middleware/api';
2
+ export { createApiMiddleware, getApiConfig, getSubdomain } from './middleware/api';
3
3
  export { createAuthenticateMiddleware } from './middleware/auth';
4
4
  export { cors } from './middleware/cors';
5
- export { graphile } from './middleware/graphile';
6
5
  export { flush, flushService } from './middleware/flush';
6
+ export { graphile } from './middleware/graphile';
package/index.js CHANGED
@@ -14,19 +14,19 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.flushService = exports.flush = exports.graphile = exports.cors = exports.createAuthenticateMiddleware = exports.getApiConfig = exports.getSubdomain = exports.createApiMiddleware = void 0;
17
+ exports.graphile = exports.flushService = exports.flush = exports.cors = exports.createAuthenticateMiddleware = exports.getSubdomain = exports.getApiConfig = exports.createApiMiddleware = void 0;
18
18
  __exportStar(require("./server"), exports);
19
19
  // Export middleware for use in testing packages
20
20
  var api_1 = require("./middleware/api");
21
21
  Object.defineProperty(exports, "createApiMiddleware", { enumerable: true, get: function () { return api_1.createApiMiddleware; } });
22
- Object.defineProperty(exports, "getSubdomain", { enumerable: true, get: function () { return api_1.getSubdomain; } });
23
22
  Object.defineProperty(exports, "getApiConfig", { enumerable: true, get: function () { return api_1.getApiConfig; } });
23
+ Object.defineProperty(exports, "getSubdomain", { enumerable: true, get: function () { return api_1.getSubdomain; } });
24
24
  var auth_1 = require("./middleware/auth");
25
25
  Object.defineProperty(exports, "createAuthenticateMiddleware", { enumerable: true, get: function () { return auth_1.createAuthenticateMiddleware; } });
26
26
  var cors_1 = require("./middleware/cors");
27
27
  Object.defineProperty(exports, "cors", { enumerable: true, get: function () { return cors_1.cors; } });
28
- var graphile_1 = require("./middleware/graphile");
29
- Object.defineProperty(exports, "graphile", { enumerable: true, get: function () { return graphile_1.graphile; } });
30
28
  var flush_1 = require("./middleware/flush");
31
29
  Object.defineProperty(exports, "flush", { enumerable: true, get: function () { return flush_1.flush; } });
32
30
  Object.defineProperty(exports, "flushService", { enumerable: true, get: function () { return flush_1.flushService; } });
31
+ var graphile_1 = require("./middleware/graphile");
32
+ Object.defineProperty(exports, "graphile", { enumerable: true, get: function () { return graphile_1.graphile; } });
@@ -1,4 +1,4 @@
1
+ import './types';
1
2
  import { PgpmOptions } from '@pgpmjs/types';
2
3
  import { RequestHandler } from 'express';
3
- import './types';
4
4
  export declare const createAuthenticateMiddleware: (opts: PgpmOptions) => RequestHandler;
@@ -4,13 +4,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.createAuthenticateMiddleware = void 0;
7
+ require("./types"); // for Request type
7
8
  const errors_1 = require("@constructive-io/errors");
8
9
  const env_1 = require("@pgpmjs/env");
9
10
  const logger_1 = require("@pgpmjs/logger");
10
11
  const pg_cache_1 = require("pg-cache");
11
12
  const pg_query_context_1 = __importDefault(require("pg-query-context"));
12
13
  const graphql_response_1 = require("../errors/graphql-response");
13
- require("./types"); // for Request type
14
14
  const log = new logger_1.Logger('auth');
15
15
  const isDev = () => (0, env_1.getNodeEnv)() === 'development';
16
16
  /** Default cookie name for session tokens. */