@pikku/core 0.12.42 → 0.12.45

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/CHANGELOG.md CHANGED
@@ -1,3 +1,42 @@
1
+ ## 0.12.45
2
+
3
+ ### Patch Changes
4
+
5
+ - e9a778f: feat(config): add optional `postgres` pool config to `CoreConfig`
6
+
7
+ Postgres is a first-class adapter, so its runtime pool tuning now lives in the
8
+ core config (sibling to `workflow`), typed via the new `PostgresConfig`:
9
+ `maxPool`, `connectTimeout`, `idleTimeout`, `maxLifetime`, `statementTimeout`,
10
+ `prepare`. The connection string itself stays the flat `postgresUrl`/`sqliteDb`
11
+ field the CLI db commands read; this block is purely runtime pool options.
12
+
13
+ ## 0.12.44
14
+
15
+ ### Patch Changes
16
+
17
+ - 41ce2cb: Upgrade to TypeScript 6 and raise the minimum Node.js version to 22.
18
+
19
+ All packages now build against `typescript@^6.0.3` and declare `engines.node >= 22`. Internal tooling (`ts-json-schema-generator`, `zod-to-ts`) was bumped to TypeScript 6-compatible releases.
20
+
21
+ ## 0.12.43
22
+
23
+ ### Patch Changes
24
+
25
+ - a8022e5: fix(workflow): scope the step advisory lock to the claim, not execution
26
+
27
+ `executeWorkflowStep` held the step's advisory lock — and, in the Postgres
28
+ workflow service, the pooled DB connection backing it — across the entire step
29
+ body, including the step's own network and DB work. Under concurrency >= the DB
30
+ pool size this self-deadlocks: every running step pins a connection on its lock
31
+ transaction while its inner queries wait for a connection that never frees, so
32
+ nothing makes progress and the API hangs.
33
+
34
+ The lock is only needed to atomically _claim_ a step (read state + mark it
35
+ `running`); once a step is `running`, the existing status guard already prevents
36
+ any concurrent worker from re-running it. The lock now covers only the claim;
37
+ execution and result persistence run with the lock released and the connection
38
+ back in the pool.
39
+
1
40
  ## 0.12.42
2
41
 
3
42
  ### Patch Changes
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * @module @pikku/core
3
3
  */
4
- export type { CommonWireMeta, CoreConfig, CorePikkuMiddleware, CorePikkuMiddlewareConfig, CorePikkuMiddlewareFactory, CorePikkuMiddlewareGroup, CoreServices, CoreSingletonServices, CoreUserSession, CreateConfig, ServerLifecycle, FunctionMeta, FunctionRuntimeMeta, FunctionServicesMeta, FunctionWiresMeta, FunctionsMeta, FunctionsRuntimeMeta, JSONPrimitive, JSONValue, MakeRequired, MiddlewareMetadata, MiddlewarePriority, PermissionMetadata, PickOptional, PickRequired, PikkuAIMiddlewareHooks, PikkuWire, PikkuRawWire, PikkuWiringTypes, RequireAtLeastOne, SerializedError, WireServices, } from './types/core.types.js';
4
+ export type { CommonWireMeta, CoreConfig, CorePikkuMiddleware, CorePikkuMiddlewareConfig, CorePikkuMiddlewareFactory, CorePikkuMiddlewareGroup, CoreServices, CoreSingletonServices, CoreUserSession, CreateConfig, ServerLifecycle, FunctionMeta, FunctionRuntimeMeta, FunctionServicesMeta, FunctionWiresMeta, FunctionsMeta, FunctionsRuntimeMeta, JSONPrimitive, JSONValue, MakeRequired, MiddlewareMetadata, MiddlewarePriority, PermissionMetadata, PickOptional, PickRequired, PikkuAIMiddlewareHooks, PikkuWire, PikkuRawWire, PikkuWiringTypes, PostgresConfig, RequireAtLeastOne, SerializedError, WireServices, } from './types/core.types.js';
5
5
  export { pikkuAIMiddleware, pikkuChannelMiddleware, pikkuChannelMiddlewareFactory, pikkuMiddleware, pikkuMiddlewareFactory, } from './types/core.types.js';
6
6
  export type { CorePikkuAuth, CorePikkuAuthConfig, CorePikkuFunction, CorePikkuFunctionConfig, CorePikkuPermission, CorePikkuPermissionConfig, CorePikkuPermissionFactory, CorePikkuApprovalDescription, CorePermissionGroup, ZodLike, } from './function/functions.types.js';
7
7
  export { pikkuAuth, pikkuPermission, pikkuPermissionFactory, pikkuApprovalDescription, } from './function/functions.types.js';
@@ -21,4 +21,5 @@ export declare const authAPIKey: import("../types/core.types.js").CorePikkuMiddl
21
21
  logLevel?: import("../services/logger.js").LogLevel;
22
22
  secrets?: {};
23
23
  workflow?: import("../wirings/workflow/workflow.types.js").WorkflowServiceConfig;
24
+ postgres?: import("../types/core.types.js").PostgresConfig;
24
25
  }>, import("../types/core.types.js").CoreUserSession>;
@@ -37,4 +37,5 @@ export declare const authBearer: import("../types/core.types.js").CorePikkuMiddl
37
37
  logLevel?: import("../services/logger.js").LogLevel;
38
38
  secrets?: {};
39
39
  workflow?: import("../wirings/workflow/workflow.types.js").WorkflowServiceConfig;
40
+ postgres?: import("../types/core.types.js").PostgresConfig;
40
41
  }>, CoreUserSession>;
@@ -36,4 +36,5 @@ export declare const authCookie: import("../types/core.types.js").CorePikkuMiddl
36
36
  logLevel?: import("../services/logger.js").LogLevel;
37
37
  secrets?: {};
38
38
  workflow?: import("../wirings/workflow/workflow.types.js").WorkflowServiceConfig;
39
+ postgres?: import("../types/core.types.js").PostgresConfig;
39
40
  }>, import("../types/core.types.js").CoreUserSession>;
@@ -43,4 +43,5 @@ export declare const cors: import("../types/core.types.js").CorePikkuMiddlewareF
43
43
  logLevel?: import("../services/logger.js").LogLevel;
44
44
  secrets?: {};
45
45
  workflow?: import("../wirings/workflow/workflow.types.js").WorkflowServiceConfig;
46
+ postgres?: import("../types/core.types.js").PostgresConfig;
46
47
  }>, import("../types/core.types.js").CoreUserSession>;
@@ -2,4 +2,5 @@ export declare const pikkuRemoteAuthMiddleware: import("../types/core.types.js")
2
2
  logLevel?: import("../services/logger.js").LogLevel;
3
3
  secrets?: {};
4
4
  workflow?: import("../wirings/workflow/workflow.types.js").WorkflowServiceConfig;
5
+ postgres?: import("../types/core.types.js").PostgresConfig;
5
6
  }>, import("../types/core.types.js").CoreUserSession>;
@@ -21,6 +21,7 @@ export declare const telemetryOuter: import("../types/core.types.js").CorePikkuM
21
21
  logLevel?: import("../services/logger.js").LogLevel;
22
22
  secrets?: {};
23
23
  workflow?: import("../wirings/workflow/workflow.types.js").WorkflowServiceConfig;
24
+ postgres?: import("../types/core.types.js").PostgresConfig;
24
25
  }>, import("../types/core.types.js").CoreUserSession>;
25
26
  /**
26
27
  * Inner telemetry middleware that captures function execution duration and user context.
@@ -44,4 +45,5 @@ export declare const telemetryInner: import("../types/core.types.js").CorePikkuM
44
45
  logLevel?: import("../services/logger.js").LogLevel;
45
46
  secrets?: {};
46
47
  workflow?: import("../wirings/workflow/workflow.types.js").WorkflowServiceConfig;
48
+ postgres?: import("../types/core.types.js").PostgresConfig;
47
49
  }>, import("../types/core.types.js").CoreUserSession>;
@@ -4,4 +4,5 @@ export declare const timeout: () => import("../types/core.types.js").CorePikkuMi
4
4
  logLevel?: import("../services/logger.js").LogLevel;
5
5
  secrets?: {};
6
6
  workflow?: import("../wirings/workflow/workflow.types.js").WorkflowServiceConfig;
7
+ postgres?: import("../types/core.types.js").PostgresConfig;
7
8
  }>, import("../types/core.types.js").CoreUserSession>;
@@ -137,6 +137,36 @@ export type PickOptional<T, K extends keyof T> = Partial<T> & Pick<T, K>;
137
137
  export type RequireAtLeastOne<T> = {
138
138
  [K in keyof T]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<keyof T, K>>>;
139
139
  }[keyof T];
140
+ /**
141
+ * Runtime options for the Postgres adapter (@pikku/kysely-postgres).
142
+ * The connection string itself stays the flat `postgresUrl` config field (the
143
+ * contract the CLI's db commands read); this block is purely runtime pool
144
+ * tuning that the CLI never touches.
145
+ */
146
+ export interface PostgresConfig {
147
+ /** Max connections in the postgres.js pool. Defaults to postgres.js's 10. */
148
+ maxPool?: number;
149
+ /** Seconds to wait establishing a new connection before failing. */
150
+ connectTimeout?: number;
151
+ /** Close a pooled connection after it has been idle this many seconds. */
152
+ idleTimeout?: number;
153
+ /**
154
+ * Recycle a connection after this many seconds. Guards against stale TCP
155
+ * connections silently dropped by load balancers / proxies.
156
+ */
157
+ maxLifetime?: number;
158
+ /**
159
+ * Server-side `statement_timeout` in milliseconds. A query running longer is
160
+ * cancelled, freeing its connection — defense against a runaway query pinning
161
+ * a pooled connection and exhausting the pool.
162
+ */
163
+ statementTimeout?: number;
164
+ /**
165
+ * Set `false` behind a transaction-mode connection pooler (pgBouncer,
166
+ * Supabase pooler) that cannot use prepared statements.
167
+ */
168
+ prepare?: boolean;
169
+ }
140
170
  /**
141
171
  * Interface for the core configuration settings of Pikku.
142
172
  */
@@ -146,6 +176,8 @@ export type CoreConfig<Config extends Record<string, unknown> = {}> = {
146
176
  /** Secrets used by the application (optional). */
147
177
  secrets?: {};
148
178
  workflow?: WorkflowServiceConfig;
179
+ /** Runtime Postgres adapter options (pool sizing). */
180
+ postgres?: PostgresConfig;
149
181
  } & Config;
150
182
  /**
151
183
  * Represents a core user session, which can be extended for more specific session information.
@@ -6,4 +6,5 @@ export declare const voiceInput: (config?: {
6
6
  logLevel?: import("../../services/logger.js").LogLevel;
7
7
  secrets?: {};
8
8
  workflow?: import("../workflow/workflow.types.js").WorkflowServiceConfig;
9
+ postgres?: import("../../types/core.types.js").PostgresConfig;
9
10
  }>>;
@@ -12,5 +12,6 @@ export declare const voiceOutput: (config?: {
12
12
  logLevel?: import("../../services/logger.js").LogLevel;
13
13
  secrets?: {};
14
14
  workflow?: import("../workflow/workflow.types.js").WorkflowServiceConfig;
15
+ postgres?: import("../../types/core.types.js").PostgresConfig;
15
16
  }>>;
16
17
  export {};
@@ -103,4 +103,5 @@ export declare const rpcService: PikkuRPCService<import("../../types/core.types.
103
103
  logLevel?: import("../../services/logger.js").LogLevel;
104
104
  secrets?: {};
105
105
  workflow?: import("../workflow/workflow.types.js").WorkflowServiceConfig;
106
+ postgres?: import("../../types/core.types.js").PostgresConfig;
106
107
  }>, PikkuRPC>;
@@ -882,114 +882,120 @@ export class PikkuWorkflowService {
882
882
  * Handles idempotency, RPC execution, result storage, retry logic, and orchestrator triggering
883
883
  */
884
884
  async executeWorkflowStep(runId, stepName, rpcName, data, rpcService) {
885
- // Use step-level lock to prevent concurrent execution of same step
886
- await this.withStepLock(runId, stepName, async () => {
887
- // Get step state
888
- let stepState = await this.getStepState(runId, stepName);
889
- // Idempotency - if already succeeded, nothing to do
890
- if (stepState.status === 'succeeded') {
891
- return;
892
- }
893
- // Log warning if already running (race condition)
894
- if (stepState.status === 'running') {
895
- return;
885
+ // Claim the step under the lock ONLY (atomic check-and-mark-running). Do NOT
886
+ // hold the advisory lock and its pooled connection — across execution: once
887
+ // a step is 'running' the guard below makes any concurrent worker return
888
+ // early, so the work + result persistence run with the lock released. Holding
889
+ // the lock across executeGraphStep (network I/O + more pool queries) let
890
+ // concurrent steps exhaust the connection pool and self-deadlock.
891
+ const claimed = await this.withStepLock(runId, stepName, async () => {
892
+ const stepState = await this.getStepState(runId, stepName);
893
+ // Already succeeded, or already claimed by another worker — nothing to do.
894
+ if (stepState.status === 'succeeded' || stepState.status === 'running') {
895
+ return null;
896
896
  }
897
- // If status is 'failed', this is a retry - create new attempt history
897
+ // A 'failed' status means this is a retry start a fresh 'running' attempt.
898
898
  if (stepState.status === 'failed') {
899
- stepState = await this.createRetryAttempt(stepState.stepId, 'running');
899
+ return this.createRetryAttempt(stepState.stepId, 'running');
900
900
  }
901
901
  if (stepState.status === 'pending' || stepState.status === 'scheduled') {
902
902
  await this.setStepRunning(stepState.stepId);
903
903
  }
904
- try {
905
- let result;
906
- const run = await this.getRun(runId);
907
- if (!run) {
908
- throw new Error(`Workflow run not found: ${runId}`);
909
- }
910
- const meta = pikkuState(null, 'workflows', 'meta');
911
- const workflowMeta = meta[run.workflow];
912
- const isGraphWorkflow = workflowMeta?.source === 'graph' ||
913
- workflowMeta?.source === 'dynamic-workflow';
914
- // Map the physical step key back to its logical node: a revisit instance
915
- // is `node#N` (ordinal), which isn't a literal key in `nodes`.
916
- let graphNodeId;
917
- if (isGraphWorkflow && workflowMeta?.nodes) {
918
- if (stepName in workflowMeta.nodes) {
919
- graphNodeId = stepName;
920
- }
921
- else {
922
- const hash = stepName.lastIndexOf('#');
923
- const base = hash > 0 ? stepName.slice(0, hash) : undefined;
924
- if (base && base in workflowMeta.nodes)
925
- graphNodeId = base;
926
- }
927
- }
928
- if (graphNodeId) {
929
- result = await executeGraphStep(this, rpcService, runId, stepState.stepId, graphNodeId, rpcName, data, run.workflow);
904
+ return stepState;
905
+ });
906
+ // Nothing to execute: already succeeded, or another worker owns this step.
907
+ if (!claimed) {
908
+ return;
909
+ }
910
+ const stepState = claimed;
911
+ try {
912
+ let result;
913
+ const run = await this.getRun(runId);
914
+ if (!run) {
915
+ throw new Error(`Workflow run not found: ${runId}`);
916
+ }
917
+ const meta = pikkuState(null, 'workflows', 'meta');
918
+ const workflowMeta = meta[run.workflow];
919
+ const isGraphWorkflow = workflowMeta?.source === 'graph' ||
920
+ workflowMeta?.source === 'dynamic-workflow';
921
+ // Map the physical step key back to its logical node: a revisit instance
922
+ // is `node#N` (ordinal), which isn't a literal key in `nodes`.
923
+ let graphNodeId;
924
+ if (isGraphWorkflow && workflowMeta?.nodes) {
925
+ if (stepName in workflowMeta.nodes) {
926
+ graphNodeId = stepName;
930
927
  }
931
928
  else {
932
- // Check if rpcName refers to a sub-workflow
933
- const subWorkflowMeta = meta[rpcName];
934
- if (subWorkflowMeta) {
935
- const childWire = {
936
- type: 'workflow',
937
- id: rpcName,
938
- parentRunId: runId,
939
- parentStepId: stepState.stepId,
940
- pikkuUserId: rpcService.wire?.pikkuUserId,
941
- };
942
- const shouldInline = !getSingletonServices()?.queueService;
943
- const { runId: childRunId } = await this.startWorkflow(rpcName, data, childWire, rpcService, { inline: shouldInline });
944
- await this.setStepChildRunId(stepState.stepId, childRunId);
945
- if (shouldInline) {
946
- const childRun = await this.getRun(childRunId);
947
- if (childRun?.status === 'failed') {
948
- throw new Error(childRun.error?.message || 'Sub-workflow failed');
949
- }
950
- if (childRun?.status === 'cancelled') {
951
- throw new Error('Sub-workflow was cancelled');
952
- }
953
- result = childRun?.output;
929
+ const hash = stepName.lastIndexOf('#');
930
+ const base = hash > 0 ? stepName.slice(0, hash) : undefined;
931
+ if (base && base in workflowMeta.nodes)
932
+ graphNodeId = base;
933
+ }
934
+ }
935
+ if (graphNodeId) {
936
+ result = await executeGraphStep(this, rpcService, runId, stepState.stepId, graphNodeId, rpcName, data, run.workflow);
937
+ }
938
+ else {
939
+ // Check if rpcName refers to a sub-workflow
940
+ const subWorkflowMeta = meta[rpcName];
941
+ if (subWorkflowMeta) {
942
+ const childWire = {
943
+ type: 'workflow',
944
+ id: rpcName,
945
+ parentRunId: runId,
946
+ parentStepId: stepState.stepId,
947
+ pikkuUserId: rpcService.wire?.pikkuUserId,
948
+ };
949
+ const shouldInline = !getSingletonServices()?.queueService;
950
+ const { runId: childRunId } = await this.startWorkflow(rpcName, data, childWire, rpcService, { inline: shouldInline });
951
+ await this.setStepChildRunId(stepState.stepId, childRunId);
952
+ if (shouldInline) {
953
+ const childRun = await this.getRun(childRunId);
954
+ if (childRun?.status === 'failed') {
955
+ throw new Error(childRun.error?.message || 'Sub-workflow failed');
954
956
  }
955
- else {
956
- throw new ChildWorkflowStartedException(runId, stepState.stepId, childRunId);
957
+ if (childRun?.status === 'cancelled') {
958
+ throw new Error('Sub-workflow was cancelled');
957
959
  }
960
+ result = childRun?.output;
958
961
  }
959
962
  else {
960
- result = await this.invokeStepRpc(runId, stepName, stepState, rpcName, data, rpcService);
963
+ throw new ChildWorkflowStartedException(runId, stepState.stepId, childRunId);
961
964
  }
962
965
  }
963
- // Store result and mark succeeded
964
- await this.setStepResult(stepState.stepId, result);
965
- // Resume orchestrator to continue workflow
966
- await this.resumeWorkflow(runId);
967
- }
968
- catch (error) {
969
- if (error instanceof ChildWorkflowStartedException) {
970
- this.logger?.debug(`Workflow step '${stepName}': child workflow ${error.childRunId} started, waiting for completion`);
971
- return;
972
- }
973
- if (error instanceof RPCNotFoundError) {
974
- await this.setStepError(stepState.stepId, error);
975
- await this.updateRunStatus(runId, 'suspended', undefined, {
976
- message: `RPC '${rpcName}' not found. Deploy the missing function and resume.`,
977
- code: 'RPC_NOT_FOUND',
978
- });
979
- return;
966
+ else {
967
+ result = await this.invokeStepRpc(runId, stepName, stepState, rpcName, data, rpcService);
980
968
  }
981
- // Store error and mark failed
969
+ }
970
+ // Store result and mark succeeded
971
+ await this.setStepResult(stepState.stepId, result);
972
+ // Resume orchestrator to continue workflow
973
+ await this.resumeWorkflow(runId);
974
+ }
975
+ catch (error) {
976
+ if (error instanceof ChildWorkflowStartedException) {
977
+ this.logger?.debug(`Workflow step '${stepName}': child workflow ${error.childRunId} started, waiting for completion`);
978
+ return;
979
+ }
980
+ if (error instanceof RPCNotFoundError) {
982
981
  await this.setStepError(stepState.stepId, error);
983
- const maxAttempts = (stepState.retries ?? DEFAULT_STEP_RETRIES) + 1;
984
- const retriesExhausted = stepState.attemptCount >= maxAttempts;
985
- if (retriesExhausted) {
986
- // No more retries - resume orchestrator to mark workflow as failed
987
- await this.resumeWorkflow(runId);
988
- }
989
- // Always throw so queue knows the job failed and can retry if needed
990
- throw error;
982
+ await this.updateRunStatus(runId, 'suspended', undefined, {
983
+ message: `RPC '${rpcName}' not found. Deploy the missing function and resume.`,
984
+ code: 'RPC_NOT_FOUND',
985
+ });
986
+ return;
991
987
  }
992
- });
988
+ // Store error and mark failed
989
+ await this.setStepError(stepState.stepId, error);
990
+ const maxAttempts = (stepState.retries ?? DEFAULT_STEP_RETRIES) + 1;
991
+ const retriesExhausted = stepState.attemptCount >= maxAttempts;
992
+ if (retriesExhausted) {
993
+ // No more retries - resume orchestrator to mark workflow as failed
994
+ await this.resumeWorkflow(runId);
995
+ }
996
+ // Always throw so queue knows the job failed and can retry if needed
997
+ throw error;
998
+ }
993
999
  }
994
1000
  /**
995
1001
  * Orchestrate workflow execution (called by orchestrator)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pikku/core",
3
- "version": "0.12.42",
3
+ "version": "0.12.45",
4
4
  "author": "yasser.fadl@gmail.com",
5
5
  "license": "MIT",
6
6
  "module": "dist/index.js",
@@ -63,9 +63,9 @@
63
63
  "devDependencies": {
64
64
  "@types/node": "^24.11.0",
65
65
  "tsx": "^4.21.0",
66
- "typescript": "^5.9"
66
+ "typescript": "^6.0.3"
67
67
  },
68
68
  "engines": {
69
- "node": ">=18"
69
+ "node": ">=22"
70
70
  }
71
71
  }
package/src/index.ts CHANGED
@@ -31,6 +31,7 @@ export type {
31
31
  PikkuWire,
32
32
  PikkuRawWire,
33
33
  PikkuWiringTypes,
34
+ PostgresConfig,
34
35
  RequireAtLeastOne,
35
36
  SerializedError,
36
37
  WireServices,
@@ -192,6 +192,37 @@ export type RequireAtLeastOne<T> = {
192
192
  [K in keyof T]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<keyof T, K>>>
193
193
  }[keyof T]
194
194
 
195
+ /**
196
+ * Runtime options for the Postgres adapter (@pikku/kysely-postgres).
197
+ * The connection string itself stays the flat `postgresUrl` config field (the
198
+ * contract the CLI's db commands read); this block is purely runtime pool
199
+ * tuning that the CLI never touches.
200
+ */
201
+ export interface PostgresConfig {
202
+ /** Max connections in the postgres.js pool. Defaults to postgres.js's 10. */
203
+ maxPool?: number
204
+ /** Seconds to wait establishing a new connection before failing. */
205
+ connectTimeout?: number
206
+ /** Close a pooled connection after it has been idle this many seconds. */
207
+ idleTimeout?: number
208
+ /**
209
+ * Recycle a connection after this many seconds. Guards against stale TCP
210
+ * connections silently dropped by load balancers / proxies.
211
+ */
212
+ maxLifetime?: number
213
+ /**
214
+ * Server-side `statement_timeout` in milliseconds. A query running longer is
215
+ * cancelled, freeing its connection — defense against a runaway query pinning
216
+ * a pooled connection and exhausting the pool.
217
+ */
218
+ statementTimeout?: number
219
+ /**
220
+ * Set `false` behind a transaction-mode connection pooler (pgBouncer,
221
+ * Supabase pooler) that cannot use prepared statements.
222
+ */
223
+ prepare?: boolean
224
+ }
225
+
195
226
  /**
196
227
  * Interface for the core configuration settings of Pikku.
197
228
  */
@@ -202,6 +233,8 @@ export type CoreConfig<Config extends Record<string, unknown> = {}> = {
202
233
  secrets?: {}
203
234
 
204
235
  workflow?: WorkflowServiceConfig
236
+ /** Runtime Postgres adapter options (pool sizing). */
237
+ postgres?: PostgresConfig
205
238
  } & Config
206
239
 
207
240
  /**