@ductape/sdk 0.1.154 → 0.1.156
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/dist/api/services/productsApi.service.d.ts +31 -0
- package/dist/api/services/productsApi.service.js +14 -0
- package/dist/api/services/productsApi.service.js.map +1 -1
- package/dist/api/urls.d.ts +1 -0
- package/dist/api/urls.js +5 -4
- package/dist/api/urls.js.map +1 -1
- package/dist/apps/utils/credential-manager.d.ts +7 -0
- package/dist/apps/utils/credential-manager.js +30 -1
- package/dist/apps/utils/credential-manager.js.map +1 -1
- package/dist/index.d.ts +40 -1
- package/dist/index.js +193 -1
- package/dist/index.js.map +1 -1
- package/dist/processor/services/processor.service.js +21 -20
- package/dist/processor/services/processor.service.js.map +1 -1
- package/dist/products/bootstrap-cache.d.ts +8 -0
- package/dist/products/bootstrap-cache.js +27 -0
- package/dist/products/bootstrap-cache.js.map +1 -1
- package/dist/products/services/products.service.js +48 -14
- package/dist/products/services/products.service.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/index.types.d.ts +9 -0
- package/dist/types/index.types.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -66,8 +66,10 @@ const runtime_defaults_1 = require("./runtime/runtime-defaults");
|
|
|
66
66
|
const healthcheck_cache_key_1 = require("./resilience/healthcheck-cache-key");
|
|
67
67
|
const enums_1 = require("./types/enums");
|
|
68
68
|
const productsApi_service_1 = require("./api/services/productsApi.service");
|
|
69
|
+
const bootstrap_cache_1 = require("./products/bootstrap-cache");
|
|
69
70
|
const imports_service_1 = __importDefault(require("./imports/imports.service"));
|
|
70
71
|
const processor_service_1 = __importDefault(require("./processor/services/processor.service"));
|
|
72
|
+
const processor_utils_1 = require("./processor/utils/processor.utils");
|
|
71
73
|
const logs_service_1 = __importDefault(require("./logs/logs.service"));
|
|
72
74
|
const mime = __importStar(require("mime-types"));
|
|
73
75
|
const database_1 = require("./database");
|
|
@@ -500,7 +502,8 @@ class Ductape {
|
|
|
500
502
|
console.warn(`[Ductape] Redis unavailable (${reason}); using in-memory bootstrap cache.`);
|
|
501
503
|
}
|
|
502
504
|
}
|
|
503
|
-
constructor({ accessKey, redis_url, product, env, local, ip_address, language }) {
|
|
505
|
+
constructor({ accessKey, redis_url, product, env, local, ip_address, language, runtime_sync }) {
|
|
506
|
+
var _a, _b, _c;
|
|
504
507
|
this.productBuilders = new Map();
|
|
505
508
|
this.appBuilders = new Map();
|
|
506
509
|
// Cached service instances (lazily initialized after auth)
|
|
@@ -523,6 +526,11 @@ class Ductape {
|
|
|
523
526
|
this._brokersService = null;
|
|
524
527
|
/** Prevent every lazy service factory from retrying a Redis URL that already failed. */
|
|
525
528
|
this.redisCacheUnavailable = false;
|
|
529
|
+
this.runtimeSyncEnabled = true;
|
|
530
|
+
this.runtimePollIntervalMs = 30000;
|
|
531
|
+
this.runtimePollJitter = 0.2;
|
|
532
|
+
this.runtimeMaxBackoffMs = 300000;
|
|
533
|
+
this.runtimeClosed = false;
|
|
526
534
|
/** Register and inspect portable application functions used by Feature JSON. */
|
|
527
535
|
this.functions = {
|
|
528
536
|
register: (contract) => functions_1.portableFunctions.register(contract),
|
|
@@ -5440,8 +5448,20 @@ class Ductape {
|
|
|
5440
5448
|
this.workspaceApi = new workspaceApi_service_1.WorkspaceApiService(this.environment);
|
|
5441
5449
|
this.productsApi = new productsApi_service_1.ProductsApiService(this.environment);
|
|
5442
5450
|
this.redis_url = redis_url;
|
|
5451
|
+
this.runtimeSyncEnabled = runtime_sync !== false && Boolean(productTag && envSlug);
|
|
5452
|
+
if (runtime_sync && typeof runtime_sync === 'object') {
|
|
5453
|
+
this.runtimePollIntervalMs = Math.max(5000, (_a = runtime_sync.interval_ms) !== null && _a !== void 0 ? _a : 30000);
|
|
5454
|
+
this.runtimePollJitter = Math.max(0, Math.min(1, (_b = runtime_sync.jitter) !== null && _b !== void 0 ? _b : 0.2));
|
|
5455
|
+
this.runtimeMaxBackoffMs = Math.max(this.runtimePollIntervalMs, (_c = runtime_sync.max_backoff_ms) !== null && _c !== void 0 ? _c : 300000);
|
|
5456
|
+
}
|
|
5443
5457
|
// Services are lazily initialized via factory methods (createNewDatabaseService, etc.)
|
|
5444
5458
|
// This ensures initUserAuth() is called before service creation
|
|
5459
|
+
if (this.runtimeSyncEnabled) {
|
|
5460
|
+
this.authInFlight = this.performUserAuth(false).finally(() => { this.authInFlight = undefined; });
|
|
5461
|
+
this.authInFlight.catch((error) => {
|
|
5462
|
+
this.runtimeLastError = error instanceof Error ? error.message : String(error);
|
|
5463
|
+
});
|
|
5464
|
+
}
|
|
5445
5465
|
return this; //createDeepProxy(this, this.environment) as this;
|
|
5446
5466
|
}
|
|
5447
5467
|
async loadBullMQ() {
|
|
@@ -5709,9 +5729,17 @@ class Ductape {
|
|
|
5709
5729
|
return this.api;
|
|
5710
5730
|
}
|
|
5711
5731
|
async initUserAuth(subCheck = true) {
|
|
5732
|
+
if (this.authInFlight)
|
|
5733
|
+
return await this.authInFlight;
|
|
5734
|
+
this.authInFlight = this.performUserAuth(subCheck).finally(() => { this.authInFlight = undefined; });
|
|
5735
|
+
return await this.authInFlight;
|
|
5736
|
+
}
|
|
5737
|
+
async performUserAuth(subCheck = true) {
|
|
5712
5738
|
var _a, _b;
|
|
5713
5739
|
// Skip if already authenticated with valid credentials
|
|
5714
5740
|
if (this.token && this.public_key) {
|
|
5741
|
+
if (this.runtimeSyncEnabled && !this.runtimeManifest)
|
|
5742
|
+
await this.refreshRuntimeSnapshot(true);
|
|
5715
5743
|
return;
|
|
5716
5744
|
}
|
|
5717
5745
|
// Only reset if we need to re-authenticate
|
|
@@ -5742,6 +5770,15 @@ class Ductape {
|
|
|
5742
5770
|
// Don't throw - private key is only needed for secrets encryption
|
|
5743
5771
|
}
|
|
5744
5772
|
}
|
|
5773
|
+
if (this.runtimeSyncEnabled && !this.runtimeClosed) {
|
|
5774
|
+
try {
|
|
5775
|
+
await this.refreshRuntimeSnapshot(true);
|
|
5776
|
+
}
|
|
5777
|
+
catch (error) {
|
|
5778
|
+
this.runtimeLastError = error instanceof Error ? error.message : String(error);
|
|
5779
|
+
this.scheduleRuntimePoll(1);
|
|
5780
|
+
}
|
|
5781
|
+
}
|
|
5745
5782
|
}
|
|
5746
5783
|
catch (e) {
|
|
5747
5784
|
const error = ((_b = (_a = e.response) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.errors) || e.message || 'Unknown error';
|
|
@@ -5749,6 +5786,161 @@ class Ductape {
|
|
|
5749
5786
|
throw new Error(error);
|
|
5750
5787
|
}
|
|
5751
5788
|
}
|
|
5789
|
+
runtimeAuth() {
|
|
5790
|
+
return {
|
|
5791
|
+
token: this.token,
|
|
5792
|
+
public_key: this.public_key,
|
|
5793
|
+
user_id: this.user_id,
|
|
5794
|
+
workspace_id: this.workspace_id,
|
|
5795
|
+
access_key: this.accessKey,
|
|
5796
|
+
};
|
|
5797
|
+
}
|
|
5798
|
+
applyRuntimeSnapshot(response) {
|
|
5799
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
5800
|
+
if (!(response === null || response === void 0 ? void 0 : response.snapshot))
|
|
5801
|
+
return;
|
|
5802
|
+
const scope = `${this.workspace_id}:${response.snapshot.product_tag}:${response.snapshot.env_slug}`;
|
|
5803
|
+
(0, bootstrap_cache_1.primeRuntimeBootstrapCache)(this.workspace_id, scope, response.snapshot.entries);
|
|
5804
|
+
const runtimeCredentials = new Map();
|
|
5805
|
+
for (const entry of response.snapshot.entries) {
|
|
5806
|
+
if ((entry === null || entry === void 0 ? void 0 : entry.domain) !== 'action')
|
|
5807
|
+
continue;
|
|
5808
|
+
const app = String(((_a = entry.parts) === null || _a === void 0 ? void 0 : _a[1]) || '');
|
|
5809
|
+
const mapping = (_b = entry.data) === null || _b === void 0 ? void 0 : _b.product_env_mapping;
|
|
5810
|
+
if (!app || !mapping)
|
|
5811
|
+
continue;
|
|
5812
|
+
const credentials = (_c = runtimeCredentials.get(app)) !== null && _c !== void 0 ? _c : {};
|
|
5813
|
+
for (const variable of (_d = mapping.variables) !== null && _d !== void 0 ? _d : []) {
|
|
5814
|
+
if ((variable === null || variable === void 0 ? void 0 : variable.key) && variable.value !== undefined)
|
|
5815
|
+
credentials[String(variable.key)] = variable.value;
|
|
5816
|
+
}
|
|
5817
|
+
// token_access data is the reusable request credential itself. credential_access
|
|
5818
|
+
// data contains login secrets and must only be sent to its dedicated auth action.
|
|
5819
|
+
if (((_f = (_e = entry.data) === null || _e === void 0 ? void 0 : _e.app_auth) === null || _f === void 0 ? void 0 : _f.setup_type) === 'token_access' && ((_g = mapping.auth) === null || _g === void 0 ? void 0 : _g.data)) {
|
|
5820
|
+
let authData = mapping.auth.data;
|
|
5821
|
+
if (typeof authData === 'string') {
|
|
5822
|
+
try {
|
|
5823
|
+
authData = JSON.parse((0, processor_utils_1.decrypt)(authData, String(entry.data.private_key)));
|
|
5824
|
+
}
|
|
5825
|
+
catch (_h) {
|
|
5826
|
+
authData = undefined;
|
|
5827
|
+
}
|
|
5828
|
+
}
|
|
5829
|
+
if (authData && typeof authData === 'object') {
|
|
5830
|
+
for (const location of ['headers', 'query', 'params', 'body']) {
|
|
5831
|
+
const values = authData[location];
|
|
5832
|
+
if (!values || typeof values !== 'object' || Array.isArray(values))
|
|
5833
|
+
continue;
|
|
5834
|
+
for (const [key, value] of Object.entries(values)) {
|
|
5835
|
+
credentials[`${location}:${key}`] = value;
|
|
5836
|
+
}
|
|
5837
|
+
}
|
|
5838
|
+
}
|
|
5839
|
+
}
|
|
5840
|
+
runtimeCredentials.set(app, credentials);
|
|
5841
|
+
}
|
|
5842
|
+
credential_manager_1.credentialManager.replaceRuntimeScope(response.snapshot.product_tag, response.snapshot.env_slug, [...runtimeCredentials].map(([app, credentials]) => ({ app, credentials })));
|
|
5843
|
+
this.runtimeManifest = response.manifest;
|
|
5844
|
+
this.runtimeLastRefreshAt = Date.now();
|
|
5845
|
+
this.runtimeLastError = undefined;
|
|
5846
|
+
}
|
|
5847
|
+
scheduleRuntimePoll(failureCount = 0) {
|
|
5848
|
+
var _a, _b;
|
|
5849
|
+
if (!this.runtimeSyncEnabled || this.runtimeClosed)
|
|
5850
|
+
return;
|
|
5851
|
+
if (this.runtimePollTimer)
|
|
5852
|
+
clearTimeout(this.runtimePollTimer);
|
|
5853
|
+
const exponential = Math.min(this.runtimeMaxBackoffMs, this.runtimePollIntervalMs * Math.pow(2, failureCount));
|
|
5854
|
+
const jitter = exponential * this.runtimePollJitter * ((Math.random() * 2) - 1);
|
|
5855
|
+
this.runtimePollTimer = setTimeout(() => {
|
|
5856
|
+
this.pollRuntimeManifest(failureCount).catch(() => undefined);
|
|
5857
|
+
}, Math.max(1000, Math.round(exponential + jitter)));
|
|
5858
|
+
(_b = (_a = this.runtimePollTimer).unref) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
5859
|
+
}
|
|
5860
|
+
async pollRuntimeManifest(failureCount) {
|
|
5861
|
+
var _a, _b;
|
|
5862
|
+
if (!this.defaultProduct || !this.defaultEnv || this.runtimeClosed)
|
|
5863
|
+
return;
|
|
5864
|
+
try {
|
|
5865
|
+
const manifestResponse = await this.productsApi.bootstrapRuntime({
|
|
5866
|
+
product_tag: this.defaultProduct,
|
|
5867
|
+
env_slug: this.defaultEnv,
|
|
5868
|
+
manifest_only: true,
|
|
5869
|
+
etag: (_a = this.runtimeManifest) === null || _a === void 0 ? void 0 : _a.runtimeRevision,
|
|
5870
|
+
}, this.runtimeAuth());
|
|
5871
|
+
if (manifestResponse && manifestResponse.manifest.runtimeRevision !== ((_b = this.runtimeManifest) === null || _b === void 0 ? void 0 : _b.runtimeRevision)) {
|
|
5872
|
+
await this.refreshRuntimeSnapshot(true);
|
|
5873
|
+
}
|
|
5874
|
+
else if (this.defaultProduct && this.defaultEnv) {
|
|
5875
|
+
(0, bootstrap_cache_1.touchRuntimeBootstrapCache)(`${this.workspace_id}:${this.defaultProduct}:${this.defaultEnv}`);
|
|
5876
|
+
}
|
|
5877
|
+
this.runtimeLastError = undefined;
|
|
5878
|
+
this.scheduleRuntimePoll(0);
|
|
5879
|
+
}
|
|
5880
|
+
catch (error) {
|
|
5881
|
+
this.runtimeLastError = error instanceof Error ? error.message : String(error);
|
|
5882
|
+
this.scheduleRuntimePoll(Math.min(failureCount + 1, 8));
|
|
5883
|
+
}
|
|
5884
|
+
}
|
|
5885
|
+
async refreshRuntimeSnapshot(force = false) {
|
|
5886
|
+
if (!this.runtimeSyncEnabled || !this.defaultProduct || !this.defaultEnv || this.runtimeClosed)
|
|
5887
|
+
return;
|
|
5888
|
+
if (this.runtimeRefreshInFlight)
|
|
5889
|
+
return await this.runtimeRefreshInFlight;
|
|
5890
|
+
this.runtimeRefreshInFlight = (async () => {
|
|
5891
|
+
var _a;
|
|
5892
|
+
const response = await this.productsApi.bootstrapRuntime(Object.assign({ product_tag: this.defaultProduct, env_slug: this.defaultEnv }, (force ? {} : { etag: (_a = this.runtimeManifest) === null || _a === void 0 ? void 0 : _a.runtimeRevision })), this.runtimeAuth());
|
|
5893
|
+
if (response)
|
|
5894
|
+
this.applyRuntimeSnapshot(response);
|
|
5895
|
+
this.scheduleRuntimePoll(0);
|
|
5896
|
+
})().finally(() => { this.runtimeRefreshInFlight = undefined; });
|
|
5897
|
+
return await this.runtimeRefreshInFlight;
|
|
5898
|
+
}
|
|
5899
|
+
/** Force a full product runtime refresh and atomically replace bootstrap cache entries. */
|
|
5900
|
+
async ready() {
|
|
5901
|
+
await this.initUserAuth(false);
|
|
5902
|
+
}
|
|
5903
|
+
/** Force a full product runtime refresh and atomically replace bootstrap cache entries. */
|
|
5904
|
+
async refreshRuntime() {
|
|
5905
|
+
await this.initUserAuth(false);
|
|
5906
|
+
await this.refreshRuntimeSnapshot(true);
|
|
5907
|
+
}
|
|
5908
|
+
/** Observe the local product runtime snapshot without exposing cached configuration or secrets. */
|
|
5909
|
+
runtimeSnapshotStatus() {
|
|
5910
|
+
return {
|
|
5911
|
+
enabled: this.runtimeSyncEnabled,
|
|
5912
|
+
product: this.defaultProduct,
|
|
5913
|
+
env: this.defaultEnv,
|
|
5914
|
+
manifest: this.runtimeManifest ? Object.assign({}, this.runtimeManifest) : undefined,
|
|
5915
|
+
lastRefreshAt: this.runtimeLastRefreshAt,
|
|
5916
|
+
lastError: this.runtimeLastError,
|
|
5917
|
+
refreshing: Boolean(this.runtimeRefreshInFlight),
|
|
5918
|
+
};
|
|
5919
|
+
}
|
|
5920
|
+
/** Stop background synchronization and release SDK-owned background resources. */
|
|
5921
|
+
async close() {
|
|
5922
|
+
var _a;
|
|
5923
|
+
this.runtimeClosed = true;
|
|
5924
|
+
if (this.runtimePollTimer)
|
|
5925
|
+
clearTimeout(this.runtimePollTimer);
|
|
5926
|
+
this.runtimePollTimer = undefined;
|
|
5927
|
+
await ((_a = this.runtimeRefreshInFlight) === null || _a === void 0 ? void 0 : _a.catch(() => undefined));
|
|
5928
|
+
const resources = [this.jobsWorker, this.healthCheckWorker, this.healthCheckUpdaterWorker,
|
|
5929
|
+
this.jobsQueue, this.healthCheckQueue, this.healthCheckUpdaterQueue];
|
|
5930
|
+
await Promise.all(resources.filter(Boolean).map((resource) => { var _a; return Promise.resolve((_a = resource.close) === null || _a === void 0 ? void 0 : _a.call(resource)).catch(() => undefined); }));
|
|
5931
|
+
if (this.redisClient) {
|
|
5932
|
+
try {
|
|
5933
|
+
await this.redisClient.quit();
|
|
5934
|
+
}
|
|
5935
|
+
catch (_b) {
|
|
5936
|
+
try {
|
|
5937
|
+
this.redisClient.disconnect(false);
|
|
5938
|
+
}
|
|
5939
|
+
catch ( /* best effort */_c) { /* best effort */ }
|
|
5940
|
+
}
|
|
5941
|
+
this.redisClient = undefined;
|
|
5942
|
+
}
|
|
5943
|
+
}
|
|
5752
5944
|
createNewLogger(product_id, app_id) {
|
|
5753
5945
|
return new logs_service_1.default(Object.assign(Object.assign({}, this.builderInit()), { product_id,
|
|
5754
5946
|
app_id }));
|