@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
|
@@ -143,10 +143,10 @@ fmt.Println(${eVar}s) // the array of records
|
|
|
143
143
|
|
|
144
144
|
if (opnames.includes('create')) {
|
|
145
145
|
// Members come from the SAME shape that generates the op's request
|
|
146
|
-
// data
|
|
147
|
-
//
|
|
148
|
-
//
|
|
149
|
-
//
|
|
146
|
+
// data (every required member appears, including a required id), each
|
|
147
|
+
// with a type-correct example VALUE via exampleValue — a
|
|
148
|
+
// `"name": /* type */` comment is not a value and yields uncompilable
|
|
149
|
+
// Go, so the example must carry a real literal.
|
|
150
150
|
const createItems = opRequestShape(entity, 'create').items
|
|
151
151
|
.filter((it: any) => !it.optional)
|
|
152
152
|
Content(`#### Example: Create
|
|
@@ -155,7 +155,7 @@ fmt.Println(${eVar}s) // the array of records
|
|
|
155
155
|
result, err := client.${entity.Name}(nil).Create(map[string]any{
|
|
156
156
|
`)
|
|
157
157
|
createItems.map((it: any) => {
|
|
158
|
-
Content(` "${it.name}":
|
|
158
|
+
Content(` "${it.name}": ${exampleValue(entity, entity.op && entity.op.create, it.name, 'example_' + it.name)},
|
|
159
159
|
`)
|
|
160
160
|
})
|
|
161
161
|
Content(`}, nil)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { cmp, Content, isAuthActive, envName, canonKey, entityIdField,
|
|
2
|
+
import { cmp, Content, isAuthActive, envName, canonKey, entityIdField, pickExampleEntity, opRequestShape } from '@voxgig/sdkgen'
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
KIT,
|
|
@@ -32,16 +32,16 @@ const ReadmeHowto = cmp(function ReadmeHowto(props: any) {
|
|
|
32
32
|
const gomodule = `github.com/${model.origin || 'voxgig-sdk'}/${model.name}-sdk/go`
|
|
33
33
|
|
|
34
34
|
const entity = getModelPath(model, `main.${KIT}.entity`)
|
|
35
|
-
|
|
35
|
+
// Pick an entity with a real op (prefer a read op) — never fabricate a
|
|
36
|
+
// `Load` on an op-less entity like Cloudsmith's `Abort`. primaryOp is null
|
|
37
|
+
// only when NO entity exposes any op (a direct()-only SDK).
|
|
38
|
+
const { entity: exampleEntity, primaryOp } = pickExampleEntity(entity)
|
|
36
39
|
const eName = exampleEntity ? nom(exampleEntity, 'Name') : 'Entity'
|
|
37
40
|
// camelCase Go identifier (never snake_case or flattened lowercase,
|
|
38
41
|
// never a Go keyword).
|
|
39
42
|
const eLower = exampleEntity ? goVarName(exampleEntity.name) : 'entity'
|
|
40
43
|
// Model-driven id key: null when the entity has no id-like field.
|
|
41
44
|
const idF = exampleEntity ? entityIdField(exampleEntity) : null
|
|
42
|
-
// Drive the test-mode example off the entity's PRIMARY op — never a hardcoded
|
|
43
|
-
// `Load` a create-only entity lacks.
|
|
44
|
-
const primaryOp = exampleEntity ? (entityPrimaryOp(exampleEntity) || 'load') : 'load'
|
|
45
45
|
const isMatchOp = 'load' === primaryOp || 'remove' === primaryOp
|
|
46
46
|
let testArg = 'nil'
|
|
47
47
|
if (exampleEntity && isMatchOp) {
|
|
@@ -54,6 +54,23 @@ const ReadmeHowto = cmp(function ReadmeHowto(props: any) {
|
|
|
54
54
|
testArg = `map[string]any{${chosen.map((it: any) => `"${it.name}": ${goLit(it.type)}`).join(', ')}}`
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
// The op-driven test-mode block, shown only when the SDK has an entity op.
|
|
58
|
+
// A direct()-only SDK (no ops anywhere) shows a Direct() call instead — never
|
|
59
|
+
// a fabricated method (`cap(primaryOp)` would also fail on a null op).
|
|
60
|
+
const testModeExample = primaryOp
|
|
61
|
+
? `${eLower}, err := client.${eName}(nil).${cap(primaryOp)}(
|
|
62
|
+
${testArg}, nil,
|
|
63
|
+
)
|
|
64
|
+
if err != nil {
|
|
65
|
+
panic(err)
|
|
66
|
+
}
|
|
67
|
+
fmt.Println(${eLower}) // the returned mock data`
|
|
68
|
+
: `result, err := client.Direct(map[string]any{"path": "/api/resource", "method": "GET"})
|
|
69
|
+
if err != nil {
|
|
70
|
+
panic(err)
|
|
71
|
+
}
|
|
72
|
+
fmt.Println(result)`
|
|
73
|
+
|
|
57
74
|
const apikeyEnvLine = isAuthActive(model)
|
|
58
75
|
? `\n${envName(model)}_APIKEY=<your-key>`
|
|
59
76
|
: ''
|
|
@@ -102,13 +119,7 @@ Create a mock client for unit testing \u2014 no server required:
|
|
|
102
119
|
\`\`\`go
|
|
103
120
|
client := sdk.Test()
|
|
104
121
|
|
|
105
|
-
${
|
|
106
|
-
${testArg}, nil,
|
|
107
|
-
)
|
|
108
|
-
if err != nil {
|
|
109
|
-
panic(err)
|
|
110
|
-
}
|
|
111
|
-
fmt.Println(${eLower}) // the returned mock data
|
|
122
|
+
${testModeExample}
|
|
112
123
|
\`\`\`
|
|
113
124
|
|
|
114
125
|
### Use a custom fetch function
|
|
@@ -50,23 +50,41 @@ const ReadmeModel = cmp(function ReadmeModel(props: any) {
|
|
|
50
50
|
? '| `"apikey"` | `string` | API key for authentication. |\n'
|
|
51
51
|
: ''
|
|
52
52
|
|
|
53
|
-
//
|
|
54
|
-
|
|
53
|
+
// Illustrate the Result shape with the first entity that ACTUALLY exposes an
|
|
54
|
+
// op — never fabricate a `Load` on an op-less first entity (e.g. Cloudsmith's
|
|
55
|
+
// `Abort`). firstPrimaryOp is null only when NO active entity has any op (a
|
|
56
|
+
// direct()-only SDK), in which case the call illustration is omitted.
|
|
57
|
+
const firstWithOp = entityList.find((e: any) => entityPrimaryOp(e) != null)
|
|
58
|
+
const firstPrimaryOp = firstWithOp ? entityPrimaryOp(firstWithOp) : null
|
|
59
|
+
const firstEntityName = firstWithOp ? ((firstWithOp as any).Name || 'Entity') : 'Entity'
|
|
55
60
|
// camelCase Go identifier (never snake_case or flattened lowercase,
|
|
56
61
|
// never a Go keyword).
|
|
57
|
-
const firstEntityVar = goVarName((
|
|
62
|
+
const firstEntityVar = goVarName((firstWithOp as any)?.name || 'entity')
|
|
58
63
|
// Model-driven id key: null when the example entity has no id-like field, so
|
|
59
64
|
// the Result-shape illustration passes a nil match.
|
|
60
|
-
const firstIdF = entityIdField(
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const firstPrimaryMethod = firstPrimaryOp.charAt(0).toUpperCase() + firstPrimaryOp.slice(1)
|
|
65
|
+
const firstIdF = firstWithOp ? entityIdField(firstWithOp) : null
|
|
66
|
+
const firstPrimaryMethod = firstPrimaryOp
|
|
67
|
+
? firstPrimaryOp.charAt(0).toUpperCase() + firstPrimaryOp.slice(1)
|
|
68
|
+
: ''
|
|
65
69
|
const firstIsMatchOp = 'load' === firstPrimaryOp || 'remove' === firstPrimaryOp
|
|
66
70
|
const firstOpArg = firstIsMatchOp
|
|
67
71
|
? (firstIdF ? `map[string]any{"${firstIdF}": "example_id"}` : 'nil')
|
|
68
72
|
: 'map[string]any{/* fields */}'
|
|
69
73
|
|
|
74
|
+
// The Result-shape call illustration, shown only when some entity exposes an
|
|
75
|
+
// op. A direct()-only SDK (no entity ops) omits it — there is no op to call.
|
|
76
|
+
const resultCallExample = firstPrimaryOp
|
|
77
|
+
? `Check \`err\` first, then use the value directly (or the typed
|
|
78
|
+
\`...Typed\` variants, which return the entity's model struct and a typed
|
|
79
|
+
slice):
|
|
80
|
+
|
|
81
|
+
${firstEntityVar}, err := client.${firstEntityName}(nil).${firstPrimaryMethod}(${firstOpArg}, nil)
|
|
82
|
+
if err != nil { /* handle */ }
|
|
83
|
+
// ${firstEntityVar} is the returned record
|
|
84
|
+
|
|
85
|
+
`
|
|
86
|
+
: ''
|
|
87
|
+
|
|
70
88
|
Content(`### New${model.const.Name}SDK
|
|
71
89
|
|
|
72
90
|
\`\`\`go
|
|
@@ -130,15 +148,7 @@ operation's data **directly** — there is no wrapper:
|
|
|
130
148
|
| --- | --- |
|
|
131
149
|
${resultShapeRows}
|
|
132
150
|
|
|
133
|
-
|
|
134
|
-
\`...Typed\` variants, which return the entity's model struct and a typed
|
|
135
|
-
slice):
|
|
136
|
-
|
|
137
|
-
${firstEntityVar}, err := client.${firstEntityName}(nil).${firstPrimaryMethod}(${firstOpArg}, nil)
|
|
138
|
-
if err != nil { /* handle */ }
|
|
139
|
-
// ${firstEntityVar} is the returned record
|
|
140
|
-
|
|
141
|
-
Only \`Direct()\` returns a response envelope — a \`map[string]any\` with
|
|
151
|
+
${resultCallExample}Only \`Direct()\` returns a response envelope — a \`map[string]any\` with
|
|
142
152
|
\`"ok"\`, \`"status"\`, \`"headers"\`, and \`"data"\` keys.
|
|
143
153
|
|
|
144
154
|
`)
|
|
@@ -296,17 +296,17 @@ fmt.Println(results)
|
|
|
296
296
|
}
|
|
297
297
|
else if ('create' === opname) {
|
|
298
298
|
// Members come from the SAME shape that generates the op's
|
|
299
|
-
// request data
|
|
300
|
-
// required id
|
|
301
|
-
//
|
|
302
|
-
//
|
|
299
|
+
// request data (every required member appears, including a
|
|
300
|
+
// required id), each with a type-correct example VALUE via
|
|
301
|
+
// exampleValue — a `"name": /* type */` comment is not a value
|
|
302
|
+
// and yields uncompilable Go.
|
|
303
303
|
const createItems = opRequestShape(ent, 'create').items
|
|
304
304
|
.filter((it: any) => !it.optional)
|
|
305
305
|
Content(`\`\`\`go
|
|
306
306
|
result, err := client.${ent.Name}(nil).Create(map[string]any{
|
|
307
307
|
`)
|
|
308
308
|
createItems.map((it: any) => {
|
|
309
|
-
Content(` "${it.name}":
|
|
309
|
+
Content(` "${it.name}": ${exampleValue(ent, ent.op && ent.op.create, it.name, 'example_' + it.name)},
|
|
310
310
|
`)
|
|
311
311
|
})
|
|
312
312
|
Content(`}, nil)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { cmp, Content, canonKey, entityIdField,
|
|
2
|
+
import { cmp, Content, canonKey, entityIdField, pickExampleEntity, opRequestShape } from '@voxgig/sdkgen'
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
KIT,
|
|
@@ -30,18 +30,17 @@ const ReadmeTopTest = cmp(function ReadmeTopTest(props: any) {
|
|
|
30
30
|
// Go module path == repo path on GitHub (org from model.origin).
|
|
31
31
|
const gomodule = `github.com/${model.origin || 'voxgig-sdk'}/${model.name}-sdk/go`
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
// Pick an entity with a real op (prefer a read op) — never fabricate a
|
|
34
|
+
// `load` on an op-less entity like Cloudsmith's `Abort`.
|
|
35
|
+
const { entity: exampleEntity, primaryOp } = pickExampleEntity(entity)
|
|
34
36
|
|
|
35
37
|
Content(`\`\`\`go
|
|
36
38
|
client := sdk.Test()
|
|
37
39
|
`)
|
|
38
40
|
|
|
39
|
-
if (exampleEntity) {
|
|
41
|
+
if (exampleEntity && primaryOp) {
|
|
40
42
|
const eName = nom(exampleEntity, 'Name')
|
|
41
|
-
// Drive the test-mode example off the entity's PRIMARY op (never a hardcoded
|
|
42
|
-
// `Load` a create-only entity lacks).
|
|
43
43
|
const idF = entityIdField(exampleEntity)
|
|
44
|
-
const primaryOp = entityPrimaryOp(exampleEntity) || 'load'
|
|
45
44
|
const isMatchOp = 'load' === primaryOp || 'remove' === primaryOp
|
|
46
45
|
let arg = 'nil'
|
|
47
46
|
if (isMatchOp) {
|
|
@@ -6,7 +6,7 @@ import (
|
|
|
6
6
|
vs "github.com/voxgig/struct"
|
|
7
7
|
)
|
|
8
8
|
|
|
9
|
-
type
|
|
9
|
+
type EntyClass struct {
|
|
10
10
|
name string
|
|
11
11
|
client *core.ProjectNameSDK
|
|
12
12
|
utility *core.Utility
|
|
@@ -16,7 +16,7 @@ type EntityNameEntity struct {
|
|
|
16
16
|
entctx *core.Context
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
func
|
|
19
|
+
func NewEntyClass(client *core.ProjectNameSDK, entopts map[string]any) *EntyClass {
|
|
20
20
|
if entopts == nil {
|
|
21
21
|
entopts = map[string]any{}
|
|
22
22
|
}
|
|
@@ -28,7 +28,7 @@ func NewEntityNameEntity(client *core.ProjectNameSDK, entopts map[string]any) *E
|
|
|
28
28
|
entopts["active"] = true
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
e := &
|
|
31
|
+
e := &EntyClass{
|
|
32
32
|
name: "entityname",
|
|
33
33
|
client: client,
|
|
34
34
|
utility: client.GetUtility(),
|
|
@@ -47,17 +47,17 @@ func NewEntityNameEntity(client *core.ProjectNameSDK, entopts map[string]any) *E
|
|
|
47
47
|
return e
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
func (e *
|
|
50
|
+
func (e *EntyClass) GetName() string { return e.name }
|
|
51
51
|
|
|
52
|
-
func (e *
|
|
52
|
+
func (e *EntyClass) Make() core.Entity {
|
|
53
53
|
opts := map[string]any{}
|
|
54
54
|
for k, v := range e.entopts {
|
|
55
55
|
opts[k] = v
|
|
56
56
|
}
|
|
57
|
-
return
|
|
57
|
+
return NewEntyClass(e.client, opts)
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
func (e *
|
|
60
|
+
func (e *EntyClass) Data(args ...any) any {
|
|
61
61
|
if len(args) > 0 && args[0] != nil {
|
|
62
62
|
e.data = core.ToMapAny(vs.Clone(args[0]))
|
|
63
63
|
if e.data == nil {
|
|
@@ -71,7 +71,7 @@ func (e *EntityNameEntity) Data(args ...any) any {
|
|
|
71
71
|
return out
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
func (e *
|
|
74
|
+
func (e *EntyClass) Match(args ...any) any {
|
|
75
75
|
if len(args) > 0 && args[0] != nil {
|
|
76
76
|
e.match = core.ToMapAny(vs.Clone(args[0]))
|
|
77
77
|
if e.match == nil {
|
|
@@ -89,7 +89,7 @@ func (e *EntityNameEntity) Match(args ...any) any {
|
|
|
89
89
|
// argument it returns the current data as an EntityName; with an argument it
|
|
90
90
|
// sets the data and returns the stored value. It delegates to the untyped Data
|
|
91
91
|
// (identical runtime) and converts at the typed boundary.
|
|
92
|
-
func (e *
|
|
92
|
+
func (e *EntyClass) DataTyped(data ...EntityName) EntityName {
|
|
93
93
|
if len(data) > 0 {
|
|
94
94
|
return typedFrom[EntityName](e.Data(asMap(data[0])))
|
|
95
95
|
}
|
|
@@ -99,7 +99,7 @@ func (e *EntityNameEntity) DataTyped(data ...EntityName) EntityName {
|
|
|
99
99
|
// MatchTyped mirrors DataTyped for the entity's match filter. The match is a
|
|
100
100
|
// partial of the entity, so it round-trips through EntityName (all fields
|
|
101
101
|
// optional at the wire level).
|
|
102
|
-
func (e *
|
|
102
|
+
func (e *EntyClass) MatchTyped(match ...EntityName) EntityName {
|
|
103
103
|
if len(match) > 0 {
|
|
104
104
|
return typedFrom[EntityName](e.Match(asMap(match[0])))
|
|
105
105
|
}
|
|
@@ -116,7 +116,7 @@ func (e *EntityNameEntity) MatchTyped(match ...EntityName) EntityName {
|
|
|
116
116
|
|
|
117
117
|
// #RemoveOp
|
|
118
118
|
|
|
119
|
-
func (e *
|
|
119
|
+
func (e *EntyClass) runOp(ctx *core.Context, postDone func()) (any, error) {
|
|
120
120
|
utility := e.utility
|
|
121
121
|
|
|
122
122
|
// #PrePoint-Hook
|
|
@@ -10,7 +10,7 @@ type entityCreateOp struct{}
|
|
|
10
10
|
|
|
11
11
|
// EJECT-START
|
|
12
12
|
|
|
13
|
-
func (e *
|
|
13
|
+
func (e *EntyClass) Create(reqdata map[string]any, ctrl map[string]any) (any, error) {
|
|
14
14
|
utility := e.utility
|
|
15
15
|
ctx := utility.MakeContext(map[string]any{
|
|
16
16
|
"opname": "create",
|
|
@@ -35,7 +35,7 @@ func (e *EntityNameEntity) Create(reqdata map[string]any, ctrl map[string]any) (
|
|
|
35
35
|
// CreateTyped is the statically-typed variant of Create: it takes an
|
|
36
36
|
// EntityNameCreateData and returns an EntityName. It delegates to the untyped
|
|
37
37
|
// Create (identical runtime) and converts at the typed boundary.
|
|
38
|
-
func (e *
|
|
38
|
+
func (e *EntyClass) CreateTyped(reqdata EntityNameCreateData, ctrl map[string]any) (EntityName, error) {
|
|
39
39
|
res, err := e.Create(asMap(reqdata), ctrl)
|
|
40
40
|
if err != nil {
|
|
41
41
|
return EntityName{}, err
|
|
@@ -4,7 +4,7 @@ type entityListOp struct{}
|
|
|
4
4
|
|
|
5
5
|
// EJECT-START
|
|
6
6
|
|
|
7
|
-
func (e *
|
|
7
|
+
func (e *EntyClass) List(reqmatch map[string]any, ctrl map[string]any) (any, error) {
|
|
8
8
|
utility := e.utility
|
|
9
9
|
ctx := utility.MakeContext(map[string]any{
|
|
10
10
|
"opname": "list",
|
|
@@ -26,7 +26,7 @@ func (e *EntityNameEntity) List(reqmatch map[string]any, ctrl map[string]any) (a
|
|
|
26
26
|
// ListTyped is the statically-typed variant of List: it takes an
|
|
27
27
|
// EntityNameListMatch and returns []EntityName. It delegates to the untyped
|
|
28
28
|
// List (identical runtime) and converts at the typed boundary.
|
|
29
|
-
func (e *
|
|
29
|
+
func (e *EntyClass) ListTyped(reqmatch EntityNameListMatch, ctrl map[string]any) ([]EntityName, error) {
|
|
30
30
|
res, err := e.List(asMap(reqmatch), ctrl)
|
|
31
31
|
if err != nil {
|
|
32
32
|
return nil, err
|
|
@@ -10,7 +10,7 @@ type entityLoadOp struct{}
|
|
|
10
10
|
|
|
11
11
|
// EJECT-START
|
|
12
12
|
|
|
13
|
-
func (e *
|
|
13
|
+
func (e *EntyClass) Load(reqmatch map[string]any, ctrl map[string]any) (any, error) {
|
|
14
14
|
utility := e.utility
|
|
15
15
|
ctx := utility.MakeContext(map[string]any{
|
|
16
16
|
"opname": "load",
|
|
@@ -38,7 +38,7 @@ func (e *EntityNameEntity) Load(reqmatch map[string]any, ctrl map[string]any) (a
|
|
|
38
38
|
// LoadTyped is the statically-typed variant of Load: it takes an
|
|
39
39
|
// EntityNameLoadMatch and returns an EntityName. It delegates to the untyped
|
|
40
40
|
// Load (identical runtime) and converts at the typed boundary.
|
|
41
|
-
func (e *
|
|
41
|
+
func (e *EntyClass) LoadTyped(reqmatch EntityNameLoadMatch, ctrl map[string]any) (EntityName, error) {
|
|
42
42
|
res, err := e.Load(asMap(reqmatch), ctrl)
|
|
43
43
|
if err != nil {
|
|
44
44
|
return EntityName{}, err
|
|
@@ -10,7 +10,7 @@ type entityRemoveOp struct{}
|
|
|
10
10
|
|
|
11
11
|
// EJECT-START
|
|
12
12
|
|
|
13
|
-
func (e *
|
|
13
|
+
func (e *EntyClass) Remove(reqmatch map[string]any, ctrl map[string]any) (any, error) {
|
|
14
14
|
utility := e.utility
|
|
15
15
|
ctx := utility.MakeContext(map[string]any{
|
|
16
16
|
"opname": "remove",
|
|
@@ -38,7 +38,7 @@ func (e *EntityNameEntity) Remove(reqmatch map[string]any, ctrl map[string]any)
|
|
|
38
38
|
// RemoveTyped is the statically-typed variant of Remove: it takes an
|
|
39
39
|
// EntityNameRemoveMatch and returns an EntityName. It delegates to the untyped
|
|
40
40
|
// Remove (identical runtime) and converts at the typed boundary.
|
|
41
|
-
func (e *
|
|
41
|
+
func (e *EntyClass) RemoveTyped(reqmatch EntityNameRemoveMatch, ctrl map[string]any) (EntityName, error) {
|
|
42
42
|
res, err := e.Remove(asMap(reqmatch), ctrl)
|
|
43
43
|
if err != nil {
|
|
44
44
|
return EntityName{}, err
|
|
@@ -10,7 +10,7 @@ type entityUpdateOp struct{}
|
|
|
10
10
|
|
|
11
11
|
// EJECT-START
|
|
12
12
|
|
|
13
|
-
func (e *
|
|
13
|
+
func (e *EntyClass) Update(reqdata map[string]any, ctrl map[string]any) (any, error) {
|
|
14
14
|
utility := e.utility
|
|
15
15
|
ctx := utility.MakeContext(map[string]any{
|
|
16
16
|
"opname": "update",
|
|
@@ -38,7 +38,7 @@ func (e *EntityNameEntity) Update(reqdata map[string]any, ctrl map[string]any) (
|
|
|
38
38
|
// UpdateTyped is the statically-typed variant of Update: it takes an
|
|
39
39
|
// EntityNameUpdateData and returns an EntityName. It delegates to the untyped
|
|
40
40
|
// Update (identical runtime) and converts at the typed boundary.
|
|
41
|
-
func (e *
|
|
41
|
+
func (e *EntyClass) UpdateTyped(reqdata EntityNameUpdateData, ctrl map[string]any) (EntityName, error) {
|
|
42
42
|
res, err := e.Update(asMap(reqdata), ctrl)
|
|
43
43
|
if err != nil {
|
|
44
44
|
return EntityName{}, err
|
|
@@ -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_js'
|
|
10
16
|
|
|
11
17
|
|
|
@@ -13,6 +19,12 @@ const Entity = cmp(function Entity(props: any) {
|
|
|
13
19
|
const { model, stdrep } = props.ctx$
|
|
14
20
|
const { target, entity } = props
|
|
15
21
|
|
|
22
|
+
// Collision-free entity CLASS name (see entityClassName): normally
|
|
23
|
+
// `<Name>Entity`, disambiguated when it would clash with another entity's
|
|
24
|
+
// data-type name. The class file name and the Main require path both use this.
|
|
25
|
+
const entityColl = getModelPath(model, `main.${KIT}.entity`)
|
|
26
|
+
const cls = entityClassName(entity, entityColl)
|
|
27
|
+
|
|
16
28
|
const entrep = {
|
|
17
29
|
...stdrep,
|
|
18
30
|
}
|
|
@@ -23,7 +35,7 @@ const Entity = cmp(function Entity(props: any) {
|
|
|
23
35
|
|
|
24
36
|
Folder({ name: 'src/entity' }, () => {
|
|
25
37
|
|
|
26
|
-
File({ name:
|
|
38
|
+
File({ name: cls + '.' + target.name }, () => {
|
|
27
39
|
|
|
28
40
|
const opnames = Object.keys(entity.op)
|
|
29
41
|
|
|
@@ -43,6 +55,10 @@ const Entity = cmp(function Entity(props: any) {
|
|
|
43
55
|
SdkName: model.const.Name,
|
|
44
56
|
EntityName: entity.Name,
|
|
45
57
|
|
|
58
|
+
// Class token decoupled from the EntityName data-type token in
|
|
59
|
+
// Entity.fragment.js so the class can be renamed independently.
|
|
60
|
+
EntyClass: cls,
|
|
61
|
+
|
|
46
62
|
'#Feature-Hook': ({ name, indent }: any) =>
|
|
47
63
|
Content({ indent }, `
|
|
48
64
|
fres = featureHook(ctx, '${name}')
|
|
@@ -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 ${
|
|
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(`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
|
|
136
|
-
// required id and parent keys like page_id
|
|
137
|
-
//
|
|
138
|
-
//
|
|
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}:
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
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
|
|
303
|
-
// a required id and parent keys like page_id
|
|
304
|
-
//
|
|
305
|
-
//
|
|
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}:
|
|
312
|
+
Content(` ${it.name}: ${exampleValue(ent, ent.op && ent.op.create, it.name, 'example_' + it.name)},
|
|
313
313
|
`)
|
|
314
314
|
})
|
|
315
315
|
Content(`})
|