@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, entityIdField,
|
|
2
|
+
import { cmp, Content, entityIdField, pickExampleEntity, opRequestShape, safeVarName } from '@voxgig/sdkgen'
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
KIT,
|
|
@@ -14,17 +14,16 @@ const ReadmeTopTest = cmp(function ReadmeTopTest(props: any) {
|
|
|
14
14
|
const { target, ctx$: { model } } = props
|
|
15
15
|
|
|
16
16
|
const entity = getModelPath(model, `main.${KIT}.entity`)
|
|
17
|
-
|
|
17
|
+
// Pick an entity with a real op (prefer a read op) — never fabricate a
|
|
18
|
+
// `load` on an op-less entity like Cloudsmith's `Abort`.
|
|
19
|
+
const { entity: exampleEntity, primaryOp } = pickExampleEntity(entity)
|
|
18
20
|
|
|
19
21
|
Content(`\`\`\`js
|
|
20
22
|
const client = ${model.const.Name}SDK.test()
|
|
21
23
|
`)
|
|
22
24
|
|
|
23
|
-
if (exampleEntity) {
|
|
25
|
+
if (exampleEntity && primaryOp) {
|
|
24
26
|
const eName = nom(exampleEntity, 'Name')
|
|
25
|
-
// Drive the test-mode example off the entity's PRIMARY op (never a
|
|
26
|
-
// hardcoded `load` a create-only entity lacks).
|
|
27
|
-
const primaryOp = entityPrimaryOp(exampleEntity) || 'load'
|
|
28
27
|
// A list() result is an array — name the variable accordingly.
|
|
29
28
|
const eVar = safeVarName(eName.toLowerCase(), 'js') +
|
|
30
29
|
('list' === primaryOp ? 's' : '')
|
|
@@ -5,7 +5,7 @@ const { ProjectNameEntityBase } = require('../ProjectNameEntityBase')
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
// TODO: needs Entity superclass
|
|
8
|
-
class
|
|
8
|
+
class EntyClass extends ProjectNameEntityBase {
|
|
9
9
|
|
|
10
10
|
constructor(client, entopts) {
|
|
11
11
|
super(client, entopts)
|
|
@@ -16,7 +16,7 @@ class EntityNameEntity extends ProjectNameEntityBase {
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
make() {
|
|
19
|
-
return new
|
|
19
|
+
return new EntyClass(this._client, this.entopts())
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
|
|
@@ -34,5 +34,5 @@ class EntityNameEntity extends ProjectNameEntityBase {
|
|
|
34
34
|
|
|
35
35
|
|
|
36
36
|
module.exports = {
|
|
37
|
-
|
|
37
|
+
EntyClass
|
|
38
38
|
}
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
|
|
8
8
|
const EntityOperation = cmp(function Operation(props: any) {
|
|
9
9
|
const { model } = props.ctx$
|
|
10
|
-
const { ff, opname, entity, entrep } = props
|
|
10
|
+
const { ff, opname, entity, entrep, cls } = props
|
|
11
11
|
|
|
12
12
|
let { indent } = props
|
|
13
13
|
|
|
@@ -29,6 +29,11 @@ const EntityOperation = cmp(function Operation(props: any) {
|
|
|
29
29
|
ProjectName: model.const.Name,
|
|
30
30
|
EntityName: entity.Name,
|
|
31
31
|
entityname: entity.name,
|
|
32
|
+
|
|
33
|
+
// Class receiver token decoupled from the EntityName data-type token in
|
|
34
|
+
// the Entity<Op>Op.fragment.lua files (receiver -> cls).
|
|
35
|
+
EntyClass: cls,
|
|
36
|
+
|
|
32
37
|
'#Feature-Hook': ({ name, indent }: any) =>
|
|
33
38
|
Content({ indent }, `
|
|
34
39
|
u.feature_hook(ctx, "${name}")
|
|
@@ -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_lua'
|
|
10
16
|
|
|
11
17
|
|
|
@@ -13,6 +19,14 @@ 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 is a module-local table (snake-cased file name),
|
|
25
|
+
// so this is used only for the local identifier — kept uniform with the other
|
|
26
|
+
// languages.
|
|
27
|
+
const entityColl = getModelPath(model, `main.${KIT}.entity`)
|
|
28
|
+
const cls = entityClassName(entity, entityColl)
|
|
29
|
+
|
|
16
30
|
const entrep = {
|
|
17
31
|
...stdrep,
|
|
18
32
|
}
|
|
@@ -33,7 +47,7 @@ const Entity = cmp(function Entity(props: any) {
|
|
|
33
47
|
.reduce((a: any, opname: string) =>
|
|
34
48
|
(a['-- #' + camelify(opname) + 'Op'] =
|
|
35
49
|
!opnames.includes(opname) ? '' : ({ indent }: any) => {
|
|
36
|
-
EntityOperation({ ff, opname, indent, entity, entrep })
|
|
50
|
+
EntityOperation({ ff, opname, indent, entity, entrep, cls })
|
|
37
51
|
}, a), {}))
|
|
38
52
|
|
|
39
53
|
Fragment({
|
|
@@ -44,6 +58,10 @@ const Entity = cmp(function Entity(props: any) {
|
|
|
44
58
|
EntityName: entity.Name,
|
|
45
59
|
entityname: entity.name,
|
|
46
60
|
|
|
61
|
+
// Class token decoupled from the EntityName data-type token in
|
|
62
|
+
// Entity.fragment.lua so the class can be renamed independently.
|
|
63
|
+
EntyClass: cls,
|
|
64
|
+
|
|
47
65
|
'#Entity-Hook': ({ name, indent }: any) =>
|
|
48
66
|
Content({ indent }, `utility.feature_hook(ctx, "${name}")`),
|
|
49
67
|
|
|
@@ -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,
|
|
@@ -61,6 +61,9 @@ const ReadmeEntity = cmp(function ReadmeEntity(props: any) {
|
|
|
61
61
|
const fields = entity.fields || []
|
|
62
62
|
// Model-driven id key: null when this entity has no id-like field.
|
|
63
63
|
const idF = entityIdField(entity)
|
|
64
|
+
// Sanitise the local variable name — an entity whose lowercased name is a
|
|
65
|
+
// Lua keyword (e.g. `end`) would otherwise emit uncompilable code.
|
|
66
|
+
const eVar = safeVarName(entity.name, 'lua')
|
|
64
67
|
|
|
65
68
|
Content(`
|
|
66
69
|
### ${entity.Name}
|
|
@@ -73,7 +76,7 @@ const ReadmeEntity = cmp(function ReadmeEntity(props: any) {
|
|
|
73
76
|
`)
|
|
74
77
|
}
|
|
75
78
|
|
|
76
|
-
Content(`Create an instance: \`local ${
|
|
79
|
+
Content(`Create an instance: \`local ${eVar} = client:${entity.Name}(nil)\`
|
|
77
80
|
|
|
78
81
|
`)
|
|
79
82
|
|
|
@@ -128,7 +131,7 @@ const ReadmeEntity = cmp(function ReadmeEntity(props: any) {
|
|
|
128
131
|
Content(`#### Example: Load
|
|
129
132
|
|
|
130
133
|
\`\`\`lua
|
|
131
|
-
local ${
|
|
134
|
+
local ${eVar}, err = client:${entity.Name}():load(${loadArg})
|
|
132
135
|
\`\`\`
|
|
133
136
|
|
|
134
137
|
`)
|
|
@@ -138,7 +141,7 @@ local ${entity.name}, err = client:${entity.Name}():load(${loadArg})
|
|
|
138
141
|
Content(`#### Example: List
|
|
139
142
|
|
|
140
143
|
\`\`\`lua
|
|
141
|
-
local ${
|
|
144
|
+
local ${eVar}s, err = client:${entity.Name}():list()
|
|
142
145
|
\`\`\`
|
|
143
146
|
|
|
144
147
|
`)
|
|
@@ -155,7 +158,7 @@ local ${entity.name}s, err = client:${entity.Name}():list()
|
|
|
155
158
|
Content(`#### Example: Create
|
|
156
159
|
|
|
157
160
|
\`\`\`lua
|
|
158
|
-
local ${
|
|
161
|
+
local ${eVar}, err = client:${entity.Name}():create({
|
|
159
162
|
`)
|
|
160
163
|
createItems.map((it: any) => {
|
|
161
164
|
Content(` ${luaKey(it.name)} = ${luaLit(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 } from '@voxgig/sdkgen'
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
KIT,
|
|
@@ -26,13 +26,13 @@ const ReadmeHowto = cmp(function ReadmeHowto(props: any) {
|
|
|
26
26
|
const { target, ctx$: { model } } = props
|
|
27
27
|
|
|
28
28
|
const entity = getModelPath(model, `main.${KIT}.entity`)
|
|
29
|
-
|
|
29
|
+
// Pick an entity with a real op (prefer a read op) — never fabricate a
|
|
30
|
+
// `load` on an op-less entity like Cloudsmith's `Abort`. primaryOp is null
|
|
31
|
+
// only when NO entity exposes any op (a direct()-only SDK).
|
|
32
|
+
const { entity: exampleEntity, primaryOp } = pickExampleEntity(entity)
|
|
30
33
|
const eName = exampleEntity ? nom(exampleEntity, 'Name') : 'Entity'
|
|
31
34
|
// Model-driven id key: null when the entity has no id-like field.
|
|
32
35
|
const idF = exampleEntity ? entityIdField(exampleEntity) : null
|
|
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 = exampleEntity ? (entityPrimaryOp(exampleEntity) || 'load') : 'load'
|
|
36
36
|
const isMatchOp = 'load' === primaryOp || 'remove' === primaryOp
|
|
37
37
|
let testCallArg = ''
|
|
38
38
|
if (exampleEntity && isMatchOp) {
|
|
@@ -45,6 +45,14 @@ const ReadmeHowto = cmp(function ReadmeHowto(props: any) {
|
|
|
45
45
|
testCallArg = `{ ${chosen.map((it: any) => `${luaKey(it.name)} = ${luaLit(it.type)}`).join(', ')} }`
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
// The op-driven test-mode line, shown only when the SDK has an entity op.
|
|
49
|
+
// A direct()-only SDK (no ops anywhere) shows a direct() call instead.
|
|
50
|
+
const testModeExample = primaryOp
|
|
51
|
+
? `local result, err = client:${eName}():${primaryOp}(${testCallArg})
|
|
52
|
+
-- result is the returned data; err is set on failure`
|
|
53
|
+
: `local result, err = client:direct({ path = "/api/resource", method = "GET" })
|
|
54
|
+
-- result is the returned data; err is set on failure`
|
|
55
|
+
|
|
48
56
|
const apikeyEnvLine = isAuthActive(model)
|
|
49
57
|
? `\n${envName(model)}_APIKEY=<your-key>`
|
|
50
58
|
: ''
|
|
@@ -89,8 +97,7 @@ Create a mock client for unit testing — no server required:
|
|
|
89
97
|
\`\`\`lua
|
|
90
98
|
local client = sdk.test()
|
|
91
99
|
|
|
92
|
-
|
|
93
|
-
-- result is the returned data; err is set on failure
|
|
100
|
+
${testModeExample}
|
|
94
101
|
\`\`\`
|
|
95
102
|
|
|
96
103
|
### 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 @@ local 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
|
+
// Lua keyword (e.g. `end`) would otherwise emit uncompilable code.
|
|
48
|
+
const eVar = safeVarName(eName.toLowerCase(), 'lua')
|
|
46
49
|
const opnames = entityOps(exampleEntity)
|
|
47
50
|
// Model-driven id key: `idF` is the MATCH key (null when none). `dataIdF`
|
|
48
51
|
// is the id on the RETURNED record's data type — an entity can key its match
|
|
@@ -91,10 +94,10 @@ Entity operations return \`(value, err)\`. For \`list\`, \`value\` is the
|
|
|
91
94
|
array of records itself — iterate it directly (there is no wrapper).
|
|
92
95
|
|
|
93
96
|
\`\`\`lua
|
|
94
|
-
local ${
|
|
97
|
+
local ${eVar}s, err = client:${eName}():list()
|
|
95
98
|
if err then error(err) end
|
|
96
99
|
|
|
97
|
-
for _, item in ipairs(${
|
|
100
|
+
for _, item in ipairs(${eVar}s) do
|
|
98
101
|
${printLine}
|
|
99
102
|
end
|
|
100
103
|
\`\`\`
|
|
@@ -105,7 +108,7 @@ end
|
|
|
105
108
|
if (nestedEntity) {
|
|
106
109
|
const neName = nom(nestedEntity, 'Name')
|
|
107
110
|
const neArticle = /^[aeiou]/i.test(neName) ? "an" : "a"
|
|
108
|
-
const neVar = neName.toLowerCase()
|
|
111
|
+
const neVar = safeVarName(neName.toLowerCase(), 'lua')
|
|
109
112
|
|
|
110
113
|
// Model-driven match: every REQUIRED load-match key — the same shape
|
|
111
114
|
// the runtime resolves path params from, so the example always works.
|
|
@@ -151,9 +154,9 @@ print(${neVar})
|
|
|
151
154
|
Content(`### 3. Load ${article} ${eName.toLowerCase()}
|
|
152
155
|
|
|
153
156
|
\`\`\`lua
|
|
154
|
-
local ${
|
|
157
|
+
local ${eVar}, err = client:${eName}():load(${loadArg})
|
|
155
158
|
if err then error(err) end
|
|
156
|
-
print(${
|
|
159
|
+
print(${eVar})
|
|
157
160
|
\`\`\`
|
|
158
161
|
|
|
159
162
|
`)
|
|
@@ -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,
|
|
@@ -174,6 +174,9 @@ same parameters as \`direct()\`.
|
|
|
174
174
|
// Model-driven id key: null when this entity has no id-like field, in
|
|
175
175
|
// which case load/remove match on no argument and update omits the id.
|
|
176
176
|
const idF = entityIdField(ent)
|
|
177
|
+
// Sanitise the local variable name — an entity whose lowercased name is
|
|
178
|
+
// a Lua keyword (e.g. `end`) would otherwise emit uncompilable code.
|
|
179
|
+
const eVar = safeVarName(ent.name, 'lua')
|
|
177
180
|
|
|
178
181
|
Content(`
|
|
179
182
|
---
|
|
@@ -189,7 +192,7 @@ same parameters as \`direct()\`.
|
|
|
189
192
|
}
|
|
190
193
|
|
|
191
194
|
Content(`\`\`\`lua
|
|
192
|
-
local ${
|
|
195
|
+
local ${eVar} = client:${ent.Name}(nil)
|
|
193
196
|
\`\`\`
|
|
194
197
|
|
|
195
198
|
`)
|
|
@@ -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,
|
|
@@ -46,6 +46,9 @@ local client = ${ctor}
|
|
|
46
46
|
|
|
47
47
|
if (exampleEntity) {
|
|
48
48
|
const eName = nom(exampleEntity, 'Name')
|
|
49
|
+
// Sanitise the local variable name — an entity whose lowercased name is a
|
|
50
|
+
// Lua keyword (e.g. `end`) would otherwise emit uncompilable code.
|
|
51
|
+
const eVar = safeVarName(eName.toLowerCase(), 'lua')
|
|
49
52
|
const opnames = Object.keys(exampleEntity.op || {})
|
|
50
53
|
// Model-driven id key: null when the entity has no id-like field, in which
|
|
51
54
|
// case the load example takes no match argument.
|
|
@@ -55,8 +58,8 @@ local client = ${ctor}
|
|
|
55
58
|
|
|
56
59
|
if (opnames.includes('list')) {
|
|
57
60
|
Content(`-- List all ${eName.toLowerCase()}s
|
|
58
|
-
local ${
|
|
59
|
-
print(${
|
|
61
|
+
local ${eVar}s, err = client:${eName}():list()
|
|
62
|
+
print(${eVar}s)
|
|
60
63
|
`)
|
|
61
64
|
hasCall = true
|
|
62
65
|
}
|
|
@@ -76,8 +79,8 @@ print(${eName.toLowerCase()}s)
|
|
|
76
79
|
: ''
|
|
77
80
|
Content(`
|
|
78
81
|
-- Load a specific ${eName.toLowerCase()}
|
|
79
|
-
local ${
|
|
80
|
-
print(${
|
|
82
|
+
local ${eVar}, err = client:${eName}():load(${loadArg})
|
|
83
|
+
print(${eVar})
|
|
81
84
|
`)
|
|
82
85
|
hasCall = true
|
|
83
86
|
}
|
|
@@ -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,
|
|
@@ -27,18 +27,17 @@ const ReadmeTopTest = cmp(function ReadmeTopTest(props: any) {
|
|
|
27
27
|
|
|
28
28
|
const entity = getModelPath(model, `main.${KIT}.entity`)
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
// Pick an entity with a real op (prefer a read op) — never fabricate a
|
|
31
|
+
// `load` on an op-less entity like Cloudsmith's `Abort`.
|
|
32
|
+
const { entity: exampleEntity, primaryOp } = pickExampleEntity(entity)
|
|
31
33
|
|
|
32
34
|
Content(`\`\`\`lua
|
|
33
35
|
local client = sdk.test()
|
|
34
36
|
`)
|
|
35
37
|
|
|
36
|
-
if (exampleEntity) {
|
|
38
|
+
if (exampleEntity && primaryOp) {
|
|
37
39
|
const eName = nom(exampleEntity, 'Name')
|
|
38
|
-
// Drive the test-mode example off the entity's PRIMARY op (never a hardcoded
|
|
39
|
-
// `load` a create-only entity lacks).
|
|
40
40
|
const idF = entityIdField(exampleEntity)
|
|
41
|
-
const primaryOp = entityPrimaryOp(exampleEntity) || 'load'
|
|
42
41
|
const isMatchOp = 'load' === primaryOp || 'remove' === primaryOp
|
|
43
42
|
let arg = ''
|
|
44
43
|
if (isMatchOp) {
|
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
local vs = require("utility.struct.struct")
|
|
4
4
|
local helpers = require("core.helpers")
|
|
5
5
|
|
|
6
|
-
local
|
|
7
|
-
|
|
6
|
+
local EntyClass = {}
|
|
7
|
+
EntyClass.__index = EntyClass
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
function
|
|
10
|
+
function EntyClass.new(client, entopts)
|
|
11
11
|
entopts = entopts or {}
|
|
12
12
|
if entopts["active"] == nil then
|
|
13
13
|
entopts["active"] = true
|
|
@@ -17,7 +17,7 @@ function EntityNameEntity.new(client, entopts)
|
|
|
17
17
|
entopts["active"] = true
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
local self = setmetatable({},
|
|
20
|
+
local self = setmetatable({}, EntyClass)
|
|
21
21
|
self._name = "entityname"
|
|
22
22
|
self._client = client
|
|
23
23
|
self._utility = client:get_utility()
|
|
@@ -36,21 +36,21 @@ function EntityNameEntity.new(client, entopts)
|
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
|
|
39
|
-
function
|
|
39
|
+
function EntyClass:get_name()
|
|
40
40
|
return self._name
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
|
|
44
|
-
function
|
|
44
|
+
function EntyClass:make()
|
|
45
45
|
local opts = {}
|
|
46
46
|
for k, v in pairs(self._entopts) do
|
|
47
47
|
opts[k] = v
|
|
48
48
|
end
|
|
49
|
-
return
|
|
49
|
+
return EntyClass.new(self._client, opts)
|
|
50
50
|
end
|
|
51
51
|
|
|
52
52
|
|
|
53
|
-
function
|
|
53
|
+
function EntyClass:data_set(args)
|
|
54
54
|
if args ~= nil then
|
|
55
55
|
self._data = helpers.to_map(vs.clone(args)) or {}
|
|
56
56
|
self._utility.feature_hook(self._entctx, "SetData")
|
|
@@ -58,13 +58,13 @@ function EntityNameEntity:data_set(args)
|
|
|
58
58
|
end
|
|
59
59
|
|
|
60
60
|
|
|
61
|
-
function
|
|
61
|
+
function EntyClass:data_get()
|
|
62
62
|
self._utility.feature_hook(self._entctx, "GetData")
|
|
63
63
|
return vs.clone(self._data)
|
|
64
64
|
end
|
|
65
65
|
|
|
66
66
|
|
|
67
|
-
function
|
|
67
|
+
function EntyClass:match_set(args)
|
|
68
68
|
if args ~= nil then
|
|
69
69
|
self._match = helpers.to_map(vs.clone(args)) or {}
|
|
70
70
|
self._utility.feature_hook(self._entctx, "SetMatch")
|
|
@@ -72,7 +72,7 @@ function EntityNameEntity:match_set(args)
|
|
|
72
72
|
end
|
|
73
73
|
|
|
74
74
|
|
|
75
|
-
function
|
|
75
|
+
function EntyClass:match_get()
|
|
76
76
|
self._utility.feature_hook(self._entctx, "GetMatch")
|
|
77
77
|
return vs.clone(self._match)
|
|
78
78
|
end
|
|
@@ -89,7 +89,7 @@ end
|
|
|
89
89
|
-- #RemoveOp
|
|
90
90
|
|
|
91
91
|
|
|
92
|
-
function
|
|
92
|
+
function EntyClass:_run_op(ctx, post_done)
|
|
93
93
|
local utility = self._utility
|
|
94
94
|
|
|
95
95
|
-- #PrePoint-Hook
|
|
@@ -144,4 +144,4 @@ function EntityNameEntity:_run_op(ctx, post_done)
|
|
|
144
144
|
end
|
|
145
145
|
|
|
146
146
|
|
|
147
|
-
return
|
|
147
|
+
return EntyClass
|
|
@@ -9,7 +9,7 @@ local entityCreateOp = {}
|
|
|
9
9
|
---@param ctrl? table
|
|
10
10
|
---@return EntityName
|
|
11
11
|
---@return string? err
|
|
12
|
-
function
|
|
12
|
+
function EntyClass:create(reqdata, ctrl)
|
|
13
13
|
local utility = self._utility
|
|
14
14
|
local ctx = utility.make_context({
|
|
15
15
|
opname = "create",
|
|
@@ -6,7 +6,7 @@ local entityListOp = {}
|
|
|
6
6
|
---@param ctrl? table
|
|
7
7
|
---@return EntityName[]
|
|
8
8
|
---@return string? err
|
|
9
|
-
function
|
|
9
|
+
function EntyClass:list(reqmatch, ctrl)
|
|
10
10
|
local utility = self._utility
|
|
11
11
|
local ctx = utility.make_context({
|
|
12
12
|
opname = "list",
|
|
@@ -9,7 +9,7 @@ local entityLoadOp = {}
|
|
|
9
9
|
---@param ctrl? table
|
|
10
10
|
---@return EntityName
|
|
11
11
|
---@return string? err
|
|
12
|
-
function
|
|
12
|
+
function EntyClass:load(reqmatch, ctrl)
|
|
13
13
|
local utility = self._utility
|
|
14
14
|
local ctx = utility.make_context({
|
|
15
15
|
opname = "load",
|
|
@@ -9,7 +9,7 @@ local entityRemoveOp = {}
|
|
|
9
9
|
---@param ctrl? table
|
|
10
10
|
---@return EntityName
|
|
11
11
|
---@return string? err
|
|
12
|
-
function
|
|
12
|
+
function EntyClass:remove(reqmatch, ctrl)
|
|
13
13
|
local utility = self._utility
|
|
14
14
|
local ctx = utility.make_context({
|
|
15
15
|
opname = "remove",
|
|
@@ -9,7 +9,7 @@ local entityUpdateOp = {}
|
|
|
9
9
|
---@param ctrl? table
|
|
10
10
|
---@return EntityName
|
|
11
11
|
---@return string? err
|
|
12
|
-
function
|
|
12
|
+
function EntyClass:update(reqdata, ctrl)
|
|
13
13
|
local utility = self._utility
|
|
14
14
|
local ctx = utility.make_context({
|
|
15
15
|
opname = "update",
|
|
@@ -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_php'
|
|
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.php 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)($ctx, "${name}");`),
|
|
49
65
|
|
|
@@ -1,6 +1,11 @@
|
|
|
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
|
// Reserved PHP method names on the SDK class that an entity accessor must
|
|
@@ -13,6 +18,10 @@ const MainEntity = cmp(async function MainEntity(props: any) {
|
|
|
13
18
|
const { entity } = props
|
|
14
19
|
const { model } = props.ctx$
|
|
15
20
|
|
|
21
|
+
// Collision-free entity CLASS name (entityClassName); the accessor METHOD name
|
|
22
|
+
// (below) is unchanged so callers still write $client->${entity.Name}().
|
|
23
|
+
const cls = entityClassName(entity, getModelPath(model, `main.${KIT}.entity`))
|
|
24
|
+
|
|
16
25
|
// Canonical facade method name is the PascalCase entity Name
|
|
17
26
|
// (`$client->${entity.Name}()`). PHP method names are case-insensitive, so
|
|
18
27
|
// the lowercase spelling `$client->${entity.name}()` still resolves here as
|
|
@@ -34,11 +43,11 @@ const MainEntity = cmp(async function MainEntity(props: any) {
|
|
|
34
43
|
require_once __DIR__ . '/entity/${entity.name}_entity.php';
|
|
35
44
|
if ($data === null) {
|
|
36
45
|
if ($this->_${entity.name} === null) {
|
|
37
|
-
$this->_${entity.name} = new ${
|
|
46
|
+
$this->_${entity.name} = new ${cls}($this, null);
|
|
38
47
|
}
|
|
39
48
|
return $this->_${entity.name};
|
|
40
49
|
}
|
|
41
|
-
return new ${
|
|
50
|
+
return new ${cls}($this, $data);
|
|
42
51
|
}
|
|
43
52
|
|
|
44
53
|
`)
|
|
@@ -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 { KIT, getModelPath, nom } from '@voxgig/apidef'
|
|
5
5
|
|
|
@@ -18,13 +18,13 @@ const ReadmeHowto = cmp(function ReadmeHowto(props: any) {
|
|
|
18
18
|
const { target, ctx$: { model } } = props
|
|
19
19
|
|
|
20
20
|
const entity = getModelPath(model, `main.${KIT}.entity`)
|
|
21
|
-
|
|
21
|
+
// Pick an entity with a real op (prefer a read op) — never fabricate a
|
|
22
|
+
// `load` on an op-less entity like Cloudsmith's `Abort`. primaryOp is null
|
|
23
|
+
// only when NO entity exposes any op (a direct()-only SDK).
|
|
24
|
+
const { entity: exampleEntity, primaryOp } = pickExampleEntity(entity)
|
|
22
25
|
const eName = exampleEntity ? nom(exampleEntity, 'Name') : 'Entity'
|
|
23
26
|
// Model-driven id key: null when the entity has no id-like field.
|
|
24
27
|
const idF = exampleEntity ? entityIdField(exampleEntity) : null
|
|
25
|
-
// Drive the test-mode example off the entity's PRIMARY op — never a hardcoded
|
|
26
|
-
// `load` a create-only entity lacks.
|
|
27
|
-
const primaryOp = exampleEntity ? (entityPrimaryOp(exampleEntity) || 'load') : 'load'
|
|
28
28
|
const isMatchOp = 'load' === primaryOp || 'remove' === primaryOp
|
|
29
29
|
const seedSentence = idF
|
|
30
30
|
? '. Seed fixture\ndata via the `entity` option so offline calls resolve without a live server'
|
|
@@ -43,6 +43,15 @@ const ReadmeHowto = cmp(function ReadmeHowto(props: any) {
|
|
|
43
43
|
testCallArg = `[${chosen.map((it: any) => `"${it.name}" => ${phpLit(it.type)}`).join(', ')}]`
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
// The op-driven test-mode line, shown only when the SDK has an entity op.
|
|
47
|
+
// A direct()-only SDK (no ops anywhere) shows a direct() call instead.
|
|
48
|
+
const testModeExample = primaryOp
|
|
49
|
+
? `// Entity ops return the bare mock record (throws on error).
|
|
50
|
+
$${eName.toLowerCase()} = $client->${eName}()->${primaryOp}(${testCallArg});
|
|
51
|
+
print_r($${eName.toLowerCase()});`
|
|
52
|
+
: `$result = $client->direct(["path" => "/api/resource", "method" => "GET"]);
|
|
53
|
+
print_r($result);`
|
|
54
|
+
|
|
46
55
|
const apikeyEnvLine = isAuthActive(model)
|
|
47
56
|
? `\n${envName(model)}_APIKEY=<your-key>`
|
|
48
57
|
: ''
|
|
@@ -93,9 +102,7 @@ Create a mock client for unit testing — no server required${seedSentence}:
|
|
|
93
102
|
\`\`\`php
|
|
94
103
|
$client = ${testCtor};
|
|
95
104
|
|
|
96
|
-
|
|
97
|
-
$${eName.toLowerCase()} = $client->${eName}()->${primaryOp}(${testCallArg});
|
|
98
|
-
print_r($${eName.toLowerCase()});
|
|
105
|
+
${testModeExample}
|
|
99
106
|
\`\`\`
|
|
100
107
|
|
|
101
108
|
### Use a custom fetch function
|