@voxgig/sdkgen 1.3.2 → 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 (97) 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/cmp/ReadmeTop.js +24 -2
  7. package/dist/cmp/ReadmeTop.js.map +1 -1
  8. package/dist/helpers/naming.js +29 -2
  9. package/dist/helpers/naming.js.map +1 -1
  10. package/dist/helpers/opShape.d.ts +6 -1
  11. package/dist/helpers/opShape.js +78 -0
  12. package/dist/helpers/opShape.js.map +1 -1
  13. package/dist/sdkgen.d.ts +2 -2
  14. package/dist/sdkgen.js +3 -1
  15. package/dist/sdkgen.js.map +1 -1
  16. package/package.json +1 -1
  17. package/project/.sdk/src/cmp/go/EntityOperation_go.ts +8 -1
  18. package/project/.sdk/src/cmp/go/Entity_go.ts +20 -3
  19. package/project/.sdk/src/cmp/go/Main_go.ts +2 -1
  20. package/project/.sdk/src/cmp/go/ReadmeEntity_go.ts +5 -5
  21. package/project/.sdk/src/cmp/go/ReadmeHowto_go.ts +23 -12
  22. package/project/.sdk/src/cmp/go/ReadmeModel_go.ts +27 -17
  23. package/project/.sdk/src/cmp/go/ReadmeRef_go.ts +5 -5
  24. package/project/.sdk/src/cmp/go/ReadmeTopTest_go.ts +5 -6
  25. package/project/.sdk/src/cmp/go/fragment/Entity.fragment.go +11 -11
  26. package/project/.sdk/src/cmp/go/fragment/EntityCreateOp.fragment.go +2 -2
  27. package/project/.sdk/src/cmp/go/fragment/EntityListOp.fragment.go +2 -2
  28. package/project/.sdk/src/cmp/go/fragment/EntityLoadOp.fragment.go +2 -2
  29. package/project/.sdk/src/cmp/go/fragment/EntityRemoveOp.fragment.go +2 -2
  30. package/project/.sdk/src/cmp/go/fragment/EntityUpdateOp.fragment.go +2 -2
  31. package/project/.sdk/src/cmp/js/Entity_js.ts +18 -2
  32. package/project/.sdk/src/cmp/js/MainEntity_js.ts +12 -2
  33. package/project/.sdk/src/cmp/js/Main_js.ts +5 -2
  34. package/project/.sdk/src/cmp/js/ReadmeEntity_js.ts +5 -5
  35. package/project/.sdk/src/cmp/js/ReadmeHowto_js.ts +37 -26
  36. package/project/.sdk/src/cmp/js/ReadmeRef_js.ts +5 -5
  37. package/project/.sdk/src/cmp/js/ReadmeTopTest_js.ts +5 -6
  38. package/project/.sdk/src/cmp/js/fragment/Entity.fragment.js +3 -3
  39. package/project/.sdk/src/cmp/lua/EntityOperation_lua.ts +6 -1
  40. package/project/.sdk/src/cmp/lua/Entity_lua.ts +20 -2
  41. package/project/.sdk/src/cmp/lua/ReadmeEntity_lua.ts +8 -5
  42. package/project/.sdk/src/cmp/lua/ReadmeHowto_lua.ts +14 -7
  43. package/project/.sdk/src/cmp/lua/ReadmeQuick_lua.ts +9 -6
  44. package/project/.sdk/src/cmp/lua/ReadmeRef_lua.ts +5 -2
  45. package/project/.sdk/src/cmp/lua/ReadmeTopQuick_lua.ts +8 -5
  46. package/project/.sdk/src/cmp/lua/ReadmeTopTest_lua.ts +5 -6
  47. package/project/.sdk/src/cmp/lua/fragment/Entity.fragment.lua +13 -13
  48. package/project/.sdk/src/cmp/lua/fragment/EntityCreateOp.fragment.lua +1 -1
  49. package/project/.sdk/src/cmp/lua/fragment/EntityListOp.fragment.lua +1 -1
  50. package/project/.sdk/src/cmp/lua/fragment/EntityLoadOp.fragment.lua +1 -1
  51. package/project/.sdk/src/cmp/lua/fragment/EntityRemoveOp.fragment.lua +1 -1
  52. package/project/.sdk/src/cmp/lua/fragment/EntityUpdateOp.fragment.lua +1 -1
  53. package/project/.sdk/src/cmp/php/Entity_php.ts +17 -1
  54. package/project/.sdk/src/cmp/php/MainEntity_php.ts +12 -3
  55. package/project/.sdk/src/cmp/php/ReadmeHowto_php.ts +15 -8
  56. package/project/.sdk/src/cmp/php/ReadmeTopTest_php.ts +5 -6
  57. package/project/.sdk/src/cmp/php/fragment/Entity.fragment.php +2 -2
  58. package/project/.sdk/src/cmp/py/Entity_py.ts +18 -1
  59. package/project/.sdk/src/cmp/py/MainEntity_py.ts +14 -4
  60. package/project/.sdk/src/cmp/py/Main_py.ts +2 -1
  61. package/project/.sdk/src/cmp/py/ReadmeEntity_py.ts +8 -5
  62. package/project/.sdk/src/cmp/py/ReadmeHowto_py.ts +18 -8
  63. package/project/.sdk/src/cmp/py/ReadmeQuick_py.ts +10 -7
  64. package/project/.sdk/src/cmp/py/ReadmeRef_py.ts +7 -4
  65. package/project/.sdk/src/cmp/py/ReadmeTopQuick_py.ts +9 -6
  66. package/project/.sdk/src/cmp/py/ReadmeTopTest_py.ts +6 -7
  67. package/project/.sdk/src/cmp/py/fragment/Entity.fragment.py +2 -2
  68. package/project/.sdk/src/cmp/rb/Entity_rb.ts +17 -1
  69. package/project/.sdk/src/cmp/rb/MainEntity_rb.ts +11 -2
  70. package/project/.sdk/src/cmp/rb/ReadmeEntity_rb.ts +8 -5
  71. package/project/.sdk/src/cmp/rb/ReadmeHowto_rb.ts +20 -8
  72. package/project/.sdk/src/cmp/rb/ReadmeQuick_rb.ts +9 -6
  73. package/project/.sdk/src/cmp/rb/ReadmeRef_rb.ts +5 -2
  74. package/project/.sdk/src/cmp/rb/ReadmeTopQuick_rb.ts +8 -5
  75. package/project/.sdk/src/cmp/rb/ReadmeTopTest_rb.ts +10 -8
  76. package/project/.sdk/src/cmp/rb/fragment/Entity.fragment.rb +2 -2
  77. package/project/.sdk/src/cmp/ts/Entity_ts.ts +18 -2
  78. package/project/.sdk/src/cmp/ts/MainEntity_ts.ts +12 -2
  79. package/project/.sdk/src/cmp/ts/Main_ts.ts +5 -2
  80. package/project/.sdk/src/cmp/ts/ReadmeEntity_ts.ts +5 -5
  81. package/project/.sdk/src/cmp/ts/ReadmeHowto_ts.ts +36 -26
  82. package/project/.sdk/src/cmp/ts/ReadmeRef_ts.ts +4 -5
  83. package/project/.sdk/src/cmp/ts/ReadmeTopTest_ts.ts +5 -6
  84. package/project/.sdk/src/cmp/ts/fragment/Entity.fragment.ts +4 -4
  85. package/project/.sdk/tm/go/utility/prepare_auth.go +6 -1
  86. package/project/.sdk/tm/js/src/utility/PrepareAuthUtility.js +3 -1
  87. package/project/.sdk/tm/lua/utility/prepare_auth.lua +6 -1
  88. package/project/.sdk/tm/php/utility/PrepareAuth.php +3 -1
  89. package/project/.sdk/tm/py/utility/prepare_auth.py +4 -1
  90. package/project/.sdk/tm/rb/utility/prepare_auth.rb +3 -1
  91. package/project/.sdk/tm/ts/src/utility/PrepareAuthUtility.ts +5 -1
  92. package/src/cmp/ReadmeErrors.ts +16 -17
  93. package/src/cmp/ReadmeExplanation.ts +37 -35
  94. package/src/cmp/ReadmeTop.ts +25 -2
  95. package/src/helpers/naming.ts +32 -2
  96. package/src/helpers/opShape.ts +89 -0
  97. package/src/sdkgen.ts +3 -1
@@ -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
@@ -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
  `)