@formancy/data-server 0.0.0-stage → 0.1.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.
- package/LICENSE.md +156 -0
- package/NOTICE +9 -0
- package/README.md +619 -2
- package/dist/drivers-DtJsZ_HW.mjs +3328 -0
- package/dist/drivers-DtJsZ_HW.mjs.map +1 -0
- package/dist/identity-keys-cli.d.mts +1 -0
- package/dist/identity-keys-cli.mjs +69 -0
- package/dist/identity-keys-cli.mjs.map +1 -0
- package/dist/identity-settings-BlQY9gHy.mjs +165 -0
- package/dist/identity-settings-BlQY9gHy.mjs.map +1 -0
- package/dist/index.d.mts +733 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +3 -0
- package/dist/main.d.mts +1 -0
- package/dist/main.mjs +189 -0
- package/dist/main.mjs.map +1 -0
- package/dist/secrets-DhKHG-Y9.mjs +689 -0
- package/dist/secrets-DhKHG-Y9.mjs.map +1 -0
- package/package.json +52 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":["_0"],"sources":["../src/config-store.ts","../src/secrets.ts","../src/connections.ts","../src/identity-keys.ts","../src/identity.ts","../src/audit.ts","../src/routes/admin.ts","../src/bundle.ts","../src/routes/runtime.ts","../src/app.ts","../src/identity-file.ts","../src/routes/admin-evolution.ts","../src/routes/admin-promotion.ts","../src/drivers.ts"],"mappings":"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"}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { c as validateBundle, d as recordReference, f as UnparsableVersionError, i as createDataServer, n as createConnectionRegistry, p as createFileConfigurationStore, r as parseConnections, s as ROUTE_OPERATIONS, t as DRIVER_FACTORIES, u as logAuditSink } from "./drivers-DtJsZ_HW.mjs";
|
|
2
|
+
import { a as KeySet, c as readKeySet, n as openKeySetFile, o as KeySetRefusal, r as createIdentityVerifier, t as resolveSecret } from "./secrets-DhKHG-Y9.mjs";
|
|
3
|
+
export { DRIVER_FACTORIES, KeySet, KeySetRefusal, ROUTE_OPERATIONS, UnparsableVersionError, createConnectionRegistry, createDataServer, createFileConfigurationStore, createIdentityVerifier, logAuditSink, openKeySetFile, parseConnections, readKeySet, recordReference, resolveSecret, validateBundle };
|
package/dist/main.d.mts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
package/dist/main.mjs
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { a as rateLimitSetting, i as createDataServer, l as lookupDeadlineSetting, n as createConnectionRegistry, o as corsOriginsSetting, p as createFileConfigurationStore, r as parseConnections, t as DRIVER_FACTORIES, u as logAuditSink } from "./drivers-DtJsZ_HW.mjs";
|
|
2
|
+
import { t as resolveSecret } from "./secrets-DhKHG-Y9.mjs";
|
|
3
|
+
import { i as readAgainOnSignal, r as openIdentity, t as identityKeySource } from "./identity-settings-BlQY9gHy.mjs";
|
|
4
|
+
import { readFile } from "node:fs/promises";
|
|
5
|
+
//#region src/planes.ts
|
|
6
|
+
/**
|
|
7
|
+
* The planes `main.ts` serves: the runtime, and the administrator's when roles
|
|
8
|
+
* administer it. One sink for both -- the server's own log, one line per
|
|
9
|
+
* event, which a collector tells apart by `audit.plane` (0033). The
|
|
10
|
+
* administrator's plane takes no audit key, because it names no record.
|
|
11
|
+
*
|
|
12
|
+
* Out of `main.ts`, which reads the environment and exits on a missing
|
|
13
|
+
* setting, so that `server-log.test.ts` can hold this wiring to what the
|
|
14
|
+
* documents say it is.
|
|
15
|
+
*/
|
|
16
|
+
function servedPlanes(found) {
|
|
17
|
+
const { registry, store, adminRoles, auditKey, origins, lookupDeadlineMs } = found;
|
|
18
|
+
const sink = (event) => logAuditSink(found.log())(event);
|
|
19
|
+
return {
|
|
20
|
+
runtime: {
|
|
21
|
+
registry,
|
|
22
|
+
store,
|
|
23
|
+
origins,
|
|
24
|
+
lookupDeadlineMs,
|
|
25
|
+
audit: {
|
|
26
|
+
sink,
|
|
27
|
+
...auditKey === void 0 ? {} : { key: auditKey }
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
...adminRoles.length === 0 ? {} : { admin: {
|
|
31
|
+
registry,
|
|
32
|
+
store,
|
|
33
|
+
adminRoles,
|
|
34
|
+
audit: { sink }
|
|
35
|
+
} }
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
//#endregion
|
|
39
|
+
//#region src/main.ts
|
|
40
|
+
/**
|
|
41
|
+
* The runnable server: `node dist/main.mjs`.
|
|
42
|
+
*
|
|
43
|
+
* Every setting the server runs on is read here -- the identity key's
|
|
44
|
+
* parsed and opened by identity-settings.ts, which identity-keys-cli.mjs
|
|
45
|
+
* shares -- and every missing one stops the process with a sentence saying
|
|
46
|
+
* which, rather than starting a server that accepts tokens from nobody or
|
|
47
|
+
* from everybody.
|
|
48
|
+
*
|
|
49
|
+
* FORMANCY_DATA_ISSUER, FORMANCY_DATA_AUDIENCE what the host's tokens say
|
|
50
|
+
* FORMANCY_DATA_IDENTITY_SECRET a secret reference: env:NAME or file:/path (HS256)
|
|
51
|
+
* FORMANCY_DATA_IDENTITY_PUBLIC_KEY_FILE or a PEM public key, with
|
|
52
|
+
* FORMANCY_DATA_IDENTITY_ALGORITHM RS256, ES256 or EdDSA
|
|
53
|
+
* FORMANCY_DATA_IDENTITY_KEYS_FILE or the absolute path of a JSON Web Key Set, with
|
|
54
|
+
* FORMANCY_DATA_IDENTITY_ALGORITHM RS256, ES256, EdDSA or HS256; read again on SIGHUP (0059)
|
|
55
|
+
* FORMANCY_DATA_ATTRIBUTES e.g. "tenant=tid,region=reg"
|
|
56
|
+
* FORMANCY_DATA_RATE_LIMIT requests a minute per client address, default 600
|
|
57
|
+
* FORMANCY_DATA_CORS_ORIGINS origins the runtime plane answers, comma-separated, each
|
|
58
|
+
* exactly as a browser sends Origin; empty or unset: none (0060)
|
|
59
|
+
* FORMANCY_DATA_LOOKUP_DEADLINE_MS how long a lookup statement may run before it is stopped,
|
|
60
|
+
* 1 to 2147483647 ms; default DEFAULT_LOOKUP_DEADLINE_MS, derived
|
|
61
|
+
* from docs/performance/results.json (0061)
|
|
62
|
+
* PORT default 4390
|
|
63
|
+
*/
|
|
64
|
+
function required(name) {
|
|
65
|
+
const value = process.env[name];
|
|
66
|
+
if (value === void 0 || value === "") {
|
|
67
|
+
console.error(`${name} is required.`);
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
return value;
|
|
71
|
+
}
|
|
72
|
+
function attributeMap(text) {
|
|
73
|
+
const map = {};
|
|
74
|
+
for (const pair of (text ?? "").split(",").map((entry) => entry.trim()).filter((entry) => entry !== "")) {
|
|
75
|
+
const [attribute, claim, extra] = pair.split("=");
|
|
76
|
+
if (attribute === void 0 || claim === void 0 || extra !== void 0 || attribute === "" || claim === "") {
|
|
77
|
+
console.error(`FORMANCY_DATA_ATTRIBUTES: "${pair}" is not attribute=claim.`);
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
map[attribute] = claim;
|
|
81
|
+
}
|
|
82
|
+
return map;
|
|
83
|
+
}
|
|
84
|
+
const limit = rateLimitSetting(process.env["FORMANCY_DATA_RATE_LIMIT"]);
|
|
85
|
+
if (!limit.ok) {
|
|
86
|
+
console.error(limit.problem);
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
const deadline = lookupDeadlineSetting(process.env["FORMANCY_DATA_LOOKUP_DEADLINE_MS"]);
|
|
90
|
+
if (!deadline.ok) {
|
|
91
|
+
console.error(deadline.problem);
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
const origins = corsOriginsSetting(process.env["FORMANCY_DATA_CORS_ORIGINS"], { runtime: process.env["FORMANCY_DATA_STORE_DIR"] !== void 0 && process.env["FORMANCY_DATA_CONNECTIONS"] !== void 0 });
|
|
95
|
+
if (!origins.ok) {
|
|
96
|
+
console.error(origins.problem);
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
const identity = identityKeySource(process.env);
|
|
100
|
+
if (!identity.ok) {
|
|
101
|
+
console.error(identity.problem);
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
const opened = await openIdentity(identity.source, {
|
|
105
|
+
env: process.env,
|
|
106
|
+
readFile: (path) => readFile(path, "utf8")
|
|
107
|
+
}, {
|
|
108
|
+
issuer: required("FORMANCY_DATA_ISSUER"),
|
|
109
|
+
audience: required("FORMANCY_DATA_AUDIENCE"),
|
|
110
|
+
attributes: attributeMap(process.env["FORMANCY_DATA_ATTRIBUTES"])
|
|
111
|
+
});
|
|
112
|
+
if (!opened.ok) {
|
|
113
|
+
console.error(opened.problem);
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
const { verifyIdentity, file: keyFile } = opened;
|
|
117
|
+
/**
|
|
118
|
+
* The two planes, each on only when its configuration is present:
|
|
119
|
+
*
|
|
120
|
+
* FORMANCY_DATA_STORE_DIR the configuration store's volume (0013)
|
|
121
|
+
* FORMANCY_DATA_CONNECTIONS the allowlist file, JSON (0019)
|
|
122
|
+
* FORMANCY_DATA_ADMIN_ROLES comma-separated roles that administer (0020)
|
|
123
|
+
* FORMANCY_DATA_AUDIT_KEY a secret reference naming records in the audit trail (0023)
|
|
124
|
+
*
|
|
125
|
+
* The store and the allowlist turn on the runtime plane; admin roles as well
|
|
126
|
+
* turn on the administrator's plane. Without them the server verifies tokens
|
|
127
|
+
* and nothing else, which is how a host is wired in before any form exists.
|
|
128
|
+
*/
|
|
129
|
+
const storeDirectory = process.env["FORMANCY_DATA_STORE_DIR"];
|
|
130
|
+
const connectionsFile = process.env["FORMANCY_DATA_CONNECTIONS"];
|
|
131
|
+
const adminRoles = (process.env["FORMANCY_DATA_ADMIN_ROLES"] ?? "").split(",").map((role) => role.trim()).filter((role) => role !== "");
|
|
132
|
+
let registry;
|
|
133
|
+
let planes = {};
|
|
134
|
+
if (storeDirectory === void 0 !== (connectionsFile === void 0)) {
|
|
135
|
+
console.error("Set both FORMANCY_DATA_STORE_DIR and FORMANCY_DATA_CONNECTIONS, or neither.");
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
if (storeDirectory !== void 0 && connectionsFile !== void 0) {
|
|
139
|
+
const parsed = parseConnections(JSON.parse(await readFile(connectionsFile, "utf8")));
|
|
140
|
+
if (!parsed.ok) {
|
|
141
|
+
console.error(`${connectionsFile}:\n ${parsed.problems.join("\n ")}`);
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
registry = createConnectionRegistry(parsed.connections, DRIVER_FACTORIES);
|
|
145
|
+
const store = createFileConfigurationStore(storeDirectory);
|
|
146
|
+
const auditReference = process.env["FORMANCY_DATA_AUDIT_KEY"];
|
|
147
|
+
const auditKey = auditReference === void 0 ? void 0 : await resolveSecret(auditReference);
|
|
148
|
+
planes = servedPlanes({
|
|
149
|
+
registry,
|
|
150
|
+
store,
|
|
151
|
+
adminRoles,
|
|
152
|
+
auditKey,
|
|
153
|
+
origins: origins.origins,
|
|
154
|
+
lookupDeadlineMs: deadline.ms,
|
|
155
|
+
log: () => app.log
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
const app = await createDataServer({
|
|
159
|
+
verifyIdentity,
|
|
160
|
+
logger: true,
|
|
161
|
+
rateLimit: limit.rateLimit,
|
|
162
|
+
...planes
|
|
163
|
+
});
|
|
164
|
+
const identityKeys = keyFile === void 0 ? {} : { identityKeys: {
|
|
165
|
+
kids: keyFile.current().kids,
|
|
166
|
+
leftOut: keyFile.current().leftOut
|
|
167
|
+
} };
|
|
168
|
+
app.log.info({
|
|
169
|
+
runtime: planes.runtime !== void 0,
|
|
170
|
+
admin: planes.admin !== void 0,
|
|
171
|
+
connections: registry?.ids() ?? [],
|
|
172
|
+
...identityKeys
|
|
173
|
+
}, "planes");
|
|
174
|
+
readAgainOnSignal(process, keyFile, app.log);
|
|
175
|
+
for (const signal of ["SIGTERM", "SIGINT"]) process.once(signal, () => {
|
|
176
|
+
(async () => {
|
|
177
|
+
await app.close();
|
|
178
|
+
await registry?.close();
|
|
179
|
+
process.exit(0);
|
|
180
|
+
})();
|
|
181
|
+
});
|
|
182
|
+
await app.listen({
|
|
183
|
+
host: "0.0.0.0",
|
|
184
|
+
port: Number(process.env["PORT"] ?? 4390)
|
|
185
|
+
});
|
|
186
|
+
//#endregion
|
|
187
|
+
export {};
|
|
188
|
+
|
|
189
|
+
//# sourceMappingURL=main.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main.mjs","names":[],"sources":["../src/planes.ts","../src/main.ts"],"sourcesContent":["import type { FastifyBaseLogger } from 'fastify'\nimport type { DataServerOptions } from './app.js'\nimport { logAuditSink } from './audit.js'\nimport type { AuditSink } from './audit.js'\nimport type { ConfigurationStore } from './config-store.js'\nimport type { ConnectionRegistry } from './connections.js'\n\n/** What the runnable server found in its configuration, once it has a store and an allowlist. */\nexport interface ServedConfiguration {\n registry: ConnectionRegistry\n store: ConfigurationStore\n /** Roles that administer; none, and the administrator's plane is off. */\n adminRoles: readonly string[]\n /** Names records in the trail by a keyed hash; without it, no record is named (0023). */\n auditKey: string | undefined\n /** `FORMANCY_DATA_CORS_ORIGINS`, parsed: the origins the runtime plane answers (0060); the administrator's answers none. */\n origins: readonly string[]\n /** `FORMANCY_DATA_LOOKUP_DEADLINE_MS`, parsed: how long a lookup statement may run (0061). */\n lookupDeadlineMs: number\n /** The server's log, looked up when an event fires: the server does not exist yet while its options are built. */\n log: () => FastifyBaseLogger\n}\n\n/**\n * The planes `main.ts` serves: the runtime, and the administrator's when roles\n * administer it. One sink for both -- the server's own log, one line per\n * event, which a collector tells apart by `audit.plane` (0033). The\n * administrator's plane takes no audit key, because it names no record.\n *\n * Out of `main.ts`, which reads the environment and exits on a missing\n * setting, so that `server-log.test.ts` can hold this wiring to what the\n * documents say it is.\n */\nexport function servedPlanes(found: ServedConfiguration): Pick<DataServerOptions, 'admin' | 'runtime'> {\n const { registry, store, adminRoles, auditKey, origins, lookupDeadlineMs } = found\n const sink: AuditSink = (event) => logAuditSink(found.log())(event)\n return {\n runtime: { registry, store, origins, lookupDeadlineMs, audit: { sink, ...(auditKey === undefined ? {} : { key: auditKey }) } },\n ...(adminRoles.length === 0 ? {} : { admin: { registry, store, adminRoles, audit: { sink } } }),\n }\n}\n","import { readFile } from 'node:fs/promises'\nimport { createDataServer } from './app.js'\nimport type { DataServerOptions } from './app.js'\nimport { createFileConfigurationStore } from './config-store.js'\nimport { createConnectionRegistry, parseConnections } from './connections.js'\nimport type { ConnectionRegistry } from './connections.js'\nimport { corsOriginsSetting } from './cors.js'\nimport { DRIVER_FACTORIES } from './drivers.js'\nimport { identityKeySource, openIdentity, readAgainOnSignal } from './identity-settings.js'\nimport { servedPlanes } from './planes.js'\nimport { lookupDeadlineSetting } from './lookup-deadline.js'\nimport { rateLimitSetting } from './rate-limit.js'\nimport { resolveSecret } from './secrets.js'\n\n/**\n * The runnable server: `node dist/main.mjs`.\n *\n * Every setting the server runs on is read here -- the identity key's\n * parsed and opened by identity-settings.ts, which identity-keys-cli.mjs\n * shares -- and every missing one stops the process with a sentence saying\n * which, rather than starting a server that accepts tokens from nobody or\n * from everybody.\n *\n * FORMANCY_DATA_ISSUER, FORMANCY_DATA_AUDIENCE what the host's tokens say\n * FORMANCY_DATA_IDENTITY_SECRET a secret reference: env:NAME or file:/path (HS256)\n * FORMANCY_DATA_IDENTITY_PUBLIC_KEY_FILE or a PEM public key, with\n * FORMANCY_DATA_IDENTITY_ALGORITHM RS256, ES256 or EdDSA\n * FORMANCY_DATA_IDENTITY_KEYS_FILE or the absolute path of a JSON Web Key Set, with\n * FORMANCY_DATA_IDENTITY_ALGORITHM RS256, ES256, EdDSA or HS256; read again on SIGHUP (0059)\n * FORMANCY_DATA_ATTRIBUTES e.g. \"tenant=tid,region=reg\"\n * FORMANCY_DATA_RATE_LIMIT requests a minute per client address, default 600\n * FORMANCY_DATA_CORS_ORIGINS origins the runtime plane answers, comma-separated, each\n * exactly as a browser sends Origin; empty or unset: none (0060)\n * FORMANCY_DATA_LOOKUP_DEADLINE_MS how long a lookup statement may run before it is stopped,\n * 1 to 2147483647 ms; default DEFAULT_LOOKUP_DEADLINE_MS, derived\n * from docs/performance/results.json (0061)\n * PORT default 4390\n */\nfunction required(name: string): string {\n const value = process.env[name]\n if (value === undefined || value === '') {\n console.error(`${name} is required.`)\n process.exit(1)\n }\n return value\n}\n\nfunction attributeMap(text: string | undefined): Record<string, string> {\n const map: Record<string, string> = {}\n for (const pair of (text ?? '').split(',').map((entry) => entry.trim()).filter((entry) => entry !== '')) {\n const [attribute, claim, extra] = pair.split('=')\n if (attribute === undefined || claim === undefined || extra !== undefined || attribute === '' || claim === '') {\n console.error(`FORMANCY_DATA_ATTRIBUTES: \"${pair}\" is not attribute=claim.`)\n process.exit(1)\n }\n map[attribute] = claim\n }\n return map\n}\n\n// Read before anything opens: a limit nobody chose stops the process here,\n// not after the planes have connected.\nconst limit = rateLimitSetting(process.env['FORMANCY_DATA_RATE_LIMIT'])\nif (!limit.ok) {\n console.error(limit.problem)\n process.exit(1)\n}\n\n// Read before anything opens, as the limit is: a deadline of 0, junk, or one\n// longer than a timer holds stops the process here (lookup-deadline.ts, 0061).\nconst deadline = lookupDeadlineSetting(process.env['FORMANCY_DATA_LOOKUP_DEADLINE_MS'])\nif (!deadline.ok) {\n console.error(deadline.problem)\n process.exit(1)\n}\n\n// Read before anything opens, as the limit is: an origin list nobody could\n// have meant -- a wildcard, a pattern, a spelling no browser sends -- or one\n// beside a server with no runtime plane, whose routes are all it grants,\n// stops the process here (cors.ts, 0060). Read once: changing it is a\n// restart.\nconst origins = corsOriginsSetting(process.env['FORMANCY_DATA_CORS_ORIGINS'], {\n runtime: process.env['FORMANCY_DATA_STORE_DIR'] !== undefined && process.env['FORMANCY_DATA_CONNECTIONS'] !== undefined,\n})\nif (!origins.ok) {\n console.error(origins.problem)\n process.exit(1)\n}\n\n// Which key, decided before any file is opened, and then opened\n// (identity-settings.ts): every refusal of it -- a secret or a file that\n// cannot be read, a short secret, a public key that does not import, a key\n// set refused -- stops the process here with one sentence.\nconst identity = identityKeySource(process.env)\nif (!identity.ok) {\n console.error(identity.problem)\n process.exit(1)\n}\nconst opened = await openIdentity(\n identity.source,\n { env: process.env, readFile: (path) => readFile(path, 'utf8') },\n { issuer: required('FORMANCY_DATA_ISSUER'), audience: required('FORMANCY_DATA_AUDIENCE'), attributes: attributeMap(process.env['FORMANCY_DATA_ATTRIBUTES']) },\n)\nif (!opened.ok) {\n console.error(opened.problem)\n process.exit(1)\n}\nconst { verifyIdentity, file: keyFile } = opened\n\n/**\n * The two planes, each on only when its configuration is present:\n *\n * FORMANCY_DATA_STORE_DIR the configuration store's volume (0013)\n * FORMANCY_DATA_CONNECTIONS the allowlist file, JSON (0019)\n * FORMANCY_DATA_ADMIN_ROLES comma-separated roles that administer (0020)\n * FORMANCY_DATA_AUDIT_KEY a secret reference naming records in the audit trail (0023)\n *\n * The store and the allowlist turn on the runtime plane; admin roles as well\n * turn on the administrator's plane. Without them the server verifies tokens\n * and nothing else, which is how a host is wired in before any form exists.\n */\nconst storeDirectory = process.env['FORMANCY_DATA_STORE_DIR']\nconst connectionsFile = process.env['FORMANCY_DATA_CONNECTIONS']\nconst adminRoles = (process.env['FORMANCY_DATA_ADMIN_ROLES'] ?? '').split(',').map((role) => role.trim()).filter((role) => role !== '')\n\nlet registry: ConnectionRegistry | undefined\nlet planes: Pick<DataServerOptions, 'admin' | 'runtime'> = {}\nif ((storeDirectory === undefined) !== (connectionsFile === undefined)) {\n console.error('Set both FORMANCY_DATA_STORE_DIR and FORMANCY_DATA_CONNECTIONS, or neither.')\n process.exit(1)\n}\nif (storeDirectory !== undefined && connectionsFile !== undefined) {\n const parsed = parseConnections(JSON.parse(await readFile(connectionsFile, 'utf8')) as unknown)\n if (!parsed.ok) {\n console.error(`${connectionsFile}:\\n ${parsed.problems.join('\\n ')}`)\n process.exit(1)\n }\n registry = createConnectionRegistry(parsed.connections, DRIVER_FACTORIES)\n const store = createFileConfigurationStore(storeDirectory)\n const auditReference = process.env['FORMANCY_DATA_AUDIT_KEY']\n const auditKey = auditReference === undefined ? undefined : await resolveSecret(auditReference)\n // One trail for both planes, the server's own log (0033).\n planes = servedPlanes({ registry, store, adminRoles, auditKey, origins: origins.origins, lookupDeadlineMs: deadline.ms, log: () => app.log })\n}\n\nconst app = await createDataServer({ verifyIdentity, logger: true, rateLimit: limit.rateLimit, ...planes })\nconst identityKeys = keyFile === undefined ? {} : { identityKeys: { kids: keyFile.current().kids, leftOut: keyFile.current().leftOut } }\napp.log.info({ runtime: planes.runtime !== undefined, admin: planes.admin !== undefined, connections: registry?.ids() ?? [], ...identityKeys }, 'planes')\n\n// SIGHUP reads the key set file again, and with a one-key setting says it\n// read nothing; either way it no longer stops the server, which is Node's\n// default. Installed before listen, so no signal arrives without it.\nreadAgainOnSignal(process, keyFile, app.log)\n\n// On SIGTERM a container stops taking requests, finishes the ones in flight,\n// and closes every database pool, so a rolling deploy leaves no half-open sessions.\nfor (const signal of ['SIGTERM', 'SIGINT'] as const) {\n process.once(signal, () => {\n void (async () => {\n await app.close()\n await registry?.close()\n process.exit(0)\n })()\n })\n}\n\nawait app.listen({ host: '0.0.0.0', port: Number(process.env['PORT'] ?? 4390) })\n"],"mappings":"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"}
|