@voxgig/sdkgen 1.3.4 → 1.3.6

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 (90) 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 +58 -29
  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/src/cmp/ReadmeErrors.ts +16 -17
  86. package/src/cmp/ReadmeExplanation.ts +37 -35
  87. package/src/cmp/ReadmeTop.ts +60 -31
  88. package/src/helpers/naming.ts +32 -2
  89. package/src/helpers/opShape.ts +89 -0
  90. package/src/sdkgen.ts +3 -1
@@ -1,16 +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
13
+ const { model } = props.ctx$
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`))
8
18
 
9
19
  Content(`
10
20
  // Entity access: \`client.${entity.Name}().list()\` / \`client.${entity.Name}().load({ id })\`.
11
21
  ${entity.Name}(data) {
12
22
  const self = this
13
- return new ${entity.Name}Entity(self,data)
23
+ return new ${cls}(self,data)
14
24
  }
15
25
 
16
26
  `)
@@ -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
 
@@ -55,8 +56,10 @@ const Main = cmp(async function Main(props: any) {
55
56
 
56
57
  Line(`// ${model.const.Name} ${target.Name} SDK\n`)
57
58
 
58
- List({ item: entity }, ({ item }: any) =>
59
- Line(`const { ${item.Name}Entity } = require('./entity/${item.Name}Entity')`))
59
+ List({ item: entity }, ({ item }: any) => {
60
+ const cls = entityClassName(item, entity)
61
+ return Line(`const { ${cls} } = require('./entity/${cls}')`)
62
+ })
60
63
 
61
64
  Fragment(
62
65
  {
@@ -132,10 +132,10 @@ const ${eVar}s = await client.${entity.Name}().list()
132
132
 
133
133
  if (opnames.includes('create')) {
134
134
  // Members come from the SAME shape the runtime validates
135
- // (opRequestShape): every required member must appear — including a
136
- // required id and parent keys like page_id (the /* type */
137
- // placeholders mark the block as an illustration); an all-optional
138
- // create renders an empty still valid — literal.
135
+ // (opRequestShape): every required member appears — including a
136
+ // required id and parent keys like page_id each with a type-correct
137
+ // example VALUE via exampleValue a `name: /* type */` comment is not
138
+ // a value and yields invalid code.
139
139
  const createItems = opRequestShape(entity, 'create').items
140
140
  .filter((it: any) => !it.optional)
141
141
  Content(`#### Example: Create
@@ -144,7 +144,7 @@ const ${eVar}s = await client.${entity.Name}().list()
144
144
  const ${eVar} = await client.${entity.Name}().create({
145
145
  `)
146
146
  createItems.map((it: any) => {
147
- Content(` ${it.name}: /* ${canonToType(it.type, target.name)} */,
147
+ Content(` ${it.name}: ${exampleValue(entity, entity.op && entity.op.create, it.name, 'example_' + it.name)},
148
148
  `)
149
149
  })
150
150
  Content(`})
@@ -1,5 +1,5 @@
1
1
 
2
- import { cmp, Content, isAuthActive, envName, entityIdField, entityDataIdField, entityPrimaryOp, opRequestShape, safeVarName } from '@voxgig/sdkgen'
2
+ import { cmp, Content, isAuthActive, envName, entityIdField, entityDataIdField, pickExampleEntity, opRequestShape, safeVarName } from '@voxgig/sdkgen'
3
3
 
4
4
  import {
5
5
  KIT,
@@ -14,14 +14,14 @@ const ReadmeHowto = cmp(function ReadmeHowto(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 && 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`. primaryOp is null
19
+ // only when NO entity exposes any op (a direct()-only SDK).
20
+ const { entity: exampleEntity, primaryOp } = pickExampleEntity(entity)
18
21
  const eName = exampleEntity ? nom(exampleEntity, 'Name') : 'Entity'
19
22
  const eVar = safeVarName(eName.toLowerCase(), 'js')
20
23
 
21
- // Drive the test-mode / stateful examples off the entity's PRIMARY op an op
22
- // it actually exposes — never a hardcoded `load` a create-only entity lacks.
23
- const primaryOp = exampleEntity ? (entityPrimaryOp(exampleEntity) || 'load') : 'load'
24
- const primaryOpDef = exampleEntity && exampleEntity.op && exampleEntity.op[primaryOp]
24
+ const primaryOpDef = exampleEntity && primaryOp && exampleEntity.op && exampleEntity.op[primaryOp]
25
25
  const isMatchOp = 'load' === primaryOp || 'remove' === primaryOp
26
26
  // idF is the MATCH key; dataIdF is the id on the RETURNED record's data type
27
27
  // (guard `.id` reads off a returned record on this, not the match key).
@@ -29,7 +29,7 @@ const ReadmeHowto = cmp(function ReadmeHowto(props: any) {
29
29
  const dataIdF = exampleEntity ? entityDataIdField(exampleEntity) : null
30
30
 
31
31
  const primaryArg = (idPlaceholder: string): string => {
32
- if (!exampleEntity) return ''
32
+ if (!exampleEntity || !primaryOp) return ''
33
33
  if ('list' === primaryOp) return ''
34
34
  if (isMatchOp) {
35
35
  return idF ? `{ ${idF}: ${exampleValue(exampleEntity, primaryOpDef, idF, idPlaceholder)} }` : ''
@@ -48,6 +48,34 @@ const ReadmeHowto = cmp(function ReadmeHowto(props: any) {
48
48
  ? `console.log(data.${dataIdF})`
49
49
  : `console.log(data)`
50
50
 
51
+ // The op-driven example lines, shown only when the SDK has an entity op.
52
+ // A direct()-only SDK (no ops anywhere) shows a direct() test call instead
53
+ // and omits the stateful section (there is no op to retain state across).
54
+ const testModeExample = primaryOp
55
+ ? `const ${eVar} = await client.${eName}().${primaryOp}(${testCallArg})
56
+ // ${eVar} is a bare entity populated with mock response data
57
+ console.log(${eVar})`
58
+ : `const result = await client.direct({ path: '/api/resource', method: 'GET' })
59
+ console.log(result)`
60
+ const stateSection = primaryOp
61
+ ? `### Retain entity state across calls
62
+
63
+ Entity instances remember their last match and data:
64
+
65
+ \`\`\`js
66
+ const entity = client.${eName}()
67
+
68
+ // First call runs the operation and stores its result
69
+ await entity.${primaryOp}(${stateCallArg})
70
+
71
+ // Subsequent calls reuse the stored state
72
+ const data = entity.data()
73
+ ${stateDataLine}
74
+ \`\`\`
75
+
76
+ `
77
+ : ''
78
+
51
79
  const authActive = isAuthActive(model)
52
80
  const apikeyTesterCtor = authActive
53
81
  ? `new ${model.const.Name}SDK({ apikey: '...' })`
@@ -97,9 +125,7 @@ Create a mock client for unit testing — no server required:
97
125
  \`\`\`js
98
126
  const client = ${model.const.Name}SDK.test()
99
127
 
100
- const ${eVar} = await client.${eName}().${primaryOp}(${testCallArg})
101
- // ${eVar} is a bare entity populated with mock response data
102
- console.log(${eVar})
128
+ ${testModeExample}
103
129
  \`\`\`
104
130
 
105
131
  You can also use the instance method:
@@ -109,22 +135,7 @@ const client = ${apikeyTesterCtor}
109
135
  const testClient = client.tester()
110
136
  \`\`\`
111
137
 
112
- ### Retain entity state across calls
113
-
114
- Entity instances remember their last match and data:
115
-
116
- \`\`\`js
117
- const entity = client.${eName}()
118
-
119
- // First call runs the operation and stores its result
120
- await entity.${primaryOp}(${stateCallArg})
121
-
122
- // Subsequent calls reuse the stored state
123
- const data = entity.data()
124
- ${stateDataLine}
125
- \`\`\`
126
-
127
- ### Add custom middleware
138
+ ${stateSection}### Add custom middleware
128
139
 
129
140
  Pass features via the \`extend\` option:
130
141
 
@@ -299,17 +299,17 @@ const results = await client.${ent.Name}().${opname}()
299
299
  }
300
300
  else if ('create' === opname) {
301
301
  // Members come from the SAME shape the runtime validates
302
- // (opRequestShape): every required member must appear — including
303
- // a required id and parent keys like page_id (the /* type */
304
- // placeholders mark the block as an illustration); an
305
- // all-optional create renders an empty still valid literal.
302
+ // (opRequestShape): every required member appears — including
303
+ // a required id and parent keys like page_id each with a
304
+ // type-correct example VALUE via exampleValue a
305
+ // `name: /* type */` comment is not a value and yields invalid code.
306
306
  const createItems = opRequestShape(ent, 'create').items
307
307
  .filter((it: any) => !it.optional)
308
308
  Content(`\`\`\`ts
309
309
  const result = await client.${ent.Name}().create({
310
310
  `)
311
311
  createItems.map((it: any) => {
312
- Content(` ${it.name}: /* ${canonToType(it.type, target.name)} */,
312
+ Content(` ${it.name}: ${exampleValue(ent, ent.op && ent.op.create, it.name, 'example_' + it.name)},
313
313
  `)
314
314
  })
315
315
  Content(`})
@@ -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