@pepys/source-github 0.0.0-stage → 0.1.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.
Files changed (51) hide show
  1. package/README.md +32 -2
  2. package/package.json +33 -4
  3. package/src/api/serve-api.ts +8 -0
  4. package/src/consumers/github-deploy.ts +199 -0
  5. package/src/consumers/github-pull-request.ts +275 -0
  6. package/src/consumers/github-ref.ts +88 -0
  7. package/src/consumers/github-release.ts +125 -0
  8. package/src/families/build-family-types.ts +16 -0
  9. package/src/families/deployment-family.ts +15 -0
  10. package/src/families/issue-family.ts +38 -0
  11. package/src/families/pull-request-family.ts +42 -0
  12. package/src/families/pull-request-review-family.ts +18 -0
  13. package/src/families/ref-family.ts +14 -0
  14. package/src/families/release-family.ts +28 -0
  15. package/src/families/workflow-run-family.ts +18 -0
  16. package/src/generated/github-deploy-boundary.ts +308 -0
  17. package/src/generated/github-deploy.sql +24 -0
  18. package/src/generated/github-pull-request-boundary.ts +286 -0
  19. package/src/generated/github-pull-request.sql +41 -0
  20. package/src/generated/github-ref-boundary.ts +149 -0
  21. package/src/generated/github-ref.sql +8 -0
  22. package/src/generated/github-release-boundary.ts +251 -0
  23. package/src/generated/github-release.sql +32 -0
  24. package/src/github-deploy-consumer.ts +40 -0
  25. package/src/github-plugin.ts +15 -0
  26. package/src/github-pull-request-consumer.ts +56 -0
  27. package/src/github-pull-request-schema-path.ts +6 -0
  28. package/src/github-ref-consumer.ts +23 -0
  29. package/src/github-release-consumer.ts +43 -0
  30. package/src/github-settings.ts +15 -0
  31. package/src/github-source.ts +56 -0
  32. package/src/index.ts +13 -0
  33. package/src/ingest/build-github-fact.ts +55 -0
  34. package/src/ingest/github-app/build-app-jwt.ts +20 -0
  35. package/src/ingest/github-app/find-app-jwt.ts +17 -0
  36. package/src/ingest/github-app/github-api-url.ts +1 -0
  37. package/src/ingest/github-app/pick-pull-failure.ts +11 -0
  38. package/src/ingest/github-app/read-installation-token.ts +43 -0
  39. package/src/ingest/github-app/request-github-api.ts +75 -0
  40. package/src/ingest/github-event-rules.ts +324 -0
  41. package/src/ingest/read-github-delivery.ts +41 -0
  42. package/src/ingest/recovery/pull-failed-deliveries.ts +100 -0
  43. package/src/ingest/resync/collect-resync-targets.ts +33 -0
  44. package/src/ingest/resync/pull-marked-objects.ts +130 -0
  45. package/src/ingest/serve-ingest.ts +8 -0
  46. package/src/queries/github-delivery-consumers.ts +13 -0
  47. package/src/queries/pull-request-delivery-query.ts +49 -0
  48. package/src/queries/select-pull-request-delivery.ts +155 -0
  49. package/src/utils/build-body-hash.ts +34 -0
  50. package/src/utils/deploy-signals.ts +5 -0
  51. package/src/utils/find-field.ts +19 -0
@@ -0,0 +1,125 @@
1
+ import {
2
+ initGithubReleaseModel,
3
+ readGithubReleaseRow,
4
+ toGithubReleaseID,
5
+ updateGithubReleaseModel,
6
+ } from '../generated/github-release-boundary.ts';
7
+ import type {
8
+ EntityID,
9
+ GithubReleaseMessage,
10
+ GithubReleaseModel,
11
+ GithubReleaseOperation,
12
+ GithubReleaseRow,
13
+ } from '../generated/github-release-boundary.ts';
14
+ import { findField } from '../utils/find-field.ts';
15
+
16
+ export type {
17
+ GithubReleaseMessage,
18
+ GithubReleaseModel,
19
+ } from '../generated/github-release-boundary.ts';
20
+
21
+ // scriptc takes the model and message types through a re-export, but not the two functions
22
+ export function init(): GithubReleaseModel {
23
+ return initGithubReleaseModel();
24
+ }
25
+
26
+ export function update(
27
+ model: GithubReleaseModel,
28
+ message: GithubReleaseMessage,
29
+ ): GithubReleaseModel {
30
+ return updateGithubReleaseModel(model, message);
31
+ }
32
+
33
+ export function applyFact(factJSON: string): string {
34
+ return JSON.stringify(mapFact(factJSON));
35
+ }
36
+
37
+ // GitHub never uses a release ID again, so a tombstone is never cleared. On a tie of times the
38
+ // later fact in the stream wins: the host gives each fact ID once, so every replay keeps that order.
39
+ function mapFact(factJSON: string): GithubReleaseOperation[] {
40
+ const fact: unknown = JSON.parse(factJSON);
41
+ const type = findField(fact, 'type');
42
+ const at = findField(fact, 'at');
43
+ const releaseID = findField(fact, 'payload', 'release', 'id');
44
+
45
+ if (typeof at !== 'string' || typeof releaseID !== 'number') {
46
+ return [];
47
+ }
48
+
49
+ const id = toGithubReleaseID(`${releaseID}`);
50
+ const stored = readGithubReleaseRow(id);
51
+ const isDelete = type === 'github.release.deleted';
52
+ const deletedAt = stored?.deleted_at ?? (isDelete ? at : null);
53
+ const given = buildReleaseRow(id, findField(fact, 'payload'), at);
54
+ const wins = given !== undefined && (stored === undefined || at >= stored.updated_at);
55
+ const row = wins ? given : stored;
56
+
57
+ if (row === undefined) {
58
+ return [];
59
+ }
60
+
61
+ const next: GithubReleaseRow = { ...row, deleted_at: deletedAt };
62
+
63
+ if (stored !== undefined && JSON.stringify(stored) === JSON.stringify(next)) {
64
+ return [];
65
+ }
66
+
67
+ return [{ upsert: { github_release: next } }];
68
+ }
69
+
70
+ // A fact that holds the release ID alone gives no row of its own.
71
+ function buildReleaseRow(
72
+ id: GithubReleaseRow['id'],
73
+ payload: unknown,
74
+ at: string,
75
+ ): GithubReleaseRow | undefined {
76
+ const release = findField(payload, 'release');
77
+ const repositoryID = findField(payload, 'repository', 'id');
78
+ const fullName = findField(payload, 'repository', 'full_name');
79
+ const tagName = findField(release, 'tag_name');
80
+ const target = findField(release, 'target_commitish');
81
+ const name = findField(release, 'name');
82
+ const url = findField(release, 'html_url');
83
+ const createdAt = findField(release, 'created_at');
84
+ const publishedAt = findField(release, 'published_at');
85
+
86
+ if (typeof repositoryID !== 'number' || typeof fullName !== 'string') {
87
+ return undefined;
88
+ }
89
+
90
+ if (typeof tagName !== 'string' || typeof target !== 'string' || typeof url !== 'string') {
91
+ return undefined;
92
+ }
93
+
94
+ if (typeof createdAt !== 'string') {
95
+ return undefined;
96
+ }
97
+
98
+ return {
99
+ id,
100
+ repository_id: toEntityID(`github:repo:${repositoryID}`),
101
+ repository_full_name: fullName,
102
+ tag_name: tagName,
103
+ target_commitish: target,
104
+ name: typeof name === 'string' && name !== '' ? name : tagName,
105
+ url,
106
+ is_draft: findField(release, 'draft') === true,
107
+ is_prerelease: findField(release, 'prerelease') === true,
108
+ created_at: createdAt,
109
+ published_at: typeof publishedAt === 'string' ? publishedAt : null,
110
+ updated_at: at,
111
+ deleted_at: null,
112
+ };
113
+ }
114
+
115
+ function isEntityID(text: string): text is EntityID {
116
+ return text.length > 0;
117
+ }
118
+
119
+ function toEntityID(text: string): EntityID {
120
+ if (!isEntityID(text)) {
121
+ throw new Error('an entity ID is never empty');
122
+ }
123
+
124
+ return text;
125
+ }
@@ -0,0 +1,16 @@
1
+ import type { z } from 'zod';
2
+
3
+ // One fact type per action, `github.<family>.<action>`, each with the family's payload schema.
4
+ export function buildFamilyTypes(
5
+ family: string,
6
+ actions: readonly string[],
7
+ schema: z.ZodType,
8
+ ): Readonly<Record<string, z.ZodType>> {
9
+ const types: Record<string, z.ZodType> = {};
10
+
11
+ for (const action of actions) {
12
+ types[`github.${family}.${action}`] = schema;
13
+ }
14
+
15
+ return types;
16
+ }
@@ -0,0 +1,15 @@
1
+ import { defineFactFamily } from '@pepys/kit';
2
+ import { z } from 'zod';
3
+
4
+ const payloadSchema = z.looseObject({ deployment: z.looseObject({ id: z.number().int() }) });
5
+
6
+ // A deployment and each of its statuses, as facts of the one deployment entity.
7
+ export const deploymentFamily = defineFactFamily({
8
+ stream: 'facts.github.deployment',
9
+ api: 'github/2022-11-28',
10
+ entityKind: 'github:deployment',
11
+ types: {
12
+ 'github.deployment.created': payloadSchema,
13
+ 'github.deployment.status_created': payloadSchema,
14
+ },
15
+ });
@@ -0,0 +1,38 @@
1
+ import { defineFactFamily } from '@pepys/kit';
2
+ import { z } from 'zod';
3
+ import { buildFamilyTypes } from './build-family-types.ts';
4
+
5
+ // The `action` values of GitHub's `issues` webhook event.
6
+ const actions = [
7
+ 'assigned',
8
+ 'closed',
9
+ 'deleted',
10
+ 'demilestoned',
11
+ 'edited',
12
+ 'labeled',
13
+ 'locked',
14
+ 'milestoned',
15
+ 'opened',
16
+ 'pinned',
17
+ 'reopened',
18
+ 'transferred',
19
+ 'typed',
20
+ 'unassigned',
21
+ 'unlabeled',
22
+ 'unlocked',
23
+ 'unpinned',
24
+ 'untyped',
25
+ ] as const;
26
+
27
+ const payloadSchema = z.looseObject({
28
+ action: z.string(),
29
+ issue: z.looseObject({ id: z.number().int() }),
30
+ });
31
+
32
+ // Stored with no consumer.
33
+ export const issueFamily = defineFactFamily({
34
+ stream: 'facts.github.issue',
35
+ api: 'github/2022-11-28',
36
+ entityKind: 'github:issue',
37
+ types: buildFamilyTypes('issue', actions, payloadSchema),
38
+ });
@@ -0,0 +1,42 @@
1
+ import { defineFactFamily } from '@pepys/kit';
2
+ import { z } from 'zod';
3
+ import { buildFamilyTypes } from './build-family-types.ts';
4
+
5
+ // The `action` values of GitHub's `pull_request` webhook event, and `resynced` for a pull request
6
+ // that the resync pull read from the API.
7
+ const actions = [
8
+ 'assigned',
9
+ 'auto_merge_disabled',
10
+ 'auto_merge_enabled',
11
+ 'closed',
12
+ 'converted_to_draft',
13
+ 'demilestoned',
14
+ 'dequeued',
15
+ 'edited',
16
+ 'enqueued',
17
+ 'labeled',
18
+ 'locked',
19
+ 'milestoned',
20
+ 'opened',
21
+ 'ready_for_review',
22
+ 'reopened',
23
+ 'resynced',
24
+ 'review_request_removed',
25
+ 'review_requested',
26
+ 'synchronize',
27
+ 'unassigned',
28
+ 'unlabeled',
29
+ 'unlocked',
30
+ ] as const;
31
+
32
+ const payloadSchema = z.looseObject({
33
+ action: z.string(),
34
+ pull_request: z.looseObject({ id: z.number().int(), updated_at: z.string() }),
35
+ });
36
+
37
+ export const pullRequestFamily = defineFactFamily({
38
+ stream: 'facts.github.pull_request',
39
+ api: 'github/2022-11-28',
40
+ entityKind: 'github:pull_request',
41
+ types: buildFamilyTypes('pull_request', actions, payloadSchema),
42
+ });
@@ -0,0 +1,18 @@
1
+ import { defineFactFamily } from '@pepys/kit';
2
+ import { z } from 'zod';
3
+ import { buildFamilyTypes } from './build-family-types.ts';
4
+
5
+ const actions = ['dismissed', 'edited', 'submitted'] as const;
6
+
7
+ const payloadSchema = z.looseObject({
8
+ action: z.string(),
9
+ review: z.looseObject({ id: z.number().int() }),
10
+ });
11
+
12
+ // Stored with no consumer.
13
+ export const pullRequestReviewFamily = defineFactFamily({
14
+ stream: 'facts.github.pull_request_review',
15
+ api: 'github/2022-11-28',
16
+ entityKind: 'github:pull_request_review',
17
+ types: buildFamilyTypes('pull_request_review', actions, payloadSchema),
18
+ });
@@ -0,0 +1,14 @@
1
+ import { defineFactFamily } from '@pepys/kit';
2
+ import { z } from 'zod';
3
+
4
+ const repositorySchema = z.looseObject({ id: z.number().int() });
5
+
6
+ // A push to the default branch or to a tag. The entity is `github:ref:<repository ID>:<ref>`.
7
+ export const refFamily = defineFactFamily({
8
+ stream: 'facts.github.ref',
9
+ api: 'github/2022-11-28',
10
+ entityKind: 'github:ref',
11
+ types: {
12
+ 'github.ref.pushed': z.looseObject({ ref: z.string(), repository: repositorySchema }),
13
+ },
14
+ });
@@ -0,0 +1,28 @@
1
+ import { defineFactFamily } from '@pepys/kit';
2
+ import { z } from 'zod';
3
+ import { buildFamilyTypes } from './build-family-types.ts';
4
+
5
+ // The `action` values of GitHub's `release` webhook event, and `resynced` for a release that the
6
+ // resync pull read from the API.
7
+ const actions = [
8
+ 'created',
9
+ 'deleted',
10
+ 'edited',
11
+ 'prereleased',
12
+ 'published',
13
+ 'released',
14
+ 'resynced',
15
+ 'unpublished',
16
+ ] as const;
17
+
18
+ const payloadSchema = z.looseObject({
19
+ action: z.string(),
20
+ release: z.looseObject({ id: z.number().int() }),
21
+ });
22
+
23
+ export const releaseFamily = defineFactFamily({
24
+ stream: 'facts.github.release',
25
+ api: 'github/2022-11-28',
26
+ entityKind: 'github:release',
27
+ types: buildFamilyTypes('release', actions, payloadSchema),
28
+ });
@@ -0,0 +1,18 @@
1
+ import { defineFactFamily } from '@pepys/kit';
2
+ import { z } from 'zod';
3
+ import { buildFamilyTypes } from './build-family-types.ts';
4
+
5
+ const actions = ['completed', 'in_progress', 'requested'] as const;
6
+
7
+ const payloadSchema = z.looseObject({
8
+ action: z.string(),
9
+ workflow_run: z.looseObject({ id: z.number().int() }),
10
+ });
11
+
12
+ // Stored with no consumer.
13
+ export const workflowRunFamily = defineFactFamily({
14
+ stream: 'facts.github.workflow_run',
15
+ api: 'github/2022-11-28',
16
+ entityKind: 'github:workflow_run',
17
+ types: buildFamilyTypes('workflow_run', actions, payloadSchema),
18
+ });
@@ -0,0 +1,308 @@
1
+ // Generated by `pepys build`. Do not edit.
2
+
3
+ declare const idTag: unique symbol;
4
+ declare const kindTag: unique symbol;
5
+
6
+ export type EntityID = string & { readonly [idTag]: 'EntityID' };
7
+
8
+ type KindedID<Kind extends string> = EntityID & { readonly [kindTag]: Kind };
9
+
10
+ export type GithubDeployID = KindedID<'github:deploy'>;
11
+
12
+ export function toGithubDeployID(id: string): GithubDeployID {
13
+ const text = 'github:deploy:' + id;
14
+
15
+ if (!isGithubDeployID(text)) {
16
+ throw new Error('an ID of the kind github:deploy is never empty');
17
+ }
18
+
19
+ return text;
20
+ }
21
+
22
+ export function parseGithubDeployID(text: string): GithubDeployID | undefined {
23
+ return isGithubDeployID(text) ? text : undefined;
24
+ }
25
+
26
+ function isGithubDeployID(text: string): text is GithubDeployID {
27
+ return text.length > 'github:deploy:'.length && text.startsWith('github:deploy:');
28
+ }
29
+
30
+ export type GithubDeploymentID = KindedID<'github:deployment'>;
31
+
32
+ export function toGithubDeploymentID(id: string): GithubDeploymentID {
33
+ const text = 'github:deployment:' + id;
34
+
35
+ if (!isGithubDeploymentID(text)) {
36
+ throw new Error('an ID of the kind github:deployment is never empty');
37
+ }
38
+
39
+ return text;
40
+ }
41
+
42
+ export function parseGithubDeploymentID(text: string): GithubDeploymentID | undefined {
43
+ return isGithubDeploymentID(text) ? text : undefined;
44
+ }
45
+
46
+ function isGithubDeploymentID(text: string): text is GithubDeploymentID {
47
+ return text.length > 'github:deployment:'.length && text.startsWith('github:deployment:');
48
+ }
49
+
50
+ export type GithubRefID = KindedID<'github:ref'>;
51
+
52
+ export function toGithubRefID(id: string): GithubRefID {
53
+ const text = 'github:ref:' + id;
54
+
55
+ if (!isGithubRefID(text)) {
56
+ throw new Error('an ID of the kind github:ref is never empty');
57
+ }
58
+
59
+ return text;
60
+ }
61
+
62
+ export function parseGithubRefID(text: string): GithubRefID | undefined {
63
+ return isGithubRefID(text) ? text : undefined;
64
+ }
65
+
66
+ function isGithubRefID(text: string): text is GithubRefID {
67
+ return text.length > 'github:ref:'.length && text.startsWith('github:ref:');
68
+ }
69
+
70
+ export const settings = {
71
+ "deployTagPattern": "deployed/<environment>"
72
+ } as const;
73
+
74
+ export interface GithubDeployModel {
75
+ input: string;
76
+ }
77
+
78
+ export type GithubDeployMessage = { kind: 'apply'; input: string } | { kind: 'reset' };
79
+
80
+ export function initGithubDeployModel(): GithubDeployModel {
81
+ return { input: '' };
82
+ }
83
+
84
+ export function updateGithubDeployModel(model: GithubDeployModel, message: GithubDeployMessage): GithubDeployModel {
85
+ return message.kind === 'reset' ? initGithubDeployModel() : { ...model, input: message.input };
86
+ }
87
+
88
+ export interface GithubRefPushedPayload {
89
+ ref: string;
90
+ repository: GithubRefPushedPayloadRepository;
91
+ }
92
+
93
+ export interface GithubRefPushedPayloadRepository {
94
+ id: number;
95
+ }
96
+
97
+ export interface GithubDeploymentPayload {
98
+ deployment: GithubDeploymentPayloadDeployment;
99
+ }
100
+
101
+ export interface GithubDeploymentPayloadDeployment {
102
+ id: number;
103
+ }
104
+
105
+ export type GithubDeployFact =
106
+ | { id: string; type: 'github.ref.pushed'; entity: string; at: string; payload: GithubRefPushedPayload }
107
+ | { id: string; type: 'github.deployment.created' | 'github.deployment.status_created'; entity: string; at: string; payload: GithubDeploymentPayload };
108
+
109
+ export function parseGithubDeployFact(factJSON: string): GithubDeployFact | undefined {
110
+ const parsed: unknown = JSON.parse(factJSON);
111
+
112
+ return decodeGithubDeployFact(parsed);
113
+ }
114
+
115
+ function decodeGithubDeployFact(value: unknown): GithubDeployFact | undefined {
116
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
117
+ return undefined;
118
+ }
119
+
120
+ const id = 'id' in value ? value.id : undefined;
121
+ const type = 'type' in value ? value.type : undefined;
122
+ const entity = 'entity' in value ? value.entity : undefined;
123
+ const at = 'at' in value ? value.at : undefined;
124
+ const payload = 'payload' in value ? value.payload : undefined;
125
+
126
+ if (typeof id !== 'string' || typeof entity !== 'string' || typeof at !== 'string') {
127
+ return undefined;
128
+ }
129
+
130
+ if (type === 'github.ref.pushed') {
131
+ const decoded = decodeGithubRefPushedPayload(payload);
132
+
133
+ if (decoded === undefined) {
134
+ return undefined;
135
+ }
136
+
137
+ return { id, type, entity, at, payload: decoded };
138
+ }
139
+
140
+ if (type === 'github.deployment.created' || type === 'github.deployment.status_created') {
141
+ const decoded = decodeGithubDeploymentPayload(payload);
142
+
143
+ if (decoded === undefined) {
144
+ return undefined;
145
+ }
146
+
147
+ return { id, type, entity, at, payload: decoded };
148
+ }
149
+
150
+ return undefined;
151
+ }
152
+
153
+ function decodeGithubRefPushedPayload(value: unknown): GithubRefPushedPayload | undefined {
154
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
155
+ return undefined;
156
+ }
157
+
158
+ const raw0 = 'ref' in value ? value.ref : undefined;
159
+ const field0 = typeof raw0 === 'string' ? raw0 : undefined;
160
+
161
+ if (field0 === undefined) {
162
+ return undefined;
163
+ }
164
+
165
+ const raw1 = 'repository' in value ? value.repository : undefined;
166
+ const field1 = decodeGithubRefPushedPayloadRepository(raw1);
167
+
168
+ if (field1 === undefined) {
169
+ return undefined;
170
+ }
171
+
172
+ return {
173
+ ref: field0,
174
+ repository: field1,
175
+ };
176
+ }
177
+
178
+ function decodeGithubRefPushedPayloadRepository(value: unknown): GithubRefPushedPayloadRepository | undefined {
179
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
180
+ return undefined;
181
+ }
182
+
183
+ const raw0 = 'id' in value ? value.id : undefined;
184
+ const field0 = typeof raw0 === 'number' ? raw0 : undefined;
185
+
186
+ if (field0 === undefined) {
187
+ return undefined;
188
+ }
189
+
190
+ return {
191
+ id: field0,
192
+ };
193
+ }
194
+
195
+ function decodeGithubDeploymentPayload(value: unknown): GithubDeploymentPayload | undefined {
196
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
197
+ return undefined;
198
+ }
199
+
200
+ const raw0 = 'deployment' in value ? value.deployment : undefined;
201
+ const field0 = decodeGithubDeploymentPayloadDeployment(raw0);
202
+
203
+ if (field0 === undefined) {
204
+ return undefined;
205
+ }
206
+
207
+ return {
208
+ deployment: field0,
209
+ };
210
+ }
211
+
212
+ function decodeGithubDeploymentPayloadDeployment(value: unknown): GithubDeploymentPayloadDeployment | undefined {
213
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
214
+ return undefined;
215
+ }
216
+
217
+ const raw0 = 'id' in value ? value.id : undefined;
218
+ const field0 = typeof raw0 === 'number' ? raw0 : undefined;
219
+
220
+ if (field0 === undefined) {
221
+ return undefined;
222
+ }
223
+
224
+ return {
225
+ id: field0,
226
+ };
227
+ }
228
+
229
+ export interface GithubDeployRow {
230
+ id: GithubDeployID;
231
+ repository_id: EntityID;
232
+ environment: string;
233
+ target: string | null;
234
+ sha: string;
235
+ signal: string;
236
+ deployed_at: string;
237
+ }
238
+
239
+ export interface GithubDeployKey {
240
+ id: GithubDeployID;
241
+ }
242
+
243
+ export type GithubDeployOperation =
244
+ | { upsert: { github_deploy: GithubDeployRow } }
245
+ | { delete: { github_deploy: GithubDeployKey } };
246
+
247
+ declare function readGithubDeployLength(key: string): number;
248
+ declare function readGithubDeployInto(key: string, row: Uint8Array): void;
249
+
250
+ export function readGithubDeployRow(key: string): GithubDeployRow | undefined {
251
+ const length = readGithubDeployLength(key);
252
+
253
+ if (length < 0) {
254
+ return undefined;
255
+ }
256
+
257
+ const row = new Uint8Array(length);
258
+
259
+ readGithubDeployInto(key, row);
260
+
261
+ const parsed: unknown = JSON.parse(new TextDecoder().decode(row));
262
+
263
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
264
+ return undefined;
265
+ }
266
+
267
+ if (!('id' in parsed) || (typeof parsed.id !== 'string' || !isGithubDeployID(parsed.id))) {
268
+ return undefined;
269
+ }
270
+
271
+ if (!('repository_id' in parsed) || (!isEntityID(parsed.repository_id))) {
272
+ return undefined;
273
+ }
274
+
275
+ if (!('environment' in parsed) || (typeof parsed.environment !== 'string')) {
276
+ return undefined;
277
+ }
278
+
279
+ if (!('target' in parsed) || (parsed.target !== null && (typeof parsed.target !== 'string'))) {
280
+ return undefined;
281
+ }
282
+
283
+ if (!('sha' in parsed) || (typeof parsed.sha !== 'string')) {
284
+ return undefined;
285
+ }
286
+
287
+ if (!('signal' in parsed) || (typeof parsed.signal !== 'string')) {
288
+ return undefined;
289
+ }
290
+
291
+ if (!('deployed_at' in parsed) || (typeof parsed.deployed_at !== 'string')) {
292
+ return undefined;
293
+ }
294
+
295
+ return {
296
+ id: parsed.id,
297
+ repository_id: parsed.repository_id,
298
+ environment: parsed.environment,
299
+ target: parsed.target,
300
+ sha: parsed.sha,
301
+ signal: parsed.signal,
302
+ deployed_at: parsed.deployed_at,
303
+ };
304
+ }
305
+
306
+ function isEntityID(value: unknown): value is EntityID {
307
+ return typeof value === 'string';
308
+ }
@@ -0,0 +1,24 @@
1
+ -- Generated by `pepys build`. Do not edit.
2
+
3
+ CREATE TABLE "github_deploy" (
4
+ "id" TEXT NOT NULL,
5
+ "repository_id" TEXT NOT NULL,
6
+ "environment" TEXT NOT NULL,
7
+ "target" TEXT,
8
+ "sha" TEXT NOT NULL,
9
+ "signal" TEXT NOT NULL,
10
+ "deployed_at" TEXT NOT NULL,
11
+ PRIMARY KEY ("id")
12
+ );
13
+
14
+ CREATE INDEX "github_deploy_repository_id_environment_idx" ON "github_deploy" ("repository_id", "environment");
15
+
16
+ CREATE VIEW "entity" AS
17
+ SELECT
18
+ "id" AS "id",
19
+ 'github:deploy' AS "kind",
20
+ "sha" AS "title",
21
+ NULL AS "url",
22
+ NULL AS "actor",
23
+ "deployed_at" AS "updated_at"
24
+ FROM "github_deploy";