@powersync/service-module-postgres-storage 0.0.0-dev-20250829094737 → 0.0.0-dev-20250903064005
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/CHANGELOG.md +26 -4
- package/dist/.tsbuildinfo +1 -1
- package/dist/@types/migrations/scripts/1756282360128-connection-reporting.d.ts +3 -0
- package/dist/@types/storage/PostgresReportStorage.d.ts +24 -0
- package/dist/@types/storage/PostgresStorageProvider.d.ts +1 -1
- package/dist/@types/storage/storage-index.d.ts +0 -1
- package/dist/@types/types/models/SdkReporting.d.ts +21 -0
- package/dist/@types/types/models/models-index.d.ts +1 -0
- package/dist/@types/{storage/PostgresTestStorageFactoryGenerator.d.ts → utils/test-utils.d.ts} +5 -3
- package/dist/@types/utils/utils-index.d.ts +1 -0
- package/dist/migrations/scripts/1756282360128-connection-reporting.js +107 -0
- package/dist/migrations/scripts/1756282360128-connection-reporting.js.map +1 -0
- package/dist/storage/PostgresReportStorage.js +238 -0
- package/dist/storage/PostgresReportStorage.js.map +1 -0
- package/dist/storage/PostgresStorageProvider.js +10 -1
- package/dist/storage/PostgresStorageProvider.js.map +1 -1
- package/dist/storage/storage-index.js +0 -1
- package/dist/storage/storage-index.js.map +1 -1
- package/dist/types/models/SdkReporting.js +17 -0
- package/dist/types/models/SdkReporting.js.map +1 -0
- package/dist/types/models/models-index.js +1 -0
- package/dist/types/models/models-index.js.map +1 -1
- package/dist/{storage/PostgresTestStorageFactoryGenerator.js → utils/test-utils.js} +22 -6
- package/dist/utils/test-utils.js.map +1 -0
- package/dist/utils/utils-index.js +1 -0
- package/dist/utils/utils-index.js.map +1 -1
- package/package.json +7 -7
- package/src/migrations/scripts/1756282360128-connection-reporting.ts +41 -0
- package/src/storage/PostgresReportStorage.ts +258 -0
- package/src/storage/PostgresStorageProvider.ts +13 -2
- package/src/storage/storage-index.ts +0 -1
- package/src/types/models/SdkReporting.ts +23 -0
- package/src/types/models/models-index.ts +1 -0
- package/src/{storage/PostgresTestStorageFactoryGenerator.ts → utils/test-utils.ts} +21 -5
- package/src/utils/utils-index.ts +1 -0
- package/test/src/__snapshots__/connection-report-storage.test.ts.snap +215 -0
- package/test/src/__snapshots__/storage.test.ts.snap +1 -1
- package/test/src/connection-report-storage.test.ts +233 -0
- package/test/src/storage.test.ts +9 -3
- package/test/src/util.ts +3 -6
- package/dist/storage/PostgresTestStorageFactoryGenerator.js.map +0 -1
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
|
2
|
+
import { POSTGRES_REPORT_STORAGE_FACTORY } from './util.js';
|
|
3
|
+
import { event_types } from '@powersync/service-types';
|
|
4
|
+
import { register, ReportUserData } from '@powersync/service-core-tests';
|
|
5
|
+
import { PostgresReportStorage } from '../../src/storage/PostgresReportStorage.js';
|
|
6
|
+
import { DateTimeValue } from '@powersync/service-sync-rules';
|
|
7
|
+
|
|
8
|
+
const factory = await POSTGRES_REPORT_STORAGE_FACTORY();
|
|
9
|
+
const userData = register.REPORT_TEST_USERS;
|
|
10
|
+
const dates = register.REPORT_TEST_DATES;
|
|
11
|
+
|
|
12
|
+
function removeVolatileFields(sdks: event_types.ClientConnection[]): Partial<event_types.ClientConnection>[] {
|
|
13
|
+
return sdks.map((sdk) => {
|
|
14
|
+
const { id, disconnected_at, connected_at, jwt_exp, ...rest } = sdk;
|
|
15
|
+
return {
|
|
16
|
+
...rest
|
|
17
|
+
};
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function loadData(userData: ReportUserData, factory: PostgresReportStorage) {
|
|
22
|
+
await factory.db.sql`
|
|
23
|
+
INSERT INTO
|
|
24
|
+
connection_report_events (
|
|
25
|
+
user_id,
|
|
26
|
+
client_id,
|
|
27
|
+
connected_at,
|
|
28
|
+
sdk,
|
|
29
|
+
user_agent,
|
|
30
|
+
jwt_exp,
|
|
31
|
+
id,
|
|
32
|
+
disconnected_at
|
|
33
|
+
)
|
|
34
|
+
VALUES
|
|
35
|
+
(
|
|
36
|
+
${{ type: 'varchar', value: userData.user_one.user_id }},
|
|
37
|
+
${{ type: 'varchar', value: userData.user_one.client_id }},
|
|
38
|
+
${{ type: 1184, value: userData.user_one.connected_at.toISOString() }},
|
|
39
|
+
${{ type: 'varchar', value: userData.user_one.sdk }},
|
|
40
|
+
${{ type: 'varchar', value: userData.user_one.user_agent }},
|
|
41
|
+
${{ type: 1184, value: userData.user_one.jwt_exp.toISOString() }},
|
|
42
|
+
${{ type: 'varchar', value: '1' }},
|
|
43
|
+
NULL
|
|
44
|
+
),
|
|
45
|
+
(
|
|
46
|
+
${{ type: 'varchar', value: userData.user_two.user_id }},
|
|
47
|
+
${{ type: 'varchar', value: userData.user_two.client_id }},
|
|
48
|
+
${{ type: 1184, value: userData.user_two.connected_at.toISOString() }},
|
|
49
|
+
${{ type: 'varchar', value: userData.user_two.sdk }},
|
|
50
|
+
${{ type: 'varchar', value: userData.user_two.user_agent }},
|
|
51
|
+
${{ type: 1184, value: userData.user_two.jwt_exp.toISOString() }},
|
|
52
|
+
${{ type: 'varchar', value: '2' }},
|
|
53
|
+
NULL
|
|
54
|
+
),
|
|
55
|
+
(
|
|
56
|
+
${{ type: 'varchar', value: userData.user_four.user_id }},
|
|
57
|
+
${{ type: 'varchar', value: userData.user_four.client_id }},
|
|
58
|
+
${{ type: 1184, value: userData.user_four.connected_at.toISOString() }},
|
|
59
|
+
${{ type: 'varchar', value: userData.user_four.sdk }},
|
|
60
|
+
${{ type: 'varchar', value: userData.user_four.user_agent }},
|
|
61
|
+
${{ type: 1184, value: userData.user_four.jwt_exp.toISOString() }},
|
|
62
|
+
${{ type: 'varchar', value: '4' }},
|
|
63
|
+
NULL
|
|
64
|
+
),
|
|
65
|
+
(
|
|
66
|
+
${{ type: 'varchar', value: userData.user_old.user_id }},
|
|
67
|
+
${{ type: 'varchar', value: userData.user_old.client_id }},
|
|
68
|
+
${{ type: 1184, value: userData.user_old.connected_at.toISOString() }},
|
|
69
|
+
${{ type: 'varchar', value: userData.user_old.sdk }},
|
|
70
|
+
${{ type: 'varchar', value: userData.user_old.user_agent }},
|
|
71
|
+
${{ type: 1184, value: userData.user_old.jwt_exp.toISOString() }},
|
|
72
|
+
${{ type: 'varchar', value: '5' }},
|
|
73
|
+
NULL
|
|
74
|
+
),
|
|
75
|
+
(
|
|
76
|
+
${{ type: 'varchar', value: userData.user_three.user_id }},
|
|
77
|
+
${{ type: 'varchar', value: userData.user_three.client_id }},
|
|
78
|
+
${{ type: 1184, value: userData.user_three.connected_at.toISOString() }},
|
|
79
|
+
${{ type: 'varchar', value: userData.user_three.sdk }},
|
|
80
|
+
${{ type: 'varchar', value: userData.user_three.user_agent }},
|
|
81
|
+
NULL,
|
|
82
|
+
${{ type: 'varchar', value: '3' }},
|
|
83
|
+
${{ type: 1184, value: userData.user_three.disconnected_at.toISOString() }}
|
|
84
|
+
),
|
|
85
|
+
(
|
|
86
|
+
${{ type: 'varchar', value: userData.user_week.user_id }},
|
|
87
|
+
${{ type: 'varchar', value: userData.user_week.client_id }},
|
|
88
|
+
${{ type: 1184, value: userData.user_week.connected_at.toISOString() }},
|
|
89
|
+
${{ type: 'varchar', value: userData.user_week.sdk }},
|
|
90
|
+
${{ type: 'varchar', value: userData.user_week.user_agent }},
|
|
91
|
+
NULL,
|
|
92
|
+
${{ type: 'varchar', value: 'week' }},
|
|
93
|
+
${{ type: 1184, value: userData.user_week.disconnected_at.toISOString() }}
|
|
94
|
+
),
|
|
95
|
+
(
|
|
96
|
+
${{ type: 'varchar', value: userData.user_month.user_id }},
|
|
97
|
+
${{ type: 'varchar', value: userData.user_month.client_id }},
|
|
98
|
+
${{ type: 1184, value: userData.user_month.connected_at.toISOString() }},
|
|
99
|
+
${{ type: 'varchar', value: userData.user_month.sdk }},
|
|
100
|
+
${{ type: 'varchar', value: userData.user_month.user_agent }},
|
|
101
|
+
NULL,
|
|
102
|
+
${{ type: 'varchar', value: 'month' }},
|
|
103
|
+
${{ type: 1184, value: userData.user_month.disconnected_at.toISOString() }}
|
|
104
|
+
),
|
|
105
|
+
(
|
|
106
|
+
${{ type: 'varchar', value: userData.user_expired.user_id }},
|
|
107
|
+
${{ type: 'varchar', value: userData.user_expired.client_id }},
|
|
108
|
+
${{ type: 1184, value: userData.user_expired.connected_at.toISOString() }},
|
|
109
|
+
${{ type: 'varchar', value: userData.user_expired.sdk }},
|
|
110
|
+
${{ type: 'varchar', value: userData.user_expired.user_agent }},
|
|
111
|
+
${{ type: 1184, value: userData.user_expired.jwt_exp.toISOString() }},
|
|
112
|
+
${{ type: 'varchar', value: 'expired' }},
|
|
113
|
+
NULL
|
|
114
|
+
)
|
|
115
|
+
`.execute();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function deleteData(factory: PostgresReportStorage) {
|
|
119
|
+
await factory.db.sql`TRUNCATE TABLE connection_report_events`.execute();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
beforeAll(async () => {
|
|
123
|
+
await loadData(userData, factory);
|
|
124
|
+
});
|
|
125
|
+
afterAll(async () => {
|
|
126
|
+
await deleteData(factory);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
describe('Report storage tests', async () => {
|
|
130
|
+
await register.registerReportTests(factory);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe('Connection report storage', async () => {
|
|
134
|
+
it('Should update a connection event if its within a day', async () => {
|
|
135
|
+
const newConnectAt = new Date(
|
|
136
|
+
dates.now.getFullYear(),
|
|
137
|
+
dates.now.getMonth(),
|
|
138
|
+
dates.now.getDate(),
|
|
139
|
+
dates.now.getHours(),
|
|
140
|
+
dates.now.getMinutes() + 20
|
|
141
|
+
);
|
|
142
|
+
const jwtExp = new Date(newConnectAt.getFullYear(), newConnectAt.getMonth(), newConnectAt.getDate() + 1);
|
|
143
|
+
await factory.reportClientConnection({
|
|
144
|
+
sdk: userData.user_one.sdk,
|
|
145
|
+
connected_at: newConnectAt,
|
|
146
|
+
jwt_exp: jwtExp,
|
|
147
|
+
client_id: userData.user_one.client_id,
|
|
148
|
+
user_id: userData.user_one.user_id,
|
|
149
|
+
user_agent: userData.user_one.user_agent
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const sdk = await factory.db
|
|
153
|
+
.sql`SELECT * FROM connection_report_events WHERE user_id = ${{ type: 'varchar', value: userData.user_one.user_id }} AND client_id = ${{ type: 'varchar', value: userData.user_one.client_id }}`.rows<event_types.ClientConnection>();
|
|
154
|
+
expect(sdk).toHaveLength(1);
|
|
155
|
+
expect(new Date((sdk[0].connected_at as unknown as DateTimeValue).iso8601Representation).toISOString()).toEqual(
|
|
156
|
+
newConnectAt.toISOString()
|
|
157
|
+
);
|
|
158
|
+
expect(new Date((sdk[0].jwt_exp! as unknown as DateTimeValue).iso8601Representation).toISOString()).toEqual(
|
|
159
|
+
jwtExp.toISOString()
|
|
160
|
+
);
|
|
161
|
+
expect(sdk[0].disconnected_at).toBeNull();
|
|
162
|
+
const cleaned = removeVolatileFields(sdk);
|
|
163
|
+
expect(cleaned).toMatchSnapshot();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('Should update a connection event and make it disconnected', async () => {
|
|
167
|
+
const disconnectAt = new Date(
|
|
168
|
+
dates.now.getFullYear(),
|
|
169
|
+
dates.now.getMonth(),
|
|
170
|
+
dates.now.getDate(),
|
|
171
|
+
dates.now.getHours(),
|
|
172
|
+
dates.now.getMinutes() + 20
|
|
173
|
+
);
|
|
174
|
+
const jwtExp = new Date(disconnectAt.getFullYear(), disconnectAt.getMonth(), disconnectAt.getDate() + 1);
|
|
175
|
+
|
|
176
|
+
await factory.reportClientDisconnection({
|
|
177
|
+
disconnected_at: disconnectAt,
|
|
178
|
+
jwt_exp: jwtExp,
|
|
179
|
+
client_id: userData.user_three.client_id,
|
|
180
|
+
user_id: userData.user_three.user_id,
|
|
181
|
+
user_agent: userData.user_three.user_agent,
|
|
182
|
+
connected_at: dates.yesterday
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
const sdk = await factory.db
|
|
186
|
+
.sql`SELECT * FROM connection_report_events WHERE user_id = ${{ type: 'varchar', value: userData.user_three.user_id }}`.rows<event_types.ClientConnection>();
|
|
187
|
+
expect(sdk).toHaveLength(1);
|
|
188
|
+
console.log(sdk[0]);
|
|
189
|
+
expect(new Date((sdk[0].disconnected_at! as unknown as DateTimeValue).iso8601Representation).toISOString()).toEqual(
|
|
190
|
+
disconnectAt.toISOString()
|
|
191
|
+
);
|
|
192
|
+
const cleaned = removeVolatileFields(sdk);
|
|
193
|
+
expect(cleaned).toMatchSnapshot();
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('Should create a connection event if its after a day', async () => {
|
|
197
|
+
const newConnectAt = new Date(
|
|
198
|
+
dates.now.getFullYear(),
|
|
199
|
+
dates.now.getMonth(),
|
|
200
|
+
dates.now.getDate() + 1,
|
|
201
|
+
dates.now.getHours()
|
|
202
|
+
);
|
|
203
|
+
const jwtExp = new Date(newConnectAt.getFullYear(), newConnectAt.getMonth(), newConnectAt.getDate() + 1);
|
|
204
|
+
|
|
205
|
+
await factory.reportClientConnection({
|
|
206
|
+
sdk: userData.user_week.sdk,
|
|
207
|
+
connected_at: newConnectAt,
|
|
208
|
+
jwt_exp: jwtExp,
|
|
209
|
+
client_id: userData.user_week.client_id,
|
|
210
|
+
user_id: userData.user_week.user_id,
|
|
211
|
+
user_agent: userData.user_week.user_agent
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
const sdk = await factory.db
|
|
215
|
+
.sql`SELECT * FROM connection_report_events WHERE user_id = ${{ type: 'varchar', value: userData.user_week.user_id }}`.rows<event_types.ClientConnection>();
|
|
216
|
+
expect(sdk).toHaveLength(2);
|
|
217
|
+
const cleaned = removeVolatileFields(sdk);
|
|
218
|
+
expect(cleaned).toMatchSnapshot();
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('Should delete rows older than specified range', async () => {
|
|
222
|
+
await deleteData(factory);
|
|
223
|
+
await loadData(userData, factory);
|
|
224
|
+
await factory.deleteOldConnectionData({
|
|
225
|
+
date: dates.weekAgo
|
|
226
|
+
});
|
|
227
|
+
const sdk = await factory.getClientConnectionReports({
|
|
228
|
+
start: dates.monthAgo,
|
|
229
|
+
end: dates.now
|
|
230
|
+
});
|
|
231
|
+
expect(sdk).toMatchSnapshot();
|
|
232
|
+
});
|
|
233
|
+
});
|
package/test/src/storage.test.ts
CHANGED
|
@@ -3,11 +3,17 @@ import { register, TEST_TABLE, test_utils } from '@powersync/service-core-tests'
|
|
|
3
3
|
import { describe, expect, test } from 'vitest';
|
|
4
4
|
import { POSTGRES_STORAGE_FACTORY } from './util.js';
|
|
5
5
|
|
|
6
|
-
describe('Sync Bucket
|
|
6
|
+
describe('Postgres Sync Bucket Storage - Parameters', () =>
|
|
7
|
+
register.registerDataStorageParameterTests(POSTGRES_STORAGE_FACTORY));
|
|
8
|
+
|
|
9
|
+
describe('Postgres Sync Bucket Storage - Data', () => register.registerDataStorageDataTests(POSTGRES_STORAGE_FACTORY));
|
|
7
10
|
|
|
8
|
-
describe('Postgres Sync Bucket Storage', () =>
|
|
9
|
-
register.
|
|
11
|
+
describe('Postgres Sync Bucket Storage - Checkpoints', () =>
|
|
12
|
+
register.registerDataStorageCheckpointTests(POSTGRES_STORAGE_FACTORY));
|
|
13
|
+
|
|
14
|
+
describe('Sync Bucket Validation', register.registerBucketValidationTests);
|
|
10
15
|
|
|
16
|
+
describe('Postgres Sync Bucket Storage - pg-specific', () => {
|
|
11
17
|
/**
|
|
12
18
|
* The split of returned results can vary depending on storage drivers.
|
|
13
19
|
* The large rows here are 2MB large while the default chunk limit is 1mb.
|
package/test/src/util.ts
CHANGED
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import { fileURLToPath } from 'url';
|
|
3
|
-
import { normalizePostgresStorageConfig } from '../../src
|
|
4
|
-
import { PostgresMigrationAgent } from '../../src/migrations/PostgresMigrationAgent.js';
|
|
5
|
-
import {
|
|
6
|
-
postgresTestSetup,
|
|
7
|
-
PostgresTestStorageFactoryGenerator
|
|
8
|
-
} from '../../src/storage/PostgresTestStorageFactoryGenerator.js';
|
|
3
|
+
import { normalizePostgresStorageConfig, PostgresMigrationAgent } from '../../src/index.js';
|
|
9
4
|
import { env } from './env.js';
|
|
5
|
+
import { postgresTestSetup } from '../../src/utils/test-utils.js';
|
|
10
6
|
|
|
11
7
|
const __filename = fileURLToPath(import.meta.url);
|
|
12
8
|
const __dirname = path.dirname(__filename);
|
|
@@ -37,3 +33,4 @@ export const POSTGRES_STORAGE_SETUP = postgresTestSetup({
|
|
|
37
33
|
});
|
|
38
34
|
|
|
39
35
|
export const POSTGRES_STORAGE_FACTORY = POSTGRES_STORAGE_SETUP.factory;
|
|
36
|
+
export const POSTGRES_REPORT_STORAGE_FACTORY = POSTGRES_STORAGE_SETUP.reportFactory;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"PostgresTestStorageFactoryGenerator.js","sourceRoot":"","sources":["../../src/storage/PostgresTestStorageFactoryGenerator.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,OAAO,EAAE,SAAS,EAAiE,MAAM,yBAAyB,CAAC;AACnH,OAAO,EAAE,sBAAsB,EAAE,MAAM,yCAAyC,CAAC;AACjF,OAAO,EAAE,8BAA8B,EAAgC,MAAM,mBAAmB,CAAC;AACjG,OAAO,EAAE,4BAA4B,EAAE,MAAM,mCAAmC,CAAC;AAWjF,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,cAA0C,EAAE,EAAE;IAC9E,MAAM,WAAW,GAAG;QAClB,IAAI,EAAE,YAAqB;QAC3B,GAAG,EAAE,cAAc,CAAC,GAAG;QACvB,OAAO,EAAE,SAAkB;KAC5B,CAAC;IAEF,MAAM,uBAAuB,GAAG,8BAA8B,CAAC,WAAW,CAAC,CAAC;IAE5E,MAAM,OAAO,GAAG,KAAK,EAAE,SAAyC,EAAE,EAAE;;;YAClE,MAAY,gBAAgB,kCAA8B,IAAI,SAAS,CAAC,gBAAgB,EAAE,OAAA,CAAC;YAC3F,MAAY,cAAc,kCAAG,cAAc,CAAC,cAAc;gBACxD,CAAC,CAAC,cAAc,CAAC,cAAc,CAAC,WAAW,CAAC;gBAC5C,CAAC,CAAC,IAAI,sBAAsB,CAAC,WAAW,CAAC,OAAA,CAAC;YAC5C,gBAAgB,CAAC,sBAAsB,CAAC,cAAc,CAAC,CAAC;YAExD,MAAM,kBAAkB,GAAG,EAAE,aAAa,EAAE,EAAE,OAAO,EAAE,WAAW,EAAE,EAA+B,CAAC;YAEpG,MAAM,gBAAgB,CAAC,OAAO,CAAC;gBAC7B,SAAS,EAAE,SAAS,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI;gBAC9C,gBAAgB,EAAE;oBAChB,eAAe,EAAE,kBAAkB;iBACpC;aACF,CAAC,CAAC;YAEH,IAAI,SAAS,IAAI,SAAS,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,EAAE,CAAC;gBACnD,MAAM,gBAAgB,CAAC,OAAO,CAAC;oBAC7B,SAAS,EAAE,SAAS,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE;oBAC5C,gBAAgB,EAAE;wBAChB,eAAe,EAAE,kBAAkB;qBACpC;iBACF,CAAC,CAAC;YACL,CAAC;;;;;;;;;;;KACF,CAAC;IAEF,OAAO;QACL,OAAO,EAAE,KAAK,EAAE,OAA4B,EAAE,EAAE;YAC9C,IAAI,CAAC;gBACH,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,CAAC;oBACzB,MAAM,OAAO,CAAC,SAAS,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;gBACnD,CAAC;gBAED,OAAO,IAAI,4BAA4B,CAAC;oBACtC,MAAM,EAAE,uBAAuB;oBAC/B,gBAAgB,EAAE,OAAO;iBAC1B,CAAC,CAAC;YACL,CAAC;YAAC,OAAO,EAAE,EAAE,CAAC;gBACZ,iFAAiF;gBACjF,OAAO,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC;gBAC5B,MAAM,EAAE,CAAC;YACX,CAAC;QACH,CAAC;QACD,OAAO;KACR,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,mCAAmC,GAAG,CAAC,cAA0C,EAAE,EAAE;IAChG,OAAO,iBAAiB,CAAC,cAAc,CAAC,CAAC,OAAO,CAAC;AACnD,CAAC,CAAC"}
|