@voxgig/sdkgen 1.3.4 → 1.3.5

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.
Files changed (87) hide show
  1. package/bin/voxgig-sdkgen +1 -1
  2. package/dist/cmp/ReadmeErrors.js +15 -15
  3. package/dist/cmp/ReadmeErrors.js.map +1 -1
  4. package/dist/cmp/ReadmeExplanation.js +37 -35
  5. package/dist/cmp/ReadmeExplanation.js.map +1 -1
  6. package/dist/helpers/naming.js +29 -2
  7. package/dist/helpers/naming.js.map +1 -1
  8. package/dist/helpers/opShape.d.ts +6 -1
  9. package/dist/helpers/opShape.js +78 -0
  10. package/dist/helpers/opShape.js.map +1 -1
  11. package/dist/sdkgen.d.ts +2 -2
  12. package/dist/sdkgen.js +3 -1
  13. package/dist/sdkgen.js.map +1 -1
  14. package/package.json +1 -1
  15. package/project/.sdk/src/cmp/go/EntityOperation_go.ts +8 -1
  16. package/project/.sdk/src/cmp/go/Entity_go.ts +20 -3
  17. package/project/.sdk/src/cmp/go/Main_go.ts +2 -1
  18. package/project/.sdk/src/cmp/go/ReadmeEntity_go.ts +5 -5
  19. package/project/.sdk/src/cmp/go/ReadmeHowto_go.ts +23 -12
  20. package/project/.sdk/src/cmp/go/ReadmeModel_go.ts +27 -17
  21. package/project/.sdk/src/cmp/go/ReadmeRef_go.ts +5 -5
  22. package/project/.sdk/src/cmp/go/ReadmeTopTest_go.ts +5 -6
  23. package/project/.sdk/src/cmp/go/fragment/Entity.fragment.go +11 -11
  24. package/project/.sdk/src/cmp/go/fragment/EntityCreateOp.fragment.go +2 -2
  25. package/project/.sdk/src/cmp/go/fragment/EntityListOp.fragment.go +2 -2
  26. package/project/.sdk/src/cmp/go/fragment/EntityLoadOp.fragment.go +2 -2
  27. package/project/.sdk/src/cmp/go/fragment/EntityRemoveOp.fragment.go +2 -2
  28. package/project/.sdk/src/cmp/go/fragment/EntityUpdateOp.fragment.go +2 -2
  29. package/project/.sdk/src/cmp/js/Entity_js.ts +18 -2
  30. package/project/.sdk/src/cmp/js/MainEntity_js.ts +12 -2
  31. package/project/.sdk/src/cmp/js/Main_js.ts +5 -2
  32. package/project/.sdk/src/cmp/js/ReadmeEntity_js.ts +5 -5
  33. package/project/.sdk/src/cmp/js/ReadmeHowto_js.ts +37 -26
  34. package/project/.sdk/src/cmp/js/ReadmeRef_js.ts +5 -5
  35. package/project/.sdk/src/cmp/js/ReadmeTopTest_js.ts +5 -6
  36. package/project/.sdk/src/cmp/js/fragment/Entity.fragment.js +3 -3
  37. package/project/.sdk/src/cmp/lua/EntityOperation_lua.ts +6 -1
  38. package/project/.sdk/src/cmp/lua/Entity_lua.ts +20 -2
  39. package/project/.sdk/src/cmp/lua/ReadmeEntity_lua.ts +8 -5
  40. package/project/.sdk/src/cmp/lua/ReadmeHowto_lua.ts +14 -7
  41. package/project/.sdk/src/cmp/lua/ReadmeQuick_lua.ts +9 -6
  42. package/project/.sdk/src/cmp/lua/ReadmeRef_lua.ts +5 -2
  43. package/project/.sdk/src/cmp/lua/ReadmeTopQuick_lua.ts +8 -5
  44. package/project/.sdk/src/cmp/lua/ReadmeTopTest_lua.ts +5 -6
  45. package/project/.sdk/src/cmp/lua/fragment/Entity.fragment.lua +13 -13
  46. package/project/.sdk/src/cmp/lua/fragment/EntityCreateOp.fragment.lua +1 -1
  47. package/project/.sdk/src/cmp/lua/fragment/EntityListOp.fragment.lua +1 -1
  48. package/project/.sdk/src/cmp/lua/fragment/EntityLoadOp.fragment.lua +1 -1
  49. package/project/.sdk/src/cmp/lua/fragment/EntityRemoveOp.fragment.lua +1 -1
  50. package/project/.sdk/src/cmp/lua/fragment/EntityUpdateOp.fragment.lua +1 -1
  51. package/project/.sdk/src/cmp/php/Entity_php.ts +17 -1
  52. package/project/.sdk/src/cmp/php/MainEntity_php.ts +12 -3
  53. package/project/.sdk/src/cmp/php/ReadmeHowto_php.ts +15 -8
  54. package/project/.sdk/src/cmp/php/ReadmeTopTest_php.ts +5 -6
  55. package/project/.sdk/src/cmp/php/fragment/Entity.fragment.php +2 -2
  56. package/project/.sdk/src/cmp/py/Entity_py.ts +18 -1
  57. package/project/.sdk/src/cmp/py/MainEntity_py.ts +14 -4
  58. package/project/.sdk/src/cmp/py/Main_py.ts +2 -1
  59. package/project/.sdk/src/cmp/py/ReadmeEntity_py.ts +8 -5
  60. package/project/.sdk/src/cmp/py/ReadmeHowto_py.ts +18 -8
  61. package/project/.sdk/src/cmp/py/ReadmeQuick_py.ts +10 -7
  62. package/project/.sdk/src/cmp/py/ReadmeRef_py.ts +7 -4
  63. package/project/.sdk/src/cmp/py/ReadmeTopQuick_py.ts +9 -6
  64. package/project/.sdk/src/cmp/py/ReadmeTopTest_py.ts +6 -7
  65. package/project/.sdk/src/cmp/py/fragment/Entity.fragment.py +2 -2
  66. package/project/.sdk/src/cmp/rb/Entity_rb.ts +17 -1
  67. package/project/.sdk/src/cmp/rb/MainEntity_rb.ts +11 -2
  68. package/project/.sdk/src/cmp/rb/ReadmeEntity_rb.ts +8 -5
  69. package/project/.sdk/src/cmp/rb/ReadmeHowto_rb.ts +20 -8
  70. package/project/.sdk/src/cmp/rb/ReadmeQuick_rb.ts +9 -6
  71. package/project/.sdk/src/cmp/rb/ReadmeRef_rb.ts +5 -2
  72. package/project/.sdk/src/cmp/rb/ReadmeTopQuick_rb.ts +8 -5
  73. package/project/.sdk/src/cmp/rb/ReadmeTopTest_rb.ts +10 -8
  74. package/project/.sdk/src/cmp/rb/fragment/Entity.fragment.rb +2 -2
  75. package/project/.sdk/src/cmp/ts/Entity_ts.ts +18 -2
  76. package/project/.sdk/src/cmp/ts/MainEntity_ts.ts +12 -2
  77. package/project/.sdk/src/cmp/ts/Main_ts.ts +5 -2
  78. package/project/.sdk/src/cmp/ts/ReadmeEntity_ts.ts +5 -5
  79. package/project/.sdk/src/cmp/ts/ReadmeHowto_ts.ts +36 -26
  80. package/project/.sdk/src/cmp/ts/ReadmeRef_ts.ts +4 -5
  81. package/project/.sdk/src/cmp/ts/ReadmeTopTest_ts.ts +5 -6
  82. package/project/.sdk/src/cmp/ts/fragment/Entity.fragment.ts +4 -4
  83. package/src/cmp/ReadmeErrors.ts +16 -17
  84. package/src/cmp/ReadmeExplanation.ts +37 -35
  85. package/src/helpers/naming.ts +32 -2
  86. package/src/helpers/opShape.ts +89 -0
  87. package/src/sdkgen.ts +3 -1
@@ -1,5 +1,5 @@
1
1
 
2
- import { cmp, Content, entityIdField, entityPrimaryOp, opRequestShape, safeVarName } from '@voxgig/sdkgen'
2
+ import { cmp, Content, entityIdField, pickExampleEntity, opRequestShape, safeVarName } from '@voxgig/sdkgen'
3
3
 
4
4
  import {
5
5
  KIT,
@@ -14,17 +14,16 @@ const ReadmeTopTest = cmp(function ReadmeTopTest(props: any) {
14
14
  const { target, ctx$: { model } } = props
15
15
 
16
16
  const entity = getModelPath(model, `main.${KIT}.entity`)
17
- const exampleEntity = Object.values(entity).find((e: any) => e.active !== false) as any
17
+ // Pick an entity with a real op (prefer a read op) never fabricate a
18
+ // `load` on an op-less entity like Cloudsmith's `Abort`.
19
+ const { entity: exampleEntity, primaryOp } = pickExampleEntity(entity)
18
20
 
19
21
  Content(`\`\`\`js
20
22
  const client = ${model.const.Name}SDK.test()
21
23
  `)
22
24
 
23
- if (exampleEntity) {
25
+ if (exampleEntity && primaryOp) {
24
26
  const eName = nom(exampleEntity, 'Name')
25
- // Drive the test-mode example off the entity's PRIMARY op (never a
26
- // hardcoded `load` a create-only entity lacks).
27
- const primaryOp = entityPrimaryOp(exampleEntity) || 'load'
28
27
  // A list() result is an array — name the variable accordingly.
29
28
  const eVar = safeVarName(eName.toLowerCase(), 'js') +
30
29
  ('list' === primaryOp ? 's' : '')
@@ -5,7 +5,7 @@ const { ProjectNameEntityBase } = require('../ProjectNameEntityBase')
5
5
 
6
6
 
7
7
  // TODO: needs Entity superclass
8
- class EntityNameEntity extends ProjectNameEntityBase {
8
+ class EntyClass extends ProjectNameEntityBase {
9
9
 
10
10
  constructor(client, entopts) {
11
11
  super(client, entopts)
@@ -16,7 +16,7 @@ class EntityNameEntity extends ProjectNameEntityBase {
16
16
 
17
17
 
18
18
  make() {
19
- return new EntityNameEntity(this._client, this.entopts())
19
+ return new EntyClass(this._client, this.entopts())
20
20
  }
21
21
 
22
22
 
@@ -34,5 +34,5 @@ class EntityNameEntity extends ProjectNameEntityBase {
34
34
 
35
35
 
36
36
  module.exports = {
37
- EntityNameEntity
37
+ EntyClass
38
38
  }
@@ -7,7 +7,7 @@ import {
7
7
 
8
8
  const EntityOperation = cmp(function Operation(props: any) {
9
9
  const { model } = props.ctx$
10
- const { ff, opname, entity, entrep } = props
10
+ const { ff, opname, entity, entrep, cls } = props
11
11
 
12
12
  let { indent } = props
13
13
 
@@ -29,6 +29,11 @@ const EntityOperation = cmp(function Operation(props: any) {
29
29
  ProjectName: model.const.Name,
30
30
  EntityName: entity.Name,
31
31
  entityname: entity.name,
32
+
33
+ // Class receiver token decoupled from the EntityName data-type token in
34
+ // the Entity<Op>Op.fragment.lua files (receiver -> cls).
35
+ EntyClass: cls,
36
+
32
37
  '#Feature-Hook': ({ name, indent }: any) =>
33
38
  Content({ indent }, `
34
39
  u.feature_hook(ctx, "${name}")
@@ -3,9 +3,15 @@ import * as Path from 'node:path'
3
3
 
4
4
  import {
5
5
  cmp, each, camelify, names,
6
- File, Content, Folder, Fragment, Line, FeatureHook, Slot
6
+ File, Content, Folder, Fragment, Line, FeatureHook, Slot,
7
+ entityClassName,
7
8
  } from '@voxgig/sdkgen'
8
9
 
10
+ import {
11
+ KIT,
12
+ getModelPath
13
+ } from '@voxgig/apidef'
14
+
9
15
  import { EntityOperation } from './EntityOperation_lua'
10
16
 
11
17
 
@@ -13,6 +19,14 @@ const Entity = cmp(function Entity(props: any) {
13
19
  const { model, stdrep } = props.ctx$
14
20
  const { target, entity } = props
15
21
 
22
+ // Collision-free entity CLASS name (see entityClassName): normally
23
+ // `<Name>Entity`, disambiguated when it would clash with another entity's
24
+ // data-type name. The class is a module-local table (snake-cased file name),
25
+ // so this is used only for the local identifier — kept uniform with the other
26
+ // languages.
27
+ const entityColl = getModelPath(model, `main.${KIT}.entity`)
28
+ const cls = entityClassName(entity, entityColl)
29
+
16
30
  const entrep = {
17
31
  ...stdrep,
18
32
  }
@@ -33,7 +47,7 @@ const Entity = cmp(function Entity(props: any) {
33
47
  .reduce((a: any, opname: string) =>
34
48
  (a['-- #' + camelify(opname) + 'Op'] =
35
49
  !opnames.includes(opname) ? '' : ({ indent }: any) => {
36
- EntityOperation({ ff, opname, indent, entity, entrep })
50
+ EntityOperation({ ff, opname, indent, entity, entrep, cls })
37
51
  }, a), {}))
38
52
 
39
53
  Fragment({
@@ -44,6 +58,10 @@ const Entity = cmp(function Entity(props: any) {
44
58
  EntityName: entity.Name,
45
59
  entityname: entity.name,
46
60
 
61
+ // Class token decoupled from the EntityName data-type token in
62
+ // Entity.fragment.lua so the class can be renamed independently.
63
+ EntyClass: cls,
64
+
47
65
  '#Entity-Hook': ({ name, indent }: any) =>
48
66
  Content({ indent }, `utility.feature_hook(ctx, "${name}")`),
49
67
 
@@ -1,5 +1,5 @@
1
1
 
2
- import { cmp, each, Content, canonToType, canonKey, entityIdField, opRequestShape } from '@voxgig/sdkgen'
2
+ import { cmp, each, Content, canonToType, canonKey, entityIdField, opRequestShape, safeVarName } from '@voxgig/sdkgen'
3
3
 
4
4
  import {
5
5
  KIT,
@@ -61,6 +61,9 @@ const ReadmeEntity = cmp(function ReadmeEntity(props: any) {
61
61
  const fields = entity.fields || []
62
62
  // Model-driven id key: null when this entity has no id-like field.
63
63
  const idF = entityIdField(entity)
64
+ // Sanitise the local variable name — an entity whose lowercased name is a
65
+ // Lua keyword (e.g. `end`) would otherwise emit uncompilable code.
66
+ const eVar = safeVarName(entity.name, 'lua')
64
67
 
65
68
  Content(`
66
69
  ### ${entity.Name}
@@ -73,7 +76,7 @@ const ReadmeEntity = cmp(function ReadmeEntity(props: any) {
73
76
  `)
74
77
  }
75
78
 
76
- Content(`Create an instance: \`local ${entity.name} = client:${entity.Name}(nil)\`
79
+ Content(`Create an instance: \`local ${eVar} = client:${entity.Name}(nil)\`
77
80
 
78
81
  `)
79
82
 
@@ -128,7 +131,7 @@ const ReadmeEntity = cmp(function ReadmeEntity(props: any) {
128
131
  Content(`#### Example: Load
129
132
 
130
133
  \`\`\`lua
131
- local ${entity.name}, err = client:${entity.Name}():load(${loadArg})
134
+ local ${eVar}, err = client:${entity.Name}():load(${loadArg})
132
135
  \`\`\`
133
136
 
134
137
  `)
@@ -138,7 +141,7 @@ local ${entity.name}, err = client:${entity.Name}():load(${loadArg})
138
141
  Content(`#### Example: List
139
142
 
140
143
  \`\`\`lua
141
- local ${entity.name}s, err = client:${entity.Name}():list()
144
+ local ${eVar}s, err = client:${entity.Name}():list()
142
145
  \`\`\`
143
146
 
144
147
  `)
@@ -155,7 +158,7 @@ local ${entity.name}s, err = client:${entity.Name}():list()
155
158
  Content(`#### Example: Create
156
159
 
157
160
  \`\`\`lua
158
- local ${entity.name}, err = client:${entity.Name}():create({
161
+ local ${eVar}, err = client:${entity.Name}():create({
159
162
  `)
160
163
  createItems.map((it: any) => {
161
164
  Content(` ${luaKey(it.name)} = ${luaLit(it.type, 'example_' + it.name)}, -- ${canonToType(it.type, target.name)}
@@ -1,5 +1,5 @@
1
1
 
2
- import { cmp, Content, isAuthActive, envName, canonKey, entityIdField, entityPrimaryOp, opRequestShape } from '@voxgig/sdkgen'
2
+ import { cmp, Content, isAuthActive, envName, canonKey, entityIdField, pickExampleEntity, opRequestShape } from '@voxgig/sdkgen'
3
3
 
4
4
  import {
5
5
  KIT,
@@ -26,13 +26,13 @@ const ReadmeHowto = cmp(function ReadmeHowto(props: any) {
26
26
  const { target, ctx$: { model } } = props
27
27
 
28
28
  const entity = getModelPath(model, `main.${KIT}.entity`)
29
- const exampleEntity = Object.values(entity).find((e: any) => e.active !== false) as any
29
+ // Pick an entity with a real op (prefer a read op) never fabricate a
30
+ // `load` on an op-less entity like Cloudsmith's `Abort`. primaryOp is null
31
+ // only when NO entity exposes any op (a direct()-only SDK).
32
+ const { entity: exampleEntity, primaryOp } = pickExampleEntity(entity)
30
33
  const eName = exampleEntity ? nom(exampleEntity, 'Name') : 'Entity'
31
34
  // Model-driven id key: null when the entity has no id-like field.
32
35
  const idF = exampleEntity ? entityIdField(exampleEntity) : null
33
- // Drive the test-mode example off the entity's PRIMARY op — never a hardcoded
34
- // `load` a create-only entity lacks.
35
- const primaryOp = exampleEntity ? (entityPrimaryOp(exampleEntity) || 'load') : 'load'
36
36
  const isMatchOp = 'load' === primaryOp || 'remove' === primaryOp
37
37
  let testCallArg = ''
38
38
  if (exampleEntity && isMatchOp) {
@@ -45,6 +45,14 @@ const ReadmeHowto = cmp(function ReadmeHowto(props: any) {
45
45
  testCallArg = `{ ${chosen.map((it: any) => `${luaKey(it.name)} = ${luaLit(it.type)}`).join(', ')} }`
46
46
  }
47
47
 
48
+ // The op-driven test-mode line, shown only when the SDK has an entity op.
49
+ // A direct()-only SDK (no ops anywhere) shows a direct() call instead.
50
+ const testModeExample = primaryOp
51
+ ? `local result, err = client:${eName}():${primaryOp}(${testCallArg})
52
+ -- result is the returned data; err is set on failure`
53
+ : `local result, err = client:direct({ path = "/api/resource", method = "GET" })
54
+ -- result is the returned data; err is set on failure`
55
+
48
56
  const apikeyEnvLine = isAuthActive(model)
49
57
  ? `\n${envName(model)}_APIKEY=<your-key>`
50
58
  : ''
@@ -89,8 +97,7 @@ Create a mock client for unit testing — no server required:
89
97
  \`\`\`lua
90
98
  local client = sdk.test()
91
99
 
92
- local result, err = client:${eName}():${primaryOp}(${testCallArg})
93
- -- result is the returned data; err is set on failure
100
+ ${testModeExample}
94
101
  \`\`\`
95
102
 
96
103
  ### Use a custom fetch function
@@ -1,5 +1,5 @@
1
1
 
2
- import { cmp, each, Content, isAuthActive, envName, canonKey, opRequestShape, entityIdField, entityDataIdField, entityOps } from '@voxgig/sdkgen'
2
+ import { cmp, each, Content, isAuthActive, envName, canonKey, opRequestShape, entityIdField, entityDataIdField, entityOps, safeVarName } from '@voxgig/sdkgen'
3
3
 
4
4
  import {
5
5
  KIT,
@@ -43,6 +43,9 @@ local client = ${ctor}
43
43
  if (exampleEntity) {
44
44
  const eName = nom(exampleEntity, 'Name')
45
45
  const article = /^[aeiou]/i.test(eName) ? "an" : "a"
46
+ // Sanitise the local variable name — an entity whose lowercased name is a
47
+ // Lua keyword (e.g. `end`) would otherwise emit uncompilable code.
48
+ const eVar = safeVarName(eName.toLowerCase(), 'lua')
46
49
  const opnames = entityOps(exampleEntity)
47
50
  // Model-driven id key: `idF` is the MATCH key (null when none). `dataIdF`
48
51
  // is the id on the RETURNED record's data type — an entity can key its match
@@ -91,10 +94,10 @@ Entity operations return \`(value, err)\`. For \`list\`, \`value\` is the
91
94
  array of records itself — iterate it directly (there is no wrapper).
92
95
 
93
96
  \`\`\`lua
94
- local ${eName.toLowerCase()}s, err = client:${eName}():list()
97
+ local ${eVar}s, err = client:${eName}():list()
95
98
  if err then error(err) end
96
99
 
97
- for _, item in ipairs(${eName.toLowerCase()}s) do
100
+ for _, item in ipairs(${eVar}s) do
98
101
  ${printLine}
99
102
  end
100
103
  \`\`\`
@@ -105,7 +108,7 @@ end
105
108
  if (nestedEntity) {
106
109
  const neName = nom(nestedEntity, 'Name')
107
110
  const neArticle = /^[aeiou]/i.test(neName) ? "an" : "a"
108
- const neVar = neName.toLowerCase()
111
+ const neVar = safeVarName(neName.toLowerCase(), 'lua')
109
112
 
110
113
  // Model-driven match: every REQUIRED load-match key — the same shape
111
114
  // the runtime resolves path params from, so the example always works.
@@ -151,9 +154,9 @@ print(${neVar})
151
154
  Content(`### 3. Load ${article} ${eName.toLowerCase()}
152
155
 
153
156
  \`\`\`lua
154
- local ${eName.toLowerCase()}, err = client:${eName}():load(${loadArg})
157
+ local ${eVar}, err = client:${eName}():load(${loadArg})
155
158
  if err then error(err) end
156
- print(${eName.toLowerCase()})
159
+ print(${eVar})
157
160
  \`\`\`
158
161
 
159
162
  `)
@@ -1,5 +1,5 @@
1
1
 
2
- import { cmp, each, Content, canonToType, canonKey, File, isAuthActive, entityIdField, opRequestShape } from '@voxgig/sdkgen'
2
+ import { cmp, each, Content, canonToType, canonKey, File, isAuthActive, entityIdField, opRequestShape, safeVarName } from '@voxgig/sdkgen'
3
3
 
4
4
  import {
5
5
  KIT,
@@ -174,6 +174,9 @@ same parameters as \`direct()\`.
174
174
  // Model-driven id key: null when this entity has no id-like field, in
175
175
  // which case load/remove match on no argument and update omits the id.
176
176
  const idF = entityIdField(ent)
177
+ // Sanitise the local variable name — an entity whose lowercased name is
178
+ // a Lua keyword (e.g. `end`) would otherwise emit uncompilable code.
179
+ const eVar = safeVarName(ent.name, 'lua')
177
180
 
178
181
  Content(`
179
182
  ---
@@ -189,7 +192,7 @@ same parameters as \`direct()\`.
189
192
  }
190
193
 
191
194
  Content(`\`\`\`lua
192
- local ${ent.name} = client:${ent.Name}(nil)
195
+ local ${eVar} = client:${ent.Name}(nil)
193
196
  \`\`\`
194
197
 
195
198
  `)
@@ -1,5 +1,5 @@
1
1
 
2
- import { cmp, Content, isAuthActive, envName, canonKey, entityIdField, opRequestShape } from '@voxgig/sdkgen'
2
+ import { cmp, Content, isAuthActive, envName, canonKey, entityIdField, opRequestShape, safeVarName } from '@voxgig/sdkgen'
3
3
 
4
4
  import {
5
5
  KIT,
@@ -46,6 +46,9 @@ local client = ${ctor}
46
46
 
47
47
  if (exampleEntity) {
48
48
  const eName = nom(exampleEntity, 'Name')
49
+ // Sanitise the local variable name — an entity whose lowercased name is a
50
+ // Lua keyword (e.g. `end`) would otherwise emit uncompilable code.
51
+ const eVar = safeVarName(eName.toLowerCase(), 'lua')
49
52
  const opnames = Object.keys(exampleEntity.op || {})
50
53
  // Model-driven id key: null when the entity has no id-like field, in which
51
54
  // case the load example takes no match argument.
@@ -55,8 +58,8 @@ local client = ${ctor}
55
58
 
56
59
  if (opnames.includes('list')) {
57
60
  Content(`-- List all ${eName.toLowerCase()}s
58
- local ${eName.toLowerCase()}s, err = client:${eName}():list()
59
- print(${eName.toLowerCase()}s)
61
+ local ${eVar}s, err = client:${eName}():list()
62
+ print(${eVar}s)
60
63
  `)
61
64
  hasCall = true
62
65
  }
@@ -76,8 +79,8 @@ print(${eName.toLowerCase()}s)
76
79
  : ''
77
80
  Content(`
78
81
  -- Load a specific ${eName.toLowerCase()}
79
- local ${eName.toLowerCase()}, err = client:${eName}():load(${loadArg})
80
- print(${eName.toLowerCase()})
82
+ local ${eVar}, err = client:${eName}():load(${loadArg})
83
+ print(${eVar})
81
84
  `)
82
85
  hasCall = true
83
86
  }
@@ -1,5 +1,5 @@
1
1
 
2
- import { cmp, Content, canonKey, entityIdField, entityPrimaryOp, opRequestShape } from '@voxgig/sdkgen'
2
+ import { cmp, Content, canonKey, entityIdField, pickExampleEntity, opRequestShape } from '@voxgig/sdkgen'
3
3
 
4
4
  import {
5
5
  KIT,
@@ -27,18 +27,17 @@ const ReadmeTopTest = cmp(function ReadmeTopTest(props: any) {
27
27
 
28
28
  const entity = getModelPath(model, `main.${KIT}.entity`)
29
29
 
30
- const exampleEntity = Object.values(entity).find((e: any) => e.active !== false) as any
30
+ // Pick an entity with a real op (prefer a read op) never fabricate a
31
+ // `load` on an op-less entity like Cloudsmith's `Abort`.
32
+ const { entity: exampleEntity, primaryOp } = pickExampleEntity(entity)
31
33
 
32
34
  Content(`\`\`\`lua
33
35
  local client = sdk.test()
34
36
  `)
35
37
 
36
- if (exampleEntity) {
38
+ if (exampleEntity && primaryOp) {
37
39
  const eName = nom(exampleEntity, 'Name')
38
- // Drive the test-mode example off the entity's PRIMARY op (never a hardcoded
39
- // `load` a create-only entity lacks).
40
40
  const idF = entityIdField(exampleEntity)
41
- const primaryOp = entityPrimaryOp(exampleEntity) || 'load'
42
41
  const isMatchOp = 'load' === primaryOp || 'remove' === primaryOp
43
42
  let arg = ''
44
43
  if (isMatchOp) {
@@ -3,11 +3,11 @@
3
3
  local vs = require("utility.struct.struct")
4
4
  local helpers = require("core.helpers")
5
5
 
6
- local EntityNameEntity = {}
7
- EntityNameEntity.__index = EntityNameEntity
6
+ local EntyClass = {}
7
+ EntyClass.__index = EntyClass
8
8
 
9
9
 
10
- function EntityNameEntity.new(client, entopts)
10
+ function EntyClass.new(client, entopts)
11
11
  entopts = entopts or {}
12
12
  if entopts["active"] == nil then
13
13
  entopts["active"] = true
@@ -17,7 +17,7 @@ function EntityNameEntity.new(client, entopts)
17
17
  entopts["active"] = true
18
18
  end
19
19
 
20
- local self = setmetatable({}, EntityNameEntity)
20
+ local self = setmetatable({}, EntyClass)
21
21
  self._name = "entityname"
22
22
  self._client = client
23
23
  self._utility = client:get_utility()
@@ -36,21 +36,21 @@ function EntityNameEntity.new(client, entopts)
36
36
  end
37
37
 
38
38
 
39
- function EntityNameEntity:get_name()
39
+ function EntyClass:get_name()
40
40
  return self._name
41
41
  end
42
42
 
43
43
 
44
- function EntityNameEntity:make()
44
+ function EntyClass:make()
45
45
  local opts = {}
46
46
  for k, v in pairs(self._entopts) do
47
47
  opts[k] = v
48
48
  end
49
- return EntityNameEntity.new(self._client, opts)
49
+ return EntyClass.new(self._client, opts)
50
50
  end
51
51
 
52
52
 
53
- function EntityNameEntity:data_set(args)
53
+ function EntyClass:data_set(args)
54
54
  if args ~= nil then
55
55
  self._data = helpers.to_map(vs.clone(args)) or {}
56
56
  self._utility.feature_hook(self._entctx, "SetData")
@@ -58,13 +58,13 @@ function EntityNameEntity:data_set(args)
58
58
  end
59
59
 
60
60
 
61
- function EntityNameEntity:data_get()
61
+ function EntyClass:data_get()
62
62
  self._utility.feature_hook(self._entctx, "GetData")
63
63
  return vs.clone(self._data)
64
64
  end
65
65
 
66
66
 
67
- function EntityNameEntity:match_set(args)
67
+ function EntyClass:match_set(args)
68
68
  if args ~= nil then
69
69
  self._match = helpers.to_map(vs.clone(args)) or {}
70
70
  self._utility.feature_hook(self._entctx, "SetMatch")
@@ -72,7 +72,7 @@ function EntityNameEntity:match_set(args)
72
72
  end
73
73
 
74
74
 
75
- function EntityNameEntity:match_get()
75
+ function EntyClass:match_get()
76
76
  self._utility.feature_hook(self._entctx, "GetMatch")
77
77
  return vs.clone(self._match)
78
78
  end
@@ -89,7 +89,7 @@ end
89
89
  -- #RemoveOp
90
90
 
91
91
 
92
- function EntityNameEntity:_run_op(ctx, post_done)
92
+ function EntyClass:_run_op(ctx, post_done)
93
93
  local utility = self._utility
94
94
 
95
95
  -- #PrePoint-Hook
@@ -144,4 +144,4 @@ function EntityNameEntity:_run_op(ctx, post_done)
144
144
  end
145
145
 
146
146
 
147
- return EntityNameEntity
147
+ return EntyClass
@@ -9,7 +9,7 @@ local entityCreateOp = {}
9
9
  ---@param ctrl? table
10
10
  ---@return EntityName
11
11
  ---@return string? err
12
- function EntityNameEntity:create(reqdata, ctrl)
12
+ function EntyClass:create(reqdata, ctrl)
13
13
  local utility = self._utility
14
14
  local ctx = utility.make_context({
15
15
  opname = "create",
@@ -6,7 +6,7 @@ local entityListOp = {}
6
6
  ---@param ctrl? table
7
7
  ---@return EntityName[]
8
8
  ---@return string? err
9
- function EntityNameEntity:list(reqmatch, ctrl)
9
+ function EntyClass:list(reqmatch, ctrl)
10
10
  local utility = self._utility
11
11
  local ctx = utility.make_context({
12
12
  opname = "list",
@@ -9,7 +9,7 @@ local entityLoadOp = {}
9
9
  ---@param ctrl? table
10
10
  ---@return EntityName
11
11
  ---@return string? err
12
- function EntityNameEntity:load(reqmatch, ctrl)
12
+ function EntyClass:load(reqmatch, ctrl)
13
13
  local utility = self._utility
14
14
  local ctx = utility.make_context({
15
15
  opname = "load",
@@ -9,7 +9,7 @@ local entityRemoveOp = {}
9
9
  ---@param ctrl? table
10
10
  ---@return EntityName
11
11
  ---@return string? err
12
- function EntityNameEntity:remove(reqmatch, ctrl)
12
+ function EntyClass:remove(reqmatch, ctrl)
13
13
  local utility = self._utility
14
14
  local ctx = utility.make_context({
15
15
  opname = "remove",
@@ -9,7 +9,7 @@ local entityUpdateOp = {}
9
9
  ---@param ctrl? table
10
10
  ---@return EntityName
11
11
  ---@return string? err
12
- function EntityNameEntity:update(reqdata, ctrl)
12
+ function EntyClass:update(reqdata, ctrl)
13
13
  local utility = self._utility
14
14
  local ctx = utility.make_context({
15
15
  opname = "update",
@@ -3,9 +3,15 @@ import * as Path from 'node:path'
3
3
 
4
4
  import {
5
5
  cmp, each, camelify, names,
6
- File, Content, Folder, Fragment, Line, FeatureHook, Slot
6
+ File, Content, Folder, Fragment, Line, FeatureHook, Slot,
7
+ entityClassName,
7
8
  } from '@voxgig/sdkgen'
8
9
 
10
+ import {
11
+ KIT,
12
+ getModelPath
13
+ } from '@voxgig/apidef'
14
+
9
15
  import { EntityOperation } from './EntityOperation_php'
10
16
 
11
17
 
@@ -13,6 +19,12 @@ const Entity = cmp(function Entity(props: any) {
13
19
  const { model, stdrep } = props.ctx$
14
20
  const { target, entity } = props
15
21
 
22
+ // Collision-free entity CLASS name (see entityClassName): normally
23
+ // `<Name>Entity`, disambiguated when it would clash with another entity's
24
+ // data-type name. The snake-cased source-file name is unaffected.
25
+ const entityColl = getModelPath(model, `main.${KIT}.entity`)
26
+ const cls = entityClassName(entity, entityColl)
27
+
16
28
  const entrep = {
17
29
  ...stdrep,
18
30
  }
@@ -44,6 +56,10 @@ const Entity = cmp(function Entity(props: any) {
44
56
  EntityName: entity.Name,
45
57
  entityname: entity.name,
46
58
 
59
+ // Class token decoupled from the EntityName data-type token in
60
+ // Entity.fragment.php so the class can be renamed independently.
61
+ EntyClass: cls,
62
+
47
63
  '#Entity-Hook': ({ name, indent }: any) =>
48
64
  Content({ indent }, `($utility->feature_hook)($ctx, "${name}");`),
49
65
 
@@ -1,6 +1,11 @@
1
1
 
2
2
 
3
- import { cmp, Content } from '@voxgig/sdkgen'
3
+ import { cmp, Content, entityClassName } from '@voxgig/sdkgen'
4
+
5
+ import {
6
+ KIT,
7
+ getModelPath
8
+ } from '@voxgig/apidef'
4
9
 
5
10
 
6
11
  // Reserved PHP method names on the SDK class that an entity accessor must
@@ -13,6 +18,10 @@ const MainEntity = cmp(async function MainEntity(props: any) {
13
18
  const { entity } = props
14
19
  const { model } = props.ctx$
15
20
 
21
+ // Collision-free entity CLASS name (entityClassName); the accessor METHOD name
22
+ // (below) is unchanged so callers still write $client->${entity.Name}().
23
+ const cls = entityClassName(entity, getModelPath(model, `main.${KIT}.entity`))
24
+
16
25
  // Canonical facade method name is the PascalCase entity Name
17
26
  // (`$client->${entity.Name}()`). PHP method names are case-insensitive, so
18
27
  // the lowercase spelling `$client->${entity.name}()` still resolves here as
@@ -34,11 +43,11 @@ const MainEntity = cmp(async function MainEntity(props: any) {
34
43
  require_once __DIR__ . '/entity/${entity.name}_entity.php';
35
44
  if ($data === null) {
36
45
  if ($this->_${entity.name} === null) {
37
- $this->_${entity.name} = new ${entity.Name}Entity($this, null);
46
+ $this->_${entity.name} = new ${cls}($this, null);
38
47
  }
39
48
  return $this->_${entity.name};
40
49
  }
41
- return new ${entity.Name}Entity($this, $data);
50
+ return new ${cls}($this, $data);
42
51
  }
43
52
 
44
53
  `)
@@ -1,5 +1,5 @@
1
1
 
2
- import { cmp, Content, isAuthActive, envName, canonKey, entityIdField, entityPrimaryOp, opRequestShape } from '@voxgig/sdkgen'
2
+ import { cmp, Content, isAuthActive, envName, canonKey, entityIdField, pickExampleEntity, opRequestShape } from '@voxgig/sdkgen'
3
3
 
4
4
  import { KIT, getModelPath, nom } from '@voxgig/apidef'
5
5
 
@@ -18,13 +18,13 @@ const ReadmeHowto = cmp(function ReadmeHowto(props: any) {
18
18
  const { target, ctx$: { model } } = props
19
19
 
20
20
  const entity = getModelPath(model, `main.${KIT}.entity`)
21
- const exampleEntity = Object.values(entity || {}).find((e: any) => e && e.active !== false) as any
21
+ // Pick an entity with a real op (prefer a read op) never fabricate a
22
+ // `load` on an op-less entity like Cloudsmith's `Abort`. primaryOp is null
23
+ // only when NO entity exposes any op (a direct()-only SDK).
24
+ const { entity: exampleEntity, primaryOp } = pickExampleEntity(entity)
22
25
  const eName = exampleEntity ? nom(exampleEntity, 'Name') : 'Entity'
23
26
  // Model-driven id key: null when the entity has no id-like field.
24
27
  const idF = exampleEntity ? entityIdField(exampleEntity) : null
25
- // Drive the test-mode example off the entity's PRIMARY op — never a hardcoded
26
- // `load` a create-only entity lacks.
27
- const primaryOp = exampleEntity ? (entityPrimaryOp(exampleEntity) || 'load') : 'load'
28
28
  const isMatchOp = 'load' === primaryOp || 'remove' === primaryOp
29
29
  const seedSentence = idF
30
30
  ? '. Seed fixture\ndata via the `entity` option so offline calls resolve without a live server'
@@ -43,6 +43,15 @@ const ReadmeHowto = cmp(function ReadmeHowto(props: any) {
43
43
  testCallArg = `[${chosen.map((it: any) => `"${it.name}" => ${phpLit(it.type)}`).join(', ')}]`
44
44
  }
45
45
 
46
+ // The op-driven test-mode line, shown only when the SDK has an entity op.
47
+ // A direct()-only SDK (no ops anywhere) shows a direct() call instead.
48
+ const testModeExample = primaryOp
49
+ ? `// Entity ops return the bare mock record (throws on error).
50
+ $${eName.toLowerCase()} = $client->${eName}()->${primaryOp}(${testCallArg});
51
+ print_r($${eName.toLowerCase()});`
52
+ : `$result = $client->direct(["path" => "/api/resource", "method" => "GET"]);
53
+ print_r($result);`
54
+
46
55
  const apikeyEnvLine = isAuthActive(model)
47
56
  ? `\n${envName(model)}_APIKEY=<your-key>`
48
57
  : ''
@@ -93,9 +102,7 @@ Create a mock client for unit testing — no server required${seedSentence}:
93
102
  \`\`\`php
94
103
  $client = ${testCtor};
95
104
 
96
- // Entity ops return the bare mock record (throws on error).
97
- $${eName.toLowerCase()} = $client->${eName}()->${primaryOp}(${testCallArg});
98
- print_r($${eName.toLowerCase()});
105
+ ${testModeExample}
99
106
  \`\`\`
100
107
 
101
108
  ### Use a custom fetch function