@carthooks/arcubase-cli 0.1.25 → 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.
@@ -9,6 +9,592 @@ var __export = (target, all) => {
9
9
  import fs3 from "fs";
10
10
  import path2 from "path";
11
11
 
12
+ // node_modules/@arcubase/code-gen/dist/index.js
13
+ var ArcubaseCodeGenError = class extends Error {
14
+ code;
15
+ constructor(code, message) {
16
+ super(message);
17
+ this.name = "ArcubaseCodeGenError";
18
+ this.code = code;
19
+ }
20
+ };
21
+ var identifierPattern = /^[A-Za-z][A-Za-z0-9_]*$/;
22
+ function isRecord(value) {
23
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
24
+ }
25
+ function unwrapData(payload) {
26
+ return isRecord(payload) && "data" in payload ? payload.data : payload;
27
+ }
28
+ function toPascalCase(value) {
29
+ const words = value.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[^A-Za-z0-9]+/).filter(Boolean);
30
+ if (words.length === 0)
31
+ return "Ingress";
32
+ return words.map((word) => `${word[0].toUpperCase()}${word.slice(1)}`).join("");
33
+ }
34
+ function normalizeFieldType(type) {
35
+ switch (type) {
36
+ case "number":
37
+ return "number";
38
+ case "boolean":
39
+ return "boolean";
40
+ case "select":
41
+ case "radio":
42
+ case "checkbox":
43
+ case "status":
44
+ return "number | string";
45
+ case "file":
46
+ case "image":
47
+ case "member":
48
+ case "members":
49
+ case "department":
50
+ case "departments":
51
+ case "linkto":
52
+ case "relation":
53
+ case "relationfield":
54
+ case "subform":
55
+ return "unknown";
56
+ default:
57
+ return "string";
58
+ }
59
+ }
60
+ function normalizeField(field) {
61
+ if (typeof field.id !== "number" || typeof field.label !== "string" || typeof field.type !== "string") {
62
+ return void 0;
63
+ }
64
+ const children = Array.isArray(field.children) ? field.children.filter((child) => isRecord(child)).map(normalizeField).filter((child) => Boolean(child)) : void 0;
65
+ return {
66
+ id: field.id,
67
+ label: field.label,
68
+ key: typeof field.key === "string" ? field.key.trim() : "",
69
+ type: field.type,
70
+ required: field.required === true,
71
+ options: isRecord(field.options) ? field.options : void 0,
72
+ children
73
+ };
74
+ }
75
+ function normalizeEntity(payload, appId) {
76
+ if (!isRecord(payload) || typeof payload.id !== "number" || typeof payload.name !== "string") {
77
+ throw new ArcubaseCodeGenError("SDK_GEN_INVALID_SCHEMA", "sdk-gen expected ingress.entity with id and name");
78
+ }
79
+ const fields = Array.isArray(payload.fields) ? payload.fields.filter((field) => isRecord(field)).map(normalizeField).filter((field) => Boolean(field)) : [];
80
+ return {
81
+ appId,
82
+ id: payload.id,
83
+ name: payload.name,
84
+ fields
85
+ };
86
+ }
87
+ function normalizeActions(ingress) {
88
+ const policy = isRecord(ingress.policy) ? ingress.policy : isRecord(ingress.options) && isRecord(ingress.options.policy) ? ingress.options.policy : void 0;
89
+ const source = Array.isArray(ingress.actions) ? ingress.actions : Array.isArray(policy?.actions) ? policy.actions : [];
90
+ return source.filter((action) => typeof action === "string" && action.trim() !== "").map((action) => action.trim());
91
+ }
92
+ function normalizeIngresses(payload) {
93
+ const root = unwrapData(payload);
94
+ if (!isRecord(root)) {
95
+ throw new ArcubaseCodeGenError("SDK_GEN_INVALID_SCHEMA", "sdk-gen expected an app ingress schema object");
96
+ }
97
+ const appId = typeof root.id === "number" ? root.id : typeof root.app_id === "number" ? root.app_id : void 0;
98
+ if (!appId) {
99
+ throw new ArcubaseCodeGenError("SDK_GEN_APP_ID_REQUIRED", "sdk-gen expected app id in schema payload");
100
+ }
101
+ const sourceIngresses = Array.isArray(root.ingresses) ? root.ingresses : [];
102
+ if (sourceIngresses.length === 0) {
103
+ throw new ArcubaseCodeGenError("SDK_GEN_NO_INGRESSES", "sdk-gen could not find ingresses in schema payload");
104
+ }
105
+ const ingresses = sourceIngresses.filter((item) => isRecord(item)).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" ? item.hash_id.trim() : typeof item.hashId === "string" ? item.hashId.trim() : "";
109
+ return {
110
+ appId,
111
+ id: typeof item.id === "number" ? item.id : void 0,
112
+ hashId,
113
+ key: typeof item.key === "string" ? item.key.trim() : "",
114
+ name: typeof item.name === "string" && item.name.trim() ? item.name.trim() : String(item.key ?? ""),
115
+ actions: normalizeActions(item),
116
+ entity,
117
+ listOptions: isRecord(options.list_options) ? options.list_options : isRecord(item.list_options) ? item.list_options : void 0
118
+ };
119
+ });
120
+ validateIngresses(ingresses);
121
+ return ingresses;
122
+ }
123
+ function walkFields(fields, visit) {
124
+ for (const field of fields) {
125
+ visit(field);
126
+ if (field.children && field.children.length > 0) {
127
+ walkFields(field.children, visit);
128
+ }
129
+ }
130
+ }
131
+ function allFields(entity) {
132
+ const out = [];
133
+ walkFields(entity.fields, (field) => out.push(field));
134
+ return out;
135
+ }
136
+ function validateIngresses(ingresses) {
137
+ const ingressKeys = /* @__PURE__ */ new Set();
138
+ for (const ingress of ingresses) {
139
+ if (!ingress.key) {
140
+ throw new ArcubaseCodeGenError("SDK_GEN_INGRESS_KEY_REQUIRED", `ingress.key is required for ${ingress.name || "unnamed ingress"}`);
141
+ }
142
+ if (!identifierPattern.test(ingress.key)) {
143
+ throw new ArcubaseCodeGenError("SDK_GEN_INVALID_INGRESS_KEY", `ingress.key must be a TypeScript identifier: ${ingress.key}`);
144
+ }
145
+ if (ingressKeys.has(ingress.key)) {
146
+ throw new ArcubaseCodeGenError("SDK_GEN_DUPLICATE_INGRESS_KEY", `duplicate ingress.key: ${ingress.key}`);
147
+ }
148
+ ingressKeys.add(ingress.key);
149
+ if (!ingress.hashId) {
150
+ throw new ArcubaseCodeGenError("SDK_GEN_INGRESS_HASH_ID_REQUIRED", `ingress.hash_id is required for ${ingress.key}`);
151
+ }
152
+ const fieldKeys = /* @__PURE__ */ new Set();
153
+ for (const field of allFields(ingress.entity)) {
154
+ if (!field.key) {
155
+ throw new ArcubaseCodeGenError("SDK_GEN_FIELD_KEY_REQUIRED", `field.key is required for ${ingress.entity.name}.${field.label} (${field.id})`);
156
+ }
157
+ if (!identifierPattern.test(field.key)) {
158
+ throw new ArcubaseCodeGenError("SDK_GEN_INVALID_FIELD_KEY", `field.key must be a TypeScript identifier: ${ingress.entity.name}.${field.key}`);
159
+ }
160
+ if (fieldKeys.has(field.key)) {
161
+ throw new ArcubaseCodeGenError("SDK_GEN_DUPLICATE_FIELD_KEY", `duplicate field.key in ${ingress.entity.name}: ${field.key}`);
162
+ }
163
+ fieldKeys.add(field.key);
164
+ }
165
+ }
166
+ }
167
+ function hasAction(ingress, action) {
168
+ return ingress.actions.includes(action);
169
+ }
170
+ function methodLines(lines) {
171
+ return lines.length > 0 ? `${lines.join("\n\n")}
172
+ ` : "";
173
+ }
174
+ function emitRuntime() {
175
+ return `export type ArcubaseClientConfig = {
176
+ baseURL: string
177
+ accessToken?: string
178
+ refreshToken?: string
179
+ }
180
+
181
+ export type ArcubaseRow<TFields> = {
182
+ id: number
183
+ title?: string
184
+ fields: TFields
185
+ raw: Record<string, any>
186
+ }
187
+
188
+ export type ArcubaseRuntime = {
189
+ request<T>(method: string, endpoint: string, body?: unknown): Promise<T>
190
+ }
191
+
192
+ type ArcubaseEnvelope<T> = {
193
+ data?: T
194
+ error?: { message?: string; key?: string; type?: string }
195
+ }
196
+
197
+ export function createRuntime(config: ArcubaseClientConfig): ArcubaseRuntime {
198
+ let accessToken = config.accessToken ?? ''
199
+ let refreshToken = config.refreshToken ?? ''
200
+
201
+ async function request<T>(method: string, endpoint: string, body?: unknown): Promise<T> {
202
+ const baseURL = config.baseURL.endsWith('/') ? config.baseURL : \`\${config.baseURL}/\`
203
+ const response = await fetch(new URL(endpoint.replace(/^\\/+/, ''), baseURL).toString(), {
204
+ method,
205
+ headers: {
206
+ 'Content-Type': 'application/json',
207
+ ...(accessToken ? { Authorization: \`Bearer \${accessToken}\` } : {}),
208
+ ...(refreshToken ? { 'X-Arcubase-Refresh-Token': refreshToken } : {}),
209
+ },
210
+ body: body === undefined ? undefined : JSON.stringify(body),
211
+ })
212
+ const payload = await response.json().catch(() => null) as ArcubaseEnvelope<T> | null
213
+ if (!response.ok || payload?.error) {
214
+ throw new Error(payload?.error?.message ?? \`Arcubase request failed: \${response.status}\`)
215
+ }
216
+ return payload?.data as T
217
+ }
218
+
219
+ return { request }
220
+ }
221
+
222
+ export function toArcubaseFields<T extends Record<string, any>>(fields: Partial<T>, fieldIds: Record<string, number>): Record<string, any> {
223
+ const out: Record<string, any> = {}
224
+ for (const [key, value] of Object.entries(fields)) {
225
+ if (value === undefined) continue
226
+ const fieldId = fieldIds[key]
227
+ if (!fieldId) {
228
+ throw new Error(\`Unknown Arcubase field: \${key}\`)
229
+ }
230
+ out[String(fieldId)] = value
231
+ }
232
+ return out
233
+ }
234
+
235
+ export function fromArcubaseFields<T>(raw: Record<string, any> | undefined, fieldIds: Record<string, number>): T {
236
+ const out: Record<string, any> = {}
237
+ const source = raw ?? {}
238
+ for (const [key, fieldId] of Object.entries(fieldIds)) {
239
+ if (String(fieldId) in source) {
240
+ out[key] = source[String(fieldId)]
241
+ }
242
+ }
243
+ return out as T
244
+ }
245
+
246
+ export function toArcubaseColumn(column: string, fieldIds: Record<string, number>): string {
247
+ return fieldIds[column] ? \`:\${fieldIds[column]}\` : column
248
+ }
249
+
250
+ export function mapArcubaseCondition(value: unknown, fieldIds: Record<string, number>): unknown {
251
+ if (Array.isArray(value)) {
252
+ return value.map((item) => mapArcubaseCondition(item, fieldIds))
253
+ }
254
+ if (!value || typeof value !== 'object') {
255
+ return value
256
+ }
257
+ const out: Record<string, unknown> = {}
258
+ for (const [key, child] of Object.entries(value as Record<string, unknown>)) {
259
+ if (key === 'column' && typeof child === 'string') {
260
+ out[key] = toArcubaseColumn(child, fieldIds)
261
+ } else {
262
+ const mappedKey = fieldIds[key] ? \`:\${fieldIds[key]}\` : key
263
+ out[mappedKey] = mapArcubaseCondition(child, fieldIds)
264
+ }
265
+ }
266
+ return out
267
+ }
268
+
269
+ export function mapArcubaseSorts<T extends { column: string; order?: string }>(sorts: T[] | undefined, fieldIds: Record<string, number>): T[] | undefined {
270
+ return sorts?.map((sort) => ({ ...sort, column: toArcubaseColumn(sort.column, fieldIds) }))
271
+ }
272
+ `;
273
+ }
274
+ function emitSchema(ingresses) {
275
+ const schema = {
276
+ ingresses: Object.fromEntries(ingresses.map((ingress) => [
277
+ ingress.key,
278
+ {
279
+ appId: ingress.appId,
280
+ ingressId: ingress.hashId,
281
+ entityId: ingress.entity.id,
282
+ name: ingress.name,
283
+ actions: ingress.actions,
284
+ fields: Object.fromEntries(allFields(ingress.entity).map((field) => [
285
+ field.key,
286
+ {
287
+ fieldId: field.id,
288
+ label: field.label,
289
+ type: field.type,
290
+ required: field.required
291
+ }
292
+ ]))
293
+ }
294
+ ]))
295
+ };
296
+ return `export const arcubaseSchema = ${JSON.stringify(schema, null, 2)} as const
297
+
298
+ export type ArcubaseIngressKey = keyof typeof arcubaseSchema.ingresses
299
+ `;
300
+ }
301
+ function emitIngress(ingress) {
302
+ const typeName = toPascalCase(ingress.key);
303
+ const fields = allFields(ingress.entity);
304
+ const fieldKeyUnion = fields.length > 0 ? fields.map((field) => `'${field.key}'`).join(" | ") : "never";
305
+ const fieldLines = fields.map((field) => {
306
+ const optional2 = field.required ? "" : "?";
307
+ return ` ${field.key}${optional2}: ${normalizeFieldType(field.type)}`;
308
+ });
309
+ const fieldMapLines = fields.map((field) => ` ${field.key}: ${field.id},`);
310
+ const methodChunks = [];
311
+ if (hasAction(ingress, "view")) {
312
+ methodChunks.push(` async query(params: ${typeName}QueryParams = {}): Promise<{ items: ArcubaseRow<${typeName}Fields>[]; total?: number; offset?: number; limit?: number }> {
313
+ const data = await runtime.request<{ items?: Record<string, any>[]; total?: number; offset?: number; limit?: number }>('POST', \`/entity/${ingress.appId}/${ingress.entity.id}/data-query\`, {
314
+ ...params,
315
+ policy_id: ingressId,
316
+ search: mapArcubaseCondition(params.search, fieldIds),
317
+ sorts: mapArcubaseSorts(params.sorts, fieldIds),
318
+ })
319
+ return {
320
+ items: (data.items ?? []).map(mapRow),
321
+ total: data.total,
322
+ offset: data.offset,
323
+ limit: data.limit,
324
+ }
325
+ },
326
+
327
+ async get(rowId: string | number): Promise<ArcubaseRow<${typeName}Fields>> {
328
+ const data = await runtime.request<{ record?: Record<string, any> }>('GET', \`/entity/${ingress.appId}/${ingress.entity.id}/row/\${rowId}?policy_id=\${encodeURIComponent(ingressId)}\`)
329
+ return mapRow(data.record ?? {})
330
+ },`);
331
+ }
332
+ if (hasAction(ingress, "add")) {
333
+ methodChunks.push(` async prepareCreate(): Promise<Record<string, any>> {
334
+ return runtime.request<Record<string, any>>('POST', \`/entity/${ingress.appId}/${ingress.entity.id}/prepare\`, {
335
+ policy_id: ingressId,
336
+ })
337
+ },
338
+
339
+ async create(fields: ${typeName}CreateInput): Promise<number> {
340
+ return runtime.request<number>('POST', \`/entity/${ingress.appId}/${ingress.entity.id}/insert\`, {
341
+ fields: toArcubaseFields(fields, fieldIds),
342
+ policy_id: ingressId,
343
+ })
344
+ },`);
345
+ }
346
+ if (hasAction(ingress, "edit")) {
347
+ methodChunks.push(` async update(rowId: string | number, fields: ${typeName}UpdateInput): Promise<boolean> {
348
+ const data = toArcubaseFields(fields, fieldIds)
349
+ return runtime.request<boolean>('PUT', \`/entity/${ingress.appId}/${ingress.entity.id}/row/\${rowId}\`, {
350
+ data,
351
+ changed_fields: Object.keys(data).map((fieldId) => Number(fieldId)),
352
+ policy_id: ingressId,
353
+ })
354
+ },`);
355
+ }
356
+ if (hasAction(ingress, "delete")) {
357
+ methodChunks.push(` async delete(selection: ${typeName}Selection): Promise<{ deleted?: number } | boolean> {
358
+ return runtime.request<{ deleted?: number } | boolean>('POST', \`/entity/${ingress.appId}/${ingress.entity.id}/delete-item\`, {
359
+ policy_id: ingressId,
360
+ selection: toArcubaseSelection(selection),
361
+ })
362
+ },`);
363
+ }
364
+ if (hasAction(ingress, "bulk_update")) {
365
+ methodChunks.push(` async bulkUpdate(selection: ${typeName}Selection, fields: ${typeName}BulkUpdateField[]): Promise<{ updated: number }> {
366
+ return runtime.request<{ updated: number }>('POST', \`/entity/${ingress.appId}/${ingress.entity.id}/bulk-update\`, {
367
+ policy_id: ingressId,
368
+ selection: toArcubaseSelection(selection),
369
+ fields: fields.map((field) => ({
370
+ id: fieldIds[field.key],
371
+ action: field.action,
372
+ })),
373
+ })
374
+ },
375
+
376
+ async saveBulkUpdate(input: { name: string; subject: string; fields: ${typeName}BulkUpdateField[] }): Promise<string> {
377
+ return runtime.request<string>('POST', \`/entity/${ingress.appId}/${ingress.entity.id}/bulk-update-save-as\`, {
378
+ name: input.name,
379
+ subject: input.subject,
380
+ fields: input.fields.map((field) => ({
381
+ id: fieldIds[field.key],
382
+ action: field.action,
383
+ })),
384
+ })
385
+ },`);
386
+ }
387
+ if (hasAction(ingress, "export")) {
388
+ methodChunks.push(` async exportRows(options: ${typeName}ExportOptions = {}): Promise<{ task_id: string }> {
389
+ const fieldKeys = options.fields ?? Object.keys(fieldIds) as ${typeName}FieldKey[]
390
+ return runtime.request<{ task_id: string }>('POST', \`/entity/${ingress.appId}/${ingress.entity.id}/export\`, {
391
+ policy_id: ingressId,
392
+ props: options.props ?? [],
393
+ fields: fieldKeys.map((key) => fieldIds[key]),
394
+ propnames: options.propNames ?? {},
395
+ options: { has_id: options.hasIdField ?? false },
396
+ })
397
+ },
398
+
399
+ async exportTask(taskId: string): Promise<Record<string, any>> {
400
+ return runtime.request<Record<string, any>>('GET', \`/export-task/\${taskId}\`)
401
+ },`);
402
+ }
403
+ if (hasAction(ingress, "batch_print")) {
404
+ methodChunks.push(` async batchPrint(selection: ${typeName}Selection, params: { limit?: number; offset?: number } = {}): Promise<{ items?: Record<string, any>[]; total?: number }> {
405
+ return runtime.request<{ items?: Record<string, any>[]; total?: number }>('POST', \`/entity/${ingress.appId}/${ingress.entity.id}/selection-query/batch_print\`, {
406
+ ...params,
407
+ policy_id: ingressId,
408
+ selection: toArcubaseSelection(selection),
409
+ })
410
+ },`);
411
+ }
412
+ if (hasAction(ingress, "archive")) {
413
+ methodChunks.push(` async archive(selection: ${typeName}Selection): Promise<{ archived?: number } | boolean> {
414
+ return runtime.request<{ archived?: number } | boolean>('POST', \`/entity/${ingress.appId}/${ingress.entity.id}/selection-query/archive\`, {
415
+ policy_id: ingressId,
416
+ selection: toArcubaseSelection(selection),
417
+ })
418
+ },`);
419
+ }
420
+ if (hasAction(ingress, "tag_manage")) {
421
+ methodChunks.push(` tags: {
422
+ async list(): Promise<string[]> {
423
+ return runtime.request<string[]>('GET', \`/user-action-entity/${ingress.appId}/${ingress.entity.id}/tag-names\`)
424
+ },
425
+ async add(selection: ${typeName}Selection, tagNames: string[]): Promise<Record<string, any>> {
426
+ return runtime.request<Record<string, any>>('POST', \`/user-action-entity/${ingress.appId}/${ingress.entity.id}/batch-add-tags\`, {
427
+ policy_id: ingressId,
428
+ selection: toArcubaseSelection(selection),
429
+ tag_names: tagNames,
430
+ })
431
+ },
432
+ async remove(selection: ${typeName}Selection, tagNames: string[]): Promise<boolean> {
433
+ return runtime.request<boolean>('POST', \`/user-action-entity/${ingress.appId}/${ingress.entity.id}/batch-remove-tags\`, {
434
+ policy_id: ingressId,
435
+ selection: toArcubaseSelection(selection),
436
+ tag_names: tagNames,
437
+ })
438
+ },
439
+ async selection(selection: ${typeName}Selection): Promise<string[]> {
440
+ return runtime.request<string[]>('POST', \`/user-action-entity/${ingress.appId}/${ingress.entity.id}/get-selection-tags\`, {
441
+ policy_id: ingressId,
442
+ selection: toArcubaseSelection(selection),
443
+ })
444
+ },
445
+ },`);
446
+ }
447
+ const customActions = ingress.actions.filter((action) => !["view", "add", "edit", "delete", "bulk_update", "export", "batch_print", "archive", "tag_manage"].includes(action));
448
+ if (customActions.length > 0) {
449
+ methodChunks.push(` async action(action: ${typeName}CustomAction, payload: Record<string, any> = {}): Promise<Record<string, any>> {
450
+ if (!allowedActions.includes(action)) {
451
+ throw new Error(\`Action is not allowed for ${ingress.key}: \${action}\`)
452
+ }
453
+ return runtime.request<Record<string, any>>('POST', \`/entity/${ingress.appId}/${ingress.entity.id}/selection-query/\${encodeURIComponent(action)}\`, {
454
+ ...payload,
455
+ policy_id: ingressId,
456
+ selection: payload.selection ? toArcubaseSelection(payload.selection) : undefined,
457
+ })
458
+ },`);
459
+ }
460
+ return `import {
461
+ fromArcubaseFields,
462
+ mapArcubaseCondition,
463
+ mapArcubaseSorts,
464
+ toArcubaseFields,
465
+ type ArcubaseRow,
466
+ type ArcubaseRuntime,
467
+ } from '../runtime.js'
468
+
469
+ export type ${typeName}FieldKey = ${fieldKeyUnion}
470
+
471
+ export type ${typeName}Fields = {
472
+ ${fieldLines.join("\n")}
473
+ }
474
+
475
+ export type ${typeName}CreateInput = ${typeName}Fields
476
+ export type ${typeName}UpdateInput = Partial<${typeName}Fields>
477
+ export type ${typeName}Action = ${ingress.actions.map((action) => `'${action}'`).join(" | ") || "never"}
478
+ export type ${typeName}CustomAction = ${customActions.map((action) => `'${action}'`).join(" | ") || "never"}
479
+
480
+ export type ${typeName}Sort = {
481
+ column: ${typeName}FieldKey | 'id' | 'created_at' | 'updated_at' | 'creator' | string
482
+ order: 'asc' | 'desc'
483
+ }
484
+
485
+ export type ${typeName}QueryParams = {
486
+ limit?: number
487
+ offset?: number
488
+ search?: Record<string, any>
489
+ sorts?: ${typeName}Sort[]
490
+ view_mode?: 'normal' | 'deleted' | 'archived'
491
+ withSubForm?: boolean
492
+ }
493
+
494
+ export type ${typeName}Selection = {
495
+ type: 'ids' | 'condition'
496
+ length?: number
497
+ ids?: Array<number | string>
498
+ condition?: Record<string, any>
499
+ sorts?: ${typeName}Sort[]
500
+ }
501
+
502
+ export type ${typeName}BulkUpdateField = {
503
+ key: ${typeName}FieldKey
504
+ action: Record<string, any>
505
+ }
506
+
507
+ export type ${typeName}ExportOptions = {
508
+ fields?: ${typeName}FieldKey[]
509
+ props?: string[]
510
+ propNames?: Record<string, string>
511
+ hasIdField?: boolean
512
+ }
513
+
514
+ const ingressId = ${JSON.stringify(ingress.hashId)}
515
+ const fieldIds = {
516
+ ${fieldMapLines.join("\n")}
517
+ } as const
518
+ const allowedActions = ${JSON.stringify(customActions)} as const
519
+
520
+ function mapRow(raw: Record<string, any>): ArcubaseRow<${typeName}Fields> {
521
+ return {
522
+ id: Number(raw.id),
523
+ title: typeof raw.title === 'string' ? raw.title : undefined,
524
+ fields: fromArcubaseFields<${typeName}Fields>(raw.fields, fieldIds),
525
+ raw,
526
+ }
527
+ }
528
+
529
+ function toArcubaseSelection(selection: ${typeName}Selection): Record<string, any> {
530
+ return {
531
+ ...selection,
532
+ condition: mapArcubaseCondition(selection.condition ?? {}, fieldIds),
533
+ sorts: mapArcubaseSorts(selection.sorts, fieldIds),
534
+ }
535
+ }
536
+
537
+ export function create${typeName}Ingress(runtime: ArcubaseRuntime) {
538
+ return {
539
+ ${methodLines(methodChunks)} }
540
+ }
541
+ `;
542
+ }
543
+ function emitClient(ingresses) {
544
+ const imports = ingresses.map((ingress) => {
545
+ const typeName = toPascalCase(ingress.key);
546
+ return `import { create${typeName}Ingress } from './ingresses/${ingress.key}.js'`;
547
+ });
548
+ const mapLines = ingresses.map((ingress) => ` ${JSON.stringify(ingress.key)}: ReturnType<typeof create${toPascalCase(ingress.key)}Ingress>`);
549
+ const cases = ingresses.map((ingress) => ` case ${JSON.stringify(ingress.key)}:
550
+ return create${toPascalCase(ingress.key)}Ingress(runtime) as ArcubaseIngressMap[K]`);
551
+ return `import { createRuntime, type ArcubaseClientConfig } from './runtime.js'
552
+ ${imports.join("\n")}
553
+
554
+ export type ArcubaseIngressMap = {
555
+ ${mapLines.join("\n")}
556
+ }
557
+
558
+ export type ArcubaseIngressKey = keyof ArcubaseIngressMap
559
+
560
+ export function createArcubaseClient(config: ArcubaseClientConfig) {
561
+ const runtime = createRuntime(config)
562
+ return {
563
+ ingress<K extends ArcubaseIngressKey>(key: K): ArcubaseIngressMap[K] {
564
+ switch (key) {
565
+ ${cases.join("\n")}
566
+ default:
567
+ throw new Error(\`Unknown Arcubase ingress: \${String(key)}\`)
568
+ }
569
+ },
570
+ }
571
+ }
572
+ `;
573
+ }
574
+ function emitIndex(ingresses) {
575
+ return `export { createArcubaseClient } from './client.js'
576
+ export type { ArcubaseClientConfig, ArcubaseRow } from './runtime.js'
577
+ export { arcubaseSchema } from './schema.js'
578
+ export type { ArcubaseIngressKey, ArcubaseIngressMap } from './client.js'
579
+ ${ingresses.map((ingress) => {
580
+ const typeName = toPascalCase(ingress.key);
581
+ return `export type { ${typeName}Fields, ${typeName}CreateInput, ${typeName}UpdateInput, ${typeName}FieldKey, ${typeName}Selection } from './ingresses/${ingress.key}.js'`;
582
+ }).join("\n")}
583
+ `;
584
+ }
585
+ function generateArcubaseProjectSDK(payload) {
586
+ const ingresses = normalizeIngresses(payload);
587
+ return {
588
+ files: [
589
+ { path: "runtime.ts", contents: emitRuntime() },
590
+ { path: "schema.ts", contents: emitSchema(ingresses) },
591
+ ...ingresses.map((ingress) => ({ path: `ingresses/${ingress.key}.ts`, contents: emitIngress(ingress) })),
592
+ { path: "client.ts", contents: emitClient(ingresses) },
593
+ { path: "index.ts", contents: emitIndex(ingresses) }
594
+ ]
595
+ };
596
+ }
597
+
12
598
  // src/runtime/errors.ts
13
599
  var CLIError = class extends Error {
14
600
  constructor(code, message, exitCode = 1, details) {
@@ -16529,10 +17115,10 @@ var PermitFieldPermitSchema = external_exports.lazy(() => external_exports.objec
16529
17115
  var PermitPolicySchema = external_exports.lazy(() => external_exports.object({ "actions": external_exports.array(external_exports.string()).optional(), "all_fields_read": external_exports.boolean().optional(), "all_fields_write": external_exports.boolean().optional(), "condition": PermitConditionSetSchema.optional(), "description": external_exports.string().optional(), "fields": external_exports.array(PermitFieldPermitSchema).optional(), "id": external_exports.string().optional(), "key": external_exports.string().optional(), "name": external_exports.string().optional() }).strict());
16530
17116
  var PermitUserScopeSchema = external_exports.lazy(() => external_exports.object({ "data": external_exports.object({ "name": external_exports.string().optional() }).strict().optional(), "id": external_exports.number().optional(), "type": external_exports.string().optional() }).strict());
16531
17117
  var EntityIngressOptionsSchema = external_exports.lazy(() => external_exports.object({ "i18n_name": I18NTextSchema.optional(), "list_options": PermitEntityListOptionsSchema.optional(), "policy": PermitPolicySchema.optional(), "user_scope": external_exports.array(PermitUserScopeSchema).optional() }).strict());
16532
- var AppIngressBulkApplyItemVOSchema = external_exports.lazy(() => external_exports.object({ "enabled": external_exports.boolean().optional(), "name": external_exports.string().optional(), "options": EntityIngressOptionsSchema.optional(), "rule_id": external_exports.number().optional(), "table_id": external_exports.number().optional(), "type": external_exports.string().optional() }).strict());
17118
+ var AppIngressBulkApplyItemVOSchema = external_exports.lazy(() => external_exports.object({ "enabled": external_exports.boolean().optional(), "key": external_exports.string().optional(), "name": external_exports.string().optional(), "options": EntityIngressOptionsSchema.optional(), "rule_id": external_exports.number().optional(), "table_id": external_exports.number().optional(), "type": external_exports.string().optional() }).strict());
16533
17119
  var AppIngressBulkApplyReqVOSchema = external_exports.lazy(() => external_exports.object({ "dry_run": external_exports.boolean().optional(), "items": external_exports.array(AppIngressBulkApplyItemVOSchema).optional(), "mode": external_exports.string().optional() }).strict());
16534
- var AppIngressCreateReqVOSchema = external_exports.lazy(() => external_exports.object({ "enabled": external_exports.boolean().optional(), "entity_id": external_exports.number().optional(), "icon_name": external_exports.string().optional(), "name": external_exports.string().optional(), "options": EntityIngressOptionsSchema.optional(), "type": external_exports.string().optional(), "url": external_exports.string().optional() }).strict());
16535
- var AppIngressUpdateReqVOSchema = external_exports.lazy(() => external_exports.object({ "enabled": external_exports.boolean().optional(), "name": external_exports.string().optional(), "options": EntityIngressOptionsSchema.optional(), "update": external_exports.array(external_exports.string()).optional() }).strict());
17120
+ var AppIngressCreateReqVOSchema = external_exports.lazy(() => external_exports.object({ "enabled": external_exports.boolean().optional(), "entity_id": external_exports.number().optional(), "icon_name": external_exports.string().optional(), "key": external_exports.string().optional(), "name": external_exports.string().optional(), "options": EntityIngressOptionsSchema.optional(), "type": external_exports.string().optional(), "url": external_exports.string().optional() }).strict());
17121
+ var AppIngressUpdateReqVOSchema = external_exports.lazy(() => external_exports.object({ "enabled": external_exports.boolean().optional(), "key": external_exports.string().optional(), "name": external_exports.string().optional(), "options": EntityIngressOptionsSchema.optional(), "update": external_exports.array(external_exports.string()).optional() }).strict());
16536
17122
  var WidgetsTypeCodeSchema = external_exports.lazy(() => external_exports.union([external_exports.literal(1), external_exports.literal(2)]));
16537
17123
  var WidgetsTypeSchema = external_exports.lazy(() => external_exports.object({ "dashboard_id": external_exports.number().optional(), "ingress_id": external_exports.number().optional(), "type": WidgetsTypeCodeSchema.optional() }).strict());
16538
17124
  var AppNewWidgetsReqVOSchema = external_exports.lazy(() => external_exports.object({ "options": WidgetsTypeSchema.optional(), "type": external_exports.string().optional() }).strict());
@@ -16943,14 +17529,14 @@ var fieldCommonShape = {
16943
17529
  code_index: external_exports.boolean(),
16944
17530
  transfers: external_exports.array(external_exports.any()).nullable()
16945
17531
  };
16946
- function isRecord(value) {
17532
+ function isRecord2(value) {
16947
17533
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
16948
17534
  }
16949
17535
  function cloneJSON(value) {
16950
17536
  return JSON.parse(JSON.stringify(value));
16951
17537
  }
16952
17538
  function normalizeFieldVO(value) {
16953
- if (!isRecord(value)) {
17539
+ if (!isRecord2(value)) {
16954
17540
  return value;
16955
17541
  }
16956
17542
  const field = { ...value };
@@ -17103,7 +17689,7 @@ var FieldVOSchema = external_exports.lazy(
17103
17689
  ])
17104
17690
  );
17105
17691
  function normalizeEntitySaveInput(value) {
17106
- if (!isRecord(value)) {
17692
+ if (!isRecord2(value)) {
17107
17693
  return value;
17108
17694
  }
17109
17695
  const entity = cloneJSON(value);
@@ -17113,7 +17699,7 @@ function normalizeEntitySaveInput(value) {
17113
17699
  if (entity.current_serial === null) {
17114
17700
  delete entity.current_serial;
17115
17701
  }
17116
- if (isRecord(entity.options)) {
17702
+ if (isRecord2(entity.options)) {
17117
17703
  if (entity.options.Buttons === null) {
17118
17704
  entity.options.Buttons = [];
17119
17705
  }
@@ -17126,7 +17712,7 @@ function normalizeEntitySaveInput(value) {
17126
17712
  if (entity.options.misc === null) {
17127
17713
  entity.options.misc = {};
17128
17714
  }
17129
- if (isRecord(entity.options.TitleFormat)) {
17715
+ if (isRecord2(entity.options.TitleFormat)) {
17130
17716
  if (entity.options.TitleFormat.Fields === null) {
17131
17717
  entity.options.TitleFormat.Fields = [];
17132
17718
  }
@@ -17927,7 +18513,7 @@ function compileCSVImportMapping(input, headers) {
17927
18513
  // src/runtime/local_upload.ts
17928
18514
  import fs2 from "fs/promises";
17929
18515
  import path from "path";
17930
- function isRecord2(value) {
18516
+ function isRecord3(value) {
17931
18517
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
17932
18518
  }
17933
18519
  async function ensureLocalFile(filePath) {
@@ -17943,7 +18529,7 @@ async function ensureLocalFile(filePath) {
17943
18529
  }
17944
18530
  function tokenData(token) {
17945
18531
  const data = token.token_data ?? token.tokenData;
17946
- if (!isRecord2(data)) {
18532
+ if (!isRecord3(data)) {
17947
18533
  throw new CLIError("UPLOAD_TOKEN_UNSUPPORTED", "upload token data is not an object", 1);
17948
18534
  }
17949
18535
  return data;
@@ -18569,447 +19155,124 @@ var helpExamplesIndex = {
18569
19155
  "id": 1003,
18570
19156
  "action": {
18571
19157
  "type": "set",
18572
- "value": 2
18573
- }
18574
- }
18575
- ]
18576
- }
18577
- },
18578
- {
18579
- "title": "bulk update rows selected by a search condition",
18580
- "body": {
18581
- "selection": {
18582
- "type": "condition",
18583
- "condition": {
18584
- "selectAll": true,
18585
- "q": "SO-1001"
18586
- },
18587
- "length": 0
18588
- },
18589
- "fields": [
18590
- {
18591
- "id": 1003,
18592
- "action": {
18593
- "type": "set",
18594
- "value": 2
18595
- }
18596
- }
18597
- ]
18598
- },
18599
- "notes": [
18600
- "condition selection uses row-query style keys such as q, tab, and filter_:1002"
18601
- ]
18602
- }
18603
- ]
18604
- },
18605
- "user.row.selection-action": {
18606
- "command": [
18607
- "row",
18608
- "selection-action"
18609
- ],
18610
- "examples": [
18611
- {
18612
- "title": "run a custom action against explicit row ids",
18613
- "body": {
18614
- "selection": {
18615
- "type": "ids",
18616
- "ids": [
18617
- 2188890411
18618
- ],
18619
- "length": 1
18620
- }
18621
- },
18622
- "notes": [
18623
- "pass the backend action code with --action",
18624
- "for query action, use ids or all selection instead of condition selection"
18625
- ]
18626
- },
18627
- {
18628
- "title": "run a custom action against a filtered selection",
18629
- "body": {
18630
- "selection": {
18631
- "type": "condition",
18632
- "condition": {
18633
- "selectAll": true,
18634
- "q": "SO-1001"
18635
- },
18636
- "length": 0
18637
- }
18638
- },
18639
- "notes": [
18640
- "condition selection uses row-query style keys such as q, tab, and filter_:1002"
18641
- ]
18642
- }
18643
- ]
18644
- }
18645
- };
18646
-
18647
- // src/runtime/help_examples.ts
18648
- function binaryForScope(scope) {
18649
- return scope === "admin" ? "arcubase-admin" : "arcubase";
18650
- }
18651
- function getFixedValue(value) {
18652
- if (!("fixedValue" in value)) {
18653
- return void 0;
18654
- }
18655
- const fixedValue = value.fixedValue;
18656
- return typeof fixedValue === "string" ? fixedValue : void 0;
18657
- }
18658
- function pathFlagPlaceholders(command) {
18659
- const parts = command.pathParams.filter((item) => !getFixedValue(item)).map((item) => `--${item.flag} <${item.flag.replace(/-/g, "_")}>`);
18660
- return parts.length > 0 ? `${parts.join(" ")} ` : "";
18661
- }
18662
- function helpExampleKey(scope, command) {
18663
- return `${scope}.${command.commandPath.join(".")}`;
18664
- }
18665
- function getCommandHelpExamples(scope, command) {
18666
- return helpExamplesIndex[helpExampleKey(scope, command)]?.examples ?? [];
18667
- }
18668
- function renderCommandHelpExamples(scope, command) {
18669
- const binary = binaryForScope(scope);
18670
- const examples = getCommandHelpExamples(scope, command);
18671
- const header = `${binary} ${command.commandPath.join(" ")} --help-examples`;
18672
- if (examples.length === 0) {
18673
- return [
18674
- header,
18675
- "",
18676
- "No dedicated examples for this final command yet.",
18677
- `Use ${binary} ${command.commandPath.join(" ")} --help for flags, docs, and type paths.`
18678
- ].join("\n");
18679
- }
18680
- return [
18681
- header,
18682
- "",
18683
- ...examples.flatMap((example) => [
18684
- `${example.title}:`,
18685
- ...example.body === void 0 ? [] : [` ${binary} ${command.commandPath.join(" ")} ${pathFlagPlaceholders(command)}--body-json '${JSON.stringify(example.body)}'`.replace(/\s+/g, " ").trim()],
18686
- ...(example.notes ?? []).map((note) => ` - ${note}`),
18687
- ""
18688
- ])
18689
- ].join("\n").trimEnd();
18690
- }
18691
-
18692
- // node_modules/@arcubase/code-gen/dist/index.js
18693
- var ArcubaseCodeGenError = class extends Error {
18694
- code;
18695
- constructor(code, message) {
18696
- super(message);
18697
- this.code = code;
18698
- this.name = "ArcubaseCodeGenError";
18699
- }
18700
- };
18701
- function isRecord3(value) {
18702
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
18703
- }
18704
- function unwrapData(payload) {
18705
- return isRecord3(payload) && "data" in payload ? payload.data : payload;
18706
- }
18707
- function toPascalCase(value) {
18708
- const words = value.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[^A-Za-z0-9]+/).filter(Boolean);
18709
- if (words.length === 0)
18710
- return "Entity";
18711
- return words.map((word) => `${word[0].toUpperCase()}${word.slice(1)}`).join("");
18712
- }
18713
- function toCamelCase(value) {
18714
- const pascal = toPascalCase(value);
18715
- return `${pascal[0].toLowerCase()}${pascal.slice(1)}`;
18716
- }
18717
- function sanitizeEntityKey(entity) {
18718
- const fallback = entity.sdkName || `entity_${entity.id}`;
18719
- return toCamelCase(fallback) === "entity" ? `entity${entity.id}` : toCamelCase(fallback);
18720
- }
18721
- function normalizeFieldType(type) {
18722
- switch (type) {
18723
- case "number":
18724
- return "number";
18725
- case "boolean":
18726
- return "boolean";
18727
- case "select":
18728
- case "radio":
18729
- case "checkbox":
18730
- case "status":
18731
- return "number | string";
18732
- case "file":
18733
- case "image":
18734
- case "member":
18735
- case "members":
18736
- case "department":
18737
- case "departments":
18738
- case "linkto":
18739
- case "relation":
18740
- case "relationfield":
18741
- case "subform":
18742
- return "unknown";
18743
- default:
18744
- return "string";
18745
- }
18746
- }
18747
- function normalizeEntities(payload) {
18748
- const root = unwrapData(payload);
18749
- if (!isRecord3(root)) {
18750
- throw new ArcubaseCodeGenError("SDK_GEN_INVALID_SCHEMA", "sdk-gen expected an app detail object");
18751
- }
18752
- const appId = typeof root.id === "number" ? root.id : typeof root.app_id === "number" ? root.app_id : void 0;
18753
- if (!appId) {
18754
- throw new ArcubaseCodeGenError("SDK_GEN_APP_ID_REQUIRED", "sdk-gen expected app id in app detail response");
18755
- }
18756
- const sourceEntities = Array.isArray(root.entities) ? root.entities : Array.isArray(root.tables) ? root.tables : Array.isArray(root.entitys) ? root.entitys : [];
18757
- const entities = sourceEntities.filter((item) => isRecord3(item)).filter((item) => typeof item.id === "number" && typeof item.name === "string").map((item) => {
18758
- const fields = Array.isArray(item.fields) ? item.fields.filter((field) => isRecord3(field)).filter((field) => typeof field.id === "number" && typeof field.label === "string" && typeof field.type === "string").map((field) => ({
18759
- id: field.id,
18760
- label: field.label,
18761
- key: typeof field.key === "string" ? field.key.trim() : "",
18762
- type: field.type,
18763
- required: field.required === true,
18764
- options: isRecord3(field.options) ? field.options : void 0
18765
- })) : [];
18766
- const entity = {
18767
- appId,
18768
- id: item.id,
18769
- name: item.name,
18770
- sdkName: typeof item.key === "string" && item.key.trim() ? item.key.trim() : "",
18771
- fields
18772
- };
18773
- entity.sdkName = sanitizeEntityKey(entity);
18774
- return entity;
18775
- });
18776
- if (entities.length === 0) {
18777
- throw new ArcubaseCodeGenError("SDK_GEN_NO_ENTITIES", "sdk-gen could not find entities in app detail response");
18778
- }
18779
- validateEntities(entities);
18780
- return entities;
18781
- }
18782
- function validateEntities(entities) {
18783
- const entityKeys = /* @__PURE__ */ new Set();
18784
- for (const entity of entities) {
18785
- if (entityKeys.has(entity.sdkName)) {
18786
- throw new ArcubaseCodeGenError("SDK_GEN_DUPLICATE_ENTITY_KEY", `duplicate generated entity key: ${entity.sdkName}`);
18787
- }
18788
- entityKeys.add(entity.sdkName);
18789
- const fieldKeys = /* @__PURE__ */ new Set();
18790
- for (const field of entity.fields) {
18791
- if (!field.key) {
18792
- throw new ArcubaseCodeGenError("SDK_GEN_FIELD_KEY_REQUIRED", `field.key is required for ${entity.name}.${field.label} (${field.id})`);
18793
- }
18794
- if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(field.key)) {
18795
- throw new ArcubaseCodeGenError("SDK_GEN_INVALID_FIELD_KEY", `field.key must be a TypeScript identifier: ${entity.name}.${field.key}`);
18796
- }
18797
- if (fieldKeys.has(field.key)) {
18798
- throw new ArcubaseCodeGenError("SDK_GEN_DUPLICATE_FIELD_KEY", `duplicate field.key in ${entity.name}: ${field.key}`);
18799
- }
18800
- fieldKeys.add(field.key);
18801
- }
18802
- }
18803
- }
18804
- function emitRuntime() {
18805
- return `export type ArcubaseSdkConfig = {
18806
- baseURL: string
18807
- accessToken?: string
18808
- refreshToken?: string
18809
- }
18810
-
18811
- export type ArcubaseRow<TFields> = {
18812
- id: number
18813
- title?: string
18814
- fields: TFields
18815
- raw: Record<string, any>
18816
- }
18817
-
18818
- export type ArcubaseRuntime = {
18819
- config: ArcubaseSdkConfig
18820
- request<T>(method: string, endpoint: string, body?: unknown): Promise<T>
18821
- }
18822
-
18823
- type ArcubaseEnvelope<T> = {
18824
- data?: T
18825
- error?: { message?: string; key?: string; type?: string }
18826
- }
18827
-
18828
- export function createRuntime(config: ArcubaseSdkConfig): ArcubaseRuntime {
18829
- let accessToken = config.accessToken ?? ''
18830
- let refreshToken = config.refreshToken ?? ''
18831
-
18832
- async function request<T>(method: string, endpoint: string, body?: unknown): Promise<T> {
18833
- const baseURL = config.baseURL.endsWith('/') ? config.baseURL : \`\${config.baseURL}/\`
18834
- const response = await fetch(new URL(endpoint.replace(/^\\/+/, ''), baseURL).toString(), {
18835
- method,
18836
- headers: {
18837
- 'Content-Type': 'application/json',
18838
- ...(accessToken ? { Authorization: \`Bearer \${accessToken}\` } : {}),
19158
+ "value": 2
19159
+ }
19160
+ }
19161
+ ]
19162
+ }
18839
19163
  },
18840
- body: body === undefined ? undefined : JSON.stringify(body),
18841
- })
18842
- const payload = await response.json().catch(() => null) as ArcubaseEnvelope<T> | null
18843
- if (!response.ok || payload?.error) {
18844
- throw new Error(payload?.error?.message ?? \`Arcubase request failed: \${response.status}\`)
18845
- }
18846
- return payload?.data as T
18847
- }
18848
-
18849
- return {
18850
- config: { ...config, accessToken, refreshToken },
18851
- request,
18852
- }
18853
- }
18854
- `;
18855
- }
18856
- function emitSchema(entities) {
18857
- const schema = {
18858
- entities: Object.fromEntries(entities.map((entity) => [
18859
- entity.sdkName,
18860
19164
  {
18861
- entityId: entity.id,
18862
- name: entity.name,
18863
- fields: Object.fromEntries(entity.fields.map((field) => [
18864
- field.key,
18865
- {
18866
- fieldId: field.id,
18867
- label: field.label,
18868
- type: field.type,
18869
- required: field.required
19165
+ "title": "bulk update rows selected by a search condition",
19166
+ "body": {
19167
+ "selection": {
19168
+ "type": "condition",
19169
+ "condition": {
19170
+ "selectAll": true,
19171
+ "q": "SO-1001"
19172
+ },
19173
+ "length": 0
19174
+ },
19175
+ "fields": [
19176
+ {
19177
+ "id": 1003,
19178
+ "action": {
19179
+ "type": "set",
19180
+ "value": 2
19181
+ }
19182
+ }
19183
+ ]
19184
+ },
19185
+ "notes": [
19186
+ "condition selection uses row-query style keys such as q, tab, and filter_:1002"
19187
+ ]
19188
+ }
19189
+ ]
19190
+ },
19191
+ "user.row.selection-action": {
19192
+ "command": [
19193
+ "row",
19194
+ "selection-action"
19195
+ ],
19196
+ "examples": [
19197
+ {
19198
+ "title": "run a custom action against explicit row ids",
19199
+ "body": {
19200
+ "selection": {
19201
+ "type": "ids",
19202
+ "ids": [
19203
+ 2188890411
19204
+ ],
19205
+ "length": 1
18870
19206
  }
18871
- ]))
19207
+ },
19208
+ "notes": [
19209
+ "pass the backend action code with --action",
19210
+ "for query action, use ids or all selection instead of condition selection"
19211
+ ]
19212
+ },
19213
+ {
19214
+ "title": "run a custom action against a filtered selection",
19215
+ "body": {
19216
+ "selection": {
19217
+ "type": "condition",
19218
+ "condition": {
19219
+ "selectAll": true,
19220
+ "q": "SO-1001"
19221
+ },
19222
+ "length": 0
19223
+ }
19224
+ },
19225
+ "notes": [
19226
+ "condition selection uses row-query style keys such as q, tab, and filter_:1002"
19227
+ ]
18872
19228
  }
18873
- ]))
18874
- };
18875
- return `export const arcubaseSchema = ${JSON.stringify(schema, null, 2)} as const
18876
-
18877
- export type ArcubaseEntityKey = keyof typeof arcubaseSchema.entities
18878
- `;
18879
- }
18880
- function emitEntity(entity) {
18881
- const typeName = toPascalCase(entity.sdkName);
18882
- const fieldLines = entity.fields.map((field) => {
18883
- const optional2 = field.required ? "" : "?";
18884
- return ` ${field.key}${optional2}: ${normalizeFieldType(field.type)}`;
18885
- });
18886
- const fieldMapLines = entity.fields.map((field) => ` ${field.key}: ${field.id},`);
18887
- return `import type { ArcubaseRuntime, ArcubaseRow } from '../runtime.js'
18888
-
18889
- export type ${typeName}Fields = {
18890
- ${fieldLines.join("\n")}
18891
- }
18892
-
18893
- export type ${typeName}CreateInput = ${typeName}Fields
18894
- export type ${typeName}UpdateInput = Partial<${typeName}Fields>
18895
-
18896
- const entityId = ${entity.id}
18897
- const fieldIds = {
18898
- ${fieldMapLines.join("\n")}
18899
- } as const
18900
-
18901
- function toArcubaseFields(fields: Partial<${typeName}Fields>): Record<string, any> {
18902
- const out: Record<string, any> = {}
18903
- for (const [key, value] of Object.entries(fields)) {
18904
- if (value === undefined) continue
18905
- const fieldId = fieldIds[key as keyof typeof fieldIds]
18906
- if (!fieldId) {
18907
- throw new Error(\`Unknown ${entity.sdkName} field: \${key}\`)
18908
- }
18909
- out[String(fieldId)] = value
18910
- }
18911
- return out
18912
- }
18913
-
18914
- function fromArcubaseFields(raw: Record<string, any> | undefined): ${typeName}Fields {
18915
- const out: Record<string, any> = {}
18916
- const source = raw ?? {}
18917
- for (const [key, fieldId] of Object.entries(fieldIds)) {
18918
- if (String(fieldId) in source) {
18919
- out[key] = source[String(fieldId)]
18920
- }
18921
- }
18922
- return out as ${typeName}Fields
18923
- }
18924
-
18925
- function mapRow(raw: Record<string, any>): ArcubaseRow<${typeName}Fields> {
18926
- return {
18927
- id: Number(raw.id),
18928
- title: typeof raw.title === 'string' ? raw.title : undefined,
18929
- fields: fromArcubaseFields(raw.fields),
18930
- raw,
19229
+ ]
18931
19230
  }
18932
- }
18933
-
18934
- export function create${typeName}SDK(runtime: ArcubaseRuntime) {
18935
- return {
18936
- async create(fields: ${typeName}CreateInput): Promise<number> {
18937
- return runtime.request<number>('POST', \`/entity/${entity.appId}/\${entityId}/insert\`, {
18938
- fields: toArcubaseFields(fields),
18939
- })
18940
- },
18941
-
18942
- async update(rowId: string | number, fields: ${typeName}UpdateInput): Promise<boolean> {
18943
- const data = toArcubaseFields(fields)
18944
- return runtime.request<boolean>('PUT', \`/entity/${entity.appId}/\${entityId}/row/\${rowId}\`, {
18945
- data,
18946
- changed_fields: Object.keys(data).map((fieldId) => Number(fieldId)),
18947
- })
18948
- },
18949
-
18950
- async get(rowId: string | number): Promise<ArcubaseRow<${typeName}Fields>> {
18951
- const data = await runtime.request<{ record?: Record<string, any> }>('GET', \`/entity/${entity.appId}/\${entityId}/row/\${rowId}\`)
18952
- return mapRow(data.record ?? {})
18953
- },
19231
+ };
18954
19232
 
18955
- async query(params: { limit?: number; offset?: number; search?: Record<string, any> } = {}): Promise<{ items: ArcubaseRow<${typeName}Fields>[]; total?: number }> {
18956
- const data = await runtime.request<{ items?: Record<string, any>[]; total?: number }>('POST', \`/entity/${entity.appId}/\${entityId}/data-query\`, params)
18957
- return {
18958
- items: (data.items ?? []).map(mapRow),
18959
- total: data.total,
18960
- }
18961
- },
18962
- }
18963
- }
18964
- `;
19233
+ // src/runtime/help_examples.ts
19234
+ function binaryForScope(scope) {
19235
+ return scope === "admin" ? "arcubase-admin" : "arcubase";
18965
19236
  }
18966
- function emitClient(entities) {
18967
- const imports = entities.map((entity) => {
18968
- const typeName = toPascalCase(entity.sdkName);
18969
- return `import { create${typeName}SDK } from './entities/${entity.sdkName}.js'`;
18970
- });
18971
- const properties = entities.map((entity) => ` ${entity.sdkName}: create${toPascalCase(entity.sdkName)}SDK(runtime),`);
18972
- return `import { createRuntime, type ArcubaseSdkConfig } from './runtime.js'
18973
- ${imports.join("\n")}
18974
-
18975
- export function createArcubaseSdk(config: ArcubaseSdkConfig) {
18976
- const runtime = createRuntime(config)
18977
- return {
18978
- ${properties.join("\n")}
19237
+ function getFixedValue(value) {
19238
+ if (!("fixedValue" in value)) {
19239
+ return void 0;
18979
19240
  }
19241
+ const fixedValue = value.fixedValue;
19242
+ return typeof fixedValue === "string" ? fixedValue : void 0;
18980
19243
  }
18981
- `;
19244
+ function pathFlagPlaceholders(command) {
19245
+ const parts = command.pathParams.filter((item) => !getFixedValue(item)).map((item) => `--${item.flag} <${item.flag.replace(/-/g, "_")}>`);
19246
+ return parts.length > 0 ? `${parts.join(" ")} ` : "";
18982
19247
  }
18983
- function emitIndex(entities) {
18984
- return `export { createArcubaseSdk } from './client.js'
18985
- export type { ArcubaseSdkConfig, ArcubaseRow } from './runtime.js'
18986
- export { arcubaseSchema } from './schema.js'
18987
- ${entities.map((entity) => `export type { ${toPascalCase(entity.sdkName)}Fields, ${toPascalCase(entity.sdkName)}CreateInput, ${toPascalCase(entity.sdkName)}UpdateInput } from './entities/${entity.sdkName}.js'`).join("\n")}
18988
- `;
19248
+ function helpExampleKey(scope, command) {
19249
+ return `${scope}.${command.commandPath.join(".")}`;
18989
19250
  }
18990
- function generateArcubaseProjectSDK(payload) {
18991
- const entities = normalizeEntities(payload);
18992
- return {
18993
- files: [
18994
- { path: "runtime.ts", contents: emitRuntime() },
18995
- { path: "schema.ts", contents: emitSchema(entities) },
18996
- ...entities.map((entity) => ({ path: `entities/${entity.sdkName}.ts`, contents: emitEntity(entity) })),
18997
- { path: "client.ts", contents: emitClient(entities) },
18998
- { path: "index.ts", contents: emitIndex(entities) }
18999
- ]
19000
- };
19251
+ function getCommandHelpExamples(scope, command) {
19252
+ return helpExamplesIndex[helpExampleKey(scope, command)]?.examples ?? [];
19001
19253
  }
19002
-
19003
- // src/runtime/dev_sdk_gen.ts
19004
- function generateArcubaseProjectSDK2(payload) {
19005
- try {
19006
- return generateArcubaseProjectSDK(payload);
19007
- } catch (error51) {
19008
- if (error51 instanceof ArcubaseCodeGenError) {
19009
- throw new CLIError(error51.code, error51.message, 2);
19010
- }
19011
- throw error51;
19254
+ function renderCommandHelpExamples(scope, command) {
19255
+ const binary = binaryForScope(scope);
19256
+ const examples = getCommandHelpExamples(scope, command);
19257
+ const header = `${binary} ${command.commandPath.join(" ")} --help-examples`;
19258
+ if (examples.length === 0) {
19259
+ return [
19260
+ header,
19261
+ "",
19262
+ "No dedicated examples for this final command yet.",
19263
+ `Use ${binary} ${command.commandPath.join(" ")} --help for flags, docs, and type paths.`
19264
+ ].join("\n");
19012
19265
  }
19266
+ return [
19267
+ header,
19268
+ "",
19269
+ ...examples.flatMap((example) => [
19270
+ `${example.title}:`,
19271
+ ...example.body === void 0 ? [] : [` ${binary} ${command.commandPath.join(" ")} ${pathFlagPlaceholders(command)}--body-json '${JSON.stringify(example.body)}'`.replace(/\s+/g, " ").trim()],
19272
+ ...(example.notes ?? []).map((note) => ` - ${note}`),
19273
+ ""
19274
+ ])
19275
+ ].join("\n").trimEnd();
19013
19276
  }
19014
19277
 
19015
19278
  // src/runtime/execute.ts
@@ -19243,10 +19506,10 @@ function renderDevSDKGenHelp() {
19243
19506
  " - arcubase-admin dev sdk-gen --app-id <app_id> --out src/arcubase",
19244
19507
  "",
19245
19508
  "requirements:",
19509
+ " - every generated ingress must have a configured ingress.key; sdk-gen fails fast when keys are missing",
19246
19510
  " - every generated field must have a configured field.key; sdk-gen fails fast when keys are missing",
19247
- " - existing field.key values must be stable unique TypeScript identifiers",
19248
- " - entity property names use entity.key when available, otherwise a stable entity<id> fallback",
19249
- " - generated code accepts createArcubaseSdk({ baseURL, accessToken, refreshToken })"
19511
+ " - ingress.key and field.key values must be stable unique TypeScript identifiers",
19512
+ ' - generated code accepts createArcubaseClient({ baseURL, accessToken, refreshToken }).ingress("<IngressKey>")'
19250
19513
  ].join("\n");
19251
19514
  }
19252
19515
  function resolveEndpoint(command, flags) {
@@ -20788,6 +21051,85 @@ function assertSDKGenFieldKeysPresent(entity) {
20788
21051
  ]
20789
21052
  });
20790
21053
  }
21054
+ function extractSDKGenIngressRefs(payload) {
21055
+ const data = unwrapResponseData(payload);
21056
+ const source = Array.isArray(data) ? data : isRecord4(data) && Array.isArray(data.list) ? data.list : [];
21057
+ return source.filter((item) => isRecord4(item));
21058
+ }
21059
+ function ingressKeyOf(ingress) {
21060
+ return typeof ingress.key === "string" ? ingress.key.trim() : "";
21061
+ }
21062
+ function ingressHashIDOf(ingress) {
21063
+ if (typeof ingress.hash_id === "string" && ingress.hash_id.trim()) {
21064
+ return ingress.hash_id.trim();
21065
+ }
21066
+ if (typeof ingress.hashId === "string" && ingress.hashId.trim()) {
21067
+ return ingress.hashId.trim();
21068
+ }
21069
+ return "";
21070
+ }
21071
+ function assertSDKGenIngressesReady(ingresses) {
21072
+ if (ingresses.length === 0) {
21073
+ throw new CLIError("SDK_GEN_NO_INGRESSES", "sdk-gen could not find ingress definitions for this app", 2, {
21074
+ operation: "dev sdk-gen",
21075
+ suggestions: ["create at least one access rule with a stable ingress.key before running sdk-gen"]
21076
+ });
21077
+ }
21078
+ const seen = /* @__PURE__ */ new Set();
21079
+ const missingKey = [];
21080
+ const missingHash = [];
21081
+ const invalidKey = [];
21082
+ const duplicateKey = [];
21083
+ for (const ingress of ingresses) {
21084
+ const key = ingressKeyOf(ingress);
21085
+ if (!key) {
21086
+ missingKey.push(ingress);
21087
+ continue;
21088
+ }
21089
+ if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(key)) {
21090
+ invalidKey.push(key);
21091
+ }
21092
+ if (seen.has(key)) {
21093
+ duplicateKey.push(key);
21094
+ }
21095
+ seen.add(key);
21096
+ if (!ingressHashIDOf(ingress)) {
21097
+ missingHash.push(ingress);
21098
+ }
21099
+ }
21100
+ if (missingKey.length > 0) {
21101
+ const preview = missingKey.slice(0, 8).map((ingress) => String(ingress.name ?? ingress.id ?? "unnamed ingress"));
21102
+ const suffix = missingKey.length > preview.length ? `, and ${missingKey.length - preview.length} more` : "";
21103
+ throw new CLIError("SDK_GEN_INGRESS_KEY_REQUIRED", `missing ingress.key values: ${preview.join(", ")}${suffix}`, 2, {
21104
+ operation: "dev sdk-gen",
21105
+ issues: missingKey.map((ingress) => ({
21106
+ path: `ingress.${String(ingress.id ?? "unknown")}.key`,
21107
+ message: `ingress.key is required for ${String(ingress.name ?? ingress.id ?? "unnamed ingress")}`
21108
+ })),
21109
+ suggestions: [
21110
+ "set stable ingress.key values in Arcubase admin before running sdk-gen",
21111
+ "rerun arcubase-admin dev sdk-gen after ingress keys are complete"
21112
+ ]
21113
+ });
21114
+ }
21115
+ if (invalidKey.length > 0) {
21116
+ throw new CLIError("SDK_GEN_INVALID_INGRESS_KEY", `invalid ingress.key values: ${invalidKey.join(", ")}`, 2, {
21117
+ operation: "dev sdk-gen",
21118
+ suggestions: ["use TypeScript identifier keys such as publicEntry or customerPortal"]
21119
+ });
21120
+ }
21121
+ if (duplicateKey.length > 0) {
21122
+ throw new CLIError("SDK_GEN_DUPLICATE_INGRESS_KEY", `duplicate ingress.key values: ${duplicateKey.join(", ")}`, 2, {
21123
+ operation: "dev sdk-gen"
21124
+ });
21125
+ }
21126
+ if (missingHash.length > 0) {
21127
+ throw new CLIError("SDK_GEN_INGRESS_HASH_ID_REQUIRED", "sdk-gen expected hash_id for every ingress definition", 2, {
21128
+ operation: "dev sdk-gen",
21129
+ suggestions: ["upgrade Arcubase server so admin access-rule list returns hash_id"]
21130
+ });
21131
+ }
21132
+ }
20791
21133
  async function executeDevSDKGen(runtimeEnv, flags, fetchImpl) {
20792
21134
  validateDevSDKGenFlags(flags);
20793
21135
  const appId = flagValue(flags, "app-id");
@@ -20814,7 +21156,7 @@ async function executeDevSDKGen(runtimeEnv, flags, fetchImpl) {
20814
21156
  if (entityRefs.length === 0) {
20815
21157
  throw new CLIError("SDK_GEN_NO_ENTITIES", "sdk-gen could not find entities in app detail response", 2);
20816
21158
  }
20817
- const entities = [];
21159
+ const ingresses = [];
20818
21160
  for (const entityRef of entityRefs) {
20819
21161
  const entityEndpoint = `/api/apps/${encodeURIComponent(appId)}/entity/${encodeURIComponent(entityRef.id)}`;
20820
21162
  const entityDetail = await requestJSON(runtimeEnv, "GET", entityEndpoint, void 0, fetchImpl);
@@ -20823,13 +21165,36 @@ async function executeDevSDKGen(runtimeEnv, flags, fetchImpl) {
20823
21165
  throw new CLIError("SDK_GEN_INVALID_SCHEMA", "sdk-gen expected entity detail response data", 2);
20824
21166
  }
20825
21167
  assertSDKGenFieldKeysPresent(entityPayload);
20826
- entities.push(entityPayload);
21168
+ const ingressEndpoint = `/api/apps/${encodeURIComponent(appId)}/entity/${encodeURIComponent(entityRef.id)}/ingress`;
21169
+ const ingressList = await requestJSON(runtimeEnv, "GET", ingressEndpoint, void 0, fetchImpl);
21170
+ const entityIngresses = extractSDKGenIngressRefs(ingressList.data).map((ingress) => ({
21171
+ ...ingress,
21172
+ hash_id: ingressHashIDOf(ingress),
21173
+ key: ingressKeyOf(ingress),
21174
+ entity: entityPayload
21175
+ }));
21176
+ ingresses.push(...entityIngresses);
21177
+ }
21178
+ assertSDKGenIngressesReady(ingresses);
21179
+ let generated;
21180
+ try {
21181
+ generated = generateArcubaseProjectSDK({
21182
+ id: resolveSDKGenAppId(appId, appPayload),
21183
+ name: typeof appPayload.name === "string" ? appPayload.name : void 0,
21184
+ ingresses
21185
+ });
21186
+ } catch (error51) {
21187
+ if (error51 instanceof ArcubaseCodeGenError) {
21188
+ throw new CLIError(error51.code, error51.message, 2, {
21189
+ operation: "dev sdk-gen",
21190
+ suggestions: [
21191
+ "set stable ingress.key and field.key values in Arcubase admin before running sdk-gen",
21192
+ "rerun arcubase-admin dev sdk-gen after schema keys are complete"
21193
+ ]
21194
+ });
21195
+ }
21196
+ throw error51;
20827
21197
  }
20828
- const generated = generateArcubaseProjectSDK2({
20829
- id: resolveSDKGenAppId(appId, appPayload),
20830
- name: typeof appPayload.name === "string" ? appPayload.name : void 0,
20831
- entities
20832
- });
20833
21198
  const absoluteOutDir = path2.resolve(process.cwd(), outDir);
20834
21199
  for (const file2 of generated.files) {
20835
21200
  const targetPath = path2.resolve(absoluteOutDir, file2.path);