@voxgig/sdkgen 1.3.4 → 1.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/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/src/cmp/ReadmeErrors.ts +16 -17
- package/src/cmp/ReadmeExplanation.ts +37 -35
- 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, 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
|
}
|
package/src/cmp/ReadmeErrors.ts
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
getModelPath
|
|
7
7
|
} from '../types'
|
|
8
8
|
|
|
9
|
-
import { entityIdField,
|
|
9
|
+
import { entityIdField, pickExampleEntity } from '../helpers/opShape'
|
|
10
10
|
import { primaryOpCall } from '../helpers/opExample'
|
|
11
11
|
import type { ExampleLang, PrimaryCall } from '../helpers/opExample'
|
|
12
12
|
import { safeVarName } from '../helpers/naming'
|
|
@@ -224,29 +224,28 @@ const ReadmeErrors = cmp(function ReadmeErrors(props: any) {
|
|
|
224
224
|
|
|
225
225
|
const lang = LANGS[target.name] || DEFAULT_LANG
|
|
226
226
|
|
|
227
|
-
//
|
|
228
|
-
//
|
|
227
|
+
// Pick a real example entity WITH a real op (prefer a read op) so the
|
|
228
|
+
// error-handling snippet never references a phantom entity or fabricates a
|
|
229
|
+
// `.load()` on an op-less one (e.g. Cloudsmith's `Abort`). primaryOp is null
|
|
230
|
+
// only when NO entity exposes any op — then the entity example is skipped
|
|
231
|
+
// and only the direct() error handling (which every SDK has) is shown.
|
|
229
232
|
const entity = getModelPath(model, `main.${KIT}.entity`, { only_active: false, required: false })
|
|
230
|
-
const ex =
|
|
231
|
-
const eName = ex ? (ex.Name || (ex.name[0].toUpperCase() + ex.name.slice(1))) : 'Entity'
|
|
232
|
-
// Sanitise the variable name against the target's reserved words (a `Delete`
|
|
233
|
-
// entity must not bind `const delete = ...`).
|
|
234
|
-
const eLower = safeVarName(eName.toLowerCase(), target.name)
|
|
235
|
-
|
|
236
|
-
// The entity's id-like key field name, or null when it has none.
|
|
237
|
-
const idF = entityIdField(ex)
|
|
238
|
-
// The entity's PRIMARY op — an op it actually exposes (prefer list/load, else
|
|
239
|
-
// create/update/remove). A create-only entity therefore never shows a
|
|
240
|
-
// phantom `.load()`.
|
|
241
|
-
const primaryOp = entityPrimaryOp(ex) || 'load'
|
|
242
|
-
const call = primaryOpCall(target.name as ExampleLang, eName, eLower, primaryOp, idF, ex)
|
|
233
|
+
const { entity: ex, primaryOp } = pickExampleEntity(entity || {})
|
|
243
234
|
|
|
244
235
|
Content(`
|
|
245
236
|
## Error handling
|
|
246
237
|
|
|
247
238
|
`)
|
|
248
239
|
|
|
249
|
-
|
|
240
|
+
if (ex && primaryOp) {
|
|
241
|
+
const eName = ex.Name || (ex.name[0].toUpperCase() + ex.name.slice(1))
|
|
242
|
+
// Sanitise the variable name against the target's reserved words (a
|
|
243
|
+
// `Delete` entity must not bind `const delete = ...`).
|
|
244
|
+
const eLower = safeVarName(eName.toLowerCase(), target.name)
|
|
245
|
+
const idF = entityIdField(ex)
|
|
246
|
+
const call = primaryOpCall(target.name as ExampleLang, eName, eLower, primaryOp, idF, ex)
|
|
247
|
+
Content(lang.entity(call, primaryOp))
|
|
248
|
+
}
|
|
250
249
|
|
|
251
250
|
Content(lang.direct)
|
|
252
251
|
})
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
|
|
9
9
|
import { requirePath } from '../utility'
|
|
10
10
|
|
|
11
|
-
import { entityIdField,
|
|
11
|
+
import { entityIdField, pickExampleEntity } from '../helpers/opShape'
|
|
12
12
|
import { idLiteral, matchArg, dataArg } from '../helpers/opExample'
|
|
13
13
|
import type { ExampleLang } from '../helpers/opExample'
|
|
14
14
|
import { safeVarName } from '../helpers/naming'
|
|
@@ -225,39 +225,40 @@ const ReadmeExplanation = cmp(function ReadmeExplanation(props: any) {
|
|
|
225
225
|
const feature = getModelPath(model, `main.${KIT}.feature`)
|
|
226
226
|
const lang = LANGS[target.name] || DEFAULT_LANG
|
|
227
227
|
|
|
228
|
-
//
|
|
229
|
-
//
|
|
230
|
-
//
|
|
228
|
+
// Pick a real example entity WITH a real op (prefer a read op) so the
|
|
229
|
+
// entity-state example never references a phantom entity or fabricates a
|
|
230
|
+
// `.load()` on an op-less one (e.g. Cloudsmith's `Abort`). primaryOp is null
|
|
231
|
+
// only when NO entity exposes any op — then the entity-state section is
|
|
232
|
+
// skipped (a direct()-only SDK has no entity op to illustrate).
|
|
231
233
|
const entity = getModelPath(model, `main.${KIT}.entity`, { only_active: false, required: false })
|
|
232
|
-
const ex =
|
|
233
|
-
const eName = ex ? (ex.Name || (ex.name[0].toUpperCase() + ex.name.slice(1))) : 'Entity'
|
|
234
|
-
// Sanitise against the target's reserved words (a `Delete` entity must not
|
|
235
|
-
// bind `const delete = ...`).
|
|
236
|
-
const eLower = safeVarName(eName.toLowerCase(), target.name)
|
|
234
|
+
const { entity: ex, primaryOp } = pickExampleEntity(entity || {})
|
|
237
235
|
const lname = target.name as ExampleLang
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
236
|
+
const hasEntityExample = !!(ex && primaryOp)
|
|
237
|
+
|
|
238
|
+
let eName = 'Entity', eLower = 'entity', stateArg = '', matchIdF: string | null = null, idLit = ''
|
|
239
|
+
if (hasEntityExample) {
|
|
240
|
+
eName = ex.Name || (ex.name[0].toUpperCase() + ex.name.slice(1))
|
|
241
|
+
// Sanitise against the target's reserved words (a `Delete` entity must
|
|
242
|
+
// not bind `const delete = ...`).
|
|
243
|
+
eLower = safeVarName(eName.toLowerCase(), target.name)
|
|
244
|
+
const idF = entityIdField(ex)
|
|
245
|
+
const isMatchOp = 'load' === primaryOp || 'remove' === primaryOp
|
|
246
|
+
// Type-correct example id literal (numeric when the id param is integer-
|
|
247
|
+
// typed), derived from the OP's param type so an id carried only in the
|
|
248
|
+
// match compiles.
|
|
249
|
+
idLit = idLiteral(ex, primaryOp as string, idF)
|
|
250
|
+
// Language-correct call argument for the primary op: a match for
|
|
251
|
+
// load/remove, a required-field body for create/update, nothing for list.
|
|
252
|
+
if ('list' === primaryOp) {
|
|
253
|
+
stateArg = 'go' === target.name ? 'nil' : ''
|
|
254
|
+
} else if (isMatchOp) {
|
|
255
|
+
stateArg = matchArg(lname, idF, idLit)
|
|
256
|
+
} else {
|
|
257
|
+
stateArg = dataArg(lname, ex, primaryOp as string, idF)
|
|
258
|
+
}
|
|
259
|
+
// Only a match op keys the `.match()` comment on `{ id: ... }`.
|
|
260
|
+
matchIdF = isMatchOp ? idF : null
|
|
258
261
|
}
|
|
259
|
-
// Only a match op keys the `.match()` comment on `{ id: ... }`.
|
|
260
|
-
const matchIdF = isMatchOp ? idF : null
|
|
261
262
|
|
|
262
263
|
Content(`
|
|
263
264
|
## Advanced
|
|
@@ -327,12 +328,13 @@ were added, so later features can override earlier ones.
|
|
|
327
328
|
}
|
|
328
329
|
|
|
329
330
|
|
|
330
|
-
// Entity state
|
|
331
|
-
|
|
331
|
+
// Entity state — only when the SDK actually has an entity op to show.
|
|
332
|
+
if (hasEntityExample) {
|
|
333
|
+
Content(`### Entity state
|
|
332
334
|
|
|
333
335
|
`)
|
|
334
|
-
|
|
335
|
-
|
|
336
|
+
Content(lang.entityState(eName, eLower, primaryOp as string, stateArg, matchIdF, idLit))
|
|
337
|
+
}
|
|
336
338
|
|
|
337
339
|
|
|
338
340
|
// Direct vs entity access
|
package/src/helpers/naming.ts
CHANGED
|
@@ -11,8 +11,11 @@
|
|
|
11
11
|
// words a `const` binding trips on). Go lists its 25 keywords — a var named
|
|
12
12
|
// `type`/`func`/`range` fails `go build`; Go builtins like `delete`/`len` are
|
|
13
13
|
// NOT keywords and are legal identifiers, so they are intentionally omitted.
|
|
14
|
-
// py/
|
|
15
|
-
//
|
|
14
|
+
// rb/py/lua list the keywords that are illegal as a local-variable binding
|
|
15
|
+
// (e.g. a `Self` entity -> `self = client.Self` is a Ruby SyntaxError, an
|
|
16
|
+
// `End` entity -> `end = ...` a Lua one). PHP variables are `$`-prefixed so a
|
|
17
|
+
// lowercased entity name never collides (only `$this` is special, and no
|
|
18
|
+
// entity is named `this`), so PHP is not treated.
|
|
16
19
|
|
|
17
20
|
const JS_RESERVED = new Set<string>([
|
|
18
21
|
'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger',
|
|
@@ -32,10 +35,37 @@ const GO_RESERVED = new Set<string>([
|
|
|
32
35
|
'var',
|
|
33
36
|
])
|
|
34
37
|
|
|
38
|
+
// Ruby keywords — illegal as a local-variable name (e.g. `self`, `end`).
|
|
39
|
+
const RB_RESERVED = new Set<string>([
|
|
40
|
+
'alias', 'and', 'begin', 'break', 'case', 'class', 'def', 'defined?',
|
|
41
|
+
'do', 'else', 'elsif', 'end', 'ensure', 'false', 'for', 'if', 'in',
|
|
42
|
+
'module', 'next', 'nil', 'not', 'or', 'redo', 'rescue', 'retry', 'return',
|
|
43
|
+
'self', 'super', 'then', 'true', 'undef', 'unless', 'until', 'when',
|
|
44
|
+
'while', 'yield', '__FILE__', '__LINE__', '__ENCODING__', 'BEGIN', 'END',
|
|
45
|
+
])
|
|
46
|
+
|
|
47
|
+
// Python keywords — illegal as a variable name.
|
|
48
|
+
const PY_RESERVED = new Set<string>([
|
|
49
|
+
'False', 'None', 'True', 'and', 'as', 'assert', 'async', 'await', 'break',
|
|
50
|
+
'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'finally',
|
|
51
|
+
'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'nonlocal',
|
|
52
|
+
'not', 'or', 'pass', 'raise', 'return', 'try', 'while', 'with', 'yield',
|
|
53
|
+
])
|
|
54
|
+
|
|
55
|
+
// Lua keywords — illegal as a variable name. (`self` is NOT reserved in Lua.)
|
|
56
|
+
const LUA_RESERVED = new Set<string>([
|
|
57
|
+
'and', 'break', 'do', 'else', 'elseif', 'end', 'false', 'for', 'function',
|
|
58
|
+
'goto', 'if', 'in', 'local', 'nil', 'not', 'or', 'repeat', 'return',
|
|
59
|
+
'then', 'true', 'until', 'while',
|
|
60
|
+
])
|
|
61
|
+
|
|
35
62
|
const RESERVED: Record<string, Set<string>> = {
|
|
36
63
|
ts: JS_RESERVED,
|
|
37
64
|
js: JS_RESERVED,
|
|
38
65
|
go: GO_RESERVED,
|
|
66
|
+
rb: RB_RESERVED,
|
|
67
|
+
py: PY_RESERVED,
|
|
68
|
+
lua: LUA_RESERVED,
|
|
39
69
|
}
|
|
40
70
|
|
|
41
71
|
|