lucid-extension-sdk 0.0.107 → 0.0.108
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/package.json +1 -1
- package/sdk/dataconnector/actions/patchresponsebody.js +8 -8
- package/sdk/dataconnector/actions/serializedactions.js +4 -5
- package/sdk/dataconnector/dataconnector.js +52 -52
- package/sdk/dataconnector/dataconnectorclient.d.ts +17 -0
- package/sdk/dataconnector/dataconnectorclient.js +39 -0
- package/sdk/dataconnector/datasourceclient.d.ts +3 -8
- package/sdk/dataconnector/datasourceclient.js +13 -1
- package/sdk/dataconnector/debugserver.js +1 -1
package/package.json
CHANGED
|
@@ -48,16 +48,16 @@ function serializePatchChanges(changes) {
|
|
|
48
48
|
const joinedErrors = {};
|
|
49
49
|
for (const changeGroup of changes) {
|
|
50
50
|
for (const change of changeGroup) {
|
|
51
|
-
if (!(change.patchId in joinedErrors)) {
|
|
52
|
-
joinedErrors[change.patchId] = {};
|
|
53
|
-
}
|
|
54
|
-
const patchErrors = joinedErrors[change.patchId];
|
|
55
51
|
for (const [primaryKey, fieldNames] of Object.entries(change.getErrors())) {
|
|
56
|
-
if (!(primaryKey in patchErrors)) {
|
|
57
|
-
patchErrors[primaryKey] = {};
|
|
58
|
-
}
|
|
59
|
-
const primaryKeyErrors = patchErrors[primaryKey];
|
|
60
52
|
for (const [fieldName, error] of Object.entries(fieldNames)) {
|
|
53
|
+
if (!(change.patchId in joinedErrors)) {
|
|
54
|
+
joinedErrors[change.patchId] = {};
|
|
55
|
+
}
|
|
56
|
+
const patchErrors = joinedErrors[change.patchId];
|
|
57
|
+
if (!(primaryKey in patchErrors)) {
|
|
58
|
+
patchErrors[primaryKey] = {};
|
|
59
|
+
}
|
|
60
|
+
const primaryKeyErrors = patchErrors[primaryKey];
|
|
61
61
|
primaryKeyErrors[fieldName] = error;
|
|
62
62
|
}
|
|
63
63
|
}
|
|
@@ -9,11 +9,11 @@ exports.actionsHeaderValidator = (0, validators_1.objectValidator)({ 'x-lucid-rs
|
|
|
9
9
|
const serializedActionsBaseValidator = (0, validators_1.objectValidator)({
|
|
10
10
|
'action': (0, validators_1.objectValidator)({ 'name': checks_1.isString, 'data': checks_1.isUnknown }),
|
|
11
11
|
'packageId': checks_1.isString,
|
|
12
|
-
'packageVersion': (0, validators_1.
|
|
12
|
+
'packageVersion': (0, validators_1.nullableOption)(checks_1.isString),
|
|
13
13
|
'dataConnectorName': checks_1.isString,
|
|
14
|
-
'installationId': (0, validators_1.
|
|
15
|
-
'userCredential': (0, validators_1.
|
|
16
|
-
'documentUpdateToken': (0, validators_1.
|
|
14
|
+
'installationId': (0, validators_1.nullableOption)(checks_1.isString),
|
|
15
|
+
'userCredential': (0, validators_1.nullableOption)(checks_1.isString),
|
|
16
|
+
'documentUpdateToken': (0, validators_1.nullableOption)(checks_1.isString),
|
|
17
17
|
'documentCollections': (0, validators_1.objectOfValidator)((0, validators_1.arrayValidator)(checks_1.isString)),
|
|
18
18
|
'updateFilterType': (0, validators_1.enumValidator)(dataupdatefiltertype_1.DataUpdateFilterType),
|
|
19
19
|
});
|
|
@@ -36,7 +36,6 @@ const serializedPatchDataValidator = (0, validators_1.arrayValidator)((0, valida
|
|
|
36
36
|
const serializedManageWebhookDataValidator = (0, validators_1.objectValidator)({
|
|
37
37
|
'webhooks': (0, validators_1.arrayValidator)((0, validators_1.objectValidator)({
|
|
38
38
|
'documentCollections': (0, validators_1.objectOfValidator)((0, validators_1.arrayValidator)(checks_1.isString)),
|
|
39
|
-
'updateFilterType': (0, validators_1.enumValidator)(dataupdatefiltertype_1.DataUpdateFilterType),
|
|
40
39
|
'webhookData': (0, validators_1.option)(checks_1.isUnknown),
|
|
41
40
|
})),
|
|
42
41
|
'webhookToken': checks_1.isString,
|
|
@@ -43,62 +43,62 @@ class DataConnector {
|
|
|
43
43
|
/** Call a defined action handler and gather its serialized response */
|
|
44
44
|
async runAction(url, headers, body) {
|
|
45
45
|
const actions = await this.client.parseActions(url, headers, body);
|
|
46
|
-
if (actions) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if (!(action instanceof action_1.DataConnectorAsynchronousAction)) {
|
|
57
|
-
throw DataConnectorRunError.withMessage(400, `Passing uncredentialed action to ${action.name} which is defined to be asynchronous`);
|
|
58
|
-
}
|
|
59
|
-
return actionResponder(action);
|
|
60
|
-
}
|
|
61
|
-
else {
|
|
62
|
-
return actionResponder(action);
|
|
63
|
-
}
|
|
64
|
-
})();
|
|
65
|
-
responses.push(await response);
|
|
66
|
-
}
|
|
67
|
-
if (actions[0].name === exports.DataConnectorActionKeys.ManageWebhook) {
|
|
68
|
-
const response = responses[0];
|
|
69
|
-
// leave the type on this variable because our return type isn't strict. This means that
|
|
70
|
-
// changes to serializeManageWebhookResponse's return type will need to consider this point
|
|
71
|
-
const body = (0, managewebhookresponsebody_1.serializeManageWebhookResponse)(response);
|
|
72
|
-
return { status: 200, body };
|
|
73
|
-
}
|
|
74
|
-
if (actions[0].name === exports.DataConnectorActionKeys.Patch) {
|
|
75
|
-
const responsesCasted = responses;
|
|
76
|
-
// leave the type on this variable because our return type isn't strict. This means that
|
|
77
|
-
// changes to serializePatchChanges's return type will need to consider this point
|
|
78
|
-
const body = (0, patchresponsebody_1.serializePatchChanges)(responsesCasted);
|
|
79
|
-
return { status: 200, body };
|
|
80
|
-
}
|
|
81
|
-
else if (responses.length === 1) {
|
|
82
|
-
return { status: 200, body: responses[0] };
|
|
83
|
-
}
|
|
84
|
-
else {
|
|
85
|
-
return { status: 200, body: responses };
|
|
46
|
+
if (!actions) {
|
|
47
|
+
console.warn('Received bad request', url, headers, body);
|
|
48
|
+
return { status: 404, body: { error: 'Bad request' } };
|
|
49
|
+
}
|
|
50
|
+
const actionName = actions[0].name;
|
|
51
|
+
try {
|
|
52
|
+
const responses = [];
|
|
53
|
+
for (const action of actions) {
|
|
54
|
+
if (!(action.name in this.actions)) {
|
|
55
|
+
throw DataConnectorRunError.withMessage(404, `Action ${action.name} is not implemented`);
|
|
86
56
|
}
|
|
57
|
+
const { actionResponder, asynchronous } = this.actions[action.name];
|
|
58
|
+
const response = (() => {
|
|
59
|
+
if (asynchronous) {
|
|
60
|
+
if (!(action instanceof action_1.DataConnectorAsynchronousAction)) {
|
|
61
|
+
throw DataConnectorRunError.withMessage(400, `Passing uncredentialed action to ${action.name} which is defined to be asynchronous`);
|
|
62
|
+
}
|
|
63
|
+
return actionResponder(action);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
return actionResponder(action);
|
|
67
|
+
}
|
|
68
|
+
})();
|
|
69
|
+
responses.push(await response);
|
|
87
70
|
}
|
|
88
|
-
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
71
|
+
if (actionName === exports.DataConnectorActionKeys.ManageWebhook) {
|
|
72
|
+
const response = responses[0];
|
|
73
|
+
// leave the type on this variable because our return type isn't strict. This means that
|
|
74
|
+
// changes to serializeManageWebhookResponse's return type will need to consider this point
|
|
75
|
+
const body = (0, managewebhookresponsebody_1.serializeManageWebhookResponse)(response);
|
|
76
|
+
return { status: 200, body };
|
|
77
|
+
}
|
|
78
|
+
if (actionName === exports.DataConnectorActionKeys.Patch) {
|
|
79
|
+
const responsesCasted = responses;
|
|
80
|
+
// leave the type on this variable because our return type isn't strict. This means that
|
|
81
|
+
// changes to serializePatchChanges's return type will need to consider this point
|
|
82
|
+
const body = (0, patchresponsebody_1.serializePatchChanges)(responsesCasted);
|
|
83
|
+
return { status: 200, body };
|
|
84
|
+
}
|
|
85
|
+
else if (responses.length === 1) {
|
|
86
|
+
return { status: 200, body: responses[0] };
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
return { status: 200, body: responses };
|
|
98
90
|
}
|
|
99
91
|
}
|
|
100
|
-
|
|
101
|
-
|
|
92
|
+
catch (e) {
|
|
93
|
+
const errorLogger = console.error ? console.error : console.log;
|
|
94
|
+
if (e instanceof DataConnectorRunError) {
|
|
95
|
+
errorLogger(`Error running ${actionName} action`, e);
|
|
96
|
+
return { status: e.status, body: e.body };
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
errorLogger(`Error running ${actionName} action`, e);
|
|
100
|
+
return { status: 500, body: { error: `error running ${actionName} action` } };
|
|
101
|
+
}
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
104
|
/** Add a route to the given express app (or generate a new app) to serve this data connector */
|
|
@@ -1,6 +1,14 @@
|
|
|
1
|
+
import { JsonObject } from '../core/jsonserializable';
|
|
1
2
|
import { DataConnectorAction } from './actions/action';
|
|
2
3
|
import { DataSourceClient, MockDataSourceClient } from './datasourceclient';
|
|
3
4
|
import { SignatureValidator } from './signaturevalidator';
|
|
5
|
+
/** @ignore */
|
|
6
|
+
export declare type WebhookValidationSecret = {
|
|
7
|
+
'oauth2': {
|
|
8
|
+
'clientId': string;
|
|
9
|
+
'clientSecret': string;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
4
12
|
/**
|
|
5
13
|
* Client for requests to and from Lucid
|
|
6
14
|
*/
|
|
@@ -24,6 +32,15 @@ export declare class DataConnectorClient {
|
|
|
24
32
|
getPublicKey(): Promise<string>;
|
|
25
33
|
/** Validate and parse an action request from Lucid */
|
|
26
34
|
parseActions(url: string, headers: Record<string, string | string[] | unknown>, actions: unknown): Promise<DataConnectorAction[] | undefined>;
|
|
35
|
+
/** @ignore */
|
|
36
|
+
getSecretFromWebhook(webhookUpdateToken: string, WebhookValidationSecret: WebhookValidationSecret): Promise<string | undefined>;
|
|
37
|
+
/** @ignore */
|
|
38
|
+
getCustomWebhookData(webhookUpdateToken: string): Promise<JsonObject>;
|
|
39
|
+
/** @ignore */
|
|
40
|
+
patchCustomWebhookData(webhookUpdateToken: string, patch: JsonObject): Promise<{
|
|
41
|
+
data: JsonObject;
|
|
42
|
+
status: number;
|
|
43
|
+
}>;
|
|
27
44
|
}
|
|
28
45
|
/** Use this for writing automated tests for your data connector */
|
|
29
46
|
export declare class MockDataConnectorClient extends DataConnectorClient {
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.MockDataConnectorClient = exports.DataConnectorClient = void 0;
|
|
4
|
+
const checks_1 = require("../core/checks");
|
|
4
5
|
const serializedactions_1 = require("./actions/serializedactions");
|
|
6
|
+
const dataconnector_1 = require("./dataconnector");
|
|
5
7
|
const datasourceclient_1 = require("./datasourceclient");
|
|
6
8
|
const defaultfetchfunction_1 = require("./defaultfetchfunction");
|
|
7
9
|
const signaturevalidator_1 = require("./signaturevalidator");
|
|
@@ -29,6 +31,9 @@ class DataConnectorClient {
|
|
|
29
31
|
if (!this.cachedPublicKey) {
|
|
30
32
|
const url = `${this.urls.main}.well-known/pem/TPCP`;
|
|
31
33
|
const result = await this.fetchMethod(url);
|
|
34
|
+
if (Math.floor(result.status / 100) !== 2) {
|
|
35
|
+
throw new dataconnector_1.DataConnectorRunError(500, { text: await result.text(), message: 'Error getting public key' });
|
|
36
|
+
}
|
|
32
37
|
this.cachedPublicKey = await result.text();
|
|
33
38
|
}
|
|
34
39
|
return this.cachedPublicKey;
|
|
@@ -43,6 +48,40 @@ class DataConnectorClient {
|
|
|
43
48
|
}
|
|
44
49
|
return (0, serializedactions_1.deserializeActions)(this, actions);
|
|
45
50
|
}
|
|
51
|
+
/** @ignore */
|
|
52
|
+
async getSecretFromWebhook(webhookUpdateToken, WebhookValidationSecret) {
|
|
53
|
+
// todo: this should be using the public API
|
|
54
|
+
const result = await this.fetchMethod(`${this.urls.dataSync}/webhookAuthorization`, {
|
|
55
|
+
headers: {
|
|
56
|
+
'data-update-token': webhookUpdateToken,
|
|
57
|
+
'webhook-validation-secret': JSON.stringify(WebhookValidationSecret),
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
const responseBody = await result.json();
|
|
61
|
+
if ((0, checks_1.isString)(responseBody)) {
|
|
62
|
+
return responseBody;
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/** @ignore */
|
|
69
|
+
async getCustomWebhookData(webhookUpdateToken) {
|
|
70
|
+
return (await // todo: this should be using the public API
|
|
71
|
+
(await this.fetchMethod(`${this.urls.dataSync}customWebhookData`, {
|
|
72
|
+
headers: { 'data-update-token': webhookUpdateToken },
|
|
73
|
+
})).json());
|
|
74
|
+
}
|
|
75
|
+
/** @ignore */
|
|
76
|
+
async patchCustomWebhookData(webhookUpdateToken, patch) {
|
|
77
|
+
// todo: this should be using the public API
|
|
78
|
+
const response = await this.fetchMethod(`${this.urls.dataSync}customWebhookData`, {
|
|
79
|
+
method: 'PATCH',
|
|
80
|
+
headers: { 'data-update-token': webhookUpdateToken },
|
|
81
|
+
body: patch,
|
|
82
|
+
});
|
|
83
|
+
return { status: response.status, data: (await response.json()) };
|
|
84
|
+
}
|
|
46
85
|
}
|
|
47
86
|
exports.DataConnectorClient = DataConnectorClient;
|
|
48
87
|
/** Use this for writing automated tests for your data connector */
|
|
@@ -16,11 +16,6 @@ export declare type DataSourceRequest = {
|
|
|
16
16
|
/** Collections to add or update to the data source */
|
|
17
17
|
collections: Record<string, CollectionPatch>;
|
|
18
18
|
};
|
|
19
|
-
/** A response to a data source update. Basically just the HTTP response from the server with JSON.parse called on it */
|
|
20
|
-
export declare type DataSourceResponse = {
|
|
21
|
-
data: unknown;
|
|
22
|
-
status: number;
|
|
23
|
-
};
|
|
24
19
|
declare type SerializedFieldConstraintForApi = {
|
|
25
20
|
'type': FieldConstraintType;
|
|
26
21
|
'details'?: JsonSerializable;
|
|
@@ -73,7 +68,7 @@ export declare class DataSourceClient {
|
|
|
73
68
|
/** Create or update a datasource. If you create a new collection it must be fully specified in terms of schema and
|
|
74
69
|
* and items. If it's just an update then the schema can be omitted (if unchanged) and items that already exist can
|
|
75
70
|
* be partial.*/
|
|
76
|
-
update(request: DataSourceRequest): Promise<
|
|
71
|
+
update(request: DataSourceRequest): Promise<void>;
|
|
77
72
|
}
|
|
78
73
|
/**
|
|
79
74
|
* A version of the DataSourceClient that you can use for any automated tests of your data connector.
|
|
@@ -81,9 +76,9 @@ export declare class DataSourceClient {
|
|
|
81
76
|
**/
|
|
82
77
|
export declare class MockDataSourceClient extends DataSourceClient {
|
|
83
78
|
/** Assign this to your mocked update function */
|
|
84
|
-
gotUpdate: (request: DataSourceRequest) =>
|
|
79
|
+
gotUpdate: (request: DataSourceRequest) => void;
|
|
85
80
|
constructor();
|
|
86
81
|
/** @ignore */
|
|
87
|
-
update(request: DataSourceRequest): Promise<
|
|
82
|
+
update(request: DataSourceRequest): Promise<void>;
|
|
88
83
|
}
|
|
89
84
|
export {};
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.MockDataSourceClient = exports.DataSourceClient = exports.serializeFieldDefinitionForApi = void 0;
|
|
4
4
|
const fieldtypedefinition_1 = require("../core/data/fieldtypedefinition/fieldtypedefinition");
|
|
5
5
|
const object_1 = require("../core/object");
|
|
6
|
+
const dataconnector_1 = require("./dataconnector");
|
|
6
7
|
const defaultfetchfunction_1 = require("./defaultfetchfunction");
|
|
7
8
|
function serializeFieldConstraintForApi(constraint) {
|
|
8
9
|
return {
|
|
@@ -82,7 +83,18 @@ class DataSourceClient {
|
|
|
82
83
|
},
|
|
83
84
|
'body': JSON.stringify(this.formatBody(request)),
|
|
84
85
|
});
|
|
85
|
-
|
|
86
|
+
if (Math.floor(response.status / 100) !== 2) {
|
|
87
|
+
throw new dataconnector_1.DataConnectorRunError(500, { message: 'Error updating data source', json: await response.text() });
|
|
88
|
+
}
|
|
89
|
+
// webhooks return 200 with an empty body
|
|
90
|
+
// document updates return valid json
|
|
91
|
+
// we can't use .json because that will fail for webhooks
|
|
92
|
+
// if we update the response type for webhook updates
|
|
93
|
+
// we can handle that here too. We should probably unify the responses
|
|
94
|
+
// to be meaningful
|
|
95
|
+
// we could just:
|
|
96
|
+
// return await response.text();
|
|
97
|
+
// but lets avoid users trying to interpret the response
|
|
86
98
|
}
|
|
87
99
|
}
|
|
88
100
|
exports.DataSourceClient = DataSourceClient;
|
|
@@ -48,7 +48,7 @@ function routeDebugServer(dataConnector, options) {
|
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
catch (e) {
|
|
51
|
-
console.log(`Error serving Connector: ${prefix} Flow: ${req.query['name']}, Exception
|
|
51
|
+
console.log(`Error serving Connector: ${prefix} Flow: ${req.query['name']}, Exception:`, e);
|
|
52
52
|
res.status(500).end();
|
|
53
53
|
}
|
|
54
54
|
});
|