@conduitplatform/module-tools 0.17.0-alpha.6 → 0.17.0-alpha.8
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/README.mdx +13 -0
- package/dist/index.cjs +229 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -1
- package/dist/index.d.ts +19 -1
- package/dist/index.esm.js +225 -21
- package/dist/index.esm.js.map +1 -1
- package/package.json +14 -12
package/README.mdx
CHANGED
|
@@ -6,6 +6,19 @@ This library provides a framework to build micro-services in NodeJS that can be
|
|
|
6
6
|
|
|
7
7
|
- run npm build
|
|
8
8
|
|
|
9
|
+
## Logging
|
|
10
|
+
|
|
11
|
+
Modules log to stdout. When `LOKI_URL` is set, the same logs are also shipped to Loki. File logging is on unless `LOG_TO_FILE=false`.
|
|
12
|
+
|
|
13
|
+
| Variable | Default | Description |
|
|
14
|
+
| -------------------- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
15
|
+
| `LOG_TO_FILE` | unset (on) | Set to `false` to skip file logs. Any other value, or leaving it unset, writes them. |
|
|
16
|
+
| `LOG_FILE_DIR` | `{tmpdir}/conduit-logs` | Directory for `combined-YYYY-MM-DD.log` and `errors-YYYY-MM-DD.log`. It is created when missing. |
|
|
17
|
+
| `LOG_RETENTION_DAYS` | `3` | Days of log files to keep. Older files are deleted. Non-positive or non-integer values use the default. |
|
|
18
|
+
| `LOG_FILE_MAX_SIZE` | `20m` | Max size of each file before a same-day size roll. Accepts `k`, `m`, or `g` suffixes, or a positive byte count. Invalid values use the default. |
|
|
19
|
+
|
|
20
|
+
Files roll once per day and are not gzipped. Metadata in file lines is single-line JSON.
|
|
21
|
+
|
|
9
22
|
## Features ✔️
|
|
10
23
|
|
|
11
24
|
- Service registration & discovery
|
package/dist/index.cjs
CHANGED
|
@@ -48,6 +48,8 @@ __export(index_exports, {
|
|
|
48
48
|
RouteBuilder: () => RouteBuilder,
|
|
49
49
|
RoutingManager: () => RoutingManager,
|
|
50
50
|
addServiceToServer: () => addServiceToServer,
|
|
51
|
+
containsRedactedMarker: () => containsRedactedMarker,
|
|
52
|
+
convictConfigParser: () => convictConfigParser,
|
|
51
53
|
createParsedRouterRequest: () => createParsedRouterRequest,
|
|
52
54
|
createServer: () => createServer,
|
|
53
55
|
exportModel: () => exportModel,
|
|
@@ -57,7 +59,12 @@ __export(index_exports, {
|
|
|
57
59
|
linearBackoffTimeoutAsync: () => linearBackoffTimeoutAsync,
|
|
58
60
|
merge: () => merge,
|
|
59
61
|
readConduitPeersManifest: () => readConduitPeersManifest,
|
|
62
|
+
reconcileStoredModuleConfig: () => reconcileStoredModuleConfig,
|
|
63
|
+
redactSensitiveConfig: () => redactSensitiveConfig,
|
|
64
|
+
restoreRedactedSecrets: () => restoreRedactedSecrets,
|
|
60
65
|
sanitizeDocumentsForExport: () => sanitizeDocumentsForExport,
|
|
66
|
+
stableConfigJson: () => stableConfigJson,
|
|
67
|
+
storedConfigsEquivalent: () => storedConfigsEquivalent,
|
|
61
68
|
stripInternalFields: () => stripInternalFields,
|
|
62
69
|
wrapFunctionsAsync: () => wrapFunctionsAsync,
|
|
63
70
|
wrapGrpcFunctions: () => wrapGrpcFunctions,
|
|
@@ -284,11 +291,19 @@ function wrapGrpcFunctions(functions, postponeRestart, noAuth = false) {
|
|
|
284
291
|
try {
|
|
285
292
|
invoked = functions[name](call, callback);
|
|
286
293
|
} catch (error) {
|
|
287
|
-
return throwError(
|
|
294
|
+
return throwError(
|
|
295
|
+
callback,
|
|
296
|
+
error.message,
|
|
297
|
+
error instanceof import_grpc_sdk3.GrpcError ? error.code : import_grpc_js2.status.INTERNAL
|
|
298
|
+
);
|
|
288
299
|
}
|
|
289
300
|
if (typeof invoked?.then === "function") {
|
|
290
301
|
invoked.then().catch((error) => {
|
|
291
|
-
return throwError(
|
|
302
|
+
return throwError(
|
|
303
|
+
callback,
|
|
304
|
+
error.message,
|
|
305
|
+
error instanceof import_grpc_sdk3.GrpcError ? error.code : import_grpc_js2.status.INTERNAL
|
|
306
|
+
);
|
|
292
307
|
});
|
|
293
308
|
}
|
|
294
309
|
};
|
|
@@ -1268,9 +1283,84 @@ async function setupLoki(module2, instance) {
|
|
|
1268
1283
|
}
|
|
1269
1284
|
}
|
|
1270
1285
|
|
|
1271
|
-
// src/
|
|
1272
|
-
var
|
|
1286
|
+
// src/logging/fileTransports.ts
|
|
1287
|
+
var import_fs = __toESM(require("fs"), 1);
|
|
1288
|
+
var import_os = __toESM(require("os"), 1);
|
|
1273
1289
|
var import_path2 = __toESM(require("path"), 1);
|
|
1290
|
+
var import_winston2 = __toESM(require("winston"), 1);
|
|
1291
|
+
var import_winston_daily_rotate_file = __toESM(require("winston-daily-rotate-file"), 1);
|
|
1292
|
+
var DEFAULT_RETENTION_DAYS = 3;
|
|
1293
|
+
var DEFAULT_MAX_SIZE = "20m";
|
|
1294
|
+
var SIZE_STRING = /^(?:0\.)?\d+[kmg]$/i;
|
|
1295
|
+
function positiveIntFromEnv(raw, fallback) {
|
|
1296
|
+
if (raw === void 0) return fallback;
|
|
1297
|
+
const trimmed = raw.trim();
|
|
1298
|
+
if (!/^[0-9]+$/.test(trimmed)) return fallback;
|
|
1299
|
+
const value = Number(trimmed);
|
|
1300
|
+
if (!Number.isSafeInteger(value) || value <= 0) return fallback;
|
|
1301
|
+
return value;
|
|
1302
|
+
}
|
|
1303
|
+
function logFileMaxSize() {
|
|
1304
|
+
const raw = process.env.LOG_FILE_MAX_SIZE?.trim();
|
|
1305
|
+
if (!raw) return DEFAULT_MAX_SIZE;
|
|
1306
|
+
if (SIZE_STRING.test(raw)) return raw.toLowerCase();
|
|
1307
|
+
if (/^[0-9]+$/.test(raw)) {
|
|
1308
|
+
const bytes = Number(raw);
|
|
1309
|
+
if (Number.isSafeInteger(bytes) && bytes > 0) return bytes;
|
|
1310
|
+
}
|
|
1311
|
+
return DEFAULT_MAX_SIZE;
|
|
1312
|
+
}
|
|
1313
|
+
function formatMeta(meta) {
|
|
1314
|
+
if (Array.isArray(meta)) {
|
|
1315
|
+
if (meta.length === 0) return "";
|
|
1316
|
+
return meta.map((item) => JSON.stringify(item)).join(" ");
|
|
1317
|
+
}
|
|
1318
|
+
if (meta !== null && typeof meta === "object") {
|
|
1319
|
+
if (Object.keys(meta).length === 0) return "";
|
|
1320
|
+
return JSON.stringify(meta);
|
|
1321
|
+
}
|
|
1322
|
+
return "";
|
|
1323
|
+
}
|
|
1324
|
+
function createFileFormat() {
|
|
1325
|
+
return import_winston2.default.format.combine(
|
|
1326
|
+
import_winston2.default.format.errors({ stack: true }),
|
|
1327
|
+
import_winston2.default.format.timestamp({
|
|
1328
|
+
format: "YYYY-MM-DD HH:mm:ss"
|
|
1329
|
+
}),
|
|
1330
|
+
import_winston2.default.format.printf((info) => {
|
|
1331
|
+
const metaText = formatMeta(info.meta);
|
|
1332
|
+
const line = `[${info.timestamp}] [${info.level.toUpperCase()}]: ${info.message}`;
|
|
1333
|
+
if (info.stack) {
|
|
1334
|
+
return `${line} ${info.stack}`;
|
|
1335
|
+
}
|
|
1336
|
+
return metaText ? `${line} ${metaText}` : line;
|
|
1337
|
+
})
|
|
1338
|
+
);
|
|
1339
|
+
}
|
|
1340
|
+
function rotateTransport(logDir, name, level) {
|
|
1341
|
+
return new import_winston_daily_rotate_file.default({
|
|
1342
|
+
dirname: logDir,
|
|
1343
|
+
filename: `${name}-%DATE%.log`,
|
|
1344
|
+
datePattern: "YYYY-MM-DD",
|
|
1345
|
+
level,
|
|
1346
|
+
maxFiles: `${positiveIntFromEnv(process.env.LOG_RETENTION_DAYS, DEFAULT_RETENTION_DAYS)}d`,
|
|
1347
|
+
maxSize: logFileMaxSize(),
|
|
1348
|
+
zippedArchive: false,
|
|
1349
|
+
auditFile: import_path2.default.join(logDir, `.${name}-audit.json`),
|
|
1350
|
+
format: createFileFormat()
|
|
1351
|
+
});
|
|
1352
|
+
}
|
|
1353
|
+
function createFileLogTransports() {
|
|
1354
|
+
if (process.env.LOG_TO_FILE?.trim() === "false") {
|
|
1355
|
+
return [];
|
|
1356
|
+
}
|
|
1357
|
+
const logDir = process.env.LOG_FILE_DIR?.trim() || import_path2.default.join(import_os.default.tmpdir(), "conduit-logs");
|
|
1358
|
+
import_fs.default.mkdirSync(logDir, { recursive: true });
|
|
1359
|
+
return [
|
|
1360
|
+
rotateTransport(logDir, "combined", "info"),
|
|
1361
|
+
rotateTransport(logDir, "errors", "error")
|
|
1362
|
+
];
|
|
1363
|
+
}
|
|
1274
1364
|
|
|
1275
1365
|
// src/metrics/index.ts
|
|
1276
1366
|
var import_prom_client = require("prom-client");
|
|
@@ -1659,17 +1749,9 @@ function clientMiddleware() {
|
|
|
1659
1749
|
var initializeSdk = (coreUrl, moduleName, watchModules = true, serviceHealthStatusGetter = () => import_grpc_sdk13.HealthCheckStatus.SERVING) => {
|
|
1660
1750
|
let logger;
|
|
1661
1751
|
try {
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
level: "info"
|
|
1666
|
-
}),
|
|
1667
|
-
new import_winston2.default.transports.File({
|
|
1668
|
-
filename: import_path2.default.join(__dirname, ".logs/errors.log"),
|
|
1669
|
-
level: "error"
|
|
1670
|
-
})
|
|
1671
|
-
]);
|
|
1672
|
-
} catch (e) {
|
|
1752
|
+
const fileTransports = createFileLogTransports();
|
|
1753
|
+
logger = fileTransports.length > 0 ? new ConduitLogger(fileTransports) : new ConduitLogger();
|
|
1754
|
+
} catch {
|
|
1673
1755
|
logger = new ConduitLogger();
|
|
1674
1756
|
}
|
|
1675
1757
|
const grpcSdk = new import_grpc_sdk13.ConduitGrpcSdk(
|
|
@@ -1775,6 +1857,110 @@ var convictConfigParser = (config) => {
|
|
|
1775
1857
|
return config;
|
|
1776
1858
|
};
|
|
1777
1859
|
|
|
1860
|
+
// src/utilities/redactSensitiveConfig.ts
|
|
1861
|
+
var WELL_KNOWN_SECRET_KEYS = /^(apiKey|api_key|password|secret|privateKey|private_key)$/i;
|
|
1862
|
+
function isRecord(value) {
|
|
1863
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1864
|
+
}
|
|
1865
|
+
function isSchemaLeaf(value) {
|
|
1866
|
+
return isRecord(value) && ("default" in value || "format" in value || "type" in value) && !isRecord(value._cvtProperties);
|
|
1867
|
+
}
|
|
1868
|
+
function isSensitiveLeaf(value) {
|
|
1869
|
+
return isSchemaLeaf(value) && value.sensitive === true;
|
|
1870
|
+
}
|
|
1871
|
+
function unwrapSchema(schema) {
|
|
1872
|
+
if (isRecord(schema) && isRecord(schema._cvtProperties)) {
|
|
1873
|
+
return schema._cvtProperties;
|
|
1874
|
+
}
|
|
1875
|
+
return schema;
|
|
1876
|
+
}
|
|
1877
|
+
var REDACTED_MARKER = "[REDACTED]";
|
|
1878
|
+
function containsRedactedMarker(value) {
|
|
1879
|
+
if (value === REDACTED_MARKER) return true;
|
|
1880
|
+
if (Array.isArray(value)) {
|
|
1881
|
+
return value.some(containsRedactedMarker);
|
|
1882
|
+
}
|
|
1883
|
+
if (!isRecord(value)) return false;
|
|
1884
|
+
return Object.values(value).some(containsRedactedMarker);
|
|
1885
|
+
}
|
|
1886
|
+
function redactSensitiveConfig(config, schema) {
|
|
1887
|
+
if (!isRecord(config)) return config;
|
|
1888
|
+
const redacted = Array.isArray(config) ? [...config] : { ...config };
|
|
1889
|
+
const node = unwrapSchema(schema);
|
|
1890
|
+
for (const [key, value] of Object.entries(redacted)) {
|
|
1891
|
+
const childSchema = isRecord(node) ? node[key] : void 0;
|
|
1892
|
+
if (isSensitiveLeaf(childSchema) || WELL_KNOWN_SECRET_KEYS.test(key)) {
|
|
1893
|
+
if (typeof value === "string" && value.length > 0) {
|
|
1894
|
+
redacted[key] = REDACTED_MARKER;
|
|
1895
|
+
}
|
|
1896
|
+
continue;
|
|
1897
|
+
}
|
|
1898
|
+
if (isRecord(value) || Array.isArray(value)) {
|
|
1899
|
+
redacted[key] = redactSensitiveConfig(
|
|
1900
|
+
value,
|
|
1901
|
+
childSchema
|
|
1902
|
+
);
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
return redacted;
|
|
1906
|
+
}
|
|
1907
|
+
function restoreRedactedSecrets(incoming, current, schema) {
|
|
1908
|
+
if (Array.isArray(incoming) && Array.isArray(current)) {
|
|
1909
|
+
return incoming.map(
|
|
1910
|
+
(item, index) => restoreRedactedSecrets(item, current[index], schema)
|
|
1911
|
+
);
|
|
1912
|
+
}
|
|
1913
|
+
if (!isRecord(incoming) || !isRecord(current)) return incoming;
|
|
1914
|
+
const restored = { ...incoming };
|
|
1915
|
+
const node = unwrapSchema(schema);
|
|
1916
|
+
for (const [key, value] of Object.entries(restored)) {
|
|
1917
|
+
const childSchema = isRecord(node) ? node[key] : void 0;
|
|
1918
|
+
const currentValue = current[key];
|
|
1919
|
+
if (isSensitiveLeaf(childSchema) || WELL_KNOWN_SECRET_KEYS.test(key)) {
|
|
1920
|
+
if (value === REDACTED_MARKER && typeof currentValue === "string" && currentValue.length > 0 && currentValue !== REDACTED_MARKER) {
|
|
1921
|
+
restored[key] = currentValue;
|
|
1922
|
+
}
|
|
1923
|
+
continue;
|
|
1924
|
+
}
|
|
1925
|
+
if ((isRecord(value) || Array.isArray(value)) && (isRecord(currentValue) || Array.isArray(currentValue))) {
|
|
1926
|
+
restored[key] = restoreRedactedSecrets(value, currentValue, childSchema);
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
return restored;
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1932
|
+
// src/utilities/reconcileModuleConfig.ts
|
|
1933
|
+
function isRecord2(value) {
|
|
1934
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1935
|
+
}
|
|
1936
|
+
function sortJson(value) {
|
|
1937
|
+
if (Array.isArray(value)) {
|
|
1938
|
+
return value.map(sortJson);
|
|
1939
|
+
}
|
|
1940
|
+
if (!isRecord2(value)) {
|
|
1941
|
+
return value;
|
|
1942
|
+
}
|
|
1943
|
+
return Object.fromEntries(
|
|
1944
|
+
Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, nested]) => [key, sortJson(nested)])
|
|
1945
|
+
);
|
|
1946
|
+
}
|
|
1947
|
+
function stableConfigJson(value) {
|
|
1948
|
+
return JSON.stringify(sortJson(value));
|
|
1949
|
+
}
|
|
1950
|
+
function storedConfigsEquivalent(left, right) {
|
|
1951
|
+
return stableConfigJson(left) === stableConfigJson(right);
|
|
1952
|
+
}
|
|
1953
|
+
async function reconcileStoredModuleConfig(args) {
|
|
1954
|
+
if (storedConfigsEquivalent(args.stored, args.migrated)) {
|
|
1955
|
+
return { config: args.migrated, persisted: false };
|
|
1956
|
+
}
|
|
1957
|
+
if (containsRedactedMarker(args.migrated)) {
|
|
1958
|
+
return { config: args.migrated, persisted: false };
|
|
1959
|
+
}
|
|
1960
|
+
const persisted = await args.configureOverride(args.migrated);
|
|
1961
|
+
return { config: persisted, persisted: true };
|
|
1962
|
+
}
|
|
1963
|
+
|
|
1778
1964
|
// src/routing/RoutingController.ts
|
|
1779
1965
|
var import_grpc_js5 = require("@grpc/grpc-js");
|
|
1780
1966
|
var RoutingController = class {
|
|
@@ -1827,6 +2013,9 @@ var ManagedModule = class extends ConduitServiceModule {
|
|
|
1827
2013
|
throw new Error("Failed to initialize grpcSdk");
|
|
1828
2014
|
}
|
|
1829
2015
|
}
|
|
2016
|
+
get configInitialized() {
|
|
2017
|
+
return this._configInitialized;
|
|
2018
|
+
}
|
|
1830
2019
|
get name() {
|
|
1831
2020
|
return this._moduleName;
|
|
1832
2021
|
}
|
|
@@ -2018,9 +2207,10 @@ var ManagedModule = class extends ConduitServiceModule {
|
|
|
2018
2207
|
});
|
|
2019
2208
|
}
|
|
2020
2209
|
let config = JSON.parse(call.request.newConfig);
|
|
2021
|
-
config = merge(this.config.getProperties(), config);
|
|
2022
|
-
config = await this.preConfig(config);
|
|
2023
2210
|
const previousConfig = this.config.getProperties();
|
|
2211
|
+
config = merge(previousConfig, config);
|
|
2212
|
+
config = restoreRedactedSecrets(config, previousConfig, this.configSchema);
|
|
2213
|
+
config = await this.preConfig(config);
|
|
2024
2214
|
try {
|
|
2025
2215
|
this.config.load(config).validate({
|
|
2026
2216
|
allowed: "warn"
|
|
@@ -2096,7 +2286,21 @@ var ManagedModule = class extends ConduitServiceModule {
|
|
|
2096
2286
|
this.configOverride
|
|
2097
2287
|
);
|
|
2098
2288
|
ConfigController.getInstance();
|
|
2099
|
-
if (config)
|
|
2289
|
+
if (config) {
|
|
2290
|
+
const migrated = await this.preConfig(config);
|
|
2291
|
+
this.config.load(migrated).validate({
|
|
2292
|
+
allowed: "warn"
|
|
2293
|
+
});
|
|
2294
|
+
const persistable = this.config.getProperties();
|
|
2295
|
+
const reconciled = await reconcileStoredModuleConfig({
|
|
2296
|
+
stored: config,
|
|
2297
|
+
migrated: persistable,
|
|
2298
|
+
configureOverride: (next) => this.grpcSdk.config.configure(next, convictConfigParser(configSchema), true)
|
|
2299
|
+
});
|
|
2300
|
+
config = reconciled.config;
|
|
2301
|
+
this.config.load(config);
|
|
2302
|
+
ConfigController.getInstance().config = this.config.getProperties();
|
|
2303
|
+
}
|
|
2100
2304
|
if (!config || config.active || !config.hasOwnProperty("active"))
|
|
2101
2305
|
await this.onConfig();
|
|
2102
2306
|
}
|
|
@@ -2141,6 +2345,8 @@ var ModuleError = class extends import_grpc_sdk15.GrpcError {
|
|
|
2141
2345
|
RouteBuilder,
|
|
2142
2346
|
RoutingManager,
|
|
2143
2347
|
addServiceToServer,
|
|
2348
|
+
containsRedactedMarker,
|
|
2349
|
+
convictConfigParser,
|
|
2144
2350
|
createParsedRouterRequest,
|
|
2145
2351
|
createServer,
|
|
2146
2352
|
exportModel,
|
|
@@ -2150,7 +2356,12 @@ var ModuleError = class extends import_grpc_sdk15.GrpcError {
|
|
|
2150
2356
|
linearBackoffTimeoutAsync,
|
|
2151
2357
|
merge,
|
|
2152
2358
|
readConduitPeersManifest,
|
|
2359
|
+
reconcileStoredModuleConfig,
|
|
2360
|
+
redactSensitiveConfig,
|
|
2361
|
+
restoreRedactedSecrets,
|
|
2153
2362
|
sanitizeDocumentsForExport,
|
|
2363
|
+
stableConfigJson,
|
|
2364
|
+
storedConfigsEquivalent,
|
|
2154
2365
|
stripInternalFields,
|
|
2155
2366
|
wrapFunctionsAsync,
|
|
2156
2367
|
wrapGrpcFunctions,
|