@boostercloud/framework-provider-azure 1.9.0 → 1.10.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.
- package/dist/constants.d.ts +13 -0
- package/dist/constants.js +13 -1
- package/dist/index.js +11 -10
- package/dist/library/api-adapter.d.ts +1 -1
- package/dist/library/api-adapter.js +10 -1
- package/dist/library/connections-adapter.d.ts +11 -0
- package/dist/library/connections-adapter.js +75 -0
- package/dist/library/graphql-adapter.d.ts +1 -1
- package/dist/library/graphql-adapter.js +12 -10
- package/dist/library/read-model-adapter.d.ts +2 -1
- package/dist/library/read-model-adapter.js +22 -1
- package/dist/library/subscription-adapter.d.ts +14 -0
- package/dist/library/subscription-adapter.js +112 -0
- package/dist/library/subscription-model.d.ts +28 -0
- package/dist/library/subscription-model.js +2 -0
- package/package.json +6 -5
package/dist/constants.d.ts
CHANGED
|
@@ -1,7 +1,20 @@
|
|
|
1
|
+
import { BoosterConfig } from '@boostercloud/framework-types';
|
|
1
2
|
export declare const eventsStoreAttributes: {
|
|
2
3
|
readonly partitionKey: "entityTypeName_entityID_kind";
|
|
3
4
|
readonly sortKey: "createdAt";
|
|
4
5
|
};
|
|
6
|
+
export declare const subscriptionsStoreAttributes: {
|
|
7
|
+
readonly partitionKey: "className";
|
|
8
|
+
readonly sortKey: "connectionID_subscriptionID";
|
|
9
|
+
readonly ttl: "expirationTime";
|
|
10
|
+
readonly indexByConnectionIDPartitionKey: "connectionID";
|
|
11
|
+
readonly indexByConnectionIDSortKey: "subscriptionID";
|
|
12
|
+
readonly indexByConnectionIDName: (config: BoosterConfig) => string;
|
|
13
|
+
};
|
|
14
|
+
export declare const connectionsStoreAttributes: {
|
|
15
|
+
readonly partitionKey: "connectionID";
|
|
16
|
+
readonly ttl: "expirationTime";
|
|
17
|
+
};
|
|
5
18
|
export declare const environmentVarNames: {
|
|
6
19
|
readonly restAPIURL: "BOOSTER_REST_API_URL";
|
|
7
20
|
readonly websocketAPIURL: "BOOSTER_WEBSOCKET_API_URL";
|
package/dist/constants.js
CHANGED
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.AZURE_PRECONDITION_FAILED_ERROR = exports.AZURE_CONFLICT_ERROR_CODE = exports.environmentVarNames = exports.eventsStoreAttributes = void 0;
|
|
3
|
+
exports.AZURE_PRECONDITION_FAILED_ERROR = exports.AZURE_CONFLICT_ERROR_CODE = exports.environmentVarNames = exports.connectionsStoreAttributes = exports.subscriptionsStoreAttributes = exports.eventsStoreAttributes = void 0;
|
|
4
4
|
exports.eventsStoreAttributes = {
|
|
5
5
|
partitionKey: 'entityTypeName_entityID_kind',
|
|
6
6
|
sortKey: 'createdAt',
|
|
7
7
|
};
|
|
8
|
+
exports.subscriptionsStoreAttributes = {
|
|
9
|
+
partitionKey: 'className',
|
|
10
|
+
sortKey: 'connectionID_subscriptionID',
|
|
11
|
+
ttl: 'expirationTime',
|
|
12
|
+
indexByConnectionIDPartitionKey: 'connectionID',
|
|
13
|
+
indexByConnectionIDSortKey: 'subscriptionID',
|
|
14
|
+
indexByConnectionIDName: (config) => config.resourceNames.subscriptionsStore + '-index-by-connection',
|
|
15
|
+
};
|
|
16
|
+
exports.connectionsStoreAttributes = {
|
|
17
|
+
partitionKey: 'connectionID',
|
|
18
|
+
ttl: 'expirationTime',
|
|
19
|
+
};
|
|
8
20
|
exports.environmentVarNames = {
|
|
9
21
|
restAPIURL: 'BOOSTER_REST_API_URL',
|
|
10
22
|
websocketAPIURL: 'BOOSTER_WEBSOCKET_API_URL',
|
package/dist/index.js
CHANGED
|
@@ -11,6 +11,8 @@ const read_model_adapter_1 = require("./library/read-model-adapter");
|
|
|
11
11
|
const searcher_adapter_1 = require("./library/searcher-adapter");
|
|
12
12
|
const scheduled_adapter_1 = require("./library/scheduled-adapter");
|
|
13
13
|
const events_searcher_adapter_1 = require("./library/events-searcher-adapter");
|
|
14
|
+
const subscription_adapter_1 = require("./library/subscription-adapter");
|
|
15
|
+
const connections_adapter_1 = require("./library/connections-adapter");
|
|
14
16
|
const rocket_adapter_1 = require("./library/rocket-adapter");
|
|
15
17
|
let cosmosClient;
|
|
16
18
|
if (typeof process.env[constants_1.environmentVarNames.cosmosDbConnectionString] === 'undefined') {
|
|
@@ -42,13 +44,13 @@ const Provider = (rockets) => ({
|
|
|
42
44
|
readModels: {
|
|
43
45
|
fetch: read_model_adapter_1.fetchReadModel.bind(null, cosmosClient),
|
|
44
46
|
search: searcher_adapter_1.searchReadModel.bind(null, cosmosClient),
|
|
45
|
-
|
|
46
|
-
rawToEnvelopes: undefined,
|
|
47
|
-
fetchSubscriptions: undefined,
|
|
47
|
+
rawToEnvelopes: read_model_adapter_1.rawReadModelEventsToEnvelopes,
|
|
48
48
|
store: read_model_adapter_1.storeReadModel.bind(null, cosmosClient),
|
|
49
49
|
delete: read_model_adapter_1.deleteReadModel.bind(null, cosmosClient),
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
subscribe: subscription_adapter_1.subscribeToReadModel.bind(null, cosmosClient),
|
|
51
|
+
fetchSubscriptions: subscription_adapter_1.fetchSubscriptions.bind(null, cosmosClient),
|
|
52
|
+
deleteSubscription: subscription_adapter_1.deleteSubscription.bind(null, cosmosClient),
|
|
53
|
+
deleteAllSubscriptions: subscription_adapter_1.deleteAllSubscriptions.bind(null, cosmosClient),
|
|
52
54
|
},
|
|
53
55
|
// ProviderGraphQLLibrary
|
|
54
56
|
graphQL: {
|
|
@@ -61,10 +63,10 @@ const Provider = (rockets) => ({
|
|
|
61
63
|
requestFailed: api_adapter_1.requestFailed,
|
|
62
64
|
},
|
|
63
65
|
connections: {
|
|
64
|
-
storeData:
|
|
65
|
-
fetchData:
|
|
66
|
-
deleteData:
|
|
67
|
-
sendMessage:
|
|
66
|
+
storeData: connections_adapter_1.storeConnectionData.bind(null, cosmosClient),
|
|
67
|
+
fetchData: connections_adapter_1.fetchConnectionData.bind(null, cosmosClient),
|
|
68
|
+
deleteData: connections_adapter_1.deleteConnectionData.bind(null, cosmosClient),
|
|
69
|
+
sendMessage: connections_adapter_1.sendMessageToConnection,
|
|
68
70
|
},
|
|
69
71
|
// ScheduledCommandsLibrary
|
|
70
72
|
scheduled: {
|
|
@@ -89,5 +91,4 @@ const Provider = (rockets) => ({
|
|
|
89
91
|
},
|
|
90
92
|
});
|
|
91
93
|
exports.Provider = Provider;
|
|
92
|
-
function notImplemented() { }
|
|
93
94
|
tslib_1.__exportStar(require("./constants"), exports);
|
|
@@ -9,5 +9,5 @@ export interface ContextResponse {
|
|
|
9
9
|
status: number;
|
|
10
10
|
cookies?: Cookie[];
|
|
11
11
|
}
|
|
12
|
-
export declare function requestSucceeded(body?: unknown, headers?: Record<string, number | string | ReadonlyArray<string>>): Promise<ContextResponse>;
|
|
12
|
+
export declare function requestSucceeded(body?: unknown, headers?: Record<string, number | string | ReadonlyArray<string>>): Promise<ContextResponse | void>;
|
|
13
13
|
export declare function requestFailed(error: Error): Promise<ContextResponse>;
|
|
@@ -2,8 +2,16 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.requestFailed = exports.requestSucceeded = void 0;
|
|
4
4
|
const framework_types_1 = require("@boostercloud/framework-types");
|
|
5
|
-
|
|
5
|
+
const WEB_SOCKET_PROTOCOL_HEADER = 'Sec-WebSocket-Protocol';
|
|
6
6
|
async function requestSucceeded(body, headers) {
|
|
7
|
+
if (!body && (!headers || Object.keys(headers).length === 0)) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
const isWebSocket = headers && Object.keys(headers).includes(WEB_SOCKET_PROTOCOL_HEADER);
|
|
11
|
+
let extraParams = {};
|
|
12
|
+
if (isWebSocket) {
|
|
13
|
+
extraParams = { Subprotocol: headers[WEB_SOCKET_PROTOCOL_HEADER] };
|
|
14
|
+
}
|
|
7
15
|
return {
|
|
8
16
|
headers: {
|
|
9
17
|
'Access-Control-Allow-Origin': '*',
|
|
@@ -12,6 +20,7 @@ async function requestSucceeded(body, headers) {
|
|
|
12
20
|
},
|
|
13
21
|
status: 200,
|
|
14
22
|
body: body ? JSON.stringify(body) : '',
|
|
23
|
+
...extraParams,
|
|
15
24
|
};
|
|
16
25
|
}
|
|
17
26
|
exports.requestSucceeded = requestSucceeded;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { BoosterConfig, ConnectionDataEnvelope } from '@boostercloud/framework-types';
|
|
2
|
+
import { connectionsStoreAttributes } from '../constants';
|
|
3
|
+
import { CosmosClient } from '@azure/cosmos';
|
|
4
|
+
export interface ConnectionIndexRecord extends ConnectionDataEnvelope {
|
|
5
|
+
id: string;
|
|
6
|
+
[connectionsStoreAttributes.partitionKey]: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function storeConnectionData(cosmosDb: CosmosClient, config: BoosterConfig, connectionID: string, data: ConnectionDataEnvelope): Promise<void>;
|
|
9
|
+
export declare function fetchConnectionData(cosmosDb: CosmosClient, config: BoosterConfig, connectionID: string): Promise<ConnectionDataEnvelope | undefined>;
|
|
10
|
+
export declare function deleteConnectionData(cosmosDb: CosmosClient, config: BoosterConfig, connectionID: string): Promise<void>;
|
|
11
|
+
export declare function sendMessageToConnection(config: BoosterConfig, connectionID: string, data: unknown): Promise<void>;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sendMessageToConnection = exports.deleteConnectionData = exports.fetchConnectionData = exports.storeConnectionData = void 0;
|
|
4
|
+
const constants_1 = require("../constants");
|
|
5
|
+
const framework_common_helpers_1 = require("@boostercloud/framework-common-helpers");
|
|
6
|
+
const web_pubsub_1 = require("@azure/web-pubsub");
|
|
7
|
+
async function storeConnectionData(cosmosDb, config, connectionID, data) {
|
|
8
|
+
const ttl = data[constants_1.connectionsStoreAttributes.ttl];
|
|
9
|
+
await cosmosDb
|
|
10
|
+
.database(config.resourceNames.applicationStack)
|
|
11
|
+
.container(config.resourceNames.connectionsStore)
|
|
12
|
+
.items.create({
|
|
13
|
+
...data,
|
|
14
|
+
[constants_1.connectionsStoreAttributes.partitionKey]: connectionID,
|
|
15
|
+
ttl: ttl,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
exports.storeConnectionData = storeConnectionData;
|
|
19
|
+
async function fetchConnectionData(cosmosDb, config, connectionID) {
|
|
20
|
+
const { resources } = await cosmosDb
|
|
21
|
+
.database(config.resourceNames.applicationStack)
|
|
22
|
+
.container(config.resourceNames.connectionsStore)
|
|
23
|
+
.items.query({
|
|
24
|
+
query: `SELECT *
|
|
25
|
+
FROM c
|
|
26
|
+
WHERE c["${constants_1.connectionsStoreAttributes.partitionKey}"] = @partitionKey`,
|
|
27
|
+
parameters: [
|
|
28
|
+
{
|
|
29
|
+
name: '@partitionKey',
|
|
30
|
+
value: connectionID,
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
})
|
|
34
|
+
.fetchAll();
|
|
35
|
+
return resources[0];
|
|
36
|
+
}
|
|
37
|
+
exports.fetchConnectionData = fetchConnectionData;
|
|
38
|
+
async function deleteConnectionData(cosmosDb, config, connectionID) {
|
|
39
|
+
const logger = (0, framework_common_helpers_1.getLogger)(config, 'connections-adapter#deleteConnectionData');
|
|
40
|
+
const { resources } = await cosmosDb
|
|
41
|
+
.database(config.resourceNames.applicationStack)
|
|
42
|
+
.container(config.resourceNames.connectionsStore)
|
|
43
|
+
.items.query({
|
|
44
|
+
query: `SELECT *
|
|
45
|
+
FROM c
|
|
46
|
+
WHERE c["${constants_1.connectionsStoreAttributes.partitionKey}"] = @partitionKey`,
|
|
47
|
+
parameters: [
|
|
48
|
+
{
|
|
49
|
+
name: '@partitionKey',
|
|
50
|
+
value: connectionID,
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
})
|
|
54
|
+
.fetchAll();
|
|
55
|
+
const foundConnections = resources;
|
|
56
|
+
if ((foundConnections === null || foundConnections === void 0 ? void 0 : foundConnections.length) < 1) {
|
|
57
|
+
logger.info(`No connections found with connectionID=${connectionID}`);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const connectionsToDelete = foundConnections[0]; // There can't be more than one, as we used the full primary key in the query
|
|
61
|
+
logger.debug('Deleting connection = ', connectionsToDelete);
|
|
62
|
+
await cosmosDb
|
|
63
|
+
.database(config.resourceNames.applicationStack)
|
|
64
|
+
.container(config.resourceNames.connectionsStore)
|
|
65
|
+
.item(connectionsToDelete.id, connectionsToDelete[constants_1.connectionsStoreAttributes.partitionKey])
|
|
66
|
+
.delete();
|
|
67
|
+
}
|
|
68
|
+
exports.deleteConnectionData = deleteConnectionData;
|
|
69
|
+
async function sendMessageToConnection(config, connectionID, data) {
|
|
70
|
+
const logger = (0, framework_common_helpers_1.getLogger)(config, 'connection-adapter#sendMessageToConnection');
|
|
71
|
+
logger.debug(`Sending message ${JSON.stringify(data)} to connection ${connectionID}`);
|
|
72
|
+
const serviceClient = new web_pubsub_1.WebPubSubServiceClient(process.env.WebPubSubConnectionString, 'booster');
|
|
73
|
+
await serviceClient.sendToConnection(connectionID, JSON.stringify(data), { contentType: 'text/plain' });
|
|
74
|
+
}
|
|
75
|
+
exports.sendMessageToConnection = sendMessageToConnection;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { GraphQLRequestEnvelope, GraphQLRequestEnvelopeError
|
|
1
|
+
import { BoosterConfig, GraphQLRequestEnvelope, GraphQLRequestEnvelopeError } from '@boostercloud/framework-types';
|
|
2
2
|
import { Context } from '@azure/functions';
|
|
3
3
|
export declare function rawGraphQLRequestToEnvelope(config: BoosterConfig, context: Context): Promise<GraphQLRequestEnvelope | GraphQLRequestEnvelopeError>;
|
|
@@ -3,26 +3,28 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.rawGraphQLRequestToEnvelope = void 0;
|
|
4
4
|
const framework_common_helpers_1 = require("@boostercloud/framework-common-helpers");
|
|
5
5
|
async function rawGraphQLRequestToEnvelope(config, context) {
|
|
6
|
-
var _a, _b, _c, _d, _e, _f;
|
|
6
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
7
7
|
const logger = (0, framework_common_helpers_1.getLogger)(config, 'graphql-adapter#rawGraphQLRequestToEnvelope');
|
|
8
8
|
logger.debug('Received GraphQL request: ', context.req);
|
|
9
9
|
const requestID = context.executionContext.invocationId;
|
|
10
|
-
const
|
|
11
|
-
const
|
|
10
|
+
const connectionContext = (_a = context.bindingData) === null || _a === void 0 ? void 0 : _a.connectionContext;
|
|
11
|
+
const connectionID = connectionContext === null || connectionContext === void 0 ? void 0 : connectionContext.connectionId;
|
|
12
|
+
const eventType = (connectionContext === null || connectionContext === void 0 ? void 0 : connectionContext.eventType) === undefined ? 'MESSAGE' : (_b = connectionContext === null || connectionContext === void 0 ? void 0 : connectionContext.eventName) === null || _b === void 0 ? void 0 : _b.toUpperCase();
|
|
12
13
|
try {
|
|
13
14
|
let graphQLValue = undefined;
|
|
14
|
-
if (context.req) {
|
|
15
|
-
graphQLValue = context.req.body;
|
|
15
|
+
if (((_c = context.bindings) === null || _c === void 0 ? void 0 : _c.data) || ((_d = context.req) === null || _d === void 0 ? void 0 : _d.body)) {
|
|
16
|
+
graphQLValue = ((_e = context.bindings) === null || _e === void 0 ? void 0 : _e.data) || ((_f = context.req) === null || _f === void 0 ? void 0 : _f.body);
|
|
16
17
|
}
|
|
17
18
|
return {
|
|
18
19
|
requestID,
|
|
20
|
+
connectionID,
|
|
19
21
|
eventType,
|
|
20
|
-
token: (
|
|
22
|
+
token: (_h = (_g = context.req) === null || _g === void 0 ? void 0 : _g.headers) === null || _h === void 0 ? void 0 : _h.authorization,
|
|
21
23
|
value: graphQLValue,
|
|
22
24
|
context: {
|
|
23
25
|
request: {
|
|
24
|
-
headers: (
|
|
25
|
-
body: (
|
|
26
|
+
headers: (_j = context.req) === null || _j === void 0 ? void 0 : _j.headers,
|
|
27
|
+
body: (_k = context.req) === null || _k === void 0 ? void 0 : _k.body,
|
|
26
28
|
},
|
|
27
29
|
rawContext: context,
|
|
28
30
|
},
|
|
@@ -37,8 +39,8 @@ async function rawGraphQLRequestToEnvelope(config, context) {
|
|
|
37
39
|
eventType,
|
|
38
40
|
context: {
|
|
39
41
|
request: {
|
|
40
|
-
headers: (
|
|
41
|
-
body: (
|
|
42
|
+
headers: (_l = context.req) === null || _l === void 0 ? void 0 : _l.headers,
|
|
43
|
+
body: (_m = context.req) === null || _m === void 0 ? void 0 : _m.body,
|
|
42
44
|
},
|
|
43
45
|
rawContext: context,
|
|
44
46
|
},
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { CosmosClient } from '@azure/cosmos';
|
|
2
|
-
import { BoosterConfig, ReadModelInterface, ReadOnlyNonEmptyArray, UUID } from '@boostercloud/framework-types';
|
|
2
|
+
import { BoosterConfig, ReadModelEnvelope, ReadModelInterface, ReadOnlyNonEmptyArray, UUID } from '@boostercloud/framework-types';
|
|
3
3
|
export declare function fetchReadModel(db: CosmosClient, config: BoosterConfig, readModelName: string, readModelID: UUID): Promise<ReadOnlyNonEmptyArray<ReadModelInterface>>;
|
|
4
4
|
export declare function storeReadModel(db: CosmosClient, config: BoosterConfig, readModelName: string, readModel: ReadModelInterface): Promise<void>;
|
|
5
5
|
export declare function deleteReadModel(db: CosmosClient, config: BoosterConfig, readModelName: string, readModel: ReadModelInterface): Promise<void>;
|
|
6
|
+
export declare function rawReadModelEventsToEnvelopes(config: BoosterConfig, rawEvents: unknown): Promise<Array<ReadModelEnvelope>>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.deleteReadModel = exports.storeReadModel = exports.fetchReadModel = void 0;
|
|
3
|
+
exports.rawReadModelEventsToEnvelopes = exports.deleteReadModel = exports.storeReadModel = exports.fetchReadModel = 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
6
|
const constants_1 = require("../constants");
|
|
@@ -95,3 +95,24 @@ async function deleteReadModel(db, config, readModelName, readModel) {
|
|
|
95
95
|
logger.debug(`[ReadModelAdapter#deleteReadModel] Read model deleted. ID = ${readModel.id}`);
|
|
96
96
|
}
|
|
97
97
|
exports.deleteReadModel = deleteReadModel;
|
|
98
|
+
async function rawReadModelEventsToEnvelopes(config, rawEvents) {
|
|
99
|
+
const logger = (0, framework_common_helpers_1.getLogger)(config, 'read-model-adapter#rawReadModelEventsToEnvelopes');
|
|
100
|
+
logger.debug(`Parsing raw read models ${JSON.stringify(rawEvents)}`);
|
|
101
|
+
if (isSubscriptionContext(rawEvents)) {
|
|
102
|
+
const typeName = rawEvents.executionContext.functionName.replace('-subscriptions-notifier', '');
|
|
103
|
+
return rawEvents.bindings.rawEvent.map((rawEvent) => {
|
|
104
|
+
const { _rid, _self, _st, _etag, _lsn, _ts, ...rest } = rawEvent;
|
|
105
|
+
return {
|
|
106
|
+
typeName: typeName,
|
|
107
|
+
value: rest,
|
|
108
|
+
};
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
logger.warn(`Unexpected events to be parsed ${JSON.stringify(rawEvents)}`);
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
exports.rawReadModelEventsToEnvelopes = rawReadModelEventsToEnvelopes;
|
|
115
|
+
function isSubscriptionContext(rawRequest) {
|
|
116
|
+
return (rawRequest.bindings !== undefined &&
|
|
117
|
+
rawRequest.bindings.rawEvent !== undefined);
|
|
118
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { BoosterConfig, SubscriptionEnvelope } from '@boostercloud/framework-types';
|
|
2
|
+
import { CosmosClient } from '@azure/cosmos';
|
|
3
|
+
import { subscriptionsStoreAttributes } from '../constants';
|
|
4
|
+
export interface SubscriptionIndexRecord {
|
|
5
|
+
id: string;
|
|
6
|
+
[subscriptionsStoreAttributes.partitionKey]: string;
|
|
7
|
+
[subscriptionsStoreAttributes.sortKey]: string;
|
|
8
|
+
[subscriptionsStoreAttributes.indexByConnectionIDPartitionKey]: string;
|
|
9
|
+
[subscriptionsStoreAttributes.indexByConnectionIDSortKey]: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function subscribeToReadModel(cosmosDb: CosmosClient, config: BoosterConfig, subscriptionEnvelope: SubscriptionEnvelope): Promise<void>;
|
|
12
|
+
export declare function fetchSubscriptions(cosmosDb: CosmosClient, config: BoosterConfig, subscriptionName: string): Promise<Array<SubscriptionEnvelope>>;
|
|
13
|
+
export declare function deleteSubscription(cosmosDb: CosmosClient, config: BoosterConfig, connectionID: string, subscriptionID: string): Promise<void>;
|
|
14
|
+
export declare function deleteAllSubscriptions(cosmosDb: CosmosClient, config: BoosterConfig, connectionID: string): Promise<void>;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.deleteAllSubscriptions = exports.deleteSubscription = exports.fetchSubscriptions = exports.subscribeToReadModel = void 0;
|
|
4
|
+
const constants_1 = require("../constants");
|
|
5
|
+
const framework_common_helpers_1 = require("@boostercloud/framework-common-helpers");
|
|
6
|
+
async function subscribeToReadModel(cosmosDb, config, subscriptionEnvelope) {
|
|
7
|
+
if (!subscriptionEnvelope[constants_1.subscriptionsStoreAttributes.partitionKey] ||
|
|
8
|
+
!subscriptionEnvelope.connectionID || // First part of subscriptionsStoreAttributes.sortKey
|
|
9
|
+
!subscriptionEnvelope.operation.id || // Second part of subscriptionsStoreAttributes.sortKey
|
|
10
|
+
!subscriptionEnvelope[constants_1.subscriptionsStoreAttributes.ttl]) {
|
|
11
|
+
throw new Error('Subscription envelope is missing any of the following required attributes: ' +
|
|
12
|
+
`"${constants_1.subscriptionsStoreAttributes.partitionKey}", "connectionID", "operation.id", ${constants_1.subscriptionsStoreAttributes.ttl}"`);
|
|
13
|
+
}
|
|
14
|
+
const ttl = subscriptionEnvelope[constants_1.subscriptionsStoreAttributes.ttl];
|
|
15
|
+
await cosmosDb
|
|
16
|
+
.database(config.resourceNames.applicationStack)
|
|
17
|
+
.container(config.resourceNames.subscriptionsStore)
|
|
18
|
+
.items.create({
|
|
19
|
+
...subscriptionEnvelope,
|
|
20
|
+
[constants_1.subscriptionsStoreAttributes.sortKey]: sortKeyForSubscription(subscriptionEnvelope.connectionID, subscriptionEnvelope.operation.id),
|
|
21
|
+
[constants_1.subscriptionsStoreAttributes.indexByConnectionIDSortKey]: subscriptionEnvelope.operation.id,
|
|
22
|
+
ttl: ttl,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
exports.subscribeToReadModel = subscribeToReadModel;
|
|
26
|
+
async function fetchSubscriptions(cosmosDb, config, subscriptionName) {
|
|
27
|
+
// TODO: filter expired ones. Or... is it needed?
|
|
28
|
+
const { resources } = await cosmosDb
|
|
29
|
+
.database(config.resourceNames.applicationStack)
|
|
30
|
+
.container(config.resourceNames.subscriptionsStore)
|
|
31
|
+
.items.query({
|
|
32
|
+
query: `SELECT *
|
|
33
|
+
FROM c
|
|
34
|
+
WHERE c["${constants_1.subscriptionsStoreAttributes.partitionKey}"] = @partitionKey`,
|
|
35
|
+
parameters: [{ name: '@partitionKey', value: subscriptionName }],
|
|
36
|
+
})
|
|
37
|
+
.fetchAll();
|
|
38
|
+
return resources;
|
|
39
|
+
}
|
|
40
|
+
exports.fetchSubscriptions = fetchSubscriptions;
|
|
41
|
+
async function deleteSubscription(cosmosDb, config, connectionID, subscriptionID) {
|
|
42
|
+
const logger = (0, framework_common_helpers_1.getLogger)(config, 'subscription-adapter#deleteSubscription');
|
|
43
|
+
// TODO: Manage query pagination
|
|
44
|
+
const { resources } = await cosmosDb
|
|
45
|
+
.database(config.resourceNames.applicationStack)
|
|
46
|
+
.container(config.resourceNames.subscriptionsStore)
|
|
47
|
+
.items.query({
|
|
48
|
+
query: `SELECT *
|
|
49
|
+
FROM c
|
|
50
|
+
WHERE c["${constants_1.subscriptionsStoreAttributes.indexByConnectionIDPartitionKey}"] = @partitionKey
|
|
51
|
+
AND c["${constants_1.subscriptionsStoreAttributes.indexByConnectionIDSortKey}"] = @sortKey`,
|
|
52
|
+
parameters: [
|
|
53
|
+
{
|
|
54
|
+
name: '@partitionKey',
|
|
55
|
+
value: connectionID,
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: '@sortKey',
|
|
59
|
+
value: subscriptionID,
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
})
|
|
63
|
+
.fetchAll();
|
|
64
|
+
const foundSubscriptions = resources;
|
|
65
|
+
if ((foundSubscriptions === null || foundSubscriptions === void 0 ? void 0 : foundSubscriptions.length) < 1) {
|
|
66
|
+
logger.info(`[deleteSubscription] No subscriptions found with connectionID=${connectionID} and subscriptionID=${subscriptionID}`);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const subscriptionToDelete = foundSubscriptions[0]; // There can't be more than one, as we used the full primary key in the query
|
|
70
|
+
logger.debug('[deleteSubscription] Deleting subscription = ', subscriptionToDelete);
|
|
71
|
+
await cosmosDb
|
|
72
|
+
.database(config.resourceNames.applicationStack)
|
|
73
|
+
.container(config.resourceNames.subscriptionsStore)
|
|
74
|
+
.item(subscriptionToDelete.id, subscriptionToDelete[constants_1.subscriptionsStoreAttributes.partitionKey])
|
|
75
|
+
.delete();
|
|
76
|
+
}
|
|
77
|
+
exports.deleteSubscription = deleteSubscription;
|
|
78
|
+
async function deleteAllSubscriptions(cosmosDb, config, connectionID) {
|
|
79
|
+
const logger = (0, framework_common_helpers_1.getLogger)(config, 'subscription-adapter#deleteAllSubscriptions');
|
|
80
|
+
// TODO: Manage query pagination and db.batchWrite limit of 25 operations at a time
|
|
81
|
+
const { resources } = await cosmosDb
|
|
82
|
+
.database(config.resourceNames.applicationStack)
|
|
83
|
+
.container(config.resourceNames.subscriptionsStore)
|
|
84
|
+
.items.query({
|
|
85
|
+
query: `SELECT *
|
|
86
|
+
FROM c
|
|
87
|
+
WHERE c["${constants_1.subscriptionsStoreAttributes.indexByConnectionIDPartitionKey}"] = @partitionKey`,
|
|
88
|
+
parameters: [
|
|
89
|
+
{
|
|
90
|
+
name: '@partitionKey',
|
|
91
|
+
value: connectionID,
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
})
|
|
95
|
+
.fetchAll();
|
|
96
|
+
const foundSubscriptions = resources;
|
|
97
|
+
if ((foundSubscriptions === null || foundSubscriptions === void 0 ? void 0 : foundSubscriptions.length) < 1) {
|
|
98
|
+
logger.info(`No subscriptions found with connectionID=${connectionID}`);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
logger.debug(`Deleting all subscriptions for connectionID=${connectionID}, which are: `, foundSubscriptions);
|
|
102
|
+
const deletePromises = foundSubscriptions.map((subscriptionRecord) => cosmosDb
|
|
103
|
+
.database(config.resourceNames.applicationStack)
|
|
104
|
+
.container(config.resourceNames.subscriptionsStore)
|
|
105
|
+
.item(subscriptionRecord.id, subscriptionRecord[constants_1.subscriptionsStoreAttributes.partitionKey])
|
|
106
|
+
.delete());
|
|
107
|
+
await Promise.allSettled(deletePromises);
|
|
108
|
+
}
|
|
109
|
+
exports.deleteAllSubscriptions = deleteAllSubscriptions;
|
|
110
|
+
function sortKeyForSubscription(connectionID, subscriptionID) {
|
|
111
|
+
return `${connectionID}-${subscriptionID}`;
|
|
112
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { BoosterMetadata } from '@boostercloud/framework-types';
|
|
2
|
+
export interface SubscriptionContext {
|
|
3
|
+
invocationId: string;
|
|
4
|
+
traceContext: unknown;
|
|
5
|
+
executionContext: ExecutionContext;
|
|
6
|
+
bindings: Bindings;
|
|
7
|
+
bindingData: unknown;
|
|
8
|
+
bindingDefinitions: unknown;
|
|
9
|
+
}
|
|
10
|
+
export interface Bindings {
|
|
11
|
+
rawEvent: RawEvent[];
|
|
12
|
+
}
|
|
13
|
+
export interface RawEvent {
|
|
14
|
+
id: string;
|
|
15
|
+
_rid: string;
|
|
16
|
+
_self: string;
|
|
17
|
+
_ts: number;
|
|
18
|
+
_etag: string;
|
|
19
|
+
[key: string]: unknown;
|
|
20
|
+
boosterMetadata: BoosterMetadata;
|
|
21
|
+
_lsn: number;
|
|
22
|
+
}
|
|
23
|
+
export interface ExecutionContext {
|
|
24
|
+
invocationId: string;
|
|
25
|
+
functionName: string;
|
|
26
|
+
functionDirectory: string;
|
|
27
|
+
retryContext: null;
|
|
28
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@boostercloud/framework-provider-azure",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.0",
|
|
4
4
|
"description": "Handle Booster's integration with Azure",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"framework-provider-azure"
|
|
@@ -23,13 +23,14 @@
|
|
|
23
23
|
"@azure/cosmos": "^3.17.0",
|
|
24
24
|
"@azure/functions": "^1.2.2",
|
|
25
25
|
"@azure/identity": "~2.1.0",
|
|
26
|
-
"@boostercloud/framework-common-helpers": "^1.
|
|
27
|
-
"@boostercloud/framework-types": "^1.
|
|
26
|
+
"@boostercloud/framework-common-helpers": "^1.10.0",
|
|
27
|
+
"@boostercloud/framework-types": "^1.10.0",
|
|
28
28
|
"tslib": "^2.4.0",
|
|
29
|
-
"@effect-ts/core": "^0.60.4"
|
|
29
|
+
"@effect-ts/core": "^0.60.4",
|
|
30
|
+
"@azure/web-pubsub": "~1.1.0"
|
|
30
31
|
},
|
|
31
32
|
"devDependencies": {
|
|
32
|
-
"@boostercloud/eslint-config": "^1.
|
|
33
|
+
"@boostercloud/eslint-config": "^1.10.0",
|
|
33
34
|
"@types/chai": "4.2.18",
|
|
34
35
|
"@types/chai-as-promised": "7.1.4",
|
|
35
36
|
"@types/faker": "5.1.5",
|