@carthooks/arcubase-cli 0.1.24 → 0.1.26

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.
@@ -16529,10 +16529,10 @@ var PermitFieldPermitSchema = external_exports.lazy(() => external_exports.objec
16529
16529
  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
16530
  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
16531
  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());
16532
+ 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
16533
  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());
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(), "key": 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(), "key": external_exports.string().optional(), "name": external_exports.string().optional(), "options": EntityIngressOptionsSchema.optional(), "update": external_exports.array(external_exports.string()).optional() }).strict());
16536
16536
  var WidgetsTypeCodeSchema = external_exports.lazy(() => external_exports.union([external_exports.literal(1), external_exports.literal(2)]));
16537
16537
  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
16538
  var AppNewWidgetsReqVOSchema = external_exports.lazy(() => external_exports.object({ "options": WidgetsTypeSchema.optional(), "type": external_exports.string().optional() }).strict());
@@ -18690,6 +18690,7 @@ function renderCommandHelpExamples(scope, command) {
18690
18690
  }
18691
18691
 
18692
18692
  // src/runtime/dev_sdk_gen.ts
18693
+ var identifierPattern = /^[A-Za-z][A-Za-z0-9_]*$/;
18693
18694
  function isRecord3(value) {
18694
18695
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
18695
18696
  }
@@ -18698,17 +18699,9 @@ function unwrapData(payload) {
18698
18699
  }
18699
18700
  function toPascalCase(value) {
18700
18701
  const words = value.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[^A-Za-z0-9]+/).filter(Boolean);
18701
- if (words.length === 0) return "Entity";
18702
+ if (words.length === 0) return "Ingress";
18702
18703
  return words.map((word) => `${word[0].toUpperCase()}${word.slice(1)}`).join("");
18703
18704
  }
18704
- function toCamelCase(value) {
18705
- const pascal = toPascalCase(value);
18706
- return `${pascal[0].toLowerCase()}${pascal.slice(1)}`;
18707
- }
18708
- function sanitizeEntityKey(entity) {
18709
- const fallback = entity.sdkName || `entity_${entity.id}`;
18710
- return toCamelCase(fallback) === "entity" ? `entity${entity.id}` : toCamelCase(fallback);
18711
- }
18712
18705
  function normalizeFieldType(type) {
18713
18706
  switch (type) {
18714
18707
  case "number":
@@ -18735,65 +18728,122 @@ function normalizeFieldType(type) {
18735
18728
  return "string";
18736
18729
  }
18737
18730
  }
18738
- function normalizeEntities(payload) {
18731
+ function normalizeField(field) {
18732
+ if (typeof field.id !== "number" || typeof field.label !== "string" || typeof field.type !== "string") {
18733
+ return void 0;
18734
+ }
18735
+ const children = Array.isArray(field.children) ? field.children.filter((child) => isRecord3(child)).map(normalizeField).filter((child) => Boolean(child)) : void 0;
18736
+ return {
18737
+ id: field.id,
18738
+ label: field.label,
18739
+ key: typeof field.key === "string" ? field.key.trim() : "",
18740
+ type: field.type,
18741
+ required: field.required === true,
18742
+ options: isRecord3(field.options) ? field.options : void 0,
18743
+ children
18744
+ };
18745
+ }
18746
+ function normalizeEntity(payload, appId) {
18747
+ if (!isRecord3(payload) || typeof payload.id !== "number" || typeof payload.name !== "string") {
18748
+ throw new CLIError("SDK_GEN_INVALID_SCHEMA", "sdk-gen expected ingress.entity with id and name", 2);
18749
+ }
18750
+ const fields = Array.isArray(payload.fields) ? payload.fields.filter((field) => isRecord3(field)).map(normalizeField).filter((field) => Boolean(field)) : [];
18751
+ return {
18752
+ appId,
18753
+ id: payload.id,
18754
+ name: payload.name,
18755
+ fields
18756
+ };
18757
+ }
18758
+ function normalizeActions(ingress) {
18759
+ const policy = isRecord3(ingress.policy) ? ingress.policy : isRecord3(ingress.options) && isRecord3(ingress.options.policy) ? ingress.options.policy : void 0;
18760
+ const source = Array.isArray(ingress.actions) ? ingress.actions : Array.isArray(policy?.actions) ? policy.actions : [];
18761
+ return source.filter((action) => typeof action === "string" && action.trim() !== "").map((action) => action.trim());
18762
+ }
18763
+ function normalizeIngresses(payload) {
18739
18764
  const root = unwrapData(payload);
18740
18765
  if (!isRecord3(root)) {
18741
- throw new CLIError("SDK_GEN_INVALID_SCHEMA", "sdk-gen expected an app detail object", 2);
18766
+ throw new CLIError("SDK_GEN_INVALID_SCHEMA", "sdk-gen expected an app ingress schema object", 2);
18742
18767
  }
18743
18768
  const appId = typeof root.id === "number" ? root.id : typeof root.app_id === "number" ? root.app_id : void 0;
18744
18769
  if (!appId) {
18745
- throw new CLIError("SDK_GEN_APP_ID_REQUIRED", "sdk-gen expected app id in app detail response", 2);
18770
+ throw new CLIError("SDK_GEN_APP_ID_REQUIRED", "sdk-gen expected app id in schema payload", 2);
18746
18771
  }
18747
- const sourceEntities = Array.isArray(root.entities) ? root.entities : Array.isArray(root.tables) ? root.tables : Array.isArray(root.entitys) ? root.entitys : [];
18748
- const entities = sourceEntities.filter((item) => isRecord3(item)).filter((item) => typeof item.id === "number" && typeof item.name === "string").map((item) => {
18749
- 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) => ({
18750
- id: field.id,
18751
- label: field.label,
18752
- key: typeof field.key === "string" ? field.key.trim() : "",
18753
- type: field.type,
18754
- required: field.required === true,
18755
- options: isRecord3(field.options) ? field.options : void 0
18756
- })) : [];
18757
- const entity = {
18772
+ const sourceIngresses = Array.isArray(root.ingresses) ? root.ingresses : [];
18773
+ if (sourceIngresses.length === 0) {
18774
+ throw new CLIError("SDK_GEN_NO_INGRESSES", "sdk-gen could not find ingresses in schema payload", 2);
18775
+ }
18776
+ const ingresses = sourceIngresses.filter((item) => isRecord3(item)).map((item) => {
18777
+ const entity = normalizeEntity(item.entity, appId);
18778
+ const options = isRecord3(item.options) ? item.options : {};
18779
+ const hashId = typeof item.hash_id === "string" ? item.hash_id.trim() : typeof item.hashId === "string" ? item.hashId.trim() : "";
18780
+ return {
18758
18781
  appId,
18759
- id: item.id,
18760
- name: item.name,
18761
- sdkName: typeof item.key === "string" && item.key.trim() ? item.key.trim() : "",
18762
- fields
18782
+ id: typeof item.id === "number" ? item.id : void 0,
18783
+ hashId,
18784
+ key: typeof item.key === "string" ? item.key.trim() : "",
18785
+ name: typeof item.name === "string" && item.name.trim() ? item.name.trim() : String(item.key ?? ""),
18786
+ actions: normalizeActions(item),
18787
+ entity,
18788
+ listOptions: isRecord3(options.list_options) ? options.list_options : isRecord3(item.list_options) ? item.list_options : void 0
18763
18789
  };
18764
- entity.sdkName = sanitizeEntityKey(entity);
18765
- return entity;
18766
18790
  });
18767
- if (entities.length === 0) {
18768
- throw new CLIError("SDK_GEN_NO_ENTITIES", "sdk-gen could not find entities in app detail response", 2);
18791
+ validateIngresses(ingresses);
18792
+ return ingresses;
18793
+ }
18794
+ function walkFields(fields, visit) {
18795
+ for (const field of fields) {
18796
+ visit(field);
18797
+ if (field.children && field.children.length > 0) {
18798
+ walkFields(field.children, visit);
18799
+ }
18769
18800
  }
18770
- validateEntities(entities);
18771
- return entities;
18772
18801
  }
18773
- function validateEntities(entities) {
18774
- const entityKeys = /* @__PURE__ */ new Set();
18775
- for (const entity of entities) {
18776
- if (entityKeys.has(entity.sdkName)) {
18777
- throw new CLIError("SDK_GEN_DUPLICATE_ENTITY_KEY", `duplicate generated entity key: ${entity.sdkName}`, 2);
18802
+ function allFields(entity) {
18803
+ const out = [];
18804
+ walkFields(entity.fields, (field) => out.push(field));
18805
+ return out;
18806
+ }
18807
+ function validateIngresses(ingresses) {
18808
+ const ingressKeys = /* @__PURE__ */ new Set();
18809
+ for (const ingress of ingresses) {
18810
+ if (!ingress.key) {
18811
+ throw new CLIError("SDK_GEN_INGRESS_KEY_REQUIRED", `ingress.key is required for ${ingress.name || "unnamed ingress"}`, 2);
18812
+ }
18813
+ if (!identifierPattern.test(ingress.key)) {
18814
+ throw new CLIError("SDK_GEN_INVALID_INGRESS_KEY", `ingress.key must be a TypeScript identifier: ${ingress.key}`, 2);
18815
+ }
18816
+ if (ingressKeys.has(ingress.key)) {
18817
+ throw new CLIError("SDK_GEN_DUPLICATE_INGRESS_KEY", `duplicate ingress.key: ${ingress.key}`, 2);
18818
+ }
18819
+ ingressKeys.add(ingress.key);
18820
+ if (!ingress.hashId) {
18821
+ throw new CLIError("SDK_GEN_INGRESS_HASH_ID_REQUIRED", `ingress.hash_id is required for ${ingress.key}`, 2);
18778
18822
  }
18779
- entityKeys.add(entity.sdkName);
18780
18823
  const fieldKeys = /* @__PURE__ */ new Set();
18781
- for (const field of entity.fields) {
18824
+ for (const field of allFields(ingress.entity)) {
18782
18825
  if (!field.key) {
18783
- throw new CLIError("SDK_GEN_FIELD_KEY_REQUIRED", `field.key is required for ${entity.name}.${field.label} (${field.id})`, 2);
18826
+ throw new CLIError("SDK_GEN_FIELD_KEY_REQUIRED", `field.key is required for ${ingress.entity.name}.${field.label} (${field.id})`, 2);
18784
18827
  }
18785
- if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(field.key)) {
18786
- throw new CLIError("SDK_GEN_INVALID_FIELD_KEY", `field.key must be a TypeScript identifier: ${entity.name}.${field.key}`, 2);
18828
+ if (!identifierPattern.test(field.key)) {
18829
+ throw new CLIError("SDK_GEN_INVALID_FIELD_KEY", `field.key must be a TypeScript identifier: ${ingress.entity.name}.${field.key}`, 2);
18787
18830
  }
18788
18831
  if (fieldKeys.has(field.key)) {
18789
- throw new CLIError("SDK_GEN_DUPLICATE_FIELD_KEY", `duplicate field.key in ${entity.name}: ${field.key}`, 2);
18832
+ throw new CLIError("SDK_GEN_DUPLICATE_FIELD_KEY", `duplicate field.key in ${ingress.entity.name}: ${field.key}`, 2);
18790
18833
  }
18791
18834
  fieldKeys.add(field.key);
18792
18835
  }
18793
18836
  }
18794
18837
  }
18838
+ function hasAction(ingress, action) {
18839
+ return ingress.actions.includes(action);
18840
+ }
18841
+ function methodLines(lines) {
18842
+ return lines.length > 0 ? `${lines.join("\n\n")}
18843
+ ` : "";
18844
+ }
18795
18845
  function emitRuntime() {
18796
- return `export type ArcubaseSdkConfig = {
18846
+ return `export type ArcubaseClientConfig = {
18797
18847
  baseURL: string
18798
18848
  accessToken?: string
18799
18849
  refreshToken?: string
@@ -18807,7 +18857,6 @@ export type ArcubaseRow<TFields> = {
18807
18857
  }
18808
18858
 
18809
18859
  export type ArcubaseRuntime = {
18810
- config: ArcubaseSdkConfig
18811
18860
  request<T>(method: string, endpoint: string, body?: unknown): Promise<T>
18812
18861
  }
18813
18862
 
@@ -18816,7 +18865,7 @@ type ArcubaseEnvelope<T> = {
18816
18865
  error?: { message?: string; key?: string; type?: string }
18817
18866
  }
18818
18867
 
18819
- export function createRuntime(config: ArcubaseSdkConfig): ArcubaseRuntime {
18868
+ export function createRuntime(config: ArcubaseClientConfig): ArcubaseRuntime {
18820
18869
  let accessToken = config.accessToken ?? ''
18821
18870
  let refreshToken = config.refreshToken ?? ''
18822
18871
 
@@ -18827,6 +18876,7 @@ export function createRuntime(config: ArcubaseSdkConfig): ArcubaseRuntime {
18827
18876
  headers: {
18828
18877
  'Content-Type': 'application/json',
18829
18878
  ...(accessToken ? { Authorization: \`Bearer \${accessToken}\` } : {}),
18879
+ ...(refreshToken ? { 'X-Arcubase-Refresh-Token': refreshToken } : {}),
18830
18880
  },
18831
18881
  body: body === undefined ? undefined : JSON.stringify(body),
18832
18882
  })
@@ -18837,21 +18887,72 @@ export function createRuntime(config: ArcubaseSdkConfig): ArcubaseRuntime {
18837
18887
  return payload?.data as T
18838
18888
  }
18839
18889
 
18840
- return {
18841
- config: { ...config, accessToken, refreshToken },
18842
- request,
18890
+ return { request }
18891
+ }
18892
+
18893
+ export function toArcubaseFields<T extends Record<string, any>>(fields: Partial<T>, fieldIds: Record<string, number>): Record<string, any> {
18894
+ const out: Record<string, any> = {}
18895
+ for (const [key, value] of Object.entries(fields)) {
18896
+ if (value === undefined) continue
18897
+ const fieldId = fieldIds[key]
18898
+ if (!fieldId) {
18899
+ throw new Error(\`Unknown Arcubase field: \${key}\`)
18900
+ }
18901
+ out[String(fieldId)] = value
18902
+ }
18903
+ return out
18904
+ }
18905
+
18906
+ export function fromArcubaseFields<T>(raw: Record<string, any> | undefined, fieldIds: Record<string, number>): T {
18907
+ const out: Record<string, any> = {}
18908
+ const source = raw ?? {}
18909
+ for (const [key, fieldId] of Object.entries(fieldIds)) {
18910
+ if (String(fieldId) in source) {
18911
+ out[key] = source[String(fieldId)]
18912
+ }
18913
+ }
18914
+ return out as T
18915
+ }
18916
+
18917
+ export function toArcubaseColumn(column: string, fieldIds: Record<string, number>): string {
18918
+ return fieldIds[column] ? \`:\${fieldIds[column]}\` : column
18919
+ }
18920
+
18921
+ export function mapArcubaseCondition(value: unknown, fieldIds: Record<string, number>): unknown {
18922
+ if (Array.isArray(value)) {
18923
+ return value.map((item) => mapArcubaseCondition(item, fieldIds))
18924
+ }
18925
+ if (!value || typeof value !== 'object') {
18926
+ return value
18843
18927
  }
18928
+ const out: Record<string, unknown> = {}
18929
+ for (const [key, child] of Object.entries(value as Record<string, unknown>)) {
18930
+ if (key === 'column' && typeof child === 'string') {
18931
+ out[key] = toArcubaseColumn(child, fieldIds)
18932
+ } else {
18933
+ const mappedKey = fieldIds[key] ? \`:\${fieldIds[key]}\` : key
18934
+ out[mappedKey] = mapArcubaseCondition(child, fieldIds)
18935
+ }
18936
+ }
18937
+ return out
18938
+ }
18939
+
18940
+ export function mapArcubaseSorts<T extends { column: string; order?: string }>(sorts: T[] | undefined, fieldIds: Record<string, number>): T[] | undefined {
18941
+ return sorts?.map((sort) => ({ ...sort, column: toArcubaseColumn(sort.column, fieldIds) }))
18844
18942
  }
18845
18943
  `;
18846
18944
  }
18847
- function emitSchema(entities) {
18945
+ function emitSchema(ingresses) {
18848
18946
  const schema = {
18849
- entities: Object.fromEntries(entities.map((entity) => [
18850
- entity.sdkName,
18947
+ ingresses: Object.fromEntries(ingresses.map((ingress) => [
18948
+ ingress.key,
18851
18949
  {
18852
- entityId: entity.id,
18853
- name: entity.name,
18854
- fields: Object.fromEntries(entity.fields.map((field) => [
18950
+ appId: ingress.appId,
18951
+ ingressId: ingress.hashId,
18952
+ entityId: ingress.entity.id,
18953
+ name: ingress.name,
18954
+ actions: ingress.actions,
18955
+ fields: Object.fromEntries(allFields(ingress.entity).map((field) => [
18855
18956
  field.key,
18856
18957
  {
18857
18958
  fieldId: field.id,
@@ -18865,17 +18966,177 @@ function emitSchema(entities) {
18865
18966
  };
18866
18967
  return `export const arcubaseSchema = ${JSON.stringify(schema, null, 2)} as const
18867
18968
 
18868
- export type ArcubaseEntityKey = keyof typeof arcubaseSchema.entities
18969
+ export type ArcubaseIngressKey = keyof typeof arcubaseSchema.ingresses
18869
18970
  `;
18870
18971
  }
18871
- function emitEntity(entity) {
18872
- const typeName = toPascalCase(entity.sdkName);
18873
- const fieldLines = entity.fields.map((field) => {
18972
+ function emitIngress(ingress) {
18973
+ const typeName = toPascalCase(ingress.key);
18974
+ const fields = allFields(ingress.entity);
18975
+ const fieldLines = fields.map((field) => {
18874
18976
  const optional2 = field.required ? "" : "?";
18875
18977
  return ` ${field.key}${optional2}: ${normalizeFieldType(field.type)}`;
18876
18978
  });
18877
- const fieldMapLines = entity.fields.map((field) => ` ${field.key}: ${field.id},`);
18878
- return `import type { ArcubaseRuntime, ArcubaseRow } from '../runtime.js'
18979
+ const fieldMapLines = fields.map((field) => ` ${field.key}: ${field.id},`);
18980
+ const methodChunks = [];
18981
+ if (hasAction(ingress, "view")) {
18982
+ methodChunks.push(` async query(params: ${typeName}QueryParams = {}): Promise<{ items: ArcubaseRow<${typeName}Fields>[]; total?: number; offset?: number; limit?: number }> {
18983
+ const data = await runtime.request<{ items?: Record<string, any>[]; total?: number; offset?: number; limit?: number }>('POST', \`/entity/${ingress.appId}/${ingress.entity.id}/data-query\`, {
18984
+ ...params,
18985
+ policy_id: ingressId,
18986
+ search: mapArcubaseCondition(params.search, fieldIds),
18987
+ sorts: mapArcubaseSorts(params.sorts, fieldIds),
18988
+ })
18989
+ return {
18990
+ items: (data.items ?? []).map(mapRow),
18991
+ total: data.total,
18992
+ offset: data.offset,
18993
+ limit: data.limit,
18994
+ }
18995
+ },
18996
+
18997
+ async get(rowId: string | number): Promise<ArcubaseRow<${typeName}Fields>> {
18998
+ const data = await runtime.request<{ record?: Record<string, any> }>('GET', \`/entity/${ingress.appId}/${ingress.entity.id}/row/\${rowId}?policy_id=\${encodeURIComponent(ingressId)}\`)
18999
+ return mapRow(data.record ?? {})
19000
+ },`);
19001
+ }
19002
+ if (hasAction(ingress, "add")) {
19003
+ methodChunks.push(` async prepareCreate(): Promise<Record<string, any>> {
19004
+ return runtime.request<Record<string, any>>('POST', \`/entity/${ingress.appId}/${ingress.entity.id}/prepare\`, {
19005
+ policy_id: ingressId,
19006
+ })
19007
+ },
19008
+
19009
+ async create(fields: ${typeName}CreateInput): Promise<number> {
19010
+ return runtime.request<number>('POST', \`/entity/${ingress.appId}/${ingress.entity.id}/insert\`, {
19011
+ fields: toArcubaseFields(fields, fieldIds),
19012
+ policy_id: ingressId,
19013
+ })
19014
+ },`);
19015
+ }
19016
+ if (hasAction(ingress, "edit")) {
19017
+ methodChunks.push(` async update(rowId: string | number, fields: ${typeName}UpdateInput): Promise<boolean> {
19018
+ const data = toArcubaseFields(fields, fieldIds)
19019
+ return runtime.request<boolean>('PUT', \`/entity/${ingress.appId}/${ingress.entity.id}/row/\${rowId}\`, {
19020
+ data,
19021
+ changed_fields: Object.keys(data).map((fieldId) => Number(fieldId)),
19022
+ policy_id: ingressId,
19023
+ })
19024
+ },`);
19025
+ }
19026
+ if (hasAction(ingress, "delete")) {
19027
+ methodChunks.push(` async delete(selection: ${typeName}Selection): Promise<{ deleted?: number } | boolean> {
19028
+ return runtime.request<{ deleted?: number } | boolean>('POST', \`/entity/${ingress.appId}/${ingress.entity.id}/delete-item\`, {
19029
+ policy_id: ingressId,
19030
+ selection: toArcubaseSelection(selection),
19031
+ })
19032
+ },`);
19033
+ }
19034
+ if (hasAction(ingress, "bulk_update")) {
19035
+ methodChunks.push(` async bulkUpdate(selection: ${typeName}Selection, fields: ${typeName}BulkUpdateField[]): Promise<{ updated: number }> {
19036
+ return runtime.request<{ updated: number }>('POST', \`/entity/${ingress.appId}/${ingress.entity.id}/bulk-update\`, {
19037
+ policy_id: ingressId,
19038
+ selection: toArcubaseSelection(selection),
19039
+ fields: fields.map((field) => ({
19040
+ id: fieldIds[field.key],
19041
+ action: field.action,
19042
+ })),
19043
+ })
19044
+ },
19045
+
19046
+ async saveBulkUpdate(input: { name: string; subject: string; fields: ${typeName}BulkUpdateField[] }): Promise<string> {
19047
+ return runtime.request<string>('POST', \`/entity/${ingress.appId}/${ingress.entity.id}/bulk-update-save-as\`, {
19048
+ name: input.name,
19049
+ subject: input.subject,
19050
+ fields: input.fields.map((field) => ({
19051
+ id: fieldIds[field.key],
19052
+ action: field.action,
19053
+ })),
19054
+ })
19055
+ },`);
19056
+ }
19057
+ if (hasAction(ingress, "export")) {
19058
+ methodChunks.push(` async exportRows(options: ${typeName}ExportOptions = {}): Promise<{ task_id: string }> {
19059
+ const fieldKeys = options.fields ?? Object.keys(fieldIds) as ${typeName}FieldKey[]
19060
+ return runtime.request<{ task_id: string }>('POST', \`/entity/${ingress.appId}/${ingress.entity.id}/export\`, {
19061
+ policy_id: ingressId,
19062
+ props: options.props ?? [],
19063
+ fields: fieldKeys.map((key) => fieldIds[key]),
19064
+ propnames: options.propNames ?? {},
19065
+ options: { has_id: options.hasIdField ?? false },
19066
+ })
19067
+ },
19068
+
19069
+ async exportTask(taskId: string): Promise<Record<string, any>> {
19070
+ return runtime.request<Record<string, any>>('GET', \`/export-task/\${taskId}\`)
19071
+ },`);
19072
+ }
19073
+ if (hasAction(ingress, "batch_print")) {
19074
+ methodChunks.push(` async batchPrint(selection: ${typeName}Selection, params: { limit?: number; offset?: number } = {}): Promise<{ items?: Record<string, any>[]; total?: number }> {
19075
+ return runtime.request<{ items?: Record<string, any>[]; total?: number }>('POST', \`/entity/${ingress.appId}/${ingress.entity.id}/selection-query/batch_print\`, {
19076
+ ...params,
19077
+ policy_id: ingressId,
19078
+ selection: toArcubaseSelection(selection),
19079
+ })
19080
+ },`);
19081
+ }
19082
+ if (hasAction(ingress, "archive")) {
19083
+ methodChunks.push(` async archive(selection: ${typeName}Selection): Promise<{ archived?: number } | boolean> {
19084
+ return runtime.request<{ archived?: number } | boolean>('POST', \`/entity/${ingress.appId}/${ingress.entity.id}/selection-query/archive\`, {
19085
+ policy_id: ingressId,
19086
+ selection: toArcubaseSelection(selection),
19087
+ })
19088
+ },`);
19089
+ }
19090
+ if (hasAction(ingress, "tag_manage")) {
19091
+ methodChunks.push(` tags: {
19092
+ async list(): Promise<string[]> {
19093
+ return runtime.request<string[]>('GET', \`/user-action-entity/${ingress.appId}/${ingress.entity.id}/tag-names\`)
19094
+ },
19095
+ async add(selection: ${typeName}Selection, tagNames: string[]): Promise<Record<string, any>> {
19096
+ return runtime.request<Record<string, any>>('POST', \`/user-action-entity/${ingress.appId}/${ingress.entity.id}/batch-add-tags\`, {
19097
+ policy_id: ingressId,
19098
+ selection: toArcubaseSelection(selection),
19099
+ tag_names: tagNames,
19100
+ })
19101
+ },
19102
+ async remove(selection: ${typeName}Selection, tagNames: string[]): Promise<boolean> {
19103
+ return runtime.request<boolean>('POST', \`/user-action-entity/${ingress.appId}/${ingress.entity.id}/batch-remove-tags\`, {
19104
+ policy_id: ingressId,
19105
+ selection: toArcubaseSelection(selection),
19106
+ tag_names: tagNames,
19107
+ })
19108
+ },
19109
+ async selection(selection: ${typeName}Selection): Promise<string[]> {
19110
+ return runtime.request<string[]>('POST', \`/user-action-entity/${ingress.appId}/${ingress.entity.id}/get-selection-tags\`, {
19111
+ policy_id: ingressId,
19112
+ selection: toArcubaseSelection(selection),
19113
+ })
19114
+ },
19115
+ },`);
19116
+ }
19117
+ const customActions = ingress.actions.filter((action) => !["view", "add", "edit", "delete", "bulk_update", "export", "batch_print", "archive", "tag_manage"].includes(action));
19118
+ if (customActions.length > 0) {
19119
+ methodChunks.push(` async action(action: ${typeName}CustomAction, payload: Record<string, any> = {}): Promise<Record<string, any>> {
19120
+ if (!allowedActions.includes(action)) {
19121
+ throw new Error(\`Action is not allowed for ${ingress.key}: \${action}\`)
19122
+ }
19123
+ return runtime.request<Record<string, any>>('POST', \`/entity/${ingress.appId}/${ingress.entity.id}/selection-query/\${encodeURIComponent(action)}\`, {
19124
+ ...payload,
19125
+ policy_id: ingressId,
19126
+ selection: payload.selection ? toArcubaseSelection(payload.selection) : undefined,
19127
+ })
19128
+ },`);
19129
+ }
19130
+ return `import {
19131
+ fromArcubaseFields,
19132
+ mapArcubaseCondition,
19133
+ mapArcubaseSorts,
19134
+ toArcubaseFields,
19135
+ type ArcubaseRow,
19136
+ type ArcubaseRuntime,
19137
+ } from '../runtime.js'
19138
+
19139
+ export type ${typeName}FieldKey = ${fields.map((field) => `'${field.key}'`).join(" | ")}
18879
19140
 
18880
19141
  export type ${typeName}Fields = {
18881
19142
  ${fieldLines.join("\n")}
@@ -18883,110 +19144,123 @@ ${fieldLines.join("\n")}
18883
19144
 
18884
19145
  export type ${typeName}CreateInput = ${typeName}Fields
18885
19146
  export type ${typeName}UpdateInput = Partial<${typeName}Fields>
19147
+ export type ${typeName}Action = ${ingress.actions.map((action) => `'${action}'`).join(" | ") || "never"}
19148
+ export type ${typeName}CustomAction = ${customActions.map((action) => `'${action}'`).join(" | ") || "never"}
18886
19149
 
18887
- const entityId = ${entity.id}
18888
- const fieldIds = {
18889
- ${fieldMapLines.join("\n")}
18890
- } as const
19150
+ export type ${typeName}Sort = {
19151
+ column: ${typeName}FieldKey | 'id' | 'created_at' | 'updated_at' | 'creator' | string
19152
+ order: 'asc' | 'desc'
19153
+ }
18891
19154
 
18892
- function toArcubaseFields(fields: Partial<${typeName}Fields>): Record<string, any> {
18893
- const out: Record<string, any> = {}
18894
- for (const [key, value] of Object.entries(fields)) {
18895
- if (value === undefined) continue
18896
- const fieldId = fieldIds[key as keyof typeof fieldIds]
18897
- if (!fieldId) {
18898
- throw new Error(\`Unknown ${entity.sdkName} field: \${key}\`)
18899
- }
18900
- out[String(fieldId)] = value
18901
- }
18902
- return out
19155
+ export type ${typeName}QueryParams = {
19156
+ limit?: number
19157
+ offset?: number
19158
+ search?: Record<string, any>
19159
+ sorts?: ${typeName}Sort[]
19160
+ view_mode?: 'normal' | 'deleted' | 'archived'
19161
+ withSubForm?: boolean
18903
19162
  }
18904
19163
 
18905
- function fromArcubaseFields(raw: Record<string, any> | undefined): ${typeName}Fields {
18906
- const out: Record<string, any> = {}
18907
- const source = raw ?? {}
18908
- for (const [key, fieldId] of Object.entries(fieldIds)) {
18909
- if (String(fieldId) in source) {
18910
- out[key] = source[String(fieldId)]
18911
- }
18912
- }
18913
- return out as ${typeName}Fields
19164
+ export type ${typeName}Selection = {
19165
+ type: 'ids' | 'condition'
19166
+ length?: number
19167
+ ids?: Array<number | string>
19168
+ condition?: Record<string, any>
19169
+ sorts?: ${typeName}Sort[]
19170
+ }
19171
+
19172
+ export type ${typeName}BulkUpdateField = {
19173
+ key: ${typeName}FieldKey
19174
+ action: Record<string, any>
19175
+ }
19176
+
19177
+ export type ${typeName}ExportOptions = {
19178
+ fields?: ${typeName}FieldKey[]
19179
+ props?: string[]
19180
+ propNames?: Record<string, string>
19181
+ hasIdField?: boolean
18914
19182
  }
18915
19183
 
19184
+ const ingressId = ${JSON.stringify(ingress.hashId)}
19185
+ const fieldIds = {
19186
+ ${fieldMapLines.join("\n")}
19187
+ } as const
19188
+ const allowedActions = ${JSON.stringify(customActions)} as const
19189
+
18916
19190
  function mapRow(raw: Record<string, any>): ArcubaseRow<${typeName}Fields> {
18917
19191
  return {
18918
19192
  id: Number(raw.id),
18919
19193
  title: typeof raw.title === 'string' ? raw.title : undefined,
18920
- fields: fromArcubaseFields(raw.fields),
19194
+ fields: fromArcubaseFields<${typeName}Fields>(raw.fields, fieldIds),
18921
19195
  raw,
18922
19196
  }
18923
19197
  }
18924
19198
 
18925
- export function create${typeName}SDK(runtime: ArcubaseRuntime) {
19199
+ function toArcubaseSelection(selection: ${typeName}Selection): Record<string, any> {
18926
19200
  return {
18927
- async create(fields: ${typeName}CreateInput): Promise<number> {
18928
- return runtime.request<number>('POST', \`/entity/${entity.appId}/\${entityId}/insert\`, {
18929
- fields: toArcubaseFields(fields),
18930
- })
18931
- },
18932
-
18933
- async update(rowId: string | number, fields: ${typeName}UpdateInput): Promise<boolean> {
18934
- const data = toArcubaseFields(fields)
18935
- return runtime.request<boolean>('PUT', \`/entity/${entity.appId}/\${entityId}/row/\${rowId}\`, {
18936
- data,
18937
- changed_fields: Object.keys(data).map((fieldId) => Number(fieldId)),
18938
- })
18939
- },
18940
-
18941
- async get(rowId: string | number): Promise<ArcubaseRow<${typeName}Fields>> {
18942
- const data = await runtime.request<{ record?: Record<string, any> }>('GET', \`/entity/${entity.appId}/\${entityId}/row/\${rowId}\`)
18943
- return mapRow(data.record ?? {})
18944
- },
18945
-
18946
- async query(params: { limit?: number; offset?: number; search?: Record<string, any> } = {}): Promise<{ items: ArcubaseRow<${typeName}Fields>[]; total?: number }> {
18947
- const data = await runtime.request<{ items?: Record<string, any>[]; total?: number }>('POST', \`/entity/${entity.appId}/\${entityId}/data-query\`, params)
18948
- return {
18949
- items: (data.items ?? []).map(mapRow),
18950
- total: data.total,
18951
- }
18952
- },
19201
+ ...selection,
19202
+ condition: mapArcubaseCondition(selection.condition ?? {}, fieldIds),
19203
+ sorts: mapArcubaseSorts(selection.sorts, fieldIds),
18953
19204
  }
18954
19205
  }
19206
+
19207
+ export function create${typeName}Ingress(runtime: ArcubaseRuntime) {
19208
+ return {
19209
+ ${methodLines(methodChunks)} }
19210
+ }
18955
19211
  `;
18956
19212
  }
18957
- function emitClient(entities) {
18958
- const imports = entities.map((entity) => {
18959
- const typeName = toPascalCase(entity.sdkName);
18960
- return `import { create${typeName}SDK } from './entities/${entity.sdkName}.js'`;
19213
+ function emitClient(ingresses) {
19214
+ const imports = ingresses.map((ingress) => {
19215
+ const typeName = toPascalCase(ingress.key);
19216
+ return `import { create${typeName}Ingress } from './ingresses/${ingress.key}.js'`;
18961
19217
  });
18962
- const properties = entities.map((entity) => ` ${entity.sdkName}: create${toPascalCase(entity.sdkName)}SDK(runtime),`);
18963
- return `import { createRuntime, type ArcubaseSdkConfig } from './runtime.js'
19218
+ const mapLines = ingresses.map((ingress) => ` ${JSON.stringify(ingress.key)}: ReturnType<typeof create${toPascalCase(ingress.key)}Ingress>`);
19219
+ const cases = ingresses.map((ingress) => ` case ${JSON.stringify(ingress.key)}:
19220
+ return create${toPascalCase(ingress.key)}Ingress(runtime) as ArcubaseIngressMap[K]`);
19221
+ return `import { createRuntime, type ArcubaseClientConfig } from './runtime.js'
18964
19222
  ${imports.join("\n")}
18965
19223
 
18966
- export function createArcubaseSdk(config: ArcubaseSdkConfig) {
19224
+ export type ArcubaseIngressMap = {
19225
+ ${mapLines.join("\n")}
19226
+ }
19227
+
19228
+ export type ArcubaseIngressKey = keyof ArcubaseIngressMap
19229
+
19230
+ export function createArcubaseClient(config: ArcubaseClientConfig) {
18967
19231
  const runtime = createRuntime(config)
18968
19232
  return {
18969
- ${properties.join("\n")}
19233
+ ingress<K extends ArcubaseIngressKey>(key: K): ArcubaseIngressMap[K] {
19234
+ switch (key) {
19235
+ ${cases.join("\n")}
19236
+ default:
19237
+ throw new Error(\`Unknown Arcubase ingress: \${String(key)}\`)
19238
+ }
19239
+ },
18970
19240
  }
18971
19241
  }
18972
19242
  `;
18973
19243
  }
18974
- function emitIndex(entities) {
18975
- return `export { createArcubaseSdk } from './client.js'
18976
- export type { ArcubaseSdkConfig, ArcubaseRow } from './runtime.js'
19244
+ function emitIndex(ingresses) {
19245
+ return `export { createArcubaseClient } from './client.js'
19246
+ export type { ArcubaseClientConfig, ArcubaseRow } from './runtime.js'
18977
19247
  export { arcubaseSchema } from './schema.js'
18978
- ${entities.map((entity) => `export type { ${toPascalCase(entity.sdkName)}Fields, ${toPascalCase(entity.sdkName)}CreateInput, ${toPascalCase(entity.sdkName)}UpdateInput } from './entities/${entity.sdkName}.js'`).join("\n")}
19248
+ export type { ArcubaseIngressKey, ArcubaseIngressMap } from './client.js'
19249
+ ${ingresses.map((ingress) => {
19250
+ const typeName = toPascalCase(ingress.key);
19251
+ return `export type { ${typeName}Fields, ${typeName}CreateInput, ${typeName}UpdateInput, ${typeName}FieldKey, ${typeName}Selection } from './ingresses/${ingress.key}.js'`;
19252
+ }).join("\n")}
18979
19253
  `;
18980
19254
  }
18981
19255
  function generateArcubaseProjectSDK(payload) {
18982
- const entities = normalizeEntities(payload);
19256
+ const ingresses = normalizeIngresses(payload);
18983
19257
  return {
18984
19258
  files: [
18985
19259
  { path: "runtime.ts", contents: emitRuntime() },
18986
- { path: "schema.ts", contents: emitSchema(entities) },
18987
- ...entities.map((entity) => ({ path: `entities/${entity.sdkName}.ts`, contents: emitEntity(entity) })),
18988
- { path: "client.ts", contents: emitClient(entities) },
18989
- { path: "index.ts", contents: emitIndex(entities) }
19260
+ { path: "schema.ts", contents: emitSchema(ingresses) },
19261
+ ...ingresses.map((ingress) => ({ path: `ingresses/${ingress.key}.ts`, contents: emitIngress(ingress) })),
19262
+ { path: "client.ts", contents: emitClient(ingresses) },
19263
+ { path: "index.ts", contents: emitIndex(ingresses) }
18990
19264
  ]
18991
19265
  };
18992
19266
  }
@@ -19222,10 +19496,10 @@ function renderDevSDKGenHelp() {
19222
19496
  " - arcubase-admin dev sdk-gen --app-id <app_id> --out src/arcubase",
19223
19497
  "",
19224
19498
  "requirements:",
19225
- " - missing field.key values are initialized through admin auth as field<id>",
19226
- " - existing field.key values must be stable unique TypeScript identifiers",
19227
- " - entity property names use entity.key when available, otherwise a stable entity<id> fallback",
19228
- " - generated code accepts createArcubaseSdk({ baseURL, accessToken, refreshToken })"
19499
+ " - every generated ingress must have a configured ingress.key; sdk-gen fails fast when keys are missing",
19500
+ " - every generated field must have a configured field.key; sdk-gen fails fast when keys are missing",
19501
+ " - ingress.key and field.key values must be stable unique TypeScript identifiers",
19502
+ ' - generated code accepts createArcubaseClient({ baseURL, accessToken, refreshToken }).ingress("<IngressKey>")'
19229
19503
  ].join("\n");
19230
19504
  }
19231
19505
  function resolveEndpoint(command, flags) {
@@ -20735,45 +21009,116 @@ function walkSDKGenFields(fields, visit) {
20735
21009
  walkSDKGenFields(field.children, visit);
20736
21010
  }
20737
21011
  }
20738
- function collectExistingSDKGenFieldKeys(entity) {
20739
- const keys = /* @__PURE__ */ new Set();
21012
+ function assertSDKGenFieldKeysPresent(entity) {
21013
+ const missing = [];
20740
21014
  walkSDKGenFields(entity.fields, (field) => {
21015
+ if (typeof field.id !== "number") {
21016
+ return;
21017
+ }
20741
21018
  if (typeof field.key === "string" && field.key.trim() !== "") {
20742
- keys.add(field.key.trim());
21019
+ return;
20743
21020
  }
21021
+ missing.push({
21022
+ id: field.id,
21023
+ label: typeof field.label === "string" && field.label.trim() ? field.label.trim() : `field ${field.id}`
21024
+ });
21025
+ });
21026
+ if (missing.length === 0) {
21027
+ return;
21028
+ }
21029
+ const entityName = typeof entity.name === "string" && entity.name.trim() ? entity.name.trim() : `entity ${String(entity.id ?? "")}`.trim();
21030
+ const preview = missing.slice(0, 8).map((field) => `${entityName}.${field.label} (${field.id})`);
21031
+ const suffix = missing.length > preview.length ? `, and ${missing.length - preview.length} more` : "";
21032
+ throw new CLIError("SDK_GEN_FIELD_KEY_REQUIRED", `missing field.key values: ${preview.join(", ")}${suffix}`, 2, {
21033
+ operation: "dev sdk-gen",
21034
+ issues: missing.map((field) => ({
21035
+ path: `entity.${String(entity.id ?? "unknown")}.field.${field.id}.key`,
21036
+ message: `field.key is required for ${entityName}.${field.label}`
21037
+ })),
21038
+ suggestions: [
21039
+ "set stable field.key values in Arcubase admin before running sdk-gen",
21040
+ "rerun arcubase-admin dev sdk-gen after the schema keys are complete"
21041
+ ]
20744
21042
  });
20745
- return keys;
20746
21043
  }
20747
- function nextSDKGenFallbackFieldKey(fieldId, usedKeys) {
20748
- const base = `field${fieldId}`;
20749
- if (!usedKeys.has(base)) {
20750
- usedKeys.add(base);
20751
- return base;
21044
+ function extractSDKGenIngressRefs(payload) {
21045
+ const data = unwrapResponseData(payload);
21046
+ const source = Array.isArray(data) ? data : isRecord4(data) && Array.isArray(data.list) ? data.list : [];
21047
+ return source.filter((item) => isRecord4(item));
21048
+ }
21049
+ function ingressKeyOf(ingress) {
21050
+ return typeof ingress.key === "string" ? ingress.key.trim() : "";
21051
+ }
21052
+ function ingressHashIDOf(ingress) {
21053
+ if (typeof ingress.hash_id === "string" && ingress.hash_id.trim()) {
21054
+ return ingress.hash_id.trim();
20752
21055
  }
20753
- let index = 2;
20754
- while (usedKeys.has(`${base}_${index}`)) {
20755
- index++;
21056
+ if (typeof ingress.hashId === "string" && ingress.hashId.trim()) {
21057
+ return ingress.hashId.trim();
20756
21058
  }
20757
- const key = `${base}_${index}`;
20758
- usedKeys.add(key);
20759
- return key;
21059
+ return "";
20760
21060
  }
20761
- function initializeMissingSDKGenFieldKeys(entity) {
20762
- const usedKeys = collectExistingSDKGenFieldKeys(entity);
20763
- const updates = [];
20764
- walkSDKGenFields(entity.fields, (field) => {
20765
- if (typeof field.id !== "number") {
20766
- return;
21061
+ function assertSDKGenIngressesReady(ingresses) {
21062
+ if (ingresses.length === 0) {
21063
+ throw new CLIError("SDK_GEN_NO_INGRESSES", "sdk-gen could not find ingress definitions for this app", 2, {
21064
+ operation: "dev sdk-gen",
21065
+ suggestions: ["create at least one access rule with a stable ingress.key before running sdk-gen"]
21066
+ });
21067
+ }
21068
+ const seen = /* @__PURE__ */ new Set();
21069
+ const missingKey = [];
21070
+ const missingHash = [];
21071
+ const invalidKey = [];
21072
+ const duplicateKey = [];
21073
+ for (const ingress of ingresses) {
21074
+ const key = ingressKeyOf(ingress);
21075
+ if (!key) {
21076
+ missingKey.push(ingress);
21077
+ continue;
20767
21078
  }
20768
- if (typeof field.key === "string" && field.key.trim() !== "") {
20769
- field.key = field.key.trim();
20770
- return;
21079
+ if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(key)) {
21080
+ invalidKey.push(key);
20771
21081
  }
20772
- const key = nextSDKGenFallbackFieldKey(field.id, usedKeys);
20773
- field.key = key;
20774
- updates.push({ id: field.id, key });
20775
- });
20776
- return updates;
21082
+ if (seen.has(key)) {
21083
+ duplicateKey.push(key);
21084
+ }
21085
+ seen.add(key);
21086
+ if (!ingressHashIDOf(ingress)) {
21087
+ missingHash.push(ingress);
21088
+ }
21089
+ }
21090
+ if (missingKey.length > 0) {
21091
+ const preview = missingKey.slice(0, 8).map((ingress) => String(ingress.name ?? ingress.id ?? "unnamed ingress"));
21092
+ const suffix = missingKey.length > preview.length ? `, and ${missingKey.length - preview.length} more` : "";
21093
+ throw new CLIError("SDK_GEN_INGRESS_KEY_REQUIRED", `missing ingress.key values: ${preview.join(", ")}${suffix}`, 2, {
21094
+ operation: "dev sdk-gen",
21095
+ issues: missingKey.map((ingress) => ({
21096
+ path: `ingress.${String(ingress.id ?? "unknown")}.key`,
21097
+ message: `ingress.key is required for ${String(ingress.name ?? ingress.id ?? "unnamed ingress")}`
21098
+ })),
21099
+ suggestions: [
21100
+ "set stable ingress.key values in Arcubase admin before running sdk-gen",
21101
+ "rerun arcubase-admin dev sdk-gen after ingress keys are complete"
21102
+ ]
21103
+ });
21104
+ }
21105
+ if (invalidKey.length > 0) {
21106
+ throw new CLIError("SDK_GEN_INVALID_INGRESS_KEY", `invalid ingress.key values: ${invalidKey.join(", ")}`, 2, {
21107
+ operation: "dev sdk-gen",
21108
+ suggestions: ["use TypeScript identifier keys such as publicEntry or customerPortal"]
21109
+ });
21110
+ }
21111
+ if (duplicateKey.length > 0) {
21112
+ throw new CLIError("SDK_GEN_DUPLICATE_INGRESS_KEY", `duplicate ingress.key values: ${duplicateKey.join(", ")}`, 2, {
21113
+ operation: "dev sdk-gen"
21114
+ });
21115
+ }
21116
+ if (missingHash.length > 0) {
21117
+ throw new CLIError("SDK_GEN_INGRESS_HASH_ID_REQUIRED", "sdk-gen expected hash_id for every ingress definition", 2, {
21118
+ operation: "dev sdk-gen",
21119
+ suggestions: ["upgrade Arcubase server so admin access-rule list returns hash_id"]
21120
+ });
21121
+ }
20777
21122
  }
20778
21123
  async function executeDevSDKGen(runtimeEnv, flags, fetchImpl) {
20779
21124
  validateDevSDKGenFlags(flags);
@@ -20801,8 +21146,7 @@ async function executeDevSDKGen(runtimeEnv, flags, fetchImpl) {
20801
21146
  if (entityRefs.length === 0) {
20802
21147
  throw new CLIError("SDK_GEN_NO_ENTITIES", "sdk-gen could not find entities in app detail response", 2);
20803
21148
  }
20804
- const entities = [];
20805
- const initializedFieldKeys = [];
21149
+ const ingresses = [];
20806
21150
  for (const entityRef of entityRefs) {
20807
21151
  const entityEndpoint = `/api/apps/${encodeURIComponent(appId)}/entity/${encodeURIComponent(entityRef.id)}`;
20808
21152
  const entityDetail = await requestJSON(runtimeEnv, "GET", entityEndpoint, void 0, fetchImpl);
@@ -20810,21 +21154,22 @@ async function executeDevSDKGen(runtimeEnv, flags, fetchImpl) {
20810
21154
  if (!isRecord4(entityPayload)) {
20811
21155
  throw new CLIError("SDK_GEN_INVALID_SCHEMA", "sdk-gen expected entity detail response data", 2);
20812
21156
  }
20813
- const customKeyUpdates = initializeMissingSDKGenFieldKeys(entityPayload);
20814
- if (customKeyUpdates.length > 0) {
20815
- const customKeysEndpoint = `/api/apps/${encodeURIComponent(appId)}/entity/${encodeURIComponent(entityRef.id)}/custom-keys`;
20816
- await requestJSON(runtimeEnv, "PUT", customKeysEndpoint, customKeyUpdates, fetchImpl);
20817
- initializedFieldKeys.push({
20818
- entityId: typeof entityPayload.id === "number" ? entityPayload.id : Number(entityRef.id),
20819
- fields: customKeyUpdates.map((item) => item.key)
20820
- });
20821
- }
20822
- entities.push(entityPayload);
20823
- }
21157
+ assertSDKGenFieldKeysPresent(entityPayload);
21158
+ const ingressEndpoint = `/api/apps/${encodeURIComponent(appId)}/entity/${encodeURIComponent(entityRef.id)}/ingress`;
21159
+ const ingressList = await requestJSON(runtimeEnv, "GET", ingressEndpoint, void 0, fetchImpl);
21160
+ const entityIngresses = extractSDKGenIngressRefs(ingressList.data).map((ingress) => ({
21161
+ ...ingress,
21162
+ hash_id: ingressHashIDOf(ingress),
21163
+ key: ingressKeyOf(ingress),
21164
+ entity: entityPayload
21165
+ }));
21166
+ ingresses.push(...entityIngresses);
21167
+ }
21168
+ assertSDKGenIngressesReady(ingresses);
20824
21169
  const generated = generateArcubaseProjectSDK({
20825
21170
  id: resolveSDKGenAppId(appId, appPayload),
20826
21171
  name: typeof appPayload.name === "string" ? appPayload.name : void 0,
20827
- entities
21172
+ ingresses
20828
21173
  });
20829
21174
  const absoluteOutDir = path2.resolve(process.cwd(), outDir);
20830
21175
  for (const file2 of generated.files) {
@@ -20841,8 +21186,7 @@ async function executeDevSDKGen(runtimeEnv, flags, fetchImpl) {
20841
21186
  status: appDetail.status,
20842
21187
  data: {
20843
21188
  out: absoluteOutDir,
20844
- files: generated.files.map((file2) => file2.path),
20845
- initializedFieldKeys
21189
+ files: generated.files.map((file2) => file2.path)
20846
21190
  }
20847
21191
  };
20848
21192
  }