@carthooks/arcubase-cli 0.1.26 → 0.1.27
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 +17982 -17958
- package/bundle/arcubase.mjs +17982 -17958
- package/dist/runtime/execute.d.ts.map +1 -1
- package/dist/runtime/execute.js +21 -6
- package/package.json +2 -1
- package/src/runtime/execute.ts +20 -6
- package/src/tests/dev_sdk_gen.test.ts +4 -4
- package/dist/runtime/dev_sdk_gen.d.ts +0 -9
- package/dist/runtime/dev_sdk_gen.d.ts.map +0 -1
- package/dist/runtime/dev_sdk_gen.js +0 -598
- package/src/runtime/dev_sdk_gen.ts +0 -666
|
@@ -1,598 +0,0 @@
|
|
|
1
|
-
import { CLIError } from './errors.js';
|
|
2
|
-
const identifierPattern = /^[A-Za-z][A-Za-z0-9_]*$/;
|
|
3
|
-
function isRecord(value) {
|
|
4
|
-
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
5
|
-
}
|
|
6
|
-
function unwrapData(payload) {
|
|
7
|
-
return isRecord(payload) && 'data' in payload ? payload.data : payload;
|
|
8
|
-
}
|
|
9
|
-
function toPascalCase(value) {
|
|
10
|
-
const words = value
|
|
11
|
-
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
12
|
-
.split(/[^A-Za-z0-9]+/)
|
|
13
|
-
.filter(Boolean);
|
|
14
|
-
if (words.length === 0)
|
|
15
|
-
return 'Ingress';
|
|
16
|
-
return words.map((word) => `${word[0].toUpperCase()}${word.slice(1)}`).join('');
|
|
17
|
-
}
|
|
18
|
-
function normalizeFieldType(type) {
|
|
19
|
-
switch (type) {
|
|
20
|
-
case 'number':
|
|
21
|
-
return 'number';
|
|
22
|
-
case 'boolean':
|
|
23
|
-
return 'boolean';
|
|
24
|
-
case 'select':
|
|
25
|
-
case 'radio':
|
|
26
|
-
case 'checkbox':
|
|
27
|
-
case 'status':
|
|
28
|
-
return 'number | string';
|
|
29
|
-
case 'file':
|
|
30
|
-
case 'image':
|
|
31
|
-
case 'member':
|
|
32
|
-
case 'members':
|
|
33
|
-
case 'department':
|
|
34
|
-
case 'departments':
|
|
35
|
-
case 'linkto':
|
|
36
|
-
case 'relation':
|
|
37
|
-
case 'relationfield':
|
|
38
|
-
case 'subform':
|
|
39
|
-
return 'unknown';
|
|
40
|
-
default:
|
|
41
|
-
return 'string';
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
function normalizeField(field) {
|
|
45
|
-
if (typeof field.id !== 'number' || typeof field.label !== 'string' || typeof field.type !== 'string') {
|
|
46
|
-
return undefined;
|
|
47
|
-
}
|
|
48
|
-
const children = Array.isArray(field.children)
|
|
49
|
-
? field.children
|
|
50
|
-
.filter((child) => isRecord(child))
|
|
51
|
-
.map(normalizeField)
|
|
52
|
-
.filter((child) => Boolean(child))
|
|
53
|
-
: undefined;
|
|
54
|
-
return {
|
|
55
|
-
id: field.id,
|
|
56
|
-
label: field.label,
|
|
57
|
-
key: typeof field.key === 'string' ? field.key.trim() : '',
|
|
58
|
-
type: field.type,
|
|
59
|
-
required: field.required === true,
|
|
60
|
-
options: isRecord(field.options) ? field.options : undefined,
|
|
61
|
-
children,
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
function normalizeEntity(payload, appId) {
|
|
65
|
-
if (!isRecord(payload) || typeof payload.id !== 'number' || typeof payload.name !== 'string') {
|
|
66
|
-
throw new CLIError('SDK_GEN_INVALID_SCHEMA', 'sdk-gen expected ingress.entity with id and name', 2);
|
|
67
|
-
}
|
|
68
|
-
const fields = Array.isArray(payload.fields)
|
|
69
|
-
? payload.fields
|
|
70
|
-
.filter((field) => isRecord(field))
|
|
71
|
-
.map(normalizeField)
|
|
72
|
-
.filter((field) => Boolean(field))
|
|
73
|
-
: [];
|
|
74
|
-
return {
|
|
75
|
-
appId,
|
|
76
|
-
id: payload.id,
|
|
77
|
-
name: payload.name,
|
|
78
|
-
fields,
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
function normalizeActions(ingress) {
|
|
82
|
-
const policy = isRecord(ingress.policy)
|
|
83
|
-
? ingress.policy
|
|
84
|
-
: isRecord(ingress.options) && isRecord(ingress.options.policy)
|
|
85
|
-
? ingress.options.policy
|
|
86
|
-
: undefined;
|
|
87
|
-
const source = Array.isArray(ingress.actions) ? ingress.actions : Array.isArray(policy?.actions) ? policy.actions : [];
|
|
88
|
-
return source.filter((action) => typeof action === 'string' && action.trim() !== '').map((action) => action.trim());
|
|
89
|
-
}
|
|
90
|
-
function normalizeIngresses(payload) {
|
|
91
|
-
const root = unwrapData(payload);
|
|
92
|
-
if (!isRecord(root)) {
|
|
93
|
-
throw new CLIError('SDK_GEN_INVALID_SCHEMA', 'sdk-gen expected an app ingress schema object', 2);
|
|
94
|
-
}
|
|
95
|
-
const appId = typeof root.id === 'number' ? root.id : typeof root.app_id === 'number' ? root.app_id : undefined;
|
|
96
|
-
if (!appId) {
|
|
97
|
-
throw new CLIError('SDK_GEN_APP_ID_REQUIRED', 'sdk-gen expected app id in schema payload', 2);
|
|
98
|
-
}
|
|
99
|
-
const sourceIngresses = Array.isArray(root.ingresses) ? root.ingresses : [];
|
|
100
|
-
if (sourceIngresses.length === 0) {
|
|
101
|
-
throw new CLIError('SDK_GEN_NO_INGRESSES', 'sdk-gen could not find ingresses in schema payload', 2);
|
|
102
|
-
}
|
|
103
|
-
const ingresses = sourceIngresses
|
|
104
|
-
.filter((item) => isRecord(item))
|
|
105
|
-
.map((item) => {
|
|
106
|
-
const entity = normalizeEntity(item.entity, appId);
|
|
107
|
-
const options = isRecord(item.options) ? item.options : {};
|
|
108
|
-
const hashId = typeof item.hash_id === 'string'
|
|
109
|
-
? item.hash_id.trim()
|
|
110
|
-
: typeof item.hashId === 'string'
|
|
111
|
-
? item.hashId.trim()
|
|
112
|
-
: '';
|
|
113
|
-
return {
|
|
114
|
-
appId,
|
|
115
|
-
id: typeof item.id === 'number' ? item.id : undefined,
|
|
116
|
-
hashId,
|
|
117
|
-
key: typeof item.key === 'string' ? item.key.trim() : '',
|
|
118
|
-
name: typeof item.name === 'string' && item.name.trim() ? item.name.trim() : String(item.key ?? ''),
|
|
119
|
-
actions: normalizeActions(item),
|
|
120
|
-
entity,
|
|
121
|
-
listOptions: isRecord(options.list_options) ? options.list_options : isRecord(item.list_options) ? item.list_options : undefined,
|
|
122
|
-
};
|
|
123
|
-
});
|
|
124
|
-
validateIngresses(ingresses);
|
|
125
|
-
return ingresses;
|
|
126
|
-
}
|
|
127
|
-
function walkFields(fields, visit) {
|
|
128
|
-
for (const field of fields) {
|
|
129
|
-
visit(field);
|
|
130
|
-
if (field.children && field.children.length > 0) {
|
|
131
|
-
walkFields(field.children, visit);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
function allFields(entity) {
|
|
136
|
-
const out = [];
|
|
137
|
-
walkFields(entity.fields, (field) => out.push(field));
|
|
138
|
-
return out;
|
|
139
|
-
}
|
|
140
|
-
function validateIngresses(ingresses) {
|
|
141
|
-
const ingressKeys = new Set();
|
|
142
|
-
for (const ingress of ingresses) {
|
|
143
|
-
if (!ingress.key) {
|
|
144
|
-
throw new CLIError('SDK_GEN_INGRESS_KEY_REQUIRED', `ingress.key is required for ${ingress.name || 'unnamed ingress'}`, 2);
|
|
145
|
-
}
|
|
146
|
-
if (!identifierPattern.test(ingress.key)) {
|
|
147
|
-
throw new CLIError('SDK_GEN_INVALID_INGRESS_KEY', `ingress.key must be a TypeScript identifier: ${ingress.key}`, 2);
|
|
148
|
-
}
|
|
149
|
-
if (ingressKeys.has(ingress.key)) {
|
|
150
|
-
throw new CLIError('SDK_GEN_DUPLICATE_INGRESS_KEY', `duplicate ingress.key: ${ingress.key}`, 2);
|
|
151
|
-
}
|
|
152
|
-
ingressKeys.add(ingress.key);
|
|
153
|
-
if (!ingress.hashId) {
|
|
154
|
-
throw new CLIError('SDK_GEN_INGRESS_HASH_ID_REQUIRED', `ingress.hash_id is required for ${ingress.key}`, 2);
|
|
155
|
-
}
|
|
156
|
-
const fieldKeys = new Set();
|
|
157
|
-
for (const field of allFields(ingress.entity)) {
|
|
158
|
-
if (!field.key) {
|
|
159
|
-
throw new CLIError('SDK_GEN_FIELD_KEY_REQUIRED', `field.key is required for ${ingress.entity.name}.${field.label} (${field.id})`, 2);
|
|
160
|
-
}
|
|
161
|
-
if (!identifierPattern.test(field.key)) {
|
|
162
|
-
throw new CLIError('SDK_GEN_INVALID_FIELD_KEY', `field.key must be a TypeScript identifier: ${ingress.entity.name}.${field.key}`, 2);
|
|
163
|
-
}
|
|
164
|
-
if (fieldKeys.has(field.key)) {
|
|
165
|
-
throw new CLIError('SDK_GEN_DUPLICATE_FIELD_KEY', `duplicate field.key in ${ingress.entity.name}: ${field.key}`, 2);
|
|
166
|
-
}
|
|
167
|
-
fieldKeys.add(field.key);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
function hasAction(ingress, action) {
|
|
172
|
-
return ingress.actions.includes(action);
|
|
173
|
-
}
|
|
174
|
-
function methodLines(lines) {
|
|
175
|
-
return lines.length > 0 ? `${lines.join('\n\n')}\n` : '';
|
|
176
|
-
}
|
|
177
|
-
function emitRuntime() {
|
|
178
|
-
return `export type ArcubaseClientConfig = {
|
|
179
|
-
baseURL: string
|
|
180
|
-
accessToken?: string
|
|
181
|
-
refreshToken?: string
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
export type ArcubaseRow<TFields> = {
|
|
185
|
-
id: number
|
|
186
|
-
title?: string
|
|
187
|
-
fields: TFields
|
|
188
|
-
raw: Record<string, any>
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
export type ArcubaseRuntime = {
|
|
192
|
-
request<T>(method: string, endpoint: string, body?: unknown): Promise<T>
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
type ArcubaseEnvelope<T> = {
|
|
196
|
-
data?: T
|
|
197
|
-
error?: { message?: string; key?: string; type?: string }
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
export function createRuntime(config: ArcubaseClientConfig): ArcubaseRuntime {
|
|
201
|
-
let accessToken = config.accessToken ?? ''
|
|
202
|
-
let refreshToken = config.refreshToken ?? ''
|
|
203
|
-
|
|
204
|
-
async function request<T>(method: string, endpoint: string, body?: unknown): Promise<T> {
|
|
205
|
-
const baseURL = config.baseURL.endsWith('/') ? config.baseURL : \`\${config.baseURL}/\`
|
|
206
|
-
const response = await fetch(new URL(endpoint.replace(/^\\/+/, ''), baseURL).toString(), {
|
|
207
|
-
method,
|
|
208
|
-
headers: {
|
|
209
|
-
'Content-Type': 'application/json',
|
|
210
|
-
...(accessToken ? { Authorization: \`Bearer \${accessToken}\` } : {}),
|
|
211
|
-
...(refreshToken ? { 'X-Arcubase-Refresh-Token': refreshToken } : {}),
|
|
212
|
-
},
|
|
213
|
-
body: body === undefined ? undefined : JSON.stringify(body),
|
|
214
|
-
})
|
|
215
|
-
const payload = await response.json().catch(() => null) as ArcubaseEnvelope<T> | null
|
|
216
|
-
if (!response.ok || payload?.error) {
|
|
217
|
-
throw new Error(payload?.error?.message ?? \`Arcubase request failed: \${response.status}\`)
|
|
218
|
-
}
|
|
219
|
-
return payload?.data as T
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
return { request }
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
export function toArcubaseFields<T extends Record<string, any>>(fields: Partial<T>, fieldIds: Record<string, number>): Record<string, any> {
|
|
226
|
-
const out: Record<string, any> = {}
|
|
227
|
-
for (const [key, value] of Object.entries(fields)) {
|
|
228
|
-
if (value === undefined) continue
|
|
229
|
-
const fieldId = fieldIds[key]
|
|
230
|
-
if (!fieldId) {
|
|
231
|
-
throw new Error(\`Unknown Arcubase field: \${key}\`)
|
|
232
|
-
}
|
|
233
|
-
out[String(fieldId)] = value
|
|
234
|
-
}
|
|
235
|
-
return out
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
export function fromArcubaseFields<T>(raw: Record<string, any> | undefined, fieldIds: Record<string, number>): T {
|
|
239
|
-
const out: Record<string, any> = {}
|
|
240
|
-
const source = raw ?? {}
|
|
241
|
-
for (const [key, fieldId] of Object.entries(fieldIds)) {
|
|
242
|
-
if (String(fieldId) in source) {
|
|
243
|
-
out[key] = source[String(fieldId)]
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
return out as T
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
export function toArcubaseColumn(column: string, fieldIds: Record<string, number>): string {
|
|
250
|
-
return fieldIds[column] ? \`:\${fieldIds[column]}\` : column
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
export function mapArcubaseCondition(value: unknown, fieldIds: Record<string, number>): unknown {
|
|
254
|
-
if (Array.isArray(value)) {
|
|
255
|
-
return value.map((item) => mapArcubaseCondition(item, fieldIds))
|
|
256
|
-
}
|
|
257
|
-
if (!value || typeof value !== 'object') {
|
|
258
|
-
return value
|
|
259
|
-
}
|
|
260
|
-
const out: Record<string, unknown> = {}
|
|
261
|
-
for (const [key, child] of Object.entries(value as Record<string, unknown>)) {
|
|
262
|
-
if (key === 'column' && typeof child === 'string') {
|
|
263
|
-
out[key] = toArcubaseColumn(child, fieldIds)
|
|
264
|
-
} else {
|
|
265
|
-
const mappedKey = fieldIds[key] ? \`:\${fieldIds[key]}\` : key
|
|
266
|
-
out[mappedKey] = mapArcubaseCondition(child, fieldIds)
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
return out
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
export function mapArcubaseSorts<T extends { column: string; order?: string }>(sorts: T[] | undefined, fieldIds: Record<string, number>): T[] | undefined {
|
|
273
|
-
return sorts?.map((sort) => ({ ...sort, column: toArcubaseColumn(sort.column, fieldIds) }))
|
|
274
|
-
}
|
|
275
|
-
`;
|
|
276
|
-
}
|
|
277
|
-
function emitSchema(ingresses) {
|
|
278
|
-
const schema = {
|
|
279
|
-
ingresses: Object.fromEntries(ingresses.map((ingress) => [
|
|
280
|
-
ingress.key,
|
|
281
|
-
{
|
|
282
|
-
appId: ingress.appId,
|
|
283
|
-
ingressId: ingress.hashId,
|
|
284
|
-
entityId: ingress.entity.id,
|
|
285
|
-
name: ingress.name,
|
|
286
|
-
actions: ingress.actions,
|
|
287
|
-
fields: Object.fromEntries(allFields(ingress.entity).map((field) => [
|
|
288
|
-
field.key,
|
|
289
|
-
{
|
|
290
|
-
fieldId: field.id,
|
|
291
|
-
label: field.label,
|
|
292
|
-
type: field.type,
|
|
293
|
-
required: field.required,
|
|
294
|
-
},
|
|
295
|
-
])),
|
|
296
|
-
},
|
|
297
|
-
])),
|
|
298
|
-
};
|
|
299
|
-
return `export const arcubaseSchema = ${JSON.stringify(schema, null, 2)} as const
|
|
300
|
-
|
|
301
|
-
export type ArcubaseIngressKey = keyof typeof arcubaseSchema.ingresses
|
|
302
|
-
`;
|
|
303
|
-
}
|
|
304
|
-
function emitIngress(ingress) {
|
|
305
|
-
const typeName = toPascalCase(ingress.key);
|
|
306
|
-
const fields = allFields(ingress.entity);
|
|
307
|
-
const fieldLines = fields.map((field) => {
|
|
308
|
-
const optional = field.required ? '' : '?';
|
|
309
|
-
return ` ${field.key}${optional}: ${normalizeFieldType(field.type)}`;
|
|
310
|
-
});
|
|
311
|
-
const fieldMapLines = fields.map((field) => ` ${field.key}: ${field.id},`);
|
|
312
|
-
const methodChunks = [];
|
|
313
|
-
if (hasAction(ingress, 'view')) {
|
|
314
|
-
methodChunks.push(` async query(params: ${typeName}QueryParams = {}): Promise<{ items: ArcubaseRow<${typeName}Fields>[]; total?: number; offset?: number; limit?: number }> {
|
|
315
|
-
const data = await runtime.request<{ items?: Record<string, any>[]; total?: number; offset?: number; limit?: number }>('POST', \`/entity/${ingress.appId}/${ingress.entity.id}/data-query\`, {
|
|
316
|
-
...params,
|
|
317
|
-
policy_id: ingressId,
|
|
318
|
-
search: mapArcubaseCondition(params.search, fieldIds),
|
|
319
|
-
sorts: mapArcubaseSorts(params.sorts, fieldIds),
|
|
320
|
-
})
|
|
321
|
-
return {
|
|
322
|
-
items: (data.items ?? []).map(mapRow),
|
|
323
|
-
total: data.total,
|
|
324
|
-
offset: data.offset,
|
|
325
|
-
limit: data.limit,
|
|
326
|
-
}
|
|
327
|
-
},
|
|
328
|
-
|
|
329
|
-
async get(rowId: string | number): Promise<ArcubaseRow<${typeName}Fields>> {
|
|
330
|
-
const data = await runtime.request<{ record?: Record<string, any> }>('GET', \`/entity/${ingress.appId}/${ingress.entity.id}/row/\${rowId}?policy_id=\${encodeURIComponent(ingressId)}\`)
|
|
331
|
-
return mapRow(data.record ?? {})
|
|
332
|
-
},`);
|
|
333
|
-
}
|
|
334
|
-
if (hasAction(ingress, 'add')) {
|
|
335
|
-
methodChunks.push(` async prepareCreate(): Promise<Record<string, any>> {
|
|
336
|
-
return runtime.request<Record<string, any>>('POST', \`/entity/${ingress.appId}/${ingress.entity.id}/prepare\`, {
|
|
337
|
-
policy_id: ingressId,
|
|
338
|
-
})
|
|
339
|
-
},
|
|
340
|
-
|
|
341
|
-
async create(fields: ${typeName}CreateInput): Promise<number> {
|
|
342
|
-
return runtime.request<number>('POST', \`/entity/${ingress.appId}/${ingress.entity.id}/insert\`, {
|
|
343
|
-
fields: toArcubaseFields(fields, fieldIds),
|
|
344
|
-
policy_id: ingressId,
|
|
345
|
-
})
|
|
346
|
-
},`);
|
|
347
|
-
}
|
|
348
|
-
if (hasAction(ingress, 'edit')) {
|
|
349
|
-
methodChunks.push(` async update(rowId: string | number, fields: ${typeName}UpdateInput): Promise<boolean> {
|
|
350
|
-
const data = toArcubaseFields(fields, fieldIds)
|
|
351
|
-
return runtime.request<boolean>('PUT', \`/entity/${ingress.appId}/${ingress.entity.id}/row/\${rowId}\`, {
|
|
352
|
-
data,
|
|
353
|
-
changed_fields: Object.keys(data).map((fieldId) => Number(fieldId)),
|
|
354
|
-
policy_id: ingressId,
|
|
355
|
-
})
|
|
356
|
-
},`);
|
|
357
|
-
}
|
|
358
|
-
if (hasAction(ingress, 'delete')) {
|
|
359
|
-
methodChunks.push(` async delete(selection: ${typeName}Selection): Promise<{ deleted?: number } | boolean> {
|
|
360
|
-
return runtime.request<{ deleted?: number } | boolean>('POST', \`/entity/${ingress.appId}/${ingress.entity.id}/delete-item\`, {
|
|
361
|
-
policy_id: ingressId,
|
|
362
|
-
selection: toArcubaseSelection(selection),
|
|
363
|
-
})
|
|
364
|
-
},`);
|
|
365
|
-
}
|
|
366
|
-
if (hasAction(ingress, 'bulk_update')) {
|
|
367
|
-
methodChunks.push(` async bulkUpdate(selection: ${typeName}Selection, fields: ${typeName}BulkUpdateField[]): Promise<{ updated: number }> {
|
|
368
|
-
return runtime.request<{ updated: number }>('POST', \`/entity/${ingress.appId}/${ingress.entity.id}/bulk-update\`, {
|
|
369
|
-
policy_id: ingressId,
|
|
370
|
-
selection: toArcubaseSelection(selection),
|
|
371
|
-
fields: fields.map((field) => ({
|
|
372
|
-
id: fieldIds[field.key],
|
|
373
|
-
action: field.action,
|
|
374
|
-
})),
|
|
375
|
-
})
|
|
376
|
-
},
|
|
377
|
-
|
|
378
|
-
async saveBulkUpdate(input: { name: string; subject: string; fields: ${typeName}BulkUpdateField[] }): Promise<string> {
|
|
379
|
-
return runtime.request<string>('POST', \`/entity/${ingress.appId}/${ingress.entity.id}/bulk-update-save-as\`, {
|
|
380
|
-
name: input.name,
|
|
381
|
-
subject: input.subject,
|
|
382
|
-
fields: input.fields.map((field) => ({
|
|
383
|
-
id: fieldIds[field.key],
|
|
384
|
-
action: field.action,
|
|
385
|
-
})),
|
|
386
|
-
})
|
|
387
|
-
},`);
|
|
388
|
-
}
|
|
389
|
-
if (hasAction(ingress, 'export')) {
|
|
390
|
-
methodChunks.push(` async exportRows(options: ${typeName}ExportOptions = {}): Promise<{ task_id: string }> {
|
|
391
|
-
const fieldKeys = options.fields ?? Object.keys(fieldIds) as ${typeName}FieldKey[]
|
|
392
|
-
return runtime.request<{ task_id: string }>('POST', \`/entity/${ingress.appId}/${ingress.entity.id}/export\`, {
|
|
393
|
-
policy_id: ingressId,
|
|
394
|
-
props: options.props ?? [],
|
|
395
|
-
fields: fieldKeys.map((key) => fieldIds[key]),
|
|
396
|
-
propnames: options.propNames ?? {},
|
|
397
|
-
options: { has_id: options.hasIdField ?? false },
|
|
398
|
-
})
|
|
399
|
-
},
|
|
400
|
-
|
|
401
|
-
async exportTask(taskId: string): Promise<Record<string, any>> {
|
|
402
|
-
return runtime.request<Record<string, any>>('GET', \`/export-task/\${taskId}\`)
|
|
403
|
-
},`);
|
|
404
|
-
}
|
|
405
|
-
if (hasAction(ingress, 'batch_print')) {
|
|
406
|
-
methodChunks.push(` async batchPrint(selection: ${typeName}Selection, params: { limit?: number; offset?: number } = {}): Promise<{ items?: Record<string, any>[]; total?: number }> {
|
|
407
|
-
return runtime.request<{ items?: Record<string, any>[]; total?: number }>('POST', \`/entity/${ingress.appId}/${ingress.entity.id}/selection-query/batch_print\`, {
|
|
408
|
-
...params,
|
|
409
|
-
policy_id: ingressId,
|
|
410
|
-
selection: toArcubaseSelection(selection),
|
|
411
|
-
})
|
|
412
|
-
},`);
|
|
413
|
-
}
|
|
414
|
-
if (hasAction(ingress, 'archive')) {
|
|
415
|
-
methodChunks.push(` async archive(selection: ${typeName}Selection): Promise<{ archived?: number } | boolean> {
|
|
416
|
-
return runtime.request<{ archived?: number } | boolean>('POST', \`/entity/${ingress.appId}/${ingress.entity.id}/selection-query/archive\`, {
|
|
417
|
-
policy_id: ingressId,
|
|
418
|
-
selection: toArcubaseSelection(selection),
|
|
419
|
-
})
|
|
420
|
-
},`);
|
|
421
|
-
}
|
|
422
|
-
if (hasAction(ingress, 'tag_manage')) {
|
|
423
|
-
methodChunks.push(` tags: {
|
|
424
|
-
async list(): Promise<string[]> {
|
|
425
|
-
return runtime.request<string[]>('GET', \`/user-action-entity/${ingress.appId}/${ingress.entity.id}/tag-names\`)
|
|
426
|
-
},
|
|
427
|
-
async add(selection: ${typeName}Selection, tagNames: string[]): Promise<Record<string, any>> {
|
|
428
|
-
return runtime.request<Record<string, any>>('POST', \`/user-action-entity/${ingress.appId}/${ingress.entity.id}/batch-add-tags\`, {
|
|
429
|
-
policy_id: ingressId,
|
|
430
|
-
selection: toArcubaseSelection(selection),
|
|
431
|
-
tag_names: tagNames,
|
|
432
|
-
})
|
|
433
|
-
},
|
|
434
|
-
async remove(selection: ${typeName}Selection, tagNames: string[]): Promise<boolean> {
|
|
435
|
-
return runtime.request<boolean>('POST', \`/user-action-entity/${ingress.appId}/${ingress.entity.id}/batch-remove-tags\`, {
|
|
436
|
-
policy_id: ingressId,
|
|
437
|
-
selection: toArcubaseSelection(selection),
|
|
438
|
-
tag_names: tagNames,
|
|
439
|
-
})
|
|
440
|
-
},
|
|
441
|
-
async selection(selection: ${typeName}Selection): Promise<string[]> {
|
|
442
|
-
return runtime.request<string[]>('POST', \`/user-action-entity/${ingress.appId}/${ingress.entity.id}/get-selection-tags\`, {
|
|
443
|
-
policy_id: ingressId,
|
|
444
|
-
selection: toArcubaseSelection(selection),
|
|
445
|
-
})
|
|
446
|
-
},
|
|
447
|
-
},`);
|
|
448
|
-
}
|
|
449
|
-
const customActions = ingress.actions.filter((action) => !['view', 'add', 'edit', 'delete', 'bulk_update', 'export', 'batch_print', 'archive', 'tag_manage'].includes(action));
|
|
450
|
-
if (customActions.length > 0) {
|
|
451
|
-
methodChunks.push(` async action(action: ${typeName}CustomAction, payload: Record<string, any> = {}): Promise<Record<string, any>> {
|
|
452
|
-
if (!allowedActions.includes(action)) {
|
|
453
|
-
throw new Error(\`Action is not allowed for ${ingress.key}: \${action}\`)
|
|
454
|
-
}
|
|
455
|
-
return runtime.request<Record<string, any>>('POST', \`/entity/${ingress.appId}/${ingress.entity.id}/selection-query/\${encodeURIComponent(action)}\`, {
|
|
456
|
-
...payload,
|
|
457
|
-
policy_id: ingressId,
|
|
458
|
-
selection: payload.selection ? toArcubaseSelection(payload.selection) : undefined,
|
|
459
|
-
})
|
|
460
|
-
},`);
|
|
461
|
-
}
|
|
462
|
-
return `import {
|
|
463
|
-
fromArcubaseFields,
|
|
464
|
-
mapArcubaseCondition,
|
|
465
|
-
mapArcubaseSorts,
|
|
466
|
-
toArcubaseFields,
|
|
467
|
-
type ArcubaseRow,
|
|
468
|
-
type ArcubaseRuntime,
|
|
469
|
-
} from '../runtime.js'
|
|
470
|
-
|
|
471
|
-
export type ${typeName}FieldKey = ${fields.map((field) => `'${field.key}'`).join(' | ')}
|
|
472
|
-
|
|
473
|
-
export type ${typeName}Fields = {
|
|
474
|
-
${fieldLines.join('\n')}
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
export type ${typeName}CreateInput = ${typeName}Fields
|
|
478
|
-
export type ${typeName}UpdateInput = Partial<${typeName}Fields>
|
|
479
|
-
export type ${typeName}Action = ${ingress.actions.map((action) => `'${action}'`).join(' | ') || 'never'}
|
|
480
|
-
export type ${typeName}CustomAction = ${customActions.map((action) => `'${action}'`).join(' | ') || 'never'}
|
|
481
|
-
|
|
482
|
-
export type ${typeName}Sort = {
|
|
483
|
-
column: ${typeName}FieldKey | 'id' | 'created_at' | 'updated_at' | 'creator' | string
|
|
484
|
-
order: 'asc' | 'desc'
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
export type ${typeName}QueryParams = {
|
|
488
|
-
limit?: number
|
|
489
|
-
offset?: number
|
|
490
|
-
search?: Record<string, any>
|
|
491
|
-
sorts?: ${typeName}Sort[]
|
|
492
|
-
view_mode?: 'normal' | 'deleted' | 'archived'
|
|
493
|
-
withSubForm?: boolean
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
export type ${typeName}Selection = {
|
|
497
|
-
type: 'ids' | 'condition'
|
|
498
|
-
length?: number
|
|
499
|
-
ids?: Array<number | string>
|
|
500
|
-
condition?: Record<string, any>
|
|
501
|
-
sorts?: ${typeName}Sort[]
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
export type ${typeName}BulkUpdateField = {
|
|
505
|
-
key: ${typeName}FieldKey
|
|
506
|
-
action: Record<string, any>
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
export type ${typeName}ExportOptions = {
|
|
510
|
-
fields?: ${typeName}FieldKey[]
|
|
511
|
-
props?: string[]
|
|
512
|
-
propNames?: Record<string, string>
|
|
513
|
-
hasIdField?: boolean
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
const ingressId = ${JSON.stringify(ingress.hashId)}
|
|
517
|
-
const fieldIds = {
|
|
518
|
-
${fieldMapLines.join('\n')}
|
|
519
|
-
} as const
|
|
520
|
-
const allowedActions = ${JSON.stringify(customActions)} as const
|
|
521
|
-
|
|
522
|
-
function mapRow(raw: Record<string, any>): ArcubaseRow<${typeName}Fields> {
|
|
523
|
-
return {
|
|
524
|
-
id: Number(raw.id),
|
|
525
|
-
title: typeof raw.title === 'string' ? raw.title : undefined,
|
|
526
|
-
fields: fromArcubaseFields<${typeName}Fields>(raw.fields, fieldIds),
|
|
527
|
-
raw,
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
function toArcubaseSelection(selection: ${typeName}Selection): Record<string, any> {
|
|
532
|
-
return {
|
|
533
|
-
...selection,
|
|
534
|
-
condition: mapArcubaseCondition(selection.condition ?? {}, fieldIds),
|
|
535
|
-
sorts: mapArcubaseSorts(selection.sorts, fieldIds),
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
export function create${typeName}Ingress(runtime: ArcubaseRuntime) {
|
|
540
|
-
return {
|
|
541
|
-
${methodLines(methodChunks)} }
|
|
542
|
-
}
|
|
543
|
-
`;
|
|
544
|
-
}
|
|
545
|
-
function emitClient(ingresses) {
|
|
546
|
-
const imports = ingresses.map((ingress) => {
|
|
547
|
-
const typeName = toPascalCase(ingress.key);
|
|
548
|
-
return `import { create${typeName}Ingress } from './ingresses/${ingress.key}.js'`;
|
|
549
|
-
});
|
|
550
|
-
const mapLines = ingresses.map((ingress) => ` ${JSON.stringify(ingress.key)}: ReturnType<typeof create${toPascalCase(ingress.key)}Ingress>`);
|
|
551
|
-
const cases = ingresses.map((ingress) => ` case ${JSON.stringify(ingress.key)}:
|
|
552
|
-
return create${toPascalCase(ingress.key)}Ingress(runtime) as ArcubaseIngressMap[K]`);
|
|
553
|
-
return `import { createRuntime, type ArcubaseClientConfig } from './runtime.js'
|
|
554
|
-
${imports.join('\n')}
|
|
555
|
-
|
|
556
|
-
export type ArcubaseIngressMap = {
|
|
557
|
-
${mapLines.join('\n')}
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
export type ArcubaseIngressKey = keyof ArcubaseIngressMap
|
|
561
|
-
|
|
562
|
-
export function createArcubaseClient(config: ArcubaseClientConfig) {
|
|
563
|
-
const runtime = createRuntime(config)
|
|
564
|
-
return {
|
|
565
|
-
ingress<K extends ArcubaseIngressKey>(key: K): ArcubaseIngressMap[K] {
|
|
566
|
-
switch (key) {
|
|
567
|
-
${cases.join('\n')}
|
|
568
|
-
default:
|
|
569
|
-
throw new Error(\`Unknown Arcubase ingress: \${String(key)}\`)
|
|
570
|
-
}
|
|
571
|
-
},
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
`;
|
|
575
|
-
}
|
|
576
|
-
function emitIndex(ingresses) {
|
|
577
|
-
return `export { createArcubaseClient } from './client.js'
|
|
578
|
-
export type { ArcubaseClientConfig, ArcubaseRow } from './runtime.js'
|
|
579
|
-
export { arcubaseSchema } from './schema.js'
|
|
580
|
-
export type { ArcubaseIngressKey, ArcubaseIngressMap } from './client.js'
|
|
581
|
-
${ingresses.map((ingress) => {
|
|
582
|
-
const typeName = toPascalCase(ingress.key);
|
|
583
|
-
return `export type { ${typeName}Fields, ${typeName}CreateInput, ${typeName}UpdateInput, ${typeName}FieldKey, ${typeName}Selection } from './ingresses/${ingress.key}.js'`;
|
|
584
|
-
}).join('\n')}
|
|
585
|
-
`;
|
|
586
|
-
}
|
|
587
|
-
export function generateArcubaseProjectSDK(payload) {
|
|
588
|
-
const ingresses = normalizeIngresses(payload);
|
|
589
|
-
return {
|
|
590
|
-
files: [
|
|
591
|
-
{ path: 'runtime.ts', contents: emitRuntime() },
|
|
592
|
-
{ path: 'schema.ts', contents: emitSchema(ingresses) },
|
|
593
|
-
...ingresses.map((ingress) => ({ path: `ingresses/${ingress.key}.ts`, contents: emitIngress(ingress) })),
|
|
594
|
-
{ path: 'client.ts', contents: emitClient(ingresses) },
|
|
595
|
-
{ path: 'index.ts', contents: emitIndex(ingresses) },
|
|
596
|
-
],
|
|
597
|
-
};
|
|
598
|
-
}
|