@ductape/sdk 0.1.102 → 0.1.103

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.
@@ -30,7 +30,7 @@ export interface IProductsBuilderService {
30
30
  generateExecutablePayload(params: IGenerateExecutablePayloadParams): Promise<IGenerateExecutablePayloadResponse>;
31
31
  createEnv(data: Partial<IProductEnv>): Promise<void>;
32
32
  updateEnv(slug: string, data: Partial<IProductEnv>): Promise<void>;
33
- fetchEnv(slug: string): Promise<IProductEnv | null>;
33
+ fetchEnv(slug: string, refresh?: boolean): Promise<IProductEnv | null>;
34
34
  fetchEnvs(): Promise<Array<IProductEnv>>;
35
35
  createSession(data: IProductSession): Promise<void>;
36
36
  updateSession(tag: string, data: Partial<IProductSession>): Promise<void>;
@@ -122,6 +122,12 @@ export interface IProductsBuilderService {
122
122
  fetchJobs(): Promise<Array<IProductJobs>>;
123
123
  createHealthcheck(data: Partial<IProductAppHealth>): Promise<void>;
124
124
  updateHealthcheck(tag: string, data: Partial<IProductAppHealth>): Promise<void>;
125
+ updateHealthcheckStatuses(productTag: string, checks: Array<{
126
+ tag: string;
127
+ environments: Array<Record<string, any> & {
128
+ slug: string;
129
+ }>;
130
+ }>): Promise<void>;
125
131
  fetchHealthcheck(access_tag: string, tag: string): Promise<IProductAppHealth | null>;
126
132
  fetchHealthchecks(access_tag: string): Promise<Array<IProductAppHealth>>;
127
133
  fetchProductHealthchecks(): Promise<Array<IProductAppHealth>>;
@@ -175,6 +181,16 @@ export default class ProductsBuilderService implements IProductsBuilderService {
175
181
  fetchProductId(): string;
176
182
  createHealthcheck(data: Partial<IProductAppHealth>): Promise<void>;
177
183
  updateHealthcheck(tag: string, data: Partial<IProductAppHealth>): Promise<void>;
184
+ /**
185
+ * Persist monitor results without re-validating or re-encrypting the probe payload.
186
+ * This is used by the local SDK's batched Redis-to-backend status flush.
187
+ */
188
+ updateHealthcheckStatuses(productTag: string, checks: Array<{
189
+ tag: string;
190
+ environments: Array<Record<string, any> & {
191
+ slug: string;
192
+ }>;
193
+ }>): Promise<void>;
178
194
  fetchHealthcheck(access_tag: string, tag: string, throwError?: boolean): Promise<IProductAppHealth | null>;
179
195
  fetchHealthchecks(access_tag: string, throwError?: boolean): Promise<Array<IProductAppHealth>>;
180
196
  fetchProductHealthchecks(): Promise<Array<IProductAppHealth>>;
@@ -193,6 +209,11 @@ export default class ProductsBuilderService implements IProductsBuilderService {
193
209
  }): Promise<void>;
194
210
  /** Keep in-memory product component arrays in sync after component CRUD (components API can lag). */
195
211
  private mergeProductComponent;
212
+ /**
213
+ * Validate app healthchecks against the product's connected app and its selected action.
214
+ * Every environment carries an explicit payload, including `{}` for actions with no inputs.
215
+ */
216
+ private validateAppHealthcheckSchema;
196
217
  /**
197
218
  * Create a healthcheck from a schema (e.g. from resilience health.define/create).
198
219
  * Persists as product component so processor healthcheck workers and health.list/fetch see it.
@@ -313,7 +334,7 @@ export default class ProductsBuilderService implements IProductsBuilderService {
313
334
  createEnv(data: IProductEnv, throwErrorIfExists?: boolean): Promise<void>;
314
335
  updateEnv(slug: string, data: Partial<IProductEnv>): Promise<void>;
315
336
  fetchEnvs(version?: string): Promise<Array<IProductEnv>>;
316
- fetchEnv(slug: string): Promise<IProductEnv | null>;
337
+ fetchEnv(slug: string, refresh?: boolean): Promise<IProductEnv | null>;
317
338
  deleteEnv(slug: string): Promise<void>;
318
339
  createMessageBroker(data: Partial<IProductMessageBroker>, throwErrorIfExists?: boolean): Promise<void>;
319
340
  updateMessageBroker(tag: string, data: Partial<IProductMessageBroker>): Promise<void>;
@@ -234,6 +234,37 @@ class ProductsBuilderService {
234
234
  throw e;
235
235
  }
236
236
  }
237
+ /**
238
+ * Persist monitor results without re-validating or re-encrypting the probe payload.
239
+ * This is used by the local SDK's batched Redis-to-backend status flush.
240
+ */
241
+ async updateHealthcheckStatuses(productTag, checks) {
242
+ await this.initializeProductByTag(productTag);
243
+ const healthchecks = await this.fetchProductHealthchecks();
244
+ for (const check of checks) {
245
+ const existing = healthchecks.find((healthcheck) => healthcheck.tag === check.tag);
246
+ if (!existing) {
247
+ console.warn(`[MONITOR] Healthcheck "${check.tag}" no longer exists on ${productTag}; skipping status update`);
248
+ continue;
249
+ }
250
+ const updates = new Map((check.environments || []).map((environment) => [
251
+ environment.slug,
252
+ environment,
253
+ ]));
254
+ const envs = (existing.envs || []).map((environment) => {
255
+ var _a, _b, _c;
256
+ const update = updates.get(environment.slug);
257
+ if (!update)
258
+ return environment;
259
+ const checkedAt = (_a = update.checkedAt) !== null && _a !== void 0 ? _a : update.lastChecked;
260
+ const latency = (_b = update.latency) !== null && _b !== void 0 ? _b : update.lastLatency;
261
+ return Object.assign(Object.assign({}, environment), { status: (_c = update.status) !== null && _c !== void 0 ? _c : environment.status, lastChecked: checkedAt !== null && checkedAt !== void 0 ? checkedAt : environment.lastChecked, lastLatency: latency !== null && latency !== void 0 ? latency : environment.lastLatency, lastAvailable: update.status === 'available'
262
+ ? checkedAt !== null && checkedAt !== void 0 ? checkedAt : new Date().toISOString()
263
+ : environment.lastAvailable });
264
+ });
265
+ await this.productApi.updateProduct(this.product_id, Object.assign(Object.assign({}, existing), { tag: check.tag, envs, component: enums_1.ProductComponents.HEALTHCHECK, action: enums_1.RequestAction.UPDATE }), this.getUserAccess());
266
+ }
267
+ }
237
268
  async fetchHealthcheck(access_tag, tag, throwError = false) {
238
269
  const healthchecks = await this.productApi.fetchProductComponents(this.product_id, 'healthcheck', this.getUserAccess());
239
270
  const health = healthchecks.find((data) => data.tag === tag && data.app === access_tag);
@@ -306,6 +337,47 @@ class ProductsBuilderService {
306
337
  list.push(item);
307
338
  this.product[key] = list;
308
339
  }
340
+ /**
341
+ * Validate app healthchecks against the product's connected app and its selected action.
342
+ * Every environment carries an explicit payload, including `{}` for actions with no inputs.
343
+ */
344
+ async validateAppHealthcheckSchema(schema) {
345
+ var _a, _b, _c, _d;
346
+ const probe = schema.probe;
347
+ const probeType = (_a = probe === null || probe === void 0 ? void 0 : probe.type) !== null && _a !== void 0 ? _a : ((probe === null || probe === void 0 ? void 0 : probe.app) || schema.app ? 'app' : undefined);
348
+ if (probeType !== 'app')
349
+ return;
350
+ const appTag = (_b = probe === null || probe === void 0 ? void 0 : probe.app) !== null && _b !== void 0 ? _b : schema.app;
351
+ const actionTag = (_c = probe === null || probe === void 0 ? void 0 : probe.event) !== null && _c !== void 0 ? _c : schema.event;
352
+ if (!appTag) {
353
+ throw new Error(`App healthcheck "${schema.tag}" requires a connected app access tag`);
354
+ }
355
+ if (!actionTag) {
356
+ throw new Error(`App healthcheck "${schema.tag}" requires an app action`);
357
+ }
358
+ if (!Array.isArray(schema.envs) || schema.envs.length === 0) {
359
+ throw new Error(`App healthcheck "${schema.tag}" requires at least one environment payload`);
360
+ }
361
+ const app = await this.fetchThirdPartyAppByAccessTag(appTag);
362
+ const version = app.versions.find((candidate) => candidate.tag === app.version);
363
+ if (!version) {
364
+ throw new Error(`Version ${app.version} not found for connected app ${appTag}`);
365
+ }
366
+ const action = version.actions.find((candidate) => candidate.tag === actionTag);
367
+ if (!action) {
368
+ throw new Error(`Action ${actionTag} not found on connected app ${appTag}`);
369
+ }
370
+ for (const env of schema.envs || []) {
371
+ if (!(await this.fetchEnv(env.slug))) {
372
+ throw new Error(`Env ${env.slug} does not exist`);
373
+ }
374
+ const payload = (_d = env.input) !== null && _d !== void 0 ? _d : probe === null || probe === void 0 ? void 0 : probe.input;
375
+ if (payload == null) {
376
+ throw new Error(`App healthcheck "${schema.tag}" requires an input payload for env ${env.slug}; use {} when the action has no inputs`);
377
+ }
378
+ await this.validateActionDataInput({ input: payload }, action, payload, 0, 0);
379
+ }
380
+ }
309
381
  /**
310
382
  * Create a healthcheck from a schema (e.g. from resilience health.define/create).
311
383
  * Persists as product component so processor healthcheck workers and health.list/fetch see it.
@@ -318,6 +390,7 @@ class ProductsBuilderService {
318
390
  const probeInput = (probe === null || probe === void 0 ? void 0 : probe.input) != null ? probe.input : undefined;
319
391
  const app = (_a = probe === null || probe === void 0 ? void 0 : probe.app) !== null && _a !== void 0 ? _a : schema.app;
320
392
  const event = (_b = probe === null || probe === void 0 ? void 0 : probe.event) !== null && _b !== void 0 ? _b : schema.event;
393
+ await this.validateAppHealthcheckSchema(schema);
321
394
  if (probeInput != null && !((_c = this.product) === null || _c === void 0 ? void 0 : _c.private_key)) {
322
395
  throw new Error('Healthcheck inputs must be encrypted with the product private key; product not initialized or missing private_key');
323
396
  }
@@ -339,6 +412,7 @@ class ProductsBuilderService {
339
412
  const probeInput = (probe === null || probe === void 0 ? void 0 : probe.input) != null ? probe.input : undefined;
340
413
  const app = (_a = probe === null || probe === void 0 ? void 0 : probe.app) !== null && _a !== void 0 ? _a : schema.app;
341
414
  const event = (_b = probe === null || probe === void 0 ? void 0 : probe.event) !== null && _b !== void 0 ? _b : schema.event;
415
+ await this.validateAppHealthcheckSchema(schema);
342
416
  if (probeInput != null && !((_c = this.product) === null || _c === void 0 ? void 0 : _c.private_key)) {
343
417
  throw new Error('Healthcheck inputs must be encrypted with the product private key; product not initialized or missing private_key');
344
418
  }
@@ -1371,6 +1445,9 @@ class ProductsBuilderService {
1371
1445
  data.slug = slug;
1372
1446
  }
1373
1447
  await this.productApi.updateProduct(this.product_id, Object.assign(Object.assign({}, Object.assign(Object.assign({}, env), data)), { component: enums_1.ProductComponents.ENV, action: enums_1.RequestAction.UPDATE }), this.getUserAccess());
1448
+ this.envCache.delete(slug);
1449
+ if (data.slug !== slug)
1450
+ this.envCache.delete(data.slug);
1374
1451
  }
1375
1452
  catch (e) {
1376
1453
  throw e;
@@ -1380,8 +1457,8 @@ class ProductsBuilderService {
1380
1457
  const components = await this.productApi.fetchProductComponents(this.product_id, 'env', this.getUserAccess());
1381
1458
  return components;
1382
1459
  }
1383
- async fetchEnv(slug) {
1384
- if (this.product_id && this.envCache.has(slug)) {
1460
+ async fetchEnv(slug, refresh = false) {
1461
+ if (!refresh && this.product_id && this.envCache.has(slug)) {
1385
1462
  return this.envCache.get(slug);
1386
1463
  }
1387
1464
  const component = await this.productApi.fetchProductComponentByTag(this.product_id, 'env', slug, this.getUserAccess());