@carthooks/arcubase-cli 0.1.24 → 0.1.25

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.
@@ -1,319 +1,13 @@
1
+ import { ArcubaseCodeGenError, generateArcubaseProjectSDK as generateArcubaseProjectSDKCore, } from '@arcubase/code-gen';
1
2
  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);
3
+ export function generateArcubaseProjectSDK(payload) {
4
+ try {
5
+ return generateArcubaseProjectSDKCore(payload);
96
6
  }
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);
7
+ catch (error) {
8
+ if (error instanceof ArcubaseCodeGenError) {
9
+ throw new CLIError(error.code, error.message, 2);
119
10
  }
11
+ throw error;
120
12
  }
121
13
  }
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;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"}
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;AA6sED,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"}
@@ -267,7 +267,7 @@ function renderDevSDKGenHelp() {
267
267
  ' - arcubase-admin dev sdk-gen --app-id <app_id> --out src/arcubase',
268
268
  '',
269
269
  'requirements:',
270
- ' - missing field.key values are initialized through admin auth as field<id>',
270
+ ' - every generated field must have a configured field.key; sdk-gen fails fast when keys are missing',
271
271
  ' - existing field.key values must be stable unique TypeScript identifiers',
272
272
  ' - entity property names use entity.key when available, otherwise a stable entity<id> fallback',
273
273
  ' - generated code accepts createArcubaseSdk({ baseURL, accessToken, refreshToken })',
@@ -1647,45 +1647,37 @@ function walkSDKGenFields(fields, visit) {
1647
1647
  walkSDKGenFields(field.children, visit);
1648
1648
  }
1649
1649
  }
1650
- function collectExistingSDKGenFieldKeys(entity) {
1651
- const keys = new Set();
1652
- walkSDKGenFields(entity.fields, (field) => {
1653
- if (typeof field.key === 'string' && field.key.trim() !== '') {
1654
- keys.add(field.key.trim());
1655
- }
1656
- });
1657
- return keys;
1658
- }
1659
- function nextSDKGenFallbackFieldKey(fieldId, usedKeys) {
1660
- const base = `field${fieldId}`;
1661
- if (!usedKeys.has(base)) {
1662
- usedKeys.add(base);
1663
- return base;
1664
- }
1665
- let index = 2;
1666
- while (usedKeys.has(`${base}_${index}`)) {
1667
- index++;
1668
- }
1669
- const key = `${base}_${index}`;
1670
- usedKeys.add(key);
1671
- return key;
1672
- }
1673
- function initializeMissingSDKGenFieldKeys(entity) {
1674
- const usedKeys = collectExistingSDKGenFieldKeys(entity);
1675
- const updates = [];
1650
+ function assertSDKGenFieldKeysPresent(entity) {
1651
+ const missing = [];
1676
1652
  walkSDKGenFields(entity.fields, (field) => {
1677
1653
  if (typeof field.id !== 'number') {
1678
1654
  return;
1679
1655
  }
1680
1656
  if (typeof field.key === 'string' && field.key.trim() !== '') {
1681
- field.key = field.key.trim();
1682
1657
  return;
1683
1658
  }
1684
- const key = nextSDKGenFallbackFieldKey(field.id, usedKeys);
1685
- field.key = key;
1686
- updates.push({ id: field.id, key });
1659
+ missing.push({
1660
+ id: field.id,
1661
+ label: typeof field.label === 'string' && field.label.trim() ? field.label.trim() : `field ${field.id}`,
1662
+ });
1663
+ });
1664
+ if (missing.length === 0) {
1665
+ return;
1666
+ }
1667
+ const entityName = typeof entity.name === 'string' && entity.name.trim() ? entity.name.trim() : `entity ${String(entity.id ?? '')}`.trim();
1668
+ const preview = missing.slice(0, 8).map((field) => `${entityName}.${field.label} (${field.id})`);
1669
+ const suffix = missing.length > preview.length ? `, and ${missing.length - preview.length} more` : '';
1670
+ throw new CLIError('SDK_GEN_FIELD_KEY_REQUIRED', `missing field.key values: ${preview.join(', ')}${suffix}`, 2, {
1671
+ operation: 'dev sdk-gen',
1672
+ issues: missing.map((field) => ({
1673
+ path: `entity.${String(entity.id ?? 'unknown')}.field.${field.id}.key`,
1674
+ message: `field.key is required for ${entityName}.${field.label}`,
1675
+ })),
1676
+ suggestions: [
1677
+ 'set stable field.key values in Arcubase admin before running sdk-gen',
1678
+ 'rerun arcubase-admin dev sdk-gen after the schema keys are complete',
1679
+ ],
1687
1680
  });
1688
- return updates;
1689
1681
  }
1690
1682
  async function executeDevSDKGen(runtimeEnv, flags, fetchImpl) {
1691
1683
  validateDevSDKGenFlags(flags);
@@ -1714,7 +1706,6 @@ async function executeDevSDKGen(runtimeEnv, flags, fetchImpl) {
1714
1706
  throw new CLIError('SDK_GEN_NO_ENTITIES', 'sdk-gen could not find entities in app detail response', 2);
1715
1707
  }
1716
1708
  const entities = [];
1717
- const initializedFieldKeys = [];
1718
1709
  for (const entityRef of entityRefs) {
1719
1710
  const entityEndpoint = `/api/apps/${encodeURIComponent(appId)}/entity/${encodeURIComponent(entityRef.id)}`;
1720
1711
  const entityDetail = await requestJSON(runtimeEnv, 'GET', entityEndpoint, undefined, fetchImpl);
@@ -1722,15 +1713,7 @@ async function executeDevSDKGen(runtimeEnv, flags, fetchImpl) {
1722
1713
  if (!isRecord(entityPayload)) {
1723
1714
  throw new CLIError('SDK_GEN_INVALID_SCHEMA', 'sdk-gen expected entity detail response data', 2);
1724
1715
  }
1725
- const customKeyUpdates = initializeMissingSDKGenFieldKeys(entityPayload);
1726
- if (customKeyUpdates.length > 0) {
1727
- const customKeysEndpoint = `/api/apps/${encodeURIComponent(appId)}/entity/${encodeURIComponent(entityRef.id)}/custom-keys`;
1728
- await requestJSON(runtimeEnv, 'PUT', customKeysEndpoint, customKeyUpdates, fetchImpl);
1729
- initializedFieldKeys.push({
1730
- entityId: typeof entityPayload.id === 'number' ? entityPayload.id : Number(entityRef.id),
1731
- fields: customKeyUpdates.map((item) => item.key),
1732
- });
1733
- }
1716
+ assertSDKGenFieldKeysPresent(entityPayload);
1734
1717
  entities.push(entityPayload);
1735
1718
  }
1736
1719
  const generated = generateArcubaseProjectSDK({
@@ -1754,7 +1737,6 @@ async function executeDevSDKGen(runtimeEnv, flags, fetchImpl) {
1754
1737
  data: {
1755
1738
  out: absoluteOutDir,
1756
1739
  files: generated.files.map((file) => file.path),
1757
- initializedFieldKeys,
1758
1740
  },
1759
1741
  };
1760
1742
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@carthooks/arcubase-cli",
3
- "version": "0.1.24",
3
+ "version": "0.1.25",
4
4
  "description": "Arcubase runtime CLI for admin and user command execution",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -31,6 +31,7 @@
31
31
  "docs:llms": "node -e \"\""
32
32
  },
33
33
  "dependencies": {
34
+ "@arcubase/code-gen": "^0.1.0",
34
35
  "@carthooks/client-ts": "0.8.0",
35
36
  "zod": "^4.4.3"
36
37
  },
@@ -8,10 +8,16 @@ arcubase-admin dev sdk-gen \
8
8
  --out src/arcubase
9
9
  ```
10
10
 
11
- The command reads the app schema from Arcubase through the normal `arcubase-admin` runtime auth and writes project-local source files under `--out`.
11
+ The command reads the app schema from Arcubase through the normal
12
+ `arcubase-admin` runtime auth and writes project-local source files under
13
+ `--out`. This is the direct Arcubase admin/runtime path.
14
+
15
+ Workbench local ordinary-user codegen must not receive Arcubase admin tokens.
16
+ It should use the BotWorks Host/JCode schema flow documented in
17
+ `docs/design/workbench/03-workbench-vibe-coding-arcubase-codegen.md`.
12
18
 
13
19
  Generated code is key based. Application code uses stable `field.key` names, while generated runtime code converts those keys to internal numeric field ids.
14
- If a field is missing `field.key`, the command initializes it through `PUT /api/apps/:app_id/entity/:entity_id/custom-keys` with a stable `field<id>` fallback before generation.
20
+ If any field is missing `field.key`, the command fails before generation and prints the missing fields. Set stable keys in Arcubase admin, then rerun the command.
15
21
 
16
22
  ```ts
17
23
  import { createArcubaseSdk } from './arcubase'
@@ -31,9 +37,14 @@ await sdk.story.update(rowId, {
31
37
  })
32
38
  ```
33
39
 
40
+ The direct `baseURL` and token construction above is for direct Arcubase
41
+ runtime consumers. Workbench browser runtime should wrap generated SDK calls
42
+ with BotWorks Workbench auth and the Host Arcubase proxy instead of exposing
43
+ Arcubase runtime credentials in browser code.
44
+
34
45
  Rules:
35
46
 
36
- - missing `field.key` values are initialized through admin auth as `field<id>`
47
+ - every visible SDK field must have a configured `field.key`; sdk-gen does not invent fallback keys
37
48
  - existing `field.key` values must be stable unique TypeScript identifiers
38
49
  - `field.key` is a developer API contract and should not track display labels
39
50
  - entity property names use `entity.key` when available, otherwise a stable `entity<id>` fallback