@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, 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,
@@ -23,16 +23,15 @@ const ReadmeTopTest = cmp(function ReadmeTopTest(props: any) {
23
23
 
24
24
  const entity = getModelPath(model, `main.${KIT}.entity`)
25
25
 
26
- const exampleEntity = Object.values(entity).find((e: any) => e.active !== false) as any
26
+ // Pick an entity with a real op (prefer a read op) never fabricate a
27
+ // `load` on an op-less entity like Cloudsmith's `Abort`.
28
+ const { entity: exampleEntity, primaryOp } = pickExampleEntity(entity)
27
29
 
28
- if (exampleEntity) {
30
+ if (exampleEntity && primaryOp) {
29
31
  const eName = nom(exampleEntity, 'Name')
30
32
  const ename = eName.toLowerCase()
31
33
  // Model-driven id key: null when the entity has no id-like field.
32
34
  const idF = entityIdField(exampleEntity)
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 = entityPrimaryOp(exampleEntity) || 'load'
36
35
  const isMatchOp = 'load' === primaryOp || 'remove' === primaryOp
37
36
  const recBody = idF ? `["${idF}" => "test01"]` : '[]'
38
37
  let callArg = ''
@@ -8,7 +8,7 @@ require_once __DIR__ . '/../core/Helpers.php';
8
8
 
9
9
  use Voxgig\Struct\Struct;
10
10
 
11
- class EntityNameEntity
11
+ class EntyClass
12
12
  {
13
13
  private string $_name;
14
14
  private $_client;
@@ -52,7 +52,7 @@ class EntityNameEntity
52
52
  public function make(): self
53
53
  {
54
54
  $opts = $this->_entopts;
55
- return new EntityNameEntity($this->_client, $opts);
55
+ return new EntyClass($this->_client, $opts);
56
56
  }
57
57
 
58
58
  /**
@@ -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_py'
10
16
 
11
17
 
@@ -19,6 +25,13 @@ const Entity = cmp(function Entity(props: any) {
19
25
  const { model, stdrep } = props.ctx$
20
26
  const { target, entity } = props
21
27
 
28
+ // Collision-free entity CLASS name (see entityClassName): normally
29
+ // `<Name>Entity`, disambiguated when it would clash with another entity's
30
+ // data-type name. The DATA type stays `<Name>`. The snake-cased source-file
31
+ // name is unaffected; only the class identifier changes.
32
+ const entityColl = getModelPath(model, `main.${KIT}.entity`)
33
+ const cls = entityClassName(entity, entityColl)
34
+
22
35
  const entrep = {
23
36
  ...stdrep,
24
37
  }
@@ -66,6 +79,10 @@ const Entity = cmp(function Entity(props: any) {
66
79
  EntityName: entity.Name,
67
80
  entityname: entity.name,
68
81
 
82
+ // Class token decoupled from the EntityName data-type token in
83
+ // Entity.fragment.py so the class can be renamed independently.
84
+ EntyClass: cls,
85
+
69
86
  // Literal-marker slot (jostraca's `#Name` tag pattern is hardcoded to
70
87
  // `//` comments, so Python uses a `# #`-prefixed literal like the op
71
88
  // frag slots) — fills in the typed-model import for this entity.
@@ -1,17 +1,27 @@
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
  const MainEntity = cmp(async function MainEntity(props: any) {
7
12
  const { entity } = props
8
13
  const { model } = props.ctx$
9
14
 
15
+ // Return the collision-free class (entityClassName); the accessor METHOD name
16
+ // (entity.Name) is unchanged so callers still write client.<Name>(). The
17
+ // source module is snake-cased, so only the imported class identifier changes.
18
+ const cls = entityClassName(entity, getModelPath(model, `main.${KIT}.entity`))
19
+
10
20
  Content(`
11
- def ${entity.Name}(self, data=None) -> "${entity.Name}Entity":
21
+ def ${entity.Name}(self, data=None) -> "${cls}":
12
22
  """Entity factory: client.${entity.Name}().list() / client.${entity.Name}().load({"id": ...})."""
13
- from entity.${entity.name}_entity import ${entity.Name}Entity
14
- return ${entity.Name}Entity(self, data)
23
+ from entity.${entity.name}_entity import ${cls}
24
+ return ${cls}(self, data)
15
25
 
16
26
  `)
17
27
 
@@ -4,6 +4,7 @@ import * as Path from 'node:path'
4
4
  import {
5
5
  cmp, each, names, cmap,
6
6
  List, File, Content, Copy, Folder, Fragment, Line, FeatureHook,
7
+ entityClassName,
7
8
  } from '@voxgig/sdkgen'
8
9
 
9
10
 
@@ -93,7 +94,7 @@ from typing import TYPE_CHECKING
93
94
  if TYPE_CHECKING:
94
95
  `)
95
96
  each(entity, (ent: any) => {
96
- Content(` from entity.${ent.name}_entity import ${ent.Name}Entity
97
+ Content(` from entity.${ent.name}_entity import ${entityClassName(ent, entity)}
97
98
  `)
98
99
  })
99
100
  }
@@ -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,
@@ -58,6 +58,9 @@ const ReadmeEntity = cmp(function ReadmeEntity(props: any) {
58
58
  const fields = entity.fields || []
59
59
  // Model-driven id key: null when this entity has no id-like field.
60
60
  const idF = entityIdField(entity)
61
+ // Sanitise the local variable name — an entity whose lowercased name is a
62
+ // Python keyword (e.g. `class`) would otherwise emit uncompilable code.
63
+ const eVar = safeVarName(entity.name, 'py')
61
64
 
62
65
  Content(`
63
66
  ### ${entity.Name}
@@ -70,7 +73,7 @@ const ReadmeEntity = cmp(function ReadmeEntity(props: any) {
70
73
  `)
71
74
  }
72
75
 
73
- Content(`Create an instance: \`${entity.name} = client.${entity.Name}()\`
76
+ Content(`Create an instance: \`${eVar} = client.${entity.Name}()\`
74
77
 
75
78
  `)
76
79
 
@@ -125,7 +128,7 @@ const ReadmeEntity = cmp(function ReadmeEntity(props: any) {
125
128
  Content(`#### Example: Load
126
129
 
127
130
  \`\`\`python
128
- ${entity.name} = client.${entity.Name}().load(${loadArg})
131
+ ${eVar} = client.${entity.Name}().load(${loadArg})
129
132
  \`\`\`
130
133
 
131
134
  `)
@@ -135,7 +138,7 @@ ${entity.name} = client.${entity.Name}().load(${loadArg})
135
138
  Content(`#### Example: List
136
139
 
137
140
  \`\`\`python
138
- ${entity.name}s = client.${entity.Name}().list()
141
+ ${eVar}s = client.${entity.Name}().list()
139
142
  \`\`\`
140
143
 
141
144
  `)
@@ -152,7 +155,7 @@ ${entity.name}s = client.${entity.Name}().list()
152
155
  Content(`#### Example: Create
153
156
 
154
157
  \`\`\`python
155
- ${entity.name} = client.${entity.Name}().create({
158
+ ${eVar} = client.${entity.Name}().create({
156
159
  `)
157
160
  createItems.map((it: any) => {
158
161
  Content(` "${it.name}": ${pyLit(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, safeVarName } from '@voxgig/sdkgen'
3
3
 
4
4
  import {
5
5
  KIT,
@@ -23,14 +23,17 @@ const ReadmeHowto = cmp(function ReadmeHowto(props: any) {
23
23
  const { target, ctx$: { model } } = props
24
24
 
25
25
  const entity = getModelPath(model, `main.${KIT}.entity`)
26
- const exampleEntity = Object.values(entity || {}).find((e: any) => e && e.active !== false) as any
26
+ // Pick an entity with a real op (prefer a read op) never fabricate a
27
+ // `load` on an op-less entity like Cloudsmith's `Abort`. primaryOp is null
28
+ // only when NO entity exposes any op (a direct()-only SDK).
29
+ const { entity: exampleEntity, primaryOp } = pickExampleEntity(entity)
27
30
  const eName = exampleEntity ? nom(exampleEntity, 'Name') : 'Entity'
31
+ // Sanitise the local variable name — an entity whose lowercased name is a
32
+ // Python keyword (e.g. `class`) would otherwise emit uncompilable code.
33
+ const eVar = safeVarName(eName.toLowerCase(), 'py')
28
34
  // Model-driven id key: null when the entity has no id-like field, so a
29
35
  // match op takes no argument.
30
36
  const idF = exampleEntity ? entityIdField(exampleEntity) : null
31
- // Drive the test-mode example off the entity's PRIMARY op — never a hardcoded
32
- // `load` a create-only entity lacks.
33
- const primaryOp = exampleEntity ? (entityPrimaryOp(exampleEntity) || 'load') : 'load'
34
37
  const isMatchOp = 'load' === primaryOp || 'remove' === primaryOp
35
38
  let testArg = ''
36
39
  if (exampleEntity && isMatchOp) {
@@ -43,6 +46,15 @@ const ReadmeHowto = cmp(function ReadmeHowto(props: any) {
43
46
  testArg = `{${chosen.map((it: any) => `"${it.name}": ${pyLit(it.type)}`).join(', ')}}`
44
47
  }
45
48
 
49
+ // The op-driven test-mode line, shown only when the SDK has an entity op.
50
+ // A direct()-only SDK (no ops anywhere) shows a direct() call instead.
51
+ const testModeExample = primaryOp
52
+ ? `# Entity ops return the bare record and raise on error.
53
+ ${eVar} = client.${eName}().${primaryOp}(${testArg})
54
+ # ${eVar} contains the mock response record`
55
+ : `result = client.direct({"path": "/api/resource", "method": "GET"})
56
+ print(result)`
57
+
46
58
  const apikeyEnvLine = isAuthActive(model)
47
59
  ? `\n${envName(model)}_APIKEY=<your-key>`
48
60
  : ''
@@ -90,9 +102,7 @@ Create a mock client for unit testing — no server required:
90
102
  \`\`\`python
91
103
  client = ${model.const.Name}SDK.test()
92
104
 
93
- # Entity ops return the bare record and raise on error.
94
- ${eName.toLowerCase()} = client.${eName}().${primaryOp}(${testArg})
95
- # ${eName.toLowerCase()} contains the mock response record
105
+ ${testModeExample}
96
106
  \`\`\`
97
107
 
98
108
  ### 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,
@@ -45,6 +45,9 @@ client = ${ctor}
45
45
  if (exampleEntity) {
46
46
  const eName = nom(exampleEntity, 'Name')
47
47
  const article = /^[aeiou]/i.test(eName) ? 'an' : 'a'
48
+ // Sanitise the local variable name — an entity whose lowercased name is a
49
+ // Python keyword (e.g. `class`) would otherwise emit uncompilable code.
50
+ const eVar = safeVarName(eName.toLowerCase(), 'py')
48
51
  const opnames = entityOps(exampleEntity)
49
52
  // Model-driven id key: `idF` is the entity's id-like MATCH field name, or
50
53
  // null when it has none (a response-wrapped spec). `dataIdF` is the id on
@@ -75,9 +78,9 @@ error — iterate it directly.
75
78
 
76
79
  \`\`\`python
77
80
  try:
78
- ${eName.toLowerCase()}s = client.${eName}().list()
79
- for ${eName.toLowerCase()} in ${eName.toLowerCase()}s:
80
- print(${eName.toLowerCase()})
81
+ ${eVar}s = client.${eName}().list()
82
+ for ${eVar} in ${eVar}s:
83
+ print(${eVar})
81
84
  except Exception as err:
82
85
  print(f"list failed: {err}")
83
86
  \`\`\`
@@ -88,7 +91,7 @@ except Exception as err:
88
91
  if (nestedEntity) {
89
92
  const neName = nom(nestedEntity, 'Name')
90
93
  const neArticle = /^[aeiou]/i.test(neName) ? 'an' : 'a'
91
- const neVar = neName.toLowerCase()
94
+ const neVar = safeVarName(neName.toLowerCase(), 'py')
92
95
 
93
96
  // Model-driven match: every REQUIRED load-match key — the same shape
94
97
  // the runtime resolves path params from, so the example always works.
@@ -140,8 +143,8 @@ except Exception as err:
140
143
 
141
144
  \`\`\`python
142
145
  try:
143
- ${eName.toLowerCase()} = client.${eName}().load(${loadArg})
144
- print(${eName.toLowerCase()})
146
+ ${eVar} = client.${eName}().load(${loadArg})
147
+ print(${eVar})
145
148
  except Exception as err:
146
149
  print(f"load failed: {err}")
147
150
  \`\`\`
@@ -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,
@@ -166,6 +166,9 @@ Prepare a fetch definition without sending. Returns the \`fetchdef\` and raises
166
166
  // Model-driven id key: null when this entity has no id-like field, in
167
167
  // which case load/remove match on no argument and update omits the id.
168
168
  const idF = entityIdField(ent)
169
+ // Sanitise the local variable name — an entity whose lowercased name is
170
+ // a Python keyword (e.g. `class`) would otherwise emit uncompilable code.
171
+ const eVar = safeVarName(ent.name, 'py')
169
172
 
170
173
  Content(`
171
174
  ---
@@ -181,7 +184,7 @@ Prepare a fetch definition without sending. Returns the \`fetchdef\` and raises
181
184
  }
182
185
 
183
186
  Content(`\`\`\`python
184
- ${ent.name} = client.${ent.Name}()
187
+ ${eVar} = client.${ent.Name}()
185
188
  \`\`\`
186
189
 
187
190
  `)
@@ -274,8 +277,8 @@ result = client.${ent.Name}().${opname}(${arg})
274
277
  else if ('list' === opname) {
275
278
  Content(`\`\`\`python
276
279
  results = client.${ent.Name}().list()
277
- for ${ent.name} in results:
278
- print(${ent.name})
280
+ for ${eVar} in results:
281
+ print(${eVar})
279
282
  \`\`\`
280
283
 
281
284
  `)
@@ -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,
@@ -44,6 +44,9 @@ client = ${ctor}
44
44
 
45
45
  if (exampleEntity) {
46
46
  const eName = nom(exampleEntity, 'Name')
47
+ // Sanitise the local variable name — an entity whose lowercased name is a
48
+ // Python keyword (e.g. `class`) would otherwise emit uncompilable code.
49
+ const eVar = safeVarName(eName.toLowerCase(), 'py')
47
50
  const opnames = Object.keys(exampleEntity.op || {})
48
51
  // Model-driven id key: null when the entity has no id-like field, in which
49
52
  // case the load example takes no match argument.
@@ -53,9 +56,9 @@ client = ${ctor}
53
56
 
54
57
  if (opnames.includes('list')) {
55
58
  Content(`# List all ${eName.toLowerCase()}s (returns a list, raises on error)
56
- ${eName.toLowerCase()}s = client.${eName}().list()
57
- for ${eName.toLowerCase()} in ${eName.toLowerCase()}s:
58
- print(${eName.toLowerCase()})
59
+ ${eVar}s = client.${eName}().list()
60
+ for ${eVar} in ${eVar}s:
61
+ print(${eVar})
59
62
  `)
60
63
  hasCall = true
61
64
  }
@@ -75,8 +78,8 @@ for ${eName.toLowerCase()} in ${eName.toLowerCase()}s:
75
78
  : ''
76
79
  Content(`
77
80
  # Load a specific ${eName.toLowerCase()} (returns the record, raises on error)
78
- ${eName.toLowerCase()} = client.${eName}().load(${loadArg})
79
- print(${eName.toLowerCase()})
81
+ ${eVar} = client.${eName}().load(${loadArg})
82
+ print(${eVar})
80
83
  `)
81
84
  hasCall = true
82
85
  }
@@ -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, safeVarName } from '@voxgig/sdkgen'
3
3
 
4
4
  import {
5
5
  KIT,
@@ -24,18 +24,17 @@ const ReadmeTopTest = cmp(function ReadmeTopTest(props: any) {
24
24
 
25
25
  const entity = getModelPath(model, `main.${KIT}.entity`)
26
26
 
27
- const exampleEntity = Object.values(entity).find((e: any) => e.active !== false) as any
27
+ // Pick an entity with a real op (prefer a read op) never fabricate a
28
+ // `load` on an op-less entity like Cloudsmith's `Abort`.
29
+ const { entity: exampleEntity, primaryOp } = pickExampleEntity(entity)
28
30
 
29
31
  Content(`\`\`\`python
30
32
  client = ${model.const.Name}SDK.test()
31
33
  `)
32
34
 
33
- if (exampleEntity) {
35
+ if (exampleEntity && primaryOp) {
34
36
  const eName = nom(exampleEntity, 'Name')
35
- // Drive the test-mode example off the entity's PRIMARY op (never a
36
- // hardcoded `load` a create-only entity lacks).
37
37
  const idF = entityIdField(exampleEntity)
38
- const primaryOp = entityPrimaryOp(exampleEntity) || 'load'
39
38
  const isMatchOp = 'load' === primaryOp || 'remove' === primaryOp
40
39
  let arg = ''
41
40
  if (isMatchOp) {
@@ -59,7 +58,7 @@ client = ${model.const.Name}SDK.test()
59
58
  // A list() result is a list — name the variable accordingly (the root
60
59
  // README doc gate concatenates blocks, so reusing the singular name for
61
60
  // a different type is a mypy assignment error).
62
- const eVar = eName.toLowerCase() + ('list' === primaryOp ? 's' : '')
61
+ const eVar = safeVarName(eName.toLowerCase(), 'py') + ('list' === primaryOp ? 's' : '')
63
62
  Content(`${eVar} = client.${eName}().${primaryOp}(${arg})
64
63
  print(${eVar})
65
64
  `)
@@ -7,7 +7,7 @@ from core import helpers
7
7
  # #TypeImports
8
8
 
9
9
 
10
- class EntityNameEntity:
10
+ class EntyClass:
11
11
 
12
12
  def __init__(self, client, entopts=None):
13
13
  if entopts is None:
@@ -40,7 +40,7 @@ class EntityNameEntity:
40
40
  opts = {}
41
41
  for k, v in self._entopts.items():
42
42
  opts[k] = v
43
- return EntityNameEntity(self._client, opts)
43
+ return EntyClass(self._client, opts)
44
44
 
45
45
  def data_set(self, args=None):
46
46
  if args is not None:
@@ -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_rb'
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.rb 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.call(ctx, "${name}")`),
49
65
 
@@ -1,17 +1,26 @@
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
  const MainEntity = cmp(async function MainEntity(props: any) {
7
12
  const { entity } = props
8
13
  const { model } = props.ctx$
9
14
 
15
+ // Return the collision-free class (entityClassName); the accessor METHOD name
16
+ // (entity.Name) is unchanged so callers still write client.<Name>.
17
+ const cls = entityClassName(entity, getModelPath(model, `main.${KIT}.entity`))
18
+
10
19
  Content(`
11
20
  # Canonical facade: client.${entity.Name}.list / client.${entity.Name}.load({ "id" => ... })
12
21
  def ${entity.Name}(data = nil)
13
22
  require_relative 'entity/${entity.name}_entity'
14
- ${entity.Name}Entity.new(self, data)
23
+ ${cls}.new(self, data)
15
24
  end
16
25
 
17
26
  `)
@@ -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,
@@ -56,6 +56,9 @@ const ReadmeEntity = cmp(function ReadmeEntity(props: any) {
56
56
  const fields = entity.fields || []
57
57
  // Model-driven id key: null when this entity has no id-like field.
58
58
  const idF = entityIdField(entity)
59
+ // Sanitise the local variable name — an entity whose lowercased name is a
60
+ // Ruby keyword (e.g. `self`) would otherwise emit uncompilable code.
61
+ const eVar = safeVarName(entity.name, 'rb')
59
62
 
60
63
  Content(`
61
64
  ### ${entity.Name}
@@ -68,7 +71,7 @@ const ReadmeEntity = cmp(function ReadmeEntity(props: any) {
68
71
  `)
69
72
  }
70
73
 
71
- Content(`Create an instance: \`${entity.name} = client.${entity.Name}\`
74
+ Content(`Create an instance: \`${eVar} = client.${entity.Name}\`
72
75
 
73
76
  `)
74
77
 
@@ -124,7 +127,7 @@ const ReadmeEntity = cmp(function ReadmeEntity(props: any) {
124
127
 
125
128
  \`\`\`ruby
126
129
  # load returns the bare ${entity.Name} record (raises on error).
127
- ${entity.name} = client.${entity.Name}.load(${loadArg})
130
+ ${eVar} = client.${entity.Name}.load(${loadArg})
128
131
  \`\`\`
129
132
 
130
133
  `)
@@ -135,7 +138,7 @@ ${entity.name} = client.${entity.Name}.load(${loadArg})
135
138
 
136
139
  \`\`\`ruby
137
140
  # list returns an Array of ${entity.Name} records (raises on error).
138
- ${entity.name}s = client.${entity.Name}.list
141
+ ${eVar}s = client.${entity.Name}.list
139
142
  \`\`\`
140
143
 
141
144
  `)
@@ -152,7 +155,7 @@ ${entity.name}s = client.${entity.Name}.list
152
155
  Content(`#### Example: Create
153
156
 
154
157
  \`\`\`ruby
155
- ${entity.name} = client.${entity.Name}.create({
158
+ ${eVar} = client.${entity.Name}.create({
156
159
  `)
157
160
  createItems.map((it: any) => {
158
161
  Content(` "${it.name}" => ${rbLit(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, safeVarName } from '@voxgig/sdkgen'
3
3
 
4
4
  import {
5
5
  KIT,
@@ -23,15 +23,20 @@ const ReadmeHowto = cmp(function ReadmeHowto(props: any) {
23
23
  const { target, ctx$: { model } } = props
24
24
 
25
25
  const entity = getModelPath(model, `main.${KIT}.entity`)
26
- const exampleEntity = Object.values(entity || {}).find((e: any) => e && e.active !== false) as any
26
+ // Pick an entity with a real op (prefer a read op) never fabricate a
27
+ // `load` on an op-less entity like Cloudsmith's `Abort`. primaryOp is null
28
+ // only when NO entity exposes any op (a direct()-only SDK).
29
+ const { entity: exampleEntity, primaryOp } = pickExampleEntity(entity)
27
30
  const eName = exampleEntity ? nom(exampleEntity, 'Name') : 'Entity'
31
+ // Sanitise the local variable name — an entity whose lowercased name is a
32
+ // Ruby keyword (e.g. `self`) would otherwise emit uncompilable code. The
33
+ // fixture KEY (`"${eName.toLowerCase()}"`) stays raw — it must match the
34
+ // entity's registered name for the mock lookup to resolve.
35
+ const eVar = safeVarName(eName.toLowerCase(), 'rb')
28
36
  // Model-driven id key: null when the entity has no id-like field (a
29
37
  // response-wrapped spec). When null the fixture seeds no id and a match op
30
38
  // takes no argument.
31
39
  const idF = exampleEntity ? entityIdField(exampleEntity) : null
32
- // Drive the test-mode example off the entity's PRIMARY op — an op it actually
33
- // exposes — never a hardcoded `load` a create-only entity lacks.
34
- const primaryOp = exampleEntity ? (entityPrimaryOp(exampleEntity) || 'load') : 'load'
35
40
  const isMatchOp = 'load' === primaryOp || 'remove' === primaryOp
36
41
  const seedSentence = idF
37
42
  ? '. Seed fixture\ndata via the `entity` option so offline calls resolve without a live server'
@@ -52,6 +57,15 @@ const ReadmeHowto = cmp(function ReadmeHowto(props: any) {
52
57
  testCallArg = `{ ${chosen.map((it: any) => `"${it.name}" => ${rbLit(it.type)}`).join(', ')} }`
53
58
  }
54
59
 
60
+ // The op-driven test-mode line, shown only when the SDK has an entity op.
61
+ // A direct()-only SDK (no ops anywhere) shows a direct() call instead.
62
+ const testModeExample = primaryOp
63
+ ? `# Entity ops return the bare mock record (raises on error).
64
+ ${eVar} = client.${eName}.${primaryOp}(${testCallArg})
65
+ puts ${eVar}`
66
+ : `result = client.direct({ "path" => "/api/resource", "method" => "GET" })
67
+ puts result`
68
+
55
69
  const apikeyEnvLine = isAuthActive(model)
56
70
  ? `\n${envName(model)}_APIKEY=<your-key>`
57
71
  : ''
@@ -101,9 +115,7 @@ Create a mock client for unit testing — no server required${seedSentence}:
101
115
  \`\`\`ruby
102
116
  client = ${testCtor}
103
117
 
104
- # Entity ops return the bare mock record (raises on error).
105
- ${eName.toLowerCase()} = client.${eName}.${primaryOp}(${testCallArg})
106
- puts ${eName.toLowerCase()}
118
+ ${testModeExample}
107
119
  \`\`\`
108
120
 
109
121
  ### 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 @@ 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
+ // Ruby keyword (e.g. `self`) would otherwise emit uncompilable code.
48
+ const eVar = safeVarName(eName.toLowerCase(), 'rb')
46
49
  const opnames = entityOps(exampleEntity)
47
50
  // Model-driven id keys: `idF` is the load-MATCH key (null when the entity
48
51
  // has no id-like field — a response-wrapped spec); when null, load/remove
@@ -84,8 +87,8 @@ client = ${ctor}
84
87
  \`\`\`ruby
85
88
  begin
86
89
  # list returns an Array of ${eName} records — iterate directly.
87
- ${eName.toLowerCase()}s = client.${eName}.list
88
- ${eName.toLowerCase()}s.each do |item|
90
+ ${eVar}s = client.${eName}.list
91
+ ${eVar}s.each do |item|
89
92
  puts "${itemPrint}"
90
93
  end
91
94
  rescue => err
@@ -99,7 +102,7 @@ end
99
102
  if (nestedEntity) {
100
103
  const neName = nom(nestedEntity, 'Name')
101
104
  const neArticle = /^[aeiou]/i.test(neName) ? "an" : "a"
102
- const neVar = neName.toLowerCase()
105
+ const neVar = safeVarName(neName.toLowerCase(), 'rb')
103
106
 
104
107
  // Model-driven match: every REQUIRED load-match key — the same shape
105
108
  // the runtime resolves path params from, so the example always works.
@@ -151,8 +154,8 @@ end
151
154
  \`\`\`ruby
152
155
  begin
153
156
  # load returns the bare ${eName} record (raises on error).
154
- ${eName.toLowerCase()} = client.${eName}.load(${loadArg})
155
- puts ${eName.toLowerCase()}
157
+ ${eVar} = client.${eName}.load(${loadArg})
158
+ puts ${eVar}
156
159
  rescue => err
157
160
  warn "load failed: #{err}"
158
161
  end