@voxgig/sdkgen 1.3.1 → 1.3.2
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/action/action.d.ts +2 -1
- package/dist/action/action.js +13 -3
- package/dist/action/action.js.map +1 -1
- package/dist/action/feature.js +3 -5
- package/dist/action/feature.js.map +1 -1
- package/dist/action/target.js +9 -7
- package/dist/action/target.js.map +1 -1
- package/dist/cmp/Deploy.js +3 -3
- package/dist/cmp/Deploy.js.map +1 -1
- package/dist/helpers/opShape.js +38 -6
- package/dist/helpers/opShape.js.map +1 -1
- package/dist/sdkgen.d.ts +2 -2
- package/dist/sdkgen.js +5 -4
- package/dist/sdkgen.js.map +1 -1
- package/dist/utility.d.ts +2 -1
- package/dist/utility.js +20 -1
- package/dist/utility.js.map +1 -1
- package/model/sdkgen.aontu +1 -1
- package/package.json +1 -1
- package/project/.sdk/model/feature/feature-index.aontu +5 -0
- package/project/.sdk/src/cmp/go/Config_go.ts +3 -2
- package/project/.sdk/src/cmp/go/ReadmeEntity_go.ts +35 -9
- package/project/.sdk/src/cmp/go/ReadmeExamplesTest_go.ts +18 -14
- package/project/.sdk/src/cmp/go/ReadmeHowto_go.ts +6 -2
- package/project/.sdk/src/cmp/go/ReadmeModel_go.ts +6 -2
- package/project/.sdk/src/cmp/go/ReadmeQuick_go.ts +51 -30
- package/project/.sdk/src/cmp/go/ReadmeRef_go.ts +66 -13
- package/project/.sdk/src/cmp/go/ReadmeTopHowto_go.ts +4 -0
- package/project/.sdk/src/cmp/go/ReadmeTopQuick_go.ts +51 -16
- package/project/.sdk/src/cmp/go/utility_go.ts +70 -0
- package/project/.sdk/src/cmp/js/Config_js.ts +3 -6
- package/project/.sdk/src/cmp/js/ReadmeEntity_js.ts +23 -6
- package/project/.sdk/src/cmp/js/ReadmeQuick_js.ts +31 -7
- package/project/.sdk/src/cmp/js/ReadmeRef_js.ts +33 -10
- package/project/.sdk/src/cmp/js/ReadmeTopQuick_js.ts +24 -14
- package/project/.sdk/src/cmp/js/ReadmeTopTest_js.ts +15 -3
- package/project/.sdk/src/cmp/lua/Config_lua.ts +3 -2
- package/project/.sdk/src/cmp/lua/ReadmeEntity_lua.ts +41 -6
- package/project/.sdk/src/cmp/lua/ReadmeQuick_lua.ts +94 -20
- package/project/.sdk/src/cmp/lua/ReadmeRef_lua.ts +51 -8
- package/project/.sdk/src/cmp/lua/ReadmeTopQuick_lua.ts +31 -2
- package/project/.sdk/src/cmp/lua/ReadmeTopTest_lua.ts +13 -2
- package/project/.sdk/src/cmp/php/Config_php.ts +3 -2
- package/project/.sdk/src/cmp/php/ReadmeEntity_php.ts +34 -6
- package/project/.sdk/src/cmp/php/ReadmeQuick_php.ts +95 -19
- package/project/.sdk/src/cmp/php/ReadmeRef_php.ts +44 -8
- package/project/.sdk/src/cmp/php/ReadmeTopQuick_php.ts +26 -2
- package/project/.sdk/src/cmp/php/ReadmeTopTest_php.ts +13 -2
- package/project/.sdk/src/cmp/py/Config_py.ts +3 -2
- package/project/.sdk/src/cmp/py/ReadmeEntity_py.ts +26 -8
- package/project/.sdk/src/cmp/py/ReadmeQuick_py.ts +95 -21
- package/project/.sdk/src/cmp/py/ReadmeRef_py.ts +37 -11
- package/project/.sdk/src/cmp/py/ReadmeTopQuick_py.ts +28 -2
- package/project/.sdk/src/cmp/py/ReadmeTopTest_py.ts +16 -3
- package/project/.sdk/src/cmp/rb/Config_rb.ts +3 -2
- package/project/.sdk/src/cmp/rb/ReadmeEntity_rb.ts +26 -8
- package/project/.sdk/src/cmp/rb/ReadmeQuick_rb.ts +96 -21
- package/project/.sdk/src/cmp/rb/ReadmeRef_rb.ts +36 -10
- package/project/.sdk/src/cmp/rb/ReadmeTopQuick_rb.ts +28 -2
- package/project/.sdk/src/cmp/rb/ReadmeTopTest_rb.ts +13 -2
- package/project/.sdk/src/cmp/ts/Config_ts.ts +3 -6
- package/project/.sdk/src/cmp/ts/ReadmeEntity_ts.ts +22 -6
- package/project/.sdk/src/cmp/ts/ReadmeQuick_ts.ts +56 -18
- package/project/.sdk/src/cmp/ts/ReadmeRef_ts.ts +33 -10
- package/project/.sdk/src/cmp/ts/ReadmeTopQuick_ts.ts +21 -14
- package/project/.sdk/src/cmp/ts/ReadmeTopTest_ts.ts +15 -3
- package/project/.sdk/tm/py/test/test_struct_utility.py +1 -1
- package/project/.sdk/tm/rb/Makefile +3 -1
- package/src/action/action.ts +15 -3
- package/src/action/feature.ts +4 -8
- package/src/action/target.ts +11 -9
- package/src/cmp/Deploy.ts +3 -3
- package/src/helpers/opShape.ts +45 -7
- package/src/sdkgen.ts +4 -3
- package/src/utility.ts +24 -1
- package/project/.sdk/model/feature/feature-index.jsonic +0 -5
- /package/project/.sdk/model/feature/{log.jsonic → log.aontu} +0 -0
- /package/project/.sdk/model/feature/{test.jsonic → test.aontu} +0 -0
- /package/project/.sdk/model/target/{go-cli.jsonic → go-cli.aontu} +0 -0
- /package/project/.sdk/model/target/{go-mcp.jsonic → go-mcp.aontu} +0 -0
- /package/project/.sdk/model/target/{go.jsonic → go.aontu} +0 -0
- /package/project/.sdk/model/target/{js.jsonic → js.aontu} +0 -0
- /package/project/.sdk/model/target/{lua.jsonic → lua.aontu} +0 -0
- /package/project/.sdk/model/target/{php.jsonic → php.aontu} +0 -0
- /package/project/.sdk/model/target/{py.jsonic → py.aontu} +0 -0
- /package/project/.sdk/model/target/{rb.jsonic → rb.aontu} +0 -0
- /package/project/.sdk/model/target/{ts.jsonic → ts.aontu} +0 -0
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
|
|
2
|
-
import { cmp, each, Content, canonToType, entityIdField,
|
|
2
|
+
import { cmp, each, Content, canonToType, entityIdField, entityOps, opRequestShape } from '@voxgig/sdkgen'
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
KIT,
|
|
6
6
|
getModelPath,
|
|
7
7
|
} from '@voxgig/apidef'
|
|
8
8
|
|
|
9
|
+
import { exampleValue, goVarName } from './utility_go'
|
|
10
|
+
|
|
9
11
|
|
|
10
12
|
// Operation method spelling differs between Go and other languages — Go
|
|
11
13
|
// uses PascalCase methods with explicit ctrl arg, others use lowercase
|
|
@@ -39,12 +41,15 @@ const ReadmeEntity = cmp(function ReadmeEntity(props: any) {
|
|
|
39
41
|
`)
|
|
40
42
|
|
|
41
43
|
publishedEntities.map((entity: any) => {
|
|
42
|
-
|
|
44
|
+
// ACTIVE ops only — an inactive op generates no method, so an example
|
|
45
|
+
// calling it would not compile.
|
|
46
|
+
const opnames = entityOps(entity)
|
|
43
47
|
const fields = entity.fields || []
|
|
44
48
|
// Model-driven id key: null when this entity has no id-like field.
|
|
45
49
|
const idF = entityIdField(entity)
|
|
46
|
-
//
|
|
47
|
-
|
|
50
|
+
// camelCase Go identifier (a `status_embed_config` entity must not bind a
|
|
51
|
+
// snake_case Go variable, and a `type`/`range` entity not a Go keyword).
|
|
52
|
+
const eVar = goVarName(entity.name)
|
|
48
53
|
|
|
49
54
|
Content(`
|
|
50
55
|
### ${entity.Name}
|
|
@@ -97,10 +102,22 @@ const ReadmeEntity = cmp(function ReadmeEntity(props: any) {
|
|
|
97
102
|
}
|
|
98
103
|
|
|
99
104
|
if (opnames.includes('load')) {
|
|
105
|
+
// The id key plus every REQUIRED match key (parent path params like
|
|
106
|
+
// page_id) — the same shape that generates the op's request match, so
|
|
107
|
+
// the example always carries the keys the route needs.
|
|
108
|
+
const loadItems = opRequestShape(entity, 'load').items
|
|
109
|
+
.filter((it: any) => !it.optional || it.name === idF)
|
|
110
|
+
.sort((a: any, b: any) =>
|
|
111
|
+
(a.name === idF ? 0 : 1) - (b.name === idF ? 0 : 1))
|
|
112
|
+
const loadArg = 0 < loadItems.length
|
|
113
|
+
? `map[string]any{${loadItems.map((it: any) =>
|
|
114
|
+
`"${it.name}": ${exampleValue(entity, entity.op && entity.op.load, it.name,
|
|
115
|
+
it.name === idF ? entity.name + '_id' : it.name)}`).join(', ')}}`
|
|
116
|
+
: 'nil'
|
|
100
117
|
Content(`#### Example: Load
|
|
101
118
|
|
|
102
119
|
\`\`\`go
|
|
103
|
-
${eVar}, err := client.${entity.Name}(nil).Load(${
|
|
120
|
+
${eVar}, err := client.${entity.Name}(nil).Load(${loadArg}, nil)
|
|
104
121
|
if err != nil {
|
|
105
122
|
panic(err)
|
|
106
123
|
}
|
|
@@ -125,18 +142,27 @@ fmt.Println(${eVar}s) // the array of records
|
|
|
125
142
|
}
|
|
126
143
|
|
|
127
144
|
if (opnames.includes('create')) {
|
|
145
|
+
// Members come from the SAME shape that generates the op's request
|
|
146
|
+
// data: every required member must appear — including a required id
|
|
147
|
+
// (the /* type */ placeholders also mark the block as an illustration
|
|
148
|
+
// for the doc gates); an all-optional create renders an empty — still
|
|
149
|
+
// valid — literal, and the compiled example is self-consuming.
|
|
150
|
+
const createItems = opRequestShape(entity, 'create').items
|
|
151
|
+
.filter((it: any) => !it.optional)
|
|
128
152
|
Content(`#### Example: Create
|
|
129
153
|
|
|
130
154
|
\`\`\`go
|
|
131
155
|
result, err := client.${entity.Name}(nil).Create(map[string]any{
|
|
132
156
|
`)
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
Content(` "${field.name}": /* ${canonToType(field.type, target.name)} */,
|
|
157
|
+
createItems.map((it: any) => {
|
|
158
|
+
Content(` "${it.name}": /* ${canonToType(it.type, target.name)} */,
|
|
136
159
|
`)
|
|
137
|
-
}
|
|
138
160
|
})
|
|
139
161
|
Content(`}, nil)
|
|
162
|
+
if err != nil {
|
|
163
|
+
panic(err)
|
|
164
|
+
}
|
|
165
|
+
fmt.Println(result)
|
|
140
166
|
\`\`\`
|
|
141
167
|
|
|
142
168
|
`)
|
|
@@ -206,10 +206,14 @@ func TestReadmeGoSnippets(t *testing.T) {
|
|
|
206
206
|
|
|
207
207
|
// Build. The Go compiler is the oracle for unused imports/vars in the
|
|
208
208
|
// wrapped fragments: blank them out and rebuild. Any OTHER error is a
|
|
209
|
-
// genuine snippet bug and fails the test.
|
|
209
|
+
// genuine snippet bug and fails the test. The loop is progress-based,
|
|
210
|
+
// not attempt-capped: \`-gcflags=-e\` lifts the compiler's 10-error cap so
|
|
211
|
+
// every error surfaces in one round, applyFixes repairs them all, and
|
|
212
|
+
// the loop continues while a round makes progress. It fails hard when no
|
|
213
|
+
// fix applies, and fails as non-converged when a round changes nothing
|
|
214
|
+
// in the build output (the fixes stopped helping).
|
|
210
215
|
var lastOut string
|
|
211
|
-
|
|
212
|
-
for attempt := 0; attempt < 15; attempt++ {
|
|
216
|
+
for {
|
|
213
217
|
for name, content := range fragFiles {
|
|
214
218
|
if err := os.WriteFile(filepath.Join(fragDir, name+".go"), []byte(content), 0o644); err != nil {
|
|
215
219
|
t.Fatal(err)
|
|
@@ -217,17 +221,16 @@ func TestReadmeGoSnippets(t *testing.T) {
|
|
|
217
221
|
}
|
|
218
222
|
out, buildErr := runGoBuild(moduleRoot, binDir, progDirs, fragPkg)
|
|
219
223
|
if buildErr == nil {
|
|
220
|
-
built = true
|
|
221
224
|
break
|
|
222
225
|
}
|
|
226
|
+
if out == lastOut {
|
|
227
|
+
t.Fatalf("README go snippets did not converge to a clean build:\\n%s", out)
|
|
228
|
+
}
|
|
223
229
|
lastOut = out
|
|
224
230
|
if !applyFixes(out, fragFiles) {
|
|
225
231
|
t.Fatalf("README go snippet failed to compile:\\n%s", out)
|
|
226
232
|
}
|
|
227
233
|
}
|
|
228
|
-
if !built {
|
|
229
|
-
t.Fatalf("README go snippets did not converge to a clean build:\\n%s", lastOut)
|
|
230
|
-
}
|
|
231
234
|
|
|
232
235
|
// Everything compiled. Now RUN the test-mode variants of the complete
|
|
233
236
|
// programs and fail on a genuine runtime panic.
|
|
@@ -357,15 +360,16 @@ func isIdentByte(c byte) bool {
|
|
|
357
360
|
(c >= '0' && c <= '9')
|
|
358
361
|
}
|
|
359
362
|
|
|
360
|
-
// runGoBuild type-checks the fragment package with
|
|
361
|
-
// (a non-main package produces no output but is still fully type-checked
|
|
362
|
-
//
|
|
363
|
-
//
|
|
364
|
-
//
|
|
363
|
+
// runGoBuild type-checks the fragment package with \`go build -gcflags=-e\`
|
|
364
|
+
// (a non-main package produces no output but is still fully type-checked —
|
|
365
|
+
// unlike \`go build -o dir/\`, which skips non-main packages — and \`-e\` lifts
|
|
366
|
+
// the compiler's 10-errors-per-package cap so EVERY unused-var/import error
|
|
367
|
+
// surfaces in a single round), and builds each complete program with
|
|
368
|
+
// \`-o binDir/\`. Any compile error from either is returned.
|
|
365
369
|
func runGoBuild(dir, binDir string, progDirs []string, fragPkg string) (string, error) {
|
|
366
370
|
var out strings.Builder
|
|
367
371
|
if fragPkg != "" {
|
|
368
|
-
cmd := exec.Command("go", "build", fragPkg)
|
|
372
|
+
cmd := exec.Command("go", "build", "-gcflags=-e", fragPkg)
|
|
369
373
|
cmd.Dir = dir
|
|
370
374
|
o, err := cmd.CombinedOutput()
|
|
371
375
|
out.Write(o)
|
|
@@ -374,7 +378,7 @@ func runGoBuild(dir, binDir string, progDirs []string, fragPkg string) (string,
|
|
|
374
378
|
}
|
|
375
379
|
}
|
|
376
380
|
if len(progDirs) > 0 {
|
|
377
|
-
args := append([]string{"build", "-o", binDir + string(os.PathSeparator)}, progDirs...)
|
|
381
|
+
args := append([]string{"build", "-gcflags=-e", "-o", binDir + string(os.PathSeparator)}, progDirs...)
|
|
378
382
|
cmd := exec.Command("go", args...)
|
|
379
383
|
cmd.Dir = dir
|
|
380
384
|
o, err := cmd.CombinedOutput()
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { cmp, Content, isAuthActive, envName, canonKey, entityIdField, entityPrimaryOp, opRequestShape
|
|
2
|
+
import { cmp, Content, isAuthActive, envName, canonKey, entityIdField, entityPrimaryOp, opRequestShape } from '@voxgig/sdkgen'
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
KIT,
|
|
@@ -7,6 +7,8 @@ import {
|
|
|
7
7
|
nom,
|
|
8
8
|
} from '@voxgig/apidef'
|
|
9
9
|
|
|
10
|
+
import { goVarName } from './utility_go'
|
|
11
|
+
|
|
10
12
|
|
|
11
13
|
// A type-correct Go literal for a field's canonical type.
|
|
12
14
|
function goLit(type: any): string {
|
|
@@ -32,7 +34,9 @@ const ReadmeHowto = cmp(function ReadmeHowto(props: any) {
|
|
|
32
34
|
const entity = getModelPath(model, `main.${KIT}.entity`)
|
|
33
35
|
const exampleEntity = Object.values(entity || {}).find((e: any) => e && e.active !== false) as any
|
|
34
36
|
const eName = exampleEntity ? nom(exampleEntity, 'Name') : 'Entity'
|
|
35
|
-
|
|
37
|
+
// camelCase Go identifier (never snake_case or flattened lowercase,
|
|
38
|
+
// never a Go keyword).
|
|
39
|
+
const eLower = exampleEntity ? goVarName(exampleEntity.name) : 'entity'
|
|
36
40
|
// Model-driven id key: null when the entity has no id-like field.
|
|
37
41
|
const idF = exampleEntity ? entityIdField(exampleEntity) : null
|
|
38
42
|
// Drive the test-mode example off the entity's PRIMARY op — never a hardcoded
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
|
|
2
|
-
import { cmp, each, Content, isAuthActive, entityIdField, entityPrimaryOp
|
|
2
|
+
import { cmp, each, Content, isAuthActive, entityIdField, entityPrimaryOp } from '@voxgig/sdkgen'
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
KIT,
|
|
6
6
|
getModelPath,
|
|
7
7
|
} from '@voxgig/apidef'
|
|
8
8
|
|
|
9
|
+
import { goVarName } from './utility_go'
|
|
10
|
+
|
|
9
11
|
|
|
10
12
|
const ReadmeModel = cmp(function ReadmeModel(props: any) {
|
|
11
13
|
const { target, ctx$: { model } } = props
|
|
@@ -50,7 +52,9 @@ const ReadmeModel = cmp(function ReadmeModel(props: any) {
|
|
|
50
52
|
|
|
51
53
|
// First published entity name, for the Result shape illustration.
|
|
52
54
|
const firstEntityName = (entityList[0] as any)?.Name || 'Entity'
|
|
53
|
-
|
|
55
|
+
// camelCase Go identifier (never snake_case or flattened lowercase,
|
|
56
|
+
// never a Go keyword).
|
|
57
|
+
const firstEntityVar = goVarName((entityList[0] as any)?.name || 'entity')
|
|
54
58
|
// Model-driven id key: null when the example entity has no id-like field, so
|
|
55
59
|
// the Result-shape illustration passes a nil match.
|
|
56
60
|
const firstIdF = entityIdField(entityList[0] || {})
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { cmp, Content, isAuthActive, envName,
|
|
2
|
+
import { cmp, Content, isAuthActive, envName, opRequestShape, entityIdField, entityOps } from '@voxgig/sdkgen'
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
KIT,
|
|
@@ -7,6 +7,8 @@ import {
|
|
|
7
7
|
nom,
|
|
8
8
|
} from '@voxgig/apidef'
|
|
9
9
|
|
|
10
|
+
import { exampleValue, goVarName } from './utility_go'
|
|
11
|
+
|
|
10
12
|
|
|
11
13
|
// Emits the go/README.md Quickstart as ONE complete, compilable program.
|
|
12
14
|
// Every entity operation returns `(value, error)` where `value` is the
|
|
@@ -35,40 +37,54 @@ const ReadmeQuick = cmp(function ReadmeQuick(props: any) {
|
|
|
35
37
|
|
|
36
38
|
if (exampleEntity) {
|
|
37
39
|
const eName = nom(exampleEntity, 'Name')
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
//
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const idField = (exampleEntity.id && exampleEntity.id.field) || 'id'
|
|
40
|
+
// camelCase variable name — a `status_embed_config` entity must not bind
|
|
41
|
+
// a snake_case Go variable, and a `Type`/`Range` entity must not bind a
|
|
42
|
+
// Go keyword (`type, err := ...` fails `go build`).
|
|
43
|
+
const eLower = goVarName(exampleEntity.name)
|
|
44
|
+
// ACTIVE ops only — an inactive op generates no method, so an example
|
|
45
|
+
// calling it would not compile.
|
|
46
|
+
const opnames = entityOps(exampleEntity)
|
|
47
|
+
|
|
47
48
|
// Model-driven id key: null when the entity has no id-like field (a
|
|
48
49
|
// response-wrapped spec). When null, load/remove pass a nil match and
|
|
49
50
|
// update omits the id member.
|
|
50
51
|
const idF = entityIdField(exampleEntity)
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const examplePairs = (opname: string): string[] => {
|
|
52
|
+
|
|
53
|
+
// Model-driven example members for an op body, from the SAME op shape the
|
|
54
|
+
// request types are built from (opRequestShape), so create/update
|
|
55
|
+
// reference REAL writable fields, not a hardcoded "name", and every value
|
|
56
|
+
// is a type-correct Go literal (exampleValue). ids are rendered
|
|
57
|
+
// separately as the match key for update/remove; a REQUIRED id stays for
|
|
58
|
+
// create (dropping it makes the payload incomplete).
|
|
59
|
+
const exampleFields = (opname: string): string[] => {
|
|
60
60
|
const items = opRequestShape(exampleEntity, opname).items
|
|
61
|
-
.filter((it: any) => it.name !==
|
|
61
|
+
.filter((it: any) => (it.name !== idF && it.name !== 'id') ||
|
|
62
|
+
('create' === opname && !it.optional))
|
|
62
63
|
const required = items.filter((it: any) => !it.optional)
|
|
64
|
+
const optional = items.filter((it: any) => it.optional)
|
|
65
|
+
// Required members must all appear or the payload is incomplete; pad
|
|
66
|
+
// update (a patch) with a sample optional field or two.
|
|
63
67
|
const chosen = 'create' === opname
|
|
64
68
|
? (required.length ? required : items.slice(0, 2))
|
|
65
|
-
:
|
|
66
|
-
return chosen.map((it: any) =>
|
|
69
|
+
: required.concat(optional).slice(0, Math.max(2, required.length))
|
|
70
|
+
return chosen.map((it: any) =>
|
|
71
|
+
`"${it.name}": ${exampleValue(exampleEntity, exampleEntity.op[opname], it.name, 'example_' + it.name)}`)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// The full REQUIRED match for load/remove (id first, then parent path
|
|
75
|
+
// params like page_id) — the same shape that generates the op's request
|
|
76
|
+
// match, so the example always carries the keys the route needs.
|
|
77
|
+
const matchArg = (opname: string): string => {
|
|
78
|
+
const matchItems = opRequestShape(exampleEntity, opname).items
|
|
79
|
+
.filter((it: any) => !it.optional || it.name === idF)
|
|
80
|
+
.sort((a: any, b: any) =>
|
|
81
|
+
(a.name === idF ? 0 : 1) - (b.name === idF ? 0 : 1))
|
|
82
|
+
return 0 < matchItems.length
|
|
83
|
+
? `map[string]any{${matchItems.map((it: any) =>
|
|
84
|
+
`"${it.name}": ${exampleValue(exampleEntity, exampleEntity.op[opname], it.name,
|
|
85
|
+
it.name === idF ? 'example_id' : 'example_' + it.name)}`).join(', ')}}`
|
|
86
|
+
: 'nil'
|
|
67
87
|
}
|
|
68
|
-
const idOp = opnames.includes('load') ? 'load' : (opnames.includes('update') ? 'update' : 'remove')
|
|
69
|
-
const idShape = opRequestShape(exampleEntity, idOp)
|
|
70
|
-
.items.find((it: any) => it.name === idField || it.name === 'id')
|
|
71
|
-
const idLit = idShape ? goLit(idShape.type) : '"example_id"'
|
|
72
88
|
|
|
73
89
|
if (opnames.includes('list')) {
|
|
74
90
|
body.push(` // List ${eLower} records — the value is the array of records itself.`)
|
|
@@ -85,7 +101,7 @@ const ReadmeQuick = cmp(function ReadmeQuick(props: any) {
|
|
|
85
101
|
|
|
86
102
|
if (opnames.includes('load')) {
|
|
87
103
|
body.push(` // Load a single ${eLower} — the value is the loaded record.`)
|
|
88
|
-
body.push(` ${eLower}, err := client.${eName}(nil).Load(${
|
|
104
|
+
body.push(` ${eLower}, err := client.${eName}(nil).Load(${matchArg('load')}, nil)`)
|
|
89
105
|
body.push(` if err != nil {`)
|
|
90
106
|
body.push(` panic(err)`)
|
|
91
107
|
body.push(` }`)
|
|
@@ -96,7 +112,7 @@ const ReadmeQuick = cmp(function ReadmeQuick(props: any) {
|
|
|
96
112
|
|
|
97
113
|
if (opnames.includes('create')) {
|
|
98
114
|
body.push(` // Create a ${eLower}.`)
|
|
99
|
-
body.push(` created, err := client.${eName}(nil).Create(map[string]any{${
|
|
115
|
+
body.push(` created, err := client.${eName}(nil).Create(map[string]any{${exampleFields('create').join(', ')}}, nil)`)
|
|
100
116
|
body.push(` if err != nil {`)
|
|
101
117
|
body.push(` panic(err)`)
|
|
102
118
|
body.push(` }`)
|
|
@@ -106,8 +122,13 @@ const ReadmeQuick = cmp(function ReadmeQuick(props: any) {
|
|
|
106
122
|
}
|
|
107
123
|
|
|
108
124
|
if (opnames.includes('update')) {
|
|
125
|
+
// The id member (when the entity has an id-like key) plus example
|
|
126
|
+
// patch fields — the same shape that generates the op's request data.
|
|
127
|
+
const updateMembers = (idF
|
|
128
|
+
? [`"${idF}": ${exampleValue(exampleEntity, exampleEntity.op.update, idF, 'example_id')}`]
|
|
129
|
+
: []).concat(exampleFields('update'))
|
|
109
130
|
body.push(` // Update a ${eLower}.`)
|
|
110
|
-
body.push(` updated, err := client.${eName}(nil).Update(map[string]any{${
|
|
131
|
+
body.push(` updated, err := client.${eName}(nil).Update(map[string]any{${updateMembers.join(', ')}}, nil)`)
|
|
111
132
|
body.push(` if err != nil {`)
|
|
112
133
|
body.push(` panic(err)`)
|
|
113
134
|
body.push(` }`)
|
|
@@ -118,7 +139,7 @@ const ReadmeQuick = cmp(function ReadmeQuick(props: any) {
|
|
|
118
139
|
|
|
119
140
|
if (opnames.includes('remove')) {
|
|
120
141
|
body.push(` // Remove a ${eLower}.`)
|
|
121
|
-
body.push(` removed, err := client.${eName}(nil).Remove(${
|
|
142
|
+
body.push(` removed, err := client.${eName}(nil).Remove(${matchArg('remove')}, nil)`)
|
|
122
143
|
body.push(` if err != nil {`)
|
|
123
144
|
body.push(` panic(err)`)
|
|
124
145
|
body.push(` }`)
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
|
|
2
|
-
import { cmp, each, Content, canonToType, File, isAuthActive, entityIdField } from '@voxgig/sdkgen'
|
|
2
|
+
import { cmp, each, Content, canonToType, File, isAuthActive, entityIdField, entityOps, opRequestShape } from '@voxgig/sdkgen'
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
KIT,
|
|
6
6
|
getModelPath,
|
|
7
7
|
} from '@voxgig/apidef'
|
|
8
8
|
|
|
9
|
+
import { exampleValue, goVarName } from './utility_go'
|
|
10
|
+
|
|
9
11
|
|
|
10
12
|
const OP_SIGNATURES: Record<string, { sig: string, returns: string, desc: string }> = {
|
|
11
13
|
load: {
|
|
@@ -158,11 +160,16 @@ same parameters as \`Direct()\`.
|
|
|
158
160
|
|
|
159
161
|
// Entity reference sections
|
|
160
162
|
publishedEntities.map((ent: any) => {
|
|
161
|
-
|
|
163
|
+
// ACTIVE ops only — an inactive op generates no method, so an example
|
|
164
|
+
// calling it would not compile.
|
|
165
|
+
const opnames = entityOps(ent)
|
|
162
166
|
const fields = ent.fields || []
|
|
163
167
|
// Model-driven id key: null when this entity has no id-like field, in
|
|
164
168
|
// which case load/remove pass a nil match and update omits the id.
|
|
165
169
|
const idF = entityIdField(ent)
|
|
170
|
+
// camelCase Go identifier (a `status_embed_config` entity must not bind
|
|
171
|
+
// a snake_case Go variable).
|
|
172
|
+
const eVar = goVarName(ent.name)
|
|
166
173
|
|
|
167
174
|
Content(`
|
|
168
175
|
---
|
|
@@ -178,7 +185,8 @@ same parameters as \`Direct()\`.
|
|
|
178
185
|
}
|
|
179
186
|
|
|
180
187
|
Content(`\`\`\`go
|
|
181
|
-
${
|
|
188
|
+
${eVar} := client.${ent.Name}(nil)
|
|
189
|
+
fmt.Println(${eVar}.GetName()) // "${ent.name}"
|
|
182
190
|
\`\`\`
|
|
183
191
|
|
|
184
192
|
`)
|
|
@@ -205,9 +213,10 @@ ${ent.name} := client.${ent.Name}(nil)
|
|
|
205
213
|
const hasFieldOps = fields.some((f: any) => f.op && Object.keys(f.op).length > 0)
|
|
206
214
|
if (hasFieldOps) {
|
|
207
215
|
// Only emit columns for operations this entity actually exposes —
|
|
208
|
-
// never advertise a create/update/remove column the entity lacks
|
|
216
|
+
// never advertise a create/update/remove column the entity lacks
|
|
217
|
+
// (opnames already carries active ops only).
|
|
209
218
|
const opcols = ['load', 'list', 'create', 'update', 'remove']
|
|
210
|
-
.filter((op: string) => opnames.includes(op)
|
|
219
|
+
.filter((op: string) => opnames.includes(op))
|
|
211
220
|
Content(`### Field Usage by Operation
|
|
212
221
|
|
|
213
222
|
| Field | ${opcols.join(' | ')} |
|
|
@@ -247,11 +256,29 @@ ${info.desc}
|
|
|
247
256
|
|
|
248
257
|
`)
|
|
249
258
|
|
|
250
|
-
// Show example
|
|
259
|
+
// Show example. Every compiled example is self-consuming — check
|
|
260
|
+
// `err` and print the result — so the doc gate builds it without
|
|
261
|
+
// unused-variable repairs, and readers see the idiomatic pattern.
|
|
251
262
|
if ('load' === opname || 'remove' === opname) {
|
|
252
263
|
const goOpName = opname.charAt(0).toUpperCase() + opname.slice(1)
|
|
264
|
+
// The id key plus every REQUIRED match key (parent path params
|
|
265
|
+
// like page_id) — the same shape that generates the op's request
|
|
266
|
+
// match, so the example always carries the keys the route needs.
|
|
267
|
+
const matchItems = opRequestShape(ent, opname).items
|
|
268
|
+
.filter((it: any) => !it.optional || it.name === idF)
|
|
269
|
+
.sort((a: any, b: any) =>
|
|
270
|
+
(a.name === idF ? 0 : 1) - (b.name === idF ? 0 : 1))
|
|
271
|
+
const arg = 0 < matchItems.length
|
|
272
|
+
? `map[string]any{${matchItems.map((it: any) =>
|
|
273
|
+
`"${it.name}": ${exampleValue(ent, ent.op && ent.op[opname], it.name,
|
|
274
|
+
it.name === idF ? ent.name + '_id' : it.name)}`).join(', ')}}`
|
|
275
|
+
: 'nil'
|
|
253
276
|
Content(`\`\`\`go
|
|
254
|
-
result, err := client.${ent.Name}(nil).${goOpName}(${
|
|
277
|
+
result, err := client.${ent.Name}(nil).${goOpName}(${arg}, nil)
|
|
278
|
+
if err != nil {
|
|
279
|
+
panic(err)
|
|
280
|
+
}
|
|
281
|
+
fmt.Println(result)
|
|
255
282
|
\`\`\`
|
|
256
283
|
|
|
257
284
|
`)
|
|
@@ -259,31 +286,57 @@ result, err := client.${ent.Name}(nil).${goOpName}(${idF ? `map[string]any{"${id
|
|
|
259
286
|
else if ('list' === opname) {
|
|
260
287
|
Content(`\`\`\`go
|
|
261
288
|
results, err := client.${ent.Name}(nil).List(nil, nil)
|
|
289
|
+
if err != nil {
|
|
290
|
+
panic(err)
|
|
291
|
+
}
|
|
292
|
+
fmt.Println(results)
|
|
262
293
|
\`\`\`
|
|
263
294
|
|
|
264
295
|
`)
|
|
265
296
|
}
|
|
266
297
|
else if ('create' === opname) {
|
|
298
|
+
// Members come from the SAME shape that generates the op's
|
|
299
|
+
// request data: every required member must appear — including a
|
|
300
|
+
// required id (the /* type */ placeholders also mark the block
|
|
301
|
+
// as an illustration for the doc gates); an all-optional create
|
|
302
|
+
// renders an empty — still valid — literal.
|
|
303
|
+
const createItems = opRequestShape(ent, 'create').items
|
|
304
|
+
.filter((it: any) => !it.optional)
|
|
267
305
|
Content(`\`\`\`go
|
|
268
306
|
result, err := client.${ent.Name}(nil).Create(map[string]any{
|
|
269
307
|
`)
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
Content(` "${field.name}": /* ${canonToType(field.type, target.name)} */,
|
|
308
|
+
createItems.map((it: any) => {
|
|
309
|
+
Content(` "${it.name}": /* ${canonToType(it.type, target.name)} */,
|
|
273
310
|
`)
|
|
274
|
-
}
|
|
275
311
|
})
|
|
276
312
|
Content(`}, nil)
|
|
313
|
+
if err != nil {
|
|
314
|
+
panic(err)
|
|
315
|
+
}
|
|
316
|
+
fmt.Println(result)
|
|
277
317
|
\`\`\`
|
|
278
318
|
|
|
279
319
|
`)
|
|
280
320
|
}
|
|
281
321
|
else if ('update' === opname) {
|
|
282
|
-
|
|
322
|
+
// The id key plus every REQUIRED data member — the same shape
|
|
323
|
+
// that generates the op's request data — then the patch-fields
|
|
324
|
+
// note.
|
|
325
|
+
const updateItems = opRequestShape(ent, 'update').items
|
|
326
|
+
.filter((it: any) => !it.optional || it.name === idF)
|
|
327
|
+
.sort((a: any, b: any) =>
|
|
328
|
+
(a.name === idF ? 0 : 1) - (b.name === idF ? 0 : 1))
|
|
329
|
+
const updateLines = updateItems.map((it: any) =>
|
|
330
|
+
` "${it.name}": ${exampleValue(ent, ent.op && ent.op.update, it.name,
|
|
331
|
+
it.name === idF ? ent.name + '_id' : it.name)},\n`).join('')
|
|
283
332
|
Content(`\`\`\`go
|
|
284
333
|
result, err := client.${ent.Name}(nil).Update(map[string]any{
|
|
285
|
-
${
|
|
334
|
+
${updateLines} // Fields to update
|
|
286
335
|
}, nil)
|
|
336
|
+
if err != nil {
|
|
337
|
+
panic(err)
|
|
338
|
+
}
|
|
339
|
+
fmt.Println(result)
|
|
287
340
|
\`\`\`
|
|
288
341
|
|
|
289
342
|
`)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { cmp, Content, isAuthActive, envName, entityIdField,
|
|
2
|
+
import { cmp, Content, isAuthActive, envName, entityIdField, entityOps, opRequestShape } from '@voxgig/sdkgen'
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
KIT,
|
|
@@ -7,6 +7,8 @@ import {
|
|
|
7
7
|
nom,
|
|
8
8
|
} from '@voxgig/apidef'
|
|
9
9
|
|
|
10
|
+
import { exampleValue, goVarName } from './utility_go'
|
|
11
|
+
|
|
10
12
|
|
|
11
13
|
const ReadmeTopQuick = cmp(function ReadmeTopQuick(props: any) {
|
|
12
14
|
const { target, ctx$: { model } } = props
|
|
@@ -31,42 +33,61 @@ client := ${ctor}
|
|
|
31
33
|
|
|
32
34
|
if (exampleEntity) {
|
|
33
35
|
const eName = nom(exampleEntity, 'Name')
|
|
34
|
-
|
|
35
|
-
const
|
|
36
|
+
// camelCase Go identifier (never snake_case, never a Go keyword).
|
|
37
|
+
const eVar = goVarName(exampleEntity.name)
|
|
38
|
+
// ACTIVE ops only — an inactive op generates no method, so an example
|
|
39
|
+
// calling it would not compile.
|
|
40
|
+
const opnames = entityOps(exampleEntity)
|
|
36
41
|
|
|
37
42
|
let hasCall = false
|
|
38
43
|
|
|
39
44
|
if (opnames.includes('list')) {
|
|
40
45
|
Content(`// List all ${eName.toLowerCase()}s
|
|
41
46
|
${eVar}s, err := client.${eName}(nil).List(nil, nil)
|
|
47
|
+
if err != nil {
|
|
48
|
+
panic(err)
|
|
49
|
+
}
|
|
42
50
|
fmt.Println(${eVar}s)
|
|
43
51
|
`)
|
|
44
52
|
hasCall = true
|
|
45
53
|
}
|
|
46
54
|
|
|
47
|
-
// Find a nested entity for a more interesting example
|
|
55
|
+
// Find a nested entity for a more interesting example: one with a parent
|
|
56
|
+
// chain (relations.ancestors), an active load op, and a required non-id
|
|
57
|
+
// load param to demonstrate (the parent key, e.g. page_id).
|
|
48
58
|
const nestedEntity = Object.values(entity).find((e: any) =>
|
|
49
|
-
e.active !== false &&
|
|
59
|
+
e.active !== false &&
|
|
60
|
+
e.relations && e.relations.ancestors && 0 < e.relations.ancestors.length &&
|
|
61
|
+
entityOps(e).includes('load') &&
|
|
62
|
+
opRequestShape(e, 'load').items.some((it: any) =>
|
|
63
|
+
!it.optional && it.name !== entityIdField(e))
|
|
50
64
|
) as any
|
|
51
65
|
|
|
52
|
-
if (nestedEntity
|
|
66
|
+
if (nestedEntity) {
|
|
53
67
|
const neName = nom(nestedEntity, 'Name')
|
|
54
|
-
const neVar =
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
//
|
|
68
|
+
const neVar = goVarName(nestedEntity.name)
|
|
69
|
+
// Model-driven match: every REQUIRED load-match key — the same shape
|
|
70
|
+
// that generates the op's request match, so the example always carries
|
|
71
|
+
// the keys the route needs. Parent keys (e.g. page_id) first, the
|
|
72
|
+
// entity's own id last, each value a type-correct Go literal.
|
|
59
73
|
const neIdF = entityIdField(nestedEntity)
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
74
|
+
const neLoadOp = nestedEntity.op && nestedEntity.op.load
|
|
75
|
+
const neMatchPairs = opRequestShape(nestedEntity, 'load').items
|
|
76
|
+
.filter((it: any) => !it.optional)
|
|
77
|
+
.sort((a: any, b: any) =>
|
|
78
|
+
(a.name === neIdF ? 1 : 0) - (b.name === neIdF ? 1 : 0))
|
|
79
|
+
.map((it: any) =>
|
|
80
|
+
`"${it.name}": ${exampleValue(nestedEntity, neLoadOp, it.name,
|
|
81
|
+
it.name === neIdF ? 'example_id' : 'example_' + it.name)}`)
|
|
64
82
|
|
|
65
83
|
Content(`
|
|
66
84
|
// Load a specific ${neName.toLowerCase()}
|
|
67
85
|
${neVar}, err := client.${neName}(nil).Load(
|
|
68
86
|
map[string]any{${neMatchPairs.join(', ')}}, nil,
|
|
69
87
|
)
|
|
88
|
+
if err != nil {
|
|
89
|
+
panic(err)
|
|
90
|
+
}
|
|
70
91
|
fmt.Println(${neVar})
|
|
71
92
|
`)
|
|
72
93
|
hasCall = true
|
|
@@ -74,8 +95,22 @@ fmt.Println(${neVar})
|
|
|
74
95
|
|
|
75
96
|
// Fallback: APIs with only `load` (no list, no nested) — still show one call.
|
|
76
97
|
if (!hasCall && opnames.includes('load')) {
|
|
98
|
+
// Every REQUIRED load-match key (id first) — nil when there are none.
|
|
99
|
+
const idF = entityIdField(exampleEntity)
|
|
100
|
+
const loadItems = opRequestShape(exampleEntity, 'load').items
|
|
101
|
+
.filter((it: any) => !it.optional || it.name === idF)
|
|
102
|
+
.sort((a: any, b: any) =>
|
|
103
|
+
(a.name === idF ? 0 : 1) - (b.name === idF ? 0 : 1))
|
|
104
|
+
const loadArg = 0 < loadItems.length
|
|
105
|
+
? `map[string]any{${loadItems.map((it: any) =>
|
|
106
|
+
`"${it.name}": ${exampleValue(exampleEntity, exampleEntity.op && exampleEntity.op.load, it.name,
|
|
107
|
+
it.name === idF ? 'example_id' : 'example_' + it.name)}`).join(', ')}}`
|
|
108
|
+
: 'nil'
|
|
77
109
|
Content(`// Load ${eName.toLowerCase()} data
|
|
78
|
-
${eVar}, err := client.${eName}(nil).Load(
|
|
110
|
+
${eVar}, err := client.${eName}(nil).Load(${loadArg}, nil)
|
|
111
|
+
if err != nil {
|
|
112
|
+
panic(err)
|
|
113
|
+
}
|
|
79
114
|
fmt.Println(${eVar})
|
|
80
115
|
`)
|
|
81
116
|
hasCall = true
|