@carthooks/arcubase-cli 0.1.21 → 0.1.23
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/bundle/arcubase-admin.mjs +1430 -83
- package/bundle/arcubase.mjs +1430 -83
- package/dist/generated/command_registry.generated.d.ts +300 -0
- package/dist/generated/command_registry.generated.d.ts.map +1 -1
- package/dist/generated/command_registry.generated.js +416 -0
- package/dist/generated/help_examples.generated.d.ts +116 -0
- package/dist/generated/help_examples.generated.d.ts.map +1 -1
- package/dist/generated/help_examples.generated.js +184 -0
- package/dist/generated/type_index.generated.d.ts +40 -1
- package/dist/generated/type_index.generated.d.ts.map +1 -1
- package/dist/generated/type_index.generated.js +42 -1
- package/dist/generated/zod_registry.generated.d.ts +29 -1
- package/dist/generated/zod_registry.generated.d.ts.map +1 -1
- package/dist/generated/zod_registry.generated.js +37 -0
- package/dist/runtime/dev_sdk_gen.d.ts +9 -0
- package/dist/runtime/dev_sdk_gen.d.ts.map +1 -0
- package/dist/runtime/dev_sdk_gen.js +319 -0
- package/dist/runtime/execute.d.ts.map +1 -1
- package/dist/runtime/execute.js +326 -1
- package/dist/runtime/zod_registry.d.ts.map +1 -1
- package/dist/runtime/zod_registry.js +58 -0
- package/package.json +1 -1
- package/sdk-dist/api/admin/index.ts +1 -0
- package/sdk-dist/api/admin/public-link.ts +48 -0
- package/sdk-dist/api/shared-link/form.ts +42 -1
- package/sdk-dist/api/user/index.ts +0 -1
- package/sdk-dist/docs/runtime-reference/dashboard.md +22 -0
- package/sdk-dist/docs/runtime-reference/dev-sdk-gen.md +41 -0
- package/sdk-dist/docs/runtime-reference/help-examples/admin/dashboard/update-layout.json +16 -0
- package/sdk-dist/docs/runtime-reference/help-examples/admin/widget/create.json +33 -0
- package/sdk-dist/docs/runtime-reference/help-examples/admin/widget/preview-data.json +66 -0
- package/sdk-dist/docs/runtime-reference/help-examples/admin/widget/update-ingress-options.json +21 -0
- package/sdk-dist/docs/runtime-reference/widgets.md +64 -0
- package/sdk-dist/generated/command_registry.generated.ts +416 -0
- package/sdk-dist/generated/help_examples.generated.ts +184 -0
- package/sdk-dist/generated/type_index.generated.ts +42 -1
- package/sdk-dist/generated/zod_registry.generated.ts +56 -0
- package/sdk-dist/types/app.ts +37 -1
- package/sdk-dist/types/common.ts +77 -35
- package/sdk-dist/types/index.ts +1 -1
- package/sdk-dist/types/public-link.ts +10 -0
- package/sdk-dist/types/shared-link.ts +16 -1
- package/sdk-dist/types/user-action.ts +1 -43
- package/src/generated/command_registry.generated.ts +416 -0
- package/src/generated/help_examples.generated.ts +184 -0
- package/src/generated/type_index.generated.ts +42 -1
- package/src/generated/zod_registry.generated.ts +56 -0
- package/src/runtime/dev_sdk_gen.ts +360 -0
- package/src/runtime/execute.ts +371 -1
- package/src/runtime/zod_registry.ts +58 -0
- package/src/tests/command_registry.test.ts +21 -2
- package/src/tests/dev_sdk_gen.test.ts +226 -0
- package/src/tests/docs_readability.test.ts +15 -0
- package/src/tests/execute_validation.test.ts +226 -0
- package/src/tests/help.test.ts +86 -0
- package/sdk-dist/api/user/copilot.ts +0 -20
- package/sdk-dist/types/copilot.ts +0 -34
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import { CLIError } from './errors.js';
|
|
2
|
+
function isRecord(value) {
|
|
3
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
4
|
+
}
|
|
5
|
+
function unwrapData(payload) {
|
|
6
|
+
return isRecord(payload) && 'data' in payload ? payload.data : payload;
|
|
7
|
+
}
|
|
8
|
+
function toPascalCase(value) {
|
|
9
|
+
const words = value
|
|
10
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
11
|
+
.split(/[^A-Za-z0-9]+/)
|
|
12
|
+
.filter(Boolean);
|
|
13
|
+
if (words.length === 0)
|
|
14
|
+
return 'Entity';
|
|
15
|
+
return words.map((word) => `${word[0].toUpperCase()}${word.slice(1)}`).join('');
|
|
16
|
+
}
|
|
17
|
+
function toCamelCase(value) {
|
|
18
|
+
const pascal = toPascalCase(value);
|
|
19
|
+
return `${pascal[0].toLowerCase()}${pascal.slice(1)}`;
|
|
20
|
+
}
|
|
21
|
+
function sanitizeEntityKey(entity) {
|
|
22
|
+
const fallback = entity.sdkName || `entity_${entity.id}`;
|
|
23
|
+
return toCamelCase(fallback) === 'entity' ? `entity${entity.id}` : toCamelCase(fallback);
|
|
24
|
+
}
|
|
25
|
+
function normalizeFieldType(type) {
|
|
26
|
+
switch (type) {
|
|
27
|
+
case 'number':
|
|
28
|
+
return 'number';
|
|
29
|
+
case 'boolean':
|
|
30
|
+
return 'boolean';
|
|
31
|
+
case 'select':
|
|
32
|
+
case 'radio':
|
|
33
|
+
case 'checkbox':
|
|
34
|
+
case 'status':
|
|
35
|
+
return 'number | string';
|
|
36
|
+
case 'file':
|
|
37
|
+
case 'image':
|
|
38
|
+
case 'member':
|
|
39
|
+
case 'members':
|
|
40
|
+
case 'department':
|
|
41
|
+
case 'departments':
|
|
42
|
+
case 'linkto':
|
|
43
|
+
case 'relation':
|
|
44
|
+
case 'relationfield':
|
|
45
|
+
case 'subform':
|
|
46
|
+
return 'unknown';
|
|
47
|
+
default:
|
|
48
|
+
return 'string';
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function normalizeEntities(payload) {
|
|
52
|
+
const root = unwrapData(payload);
|
|
53
|
+
if (!isRecord(root)) {
|
|
54
|
+
throw new CLIError('SDK_GEN_INVALID_SCHEMA', 'sdk-gen expected an app detail object', 2);
|
|
55
|
+
}
|
|
56
|
+
const appId = typeof root.id === 'number' ? root.id : typeof root.app_id === 'number' ? root.app_id : undefined;
|
|
57
|
+
if (!appId) {
|
|
58
|
+
throw new CLIError('SDK_GEN_APP_ID_REQUIRED', 'sdk-gen expected app id in app detail response', 2);
|
|
59
|
+
}
|
|
60
|
+
const sourceEntities = Array.isArray(root.entities)
|
|
61
|
+
? root.entities
|
|
62
|
+
: Array.isArray(root.tables)
|
|
63
|
+
? root.tables
|
|
64
|
+
: Array.isArray(root.entitys)
|
|
65
|
+
? root.entitys
|
|
66
|
+
: [];
|
|
67
|
+
const entities = sourceEntities
|
|
68
|
+
.filter((item) => isRecord(item))
|
|
69
|
+
.filter((item) => typeof item.id === 'number' && typeof item.name === 'string')
|
|
70
|
+
.map((item) => {
|
|
71
|
+
const fields = Array.isArray(item.fields)
|
|
72
|
+
? item.fields
|
|
73
|
+
.filter((field) => isRecord(field))
|
|
74
|
+
.filter((field) => typeof field.id === 'number' && typeof field.label === 'string' && typeof field.type === 'string')
|
|
75
|
+
.map((field) => ({
|
|
76
|
+
id: field.id,
|
|
77
|
+
label: field.label,
|
|
78
|
+
key: typeof field.key === 'string' ? field.key.trim() : '',
|
|
79
|
+
type: field.type,
|
|
80
|
+
required: field.required === true,
|
|
81
|
+
options: isRecord(field.options) ? field.options : undefined,
|
|
82
|
+
}))
|
|
83
|
+
: [];
|
|
84
|
+
const entity = {
|
|
85
|
+
appId,
|
|
86
|
+
id: item.id,
|
|
87
|
+
name: item.name,
|
|
88
|
+
sdkName: typeof item.key === 'string' && item.key.trim() ? item.key.trim() : '',
|
|
89
|
+
fields,
|
|
90
|
+
};
|
|
91
|
+
entity.sdkName = sanitizeEntityKey(entity);
|
|
92
|
+
return entity;
|
|
93
|
+
});
|
|
94
|
+
if (entities.length === 0) {
|
|
95
|
+
throw new CLIError('SDK_GEN_NO_ENTITIES', 'sdk-gen could not find entities in app detail response', 2);
|
|
96
|
+
}
|
|
97
|
+
validateEntities(entities);
|
|
98
|
+
return entities;
|
|
99
|
+
}
|
|
100
|
+
function validateEntities(entities) {
|
|
101
|
+
const entityKeys = new Set();
|
|
102
|
+
for (const entity of entities) {
|
|
103
|
+
if (entityKeys.has(entity.sdkName)) {
|
|
104
|
+
throw new CLIError('SDK_GEN_DUPLICATE_ENTITY_KEY', `duplicate generated entity key: ${entity.sdkName}`, 2);
|
|
105
|
+
}
|
|
106
|
+
entityKeys.add(entity.sdkName);
|
|
107
|
+
const fieldKeys = new Set();
|
|
108
|
+
for (const field of entity.fields) {
|
|
109
|
+
if (!field.key) {
|
|
110
|
+
throw new CLIError('SDK_GEN_FIELD_KEY_REQUIRED', `field.key is required for ${entity.name}.${field.label} (${field.id})`, 2);
|
|
111
|
+
}
|
|
112
|
+
if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(field.key)) {
|
|
113
|
+
throw new CLIError('SDK_GEN_INVALID_FIELD_KEY', `field.key must be a TypeScript identifier: ${entity.name}.${field.key}`, 2);
|
|
114
|
+
}
|
|
115
|
+
if (fieldKeys.has(field.key)) {
|
|
116
|
+
throw new CLIError('SDK_GEN_DUPLICATE_FIELD_KEY', `duplicate field.key in ${entity.name}: ${field.key}`, 2);
|
|
117
|
+
}
|
|
118
|
+
fieldKeys.add(field.key);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function emitRuntime() {
|
|
123
|
+
return `export type ArcubaseSdkConfig = {
|
|
124
|
+
baseURL: string
|
|
125
|
+
accessToken?: string
|
|
126
|
+
refreshToken?: string
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export type ArcubaseRow<TFields> = {
|
|
130
|
+
id: number
|
|
131
|
+
title?: string
|
|
132
|
+
fields: TFields
|
|
133
|
+
raw: Record<string, any>
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export type ArcubaseRuntime = {
|
|
137
|
+
config: ArcubaseSdkConfig
|
|
138
|
+
request<T>(method: string, endpoint: string, body?: unknown): Promise<T>
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
type ArcubaseEnvelope<T> = {
|
|
142
|
+
data?: T
|
|
143
|
+
error?: { message?: string; key?: string; type?: string }
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function createRuntime(config: ArcubaseSdkConfig): ArcubaseRuntime {
|
|
147
|
+
let accessToken = config.accessToken ?? ''
|
|
148
|
+
let refreshToken = config.refreshToken ?? ''
|
|
149
|
+
|
|
150
|
+
async function request<T>(method: string, endpoint: string, body?: unknown): Promise<T> {
|
|
151
|
+
const baseURL = config.baseURL.endsWith('/') ? config.baseURL : \`\${config.baseURL}/\`
|
|
152
|
+
const response = await fetch(new URL(endpoint.replace(/^\\/+/, ''), baseURL).toString(), {
|
|
153
|
+
method,
|
|
154
|
+
headers: {
|
|
155
|
+
'Content-Type': 'application/json',
|
|
156
|
+
...(accessToken ? { Authorization: \`Bearer \${accessToken}\` } : {}),
|
|
157
|
+
},
|
|
158
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
159
|
+
})
|
|
160
|
+
const payload = await response.json().catch(() => null) as ArcubaseEnvelope<T> | null
|
|
161
|
+
if (!response.ok || payload?.error) {
|
|
162
|
+
throw new Error(payload?.error?.message ?? \`Arcubase request failed: \${response.status}\`)
|
|
163
|
+
}
|
|
164
|
+
return payload?.data as T
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
config: { ...config, accessToken, refreshToken },
|
|
169
|
+
request,
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
`;
|
|
173
|
+
}
|
|
174
|
+
function emitSchema(entities) {
|
|
175
|
+
const schema = {
|
|
176
|
+
entities: Object.fromEntries(entities.map((entity) => [
|
|
177
|
+
entity.sdkName,
|
|
178
|
+
{
|
|
179
|
+
entityId: entity.id,
|
|
180
|
+
name: entity.name,
|
|
181
|
+
fields: Object.fromEntries(entity.fields.map((field) => [
|
|
182
|
+
field.key,
|
|
183
|
+
{
|
|
184
|
+
fieldId: field.id,
|
|
185
|
+
label: field.label,
|
|
186
|
+
type: field.type,
|
|
187
|
+
required: field.required,
|
|
188
|
+
},
|
|
189
|
+
])),
|
|
190
|
+
},
|
|
191
|
+
])),
|
|
192
|
+
};
|
|
193
|
+
return `export const arcubaseSchema = ${JSON.stringify(schema, null, 2)} as const
|
|
194
|
+
|
|
195
|
+
export type ArcubaseEntityKey = keyof typeof arcubaseSchema.entities
|
|
196
|
+
`;
|
|
197
|
+
}
|
|
198
|
+
function emitEntity(entity) {
|
|
199
|
+
const typeName = toPascalCase(entity.sdkName);
|
|
200
|
+
const fieldLines = entity.fields.map((field) => {
|
|
201
|
+
const optional = field.required ? '' : '?';
|
|
202
|
+
return ` ${field.key}${optional}: ${normalizeFieldType(field.type)}`;
|
|
203
|
+
});
|
|
204
|
+
const fieldMapLines = entity.fields.map((field) => ` ${field.key}: ${field.id},`);
|
|
205
|
+
return `import type { ArcubaseRuntime, ArcubaseRow } from '../runtime.js'
|
|
206
|
+
|
|
207
|
+
export type ${typeName}Fields = {
|
|
208
|
+
${fieldLines.join('\n')}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export type ${typeName}CreateInput = ${typeName}Fields
|
|
212
|
+
export type ${typeName}UpdateInput = Partial<${typeName}Fields>
|
|
213
|
+
|
|
214
|
+
const entityId = ${entity.id}
|
|
215
|
+
const fieldIds = {
|
|
216
|
+
${fieldMapLines.join('\n')}
|
|
217
|
+
} as const
|
|
218
|
+
|
|
219
|
+
function toArcubaseFields(fields: Partial<${typeName}Fields>): Record<string, any> {
|
|
220
|
+
const out: Record<string, any> = {}
|
|
221
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
222
|
+
if (value === undefined) continue
|
|
223
|
+
const fieldId = fieldIds[key as keyof typeof fieldIds]
|
|
224
|
+
if (!fieldId) {
|
|
225
|
+
throw new Error(\`Unknown ${entity.sdkName} field: \${key}\`)
|
|
226
|
+
}
|
|
227
|
+
out[String(fieldId)] = value
|
|
228
|
+
}
|
|
229
|
+
return out
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function fromArcubaseFields(raw: Record<string, any> | undefined): ${typeName}Fields {
|
|
233
|
+
const out: Record<string, any> = {}
|
|
234
|
+
const source = raw ?? {}
|
|
235
|
+
for (const [key, fieldId] of Object.entries(fieldIds)) {
|
|
236
|
+
if (String(fieldId) in source) {
|
|
237
|
+
out[key] = source[String(fieldId)]
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return out as ${typeName}Fields
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function mapRow(raw: Record<string, any>): ArcubaseRow<${typeName}Fields> {
|
|
244
|
+
return {
|
|
245
|
+
id: Number(raw.id),
|
|
246
|
+
title: typeof raw.title === 'string' ? raw.title : undefined,
|
|
247
|
+
fields: fromArcubaseFields(raw.fields),
|
|
248
|
+
raw,
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export function create${typeName}SDK(runtime: ArcubaseRuntime) {
|
|
253
|
+
return {
|
|
254
|
+
async create(fields: ${typeName}CreateInput): Promise<number> {
|
|
255
|
+
return runtime.request<number>('POST', \`/entity/${entity.appId}/\${entityId}/insert\`, {
|
|
256
|
+
fields: toArcubaseFields(fields),
|
|
257
|
+
})
|
|
258
|
+
},
|
|
259
|
+
|
|
260
|
+
async update(rowId: string | number, fields: ${typeName}UpdateInput): Promise<boolean> {
|
|
261
|
+
const data = toArcubaseFields(fields)
|
|
262
|
+
return runtime.request<boolean>('PUT', \`/entity/${entity.appId}/\${entityId}/row/\${rowId}\`, {
|
|
263
|
+
data,
|
|
264
|
+
changed_fields: Object.keys(data).map((fieldId) => Number(fieldId)),
|
|
265
|
+
})
|
|
266
|
+
},
|
|
267
|
+
|
|
268
|
+
async get(rowId: string | number): Promise<ArcubaseRow<${typeName}Fields>> {
|
|
269
|
+
const data = await runtime.request<{ record?: Record<string, any> }>('GET', \`/entity/${entity.appId}/\${entityId}/row/\${rowId}\`)
|
|
270
|
+
return mapRow(data.record ?? {})
|
|
271
|
+
},
|
|
272
|
+
|
|
273
|
+
async query(params: { limit?: number; offset?: number; search?: Record<string, any> } = {}): Promise<{ items: ArcubaseRow<${typeName}Fields>[]; total?: number }> {
|
|
274
|
+
const data = await runtime.request<{ items?: Record<string, any>[]; total?: number }>('POST', \`/entity/${entity.appId}/\${entityId}/data-query\`, params)
|
|
275
|
+
return {
|
|
276
|
+
items: (data.items ?? []).map(mapRow),
|
|
277
|
+
total: data.total,
|
|
278
|
+
}
|
|
279
|
+
},
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
`;
|
|
283
|
+
}
|
|
284
|
+
function emitClient(entities) {
|
|
285
|
+
const imports = entities.map((entity) => {
|
|
286
|
+
const typeName = toPascalCase(entity.sdkName);
|
|
287
|
+
return `import { create${typeName}SDK } from './entities/${entity.sdkName}.js'`;
|
|
288
|
+
});
|
|
289
|
+
const properties = entities.map((entity) => ` ${entity.sdkName}: create${toPascalCase(entity.sdkName)}SDK(runtime),`);
|
|
290
|
+
return `import { createRuntime, type ArcubaseSdkConfig } from './runtime.js'
|
|
291
|
+
${imports.join('\n')}
|
|
292
|
+
|
|
293
|
+
export function createArcubaseSdk(config: ArcubaseSdkConfig) {
|
|
294
|
+
const runtime = createRuntime(config)
|
|
295
|
+
return {
|
|
296
|
+
${properties.join('\n')}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
`;
|
|
300
|
+
}
|
|
301
|
+
function emitIndex(entities) {
|
|
302
|
+
return `export { createArcubaseSdk } from './client.js'
|
|
303
|
+
export type { ArcubaseSdkConfig, ArcubaseRow } from './runtime.js'
|
|
304
|
+
export { arcubaseSchema } from './schema.js'
|
|
305
|
+
${entities.map((entity) => `export type { ${toPascalCase(entity.sdkName)}Fields, ${toPascalCase(entity.sdkName)}CreateInput, ${toPascalCase(entity.sdkName)}UpdateInput } from './entities/${entity.sdkName}.js'`).join('\n')}
|
|
306
|
+
`;
|
|
307
|
+
}
|
|
308
|
+
export function generateArcubaseProjectSDK(payload) {
|
|
309
|
+
const entities = normalizeEntities(payload);
|
|
310
|
+
return {
|
|
311
|
+
files: [
|
|
312
|
+
{ path: 'runtime.ts', contents: emitRuntime() },
|
|
313
|
+
{ path: 'schema.ts', contents: emitSchema(entities) },
|
|
314
|
+
...entities.map((entity) => ({ path: `entities/${entity.sdkName}.ts`, contents: emitEntity(entity) })),
|
|
315
|
+
{ path: 'client.ts', contents: emitClient(entities) },
|
|
316
|
+
{ path: 'index.ts', contents: emitIndex(entities) },
|
|
317
|
+
],
|
|
318
|
+
};
|
|
319
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"execute.d.ts","sourceRoot":"","sources":["../../src/runtime/execute.ts"],"names":[],"mappings":"AAGA,OAAO,EAAkB,KAAK,UAAU,EAAE,MAAM,UAAU,CAAA;AAG1D,OAAO,EAAyF,KAAK,YAAY,EAAE,MAAM,uBAAuB,CAAA;
|
|
1
|
+
{"version":3,"file":"execute.d.ts","sourceRoot":"","sources":["../../src/runtime/execute.ts"],"names":[],"mappings":"AAGA,OAAO,EAAkB,KAAK,UAAU,EAAE,MAAM,UAAU,CAAA;AAG1D,OAAO,EAAyF,KAAK,YAAY,EAAE,MAAM,uBAAuB,CAAA;AAShJ,MAAM,MAAM,uBAAuB,GAAG;IACpC,KAAK,EAAE,YAAY,CAAA;IACnB,WAAW,EAAE,SAAS,MAAM,EAAE,CAAA;IAC9B,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,SAAS,EAAE,SAAS,MAAM,EAAE,CAAA;IAC5B,UAAU,EAAE,SAAS,MAAM,EAAE,CAAA;CAC9B,CAAA;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,CAe1D;AAaD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,GAAE,QAAsB,GAAG,MAAM,CAoB7G;AA8BD,KAAK,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAA;AAUlD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,EAAE,GAAG,GAAE,QAAsB,GAAG,MAAM,CA8HpI;AAquED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,uBAAuB,CActH;AAED,wBAAsB,UAAU,CAAC,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,CAAC,EAAE,UAAU,EAAE,SAAS,GAAE,OAAO,KAAa,GAAG,OAAO,CAAC,GAAG,CAAC,CAyOrI"}
|