@relayfile/adapter-airtable 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 (37) hide show
  1. package/dist/__tests__/airtable-adapter.test.d.ts +2 -0
  2. package/dist/__tests__/airtable-adapter.test.d.ts.map +1 -0
  3. package/dist/__tests__/airtable-adapter.test.js +201 -0
  4. package/dist/__tests__/airtable-adapter.test.js.map +1 -0
  5. package/dist/__tests__/webhook-normalizer.test.d.ts +2 -0
  6. package/dist/__tests__/webhook-normalizer.test.d.ts.map +1 -0
  7. package/dist/__tests__/webhook-normalizer.test.js +102 -0
  8. package/dist/__tests__/webhook-normalizer.test.js.map +1 -0
  9. package/dist/airtable-adapter.d.ts +77 -0
  10. package/dist/airtable-adapter.d.ts.map +1 -0
  11. package/dist/airtable-adapter.js +705 -0
  12. package/dist/airtable-adapter.js.map +1 -0
  13. package/dist/index.d.ts +7 -0
  14. package/dist/index.d.ts.map +1 -0
  15. package/dist/index.js +7 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/path-mapper.d.ts +21 -0
  18. package/dist/path-mapper.d.ts.map +1 -0
  19. package/dist/path-mapper.js +116 -0
  20. package/dist/path-mapper.js.map +1 -0
  21. package/dist/queries.d.ts +6 -0
  22. package/dist/queries.d.ts.map +1 -0
  23. package/dist/queries.js +43 -0
  24. package/dist/queries.js.map +1 -0
  25. package/dist/types.d.ts +107 -0
  26. package/dist/types.d.ts.map +1 -0
  27. package/dist/types.js +11 -0
  28. package/dist/types.js.map +1 -0
  29. package/dist/webhook-normalizer.d.ts +50 -0
  30. package/dist/webhook-normalizer.d.ts.map +1 -0
  31. package/dist/webhook-normalizer.js +501 -0
  32. package/dist/webhook-normalizer.js.map +1 -0
  33. package/dist/writeback.d.ts +5 -0
  34. package/dist/writeback.d.ts.map +1 -0
  35. package/dist/writeback.js +139 -0
  36. package/dist/writeback.js.map +1 -0
  37. package/package.json +71 -0
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=airtable-adapter.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"airtable-adapter.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/airtable-adapter.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,201 @@
1
+ import assert from 'node:assert/strict';
2
+ import test from 'node:test';
3
+ import { AirtableAdapter, airtableBasePath, airtableRecordPath, airtableTablePath, computeAirtablePath, resolveAirtableReadRequest, resolveAirtableWritebackRequest, } from '../index.js';
4
+ function createAdapter(config = {}, writes = []) {
5
+ const client = {
6
+ async writeFile(input) {
7
+ writes.push(input);
8
+ return { created: true };
9
+ },
10
+ async deleteFile() {
11
+ return undefined;
12
+ },
13
+ };
14
+ const provider = {
15
+ name: 'relayfile-test-provider',
16
+ async proxy(_request) {
17
+ return {
18
+ data: null,
19
+ headers: {},
20
+ status: 200,
21
+ };
22
+ },
23
+ async healthCheck() {
24
+ return true;
25
+ },
26
+ };
27
+ return new AirtableAdapter(client, provider, config);
28
+ }
29
+ test('AirtableAdapter exposes provider name and supported Airtable webhook events', () => {
30
+ const adapter = createAdapter();
31
+ assert.equal(adapter.name, 'airtable');
32
+ assert.deepEqual(adapter.supportedEvents(), [
33
+ 'record.create',
34
+ 'record.update',
35
+ 'record.delete',
36
+ 'table.create',
37
+ 'table.update',
38
+ 'table.delete',
39
+ 'base.create',
40
+ 'base.update',
41
+ 'base.delete',
42
+ ]);
43
+ });
44
+ test('ingestWebhook writes record events with deterministic content and semantics', async () => {
45
+ const writes = [];
46
+ const adapter = createAdapter({}, writes);
47
+ const result = await adapter.ingestWebhook('ws_relay', {
48
+ eventType: 'record.update',
49
+ objectId: 'rec_1',
50
+ objectType: 'record',
51
+ payload: {
52
+ baseId: 'app_base',
53
+ fields: {
54
+ Assignee: 'Ada',
55
+ Linked: ['rec_2'],
56
+ Notes: 'Implementation notes',
57
+ Status: 'In progress',
58
+ },
59
+ id: 'rec_1',
60
+ tableId: 'tbl_tasks',
61
+ },
62
+ provider: 'airtable',
63
+ });
64
+ assert.equal(result.filesWritten, 1);
65
+ assert.deepEqual(result.paths, ['/airtable/bases/app_base/tables/tbl_tasks/records/rec_1.json']);
66
+ assert.equal(writes[0]?.path, '/airtable/bases/app_base/tables/tbl_tasks/records/rec_1.json');
67
+ assert.equal(writes[0]?.semantics?.properties?.['airtable.field.assignee'], 'Ada');
68
+ assert.equal(writes[0]?.semantics?.properties?.['airtable.field.status'], 'In progress');
69
+ assert.deepEqual(writes[0]?.semantics?.comments, ['Implementation notes']);
70
+ assert.deepEqual(writes[0]?.semantics?.relations, [
71
+ '/airtable/bases/app_base.json',
72
+ '/airtable/bases/app_base/tables/tbl_tasks.json',
73
+ '/airtable/bases/app_base/tables/tbl_tasks/records/rec_2.json',
74
+ ]);
75
+ });
76
+ test('ingestWebhook writes table events and extracts schema semantics', async () => {
77
+ const writes = [];
78
+ const adapter = createAdapter({}, writes);
79
+ const result = await adapter.ingestWebhook('ws_relay', {
80
+ eventType: 'table.update',
81
+ objectId: 'tbl_tasks',
82
+ objectType: 'table',
83
+ payload: {
84
+ base: { id: 'app_base', name: 'Ops' },
85
+ description: 'Operational work tracker',
86
+ fields: [
87
+ { id: 'fld_name', name: 'Name', type: 'singleLineText' },
88
+ { id: 'fld_status', name: 'Status', type: 'singleSelect' },
89
+ ],
90
+ id: 'tbl_tasks',
91
+ name: 'Tasks',
92
+ views: [{ id: 'viw_grid', name: 'Grid view', type: 'grid' }],
93
+ },
94
+ provider: 'airtable',
95
+ });
96
+ assert.equal(result.filesWritten, 1);
97
+ assert.deepEqual(result.paths, ['/airtable/bases/app_base/tables/tbl_tasks.json']);
98
+ assert.equal(writes[0]?.semantics?.properties?.['airtable.field_count'], '2');
99
+ assert.equal(writes[0]?.semantics?.properties?.['airtable.schema.status.type'], 'singleSelect');
100
+ assert.deepEqual(writes[0]?.semantics?.relations, ['/airtable/bases/app_base.json']);
101
+ });
102
+ test('ingestWebhook writes base events and extracts child table relations', async () => {
103
+ const writes = [];
104
+ const adapter = createAdapter({}, writes);
105
+ const result = await adapter.ingestWebhook('ws_relay', {
106
+ eventType: 'base.update',
107
+ objectId: 'app_base',
108
+ objectType: 'base',
109
+ payload: {
110
+ id: 'app_base',
111
+ name: 'Ops',
112
+ permissionLevel: 'create',
113
+ tables: [
114
+ { id: 'tbl_tasks', name: 'Tasks' },
115
+ { id: 'tbl_bugs', name: 'Bugs' },
116
+ ],
117
+ workspace: { id: 'wsp_1', name: 'Engineering' },
118
+ },
119
+ provider: 'airtable',
120
+ });
121
+ assert.equal(result.filesWritten, 1);
122
+ assert.deepEqual(result.paths, ['/airtable/bases/app_base.json']);
123
+ assert.equal(writes[0]?.semantics?.properties?.['airtable.table_count'], '2');
124
+ assert.equal(writes[0]?.semantics?.properties?.['airtable.workspace_name'], 'Engineering');
125
+ assert.deepEqual(writes[0]?.semantics?.relations, [
126
+ '/airtable/bases/app_base/tables/tbl_bugs.json',
127
+ '/airtable/bases/app_base/tables/tbl_tasks.json',
128
+ ]);
129
+ });
130
+ test('ingestWebhook normalizes raw Airtable record payloads using adapter config context', async () => {
131
+ const writes = [];
132
+ const adapter = createAdapter({ baseId: 'app_base', connectionId: 'conn_airtable_1', tableId: 'tbl_tasks' }, writes);
133
+ const result = await adapter.ingestWebhook('ws_relay', {
134
+ action: 'changed',
135
+ data: {
136
+ fields: {
137
+ Name: 'Ship Airtable adapter',
138
+ Status: 'Done',
139
+ },
140
+ id: 'rec_1',
141
+ },
142
+ type: 'record',
143
+ });
144
+ assert.equal(result.filesWritten, 1);
145
+ assert.deepEqual(result.paths, ['/airtable/bases/app_base/tables/tbl_tasks/records/rec_1.json']);
146
+ assert.equal(JSON.parse(writes[0]?.content ?? '{}').connectionId, 'conn_airtable_1');
147
+ assert.equal(writes[0]?.semantics?.properties?.['airtable.record_title'], 'Ship Airtable adapter');
148
+ });
149
+ test('computeSemantics extracts record fields, comments, and webhook metadata', () => {
150
+ const adapter = createAdapter({ baseId: 'app_base', tableId: 'tbl_tasks' });
151
+ const semantics = adapter.computeSemantics('AirtableRecord', 'rec_1', {
152
+ fields: {
153
+ Name: 'Review launch list',
154
+ Notes: 'Needs owner review',
155
+ Score: 3,
156
+ Tags: ['launch', 'adapter'],
157
+ },
158
+ id: 'rec_1',
159
+ _webhook: {
160
+ action: 'update',
161
+ deliveryId: 'delivery_1',
162
+ eventType: 'record.update',
163
+ webhookTimestamp: 1_777_639_200_000,
164
+ },
165
+ });
166
+ assert.equal(semantics.properties?.['airtable.field.name'], 'Review launch list');
167
+ assert.equal(semantics.properties?.['airtable.field.score'], '3');
168
+ assert.equal(semantics.properties?.['airtable.field.tags'], 'launch, adapter');
169
+ assert.equal(semantics.properties?.['airtable.webhook.delivery_id'], 'delivery_1');
170
+ assert.deepEqual(semantics.comments, ['Needs owner review']);
171
+ });
172
+ test('path mapper, read routes, and writeback routes cover primary Airtable objects', () => {
173
+ const adapter = createAdapter({ baseId: 'app_base', tableId: 'tbl_tasks' });
174
+ assert.equal(airtableBasePath('app base'), '/airtable/bases/app%20base.json');
175
+ assert.equal(airtableTablePath('app/base', 'tbl tasks'), '/airtable/bases/app%2Fbase/tables/tbl%20tasks.json');
176
+ assert.equal(airtableRecordPath('app_base', 'tbl_tasks', 'rec#7'), '/airtable/bases/app_base/tables/tbl_tasks/records/rec%237.json');
177
+ assert.equal(computeAirtablePath('records', 'rec_1', { baseId: 'app_base', tableId: 'tbl_tasks' }), '/airtable/bases/app_base/tables/tbl_tasks/records/rec_1.json');
178
+ assert.equal(adapter.computePath('table', 'tbl_tasks'), '/airtable/bases/app_base/tables/tbl_tasks.json');
179
+ assert.deepEqual(resolveAirtableReadRequest('/airtable/bases/app_base/tables/tbl_tasks.json'), {
180
+ action: 'get_table_records',
181
+ endpoint: '/v0/app_base/tbl_tasks',
182
+ method: 'GET',
183
+ routeTemplate: '/v0/{baseId}/{tableId}',
184
+ });
185
+ assert.deepEqual(resolveAirtableWritebackRequest('/airtable/bases/app_base/tables/tbl_tasks/records/rec_1.json', '{"fields":{"Status":"Done"}}'), {
186
+ action: 'update_record',
187
+ body: {
188
+ records: [
189
+ {
190
+ fields: { Status: 'Done' },
191
+ id: 'rec_1',
192
+ },
193
+ ],
194
+ },
195
+ endpoint: '/v0/app_base/tbl_tasks',
196
+ method: 'PATCH',
197
+ routeTemplate: '/v0/{baseId}/{tableId}',
198
+ });
199
+ assert.throws(() => resolveAirtableWritebackRequest('/airtable/bases/app_base/tables/tbl_tasks/records/rec_1.json', '{"fields":{}}'), /at least one field/);
200
+ });
201
+ //# sourceMappingURL=airtable-adapter.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"airtable-adapter.test.js","sourceRoot":"","sources":["../../src/__tests__/airtable-adapter.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=webhook-normalizer.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook-normalizer.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/webhook-normalizer.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,102 @@
1
+ import assert from 'node:assert/strict';
2
+ import { createHmac } from 'node:crypto';
3
+ import test from 'node:test';
4
+ import { AIRTABLE_CONTENT_MAC_HEADER, AIRTABLE_TIMESTAMP_HEADER, assertValidAirtableWebhookSignature, computeAirtableWebhookSignature, normalizeAirtableWebhook, validateAirtableWebhookSignature, validateAirtableWebhookTimestamp, } from '../index.js';
5
+ const payload = {
6
+ action: 'changed',
7
+ baseId: 'app_base',
8
+ data: {
9
+ fields: {
10
+ Name: 'Ship Airtable adapter',
11
+ Status: 'In progress',
12
+ },
13
+ id: 'rec_1',
14
+ },
15
+ tableId: 'tbl_tasks',
16
+ timestamp: 1_777_639_200_000,
17
+ type: 'record',
18
+ };
19
+ test('accepts known-good HMAC content MAC and normalizes Airtable webhook payload', () => {
20
+ const rawPayload = JSON.stringify(payload);
21
+ const secret = 'airtable-secret';
22
+ const signature = `hmac-sha256=${createHmac('sha256', secret).update(rawPayload).digest('hex')}`;
23
+ assert.equal(computeAirtableWebhookSignature(rawPayload, secret), signature);
24
+ const validation = validateAirtableWebhookSignature(rawPayload, {
25
+ [AIRTABLE_CONTENT_MAC_HEADER]: signature,
26
+ [AIRTABLE_TIMESTAMP_HEADER]: '1777639200000',
27
+ 'X-Relay-Connection-Id': 'conn_airtable_1',
28
+ }, secret);
29
+ assert.equal(validation.ok, true);
30
+ const normalized = normalizeAirtableWebhook(rawPayload, {
31
+ [AIRTABLE_CONTENT_MAC_HEADER]: signature,
32
+ [AIRTABLE_TIMESTAMP_HEADER]: '1777639200000',
33
+ 'X-Relay-Connection-Id': 'conn_airtable_1',
34
+ 'X-Relay-Provider-Config-Key': 'airtable-primary',
35
+ }, {
36
+ nowMs: 1_777_639_200_000,
37
+ webhookSecret: secret,
38
+ });
39
+ assert.equal(normalized.provider, 'airtable');
40
+ assert.equal(normalized.connectionId, 'conn_airtable_1');
41
+ assert.equal(normalized.eventType, 'record.update');
42
+ assert.equal(normalized.objectType, 'record');
43
+ assert.equal(normalized.objectId, 'rec_1');
44
+ assert.deepEqual(normalized.payload._connection, {
45
+ connectionId: 'conn_airtable_1',
46
+ provider: 'airtable',
47
+ providerConfigKey: 'airtable-primary',
48
+ });
49
+ });
50
+ test('rejects tampered body with invalid-signature result and throwing helper', () => {
51
+ const rawPayload = JSON.stringify(payload);
52
+ const tamperedPayload = JSON.stringify({
53
+ ...payload,
54
+ data: {
55
+ fields: {
56
+ Name: 'Tampered',
57
+ },
58
+ id: 'rec_1',
59
+ },
60
+ });
61
+ const secret = 'airtable-secret';
62
+ const signature = computeAirtableWebhookSignature(rawPayload, secret);
63
+ const invalid = validateAirtableWebhookSignature(tamperedPayload, {
64
+ [AIRTABLE_CONTENT_MAC_HEADER]: signature,
65
+ }, secret);
66
+ assert.equal(invalid.ok, false);
67
+ assert.equal(invalid.reason, 'invalid-signature');
68
+ assert.throws(() => assertValidAirtableWebhookSignature(tamperedPayload, { [AIRTABLE_CONTENT_MAC_HEADER]: signature }, secret), /invalid-signature/);
69
+ });
70
+ test('normalizeAirtableWebhook requires raw request bytes for signature validation', () => {
71
+ const rawPayload = JSON.stringify(payload);
72
+ const signature = computeAirtableWebhookSignature(rawPayload, 'airtable-secret');
73
+ assert.throws(() => normalizeAirtableWebhook(payload, {
74
+ [AIRTABLE_CONTENT_MAC_HEADER]: signature,
75
+ }, {
76
+ webhookSecret: 'airtable-secret',
77
+ }), /original raw request body/);
78
+ });
79
+ test('rejects missing content MAC header', () => {
80
+ const rawPayload = JSON.stringify(payload);
81
+ const missing = validateAirtableWebhookSignature(rawPayload, {}, 'airtable-secret');
82
+ assert.deepEqual(missing, {
83
+ ok: false,
84
+ reason: 'missing-signature',
85
+ });
86
+ });
87
+ test('rejects malformed content MAC header', () => {
88
+ const rawPayload = JSON.stringify(payload);
89
+ const malformed = validateAirtableWebhookSignature(rawPayload, {
90
+ [AIRTABLE_CONTENT_MAC_HEADER]: 'sha256=not-the-provider-scheme',
91
+ }, 'airtable-secret');
92
+ assert.equal(malformed.ok, false);
93
+ assert.equal(malformed.reason, 'malformed-signature');
94
+ });
95
+ test('rejects expired timestamp when timestamp freshness is required', () => {
96
+ const stale = validateAirtableWebhookTimestamp(payload, {
97
+ [AIRTABLE_TIMESTAMP_HEADER]: '1777639200000',
98
+ }, 60_000, 1_777_640_000_001, true);
99
+ assert.equal(stale.ok, false);
100
+ assert.equal(stale.reason, 'stale-timestamp');
101
+ });
102
+ //# sourceMappingURL=webhook-normalizer.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook-normalizer.test.js","sourceRoot":"","sources":["../../src/__tests__/webhook-normalizer.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,77 @@
1
+ import type { ConnectionProvider } from '@relayfile/sdk';
2
+ export type { ConnectionProvider, ProxyRequest, ProxyResponse } from '@relayfile/sdk';
3
+ import type { AirtableAdapterConfig, AirtableWebhookPayload } from './types.js';
4
+ export interface FileSemantics {
5
+ properties?: Record<string, string>;
6
+ relations?: string[];
7
+ permissions?: string[];
8
+ comments?: string[];
9
+ }
10
+ export interface IngestError {
11
+ path: string;
12
+ error: string;
13
+ }
14
+ export interface IngestResult {
15
+ filesWritten: number;
16
+ filesUpdated: number;
17
+ filesDeleted: number;
18
+ paths: string[];
19
+ errors: IngestError[];
20
+ }
21
+ export interface NormalizedWebhook {
22
+ provider: string;
23
+ connectionId?: string;
24
+ eventType: string;
25
+ objectType: string;
26
+ objectId: string;
27
+ payload: Record<string, unknown>;
28
+ }
29
+ export interface WriteFileInput {
30
+ workspaceId: string;
31
+ path: string;
32
+ content: string;
33
+ contentType?: string;
34
+ semantics?: FileSemantics;
35
+ }
36
+ export interface WriteFileResult {
37
+ created?: boolean;
38
+ updated?: boolean;
39
+ status?: 'created' | 'updated' | 'queued' | 'pending';
40
+ }
41
+ export interface DeleteFileInput {
42
+ workspaceId: string;
43
+ path: string;
44
+ }
45
+ export interface RelayFileClientLike {
46
+ writeFile(input: WriteFileInput): Promise<WriteFileResult | void>;
47
+ deleteFile?(input: DeleteFileInput): Promise<void> | void;
48
+ }
49
+ export declare abstract class IntegrationAdapter {
50
+ protected readonly client: RelayFileClientLike;
51
+ protected readonly provider: ConnectionProvider;
52
+ abstract readonly name: string;
53
+ abstract readonly version: string;
54
+ constructor(client: RelayFileClientLike, provider: ConnectionProvider);
55
+ abstract ingestWebhook(workspaceId: string, event: NormalizedWebhook | AirtableWebhookPayload): Promise<IngestResult>;
56
+ abstract computePath(objectType: string, objectId: string): string;
57
+ abstract computeSemantics(objectType: string, objectId: string, payload: Record<string, unknown>): FileSemantics;
58
+ supportedEvents?(): string[];
59
+ }
60
+ export declare class AirtableAdapter extends IntegrationAdapter {
61
+ readonly name = "airtable";
62
+ readonly version = "0.1.0";
63
+ readonly config: AirtableAdapterConfig;
64
+ constructor(client: RelayFileClientLike, provider: ConnectionProvider, config?: AirtableAdapterConfig);
65
+ supportedEvents(): string[];
66
+ ingestWebhook(workspaceId: string, event: NormalizedWebhook | AirtableWebhookPayload): Promise<IngestResult>;
67
+ computePath(objectType: string, objectId: string, context?: {
68
+ baseId?: string;
69
+ tableId?: string;
70
+ }): string;
71
+ computeSemantics(objectType: string, objectId: string, payload: Record<string, unknown>): FileSemantics;
72
+ private normalizeEvent;
73
+ private resolveContext;
74
+ private isDeleteEvent;
75
+ private renderContent;
76
+ }
77
+ //# sourceMappingURL=airtable-adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"airtable-adapter.d.ts","sourceRoot":"","sources":["../src/airtable-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACzD,YAAY,EAAE,kBAAkB,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAUtF,OAAO,KAAK,EACV,qBAAqB,EAOrB,sBAAsB,EACvB,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,aAAa;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,MAAM,EAAE,WAAW,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,aAAa,CAAC;CAC3B;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAC;CACvD;AAED,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,mBAAmB;IAClC,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC;IAClE,UAAU,CAAC,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAC3D;AAED,8BAAsB,kBAAkB;IACtC,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,mBAAmB,CAAC;IAC/C,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAE,kBAAkB,CAAC;IAEhD,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;gBAEtB,MAAM,EAAE,mBAAmB,EAAE,QAAQ,EAAE,kBAAkB;IAKrE,QAAQ,CAAC,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,iBAAiB,GAAG,sBAAsB,GAAG,OAAO,CAAC,YAAY,CAAC;IAErH,QAAQ,CAAC,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM;IAElE,QAAQ,CAAC,gBAAgB,CACvB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,aAAa;IAEhB,eAAe,CAAC,IAAI,MAAM,EAAE;CAC7B;AAQD,qBAAa,eAAgB,SAAQ,kBAAkB;IACrD,SAAkB,IAAI,cAA0B;IAChD,SAAkB,OAAO,WAAW;IAEpC,QAAQ,CAAC,MAAM,EAAE,qBAAqB,CAAC;gBAGrC,MAAM,EAAE,mBAAmB,EAC3B,QAAQ,EAAE,kBAAkB,EAC5B,MAAM,GAAE,qBAA0B;IAM3B,eAAe,IAAI,MAAM,EAAE;IAQrB,aAAa,CAC1B,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,iBAAiB,GAAG,sBAAsB,GAChD,OAAO,CAAC,YAAY,CAAC;IA4Ef,WAAW,CAClB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAO,GAClD,MAAM;IAIA,gBAAgB,CACvB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,aAAa;IAwDhB,OAAO,CAAC,cAAc;IA0CtB,OAAO,CAAC,cAAc;IAmCtB,OAAO,CAAC,aAAa;IAKrB,OAAO,CAAC,aAAa;CAYtB"}