@boostercloud/framework-provider-azure 2.16.0 → 2.18.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.
@@ -27,6 +27,7 @@ export declare const environmentVarNames: {
27
27
  readonly eventHubName: "EVENTHUB_NAME";
28
28
  readonly eventHubMaxRetries: "EVENTHUB_MAX_RETRIES";
29
29
  readonly eventHubMode: "EVENTHUB_MODE";
30
+ readonly rocketFunctionAppNames: "ROCKET_FUNCTION_APP_NAMES";
30
31
  };
31
32
  export declare const AZURE_CONFLICT_ERROR_CODE = 409;
32
33
  export declare const AZURE_PRECONDITION_FAILED_ERROR = 412;
package/dist/constants.js CHANGED
@@ -29,6 +29,7 @@ exports.environmentVarNames = {
29
29
  eventHubName: 'EVENTHUB_NAME',
30
30
  eventHubMaxRetries: 'EVENTHUB_MAX_RETRIES',
31
31
  eventHubMode: 'EVENTHUB_MODE',
32
+ rocketFunctionAppNames: 'ROCKET_FUNCTION_APP_NAMES',
32
33
  };
33
34
  // Azure special error codes
34
35
  exports.AZURE_CONFLICT_ERROR_CODE = 409;
@@ -1,3 +1,4 @@
1
- import { CosmosClient } from '@azure/cosmos';
1
+ import { CosmosClient, ItemDefinition } from '@azure/cosmos';
2
2
  import { BoosterConfig, FilterFor, ProjectionFor, ReadModelListResult, SortFor } from '@boostercloud/framework-types';
3
+ export declare function replaceOrDeleteItem(cosmosDb: CosmosClient, container: string, config: BoosterConfig, id: string, partitionKey: string, newValue?: ItemDefinition): Promise<void>;
3
4
  export declare function search<TResult>(cosmosDb: CosmosClient, config: BoosterConfig, containerName: string, filters: FilterFor<unknown>, limit?: number | undefined, afterCursor?: Record<string, string> | undefined, paginatedVersion?: boolean, order?: SortFor<unknown>, projections?: ProjectionFor<unknown> | string): Promise<Array<TResult> | ReadModelListResult<TResult>>;
@@ -1,8 +1,21 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.search = void 0;
3
+ exports.search = exports.replaceOrDeleteItem = void 0;
4
4
  const framework_types_1 = require("@boostercloud/framework-types");
5
5
  const framework_common_helpers_1 = require("@boostercloud/framework-common-helpers");
6
+ async function replaceOrDeleteItem(cosmosDb, container, config, id, partitionKey, newValue) {
7
+ if (newValue) {
8
+ await cosmosDb
9
+ .database(config.resourceNames.applicationStack)
10
+ .container(container)
11
+ .item(id, partitionKey)
12
+ .replace(newValue);
13
+ }
14
+ else {
15
+ await cosmosDb.database(config.resourceNames.applicationStack).container(container).item(id, partitionKey).delete();
16
+ }
17
+ }
18
+ exports.replaceOrDeleteItem = replaceOrDeleteItem;
6
19
  async function search(cosmosDb, config, containerName, filters, limit, afterCursor, paginatedVersion = false, order, projections = '*') {
7
20
  const logger = (0, framework_common_helpers_1.getLogger)(config, 'query-helper#search');
8
21
  const filterExpression = buildFilterExpression(filters);
package/dist/index.js CHANGED
@@ -19,6 +19,7 @@ const events_stream_producer_adapter_1 = require("./library/events-stream-produc
19
19
  const event_hubs_1 = require("@azure/event-hubs");
20
20
  const events_stream_consumer_adapter_1 = require("./library/events-stream-consumer-adapter");
21
21
  const health_adapter_1 = require("./library/health-adapter");
22
+ const event_delete_adapter_1 = require("./library/event-delete-adapter");
22
23
  const events_store_adapter_1 = require("./library/events-store-adapter");
23
24
  let cosmosClient;
24
25
  if (typeof process.env[constants_1.environmentVarNames.cosmosDbConnectionString] === 'undefined') {
@@ -75,6 +76,10 @@ const Provider = (rockets) => ({
75
76
  search: events_searcher_adapter_1.searchEvents.bind(null, cosmosClient),
76
77
  searchEntitiesIDs: events_searcher_adapter_1.searchEntitiesIds.bind(null, cosmosClient),
77
78
  storeDispatched: events_adapter_1.storeDispatchedEvent.bind(null, cosmosClient),
79
+ findDeletableEvent: event_delete_adapter_1.findDeletableEvent.bind(null, cosmosClient),
80
+ findDeletableSnapshot: event_delete_adapter_1.findDeletableSnapshot.bind(null, cosmosClient),
81
+ deleteEvent: event_delete_adapter_1.deleteEvent.bind(null, cosmosClient),
82
+ deleteSnapshot: event_delete_adapter_1.deleteSnapshot.bind(null, cosmosClient),
78
83
  },
79
84
  // ProviderReadModelsLibrary
80
85
  readModels: {
@@ -120,6 +125,7 @@ const Provider = (rockets) => ({
120
125
  graphQLFunctionUrl: health_adapter_1.graphqlFunctionUrl,
121
126
  isGraphQLFunctionUp: health_adapter_1.isGraphQLFunctionUp,
122
127
  rawRequestToHealthEnvelope: health_adapter_1.rawRequestToSensorHealth,
128
+ areRocketFunctionsUp: health_adapter_1.areRocketFunctionsUp,
123
129
  },
124
130
  // ProviderInfrastructureGetter
125
131
  infrastructure: () => {
@@ -0,0 +1,6 @@
1
+ import { BoosterConfig, EventDeleteParameters, SnapshotDeleteParameters, EventEnvelopeFromDatabase, EntitySnapshotEnvelopeFromDatabase } from '@boostercloud/framework-types';
2
+ import { CosmosClient } from '@azure/cosmos';
3
+ export declare function findDeletableEvent(cosmosDb: CosmosClient, config: BoosterConfig, parameters: EventDeleteParameters): Promise<Array<EventEnvelopeFromDatabase>>;
4
+ export declare function findDeletableSnapshot(cosmosDb: CosmosClient, config: BoosterConfig, parameters: SnapshotDeleteParameters): Promise<Array<EntitySnapshotEnvelopeFromDatabase>>;
5
+ export declare function deleteEvent(cosmosDb: CosmosClient, config: BoosterConfig, events: Array<EventEnvelopeFromDatabase>): Promise<void>;
6
+ export declare function deleteSnapshot(cosmosDb: CosmosClient, config: BoosterConfig, snapshots: Array<EntitySnapshotEnvelopeFromDatabase>): Promise<void>;
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.deleteSnapshot = exports.deleteEvent = exports.findDeletableSnapshot = exports.findDeletableEvent = void 0;
4
+ const framework_common_helpers_1 = require("@boostercloud/framework-common-helpers");
5
+ const query_helper_1 = require("../helpers/query-helper");
6
+ async function findDeletableEvent(cosmosDb, config, parameters) {
7
+ const logger = (0, framework_common_helpers_1.getLogger)(config, 'events-delete-adapter#findDeletableEvent');
8
+ const stringifyParameters = JSON.stringify(parameters);
9
+ logger.debug(`Initiating a deletable event search for ${stringifyParameters}`);
10
+ const eventStore = config.resourceNames.eventsStore;
11
+ const filter = buildDeleteEventFilter(parameters.entityTypeName, parameters.entityID, parameters.createdAt);
12
+ const result = (await (0, query_helper_1.search)(cosmosDb, config, eventStore, filter));
13
+ logger.debug(`Finished deletable event search for ${stringifyParameters}`);
14
+ return result;
15
+ }
16
+ exports.findDeletableEvent = findDeletableEvent;
17
+ async function findDeletableSnapshot(cosmosDb, config, parameters) {
18
+ const logger = (0, framework_common_helpers_1.getLogger)(config, 'events-delete-adapter#findDeletableSnapshot');
19
+ const stringifyParameters = JSON.stringify(parameters);
20
+ logger.debug(`Initiating a deletable snapshot search for ${stringifyParameters}`);
21
+ const eventStore = config.resourceNames.eventsStore;
22
+ const filter = buildDeleteEntityFilter(parameters.entityTypeName, parameters.entityID, parameters.createdAt);
23
+ const result = (await (0, query_helper_1.search)(cosmosDb, config, eventStore, filter));
24
+ logger.debug(`Finished deletable snapshot search for ${stringifyParameters}`);
25
+ return result;
26
+ }
27
+ exports.findDeletableSnapshot = findDeletableSnapshot;
28
+ async function deleteEvent(cosmosDb, config, events) {
29
+ const logger = (0, framework_common_helpers_1.getLogger)(config, 'events-delete-adapter#deleteEvent');
30
+ const stringifyParameters = JSON.stringify(events);
31
+ logger.debug(`Initiating an event delete for ${stringifyParameters}`);
32
+ const eventStore = config.resourceNames.eventsStore;
33
+ if (!events || events.length === 0) {
34
+ logger.warn('Could not find events to delete');
35
+ return;
36
+ }
37
+ for (const event of events) {
38
+ const newEvent = buildNewEvent(event);
39
+ const partitionKey = partitionKeyBuilder(event.entityTypeName, event.entityID, 'event');
40
+ await (0, query_helper_1.replaceOrDeleteItem)(cosmosDb, eventStore, config, event.id, partitionKey, newEvent);
41
+ }
42
+ logger.debug(`Finished event delete for ${stringifyParameters}`);
43
+ }
44
+ exports.deleteEvent = deleteEvent;
45
+ async function deleteSnapshot(cosmosDb, config, snapshots) {
46
+ const logger = (0, framework_common_helpers_1.getLogger)(config, 'events-delete-adapter#deleteSnapshot');
47
+ const stringifyParameters = JSON.stringify(snapshots);
48
+ logger.debug(`Initiating a snapshot delete for ${stringifyParameters}`);
49
+ const eventStore = config.resourceNames.eventsStore;
50
+ if (!snapshots || snapshots.length === 0) {
51
+ logger.warn('Could not find snapshot to delete');
52
+ return;
53
+ }
54
+ for (const snapshot of snapshots) {
55
+ const partitionKey = partitionKeyBuilder(snapshot.entityTypeName, snapshot.entityID, 'snapshot');
56
+ await (0, query_helper_1.replaceOrDeleteItem)(cosmosDb, eventStore, config, snapshot.id, partitionKey);
57
+ }
58
+ logger.debug(`Finished snapshot delete for ${stringifyParameters}`);
59
+ }
60
+ exports.deleteSnapshot = deleteSnapshot;
61
+ function buildNewEvent(existingEvent) {
62
+ return {
63
+ ...existingEvent,
64
+ deletedAt: new Date().toISOString(),
65
+ value: {},
66
+ };
67
+ }
68
+ function buildDeleteEventFilter(entityTypeName, entityId, createdAt) {
69
+ const value = `${entityTypeName}-${entityId}-event`;
70
+ return {
71
+ entityTypeName_entityID_kind: { eq: value },
72
+ createdAt: { eq: createdAt },
73
+ kind: { eq: 'event' },
74
+ deletedAt: { isDefined: false },
75
+ };
76
+ }
77
+ function buildDeleteEntityFilter(entityTypeName, entityId, createdAt) {
78
+ const value = `${entityTypeName}-${entityId}-snapshot`;
79
+ return {
80
+ entityTypeName_entityID_kind: { eq: value },
81
+ kind: { eq: 'snapshot' },
82
+ createdAt: { eq: createdAt },
83
+ deletedAt: { isDefined: false },
84
+ };
85
+ }
86
+ function partitionKeyBuilder(entityTypeName, entityID, kind) {
87
+ return `${entityTypeName}-${entityID}-${kind}`;
88
+ }
@@ -10,10 +10,13 @@ function rawEventsToEnvelopes(context) {
10
10
  }
11
11
  exports.rawEventsToEnvelopes = rawEventsToEnvelopes;
12
12
  async function readEntityEventsSince(cosmosDb, config, entityTypeName, entityID, since) {
13
+ const logger = (0, framework_common_helpers_1.getLogger)(config, 'events-adapter#readEntityEventsSince');
13
14
  const fromTime = since ? since : originOfTime;
14
15
  const querySpec = {
15
16
  query: `SELECT * FROM c WHERE c["${constants_1.eventsStoreAttributes.partitionKey}"] = @partitionKey ` +
16
- `AND c["${constants_1.eventsStoreAttributes.sortKey}"] > @fromTime ORDER BY c["${constants_1.eventsStoreAttributes.sortKey}"] ASC`,
17
+ `AND c["${constants_1.eventsStoreAttributes.sortKey}"] > @fromTime ` +
18
+ 'AND NOT IS_DEFINED(c["deletedAt"]) ' +
19
+ `ORDER BY c["${constants_1.eventsStoreAttributes.sortKey}"] ASC`,
17
20
  parameters: [
18
21
  {
19
22
  name: '@partitionKey',
@@ -30,6 +33,7 @@ async function readEntityEventsSince(cosmosDb, config, entityTypeName, entityID,
30
33
  .container(config.resourceNames.eventsStore)
31
34
  .items.query(querySpec)
32
35
  .fetchAll();
36
+ logger.debug(`Loaded events for entity ${entityTypeName} with ID ${entityID} with result:`, resources);
33
37
  return resources;
34
38
  }
35
39
  exports.readEntityEventsSince = readEntityEventsSince;
@@ -22,7 +22,11 @@ exports.searchEvents = searchEvents;
22
22
  async function searchEntitiesIds(cosmosDb, config, limit, afterCursor, entityTypeName) {
23
23
  const logger = (0, framework_common_helpers_1.getLogger)(config, 'events-searcher-adapter#searchEntitiesIds');
24
24
  logger.debug(`Initiating a paginated events search. limit: ${limit}, afterCursor: ${JSON.stringify(afterCursor)}, entityTypeName: ${entityTypeName}`);
25
- const filterQuery = { kind: { eq: 'event' }, entityTypeName: { eq: entityTypeName } };
25
+ const filterQuery = {
26
+ kind: { eq: 'event' },
27
+ entityTypeName: { eq: entityTypeName },
28
+ deletedAt: { isDefined: false },
29
+ };
26
30
  const eventStore = config.resourceNames.eventsStore;
27
31
  const result = (await (0, query_helper_1.search)(cosmosDb, config, eventStore, filterQuery, limit, afterCursor, true, undefined, 'DISTINCT c.entityID'));
28
32
  logger.debug('Unique events search result', result);
@@ -47,6 +47,7 @@ function resultToEventSearchResponse(result) {
47
47
  user: item.currentUser,
48
48
  createdAt: item.createdAt,
49
49
  value: item.value,
50
+ deletedAt: item.deletedAt,
50
51
  };
51
52
  });
52
53
  return eventSearchResult !== null && eventSearchResult !== void 0 ? eventSearchResult : [];
@@ -7,9 +7,14 @@ export declare function isContainerUp(cosmosDb: CosmosClient, config: BoosterCon
7
7
  export declare function countAll(container: Container): Promise<number>;
8
8
  export declare function databaseEventsHealthDetails(cosmosDb: CosmosClient, config: BoosterConfig): Promise<unknown>;
9
9
  export declare function graphqlFunctionUrl(): Promise<string>;
10
+ export declare function rocketFunctionAppUrl(functionAppName: string): Promise<string>;
10
11
  export declare function isDatabaseEventUp(cosmosDb: CosmosClient, config: BoosterConfig): Promise<boolean>;
11
12
  export declare function areDatabaseReadModelsUp(cosmosDb: CosmosClient, config: BoosterConfig): Promise<boolean>;
12
13
  export declare function isGraphQLFunctionUp(): Promise<boolean>;
14
+ export declare function isRocketFunctionUp(rocketFunctionAppName: string): Promise<boolean>;
15
+ export declare function areRocketFunctionsUp(): Promise<{
16
+ [key: string]: boolean;
17
+ }>;
13
18
  export declare function rawRequestToSensorHealthComponentPath(rawRequest: Context): string;
14
19
  export declare function rawRequestToSensorHealth(context: Context): HealthEnvelope;
15
20
  export declare function databaseReadModelsHealthDetails(cosmosDb: CosmosClient, config: BoosterConfig): Promise<unknown>;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.databaseReadModelsHealthDetails = exports.rawRequestToSensorHealth = exports.rawRequestToSensorHealthComponentPath = exports.isGraphQLFunctionUp = exports.areDatabaseReadModelsUp = exports.isDatabaseEventUp = exports.graphqlFunctionUrl = exports.databaseEventsHealthDetails = exports.countAll = exports.isContainerUp = exports.getContainer = exports.databaseUrl = void 0;
3
+ exports.databaseReadModelsHealthDetails = exports.rawRequestToSensorHealth = exports.rawRequestToSensorHealthComponentPath = exports.areRocketFunctionsUp = exports.isRocketFunctionUp = exports.isGraphQLFunctionUp = exports.areDatabaseReadModelsUp = exports.isDatabaseEventUp = exports.rocketFunctionAppUrl = exports.graphqlFunctionUrl = exports.databaseEventsHealthDetails = exports.countAll = exports.isContainerUp = exports.getContainer = exports.databaseUrl = void 0;
4
4
  const constants_1 = require("../constants");
5
5
  const framework_common_helpers_1 = require("@boostercloud/framework-common-helpers");
6
6
  async function databaseUrl(cosmosDb, config) {
@@ -43,6 +43,10 @@ async function graphqlFunctionUrl() {
43
43
  }
44
44
  }
45
45
  exports.graphqlFunctionUrl = graphqlFunctionUrl;
46
+ async function rocketFunctionAppUrl(functionAppName) {
47
+ return `https://${functionAppName}.azurewebsites.net`;
48
+ }
49
+ exports.rocketFunctionAppUrl = rocketFunctionAppUrl;
46
50
  async function isDatabaseEventUp(cosmosDb, config) {
47
51
  return await isContainerUp(cosmosDb, config, config.resourceNames.eventsStore);
48
52
  }
@@ -68,6 +72,27 @@ async function isGraphQLFunctionUp() {
68
72
  }
69
73
  }
70
74
  exports.isGraphQLFunctionUp = isGraphQLFunctionUp;
75
+ async function isRocketFunctionUp(rocketFunctionAppName) {
76
+ try {
77
+ const functionAppUrl = await rocketFunctionAppUrl(rocketFunctionAppName);
78
+ const response = await (0, framework_common_helpers_1.request)(functionAppUrl, 'GET');
79
+ return response.status === 200;
80
+ }
81
+ catch (e) {
82
+ return false;
83
+ }
84
+ }
85
+ exports.isRocketFunctionUp = isRocketFunctionUp;
86
+ async function areRocketFunctionsUp() {
87
+ var _a;
88
+ const functionAppNames = ((_a = process.env[constants_1.environmentVarNames.rocketFunctionAppNames]) === null || _a === void 0 ? void 0 : _a.split(',').filter((str) => str.trim() !== '')) || [];
89
+ const results = await Promise.all(functionAppNames.map(async (functionAppName) => {
90
+ const isUp = await isRocketFunctionUp(functionAppName);
91
+ return { [functionAppName]: isUp };
92
+ }));
93
+ return results.reduce((acc, result) => ({ ...acc, ...result }), {});
94
+ }
95
+ exports.areRocketFunctionsUp = areRocketFunctionsUp;
71
96
  function rawRequestToSensorHealthComponentPath(rawRequest) {
72
97
  var _a;
73
98
  const parameters = (_a = rawRequest.req) === null || _a === void 0 ? void 0 : _a.url.replace(/^.*sensor\/health\/?/, '');
@@ -86,13 +86,18 @@ async function storeReadModel(db, config, readModelName, readModel) {
86
86
  exports.storeReadModel = storeReadModel;
87
87
  async function deleteReadModel(db, config, readModelName, readModel) {
88
88
  const logger = (0, framework_common_helpers_1.getLogger)(config, 'read-model-adapter#deleteReadModel');
89
- logger.debug(`[ReadModelAdapter#deleteReadModel] Entering to Read model deleted. ID = ${readModel.id}`);
90
- await db
91
- .database(config.resourceNames.applicationStack)
92
- .container(config.resourceNames.forReadModel(readModelName))
93
- .item(readModel.id, readModel.id)
94
- .delete();
95
- logger.debug(`[ReadModelAdapter#deleteReadModel] Read model deleted. ID = ${readModel.id}`);
89
+ logger.debug(`Entering to Read model deleted. ${readModelName} ID = ${readModel.id}`);
90
+ try {
91
+ await db
92
+ .database(config.resourceNames.applicationStack)
93
+ .container(config.resourceNames.forReadModel(readModelName))
94
+ .item(readModel.id, readModel.id)
95
+ .delete();
96
+ logger.debug(`Read model deleted. ${readModelName} ID = ${readModel.id}`);
97
+ }
98
+ catch (e) {
99
+ logger.warn(`Read model to delete ${readModelName} ID = ${readModel.id} not found`);
100
+ }
96
101
  }
97
102
  exports.deleteReadModel = deleteReadModel;
98
103
  async function rawReadModelEventsToEnvelopes(config, rawEvents) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boostercloud/framework-provider-azure",
3
- "version": "2.16.0",
3
+ "version": "2.18.0",
4
4
  "description": "Handle Booster's integration with Azure",
5
5
  "keywords": [
6
6
  "framework-provider-azure"
@@ -27,14 +27,14 @@
27
27
  "@azure/functions": "^1.2.2",
28
28
  "@azure/identity": "~2.1.0",
29
29
  "@azure/event-hubs": "5.11.1",
30
- "@boostercloud/framework-common-helpers": "^2.16.0",
31
- "@boostercloud/framework-types": "^2.16.0",
30
+ "@boostercloud/framework-common-helpers": "^2.18.0",
31
+ "@boostercloud/framework-types": "^2.18.0",
32
32
  "tslib": "^2.4.0",
33
33
  "@effect-ts/core": "^0.60.4",
34
34
  "@azure/web-pubsub": "~1.1.0"
35
35
  },
36
36
  "devDependencies": {
37
- "@boostercloud/eslint-config": "^2.16.0",
37
+ "@boostercloud/eslint-config": "^2.18.0",
38
38
  "@types/chai": "4.2.18",
39
39
  "@types/chai-as-promised": "7.1.4",
40
40
  "@types/faker": "5.1.5",