@boostercloud/framework-provider-azure 0.21.4 → 0.22.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.
@@ -7,3 +7,5 @@ export declare const environmentVarNames: {
7
7
  readonly websocketAPIURL: "BOOSTER_WEBSOCKET_API_URL";
8
8
  readonly cosmosDbConnectionString: "COSMOSDB_CONNECTION_STRING";
9
9
  };
10
+ export declare const AZURE_CONFLICT_ERROR_CODE = 409;
11
+ export declare const AZURE_PRECONDITION_FAILED_ERROR = 412;
package/dist/constants.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.environmentVarNames = exports.eventsStoreAttributes = void 0;
3
+ exports.AZURE_PRECONDITION_FAILED_ERROR = exports.AZURE_CONFLICT_ERROR_CODE = exports.environmentVarNames = exports.eventsStoreAttributes = void 0;
4
4
  exports.eventsStoreAttributes = {
5
5
  partitionKey: 'entityTypeName_entityID_kind',
6
6
  sortKey: 'createdAt',
@@ -10,3 +10,5 @@ exports.environmentVarNames = {
10
10
  websocketAPIURL: 'BOOSTER_WEBSOCKET_API_URL',
11
11
  cosmosDbConnectionString: 'COSMOSDB_CONNECTION_STRING',
12
12
  };
13
+ exports.AZURE_CONFLICT_ERROR_CODE = 409;
14
+ exports.AZURE_PRECONDITION_FAILED_ERROR = 412;
package/dist/index.js CHANGED
@@ -9,6 +9,7 @@ const cosmos_1 = require("@azure/cosmos");
9
9
  const constants_1 = require("./constants");
10
10
  const read_model_adapter_1 = require("./library/read-model-adapter");
11
11
  const searcher_adapter_1 = require("./library/searcher-adapter");
12
+ const scheduled_adapter_1 = require("./library/scheduled-adapter");
12
13
  let cosmosClient;
13
14
  if (typeof process.env[constants_1.environmentVarNames.cosmosDbConnectionString] === 'undefined') {
14
15
  cosmosClient = {};
@@ -41,7 +42,7 @@ const Provider = () => ({
41
42
  rawToEnvelopes: undefined,
42
43
  fetchSubscriptions: undefined,
43
44
  store: read_model_adapter_1.storeReadModel.bind(null, cosmosClient),
44
- delete: undefined,
45
+ delete: read_model_adapter_1.deleteReadModel.bind(null, cosmosClient),
45
46
  deleteSubscription: undefined,
46
47
  deleteAllSubscriptions: undefined,
47
48
  },
@@ -63,7 +64,7 @@ const Provider = () => ({
63
64
  },
64
65
  // ScheduledCommandsLibrary
65
66
  scheduled: {
66
- rawToEnvelope: undefined,
67
+ rawToEnvelope: scheduled_adapter_1.rawScheduledInputToEnvelope,
67
68
  },
68
69
  // ProviderInfrastructureGetter
69
70
  infrastructure: () => {
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.rawGraphQLRequestToEnvelope = void 0;
4
4
  async function rawGraphQLRequestToEnvelope(context, logger) {
5
+ var _a, _b;
5
6
  logger.debug('Received GraphQL request: ', context.req);
6
7
  const requestID = context.executionContext.invocationId;
7
8
  const connectionID = undefined; // TODO: Add this when sockets are supported
@@ -14,6 +15,7 @@ async function rawGraphQLRequestToEnvelope(context, logger) {
14
15
  return {
15
16
  requestID,
16
17
  eventType,
18
+ token: (_b = (_a = context.req) === null || _a === void 0 ? void 0 : _a.headers) === null || _b === void 0 ? void 0 : _b.authorization,
17
19
  value: graphQLValue,
18
20
  };
19
21
  }
@@ -2,3 +2,4 @@ import { CosmosClient } from '@azure/cosmos';
2
2
  import { BoosterConfig, Logger, ReadModelInterface, ReadOnlyNonEmptyArray, UUID } from '@boostercloud/framework-types';
3
3
  export declare function fetchReadModel(db: CosmosClient, config: BoosterConfig, logger: Logger, readModelName: string, readModelID: UUID): Promise<ReadOnlyNonEmptyArray<ReadModelInterface>>;
4
4
  export declare function storeReadModel(db: CosmosClient, config: BoosterConfig, logger: Logger, readModelName: string, readModel: ReadModelInterface): Promise<void>;
5
+ export declare function deleteReadModel(db: CosmosClient, config: BoosterConfig, logger: Logger, readModelName: string, readModel: ReadModelInterface): Promise<void>;
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.storeReadModel = exports.fetchReadModel = void 0;
3
+ exports.deleteReadModel = exports.storeReadModel = exports.fetchReadModel = void 0;
4
+ const framework_types_1 = require("@boostercloud/framework-types");
5
+ const constants_1 = require("../constants");
4
6
  async function fetchReadModel(db, config, logger, readModelName, readModelID) {
5
7
  const { resource } = await db
6
8
  .database(config.resourceNames.applicationStack)
@@ -8,14 +10,78 @@ async function fetchReadModel(db, config, logger, readModelName, readModelID) {
8
10
  .item(readModelID, readModelID)
9
11
  .read();
10
12
  logger.debug(`[ReadModelAdapter#fetchReadModel] Loaded read model ${readModelName} with ID ${readModelID} with result:`, resource);
11
- return [resource];
13
+ if (!resource)
14
+ return [resource];
15
+ return [
16
+ {
17
+ ...resource,
18
+ boosterMetadata: {
19
+ ...resource === null || resource === void 0 ? void 0 : resource.boosterMetadata,
20
+ optimisticConcurrencyValue: resource._etag,
21
+ },
22
+ },
23
+ ];
12
24
  }
13
25
  exports.fetchReadModel = fetchReadModel;
26
+ async function insertReadModel(logger, readModel, db, config, readModelName) {
27
+ const itemModel = {
28
+ ...readModel,
29
+ id: readModel.id.toString(),
30
+ };
31
+ try {
32
+ await db
33
+ .database(config.resourceNames.applicationStack)
34
+ .container(config.resourceNames.forReadModel(readModelName))
35
+ .items.create(itemModel);
36
+ logger.debug('[ReadModelAdapter#insertReadModel] Read model inserted');
37
+ }
38
+ catch (err) {
39
+ // In case of conflict (The ID provided for a resource on a PUT or POST operation has been taken by an existing resource) we should retry it
40
+ if (err.code == constants_1.AZURE_CONFLICT_ERROR_CODE) {
41
+ logger.debug('[ReadModelAdapter#insertReadModel] Read model insert failed with a conflict failure');
42
+ throw new framework_types_1.OptimisticConcurrencyUnexpectedVersionError(err.message);
43
+ }
44
+ throw err;
45
+ }
46
+ }
47
+ async function updateReadModel(readModel, db, config, readModelName, logger) {
48
+ var _a;
49
+ /** upsert only occurs if the etag we are sending matches the etag on the server. i.e. Only replace if the item hasn't changed */
50
+ const options = {
51
+ accessCondition: { condition: ((_a = readModel.boosterMetadata) === null || _a === void 0 ? void 0 : _a.optimisticConcurrencyValue) || '*', type: 'IfMatch' },
52
+ };
53
+ try {
54
+ await db
55
+ .database(config.resourceNames.applicationStack)
56
+ .container(config.resourceNames.forReadModel(readModelName))
57
+ .items.upsert(readModel, options);
58
+ logger.debug('[ReadModelAdapter#updateReadModel] Read model updated');
59
+ }
60
+ catch (err) {
61
+ // If there is a precondition failure then we should retry it
62
+ if (err.code == constants_1.AZURE_PRECONDITION_FAILED_ERROR) {
63
+ logger.debug('[ReadModelAdapter#updateReadModel] Read model update failed with a pre-condition failure');
64
+ throw new framework_types_1.OptimisticConcurrencyUnexpectedVersionError(err.message);
65
+ }
66
+ throw err;
67
+ }
68
+ }
14
69
  async function storeReadModel(db, config, logger, readModelName, readModel) {
70
+ var _a, _b;
71
+ const version = (_b = (_a = readModel.boosterMetadata) === null || _a === void 0 ? void 0 : _a.version) !== null && _b !== void 0 ? _b : 0;
72
+ if (version === 0) {
73
+ await insertReadModel(logger, readModel, db, config, readModelName);
74
+ }
75
+ await updateReadModel(readModel, db, config, readModelName, logger);
76
+ }
77
+ exports.storeReadModel = storeReadModel;
78
+ async function deleteReadModel(db, config, logger, readModelName, readModel) {
79
+ logger.debug(`[ReadModelAdapter#deleteReadModel] Entering to Read model deleted. ID = ${readModel.id}`);
15
80
  await db
16
81
  .database(config.resourceNames.applicationStack)
17
82
  .container(config.resourceNames.forReadModel(readModelName))
18
- .items.upsert(readModel);
19
- logger.debug('[ReadModelAdapter#storeReadModel] Read model stored');
83
+ .item(readModel.id, readModel.id)
84
+ .delete();
85
+ logger.debug(`[ReadModelAdapter#deleteReadModel] Read model deleted. ID = ${readModel.id}`);
20
86
  }
21
- exports.storeReadModel = storeReadModel;
87
+ exports.deleteReadModel = deleteReadModel;
@@ -0,0 +1,8 @@
1
+ import { Logger, ScheduledCommandEnvelope } from '@boostercloud/framework-types';
2
+ interface AzureScheduledCommandEnvelope {
3
+ bindings: {
4
+ [name: string]: unknown;
5
+ };
6
+ }
7
+ export declare function rawScheduledInputToEnvelope(input: Partial<AzureScheduledCommandEnvelope>, logger: Logger): Promise<ScheduledCommandEnvelope>;
8
+ export {};
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.rawScheduledInputToEnvelope = void 0;
4
+ const framework_types_1 = require("@boostercloud/framework-types");
5
+ async function rawScheduledInputToEnvelope(input, logger) {
6
+ logger.debug('Received AzureScheduledCommand request: ', input);
7
+ if (!input.bindings || !Object.keys(input.bindings).length)
8
+ throw new Error(`bindings is not defined or empty, scheduled command envelope should have the structure {bindings: [name: string]: string }, but you gave ${JSON.stringify(input)}`);
9
+ return {
10
+ requestID: framework_types_1.UUID.generate(),
11
+ typeName: Object.keys(input.bindings)[0],
12
+ };
13
+ }
14
+ exports.rawScheduledInputToEnvelope = rawScheduledInputToEnvelope;
@@ -1,3 +1,3 @@
1
1
  import { CosmosClient } from '@azure/cosmos';
2
- import { BoosterConfig, Logger, FilterFor } from '@boostercloud/framework-types';
3
- export declare function searchReadModel(cosmosDb: CosmosClient, config: BoosterConfig, logger: Logger, readModelName: string, filters: FilterFor<unknown>): Promise<Array<any>>;
2
+ import { BoosterConfig, Logger, FilterFor, ReadModelListResult } from '@boostercloud/framework-types';
3
+ export declare function searchReadModel(cosmosDb: CosmosClient, config: BoosterConfig, logger: Logger, readModelName: string, filters: FilterFor<unknown>, limit?: number, afterCursor?: Record<string, string> | undefined, paginatedVersion?: boolean): Promise<Array<any> | ReadModelListResult<any>>;
@@ -2,10 +2,12 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.searchReadModel = void 0;
4
4
  const framework_types_1 = require("@boostercloud/framework-types");
5
- async function searchReadModel(cosmosDb, config, logger, readModelName, filters) {
5
+ async function searchReadModel(cosmosDb, config, logger, readModelName, filters, limit, afterCursor, paginatedVersion = false) {
6
6
  const filterExpression = buildFilterExpression(filters);
7
+ const queryDefinition = `SELECT * FROM c ${filterExpression !== '' ? `WHERE ${filterExpression}` : filterExpression}`;
8
+ const queryWithPagination = queryDefinition + (paginatedVersion && limit ? ` OFFSET ${(afterCursor === null || afterCursor === void 0 ? void 0 : afterCursor.id) || 0} LIMIT ${limit}` : '');
7
9
  const querySpec = {
8
- query: `SELECT * FROM c ${filterExpression !== '' ? `WHERE ${filterExpression}` : filterExpression}`,
10
+ query: queryWithPagination,
9
11
  parameters: buildExpressionAttributeValues(filters),
10
12
  };
11
13
  logger.debug('Running search with the following params: \n', querySpec);
@@ -14,8 +16,16 @@ async function searchReadModel(cosmosDb, config, logger, readModelName, filters)
14
16
  .container(config.resourceNames.forReadModel(readModelName))
15
17
  .items.query(querySpec)
16
18
  .fetchAll();
17
- logger.debug('Search result: ', resources);
18
- return resources !== null && resources !== void 0 ? resources : [];
19
+ if (paginatedVersion) {
20
+ return {
21
+ items: resources !== null && resources !== void 0 ? resources : [],
22
+ count: resources.length,
23
+ cursor: { id: ((limit ? limit : 1) + ((afterCursor === null || afterCursor === void 0 ? void 0 : afterCursor.id) ? parseInt(afterCursor === null || afterCursor === void 0 ? void 0 : afterCursor.id) : 0)).toString() },
24
+ };
25
+ }
26
+ else {
27
+ return resources !== null && resources !== void 0 ? resources : [];
28
+ }
19
29
  }
20
30
  exports.searchReadModel = searchReadModel;
21
31
  function buildFilterExpression(filters, usedPlaceholders = []) {
@@ -59,8 +69,9 @@ function buildOperation(propName, filter = {}, usedPlaceholders, nested) {
59
69
  return `CONTAINS(${propName}, ${holder(index)})`;
60
70
  case 'beginsWith':
61
71
  return `STARTSWITH(${propName}, ${holder(index)})`;
62
- case 'includes':
63
- return `CONTAINS(${propName}, ${holder(index)})`;
72
+ case 'includes': {
73
+ return `ARRAY_CONTAINS(${propName}, ${holder(index)}, true)`;
74
+ }
64
75
  default:
65
76
  if (typeof value === 'object') {
66
77
  return buildOperation(operation, value, usedPlaceholders, propName);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boostercloud/framework-provider-azure",
3
- "version": "0.21.4",
3
+ "version": "0.22.0",
4
4
  "description": "Handle Booster's integration with Azure",
5
5
  "keywords": [
6
6
  "framework-provider-azure"
@@ -22,7 +22,7 @@
22
22
  "dependencies": {
23
23
  "@azure/cosmos": "3.7.3",
24
24
  "@azure/functions": "^1.2.2",
25
- "@boostercloud/framework-types": "^0.21.4",
25
+ "@boostercloud/framework-types": "^0.22.0",
26
26
  "chai": "4.2.0",
27
27
  "chai-as-promised": "7.1.1",
28
28
  "mocha": "8.4.0",
@@ -41,5 +41,5 @@
41
41
  "bugs": {
42
42
  "url": "https://github.com/boostercloud/booster/issues"
43
43
  },
44
- "gitHead": "f0fe51ca4d0cb3745e9880a5dc05fd7b474bbc26"
44
+ "gitHead": "b479b1f7e3614d7737b4c82aac7b2020a640b269"
45
45
  }