@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, Content, canonKey, entityIdField,
|
|
2
|
+
import { cmp, Content, canonKey, entityIdField, pickExampleEntity, opRequestShape } from '@voxgig/sdkgen'
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
KIT,
|
|
@@ -23,16 +23,15 @@ const ReadmeTopTest = cmp(function ReadmeTopTest(props: any) {
|
|
|
23
23
|
|
|
24
24
|
const entity = getModelPath(model, `main.${KIT}.entity`)
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
// Pick an entity with a real op (prefer a read op) — never fabricate a
|
|
27
|
+
// `load` on an op-less entity like Cloudsmith's `Abort`.
|
|
28
|
+
const { entity: exampleEntity, primaryOp } = pickExampleEntity(entity)
|
|
27
29
|
|
|
28
|
-
if (exampleEntity) {
|
|
30
|
+
if (exampleEntity && primaryOp) {
|
|
29
31
|
const eName = nom(exampleEntity, 'Name')
|
|
30
32
|
const ename = eName.toLowerCase()
|
|
31
33
|
// Model-driven id key: null when the entity has no id-like field.
|
|
32
34
|
const idF = entityIdField(exampleEntity)
|
|
33
|
-
// Drive the test-mode example off the entity's PRIMARY op (never a hardcoded
|
|
34
|
-
// `load` a create-only entity lacks).
|
|
35
|
-
const primaryOp = entityPrimaryOp(exampleEntity) || 'load'
|
|
36
35
|
const isMatchOp = 'load' === primaryOp || 'remove' === primaryOp
|
|
37
36
|
const recBody = idF ? `["${idF}" => "test01"]` : '[]'
|
|
38
37
|
let callArg = ''
|
|
@@ -8,7 +8,7 @@ require_once __DIR__ . '/../core/Helpers.php';
|
|
|
8
8
|
|
|
9
9
|
use Voxgig\Struct\Struct;
|
|
10
10
|
|
|
11
|
-
class
|
|
11
|
+
class EntyClass
|
|
12
12
|
{
|
|
13
13
|
private string $_name;
|
|
14
14
|
private $_client;
|
|
@@ -52,7 +52,7 @@ class EntityNameEntity
|
|
|
52
52
|
public function make(): self
|
|
53
53
|
{
|
|
54
54
|
$opts = $this->_entopts;
|
|
55
|
-
return new
|
|
55
|
+
return new EntyClass($this->_client, $opts);
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
/**
|
|
@@ -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_py'
|
|
10
16
|
|
|
11
17
|
|
|
@@ -19,6 +25,13 @@ const Entity = cmp(function Entity(props: any) {
|
|
|
19
25
|
const { model, stdrep } = props.ctx$
|
|
20
26
|
const { target, entity } = props
|
|
21
27
|
|
|
28
|
+
// Collision-free entity CLASS name (see entityClassName): normally
|
|
29
|
+
// `<Name>Entity`, disambiguated when it would clash with another entity's
|
|
30
|
+
// data-type name. The DATA type stays `<Name>`. The snake-cased source-file
|
|
31
|
+
// name is unaffected; only the class identifier changes.
|
|
32
|
+
const entityColl = getModelPath(model, `main.${KIT}.entity`)
|
|
33
|
+
const cls = entityClassName(entity, entityColl)
|
|
34
|
+
|
|
22
35
|
const entrep = {
|
|
23
36
|
...stdrep,
|
|
24
37
|
}
|
|
@@ -66,6 +79,10 @@ const Entity = cmp(function Entity(props: any) {
|
|
|
66
79
|
EntityName: entity.Name,
|
|
67
80
|
entityname: entity.name,
|
|
68
81
|
|
|
82
|
+
// Class token decoupled from the EntityName data-type token in
|
|
83
|
+
// Entity.fragment.py so the class can be renamed independently.
|
|
84
|
+
EntyClass: cls,
|
|
85
|
+
|
|
69
86
|
// Literal-marker slot (jostraca's `#Name` tag pattern is hardcoded to
|
|
70
87
|
// `//` comments, so Python uses a `# #`-prefixed literal like the op
|
|
71
88
|
// frag slots) — fills in the typed-model import for this entity.
|
|
@@ -1,17 +1,27 @@
|
|
|
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
|
|
8
13
|
const { model } = props.ctx$
|
|
9
14
|
|
|
15
|
+
// Return the collision-free class (entityClassName); the accessor METHOD name
|
|
16
|
+
// (entity.Name) is unchanged so callers still write client.<Name>(). The
|
|
17
|
+
// source module is snake-cased, so only the imported class identifier changes.
|
|
18
|
+
const cls = entityClassName(entity, getModelPath(model, `main.${KIT}.entity`))
|
|
19
|
+
|
|
10
20
|
Content(`
|
|
11
|
-
def ${entity.Name}(self, data=None) -> "${
|
|
21
|
+
def ${entity.Name}(self, data=None) -> "${cls}":
|
|
12
22
|
"""Entity factory: client.${entity.Name}().list() / client.${entity.Name}().load({"id": ...})."""
|
|
13
|
-
from entity.${entity.name}_entity import ${
|
|
14
|
-
return ${
|
|
23
|
+
from entity.${entity.name}_entity import ${cls}
|
|
24
|
+
return ${cls}(self, data)
|
|
15
25
|
|
|
16
26
|
`)
|
|
17
27
|
|
|
@@ -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
|
|
|
@@ -93,7 +94,7 @@ from typing import TYPE_CHECKING
|
|
|
93
94
|
if TYPE_CHECKING:
|
|
94
95
|
`)
|
|
95
96
|
each(entity, (ent: any) => {
|
|
96
|
-
Content(` from entity.${ent.name}_entity import ${ent
|
|
97
|
+
Content(` from entity.${ent.name}_entity import ${entityClassName(ent, entity)}
|
|
97
98
|
`)
|
|
98
99
|
})
|
|
99
100
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { cmp, each, Content, canonToType, canonKey, entityIdField, opRequestShape } from '@voxgig/sdkgen'
|
|
2
|
+
import { cmp, each, Content, canonToType, canonKey, entityIdField, opRequestShape, safeVarName } from '@voxgig/sdkgen'
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
KIT,
|
|
@@ -58,6 +58,9 @@ const ReadmeEntity = cmp(function ReadmeEntity(props: any) {
|
|
|
58
58
|
const fields = entity.fields || []
|
|
59
59
|
// Model-driven id key: null when this entity has no id-like field.
|
|
60
60
|
const idF = entityIdField(entity)
|
|
61
|
+
// Sanitise the local variable name — an entity whose lowercased name is a
|
|
62
|
+
// Python keyword (e.g. `class`) would otherwise emit uncompilable code.
|
|
63
|
+
const eVar = safeVarName(entity.name, 'py')
|
|
61
64
|
|
|
62
65
|
Content(`
|
|
63
66
|
### ${entity.Name}
|
|
@@ -70,7 +73,7 @@ const ReadmeEntity = cmp(function ReadmeEntity(props: any) {
|
|
|
70
73
|
`)
|
|
71
74
|
}
|
|
72
75
|
|
|
73
|
-
Content(`Create an instance: \`${
|
|
76
|
+
Content(`Create an instance: \`${eVar} = client.${entity.Name}()\`
|
|
74
77
|
|
|
75
78
|
`)
|
|
76
79
|
|
|
@@ -125,7 +128,7 @@ const ReadmeEntity = cmp(function ReadmeEntity(props: any) {
|
|
|
125
128
|
Content(`#### Example: Load
|
|
126
129
|
|
|
127
130
|
\`\`\`python
|
|
128
|
-
${
|
|
131
|
+
${eVar} = client.${entity.Name}().load(${loadArg})
|
|
129
132
|
\`\`\`
|
|
130
133
|
|
|
131
134
|
`)
|
|
@@ -135,7 +138,7 @@ ${entity.name} = client.${entity.Name}().load(${loadArg})
|
|
|
135
138
|
Content(`#### Example: List
|
|
136
139
|
|
|
137
140
|
\`\`\`python
|
|
138
|
-
${
|
|
141
|
+
${eVar}s = client.${entity.Name}().list()
|
|
139
142
|
\`\`\`
|
|
140
143
|
|
|
141
144
|
`)
|
|
@@ -152,7 +155,7 @@ ${entity.name}s = client.${entity.Name}().list()
|
|
|
152
155
|
Content(`#### Example: Create
|
|
153
156
|
|
|
154
157
|
\`\`\`python
|
|
155
|
-
${
|
|
158
|
+
${eVar} = client.${entity.Name}().create({
|
|
156
159
|
`)
|
|
157
160
|
createItems.map((it: any) => {
|
|
158
161
|
Content(` "${it.name}": ${pyLit(it.type, 'example_' + it.name)}, # ${canonToType(it.type, target.name)}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { cmp, Content, isAuthActive, envName, canonKey, entityIdField,
|
|
2
|
+
import { cmp, Content, isAuthActive, envName, canonKey, entityIdField, pickExampleEntity, opRequestShape, safeVarName } from '@voxgig/sdkgen'
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
KIT,
|
|
@@ -23,14 +23,17 @@ const ReadmeHowto = cmp(function ReadmeHowto(props: any) {
|
|
|
23
23
|
const { target, ctx$: { model } } = props
|
|
24
24
|
|
|
25
25
|
const entity = getModelPath(model, `main.${KIT}.entity`)
|
|
26
|
-
|
|
26
|
+
// Pick an entity with a real op (prefer a read op) — never fabricate a
|
|
27
|
+
// `load` on an op-less entity like Cloudsmith's `Abort`. primaryOp is null
|
|
28
|
+
// only when NO entity exposes any op (a direct()-only SDK).
|
|
29
|
+
const { entity: exampleEntity, primaryOp } = pickExampleEntity(entity)
|
|
27
30
|
const eName = exampleEntity ? nom(exampleEntity, 'Name') : 'Entity'
|
|
31
|
+
// Sanitise the local variable name — an entity whose lowercased name is a
|
|
32
|
+
// Python keyword (e.g. `class`) would otherwise emit uncompilable code.
|
|
33
|
+
const eVar = safeVarName(eName.toLowerCase(), 'py')
|
|
28
34
|
// Model-driven id key: null when the entity has no id-like field, so a
|
|
29
35
|
// match op takes no argument.
|
|
30
36
|
const idF = exampleEntity ? entityIdField(exampleEntity) : null
|
|
31
|
-
// Drive the test-mode example off the entity's PRIMARY op — never a hardcoded
|
|
32
|
-
// `load` a create-only entity lacks.
|
|
33
|
-
const primaryOp = exampleEntity ? (entityPrimaryOp(exampleEntity) || 'load') : 'load'
|
|
34
37
|
const isMatchOp = 'load' === primaryOp || 'remove' === primaryOp
|
|
35
38
|
let testArg = ''
|
|
36
39
|
if (exampleEntity && isMatchOp) {
|
|
@@ -43,6 +46,15 @@ const ReadmeHowto = cmp(function ReadmeHowto(props: any) {
|
|
|
43
46
|
testArg = `{${chosen.map((it: any) => `"${it.name}": ${pyLit(it.type)}`).join(', ')}}`
|
|
44
47
|
}
|
|
45
48
|
|
|
49
|
+
// The op-driven test-mode line, shown only when the SDK has an entity op.
|
|
50
|
+
// A direct()-only SDK (no ops anywhere) shows a direct() call instead.
|
|
51
|
+
const testModeExample = primaryOp
|
|
52
|
+
? `# Entity ops return the bare record and raise on error.
|
|
53
|
+
${eVar} = client.${eName}().${primaryOp}(${testArg})
|
|
54
|
+
# ${eVar} contains the mock response record`
|
|
55
|
+
: `result = client.direct({"path": "/api/resource", "method": "GET"})
|
|
56
|
+
print(result)`
|
|
57
|
+
|
|
46
58
|
const apikeyEnvLine = isAuthActive(model)
|
|
47
59
|
? `\n${envName(model)}_APIKEY=<your-key>`
|
|
48
60
|
: ''
|
|
@@ -90,9 +102,7 @@ Create a mock client for unit testing — no server required:
|
|
|
90
102
|
\`\`\`python
|
|
91
103
|
client = ${model.const.Name}SDK.test()
|
|
92
104
|
|
|
93
|
-
|
|
94
|
-
${eName.toLowerCase()} = client.${eName}().${primaryOp}(${testArg})
|
|
95
|
-
# ${eName.toLowerCase()} contains the mock response record
|
|
105
|
+
${testModeExample}
|
|
96
106
|
\`\`\`
|
|
97
107
|
|
|
98
108
|
### Use a custom fetch function
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { cmp, each, Content, isAuthActive, envName, canonKey, opRequestShape, entityIdField, entityDataIdField, entityOps } from '@voxgig/sdkgen'
|
|
2
|
+
import { cmp, each, Content, isAuthActive, envName, canonKey, opRequestShape, entityIdField, entityDataIdField, entityOps, safeVarName } from '@voxgig/sdkgen'
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
KIT,
|
|
@@ -45,6 +45,9 @@ client = ${ctor}
|
|
|
45
45
|
if (exampleEntity) {
|
|
46
46
|
const eName = nom(exampleEntity, 'Name')
|
|
47
47
|
const article = /^[aeiou]/i.test(eName) ? 'an' : 'a'
|
|
48
|
+
// Sanitise the local variable name — an entity whose lowercased name is a
|
|
49
|
+
// Python keyword (e.g. `class`) would otherwise emit uncompilable code.
|
|
50
|
+
const eVar = safeVarName(eName.toLowerCase(), 'py')
|
|
48
51
|
const opnames = entityOps(exampleEntity)
|
|
49
52
|
// Model-driven id key: `idF` is the entity's id-like MATCH field name, or
|
|
50
53
|
// null when it has none (a response-wrapped spec). `dataIdF` is the id on
|
|
@@ -75,9 +78,9 @@ error — iterate it directly.
|
|
|
75
78
|
|
|
76
79
|
\`\`\`python
|
|
77
80
|
try:
|
|
78
|
-
${
|
|
79
|
-
for ${
|
|
80
|
-
print(${
|
|
81
|
+
${eVar}s = client.${eName}().list()
|
|
82
|
+
for ${eVar} in ${eVar}s:
|
|
83
|
+
print(${eVar})
|
|
81
84
|
except Exception as err:
|
|
82
85
|
print(f"list failed: {err}")
|
|
83
86
|
\`\`\`
|
|
@@ -88,7 +91,7 @@ except Exception as err:
|
|
|
88
91
|
if (nestedEntity) {
|
|
89
92
|
const neName = nom(nestedEntity, 'Name')
|
|
90
93
|
const neArticle = /^[aeiou]/i.test(neName) ? 'an' : 'a'
|
|
91
|
-
const neVar = neName.toLowerCase()
|
|
94
|
+
const neVar = safeVarName(neName.toLowerCase(), 'py')
|
|
92
95
|
|
|
93
96
|
// Model-driven match: every REQUIRED load-match key — the same shape
|
|
94
97
|
// the runtime resolves path params from, so the example always works.
|
|
@@ -140,8 +143,8 @@ except Exception as err:
|
|
|
140
143
|
|
|
141
144
|
\`\`\`python
|
|
142
145
|
try:
|
|
143
|
-
${
|
|
144
|
-
print(${
|
|
146
|
+
${eVar} = client.${eName}().load(${loadArg})
|
|
147
|
+
print(${eVar})
|
|
145
148
|
except Exception as err:
|
|
146
149
|
print(f"load failed: {err}")
|
|
147
150
|
\`\`\`
|
|
@@ -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,
|
|
@@ -166,6 +166,9 @@ Prepare a fetch definition without sending. Returns the \`fetchdef\` and raises
|
|
|
166
166
|
// Model-driven id key: null when this entity has no id-like field, in
|
|
167
167
|
// which case load/remove match on no argument and update omits the id.
|
|
168
168
|
const idF = entityIdField(ent)
|
|
169
|
+
// Sanitise the local variable name — an entity whose lowercased name is
|
|
170
|
+
// a Python keyword (e.g. `class`) would otherwise emit uncompilable code.
|
|
171
|
+
const eVar = safeVarName(ent.name, 'py')
|
|
169
172
|
|
|
170
173
|
Content(`
|
|
171
174
|
---
|
|
@@ -181,7 +184,7 @@ Prepare a fetch definition without sending. Returns the \`fetchdef\` and raises
|
|
|
181
184
|
}
|
|
182
185
|
|
|
183
186
|
Content(`\`\`\`python
|
|
184
|
-
${
|
|
187
|
+
${eVar} = client.${ent.Name}()
|
|
185
188
|
\`\`\`
|
|
186
189
|
|
|
187
190
|
`)
|
|
@@ -274,8 +277,8 @@ result = client.${ent.Name}().${opname}(${arg})
|
|
|
274
277
|
else if ('list' === opname) {
|
|
275
278
|
Content(`\`\`\`python
|
|
276
279
|
results = client.${ent.Name}().list()
|
|
277
|
-
for ${
|
|
278
|
-
print(${
|
|
280
|
+
for ${eVar} in results:
|
|
281
|
+
print(${eVar})
|
|
279
282
|
\`\`\`
|
|
280
283
|
|
|
281
284
|
`)
|
|
@@ -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,
|
|
@@ -44,6 +44,9 @@ client = ${ctor}
|
|
|
44
44
|
|
|
45
45
|
if (exampleEntity) {
|
|
46
46
|
const eName = nom(exampleEntity, 'Name')
|
|
47
|
+
// Sanitise the local variable name — an entity whose lowercased name is a
|
|
48
|
+
// Python keyword (e.g. `class`) would otherwise emit uncompilable code.
|
|
49
|
+
const eVar = safeVarName(eName.toLowerCase(), 'py')
|
|
47
50
|
const opnames = Object.keys(exampleEntity.op || {})
|
|
48
51
|
// Model-driven id key: null when the entity has no id-like field, in which
|
|
49
52
|
// case the load example takes no match argument.
|
|
@@ -53,9 +56,9 @@ client = ${ctor}
|
|
|
53
56
|
|
|
54
57
|
if (opnames.includes('list')) {
|
|
55
58
|
Content(`# List all ${eName.toLowerCase()}s (returns a list, raises on error)
|
|
56
|
-
${
|
|
57
|
-
for ${
|
|
58
|
-
print(${
|
|
59
|
+
${eVar}s = client.${eName}().list()
|
|
60
|
+
for ${eVar} in ${eVar}s:
|
|
61
|
+
print(${eVar})
|
|
59
62
|
`)
|
|
60
63
|
hasCall = true
|
|
61
64
|
}
|
|
@@ -75,8 +78,8 @@ for ${eName.toLowerCase()} in ${eName.toLowerCase()}s:
|
|
|
75
78
|
: ''
|
|
76
79
|
Content(`
|
|
77
80
|
# Load a specific ${eName.toLowerCase()} (returns the record, raises on error)
|
|
78
|
-
${
|
|
79
|
-
print(${
|
|
81
|
+
${eVar} = client.${eName}().load(${loadArg})
|
|
82
|
+
print(${eVar})
|
|
80
83
|
`)
|
|
81
84
|
hasCall = true
|
|
82
85
|
}
|
|
@@ -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,18 +24,17 @@ 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
31
|
Content(`\`\`\`python
|
|
30
32
|
client = ${model.const.Name}SDK.test()
|
|
31
33
|
`)
|
|
32
34
|
|
|
33
|
-
if (exampleEntity) {
|
|
35
|
+
if (exampleEntity && primaryOp) {
|
|
34
36
|
const eName = nom(exampleEntity, 'Name')
|
|
35
|
-
// Drive the test-mode example off the entity's PRIMARY op (never a
|
|
36
|
-
// hardcoded `load` a create-only entity lacks).
|
|
37
37
|
const idF = entityIdField(exampleEntity)
|
|
38
|
-
const primaryOp = entityPrimaryOp(exampleEntity) || 'load'
|
|
39
38
|
const isMatchOp = 'load' === primaryOp || 'remove' === primaryOp
|
|
40
39
|
let arg = ''
|
|
41
40
|
if (isMatchOp) {
|
|
@@ -59,7 +58,7 @@ client = ${model.const.Name}SDK.test()
|
|
|
59
58
|
// A list() result is a list — name the variable accordingly (the root
|
|
60
59
|
// README doc gate concatenates blocks, so reusing the singular name for
|
|
61
60
|
// a different type is a mypy assignment error).
|
|
62
|
-
const eVar = eName.toLowerCase() + ('list' === primaryOp ? 's' : '')
|
|
61
|
+
const eVar = safeVarName(eName.toLowerCase(), 'py') + ('list' === primaryOp ? 's' : '')
|
|
63
62
|
Content(`${eVar} = client.${eName}().${primaryOp}(${arg})
|
|
64
63
|
print(${eVar})
|
|
65
64
|
`)
|
|
@@ -7,7 +7,7 @@ from core import helpers
|
|
|
7
7
|
# #TypeImports
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
class
|
|
10
|
+
class EntyClass:
|
|
11
11
|
|
|
12
12
|
def __init__(self, client, entopts=None):
|
|
13
13
|
if entopts is None:
|
|
@@ -40,7 +40,7 @@ class EntityNameEntity:
|
|
|
40
40
|
opts = {}
|
|
41
41
|
for k, v in self._entopts.items():
|
|
42
42
|
opts[k] = v
|
|
43
|
-
return
|
|
43
|
+
return EntyClass(self._client, opts)
|
|
44
44
|
|
|
45
45
|
def data_set(self, args=None):
|
|
46
46
|
if args is not None:
|
|
@@ -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_rb'
|
|
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 snake-cased source-file name is unaffected.
|
|
25
|
+
const entityColl = getModelPath(model, `main.${KIT}.entity`)
|
|
26
|
+
const cls = entityClassName(entity, entityColl)
|
|
27
|
+
|
|
16
28
|
const entrep = {
|
|
17
29
|
...stdrep,
|
|
18
30
|
}
|
|
@@ -44,6 +56,10 @@ const Entity = cmp(function Entity(props: any) {
|
|
|
44
56
|
EntityName: entity.Name,
|
|
45
57
|
entityname: entity.name,
|
|
46
58
|
|
|
59
|
+
// Class token decoupled from the EntityName data-type token in
|
|
60
|
+
// Entity.fragment.rb so the class can be renamed independently.
|
|
61
|
+
EntyClass: cls,
|
|
62
|
+
|
|
47
63
|
'#Entity-Hook': ({ name, indent }: any) =>
|
|
48
64
|
Content({ indent }, `utility.feature_hook.call(ctx, "${name}")`),
|
|
49
65
|
|
|
@@ -1,17 +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
|
|
8
13
|
const { model } = props.ctx$
|
|
9
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`))
|
|
18
|
+
|
|
10
19
|
Content(`
|
|
11
20
|
# Canonical facade: client.${entity.Name}.list / client.${entity.Name}.load({ "id" => ... })
|
|
12
21
|
def ${entity.Name}(data = nil)
|
|
13
22
|
require_relative 'entity/${entity.name}_entity'
|
|
14
|
-
${
|
|
23
|
+
${cls}.new(self, data)
|
|
15
24
|
end
|
|
16
25
|
|
|
17
26
|
`)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { cmp, each, Content, canonToType, canonKey, entityIdField, opRequestShape } from '@voxgig/sdkgen'
|
|
2
|
+
import { cmp, each, Content, canonToType, canonKey, entityIdField, opRequestShape, safeVarName } from '@voxgig/sdkgen'
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
KIT,
|
|
@@ -56,6 +56,9 @@ const ReadmeEntity = cmp(function ReadmeEntity(props: any) {
|
|
|
56
56
|
const fields = entity.fields || []
|
|
57
57
|
// Model-driven id key: null when this entity has no id-like field.
|
|
58
58
|
const idF = entityIdField(entity)
|
|
59
|
+
// Sanitise the local variable name — an entity whose lowercased name is a
|
|
60
|
+
// Ruby keyword (e.g. `self`) would otherwise emit uncompilable code.
|
|
61
|
+
const eVar = safeVarName(entity.name, 'rb')
|
|
59
62
|
|
|
60
63
|
Content(`
|
|
61
64
|
### ${entity.Name}
|
|
@@ -68,7 +71,7 @@ const ReadmeEntity = cmp(function ReadmeEntity(props: any) {
|
|
|
68
71
|
`)
|
|
69
72
|
}
|
|
70
73
|
|
|
71
|
-
Content(`Create an instance: \`${
|
|
74
|
+
Content(`Create an instance: \`${eVar} = client.${entity.Name}\`
|
|
72
75
|
|
|
73
76
|
`)
|
|
74
77
|
|
|
@@ -124,7 +127,7 @@ const ReadmeEntity = cmp(function ReadmeEntity(props: any) {
|
|
|
124
127
|
|
|
125
128
|
\`\`\`ruby
|
|
126
129
|
# load returns the bare ${entity.Name} record (raises on error).
|
|
127
|
-
${
|
|
130
|
+
${eVar} = client.${entity.Name}.load(${loadArg})
|
|
128
131
|
\`\`\`
|
|
129
132
|
|
|
130
133
|
`)
|
|
@@ -135,7 +138,7 @@ ${entity.name} = client.${entity.Name}.load(${loadArg})
|
|
|
135
138
|
|
|
136
139
|
\`\`\`ruby
|
|
137
140
|
# list returns an Array of ${entity.Name} records (raises on error).
|
|
138
|
-
${
|
|
141
|
+
${eVar}s = client.${entity.Name}.list
|
|
139
142
|
\`\`\`
|
|
140
143
|
|
|
141
144
|
`)
|
|
@@ -152,7 +155,7 @@ ${entity.name}s = client.${entity.Name}.list
|
|
|
152
155
|
Content(`#### Example: Create
|
|
153
156
|
|
|
154
157
|
\`\`\`ruby
|
|
155
|
-
${
|
|
158
|
+
${eVar} = client.${entity.Name}.create({
|
|
156
159
|
`)
|
|
157
160
|
createItems.map((it: any) => {
|
|
158
161
|
Content(` "${it.name}" => ${rbLit(it.type, 'example_' + it.name)}, # ${canonToType(it.type, target.name)}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { cmp, Content, isAuthActive, envName, canonKey, entityIdField,
|
|
2
|
+
import { cmp, Content, isAuthActive, envName, canonKey, entityIdField, pickExampleEntity, opRequestShape, safeVarName } from '@voxgig/sdkgen'
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
KIT,
|
|
@@ -23,15 +23,20 @@ const ReadmeHowto = cmp(function ReadmeHowto(props: any) {
|
|
|
23
23
|
const { target, ctx$: { model } } = props
|
|
24
24
|
|
|
25
25
|
const entity = getModelPath(model, `main.${KIT}.entity`)
|
|
26
|
-
|
|
26
|
+
// Pick an entity with a real op (prefer a read op) — never fabricate a
|
|
27
|
+
// `load` on an op-less entity like Cloudsmith's `Abort`. primaryOp is null
|
|
28
|
+
// only when NO entity exposes any op (a direct()-only SDK).
|
|
29
|
+
const { entity: exampleEntity, primaryOp } = pickExampleEntity(entity)
|
|
27
30
|
const eName = exampleEntity ? nom(exampleEntity, 'Name') : 'Entity'
|
|
31
|
+
// Sanitise the local variable name — an entity whose lowercased name is a
|
|
32
|
+
// Ruby keyword (e.g. `self`) would otherwise emit uncompilable code. The
|
|
33
|
+
// fixture KEY (`"${eName.toLowerCase()}"`) stays raw — it must match the
|
|
34
|
+
// entity's registered name for the mock lookup to resolve.
|
|
35
|
+
const eVar = safeVarName(eName.toLowerCase(), 'rb')
|
|
28
36
|
// Model-driven id key: null when the entity has no id-like field (a
|
|
29
37
|
// response-wrapped spec). When null the fixture seeds no id and a match op
|
|
30
38
|
// takes no argument.
|
|
31
39
|
const idF = exampleEntity ? entityIdField(exampleEntity) : null
|
|
32
|
-
// Drive the test-mode example off the entity's PRIMARY op — an op it actually
|
|
33
|
-
// exposes — never a hardcoded `load` a create-only entity lacks.
|
|
34
|
-
const primaryOp = exampleEntity ? (entityPrimaryOp(exampleEntity) || 'load') : 'load'
|
|
35
40
|
const isMatchOp = 'load' === primaryOp || 'remove' === primaryOp
|
|
36
41
|
const seedSentence = idF
|
|
37
42
|
? '. Seed fixture\ndata via the `entity` option so offline calls resolve without a live server'
|
|
@@ -52,6 +57,15 @@ const ReadmeHowto = cmp(function ReadmeHowto(props: any) {
|
|
|
52
57
|
testCallArg = `{ ${chosen.map((it: any) => `"${it.name}" => ${rbLit(it.type)}`).join(', ')} }`
|
|
53
58
|
}
|
|
54
59
|
|
|
60
|
+
// The op-driven test-mode line, shown only when the SDK has an entity op.
|
|
61
|
+
// A direct()-only SDK (no ops anywhere) shows a direct() call instead.
|
|
62
|
+
const testModeExample = primaryOp
|
|
63
|
+
? `# Entity ops return the bare mock record (raises on error).
|
|
64
|
+
${eVar} = client.${eName}.${primaryOp}(${testCallArg})
|
|
65
|
+
puts ${eVar}`
|
|
66
|
+
: `result = client.direct({ "path" => "/api/resource", "method" => "GET" })
|
|
67
|
+
puts result`
|
|
68
|
+
|
|
55
69
|
const apikeyEnvLine = isAuthActive(model)
|
|
56
70
|
? `\n${envName(model)}_APIKEY=<your-key>`
|
|
57
71
|
: ''
|
|
@@ -101,9 +115,7 @@ Create a mock client for unit testing — no server required${seedSentence}:
|
|
|
101
115
|
\`\`\`ruby
|
|
102
116
|
client = ${testCtor}
|
|
103
117
|
|
|
104
|
-
|
|
105
|
-
${eName.toLowerCase()} = client.${eName}.${primaryOp}(${testCallArg})
|
|
106
|
-
puts ${eName.toLowerCase()}
|
|
118
|
+
${testModeExample}
|
|
107
119
|
\`\`\`
|
|
108
120
|
|
|
109
121
|
### Use a custom fetch function
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { cmp, each, Content, isAuthActive, envName, canonKey, opRequestShape, entityIdField, entityDataIdField, entityOps } from '@voxgig/sdkgen'
|
|
2
|
+
import { cmp, each, Content, isAuthActive, envName, canonKey, opRequestShape, entityIdField, entityDataIdField, entityOps, safeVarName } from '@voxgig/sdkgen'
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
KIT,
|
|
@@ -43,6 +43,9 @@ client = ${ctor}
|
|
|
43
43
|
if (exampleEntity) {
|
|
44
44
|
const eName = nom(exampleEntity, 'Name')
|
|
45
45
|
const article = /^[aeiou]/i.test(eName) ? "an" : "a"
|
|
46
|
+
// Sanitise the local variable name — an entity whose lowercased name is a
|
|
47
|
+
// Ruby keyword (e.g. `self`) would otherwise emit uncompilable code.
|
|
48
|
+
const eVar = safeVarName(eName.toLowerCase(), 'rb')
|
|
46
49
|
const opnames = entityOps(exampleEntity)
|
|
47
50
|
// Model-driven id keys: `idF` is the load-MATCH key (null when the entity
|
|
48
51
|
// has no id-like field — a response-wrapped spec); when null, load/remove
|
|
@@ -84,8 +87,8 @@ client = ${ctor}
|
|
|
84
87
|
\`\`\`ruby
|
|
85
88
|
begin
|
|
86
89
|
# list returns an Array of ${eName} records — iterate directly.
|
|
87
|
-
${
|
|
88
|
-
${
|
|
90
|
+
${eVar}s = client.${eName}.list
|
|
91
|
+
${eVar}s.each do |item|
|
|
89
92
|
puts "${itemPrint}"
|
|
90
93
|
end
|
|
91
94
|
rescue => err
|
|
@@ -99,7 +102,7 @@ end
|
|
|
99
102
|
if (nestedEntity) {
|
|
100
103
|
const neName = nom(nestedEntity, 'Name')
|
|
101
104
|
const neArticle = /^[aeiou]/i.test(neName) ? "an" : "a"
|
|
102
|
-
const neVar = neName.toLowerCase()
|
|
105
|
+
const neVar = safeVarName(neName.toLowerCase(), 'rb')
|
|
103
106
|
|
|
104
107
|
// Model-driven match: every REQUIRED load-match key — the same shape
|
|
105
108
|
// the runtime resolves path params from, so the example always works.
|
|
@@ -151,8 +154,8 @@ end
|
|
|
151
154
|
\`\`\`ruby
|
|
152
155
|
begin
|
|
153
156
|
# load returns the bare ${eName} record (raises on error).
|
|
154
|
-
${
|
|
155
|
-
puts ${
|
|
157
|
+
${eVar} = client.${eName}.load(${loadArg})
|
|
158
|
+
puts ${eVar}
|
|
156
159
|
rescue => err
|
|
157
160
|
warn "load failed: #{err}"
|
|
158
161
|
end
|