@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.
- package/bin/voxgig-sdkgen +1 -1
- package/dist/cmp/ReadmeErrors.js +15 -15
- package/dist/cmp/ReadmeErrors.js.map +1 -1
- package/dist/cmp/ReadmeExplanation.js +37 -35
- package/dist/cmp/ReadmeExplanation.js.map +1 -1
- package/dist/cmp/ReadmeTop.js +24 -2
- package/dist/cmp/ReadmeTop.js.map +1 -1
- package/dist/helpers/naming.js +29 -2
- package/dist/helpers/naming.js.map +1 -1
- package/dist/helpers/opShape.d.ts +6 -1
- package/dist/helpers/opShape.js +78 -0
- package/dist/helpers/opShape.js.map +1 -1
- package/dist/sdkgen.d.ts +2 -2
- package/dist/sdkgen.js +3 -1
- package/dist/sdkgen.js.map +1 -1
- package/package.json +1 -1
- package/project/.sdk/src/cmp/go/EntityOperation_go.ts +8 -1
- package/project/.sdk/src/cmp/go/Entity_go.ts +20 -3
- package/project/.sdk/src/cmp/go/Main_go.ts +2 -1
- package/project/.sdk/src/cmp/go/ReadmeEntity_go.ts +5 -5
- package/project/.sdk/src/cmp/go/ReadmeHowto_go.ts +23 -12
- package/project/.sdk/src/cmp/go/ReadmeModel_go.ts +27 -17
- package/project/.sdk/src/cmp/go/ReadmeRef_go.ts +5 -5
- package/project/.sdk/src/cmp/go/ReadmeTopTest_go.ts +5 -6
- package/project/.sdk/src/cmp/go/fragment/Entity.fragment.go +11 -11
- package/project/.sdk/src/cmp/go/fragment/EntityCreateOp.fragment.go +2 -2
- package/project/.sdk/src/cmp/go/fragment/EntityListOp.fragment.go +2 -2
- package/project/.sdk/src/cmp/go/fragment/EntityLoadOp.fragment.go +2 -2
- package/project/.sdk/src/cmp/go/fragment/EntityRemoveOp.fragment.go +2 -2
- package/project/.sdk/src/cmp/go/fragment/EntityUpdateOp.fragment.go +2 -2
- package/project/.sdk/src/cmp/js/Entity_js.ts +18 -2
- package/project/.sdk/src/cmp/js/MainEntity_js.ts +12 -2
- package/project/.sdk/src/cmp/js/Main_js.ts +5 -2
- package/project/.sdk/src/cmp/js/ReadmeEntity_js.ts +5 -5
- package/project/.sdk/src/cmp/js/ReadmeHowto_js.ts +37 -26
- package/project/.sdk/src/cmp/js/ReadmeRef_js.ts +5 -5
- package/project/.sdk/src/cmp/js/ReadmeTopTest_js.ts +5 -6
- package/project/.sdk/src/cmp/js/fragment/Entity.fragment.js +3 -3
- package/project/.sdk/src/cmp/lua/EntityOperation_lua.ts +6 -1
- package/project/.sdk/src/cmp/lua/Entity_lua.ts +20 -2
- package/project/.sdk/src/cmp/lua/ReadmeEntity_lua.ts +8 -5
- package/project/.sdk/src/cmp/lua/ReadmeHowto_lua.ts +14 -7
- package/project/.sdk/src/cmp/lua/ReadmeQuick_lua.ts +9 -6
- package/project/.sdk/src/cmp/lua/ReadmeRef_lua.ts +5 -2
- package/project/.sdk/src/cmp/lua/ReadmeTopQuick_lua.ts +8 -5
- package/project/.sdk/src/cmp/lua/ReadmeTopTest_lua.ts +5 -6
- package/project/.sdk/src/cmp/lua/fragment/Entity.fragment.lua +13 -13
- package/project/.sdk/src/cmp/lua/fragment/EntityCreateOp.fragment.lua +1 -1
- package/project/.sdk/src/cmp/lua/fragment/EntityListOp.fragment.lua +1 -1
- package/project/.sdk/src/cmp/lua/fragment/EntityLoadOp.fragment.lua +1 -1
- package/project/.sdk/src/cmp/lua/fragment/EntityRemoveOp.fragment.lua +1 -1
- package/project/.sdk/src/cmp/lua/fragment/EntityUpdateOp.fragment.lua +1 -1
- package/project/.sdk/src/cmp/php/Entity_php.ts +17 -1
- package/project/.sdk/src/cmp/php/MainEntity_php.ts +12 -3
- package/project/.sdk/src/cmp/php/ReadmeHowto_php.ts +15 -8
- package/project/.sdk/src/cmp/php/ReadmeTopTest_php.ts +5 -6
- package/project/.sdk/src/cmp/php/fragment/Entity.fragment.php +2 -2
- package/project/.sdk/src/cmp/py/Entity_py.ts +18 -1
- package/project/.sdk/src/cmp/py/MainEntity_py.ts +14 -4
- package/project/.sdk/src/cmp/py/Main_py.ts +2 -1
- package/project/.sdk/src/cmp/py/ReadmeEntity_py.ts +8 -5
- package/project/.sdk/src/cmp/py/ReadmeHowto_py.ts +18 -8
- package/project/.sdk/src/cmp/py/ReadmeQuick_py.ts +10 -7
- package/project/.sdk/src/cmp/py/ReadmeRef_py.ts +7 -4
- package/project/.sdk/src/cmp/py/ReadmeTopQuick_py.ts +9 -6
- package/project/.sdk/src/cmp/py/ReadmeTopTest_py.ts +6 -7
- package/project/.sdk/src/cmp/py/fragment/Entity.fragment.py +2 -2
- package/project/.sdk/src/cmp/rb/Entity_rb.ts +17 -1
- package/project/.sdk/src/cmp/rb/MainEntity_rb.ts +11 -2
- package/project/.sdk/src/cmp/rb/ReadmeEntity_rb.ts +8 -5
- package/project/.sdk/src/cmp/rb/ReadmeHowto_rb.ts +20 -8
- package/project/.sdk/src/cmp/rb/ReadmeQuick_rb.ts +9 -6
- package/project/.sdk/src/cmp/rb/ReadmeRef_rb.ts +5 -2
- package/project/.sdk/src/cmp/rb/ReadmeTopQuick_rb.ts +8 -5
- package/project/.sdk/src/cmp/rb/ReadmeTopTest_rb.ts +10 -8
- package/project/.sdk/src/cmp/rb/fragment/Entity.fragment.rb +2 -2
- package/project/.sdk/src/cmp/ts/Entity_ts.ts +18 -2
- package/project/.sdk/src/cmp/ts/MainEntity_ts.ts +12 -2
- package/project/.sdk/src/cmp/ts/Main_ts.ts +5 -2
- package/project/.sdk/src/cmp/ts/ReadmeEntity_ts.ts +5 -5
- package/project/.sdk/src/cmp/ts/ReadmeHowto_ts.ts +36 -26
- package/project/.sdk/src/cmp/ts/ReadmeRef_ts.ts +4 -5
- package/project/.sdk/src/cmp/ts/ReadmeTopTest_ts.ts +5 -6
- package/project/.sdk/src/cmp/ts/fragment/Entity.fragment.ts +4 -4
- package/project/.sdk/tm/go/utility/prepare_auth.go +6 -1
- package/project/.sdk/tm/js/src/utility/PrepareAuthUtility.js +3 -1
- package/project/.sdk/tm/lua/utility/prepare_auth.lua +6 -1
- package/project/.sdk/tm/php/utility/PrepareAuth.php +3 -1
- package/project/.sdk/tm/py/utility/prepare_auth.py +4 -1
- package/project/.sdk/tm/rb/utility/prepare_auth.rb +3 -1
- package/project/.sdk/tm/ts/src/utility/PrepareAuthUtility.ts +5 -1
- package/src/cmp/ReadmeErrors.ts +16 -17
- package/src/cmp/ReadmeExplanation.ts +37 -35
- package/src/cmp/ReadmeTop.ts +25 -2
- package/src/helpers/naming.ts +32 -2
- package/src/helpers/opShape.ts +89 -0
- package/src/sdkgen.ts +3 -1
|
@@ -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: \`${
|
|
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
|
-
${
|
|
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
|
-
${
|
|
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
|
-
${
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
${
|
|
88
|
-
${
|
|
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
|
-
${
|
|
155
|
-
puts ${
|
|
157
|
+
${eVar} = client.${eName}.load(${loadArg})
|
|
158
|
+
puts ${eVar}
|
|
156
159
|
rescue => err
|
|
157
160
|
warn "load failed: #{err}"
|
|
158
161
|
end
|
|
@@ -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,
|
|
@@ -172,6 +172,9 @@ same parameters as \`direct()\`. Raises on error.
|
|
|
172
172
|
// Model-driven id key: null when this entity has no id-like field, in
|
|
173
173
|
// which case load/remove match on no argument and update omits the id.
|
|
174
174
|
const idF = entityIdField(ent)
|
|
175
|
+
// Sanitise the local variable name — an entity whose lowercased name is
|
|
176
|
+
// a Ruby keyword (e.g. `self`) would otherwise emit uncompilable code.
|
|
177
|
+
const eVar = safeVarName(ent.name, 'rb')
|
|
175
178
|
|
|
176
179
|
Content(`
|
|
177
180
|
---
|
|
@@ -187,7 +190,7 @@ same parameters as \`direct()\`. Raises on error.
|
|
|
187
190
|
}
|
|
188
191
|
|
|
189
192
|
Content(`\`\`\`ruby
|
|
190
|
-
${
|
|
193
|
+
${eVar} = client.${ent.Name}
|
|
191
194
|
\`\`\`
|
|
192
195
|
|
|
193
196
|
`)
|
|
@@ -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,
|
|
@@ -43,6 +43,9 @@ client = ${ctor}
|
|
|
43
43
|
|
|
44
44
|
if (exampleEntity) {
|
|
45
45
|
const eName = nom(exampleEntity, 'Name')
|
|
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 = Object.keys(exampleEntity.op || {})
|
|
47
50
|
// Model-driven id key: null when the entity has no id-like field, in which
|
|
48
51
|
// case the load example takes no match argument.
|
|
@@ -52,8 +55,8 @@ client = ${ctor}
|
|
|
52
55
|
|
|
53
56
|
if (opnames.includes('list')) {
|
|
54
57
|
Content(`# List all ${eName.toLowerCase()}s (returns an Array; raises on error)
|
|
55
|
-
${
|
|
56
|
-
puts ${
|
|
58
|
+
${eVar}s = client.${eName}.list
|
|
59
|
+
puts ${eVar}s
|
|
57
60
|
`)
|
|
58
61
|
hasCall = true
|
|
59
62
|
}
|
|
@@ -73,8 +76,8 @@ puts ${eName.toLowerCase()}s
|
|
|
73
76
|
: ''
|
|
74
77
|
Content(`
|
|
75
78
|
# Load a specific ${eName.toLowerCase()} (returns the bare record; raises on error)
|
|
76
|
-
${
|
|
77
|
-
puts ${
|
|
79
|
+
${eVar} = client.${eName}.load(${loadArg})
|
|
80
|
+
puts ${eVar}
|
|
78
81
|
`)
|
|
79
82
|
hasCall = true
|
|
80
83
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { cmp, Content, canonKey, entityIdField,
|
|
2
|
+
import { cmp, Content, canonKey, entityIdField, pickExampleEntity, opRequestShape, safeVarName } from '@voxgig/sdkgen'
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
KIT,
|
|
@@ -24,17 +24,16 @@ const ReadmeTopTest = cmp(function ReadmeTopTest(props: any) {
|
|
|
24
24
|
|
|
25
25
|
const entity = getModelPath(model, `main.${KIT}.entity`)
|
|
26
26
|
|
|
27
|
-
|
|
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
|
-
if (exampleEntity) {
|
|
31
|
+
if (exampleEntity && primaryOp) {
|
|
30
32
|
const eName = nom(exampleEntity, 'Name')
|
|
31
33
|
const ename = eName.toLowerCase()
|
|
32
34
|
// Model-driven id key: null when the entity has no id-like field, so the
|
|
33
35
|
// seeded record carries no id and a match op takes no argument.
|
|
34
36
|
const idF = entityIdField(exampleEntity)
|
|
35
|
-
// Drive the test-mode example off the entity's PRIMARY op — never a
|
|
36
|
-
// hardcoded `load` a create-only entity lacks.
|
|
37
|
-
const primaryOp = entityPrimaryOp(exampleEntity) || 'load'
|
|
38
37
|
const isMatchOp = 'load' === primaryOp || 'remove' === primaryOp
|
|
39
38
|
const recBody = idF ? `{ "${idF}" => "test01" }` : '{}'
|
|
40
39
|
let callArg = ''
|
|
@@ -56,8 +55,11 @@ const ReadmeTopTest = cmp(function ReadmeTopTest(props: any) {
|
|
|
56
55
|
const chosen = required.length ? required : items.slice(0, 3)
|
|
57
56
|
callArg = `{ ${chosen.map((it: any) => `"${it.name}" => ${rbLit(it.type)}`).join(', ')} }`
|
|
58
57
|
}
|
|
59
|
-
// A list result is an Array — name the variable accordingly.
|
|
60
|
-
|
|
58
|
+
// A list result is an Array — name the variable accordingly. Sanitise the
|
|
59
|
+
// base name — an entity whose lowercased name is a Ruby keyword (e.g.
|
|
60
|
+
// `self`) would otherwise emit uncompilable code. The fixture KEY (`ename`)
|
|
61
|
+
// stays raw so the mock lookup resolves.
|
|
62
|
+
const eVar = safeVarName(ename, 'rb') + ('list' === primaryOp ? 's' : '')
|
|
61
63
|
Content(`\`\`\`ruby
|
|
62
64
|
# Seed fixture data so offline calls resolve without a live server.
|
|
63
65
|
client = ${model.const.Name}SDK.test({
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
require_relative '../utility/struct/voxgig_struct'
|
|
4
4
|
require_relative '../core/helpers'
|
|
5
5
|
|
|
6
|
-
class
|
|
6
|
+
class EntyClass
|
|
7
7
|
def initialize(client, entopts = nil)
|
|
8
8
|
entopts ||= {}
|
|
9
9
|
if entopts["active"].nil?
|
|
@@ -35,7 +35,7 @@ class EntityNameEntity
|
|
|
35
35
|
|
|
36
36
|
def make
|
|
37
37
|
opts = @_entopts.dup
|
|
38
|
-
|
|
38
|
+
EntyClass.new(@_client, opts)
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
def data_set(args)
|
|
@@ -4,9 +4,14 @@ import * as Path from 'node:path'
|
|
|
4
4
|
import {
|
|
5
5
|
cmp, each, camelify, names,
|
|
6
6
|
File, Content, Folder, Fragment, Line, FeatureHook, Slot,
|
|
7
|
-
opTypeName,
|
|
7
|
+
opTypeName, entityClassName,
|
|
8
8
|
} from '@voxgig/sdkgen'
|
|
9
9
|
|
|
10
|
+
import {
|
|
11
|
+
KIT,
|
|
12
|
+
getModelPath
|
|
13
|
+
} from '@voxgig/apidef'
|
|
14
|
+
|
|
10
15
|
import { EntityOperation } from './EntityOperation_ts'
|
|
11
16
|
// import { EntityTest } from './EntityTest_ts'
|
|
12
17
|
|
|
@@ -15,6 +20,13 @@ const Entity = cmp(function Entity(props: any) {
|
|
|
15
20
|
const { model, stdrep } = props.ctx$
|
|
16
21
|
const { target, entity } = props
|
|
17
22
|
|
|
23
|
+
// Collision-free entity CLASS name (see entityClassName): normally
|
|
24
|
+
// `<Name>Entity`, disambiguated when it would clash with another entity's
|
|
25
|
+
// data-type name. The DATA type stays `<Name>`. The class file name and the
|
|
26
|
+
// Main import path both use this, so they always agree.
|
|
27
|
+
const entityColl = getModelPath(model, `main.${KIT}.entity`)
|
|
28
|
+
const cls = entityClassName(entity, entityColl)
|
|
29
|
+
|
|
18
30
|
const entrep = {
|
|
19
31
|
...stdrep,
|
|
20
32
|
}
|
|
@@ -38,7 +50,7 @@ const Entity = cmp(function Entity(props: any) {
|
|
|
38
50
|
|
|
39
51
|
Folder({ name: 'src/entity' }, () => {
|
|
40
52
|
|
|
41
|
-
File({ name:
|
|
53
|
+
File({ name: cls + '.' + target.name }, () => {
|
|
42
54
|
|
|
43
55
|
const opnames = Object.keys(entity.op)
|
|
44
56
|
|
|
@@ -58,6 +70,10 @@ const Entity = cmp(function Entity(props: any) {
|
|
|
58
70
|
SdkName: model.const.Name,
|
|
59
71
|
EntityName: entity.Name,
|
|
60
72
|
|
|
73
|
+
// Class token decoupled from the EntityName data-type token in
|
|
74
|
+
// Entity.fragment.ts so the class can be renamed independently.
|
|
75
|
+
EntyClass: cls,
|
|
76
|
+
|
|
61
77
|
'#TypeImports': ({ indent }: any) => Content({ indent }, typeImport),
|
|
62
78
|
|
|
63
79
|
'#Feature-Hook': ({ name, indent }: any) =>
|
|
@@ -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 TYPE (entityClassName); the accessor METHOD
|
|
16
|
+
// name (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?: any) {
|
|
12
22
|
const self = this
|
|
13
|
-
return new ${
|
|
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
|
-
|
|
59
|
+
List({ item: entity }, ({ item }: any) => {
|
|
60
|
+
const cls = entityClassName(item, entity)
|
|
61
|
+
return Line(`import { ${cls} } from './entity/${cls}'`)
|
|
62
|
+
})
|
|
60
63
|
|
|
61
64
|
// Re-export the generated typed models so consumers can
|
|
62
65
|
// `import { Advice, AdviceLoadMatch } from '<pkg>'`.
|
|
@@ -131,10 +131,10 @@ const ${eVar}s = await client.${entity.Name}().list()
|
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
if (opnames.includes('create')) {
|
|
134
|
-
// Members come from the SAME shape that generates <Name>CreateData
|
|
135
|
-
// every required member
|
|
136
|
-
//
|
|
137
|
-
//
|
|
134
|
+
// Members come from the SAME shape that generates <Name>CreateData
|
|
135
|
+
// (every required member appears), with a type-correct example VALUE
|
|
136
|
+
// via exampleValue — a `name: /* type */` comment is not a value and
|
|
137
|
+
// yields invalid TS (TS1109), so the example must carry a real literal.
|
|
138
138
|
const createItems = opRequestShape(entity, 'create').items
|
|
139
139
|
.filter((it: any) => !it.optional)
|
|
140
140
|
Content(`#### Example: Create
|
|
@@ -143,7 +143,7 @@ const ${eVar}s = await client.${entity.Name}().list()
|
|
|
143
143
|
const ${eVar} = await client.${entity.Name}().create({
|
|
144
144
|
`)
|
|
145
145
|
createItems.map((it: any) => {
|
|
146
|
-
Content(` ${it.name}:
|
|
146
|
+
Content(` ${it.name}: ${exampleValue(entity, entity.op && entity.op.create, it.name, 'example_' + it.name)},
|
|
147
147
|
`)
|
|
148
148
|
})
|
|
149
149
|
Content(`})
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { cmp, Content, isAuthActive, envName, entityIdField, entityDataIdField,
|
|
2
|
+
import { cmp, Content, isAuthActive, envName, entityIdField, entityDataIdField, pickExampleEntity, opRequestShape, safeVarName } from '@voxgig/sdkgen'
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
KIT,
|
|
@@ -14,15 +14,15 @@ 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
|
-
|
|
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
|
// Variable-safe lowercase name (a `Delete` entity must not bind `delete`).
|
|
20
23
|
const eVar = safeVarName(eName.toLowerCase(), 'ts')
|
|
21
24
|
|
|
22
|
-
|
|
23
|
-
// it actually exposes — never a hardcoded `load` a create-only entity lacks.
|
|
24
|
-
const primaryOp = exampleEntity ? (entityPrimaryOp(exampleEntity) || 'load') : 'load'
|
|
25
|
-
const primaryOpDef = exampleEntity && exampleEntity.op && exampleEntity.op[primaryOp]
|
|
25
|
+
const primaryOpDef = exampleEntity && primaryOp && exampleEntity.op && exampleEntity.op[primaryOp]
|
|
26
26
|
const isMatchOp = 'load' === primaryOp || 'remove' === primaryOp
|
|
27
27
|
// Model-driven id key: `idF` is the entity's id-like MATCH field name, or null
|
|
28
28
|
// when it has none. `dataIdF` is the id on the RETURNED record's data type —
|
|
@@ -32,7 +32,7 @@ const ReadmeHowto = cmp(function ReadmeHowto(props: any) {
|
|
|
32
32
|
|
|
33
33
|
// A type-correct, language-idiomatic argument for the primary op call.
|
|
34
34
|
const primaryArg = (idPlaceholder: string): string => {
|
|
35
|
-
if (!exampleEntity) return ''
|
|
35
|
+
if (!exampleEntity || !primaryOp) return ''
|
|
36
36
|
if ('list' === primaryOp) return ''
|
|
37
37
|
if (isMatchOp) {
|
|
38
38
|
return idF ? `{ ${idF}: ${exampleValue(exampleEntity, primaryOpDef, idF, idPlaceholder)} }` : ''
|
|
@@ -53,6 +53,33 @@ const ReadmeHowto = cmp(function ReadmeHowto(props: any) {
|
|
|
53
53
|
? `console.log(data.${dataIdF})`
|
|
54
54
|
: `console.log(data)`
|
|
55
55
|
|
|
56
|
+
// The op-driven example lines, shown only when the SDK has an entity op.
|
|
57
|
+
// A direct()-only SDK (no ops anywhere) shows a direct() test call instead.
|
|
58
|
+
const testModeExample = primaryOp
|
|
59
|
+
? `const ${eVar} = await client.${eName}().${primaryOp}(${testCallArg})
|
|
60
|
+
// ${eVar} is a bare entity populated with mock response data
|
|
61
|
+
console.log(${eVar})`
|
|
62
|
+
: `const result = await client.direct({ path: '/api/resource', method: 'GET' })
|
|
63
|
+
console.log(result)`
|
|
64
|
+
const stateSection = primaryOp
|
|
65
|
+
? `### Retain entity state across calls
|
|
66
|
+
|
|
67
|
+
Entity instances remember their last match and data:
|
|
68
|
+
|
|
69
|
+
\`\`\`ts
|
|
70
|
+
const entity = client.${eName}()
|
|
71
|
+
|
|
72
|
+
// First call runs the operation and stores its result
|
|
73
|
+
await entity.${primaryOp}(${stateCallArg})
|
|
74
|
+
|
|
75
|
+
// Subsequent calls reuse the stored state
|
|
76
|
+
const data = entity.data()
|
|
77
|
+
${stateDataLine}
|
|
78
|
+
\`\`\`
|
|
79
|
+
|
|
80
|
+
`
|
|
81
|
+
: ''
|
|
82
|
+
|
|
56
83
|
const authActive = isAuthActive(model)
|
|
57
84
|
const apikeyTesterCtor = authActive
|
|
58
85
|
? `new ${model.const.Name}SDK({ apikey: '...' })`
|
|
@@ -102,9 +129,7 @@ Create a mock client for unit testing \u2014 no server required:
|
|
|
102
129
|
\`\`\`ts
|
|
103
130
|
const client = ${model.const.Name}SDK.test()
|
|
104
131
|
|
|
105
|
-
|
|
106
|
-
// ${eVar} is a bare entity populated with mock response data
|
|
107
|
-
console.log(${eVar})
|
|
132
|
+
${testModeExample}
|
|
108
133
|
\`\`\`
|
|
109
134
|
|
|
110
135
|
You can also use the instance method:
|
|
@@ -114,22 +139,7 @@ const client = ${apikeyTesterCtor}
|
|
|
114
139
|
const testClient = client.tester()
|
|
115
140
|
\`\`\`
|
|
116
141
|
|
|
117
|
-
###
|
|
118
|
-
|
|
119
|
-
Entity instances remember their last match and data:
|
|
120
|
-
|
|
121
|
-
\`\`\`ts
|
|
122
|
-
const entity = client.${eName}()
|
|
123
|
-
|
|
124
|
-
// First call runs the operation and stores its result
|
|
125
|
-
await entity.${primaryOp}(${stateCallArg})
|
|
126
|
-
|
|
127
|
-
// Subsequent calls reuse the stored state
|
|
128
|
-
const data = entity.data()
|
|
129
|
-
${stateDataLine}
|
|
130
|
-
\`\`\`
|
|
131
|
-
|
|
132
|
-
### Add custom middleware
|
|
142
|
+
${stateSection}### Add custom middleware
|
|
133
143
|
|
|
134
144
|
Pass features via the \`extend\` option:
|
|
135
145
|
|
|
@@ -299,17 +299,16 @@ const results = await client.${ent.Name}().${opname}()
|
|
|
299
299
|
}
|
|
300
300
|
else if ('create' === opname) {
|
|
301
301
|
// Members come from the SAME shape that generates
|
|
302
|
-
// <Name>CreateData
|
|
303
|
-
//
|
|
304
|
-
//
|
|
305
|
-
// still assignable — literal.
|
|
302
|
+
// <Name>CreateData (every required member appears), each with a
|
|
303
|
+
// type-correct example VALUE via exampleValue — a `name: /* type */`
|
|
304
|
+
// comment is not a value and yields invalid TS (TS1109).
|
|
306
305
|
const createItems = opRequestShape(ent, 'create').items
|
|
307
306
|
.filter((it: any) => !it.optional)
|
|
308
307
|
Content(`\`\`\`ts
|
|
309
308
|
const result = await client.${ent.Name}().create({
|
|
310
309
|
`)
|
|
311
310
|
createItems.map((it: any) => {
|
|
312
|
-
Content(` ${it.name}:
|
|
311
|
+
Content(` ${it.name}: ${exampleValue(ent, ent.op && ent.op.create, it.name, 'example_' + it.name)},
|
|
313
312
|
`)
|
|
314
313
|
})
|
|
315
314
|
Content(`})
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { cmp, Content, entityIdField,
|
|
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
|
-
|
|
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(`\`\`\`ts
|
|
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(), 'ts') +
|
|
30
29
|
('list' === primaryOp ? 's' : '')
|
|
@@ -18,7 +18,7 @@ import type {
|
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
// TODO: needs Entity superclass
|
|
21
|
-
class
|
|
21
|
+
class EntyClass extends ProjectNameEntityBase<EntityName> {
|
|
22
22
|
|
|
23
23
|
constructor(client: ProjectNameSDK, entopts: any) {
|
|
24
24
|
super(client, entopts)
|
|
@@ -28,8 +28,8 @@ class EntityNameEntity extends ProjectNameEntityBase<EntityName> {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
|
|
31
|
-
make(this:
|
|
32
|
-
return new
|
|
31
|
+
make(this: EntyClass) {
|
|
32
|
+
return new EntyClass(this._client, this.entopts())
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
|
|
@@ -47,5 +47,5 @@ class EntityNameEntity extends ProjectNameEntityBase<EntityName> {
|
|
|
47
47
|
|
|
48
48
|
|
|
49
49
|
export {
|
|
50
|
-
|
|
50
|
+
EntyClass
|
|
51
51
|
}
|
|
@@ -47,7 +47,12 @@ func prepareAuthUtil(ctx *core.Context) (*core.Spec, error) {
|
|
|
47
47
|
if av, ok := apikey.(string); ok {
|
|
48
48
|
apikeyVal = av
|
|
49
49
|
}
|
|
50
|
-
|
|
50
|
+
// Empty prefix (raw apiKey credential) must not add a leading space.
|
|
51
|
+
if authPrefix == "" {
|
|
52
|
+
headers[headerAuth] = apikeyVal
|
|
53
|
+
} else {
|
|
54
|
+
headers[headerAuth] = authPrefix + " " + apikeyVal
|
|
55
|
+
}
|
|
51
56
|
}
|
|
52
57
|
|
|
53
58
|
return spec, nil
|
|
@@ -36,7 +36,9 @@ function prepareAuth(ctx) {
|
|
|
36
36
|
delprop(headers, HEADER_auth)
|
|
37
37
|
}
|
|
38
38
|
else {
|
|
39
|
-
|
|
39
|
+
// Empty prefix (raw apiKey credential) must not add a leading space.
|
|
40
|
+
setprop(headers, HEADER_auth,
|
|
41
|
+
options.auth.prefix ? options.auth.prefix + ' ' + apikey : apikey)
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
return spec
|
|
@@ -38,7 +38,12 @@ local function prepare_auth_util(ctx)
|
|
|
38
38
|
if type(apikey) == "string" then
|
|
39
39
|
apikey_val = apikey
|
|
40
40
|
end
|
|
41
|
-
|
|
41
|
+
-- Empty prefix (raw apiKey credential) must not add a leading space.
|
|
42
|
+
if auth_prefix == "" then
|
|
43
|
+
headers[HEADER_AUTH] = apikey_val
|
|
44
|
+
else
|
|
45
|
+
headers[HEADER_AUTH] = auth_prefix .. " " .. apikey_val
|
|
46
|
+
end
|
|
42
47
|
end
|
|
43
48
|
|
|
44
49
|
return spec, nil
|
|
@@ -35,7 +35,9 @@ class ProjectNamePrepareAuth
|
|
|
35
35
|
} else {
|
|
36
36
|
$auth_prefix = \Voxgig\Struct\Struct::getpath($options, 'auth.prefix') ?? '';
|
|
37
37
|
$apikey_val = is_string($apikey) ? $apikey : '';
|
|
38
|
-
|
|
38
|
+
// Empty prefix (raw apiKey credential) must not add a leading space.
|
|
39
|
+
$headers[self::HEADER_AUTH] = $auth_prefix === ''
|
|
40
|
+
? $apikey_val : "{$auth_prefix} {$apikey_val}";
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
return [$spec, null];
|