@ductape/sdk 0.2.5 → 0.2.7
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 +9 -0
- package/dist/api/services/productsApi.service.js.map +1 -1
- package/dist/database/databases.service.js +15 -2
- package/dist/database/databases.service.js.map +1 -1
- package/dist/index.d.ts +42 -0
- package/dist/index.js +126 -12
- package/dist/index.js.map +1 -1
- package/dist/processor/services/processor.service.js +6 -3
- package/dist/processor/services/processor.service.js.map +1 -1
- package/dist/resilience/fallback.service.d.ts +6 -0
- package/dist/resilience/fallback.service.js +31 -1
- package/dist/resilience/fallback.service.js.map +1 -1
- package/dist/secrets/secrets.service.d.ts +7 -0
- package/dist/secrets/secrets.service.js +25 -0
- package/dist/secrets/secrets.service.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/index.types.d.ts +2 -0
- package/dist/types/index.types.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -530,6 +530,8 @@ class Ductape {
|
|
|
530
530
|
this.runtimePollIntervalMs = 30000;
|
|
531
531
|
this.runtimePollJitter = 0.2;
|
|
532
532
|
this.runtimeMaxBackoffMs = 300000;
|
|
533
|
+
this.runtimeWarmupEnabled = true;
|
|
534
|
+
this.runtimeCredentialState = { status: 'idle', referenced: 0, cached: 0, missing: 0 };
|
|
533
535
|
this.runtimeClosed = false;
|
|
534
536
|
/** Register and inspect portable application functions used by Feature JSON. */
|
|
535
537
|
this.functions = {
|
|
@@ -5453,6 +5455,7 @@ class Ductape {
|
|
|
5453
5455
|
this.runtimePollIntervalMs = Math.max(5000, (_a = runtime_sync.interval_ms) !== null && _a !== void 0 ? _a : 30000);
|
|
5454
5456
|
this.runtimePollJitter = Math.max(0, Math.min(1, (_b = runtime_sync.jitter) !== null && _b !== void 0 ? _b : 0.2));
|
|
5455
5457
|
this.runtimeMaxBackoffMs = Math.max(this.runtimePollIntervalMs, (_c = runtime_sync.max_backoff_ms) !== null && _c !== void 0 ? _c : 300000);
|
|
5458
|
+
this.runtimeWarmupEnabled = runtime_sync.warm_connections !== false;
|
|
5456
5459
|
}
|
|
5457
5460
|
// Services are lazily initialized via factory methods (createNewDatabaseService, etc.)
|
|
5458
5461
|
// This ensures initUserAuth() is called before service creation
|
|
@@ -5796,21 +5799,31 @@ class Ductape {
|
|
|
5796
5799
|
};
|
|
5797
5800
|
}
|
|
5798
5801
|
applyRuntimeSnapshot(response) {
|
|
5799
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
5802
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
5800
5803
|
if (!(response === null || response === void 0 ? void 0 : response.snapshot))
|
|
5801
5804
|
return;
|
|
5805
|
+
const previousManifest = this.runtimeManifest;
|
|
5802
5806
|
const scope = `${this.workspace_id}:${response.snapshot.product_tag}:${response.snapshot.env_slug}`;
|
|
5803
5807
|
(0, bootstrap_cache_1.primeRuntimeBootstrapCache)(this.workspace_id, scope, response.snapshot.entries);
|
|
5808
|
+
this.runtimeManifest = response.manifest;
|
|
5809
|
+
this.runtimeCredentialBundle = (_a = response.snapshot.credentials) !== null && _a !== void 0 ? _a : { referenced_keys: [], encrypted_secrets: [] };
|
|
5810
|
+
const referenced = (_c = (_b = this.runtimeCredentialBundle.referenced_keys) === null || _b === void 0 ? void 0 : _b.length) !== null && _c !== void 0 ? _c : this.runtimeCredentialBundle.encrypted_secrets.length;
|
|
5811
|
+
const cached = this.runtimeCredentialBundle.encrypted_secrets.length;
|
|
5812
|
+
this.runtimeCredentialState = {
|
|
5813
|
+
status: 'loading', referenced, cached, missing: Math.max(0, referenced - cached),
|
|
5814
|
+
};
|
|
5815
|
+
if (this._secretsService)
|
|
5816
|
+
this.applyRuntimeCredentialBundle(this._secretsService);
|
|
5804
5817
|
const runtimeCredentials = new Map();
|
|
5805
5818
|
for (const entry of response.snapshot.entries) {
|
|
5806
5819
|
if ((entry === null || entry === void 0 ? void 0 : entry.domain) !== 'action')
|
|
5807
5820
|
continue;
|
|
5808
|
-
const app = String(((
|
|
5809
|
-
const mapping = (
|
|
5821
|
+
const app = String(((_d = entry.parts) === null || _d === void 0 ? void 0 : _d[1]) || '');
|
|
5822
|
+
const mapping = (_e = entry.data) === null || _e === void 0 ? void 0 : _e.product_env_mapping;
|
|
5810
5823
|
if (!app || !mapping)
|
|
5811
5824
|
continue;
|
|
5812
|
-
const credentials = (
|
|
5813
|
-
for (const variable of (
|
|
5825
|
+
const credentials = (_f = runtimeCredentials.get(app)) !== null && _f !== void 0 ? _f : {};
|
|
5826
|
+
for (const variable of (_g = mapping.variables) !== null && _g !== void 0 ? _g : []) {
|
|
5814
5827
|
if ((variable === null || variable === void 0 ? void 0 : variable.key) && variable.value !== undefined)
|
|
5815
5828
|
credentials[String(variable.key)] = variable.value;
|
|
5816
5829
|
}
|
|
@@ -5819,13 +5832,13 @@ class Ductape {
|
|
|
5819
5832
|
}
|
|
5820
5833
|
// token_access data is the reusable request credential itself. credential_access
|
|
5821
5834
|
// data contains login secrets and must only be sent to its dedicated auth action.
|
|
5822
|
-
if (((
|
|
5835
|
+
if (((_j = (_h = entry.data) === null || _h === void 0 ? void 0 : _h.app_auth) === null || _j === void 0 ? void 0 : _j.setup_type) === 'token_access' && ((_k = mapping.auth) === null || _k === void 0 ? void 0 : _k.data)) {
|
|
5823
5836
|
let authData = mapping.auth.data;
|
|
5824
5837
|
if (typeof authData === 'string') {
|
|
5825
5838
|
try {
|
|
5826
5839
|
authData = JSON.parse((0, processor_utils_1.decrypt)(authData, String(entry.data.private_key)));
|
|
5827
5840
|
}
|
|
5828
|
-
catch (
|
|
5841
|
+
catch (_l) {
|
|
5829
5842
|
authData = undefined;
|
|
5830
5843
|
}
|
|
5831
5844
|
}
|
|
@@ -5843,9 +5856,51 @@ class Ductape {
|
|
|
5843
5856
|
runtimeCredentials.set(app, credentials);
|
|
5844
5857
|
}
|
|
5845
5858
|
credential_manager_1.credentialManager.replaceRuntimeScope(response.snapshot.product_tag, response.snapshot.env_slug, [...runtimeCredentials].map(([app, credentials]) => ({ app, credentials })));
|
|
5846
|
-
this.runtimeManifest = response.manifest;
|
|
5847
5859
|
this.runtimeLastRefreshAt = Date.now();
|
|
5848
5860
|
this.runtimeLastError = undefined;
|
|
5861
|
+
const databaseTags = [...new Set(response.snapshot.entries
|
|
5862
|
+
.filter((entry) => (entry === null || entry === void 0 ? void 0 : entry.domain) === 'database')
|
|
5863
|
+
.map((entry) => { var _a, _b, _c; return String(((_a = entry === null || entry === void 0 ? void 0 : entry.parts) === null || _a === void 0 ? void 0 : _a[2]) || ((_c = (_b = entry === null || entry === void 0 ? void 0 : entry.data) === null || _b === void 0 ? void 0 : _b.database) === null || _c === void 0 ? void 0 : _c.tag) || '').trim(); })
|
|
5864
|
+
.filter(Boolean))];
|
|
5865
|
+
this.startRuntimeWarmup(databaseTags, Boolean(previousManifest && previousManifest.connectionRevision !== response.manifest.connectionRevision));
|
|
5866
|
+
}
|
|
5867
|
+
startRuntimeWarmup(databaseTags, recycleConnections) {
|
|
5868
|
+
var _a, _b;
|
|
5869
|
+
if (!this.runtimeWarmupEnabled || this.runtimeClosed || !this.defaultProduct || !this.defaultEnv || !databaseTags.length)
|
|
5870
|
+
return;
|
|
5871
|
+
const previous = (_b = (_a = this.runtimeWarmupInFlight) === null || _a === void 0 ? void 0 : _a.catch(() => undefined)) !== null && _b !== void 0 ? _b : Promise.resolve();
|
|
5872
|
+
const warmup = previous.then(async () => {
|
|
5873
|
+
const startedAt = Date.now();
|
|
5874
|
+
this.runtimeWarmupState = { startedAt, databases: [] };
|
|
5875
|
+
const service = await this.createNewDatabaseService();
|
|
5876
|
+
if (recycleConnections)
|
|
5877
|
+
await service.closeAll();
|
|
5878
|
+
const databases = await Promise.all(databaseTags.map(async (tag) => {
|
|
5879
|
+
const start = Date.now();
|
|
5880
|
+
try {
|
|
5881
|
+
await service.connect({ product: this.defaultProduct, env: this.defaultEnv, database: tag });
|
|
5882
|
+
return { tag, status: 'ready', durationMs: Date.now() - start };
|
|
5883
|
+
}
|
|
5884
|
+
catch (error) {
|
|
5885
|
+
return {
|
|
5886
|
+
tag,
|
|
5887
|
+
status: 'failed',
|
|
5888
|
+
durationMs: Date.now() - start,
|
|
5889
|
+
error: error instanceof Error ? error.message : String(error),
|
|
5890
|
+
};
|
|
5891
|
+
}
|
|
5892
|
+
}));
|
|
5893
|
+
const completedAt = Date.now();
|
|
5894
|
+
this.runtimeWarmupState = { startedAt, completedAt, durationMs: completedAt - startedAt, databases };
|
|
5895
|
+
});
|
|
5896
|
+
this.runtimeWarmupInFlight = warmup;
|
|
5897
|
+
warmup.finally(() => {
|
|
5898
|
+
if (this.runtimeWarmupInFlight === warmup)
|
|
5899
|
+
this.runtimeWarmupInFlight = undefined;
|
|
5900
|
+
}).catch(() => undefined);
|
|
5901
|
+
warmup.catch((error) => {
|
|
5902
|
+
this.runtimeLastError = `Runtime connection warm-up failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
5903
|
+
});
|
|
5849
5904
|
}
|
|
5850
5905
|
scheduleRuntimePoll(failureCount = 0) {
|
|
5851
5906
|
var _a, _b;
|
|
@@ -5893,8 +5948,11 @@ class Ductape {
|
|
|
5893
5948
|
this.runtimeRefreshInFlight = (async () => {
|
|
5894
5949
|
var _a;
|
|
5895
5950
|
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());
|
|
5896
|
-
if (response)
|
|
5951
|
+
if (response) {
|
|
5897
5952
|
this.applyRuntimeSnapshot(response);
|
|
5953
|
+
const secrets = await this.createNewSecretsService();
|
|
5954
|
+
this.applyRuntimeCredentialBundle(secrets);
|
|
5955
|
+
}
|
|
5898
5956
|
this.scheduleRuntimePoll(0);
|
|
5899
5957
|
})().finally(() => { this.runtimeRefreshInFlight = undefined; });
|
|
5900
5958
|
return await this.runtimeRefreshInFlight;
|
|
@@ -5902,14 +5960,34 @@ class Ductape {
|
|
|
5902
5960
|
/** Force a full product runtime refresh and atomically replace bootstrap cache entries. */
|
|
5903
5961
|
async ready() {
|
|
5904
5962
|
await this.initUserAuth(false);
|
|
5963
|
+
await this.runtimeWarmupInFlight;
|
|
5964
|
+
this.assertRuntimeReady();
|
|
5905
5965
|
}
|
|
5906
5966
|
/** Force a full product runtime refresh and atomically replace bootstrap cache entries. */
|
|
5907
5967
|
async refreshRuntime() {
|
|
5908
5968
|
await this.initUserAuth(false);
|
|
5909
5969
|
await this.refreshRuntimeSnapshot(true);
|
|
5970
|
+
await this.runtimeWarmupInFlight;
|
|
5971
|
+
this.assertRuntimeReady();
|
|
5972
|
+
}
|
|
5973
|
+
assertRuntimeReady() {
|
|
5974
|
+
var _a, _b;
|
|
5975
|
+
if (!this.runtimeSyncEnabled)
|
|
5976
|
+
return;
|
|
5977
|
+
if (this.runtimeCredentialState.status === 'incomplete') {
|
|
5978
|
+
throw new Error(`Runtime credentials are incomplete (${this.runtimeCredentialState.missing} referenced secret(s) unavailable)`);
|
|
5979
|
+
}
|
|
5980
|
+
if (this.runtimeCredentialState.status === 'failed') {
|
|
5981
|
+
throw new Error(`Runtime credential preparation failed: ${this.runtimeCredentialState.error || 'unknown error'}`);
|
|
5982
|
+
}
|
|
5983
|
+
const failedConnections = (_b = (_a = this.runtimeWarmupState) === null || _a === void 0 ? void 0 : _a.databases.filter((database) => database.status === 'failed')) !== null && _b !== void 0 ? _b : [];
|
|
5984
|
+
if (this.runtimeWarmupEnabled && failedConnections.length) {
|
|
5985
|
+
throw new Error(`Runtime connection warm-up failed for: ${failedConnections.map((database) => database.tag).join(', ')}`);
|
|
5986
|
+
}
|
|
5910
5987
|
}
|
|
5911
5988
|
/** Observe the local product runtime snapshot without exposing cached configuration or secrets. */
|
|
5912
5989
|
runtimeSnapshotStatus() {
|
|
5990
|
+
var _a;
|
|
5913
5991
|
return {
|
|
5914
5992
|
enabled: this.runtimeSyncEnabled,
|
|
5915
5993
|
product: this.defaultProduct,
|
|
@@ -5918,16 +5996,28 @@ class Ductape {
|
|
|
5918
5996
|
lastRefreshAt: this.runtimeLastRefreshAt,
|
|
5919
5997
|
lastError: this.runtimeLastError,
|
|
5920
5998
|
refreshing: Boolean(this.runtimeRefreshInFlight),
|
|
5999
|
+
readiness: {
|
|
6000
|
+
configuration: this.runtimeManifest ? 'ready' : 'loading',
|
|
6001
|
+
credentials: Object.assign({}, this.runtimeCredentialState),
|
|
6002
|
+
connections: this.runtimeWarmupInFlight
|
|
6003
|
+
? 'warming'
|
|
6004
|
+
: ((_a = this.runtimeWarmupState) === null || _a === void 0 ? void 0 : _a.databases.some((database) => database.status === 'failed'))
|
|
6005
|
+
? 'degraded'
|
|
6006
|
+
: 'ready',
|
|
6007
|
+
},
|
|
6008
|
+
warmup: this.runtimeWarmupState ? Object.assign(Object.assign({}, this.runtimeWarmupState), { databases: this.runtimeWarmupState.databases.map((database) => (Object.assign({}, database))), warming: Boolean(this.runtimeWarmupInFlight) }) : { warming: Boolean(this.runtimeWarmupInFlight), databases: [] },
|
|
5921
6009
|
};
|
|
5922
6010
|
}
|
|
5923
6011
|
/** Stop background synchronization and release SDK-owned background resources. */
|
|
5924
6012
|
async close() {
|
|
5925
|
-
var _a;
|
|
6013
|
+
var _a, _b, _c;
|
|
5926
6014
|
this.runtimeClosed = true;
|
|
5927
6015
|
if (this.runtimePollTimer)
|
|
5928
6016
|
clearTimeout(this.runtimePollTimer);
|
|
5929
6017
|
this.runtimePollTimer = undefined;
|
|
5930
6018
|
await ((_a = this.runtimeRefreshInFlight) === null || _a === void 0 ? void 0 : _a.catch(() => undefined));
|
|
6019
|
+
await ((_b = this.runtimeWarmupInFlight) === null || _b === void 0 ? void 0 : _b.catch(() => undefined));
|
|
6020
|
+
await ((_c = this._databaseService) === null || _c === void 0 ? void 0 : _c.closeAll().catch(() => undefined));
|
|
5931
6021
|
const resources = [this.jobsWorker, this.healthCheckWorker, this.healthCheckUpdaterWorker,
|
|
5932
6022
|
this.jobsQueue, this.healthCheckQueue, this.healthCheckUpdaterQueue];
|
|
5933
6023
|
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); }));
|
|
@@ -5935,11 +6025,11 @@ class Ductape {
|
|
|
5935
6025
|
try {
|
|
5936
6026
|
await this.redisClient.quit();
|
|
5937
6027
|
}
|
|
5938
|
-
catch (
|
|
6028
|
+
catch (_d) {
|
|
5939
6029
|
try {
|
|
5940
6030
|
this.redisClient.disconnect(false);
|
|
5941
6031
|
}
|
|
5942
|
-
catch ( /* best effort */
|
|
6032
|
+
catch ( /* best effort */_e) { /* best effort */ }
|
|
5943
6033
|
}
|
|
5944
6034
|
this.redisClient = undefined;
|
|
5945
6035
|
}
|
|
@@ -6183,8 +6273,32 @@ class Ductape {
|
|
|
6183
6273
|
// Set the global instance for use by other services (e.g., database, storage)
|
|
6184
6274
|
(0, secrets_1.setSecretsService)(this._secretsService);
|
|
6185
6275
|
}
|
|
6276
|
+
this.applyRuntimeCredentialBundle(this._secretsService);
|
|
6186
6277
|
return this._secretsService;
|
|
6187
6278
|
}
|
|
6279
|
+
applyRuntimeCredentialBundle(service) {
|
|
6280
|
+
var _a, _b, _c;
|
|
6281
|
+
if (!this.runtimeCredentialBundle)
|
|
6282
|
+
return;
|
|
6283
|
+
const revision = (_a = this.runtimeManifest) === null || _a === void 0 ? void 0 : _a.secretRevision;
|
|
6284
|
+
if (this.runtimeCredentialAppliedService === service && this.runtimeCredentialAppliedRevision === revision)
|
|
6285
|
+
return;
|
|
6286
|
+
try {
|
|
6287
|
+
service.replaceRuntimeBundle(this.runtimeCredentialBundle.encrypted_secrets);
|
|
6288
|
+
this.runtimeCredentialAppliedService = service;
|
|
6289
|
+
this.runtimeCredentialAppliedRevision = revision;
|
|
6290
|
+
const referenced = (_c = (_b = this.runtimeCredentialBundle.referenced_keys) === null || _b === void 0 ? void 0 : _b.length) !== null && _c !== void 0 ? _c : this.runtimeCredentialBundle.encrypted_secrets.length;
|
|
6291
|
+
const cached = this.runtimeCredentialBundle.encrypted_secrets.length;
|
|
6292
|
+
const missing = Math.max(0, referenced - cached);
|
|
6293
|
+
this.runtimeCredentialState = {
|
|
6294
|
+
status: missing ? 'incomplete' : 'ready', referenced, cached, missing, refreshedAt: Date.now(),
|
|
6295
|
+
};
|
|
6296
|
+
}
|
|
6297
|
+
catch (error) {
|
|
6298
|
+
this.runtimeCredentialState = Object.assign(Object.assign({}, this.runtimeCredentialState), { status: 'failed', error: error instanceof Error ? error.message : String(error) });
|
|
6299
|
+
throw error;
|
|
6300
|
+
}
|
|
6301
|
+
}
|
|
6188
6302
|
async createNewCloudService() {
|
|
6189
6303
|
const subCheck = false;
|
|
6190
6304
|
if (!this.token || !this.public_key) {
|