airbyte-faros-destination 0.1.53
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/README.md +161 -0
- package/lib/common/write-stats.d.ts +16 -0
- package/lib/common/write-stats.js +48 -0
- package/lib/community/hasura-client.d.ts +27 -0
- package/lib/community/hasura-client.js +278 -0
- package/lib/community/hasura-writer.d.ts +12 -0
- package/lib/community/hasura-writer.js +44 -0
- package/lib/community/types.d.ts +26 -0
- package/lib/community/types.js +9 -0
- package/lib/converters/agileaccelerator/agileaccelerator_types.d.ts +315 -0
- package/lib/converters/agileaccelerator/agileaccelerator_types.js +81 -0
- package/lib/converters/agileaccelerator/common.d.ts +64 -0
- package/lib/converters/agileaccelerator/common.js +167 -0
- package/lib/converters/agileaccelerator/works.d.ts +7 -0
- package/lib/converters/agileaccelerator/works.js +105 -0
- package/lib/converters/asana/common.d.ts +39 -0
- package/lib/converters/asana/common.js +64 -0
- package/lib/converters/asana/projects.d.ts +7 -0
- package/lib/converters/asana/projects.js +29 -0
- package/lib/converters/asana/sections.d.ts +7 -0
- package/lib/converters/asana/sections.js +28 -0
- package/lib/converters/asana/stories.d.ts +7 -0
- package/lib/converters/asana/stories.js +32 -0
- package/lib/converters/asana/tags.d.ts +7 -0
- package/lib/converters/asana/tags.js +22 -0
- package/lib/converters/asana/tasks.d.ts +11 -0
- package/lib/converters/asana/tasks.js +160 -0
- package/lib/converters/asana/users.d.ts +7 -0
- package/lib/converters/asana/users.js +16 -0
- package/lib/converters/backlog/common.d.ts +20 -0
- package/lib/converters/backlog/common.js +59 -0
- package/lib/converters/backlog/issues.d.ts +7 -0
- package/lib/converters/backlog/issues.js +119 -0
- package/lib/converters/backlog/models.d.ts +156 -0
- package/lib/converters/backlog/models.js +23 -0
- package/lib/converters/backlog/projects.d.ts +7 -0
- package/lib/converters/backlog/projects.js +78 -0
- package/lib/converters/backlog/users.d.ts +7 -0
- package/lib/converters/backlog/users.js +26 -0
- package/lib/converters/bitbucket/branches.d.ts +7 -0
- package/lib/converters/bitbucket/branches.js +31 -0
- package/lib/converters/bitbucket/commits.d.ts +7 -0
- package/lib/converters/bitbucket/commits.js +50 -0
- package/lib/converters/bitbucket/common.d.ts +28 -0
- package/lib/converters/bitbucket/common.js +56 -0
- package/lib/converters/bitbucket/deployments.d.ts +11 -0
- package/lib/converters/bitbucket/deployments.js +100 -0
- package/lib/converters/bitbucket/issues.d.ts +12 -0
- package/lib/converters/bitbucket/issues.js +111 -0
- package/lib/converters/bitbucket/pipeline_steps.d.ts +10 -0
- package/lib/converters/bitbucket/pipeline_steps.js +83 -0
- package/lib/converters/bitbucket/pipelines.d.ts +8 -0
- package/lib/converters/bitbucket/pipelines.js +90 -0
- package/lib/converters/bitbucket/pull_request_activities.d.ts +13 -0
- package/lib/converters/bitbucket/pull_request_activities.js +112 -0
- package/lib/converters/bitbucket/pull_requests.d.ts +10 -0
- package/lib/converters/bitbucket/pull_requests.js +95 -0
- package/lib/converters/bitbucket/repositories.d.ts +9 -0
- package/lib/converters/bitbucket/repositories.js +41 -0
- package/lib/converters/bitbucket/types.d.ts +667 -0
- package/lib/converters/bitbucket/types.js +2 -0
- package/lib/converters/bitbucket/workspace_users.d.ts +7 -0
- package/lib/converters/bitbucket/workspace_users.js +31 -0
- package/lib/converters/bitbucket/workspaces.d.ts +8 -0
- package/lib/converters/bitbucket/workspaces.js +61 -0
- package/lib/converters/converter-registry.d.ts +25 -0
- package/lib/converters/converter-registry.js +64 -0
- package/lib/converters/converter.d.ts +63 -0
- package/lib/converters/converter.js +122 -0
- package/lib/converters/datadog/common.d.ts +27 -0
- package/lib/converters/datadog/common.js +34 -0
- package/lib/converters/datadog/incidents.d.ts +9 -0
- package/lib/converters/datadog/incidents.js +128 -0
- package/lib/converters/datadog/users.d.ts +7 -0
- package/lib/converters/datadog/users.js +27 -0
- package/lib/converters/docker/common.d.ts +87 -0
- package/lib/converters/docker/common.js +76 -0
- package/lib/converters/docker/tags.d.ts +8 -0
- package/lib/converters/docker/tags.js +98 -0
- package/lib/converters/github/assignees.d.ts +7 -0
- package/lib/converters/github/assignees.js +16 -0
- package/lib/converters/github/branches.d.ts +7 -0
- package/lib/converters/github/branches.js +28 -0
- package/lib/converters/github/collaborators.d.ts +7 -0
- package/lib/converters/github/collaborators.js +19 -0
- package/lib/converters/github/commits.d.ts +7 -0
- package/lib/converters/github/commits.js +48 -0
- package/lib/converters/github/common.d.ts +37 -0
- package/lib/converters/github/common.js +130 -0
- package/lib/converters/github/issue_labels.d.ts +7 -0
- package/lib/converters/github/issue_labels.js +20 -0
- package/lib/converters/github/issue_milestones.d.ts +8 -0
- package/lib/converters/github/issue_milestones.js +42 -0
- package/lib/converters/github/issues.d.ts +9 -0
- package/lib/converters/github/issues.js +90 -0
- package/lib/converters/github/organizations.d.ts +7 -0
- package/lib/converters/github/organizations.js +37 -0
- package/lib/converters/github/projects.d.ts +7 -0
- package/lib/converters/github/projects.js +21 -0
- package/lib/converters/github/pull_request_stats.d.ts +7 -0
- package/lib/converters/github/pull_request_stats.js +43 -0
- package/lib/converters/github/pull_requests.d.ts +7 -0
- package/lib/converters/github/pull_requests.js +63 -0
- package/lib/converters/github/releases.d.ts +7 -0
- package/lib/converters/github/releases.js +51 -0
- package/lib/converters/github/repositories.d.ts +7 -0
- package/lib/converters/github/repositories.js +44 -0
- package/lib/converters/github/review_comments.d.ts +7 -0
- package/lib/converters/github/review_comments.js +39 -0
- package/lib/converters/github/reviews.d.ts +7 -0
- package/lib/converters/github/reviews.js +58 -0
- package/lib/converters/github/tags.d.ts +7 -0
- package/lib/converters/github/tags.js +31 -0
- package/lib/converters/github/users.d.ts +7 -0
- package/lib/converters/github/users.js +19 -0
- package/lib/converters/gitlab/branches.d.ts +8 -0
- package/lib/converters/gitlab/branches.js +44 -0
- package/lib/converters/gitlab/commits.d.ts +7 -0
- package/lib/converters/gitlab/commits.js +33 -0
- package/lib/converters/gitlab/common.d.ts +30 -0
- package/lib/converters/gitlab/common.js +70 -0
- package/lib/converters/gitlab/group_labels.d.ts +7 -0
- package/lib/converters/gitlab/group_labels.js +15 -0
- package/lib/converters/gitlab/group_milestones.d.ts +8 -0
- package/lib/converters/gitlab/group_milestones.js +41 -0
- package/lib/converters/gitlab/groups.d.ts +7 -0
- package/lib/converters/gitlab/groups.js +43 -0
- package/lib/converters/gitlab/issues.d.ts +10 -0
- package/lib/converters/gitlab/issues.js +79 -0
- package/lib/converters/gitlab/jobs.d.ts +11 -0
- package/lib/converters/gitlab/jobs.js +75 -0
- package/lib/converters/gitlab/merge_request_commits.d.ts +7 -0
- package/lib/converters/gitlab/merge_request_commits.js +34 -0
- package/lib/converters/gitlab/merge_requests.d.ts +10 -0
- package/lib/converters/gitlab/merge_requests.js +65 -0
- package/lib/converters/gitlab/pipelines.d.ts +7 -0
- package/lib/converters/gitlab/pipelines.js +41 -0
- package/lib/converters/gitlab/project_labels.d.ts +7 -0
- package/lib/converters/gitlab/project_labels.js +15 -0
- package/lib/converters/gitlab/project_milestones.d.ts +8 -0
- package/lib/converters/gitlab/project_milestones.js +41 -0
- package/lib/converters/gitlab/projects.d.ts +7 -0
- package/lib/converters/gitlab/projects.js +51 -0
- package/lib/converters/gitlab/releases.d.ts +10 -0
- package/lib/converters/gitlab/releases.js +68 -0
- package/lib/converters/gitlab/tags.d.ts +10 -0
- package/lib/converters/gitlab/tags.js +41 -0
- package/lib/converters/gitlab/users.d.ts +7 -0
- package/lib/converters/gitlab/users.js +33 -0
- package/lib/converters/googlecalendar/calendar_models.d.ts +618 -0
- package/lib/converters/googlecalendar/calendar_models.js +2 -0
- package/lib/converters/googlecalendar/calendars.d.ts +7 -0
- package/lib/converters/googlecalendar/calendars.js +29 -0
- package/lib/converters/googlecalendar/common.d.ts +52 -0
- package/lib/converters/googlecalendar/common.js +130 -0
- package/lib/converters/googlecalendar/events.d.ts +8 -0
- package/lib/converters/googlecalendar/events.js +117 -0
- package/lib/converters/harness/common.d.ts +105 -0
- package/lib/converters/harness/common.js +16 -0
- package/lib/converters/harness/executions.d.ts +15 -0
- package/lib/converters/harness/executions.js +245 -0
- package/lib/converters/jenkins/builds.d.ts +9 -0
- package/lib/converters/jenkins/builds.js +74 -0
- package/lib/converters/jenkins/common.d.ts +25 -0
- package/lib/converters/jenkins/common.js +54 -0
- package/lib/converters/jenkins/jobs.d.ts +8 -0
- package/lib/converters/jenkins/jobs.js +29 -0
- package/lib/converters/jira/board_issues.d.ts +7 -0
- package/lib/converters/jira/board_issues.js +28 -0
- package/lib/converters/jira/boards.d.ts +7 -0
- package/lib/converters/jira/boards.js +34 -0
- package/lib/converters/jira/common.d.ts +61 -0
- package/lib/converters/jira/common.js +64 -0
- package/lib/converters/jira/epics.d.ts +8 -0
- package/lib/converters/jira/epics.js +45 -0
- package/lib/converters/jira/issue_fields.d.ts +7 -0
- package/lib/converters/jira/issue_fields.js +15 -0
- package/lib/converters/jira/issues.d.ts +52 -0
- package/lib/converters/jira/issues.js +545 -0
- package/lib/converters/jira/project_versions.d.ts +9 -0
- package/lib/converters/jira/project_versions.js +48 -0
- package/lib/converters/jira/projects.d.ts +7 -0
- package/lib/converters/jira/projects.js +40 -0
- package/lib/converters/jira/pull_requests.d.ts +7 -0
- package/lib/converters/jira/pull_requests.js +15 -0
- package/lib/converters/jira/sprint_issues.d.ts +7 -0
- package/lib/converters/jira/sprint_issues.js +15 -0
- package/lib/converters/jira/sprints.d.ts +16 -0
- package/lib/converters/jira/sprints.js +80 -0
- package/lib/converters/jira/users.d.ts +8 -0
- package/lib/converters/jira/users.js +33 -0
- package/lib/converters/jira/workflow_statuses.d.ts +7 -0
- package/lib/converters/jira/workflow_statuses.js +15 -0
- package/lib/converters/jsonata.d.ts +17 -0
- package/lib/converters/jsonata.js +47 -0
- package/lib/converters/okta/common.d.ts +8 -0
- package/lib/converters/okta/common.js +17 -0
- package/lib/converters/okta/groups.d.ts +7 -0
- package/lib/converters/okta/groups.js +47 -0
- package/lib/converters/okta/models.d.ts +102 -0
- package/lib/converters/okta/models.js +2 -0
- package/lib/converters/okta/users.d.ts +8 -0
- package/lib/converters/okta/users.js +73 -0
- package/lib/converters/okta_faros/common.d.ts +4 -0
- package/lib/converters/okta_faros/common.js +11 -0
- package/lib/converters/okta_faros/groups.d.ts +13 -0
- package/lib/converters/okta_faros/groups.js +23 -0
- package/lib/converters/okta_faros/users.d.ts +13 -0
- package/lib/converters/okta_faros/users.js +23 -0
- package/lib/converters/pagerduty/common.d.ts +37 -0
- package/lib/converters/pagerduty/common.js +40 -0
- package/lib/converters/pagerduty/incident_log_entries.d.ts +8 -0
- package/lib/converters/pagerduty/incident_log_entries.js +56 -0
- package/lib/converters/pagerduty/incidents.d.ts +10 -0
- package/lib/converters/pagerduty/incidents.js +127 -0
- package/lib/converters/pagerduty/priorities_resource.d.ts +11 -0
- package/lib/converters/pagerduty/priorities_resource.js +64 -0
- package/lib/converters/pagerduty/users.d.ts +7 -0
- package/lib/converters/pagerduty/users.js +26 -0
- package/lib/converters/phabricator/commits.d.ts +8 -0
- package/lib/converters/phabricator/commits.js +75 -0
- package/lib/converters/phabricator/common.d.ts +40 -0
- package/lib/converters/phabricator/common.js +107 -0
- package/lib/converters/phabricator/repositories.d.ts +7 -0
- package/lib/converters/phabricator/repositories.js +56 -0
- package/lib/converters/phabricator/revisions.d.ts +7 -0
- package/lib/converters/phabricator/revisions.js +83 -0
- package/lib/converters/phabricator/users.d.ts +7 -0
- package/lib/converters/phabricator/users.js +39 -0
- package/lib/converters/shortcut/common.d.ts +16 -0
- package/lib/converters/shortcut/common.js +68 -0
- package/lib/converters/shortcut/epics.d.ts +7 -0
- package/lib/converters/shortcut/epics.js +29 -0
- package/lib/converters/shortcut/iterations.d.ts +7 -0
- package/lib/converters/shortcut/iterations.js +29 -0
- package/lib/converters/shortcut/members.d.ts +7 -0
- package/lib/converters/shortcut/members.js +26 -0
- package/lib/converters/shortcut/models.d.ts +392 -0
- package/lib/converters/shortcut/models.js +30 -0
- package/lib/converters/shortcut/projects.d.ts +7 -0
- package/lib/converters/shortcut/projects.js +46 -0
- package/lib/converters/shortcut/stories.d.ts +7 -0
- package/lib/converters/shortcut/stories.js +127 -0
- package/lib/converters/squadcast/common.d.ts +169 -0
- package/lib/converters/squadcast/common.js +62 -0
- package/lib/converters/squadcast/events.d.ts +9 -0
- package/lib/converters/squadcast/events.js +53 -0
- package/lib/converters/squadcast/incidents.d.ts +10 -0
- package/lib/converters/squadcast/incidents.js +141 -0
- package/lib/converters/squadcast/services.d.ts +7 -0
- package/lib/converters/squadcast/services.js +28 -0
- package/lib/converters/squadcast/users.d.ts +7 -0
- package/lib/converters/squadcast/users.js +27 -0
- package/lib/converters/statuspage/common.d.ts +93 -0
- package/lib/converters/statuspage/common.js +80 -0
- package/lib/converters/statuspage/incident_updates.d.ts +8 -0
- package/lib/converters/statuspage/incident_updates.js +45 -0
- package/lib/converters/statuspage/incidents.d.ts +10 -0
- package/lib/converters/statuspage/incidents.js +128 -0
- package/lib/converters/statuspage/users.d.ts +7 -0
- package/lib/converters/statuspage/users.js +26 -0
- package/lib/converters/victorops/common.d.ts +16 -0
- package/lib/converters/victorops/common.js +24 -0
- package/lib/converters/victorops/incidents.d.ts +12 -0
- package/lib/converters/victorops/incidents.js +209 -0
- package/lib/converters/victorops/teams.d.ts +8 -0
- package/lib/converters/victorops/teams.js +30 -0
- package/lib/converters/victorops/users.d.ts +8 -0
- package/lib/converters/victorops/users.js +33 -0
- package/lib/destination-runner.d.ts +12 -0
- package/lib/destination-runner.js +22 -0
- package/lib/destination.d.ts +42 -0
- package/lib/destination.js +508 -0
- package/lib/index.d.ts +16 -0
- package/lib/index.js +39 -0
- package/lib/tsconfig.tsbuildinfo +1 -0
- package/package.json +71 -0
- package/resources/fetch-primary-keys.sql +16 -0
- package/resources/introspection-query.gql +61 -0
- package/resources/spec.json +468 -0
package/README.md
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# Faros Destination for Airbyte
|
|
2
|
+
|
|
3
|
+
Faros Destination is a universal destination to import data from any Airbyte source into Faros.
|
|
4
|
+
|
|
5
|
+
Each source stream is handled by an appropriate [Converter](src/converters/converter.ts) implementation. Here are all the [supported sources](src/converters).
|
|
6
|
+
|
|
7
|
+
Any additional source streams can be handled by providing a JSONata expression for the built-in [JSONataConverter](src/converters/jsonata.ts) or by implementing converters for the streams ([read more below](#adding-support-for-additional-sources)).
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
### Run from Airbyte
|
|
12
|
+
|
|
13
|
+
1. Open Airbyte UI
|
|
14
|
+
2. Go to Settings -> Destinations -> New Connector
|
|
15
|
+
3. Set image - `farosai/airbyte-faros-destination`
|
|
16
|
+
4. Set documentation - `https://github.com/faros-ai/airbyte-connectors`
|
|
17
|
+
5. Set the desired version `x.y.z`, e.g the latest [](https://hub.docker.com/r/farosai/airbyte-faros-destination/tags)
|
|
18
|
+
6. Click Add
|
|
19
|
+
|
|
20
|
+
More details on how to add a custom connector can be found in the official Airbyte [guide](https://docs.airbyte.com/integrations/custom-connectors).
|
|
21
|
+
|
|
22
|
+
### Run from Terminal
|
|
23
|
+
|
|
24
|
+
```shell
|
|
25
|
+
docker pull farosai/airbyte-faros-destination
|
|
26
|
+
docker run farosai/airbyte-faros-destination
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Adding Support for Additional Sources
|
|
30
|
+
|
|
31
|
+
Faros Destination is built to be easily extensible to support additional sources by adding stream converters.
|
|
32
|
+
|
|
33
|
+
### Well-Known Sources
|
|
34
|
+
|
|
35
|
+
For well-known sources you can simply open a PR against this repository and add any new stream converters.
|
|
36
|
+
We are always happy for you to contribute them to the community.
|
|
37
|
+
|
|
38
|
+
When developing a converter, there is a specific naming convention that must be followed to ensure that the converter is correctly picked up by the Faros Destination. The converter class name must be the PascalCase (or UpperCamelCase) version of the stream name, e.g. [PullRequestStats](src/converters/github/pull_request_stats.ts) for GitHub.
|
|
39
|
+
|
|
40
|
+
For testing, create the `all-streams.log` file and make sure that no real data is included. Also, ensure that the stream names are prefixed with the stream prefix specified in the test file. For example: `mytestsource__github__`.
|
|
41
|
+
|
|
42
|
+
We have implemented [a lot of converters](src/converters) already so you have plenty examples to follow.
|
|
43
|
+
|
|
44
|
+
### Custom Sources
|
|
45
|
+
|
|
46
|
+
For custom or internal sources you can use Faros Destination as npm package in your project without having to fork or open PRs against this repo:
|
|
47
|
+
```
|
|
48
|
+
npm i airbyte-faros-destination
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The library currently provides a main class `FarosDestinationRunner` which allows you to register the custom converters you created. Additionally, use the program property to get a default CLI app that provides the basic commands needed to write records to Faros.
|
|
52
|
+
|
|
53
|
+
Example `index.ts`:
|
|
54
|
+
```typescript
|
|
55
|
+
import {Command} from 'commander';
|
|
56
|
+
import {AirbyteRecord} from 'faros-airbyte-cdk';
|
|
57
|
+
import {Converter, DestinationModel, DestinationRecord,FarosDestinationRunner,StreamContext} from 'airbyte-faros-destination'
|
|
58
|
+
|
|
59
|
+
class Builds extends Converter {
|
|
60
|
+
source: string = 'CustomSource'
|
|
61
|
+
destinationModels: ReadonlyArray<DestinationModel> = ['cicd_Build'];
|
|
62
|
+
|
|
63
|
+
id(record: AirbyteRecord): string {
|
|
64
|
+
return record.record.data.id;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async convert(
|
|
68
|
+
record: AirbyteRecord,
|
|
69
|
+
_ctx: StreamContext
|
|
70
|
+
): Promise<ReadonlyArray<DestinationRecord>> {
|
|
71
|
+
const build = record.record.data
|
|
72
|
+
return [
|
|
73
|
+
{
|
|
74
|
+
model: 'cicd_Build',
|
|
75
|
+
record: {
|
|
76
|
+
uid: String(build.bid),
|
|
77
|
+
startedAt: new Date(build.created_at),
|
|
78
|
+
endedAt: new Date(build.finished_at),
|
|
79
|
+
status: build.status,
|
|
80
|
+
source: this.source
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
];
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
class Pipelines extends Converter {
|
|
87
|
+
// similar to the Builds in the example above
|
|
88
|
+
...
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// main entry point
|
|
92
|
+
export function mainCommand(): Command {
|
|
93
|
+
const destinationRunner = new FarosDestinationRunner();
|
|
94
|
+
|
|
95
|
+
// Register your custom converter(s)
|
|
96
|
+
destinationRunner.registerConverters(
|
|
97
|
+
new Builds(),
|
|
98
|
+
new Pipelines()
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
return destinationRunner.program;
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Example shell script to run file `bin/main`:
|
|
106
|
+
```shell
|
|
107
|
+
#!/usr/bin/env node
|
|
108
|
+
|
|
109
|
+
const {mainCommand} = require('../lib');
|
|
110
|
+
|
|
111
|
+
mainCommand().parseAsync(process.argv).catch((err) => {
|
|
112
|
+
console.error(err.message);
|
|
113
|
+
process.exit(1);
|
|
114
|
+
});
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Writing Records into Faros
|
|
118
|
+
```shell
|
|
119
|
+
./bin/main write --config config.json --catalog catalog.json
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
You will need to provide two parameters, a `config.json` which specifies the
|
|
123
|
+
details of the Faros destination to write to, and `catalog.json` to
|
|
124
|
+
document source streams to write records for.
|
|
125
|
+
|
|
126
|
+
Example `config.json`
|
|
127
|
+
```json
|
|
128
|
+
{
|
|
129
|
+
"edition_configs": {
|
|
130
|
+
"edition": "cloud",
|
|
131
|
+
"api_url": "https://prod.api.faros.ai",
|
|
132
|
+
"api_key": "<my_faros_api_key>",
|
|
133
|
+
"graph": "default"
|
|
134
|
+
},
|
|
135
|
+
"origin": "mydatasource"
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
See [spec.json](resources/spec.json) for more properties for the `config.json`
|
|
139
|
+
|
|
140
|
+
Example `catalog.json`
|
|
141
|
+
```json
|
|
142
|
+
{
|
|
143
|
+
"streams": [
|
|
144
|
+
{
|
|
145
|
+
"stream": {
|
|
146
|
+
"name": "mydatasource__CustomSource__builds"
|
|
147
|
+
},
|
|
148
|
+
"destination_sync_mode": "append"
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
"stream": {
|
|
152
|
+
"name": "mydatasource__CustomSource__pipelines"
|
|
153
|
+
},
|
|
154
|
+
"destination_sync_mode": "append"
|
|
155
|
+
}
|
|
156
|
+
]
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Additional Commands
|
|
161
|
+
Run `./bin/main --help` for detailed information on available commands.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { AirbyteLogger } from 'faros-airbyte-cdk';
|
|
2
|
+
import { Dictionary } from 'ts-essentials';
|
|
3
|
+
export declare class WriteStats {
|
|
4
|
+
messagesRead: number;
|
|
5
|
+
recordsRead: number;
|
|
6
|
+
recordsProcessed: number;
|
|
7
|
+
recordsWritten: number;
|
|
8
|
+
recordsErrored: number;
|
|
9
|
+
recordsSkipped: number;
|
|
10
|
+
processedByStream: Dictionary<number>;
|
|
11
|
+
writtenByModel: Dictionary<number>;
|
|
12
|
+
constructor(messagesRead?: number, recordsRead?: number, recordsProcessed?: number, recordsWritten?: number, recordsErrored?: number, recordsSkipped?: number, processedByStream?: Dictionary<number>, writtenByModel?: Dictionary<number>);
|
|
13
|
+
incrementProcessedByStream(stream: string): void;
|
|
14
|
+
incrementWrittenByModel(model: string): void;
|
|
15
|
+
log(logger: AirbyteLogger, writeMsg: 'Wrote' | 'Would write'): void;
|
|
16
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.WriteStats = void 0;
|
|
7
|
+
const lodash_1 = __importDefault(require("lodash"));
|
|
8
|
+
class WriteStats {
|
|
9
|
+
constructor(messagesRead = 0, recordsRead = 0, recordsProcessed = 0, recordsWritten = 0, recordsErrored = 0, recordsSkipped = 0, processedByStream = {}, writtenByModel = {}) {
|
|
10
|
+
this.messagesRead = messagesRead;
|
|
11
|
+
this.recordsRead = recordsRead;
|
|
12
|
+
this.recordsProcessed = recordsProcessed;
|
|
13
|
+
this.recordsWritten = recordsWritten;
|
|
14
|
+
this.recordsErrored = recordsErrored;
|
|
15
|
+
this.recordsSkipped = recordsSkipped;
|
|
16
|
+
this.processedByStream = processedByStream;
|
|
17
|
+
this.writtenByModel = writtenByModel;
|
|
18
|
+
}
|
|
19
|
+
incrementProcessedByStream(stream) {
|
|
20
|
+
const count = this.processedByStream[stream];
|
|
21
|
+
this.processedByStream[stream] = count ? count + 1 : 1;
|
|
22
|
+
}
|
|
23
|
+
incrementWrittenByModel(model) {
|
|
24
|
+
const count = this.writtenByModel[model];
|
|
25
|
+
this.writtenByModel[model] = count ? count + 1 : 1;
|
|
26
|
+
}
|
|
27
|
+
log(logger, writeMsg) {
|
|
28
|
+
logger.info(`Read ${this.messagesRead} messages`);
|
|
29
|
+
logger.info(`Read ${this.recordsRead} records`);
|
|
30
|
+
logger.info(`Processed ${this.recordsProcessed} records`);
|
|
31
|
+
const processed = (0, lodash_1.default)(this.processedByStream)
|
|
32
|
+
.toPairs()
|
|
33
|
+
.orderBy(0, 'asc')
|
|
34
|
+
.fromPairs()
|
|
35
|
+
.value();
|
|
36
|
+
logger.info(`Processed records by stream: ${JSON.stringify(processed)}`);
|
|
37
|
+
logger.info(`${writeMsg} ${this.recordsWritten} records`);
|
|
38
|
+
const written = (0, lodash_1.default)(this.writtenByModel)
|
|
39
|
+
.toPairs()
|
|
40
|
+
.orderBy(0, 'asc')
|
|
41
|
+
.fromPairs()
|
|
42
|
+
.value();
|
|
43
|
+
logger.info(`${writeMsg} records by model: ${JSON.stringify(written)}`);
|
|
44
|
+
logger.info(`Skipped ${this.recordsSkipped} records`);
|
|
45
|
+
logger.info(`Errored ${this.recordsErrored} records`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
exports.WriteStats = WriteStats;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Dictionary } from 'ts-essentials';
|
|
2
|
+
import { TimestampedRecord } from './types';
|
|
3
|
+
export declare class HasuraClient {
|
|
4
|
+
private readonly api;
|
|
5
|
+
private readonly logger;
|
|
6
|
+
private readonly primaryKeys;
|
|
7
|
+
private readonly scalars;
|
|
8
|
+
private readonly references;
|
|
9
|
+
private readonly backReferences;
|
|
10
|
+
private sortedModelDependencies;
|
|
11
|
+
constructor(url: string);
|
|
12
|
+
healthCheck(): Promise<void>;
|
|
13
|
+
private fetchDbSource;
|
|
14
|
+
loadSchema(): Promise<void>;
|
|
15
|
+
private fetchPrimaryKeys;
|
|
16
|
+
private backReferenceOriginCheck;
|
|
17
|
+
resetData(origin: string, models: ReadonlyArray<string>): Promise<void>;
|
|
18
|
+
writeRecord(model: string, record: Dictionary<any>, origin: string): Promise<void>;
|
|
19
|
+
writeTimestampedRecord(record: TimestampedRecord): Promise<void>;
|
|
20
|
+
private writeUpdateRecord;
|
|
21
|
+
private writeDeletionRecord;
|
|
22
|
+
private postQuery;
|
|
23
|
+
private createWhereClause;
|
|
24
|
+
private createMutationObject;
|
|
25
|
+
private formatFieldValue;
|
|
26
|
+
private createConflictClause;
|
|
27
|
+
}
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.HasuraClient = void 0;
|
|
7
|
+
const axios_1 = __importDefault(require("axios"));
|
|
8
|
+
const date_format_1 = __importDefault(require("date-format"));
|
|
9
|
+
const faros_airbyte_cdk_1 = require("faros-airbyte-cdk");
|
|
10
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
11
|
+
const json_to_graphql_query_1 = require("json-to-graphql-query");
|
|
12
|
+
const lodash_1 = require("lodash");
|
|
13
|
+
const path_1 = __importDefault(require("path"));
|
|
14
|
+
const toposort_1 = __importDefault(require("toposort"));
|
|
15
|
+
const traverse_1 = __importDefault(require("traverse"));
|
|
16
|
+
const verror_1 = require("verror");
|
|
17
|
+
const types_1 = require("./types");
|
|
18
|
+
class HasuraClient {
|
|
19
|
+
constructor(url) {
|
|
20
|
+
this.logger = new faros_airbyte_cdk_1.AirbyteLogger();
|
|
21
|
+
this.primaryKeys = {};
|
|
22
|
+
this.scalars = {};
|
|
23
|
+
this.references = {};
|
|
24
|
+
this.backReferences = {};
|
|
25
|
+
this.api = axios_1.default.create({
|
|
26
|
+
baseURL: url,
|
|
27
|
+
headers: { 'X-Hasura-Role': 'admin' },
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
async healthCheck() {
|
|
31
|
+
try {
|
|
32
|
+
await this.api.get('/healthz');
|
|
33
|
+
}
|
|
34
|
+
catch (e) {
|
|
35
|
+
throw new verror_1.VError(e, 'Failed to check Hasura health');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async fetchDbSource() {
|
|
39
|
+
const response = await this.api.post('/v1/metadata', {
|
|
40
|
+
type: 'export_metadata',
|
|
41
|
+
version: 2,
|
|
42
|
+
args: {},
|
|
43
|
+
});
|
|
44
|
+
const sources = response.data.metadata.sources;
|
|
45
|
+
const defaultSource = (0, lodash_1.find)(sources, (source) => source.name === 'default');
|
|
46
|
+
if (!defaultSource) {
|
|
47
|
+
throw new verror_1.VError('Faros database not connected to Hasura');
|
|
48
|
+
}
|
|
49
|
+
return defaultSource;
|
|
50
|
+
}
|
|
51
|
+
async loadSchema() {
|
|
52
|
+
var _a, _b, _c, _d;
|
|
53
|
+
await this.fetchPrimaryKeys();
|
|
54
|
+
const source = await this.fetchDbSource();
|
|
55
|
+
const query = await fs_extra_1.default.readFile(path_1.default.join(__dirname, '../../resources/introspection-query.gql'), 'utf8');
|
|
56
|
+
const response = await this.api.post('/v1/graphql', { query });
|
|
57
|
+
const schema = response.data.data.__schema;
|
|
58
|
+
for (const table of source.tables) {
|
|
59
|
+
const tableName = table.table.name;
|
|
60
|
+
const type = (0, lodash_1.find)(schema.types, (t) => t.name === tableName && t.kind === 'OBJECT');
|
|
61
|
+
if (!type)
|
|
62
|
+
continue;
|
|
63
|
+
const scalarTypes = type.fields.filter((t) => (t.type.kind === 'SCALAR' ||
|
|
64
|
+
(t.type.kind === 'NON_NULL' && t.type.ofType.kind === 'SCALAR')) &&
|
|
65
|
+
t.description !== 'generated');
|
|
66
|
+
const scalars = {};
|
|
67
|
+
for (const scalar of scalarTypes) {
|
|
68
|
+
scalars[scalar.name] = (_b = (_a = scalar.type.ofType) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : scalar.type.name;
|
|
69
|
+
}
|
|
70
|
+
this.scalars[tableName] = scalars;
|
|
71
|
+
const references = {};
|
|
72
|
+
for (const rel of (_c = table.object_relationships) !== null && _c !== void 0 ? _c : []) {
|
|
73
|
+
const [refType] = rel.name.split('__');
|
|
74
|
+
references[rel.using.foreign_key_constraint_on] = {
|
|
75
|
+
field: rel.name,
|
|
76
|
+
model: refType,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
this.references[tableName] = references;
|
|
80
|
+
this.backReferences[tableName] = ((_d = table.array_relationships) !== null && _d !== void 0 ? _d : []).map((rel) => {
|
|
81
|
+
return {
|
|
82
|
+
field: rel.name,
|
|
83
|
+
model: rel.using.foreign_key_constraint_on.table.name,
|
|
84
|
+
};
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
const modelDeps = [];
|
|
88
|
+
for (const model of Object.keys(this.references)) {
|
|
89
|
+
for (const ref of Object.values(this.references[model])) {
|
|
90
|
+
if (model !== ref.model) {
|
|
91
|
+
modelDeps.push([model, ref.model]);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
this.sortedModelDependencies = (0, toposort_1.default)(modelDeps);
|
|
96
|
+
}
|
|
97
|
+
async fetchPrimaryKeys() {
|
|
98
|
+
const response = await this.api.post('/v2/query', {
|
|
99
|
+
type: 'run_sql',
|
|
100
|
+
args: {
|
|
101
|
+
source: 'default',
|
|
102
|
+
sql: await fs_extra_1.default.readFile(path_1.default.join(__dirname, '../../resources/fetch-primary-keys.sql'), 'utf8'),
|
|
103
|
+
cascade: false,
|
|
104
|
+
read_only: true,
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
const result = response.data.result;
|
|
108
|
+
result
|
|
109
|
+
.filter((row) => row[0] !== 'table_name')
|
|
110
|
+
.forEach(([table, exp]) => {
|
|
111
|
+
// TODO: better way to do this?
|
|
112
|
+
const columns = exp
|
|
113
|
+
.replace('pkey(VARIADIC ARRAY[', '')
|
|
114
|
+
.replace('])', '')
|
|
115
|
+
.split(', ')
|
|
116
|
+
.map((col) => col.replace(/"/g, ''));
|
|
117
|
+
this.primaryKeys[table] = columns;
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
backReferenceOriginCheck(br, origin) {
|
|
121
|
+
const base = { origin: { _neq: origin } };
|
|
122
|
+
const nestedChecks = this.backReferences[br.model]
|
|
123
|
+
.filter((nbr) => nbr.field != br.field)
|
|
124
|
+
.map((nbr) => this.backReferenceOriginCheck(nbr, origin));
|
|
125
|
+
return {
|
|
126
|
+
[br.field]: {
|
|
127
|
+
_or: [base].concat(nestedChecks),
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
async resetData(origin, models) {
|
|
132
|
+
for (const model of (0, lodash_1.intersection)(this.sortedModelDependencies, models)) {
|
|
133
|
+
this.logger.info(`Resetting ${model} data for origin ${origin}`);
|
|
134
|
+
const deleteConditions = {
|
|
135
|
+
origin: { _eq: origin },
|
|
136
|
+
_not: {
|
|
137
|
+
_or: this.backReferences[model].map((br) => this.backReferenceOriginCheck(br, origin)),
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
const mutation = {
|
|
141
|
+
[`delete_${model}`]: {
|
|
142
|
+
__args: {
|
|
143
|
+
where: {
|
|
144
|
+
_and: { ...deleteConditions },
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
affected_rows: true,
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
await this.postQuery({ mutation }, `Failed to reset ${model} data for origin ${origin}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// TODO: find alternate batching strategy
|
|
154
|
+
// cannot use Hasura batching due to https://github.com/hasura/graphql-engine/issues/4633
|
|
155
|
+
async writeRecord(model, record, origin) {
|
|
156
|
+
const obj = this.createMutationObject(model, origin, record);
|
|
157
|
+
const mutation = {
|
|
158
|
+
[`insert_${model}_one`]: { __args: obj, id: true },
|
|
159
|
+
};
|
|
160
|
+
await this.postQuery({ mutation }, `Failed to write ${model} record`);
|
|
161
|
+
}
|
|
162
|
+
async writeTimestampedRecord(record) {
|
|
163
|
+
switch (record.operation) {
|
|
164
|
+
case types_1.Operation.UPSERT:
|
|
165
|
+
await this.writeRecord(record.model, record.data, record.origin);
|
|
166
|
+
break;
|
|
167
|
+
case types_1.Operation.UPDATE:
|
|
168
|
+
await this.writeUpdateRecord(record);
|
|
169
|
+
break;
|
|
170
|
+
case types_1.Operation.DELETION:
|
|
171
|
+
await this.writeDeletionRecord(record);
|
|
172
|
+
break;
|
|
173
|
+
default:
|
|
174
|
+
throw new verror_1.VError(`Unuspported operation ${record.operation} for ${record}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
async writeUpdateRecord(record) {
|
|
178
|
+
const mutation = {
|
|
179
|
+
[`update_${record.model}`]: {
|
|
180
|
+
__args: {
|
|
181
|
+
where: this.createWhereClause(record.model, record.where),
|
|
182
|
+
_set: this.createMutationObject(record.model, record.origin, record.patch).object,
|
|
183
|
+
},
|
|
184
|
+
returning: {
|
|
185
|
+
id: true,
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
await this.postQuery({ mutation }, `Failed to update ${record.model} record`);
|
|
190
|
+
}
|
|
191
|
+
async writeDeletionRecord(record) {
|
|
192
|
+
const mutation = {
|
|
193
|
+
[`delete_${record.model}`]: {
|
|
194
|
+
__args: {
|
|
195
|
+
where: this.createWhereClause(record.model, record.where),
|
|
196
|
+
},
|
|
197
|
+
affected_rows: true,
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
await this.postQuery({ mutation }, `Failed to delete ${record.model} record`);
|
|
201
|
+
}
|
|
202
|
+
async postQuery(query, errorMsg) {
|
|
203
|
+
const response = await this.api.post('/v1/graphql', {
|
|
204
|
+
query: (0, json_to_graphql_query_1.jsonToGraphQLQuery)(query),
|
|
205
|
+
});
|
|
206
|
+
if (response.data.errors) {
|
|
207
|
+
throw new verror_1.VError(`${errorMsg}: ${JSON.stringify(response.data.errors)}`);
|
|
208
|
+
}
|
|
209
|
+
return response.data;
|
|
210
|
+
}
|
|
211
|
+
createWhereClause(model, record) {
|
|
212
|
+
const obj = {};
|
|
213
|
+
for (const [field, value] of Object.entries(record)) {
|
|
214
|
+
const nestedModel = this.references[model][field];
|
|
215
|
+
if (nestedModel && value) {
|
|
216
|
+
obj[nestedModel.field] = this.createWhereClause(nestedModel.model, value);
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
const val = this.formatFieldValue(model, field, value);
|
|
220
|
+
if (val)
|
|
221
|
+
obj[field] = { _eq: val };
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return obj;
|
|
225
|
+
}
|
|
226
|
+
createMutationObject(model, origin, record, nested) {
|
|
227
|
+
const obj = {};
|
|
228
|
+
for (const [field, value] of Object.entries(record)) {
|
|
229
|
+
const nestedModel = this.references[model][field];
|
|
230
|
+
if (nestedModel && value) {
|
|
231
|
+
obj[nestedModel.field] = this.createMutationObject(nestedModel.model, origin, value, true);
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
const val = this.formatFieldValue(model, field, value);
|
|
235
|
+
if (val)
|
|
236
|
+
obj[field] = val;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
obj['origin'] = origin;
|
|
240
|
+
return {
|
|
241
|
+
[nested ? 'data' : 'object']: obj,
|
|
242
|
+
on_conflict: this.createConflictClause(model, nested),
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
formatFieldValue(model, field, value) {
|
|
246
|
+
if (!value)
|
|
247
|
+
return undefined;
|
|
248
|
+
const type = this.scalars[model][field];
|
|
249
|
+
if (!type) {
|
|
250
|
+
this.logger.debug(`Could not find type of ${field} in ${model}`);
|
|
251
|
+
return undefined;
|
|
252
|
+
}
|
|
253
|
+
if (type === 'timestamptz') {
|
|
254
|
+
return timestamptz(value);
|
|
255
|
+
}
|
|
256
|
+
else if (typeof value === 'object' || Array.isArray(value)) {
|
|
257
|
+
return (0, traverse_1.default)(value).map(function (val) {
|
|
258
|
+
if (val instanceof Date) {
|
|
259
|
+
this.update(val.getTime());
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
return value;
|
|
264
|
+
}
|
|
265
|
+
createConflictClause(model, nested) {
|
|
266
|
+
const updateColumns = nested
|
|
267
|
+
? ['refreshedAt']
|
|
268
|
+
: (0, lodash_1.difference)(Object.keys(this.scalars[model]), this.primaryKeys[model]);
|
|
269
|
+
return {
|
|
270
|
+
constraint: new json_to_graphql_query_1.EnumType(`${model}_pkey`),
|
|
271
|
+
update_columns: updateColumns.map((c) => new json_to_graphql_query_1.EnumType(c)),
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
exports.HasuraClient = HasuraClient;
|
|
276
|
+
function timestamptz(date) {
|
|
277
|
+
return date_format_1.default.asString(date_format_1.default.ISO8601_WITH_TZ_OFFSET_FORMAT, date);
|
|
278
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { WriteStats } from '../common/write-stats';
|
|
2
|
+
import { HasuraClient } from './hasura-client';
|
|
3
|
+
export declare class HasuraWriter {
|
|
4
|
+
private readonly hasuraClient;
|
|
5
|
+
private readonly origin;
|
|
6
|
+
private readonly stats;
|
|
7
|
+
private readonly handleRecordProcessingError;
|
|
8
|
+
private readonly timestampedRecords;
|
|
9
|
+
constructor(hasuraClient: HasuraClient, origin: string, stats: WriteStats, handleRecordProcessingError: (stats: WriteStats, processRecord: () => Promise<void>) => Promise<void>);
|
|
10
|
+
write(result: any): Promise<boolean>;
|
|
11
|
+
end(): Promise<void>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HasuraWriter = void 0;
|
|
4
|
+
const lodash_1 = require("lodash");
|
|
5
|
+
const verror_1 = require("verror");
|
|
6
|
+
const types_1 = require("./types");
|
|
7
|
+
class HasuraWriter {
|
|
8
|
+
constructor(hasuraClient, origin, stats, handleRecordProcessingError) {
|
|
9
|
+
this.hasuraClient = hasuraClient;
|
|
10
|
+
this.origin = origin;
|
|
11
|
+
this.stats = stats;
|
|
12
|
+
this.handleRecordProcessingError = handleRecordProcessingError;
|
|
13
|
+
this.timestampedRecords = [];
|
|
14
|
+
}
|
|
15
|
+
async write(result) {
|
|
16
|
+
const [baseModel, operation] = result.model.split('__', 2);
|
|
17
|
+
if (!operation) {
|
|
18
|
+
await this.hasuraClient.writeRecord(result.model, result.record, this.origin);
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
else if (Object.values(types_1.Operation).includes(operation)) {
|
|
22
|
+
this.timestampedRecords.push({
|
|
23
|
+
model: baseModel,
|
|
24
|
+
operation,
|
|
25
|
+
origin: this.origin,
|
|
26
|
+
...result.record,
|
|
27
|
+
});
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
throw new verror_1.VError(`Unuspported model operation ${operation} for ${result.model}: ${result.record}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
async end() {
|
|
35
|
+
for (const record of (0, lodash_1.sortBy)(this.timestampedRecords, (r) => r.at)) {
|
|
36
|
+
await this.handleRecordProcessingError(this.stats, async () => {
|
|
37
|
+
await this.hasuraClient.writeTimestampedRecord(record);
|
|
38
|
+
this.stats.recordsWritten++;
|
|
39
|
+
this.stats.incrementWrittenByModel(`${record.model}__${record.operation}`);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
exports.HasuraWriter = HasuraWriter;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Dictionary } from 'ts-essentials';
|
|
2
|
+
export declare enum Operation {
|
|
3
|
+
UPSERT = "Upsert",
|
|
4
|
+
UPDATE = "Update",
|
|
5
|
+
DELETION = "Deletion"
|
|
6
|
+
}
|
|
7
|
+
export interface TimestampedRecord {
|
|
8
|
+
model: string;
|
|
9
|
+
origin: string;
|
|
10
|
+
at: number;
|
|
11
|
+
operation: Operation;
|
|
12
|
+
}
|
|
13
|
+
export interface UpsertRecord extends TimestampedRecord {
|
|
14
|
+
operation: Operation.UPSERT;
|
|
15
|
+
data: Dictionary<any>;
|
|
16
|
+
}
|
|
17
|
+
export interface UpdateRecord extends TimestampedRecord {
|
|
18
|
+
operation: Operation.UPDATE;
|
|
19
|
+
where: Dictionary<any>;
|
|
20
|
+
mask: string[];
|
|
21
|
+
patch: Dictionary<any>;
|
|
22
|
+
}
|
|
23
|
+
export interface DeletionRecord extends TimestampedRecord {
|
|
24
|
+
operation: Operation.DELETION;
|
|
25
|
+
where: Dictionary<any>;
|
|
26
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Operation = void 0;
|
|
4
|
+
var Operation;
|
|
5
|
+
(function (Operation) {
|
|
6
|
+
Operation["UPSERT"] = "Upsert";
|
|
7
|
+
Operation["UPDATE"] = "Update";
|
|
8
|
+
Operation["DELETION"] = "Deletion";
|
|
9
|
+
})(Operation = exports.Operation || (exports.Operation = {}));
|