@voxgig/sdkgen 1.3.1 → 1.3.4
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/cmp/ReadmeTop.js +24 -2
- package/dist/cmp/ReadmeTop.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/go/utility/prepare_auth.go +6 -1
- package/project/.sdk/tm/js/src/utility/PrepareAuthUtility.js +3 -1
- package/project/.sdk/tm/lua/utility/prepare_auth.lua +6 -1
- package/project/.sdk/tm/php/utility/PrepareAuth.php +3 -1
- package/project/.sdk/tm/py/test/test_struct_utility.py +1 -1
- package/project/.sdk/tm/py/utility/prepare_auth.py +4 -1
- package/project/.sdk/tm/rb/Makefile +3 -1
- package/project/.sdk/tm/rb/utility/prepare_auth.rb +3 -1
- package/project/.sdk/tm/ts/src/utility/PrepareAuthUtility.ts +5 -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/cmp/ReadmeTop.ts +25 -2
- 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,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { cmp, each, Content, canonToType, canonKey, entityIdField } from '@voxgig/sdkgen'
|
|
2
|
+
import { cmp, each, Content, canonToType, canonKey, entityIdField, opRequestShape } from '@voxgig/sdkgen'
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
KIT,
|
|
@@ -9,13 +9,14 @@ import {
|
|
|
9
9
|
|
|
10
10
|
// A type-correct Ruby literal for a field's canonical type — the create body
|
|
11
11
|
// is EXECUTED by the doc test, so it must carry a real value per field.
|
|
12
|
-
|
|
12
|
+
// Strings render the quoted placeholder.
|
|
13
|
+
function rbLit(type: any, placeholder: string = 'example'): string {
|
|
13
14
|
const k = canonKey(type)
|
|
14
15
|
if ('INTEGER' === k || 'NUMBER' === k) return '1'
|
|
15
16
|
if ('BOOLEAN' === k) return 'true'
|
|
16
17
|
if ('ARRAY' === k) return '[]'
|
|
17
18
|
if ('OBJECT' === k) return '{}'
|
|
18
|
-
return
|
|
19
|
+
return `"${placeholder}"`
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
|
|
@@ -107,11 +108,23 @@ const ReadmeEntity = cmp(function ReadmeEntity(props: any) {
|
|
|
107
108
|
}
|
|
108
109
|
|
|
109
110
|
if (opnames.includes('load')) {
|
|
111
|
+
// The id key plus every REQUIRED match key (parent path params like
|
|
112
|
+
// page_id) — the same shape the runtime resolves path params from, so
|
|
113
|
+
// the example always works.
|
|
114
|
+
const loadItems = opRequestShape(entity, 'load').items
|
|
115
|
+
.filter((it: any) => !it.optional || it.name === idF)
|
|
116
|
+
.sort((a: any, b: any) =>
|
|
117
|
+
(a.name === idF ? 0 : 1) - (b.name === idF ? 0 : 1))
|
|
118
|
+
const loadArg = 0 < loadItems.length
|
|
119
|
+
? `{ ${loadItems.map((it: any) =>
|
|
120
|
+
`"${it.name}" => ${rbLit(it.type,
|
|
121
|
+
it.name === idF ? entity.name + '_id' : it.name)}`).join(', ')} }`
|
|
122
|
+
: ''
|
|
110
123
|
Content(`#### Example: Load
|
|
111
124
|
|
|
112
125
|
\`\`\`ruby
|
|
113
126
|
# load returns the bare ${entity.Name} record (raises on error).
|
|
114
|
-
${entity.name} = client.${entity.Name}.load(${
|
|
127
|
+
${entity.name} = client.${entity.Name}.load(${loadArg})
|
|
115
128
|
\`\`\`
|
|
116
129
|
|
|
117
130
|
`)
|
|
@@ -129,16 +142,21 @@ ${entity.name}s = client.${entity.Name}.list
|
|
|
129
142
|
}
|
|
130
143
|
|
|
131
144
|
if (opnames.includes('create')) {
|
|
145
|
+
// Members come from the SAME shape the runtime validates
|
|
146
|
+
// (opRequestShape): every required member must appear — including a
|
|
147
|
+
// required id and parent keys like page_id — with a real, executable
|
|
148
|
+
// literal (the doc test RUNS this block, so a comment placeholder
|
|
149
|
+
// would break it).
|
|
150
|
+
const createItems = opRequestShape(entity, 'create').items
|
|
151
|
+
.filter((it: any) => !it.optional)
|
|
132
152
|
Content(`#### Example: Create
|
|
133
153
|
|
|
134
154
|
\`\`\`ruby
|
|
135
155
|
${entity.name} = client.${entity.Name}.create({
|
|
136
156
|
`)
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
Content(` "${field.name}" => ${rbLit(field.type)}, # ${canonToType(field.type, target.name)}
|
|
157
|
+
createItems.map((it: any) => {
|
|
158
|
+
Content(` "${it.name}" => ${rbLit(it.type, 'example_' + it.name)}, # ${canonToType(it.type, target.name)}
|
|
140
159
|
`)
|
|
141
|
-
}
|
|
142
160
|
})
|
|
143
161
|
Content(`})
|
|
144
162
|
\`\`\`
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { cmp, each, Content, isAuthActive, envName, canonKey, opRequestShape, entityIdField, entityDataIdField } from '@voxgig/sdkgen'
|
|
2
|
+
import { cmp, each, Content, isAuthActive, envName, canonKey, opRequestShape, entityIdField, entityDataIdField, entityOps } from '@voxgig/sdkgen'
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
KIT,
|
|
@@ -14,8 +14,16 @@ const ReadmeQuick = cmp(function ReadmeQuick(props: any) {
|
|
|
14
14
|
const entity = getModelPath(model, `main.${KIT}.entity`)
|
|
15
15
|
|
|
16
16
|
const exampleEntity = Object.values(entity).find((e: any) => e.active !== false) as any
|
|
17
|
+
|
|
18
|
+
// Find a nested entity if available: one with a parent chain
|
|
19
|
+
// (relations.ancestors), an active load op, and a required non-id load
|
|
20
|
+
// param to demonstrate (the parent key, e.g. page_id).
|
|
17
21
|
const nestedEntity = Object.values(entity).find((e: any) =>
|
|
18
|
-
e.active !== false &&
|
|
22
|
+
e.active !== false &&
|
|
23
|
+
e.relations && e.relations.ancestors && 0 < e.relations.ancestors.length &&
|
|
24
|
+
entityOps(e).includes('load') &&
|
|
25
|
+
opRequestShape(e, 'load').items.some((it: any) =>
|
|
26
|
+
!it.optional && it.name !== entityIdField(e))
|
|
19
27
|
) as any
|
|
20
28
|
|
|
21
29
|
const ctor = isAuthActive(model)
|
|
@@ -35,7 +43,7 @@ client = ${ctor}
|
|
|
35
43
|
if (exampleEntity) {
|
|
36
44
|
const eName = nom(exampleEntity, 'Name')
|
|
37
45
|
const article = /^[aeiou]/i.test(eName) ? "an" : "a"
|
|
38
|
-
const opnames =
|
|
46
|
+
const opnames = entityOps(exampleEntity)
|
|
39
47
|
// Model-driven id keys: `idF` is the load-MATCH key (null when the entity
|
|
40
48
|
// has no id-like field — a response-wrapped spec); when null, load/remove
|
|
41
49
|
// take no argument. `dataIdF` is the id on the RETURNED record's data type —
|
|
@@ -45,6 +53,19 @@ client = ${ctor}
|
|
|
45
53
|
const idF = entityIdField(exampleEntity)
|
|
46
54
|
const dataIdF = entityDataIdField(exampleEntity)
|
|
47
55
|
|
|
56
|
+
// A type-correct, executable Ruby literal for a param: numeric/boolean/
|
|
57
|
+
// array/hash params render a typed literal; strings render the quoted
|
|
58
|
+
// placeholder (the doc test EXECUTES these blocks, so a comment
|
|
59
|
+
// placeholder would not parse).
|
|
60
|
+
const rbLit = (type: any, placeholder: string = 'example'): string => {
|
|
61
|
+
const k = canonKey(type)
|
|
62
|
+
if ('INTEGER' === k || 'NUMBER' === k) return '1'
|
|
63
|
+
if ('BOOLEAN' === k) return 'true'
|
|
64
|
+
if ('ARRAY' === k) return '[]'
|
|
65
|
+
if ('OBJECT' === k) return '{}'
|
|
66
|
+
return `"${placeholder}"`
|
|
67
|
+
}
|
|
68
|
+
|
|
48
69
|
// Model-driven display field: the entity's first non-id string field
|
|
49
70
|
// (falling back to any non-id field), so the list example prints a real
|
|
50
71
|
// column instead of a hardcoded "name" the entity may not have.
|
|
@@ -75,13 +96,62 @@ end
|
|
|
75
96
|
`)
|
|
76
97
|
}
|
|
77
98
|
|
|
78
|
-
if (
|
|
99
|
+
if (nestedEntity) {
|
|
100
|
+
const neName = nom(nestedEntity, 'Name')
|
|
101
|
+
const neArticle = /^[aeiou]/i.test(neName) ? "an" : "a"
|
|
102
|
+
const neVar = neName.toLowerCase()
|
|
103
|
+
|
|
104
|
+
// Model-driven match: every REQUIRED load-match key — the same shape
|
|
105
|
+
// the runtime resolves path params from, so the example always works.
|
|
106
|
+
// Parent keys (e.g. page_id) first, the entity's own id last.
|
|
107
|
+
const neIdF = entityIdField(nestedEntity)
|
|
108
|
+
const neRequired = opRequestShape(nestedEntity, 'load').items
|
|
109
|
+
.filter((it: any) => !it.optional)
|
|
110
|
+
.sort((a: any, b: any) =>
|
|
111
|
+
(a.name === neIdF ? 1 : 0) - (b.name === neIdF ? 1 : 0))
|
|
112
|
+
const parentItem = neRequired.find((it: any) => it.name !== neIdF) as any
|
|
113
|
+
const parentParam = parentItem && parentItem.name
|
|
114
|
+
const parentName = parentParam ? parentParam.replace(/_id$/, '') : 'its parent'
|
|
115
|
+
const neMatch = neRequired.map((it: any) =>
|
|
116
|
+
`"${it.name}" => ${rbLit(it.type,
|
|
117
|
+
it.name === neIdF ? 'example_id' : 'example_' + it.name)}`)
|
|
118
|
+
|
|
119
|
+
Content(`### 3. Load ${neArticle} ${neName.toLowerCase()}
|
|
120
|
+
|
|
121
|
+
${neName} is nested under ${parentName}, so provide the \`${parentParam}\`.
|
|
122
|
+
|
|
123
|
+
\`\`\`ruby
|
|
124
|
+
begin
|
|
125
|
+
# load returns the bare ${neName} record (raises on error).
|
|
126
|
+
${neVar} = client.${neName}.load({ ${neMatch.join(', ')} })
|
|
127
|
+
puts ${neVar}
|
|
128
|
+
rescue => err
|
|
129
|
+
warn "load failed: #{err}"
|
|
130
|
+
end
|
|
131
|
+
\`\`\`
|
|
132
|
+
|
|
133
|
+
`)
|
|
134
|
+
}
|
|
135
|
+
else if (opnames.includes('load')) {
|
|
136
|
+
// Every REQUIRED load-match key (id first, then parent path params like
|
|
137
|
+
// page_id) — the same shape the runtime resolves path params from, so
|
|
138
|
+
// the example always works.
|
|
139
|
+
const loadRequired = opRequestShape(exampleEntity, 'load').items
|
|
140
|
+
.filter((it: any) => !it.optional || it.name === idF)
|
|
141
|
+
.sort((a: any, b: any) =>
|
|
142
|
+
(a.name === idF ? 0 : 1) - (b.name === idF ? 0 : 1))
|
|
143
|
+
const loadArg = 0 < loadRequired.length
|
|
144
|
+
? `{ ${loadRequired.map((it: any) =>
|
|
145
|
+
`"${it.name}" => ${rbLit(it.type,
|
|
146
|
+
it.name === idF ? 'example_id' : 'example_' + it.name)}`).join(', ')} }`
|
|
147
|
+
: ''
|
|
148
|
+
|
|
79
149
|
Content(`### 3. Load ${article} ${eName.toLowerCase()}
|
|
80
150
|
|
|
81
151
|
\`\`\`ruby
|
|
82
152
|
begin
|
|
83
153
|
# load returns the bare ${eName} record (raises on error).
|
|
84
|
-
${eName.toLowerCase()} = client.${eName}.load(${
|
|
154
|
+
${eName.toLowerCase()} = client.${eName}.load(${loadArg})
|
|
85
155
|
puts ${eName.toLowerCase()}
|
|
86
156
|
rescue => err
|
|
87
157
|
warn "load failed: #{err}"
|
|
@@ -94,25 +164,21 @@ end
|
|
|
94
164
|
// Model-driven example fields: derive the create/update body from the op
|
|
95
165
|
// shape (opRequestShape) so the docs reference REAL writable fields, not a
|
|
96
166
|
// hardcoded "name" the entity may not have. Literals are Ruby-typed by the
|
|
97
|
-
// field's canonical type.
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const k = canonKey(type)
|
|
101
|
-
if ('INTEGER' === k || 'NUMBER' === k) return '1'
|
|
102
|
-
if ('BOOLEAN' === k) return 'true'
|
|
103
|
-
if ('ARRAY' === k) return '[]'
|
|
104
|
-
if ('OBJECT' === k) return '{}'
|
|
105
|
-
return '"example"'
|
|
106
|
-
}
|
|
167
|
+
// field's canonical type. ids are rendered separately as the match key for
|
|
168
|
+
// update/remove; a REQUIRED create id stays (the call is invalid without
|
|
169
|
+
// it).
|
|
107
170
|
const examplePairs = (opname: string): string[] => {
|
|
108
171
|
const items = opRequestShape(exampleEntity, opname).items
|
|
109
|
-
.filter((it: any) => it.name !==
|
|
172
|
+
.filter((it: any) => (it.name !== idF && it.name !== 'id') ||
|
|
173
|
+
('create' === opname && !it.optional))
|
|
110
174
|
const required = items.filter((it: any) => !it.optional)
|
|
111
|
-
|
|
175
|
+
const optional = items.filter((it: any) => it.optional)
|
|
176
|
+
// create needs ALL required fields; update is a patch, so the required
|
|
177
|
+
// members plus a sample optional field or two suffice.
|
|
112
178
|
const chosen = 'create' === opname
|
|
113
179
|
? (required.length ? required : items.slice(0, 2))
|
|
114
|
-
:
|
|
115
|
-
return chosen.map((it: any) => `"${it.name}" => ${rbLit(it.type)}`)
|
|
180
|
+
: required.concat(optional).slice(0, Math.max(2, required.length))
|
|
181
|
+
return chosen.map((it: any) => `"${it.name}" => ${rbLit(it.type, 'example_' + it.name)}`)
|
|
116
182
|
}
|
|
117
183
|
|
|
118
184
|
// The id VALUE for an update/remove match: off the returned `created`
|
|
@@ -125,7 +191,7 @@ end
|
|
|
125
191
|
}
|
|
126
192
|
const idValueFor = (opname: string): string => (null != dataIdF && opnames.includes('create'))
|
|
127
193
|
? `created["${dataIdF}"]`
|
|
128
|
-
: rbLit(idParamType(opname))
|
|
194
|
+
: rbLit(idParamType(opname), 'example_id')
|
|
129
195
|
|
|
130
196
|
if (opnames.includes('create') || opnames.includes('update') || opnames.includes('remove')) {
|
|
131
197
|
Content(`### 4. Create, update, and remove
|
|
@@ -147,8 +213,17 @@ client.${eName}.update({ ${updatePairs.join(', ')} })
|
|
|
147
213
|
`)
|
|
148
214
|
}
|
|
149
215
|
if (opnames.includes('remove')) {
|
|
216
|
+
// Every REQUIRED remove-match key: the id (off the created record
|
|
217
|
+
// when possible) plus parent keys like page_id.
|
|
218
|
+
const removePairs = opRequestShape(exampleEntity, 'remove').items
|
|
219
|
+
.filter((it: any) => !it.optional || it.name === idF)
|
|
220
|
+
.sort((a: any, b: any) =>
|
|
221
|
+
(a.name === idF ? 0 : 1) - (b.name === idF ? 0 : 1))
|
|
222
|
+
.map((it: any) => it.name === idF
|
|
223
|
+
? `"${it.name}" => ${idValueFor('remove')}`
|
|
224
|
+
: `"${it.name}" => ${rbLit(it.type, 'example_' + it.name)}`)
|
|
150
225
|
Content(`# Remove
|
|
151
|
-
client.${eName}.remove(${
|
|
226
|
+
client.${eName}.remove(${removePairs.length ? `{ ${removePairs.join(', ')} }` : ''})
|
|
152
227
|
`)
|
|
153
228
|
}
|
|
154
229
|
Content(`\`\`\`
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { cmp, each, Content, canonToType, canonKey, File, isAuthActive, entityIdField } from '@voxgig/sdkgen'
|
|
2
|
+
import { cmp, each, Content, canonToType, canonKey, File, isAuthActive, entityIdField, opRequestShape } from '@voxgig/sdkgen'
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
KIT,
|
|
@@ -9,13 +9,14 @@ import {
|
|
|
9
9
|
|
|
10
10
|
// A type-correct Ruby literal for a field's canonical type — the create body
|
|
11
11
|
// is EXECUTED by the doc test, so it must carry a real value per field.
|
|
12
|
-
|
|
12
|
+
// Strings render the quoted placeholder.
|
|
13
|
+
function rbLit(type: any, placeholder: string = 'example'): string {
|
|
13
14
|
const k = canonKey(type)
|
|
14
15
|
if ('INTEGER' === k || 'NUMBER' === k) return '1'
|
|
15
16
|
if ('BOOLEAN' === k) return 'true'
|
|
16
17
|
if ('ARRAY' === k) return '[]'
|
|
17
18
|
if ('OBJECT' === k) return '{}'
|
|
18
|
-
return
|
|
19
|
+
return `"${placeholder}"`
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
|
|
@@ -257,8 +258,20 @@ ${info.desc}
|
|
|
257
258
|
|
|
258
259
|
// Show example
|
|
259
260
|
if ('load' === opname || 'remove' === opname) {
|
|
261
|
+
// The id key plus every REQUIRED match key (parent path params
|
|
262
|
+
// like page_id) — the same shape the runtime resolves path
|
|
263
|
+
// params from, so the example always works.
|
|
264
|
+
const matchItems = opRequestShape(ent, opname).items
|
|
265
|
+
.filter((it: any) => !it.optional || it.name === idF)
|
|
266
|
+
.sort((a: any, b: any) =>
|
|
267
|
+
(a.name === idF ? 0 : 1) - (b.name === idF ? 0 : 1))
|
|
268
|
+
const arg = 0 < matchItems.length
|
|
269
|
+
? `{ ${matchItems.map((it: any) =>
|
|
270
|
+
`"${it.name}" => ${rbLit(it.type,
|
|
271
|
+
it.name === idF ? ent.name + '_id' : it.name)}`).join(', ')} }`
|
|
272
|
+
: ''
|
|
260
273
|
Content(`\`\`\`ruby
|
|
261
|
-
result = client.${ent.Name}.${opname}(${
|
|
274
|
+
result = client.${ent.Name}.${opname}(${arg})
|
|
262
275
|
\`\`\`
|
|
263
276
|
|
|
264
277
|
`)
|
|
@@ -271,14 +284,19 @@ results = client.${ent.Name}.list
|
|
|
271
284
|
`)
|
|
272
285
|
}
|
|
273
286
|
else if ('create' === opname) {
|
|
287
|
+
// Members come from the SAME shape the runtime validates
|
|
288
|
+
// (opRequestShape): every required member must appear — including
|
|
289
|
+
// a required id and parent keys like page_id — with a real,
|
|
290
|
+
// executable literal (the doc test RUNS this block, so a comment
|
|
291
|
+
// placeholder would break it).
|
|
292
|
+
const createItems = opRequestShape(ent, 'create').items
|
|
293
|
+
.filter((it: any) => !it.optional)
|
|
274
294
|
Content(`\`\`\`ruby
|
|
275
295
|
result = client.${ent.Name}.create({
|
|
276
296
|
`)
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
Content(` "${field.name}" => ${rbLit(field.type)}, # ${canonToType(field.type, target.name)}
|
|
297
|
+
createItems.map((it: any) => {
|
|
298
|
+
Content(` "${it.name}" => ${rbLit(it.type, 'example_' + it.name)}, # ${canonToType(it.type, target.name)}
|
|
280
299
|
`)
|
|
281
|
-
}
|
|
282
300
|
})
|
|
283
301
|
Content(`})
|
|
284
302
|
\`\`\`
|
|
@@ -286,10 +304,18 @@ result = client.${ent.Name}.create({
|
|
|
286
304
|
`)
|
|
287
305
|
}
|
|
288
306
|
else if ('update' === opname) {
|
|
289
|
-
|
|
307
|
+
// The id key plus every REQUIRED data member — the same shape the
|
|
308
|
+
// runtime validates — then the patch-fields note.
|
|
309
|
+
const updateItems = opRequestShape(ent, 'update').items
|
|
310
|
+
.filter((it: any) => !it.optional || it.name === idF)
|
|
311
|
+
.sort((a: any, b: any) =>
|
|
312
|
+
(a.name === idF ? 0 : 1) - (b.name === idF ? 0 : 1))
|
|
313
|
+
const updateLines = updateItems.map((it: any) =>
|
|
314
|
+
` "${it.name}" => ${rbLit(it.type,
|
|
315
|
+
it.name === idF ? ent.name + '_id' : it.name)},\n`).join('')
|
|
290
316
|
Content(`\`\`\`ruby
|
|
291
317
|
result = client.${ent.Name}.update({
|
|
292
|
-
${
|
|
318
|
+
${updateLines} # Fields to update
|
|
293
319
|
})
|
|
294
320
|
\`\`\`
|
|
295
321
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { cmp, Content, isAuthActive, envName, entityIdField } from '@voxgig/sdkgen'
|
|
2
|
+
import { cmp, Content, isAuthActive, envName, canonKey, entityIdField, opRequestShape } from '@voxgig/sdkgen'
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
KIT,
|
|
@@ -8,6 +8,20 @@ import {
|
|
|
8
8
|
} from '@voxgig/apidef'
|
|
9
9
|
|
|
10
10
|
|
|
11
|
+
// A type-correct, executable Ruby literal for a param: numeric/boolean/
|
|
12
|
+
// array/hash params render a typed literal; strings render the quoted
|
|
13
|
+
// placeholder (the doc test EXECUTES this block, so a comment placeholder
|
|
14
|
+
// would break it).
|
|
15
|
+
function rbLit(type: any, placeholder: string = 'example'): string {
|
|
16
|
+
const k = canonKey(type)
|
|
17
|
+
if ('INTEGER' === k || 'NUMBER' === k) return '1'
|
|
18
|
+
if ('BOOLEAN' === k) return 'true'
|
|
19
|
+
if ('ARRAY' === k) return '[]'
|
|
20
|
+
if ('OBJECT' === k) return '{}'
|
|
21
|
+
return `"${placeholder}"`
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
11
25
|
const ReadmeTopQuick = cmp(function ReadmeTopQuick(props: any) {
|
|
12
26
|
const { target, ctx$: { model } } = props
|
|
13
27
|
|
|
@@ -45,9 +59,21 @@ puts ${eName.toLowerCase()}s
|
|
|
45
59
|
}
|
|
46
60
|
|
|
47
61
|
if (opnames.includes('load')) {
|
|
62
|
+
// Every REQUIRED load-match key (id first, then parent path params like
|
|
63
|
+
// page_id) — the same shape the runtime resolves path params from, so
|
|
64
|
+
// the example always works.
|
|
65
|
+
const loadItems = opRequestShape(exampleEntity, 'load').items
|
|
66
|
+
.filter((it: any) => !it.optional || it.name === idF)
|
|
67
|
+
.sort((a: any, b: any) =>
|
|
68
|
+
(a.name === idF ? 0 : 1) - (b.name === idF ? 0 : 1))
|
|
69
|
+
const loadArg = 0 < loadItems.length
|
|
70
|
+
? `{ ${loadItems.map((it: any) =>
|
|
71
|
+
`"${it.name}" => ${rbLit(it.type,
|
|
72
|
+
it.name === idF ? 'example_id' : 'example_' + it.name)}`).join(', ')} }`
|
|
73
|
+
: ''
|
|
48
74
|
Content(`
|
|
49
75
|
# Load a specific ${eName.toLowerCase()} (returns the bare record; raises on error)
|
|
50
|
-
${eName.toLowerCase()} = client.${eName}.load(${
|
|
76
|
+
${eName.toLowerCase()} = client.${eName}.load(${loadArg})
|
|
51
77
|
puts ${eName.toLowerCase()}
|
|
52
78
|
`)
|
|
53
79
|
hasCall = true
|
|
@@ -39,7 +39,16 @@ const ReadmeTopTest = cmp(function ReadmeTopTest(props: any) {
|
|
|
39
39
|
const recBody = idF ? `{ "${idF}" => "test01" }` : '{}'
|
|
40
40
|
let callArg = ''
|
|
41
41
|
if (isMatchOp) {
|
|
42
|
-
|
|
42
|
+
// Every REQUIRED match key (id first, then parent path params like
|
|
43
|
+
// page_id) — the same shape the runtime resolves path params from.
|
|
44
|
+
const items = opRequestShape(exampleEntity, primaryOp).items
|
|
45
|
+
.filter((it: any) => !it.optional || it.name === idF)
|
|
46
|
+
.sort((a: any, b: any) =>
|
|
47
|
+
(a.name === idF ? 0 : 1) - (b.name === idF ? 0 : 1))
|
|
48
|
+
callArg = 0 < items.length
|
|
49
|
+
? `{ ${items.map((it: any) =>
|
|
50
|
+
`"${it.name}" => ${it.name === idF ? '"test01"' : rbLit(it.type)}`).join(', ')} }`
|
|
51
|
+
: ''
|
|
43
52
|
} else if ('create' === primaryOp || 'update' === primaryOp) {
|
|
44
53
|
const items = opRequestShape(exampleEntity, primaryOp).items
|
|
45
54
|
.filter((it: any) => it.name !== idF && it.name !== 'id')
|
|
@@ -47,12 +56,14 @@ const ReadmeTopTest = cmp(function ReadmeTopTest(props: any) {
|
|
|
47
56
|
const chosen = required.length ? required : items.slice(0, 3)
|
|
48
57
|
callArg = `{ ${chosen.map((it: any) => `"${it.name}" => ${rbLit(it.type)}`).join(', ')} }`
|
|
49
58
|
}
|
|
59
|
+
// A list result is an Array — name the variable accordingly.
|
|
60
|
+
const eVar = ename + ('list' === primaryOp ? 's' : '')
|
|
50
61
|
Content(`\`\`\`ruby
|
|
51
62
|
# Seed fixture data so offline calls resolve without a live server.
|
|
52
63
|
client = ${model.const.Name}SDK.test({
|
|
53
64
|
"entity" => { "${ename}" => { "test01" => ${recBody} } },
|
|
54
65
|
})
|
|
55
|
-
${
|
|
66
|
+
${eVar} = client.${eName}.${primaryOp}(${callArg})
|
|
56
67
|
\`\`\`
|
|
57
68
|
`)
|
|
58
69
|
} else {
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
each,
|
|
12
12
|
indent,
|
|
13
13
|
isAuthActive,
|
|
14
|
+
resolveAuthPrefix,
|
|
14
15
|
} from '@voxgig/sdkgen'
|
|
15
16
|
|
|
16
17
|
|
|
@@ -42,12 +43,8 @@ const Config = cmp(async function Config(props: any) {
|
|
|
42
43
|
const headers = getModelPath(model, `main.${KIT}.config.headers`) || {}
|
|
43
44
|
|
|
44
45
|
const authActive = isAuthActive(model)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const v = getModelPath(model, `main.${KIT}.config.auth.prefix`,
|
|
48
|
-
{ only_active: false, required: false })
|
|
49
|
-
if (null != v) authPrefix = v
|
|
50
|
-
} catch (_e) { /* ignore */ }
|
|
46
|
+
// config.auth.prefix override -> spec-derived info.security.prefix -> 'Bearer'
|
|
47
|
+
const authPrefix = resolveAuthPrefix(model)
|
|
51
48
|
const authBlock = authActive
|
|
52
49
|
? `auth: {
|
|
53
50
|
prefix: '${authPrefix}',
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { cmp, each, Content, canonToType, entityIdField, safeVarName } from '@voxgig/sdkgen'
|
|
2
|
+
import { cmp, each, Content, canonToType, entityIdField, opRequestShape, safeVarName } from '@voxgig/sdkgen'
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
KIT,
|
|
@@ -99,10 +99,22 @@ const ReadmeEntity = cmp(function ReadmeEntity(props: any) {
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
if (opnames.includes('load')) {
|
|
102
|
+
// The id key plus every REQUIRED match key (parent path params like
|
|
103
|
+
// page_id) — the same shape that generates <Name>LoadMatch, so the
|
|
104
|
+
// example always type-checks.
|
|
105
|
+
const loadItems = opRequestShape(entity, 'load').items
|
|
106
|
+
.filter((it: any) => !it.optional || it.name === idF)
|
|
107
|
+
.sort((a: any, b: any) =>
|
|
108
|
+
(a.name === idF ? 0 : 1) - (b.name === idF ? 0 : 1))
|
|
109
|
+
const loadArg = 0 < loadItems.length
|
|
110
|
+
? `{ ${loadItems.map((it: any) =>
|
|
111
|
+
`${it.name}: ${exampleValue(entity, entity.op && entity.op.load, it.name,
|
|
112
|
+
it.name === idF ? entity.name + '_id' : it.name)}`).join(', ')} }`
|
|
113
|
+
: ''
|
|
102
114
|
Content(`#### Example: Load
|
|
103
115
|
|
|
104
116
|
\`\`\`ts
|
|
105
|
-
const ${eVar} = await client.${entity.Name}().load(${
|
|
117
|
+
const ${eVar} = await client.${entity.Name}().load(${loadArg})
|
|
106
118
|
\`\`\`
|
|
107
119
|
|
|
108
120
|
`)
|
|
@@ -119,16 +131,20 @@ const ${eVar}s = await client.${entity.Name}().list()
|
|
|
119
131
|
}
|
|
120
132
|
|
|
121
133
|
if (opnames.includes('create')) {
|
|
134
|
+
// Members come from the SAME shape that generates <Name>CreateData:
|
|
135
|
+
// every required member must appear (the /* type */ placeholders also
|
|
136
|
+
// mark the block as an illustration for the doc gates); an
|
|
137
|
+
// all-optional create renders an empty — still assignable — literal.
|
|
138
|
+
const createItems = opRequestShape(entity, 'create').items
|
|
139
|
+
.filter((it: any) => !it.optional)
|
|
122
140
|
Content(`#### Example: Create
|
|
123
141
|
|
|
124
142
|
\`\`\`ts
|
|
125
143
|
const ${eVar} = await client.${entity.Name}().create({
|
|
126
144
|
`)
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
Content(` ${field.name}: /* ${canonToType(field.type, target.name)} */,
|
|
145
|
+
createItems.map((it: any) => {
|
|
146
|
+
Content(` ${it.name}: /* ${canonToType(it.type, target.name)} */,
|
|
130
147
|
`)
|
|
131
|
-
}
|
|
132
148
|
})
|
|
133
149
|
Content(`})
|
|
134
150
|
\`\`\`
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { cmp, each, Content, isAuthActive, packageName, envName, opRequestShape, entityIdField, entityDataIdField, safeVarName } from '@voxgig/sdkgen'
|
|
2
|
+
import { cmp, each, Content, isAuthActive, packageName, envName, opRequestShape, entityIdField, entityDataIdField, entityOps, safeVarName } from '@voxgig/sdkgen'
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
KIT,
|
|
@@ -18,9 +18,15 @@ const ReadmeQuick = cmp(function ReadmeQuick(props: any) {
|
|
|
18
18
|
// Find the first published entity for examples
|
|
19
19
|
const exampleEntity = Object.values(entity).find((e: any) => e.active !== false) as any
|
|
20
20
|
|
|
21
|
-
// Find a nested entity if available
|
|
21
|
+
// Find a nested entity if available: one with a parent chain
|
|
22
|
+
// (relations.ancestors), an active load op, and a required non-id load
|
|
23
|
+
// param to demonstrate (the parent key, e.g. page_id).
|
|
22
24
|
const nestedEntity = Object.values(entity).find((e: any) =>
|
|
23
|
-
e.active !== false &&
|
|
25
|
+
e.active !== false &&
|
|
26
|
+
e.relations && e.relations.ancestors && 0 < e.relations.ancestors.length &&
|
|
27
|
+
entityOps(e).includes('load') &&
|
|
28
|
+
opRequestShape(e, 'load').items.some((it: any) =>
|
|
29
|
+
!it.optional && it.name !== entityIdField(e))
|
|
24
30
|
) as any
|
|
25
31
|
|
|
26
32
|
const ctor = isAuthActive(model)
|
|
@@ -44,7 +50,7 @@ const client = ${ctor}
|
|
|
44
50
|
// reserved word (`const delete = ...` is a TS1109 syntax error).
|
|
45
51
|
const eVar = safeVarName(eName.toLowerCase(), 'ts')
|
|
46
52
|
const article = /^[aeiou]/i.test(eName) ? 'an' : 'a'
|
|
47
|
-
const opnames =
|
|
53
|
+
const opnames = entityOps(exampleEntity)
|
|
48
54
|
// Model-driven id key: `idF` is the entity's id-like MATCH field name, or
|
|
49
55
|
// null when it has none (then load/remove match on no argument).
|
|
50
56
|
const idF = entityIdField(exampleEntity)
|
|
@@ -74,22 +80,26 @@ for (const ${eVar} of ${eVar}s) {
|
|
|
74
80
|
const neName = nom(nestedEntity, 'Name')
|
|
75
81
|
const neVar = safeVarName(neName.toLowerCase(), 'ts')
|
|
76
82
|
const neArticle = /^[aeiou]/i.test(neName) ? 'an' : 'a'
|
|
77
|
-
const parentFields = (nestedEntity.fields || [])
|
|
78
|
-
.filter((f: any) => f.name !== 'id' && f.name.endsWith('_id'))
|
|
79
|
-
const parentParam = parentFields.length > 0 ? parentFields[0].name : 'parent_id'
|
|
80
83
|
const loadOp = nestedEntity.op && nestedEntity.op.load
|
|
81
84
|
|
|
82
|
-
// Model-driven
|
|
83
|
-
//
|
|
85
|
+
// Model-driven match: every REQUIRED load-match key — the same shape
|
|
86
|
+
// that generates <Name>LoadMatch, so the example always type-checks.
|
|
87
|
+
// Parent keys (e.g. page_id) first, the entity's own id last.
|
|
84
88
|
const neIdF = entityIdField(nestedEntity)
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
+
const neRequired = opRequestShape(nestedEntity, 'load').items
|
|
90
|
+
.filter((it: any) => !it.optional)
|
|
91
|
+
.sort((a: any, b: any) =>
|
|
92
|
+
(a.name === neIdF ? 1 : 0) - (b.name === neIdF ? 1 : 0))
|
|
93
|
+
const parentItem = neRequired.find((it: any) => it.name !== neIdF) as any
|
|
94
|
+
const parentParam = parentItem && parentItem.name
|
|
95
|
+
const parentName = parentParam ? parentParam.replace(/_id$/, '') : 'its parent'
|
|
96
|
+
const neMatchLines = neRequired.map((it: any) =>
|
|
97
|
+
` ${it.name}: ${exampleValue(nestedEntity, loadOp, it.name,
|
|
98
|
+
it.name === neIdF ? 'example_id' : 'example_' + it.name)},`)
|
|
89
99
|
|
|
90
100
|
Content(`### 3. Load ${neArticle} ${neName.toLowerCase()}
|
|
91
101
|
|
|
92
|
-
${neName} is nested under ${
|
|
102
|
+
${neName} is nested under ${parentName}, so provide the \`${parentParam}\`.
|
|
93
103
|
\`load()\` returns the entity directly and throws on failure:
|
|
94
104
|
|
|
95
105
|
\`\`\`ts
|
|
@@ -106,13 +116,25 @@ ${neMatchLines.join('\n')}
|
|
|
106
116
|
`)
|
|
107
117
|
}
|
|
108
118
|
else if (opnames.includes('load')) {
|
|
119
|
+
// Every REQUIRED load-match key (id first) — the same shape that
|
|
120
|
+
// generates <Name>LoadMatch, so the example always type-checks.
|
|
121
|
+
const loadRequired = opRequestShape(exampleEntity, 'load').items
|
|
122
|
+
.filter((it: any) => !it.optional || it.name === idF)
|
|
123
|
+
.sort((a: any, b: any) =>
|
|
124
|
+
(a.name === idF ? 0 : 1) - (b.name === idF ? 0 : 1))
|
|
125
|
+
const loadArg = 0 < loadRequired.length
|
|
126
|
+
? `{ ${loadRequired.map((it: any) =>
|
|
127
|
+
`${it.name}: ${exampleValue(exampleEntity, exampleEntity.op && exampleEntity.op.load, it.name,
|
|
128
|
+
it.name === idF ? 'example_id' : 'example_' + it.name)}`).join(', ')} }`
|
|
129
|
+
: ''
|
|
130
|
+
|
|
109
131
|
Content(`### 3. Load ${article} ${eName.toLowerCase()}
|
|
110
132
|
|
|
111
133
|
\`load()\` returns the entity directly and throws on failure:
|
|
112
134
|
|
|
113
135
|
\`\`\`ts
|
|
114
136
|
try {
|
|
115
|
-
const ${eVar} = await client.${eName}().load(${
|
|
137
|
+
const ${eVar} = await client.${eName}().load(${loadArg})
|
|
116
138
|
console.log(${eVar})
|
|
117
139
|
} catch (err) {
|
|
118
140
|
console.error('load failed:', err)
|
|
@@ -133,12 +155,19 @@ try {
|
|
|
133
155
|
// <Name>CreateData (a TS2345); update is a patch, so a couple of fields
|
|
134
156
|
// suffice.
|
|
135
157
|
const exampleFields = (opname: string): string[] => {
|
|
158
|
+
// ids are rendered separately as the match key for update/remove; a
|
|
159
|
+
// REQUIRED id stays (dropping it makes the literal unassignable).
|
|
136
160
|
const items = opRequestShape(exampleEntity, opname).items
|
|
137
|
-
.filter((it: any) => it.name !== idF && it.name !== 'id')
|
|
161
|
+
.filter((it: any) => (it.name !== idF && it.name !== 'id') ||
|
|
162
|
+
('create' === opname && !it.optional))
|
|
138
163
|
const required = items.filter((it: any) => !it.optional)
|
|
164
|
+
const optional = items.filter((it: any) => it.optional)
|
|
165
|
+
// Required members must all appear or the literal is not assignable
|
|
166
|
+
// to the typed <Name>{Create,Update}Data; pad update (a patch) with a
|
|
167
|
+
// sample optional field or two.
|
|
139
168
|
const chosen = 'create' === opname
|
|
140
169
|
? (required.length ? required : items.slice(0, 2))
|
|
141
|
-
:
|
|
170
|
+
: required.concat(optional).slice(0, Math.max(2, required.length))
|
|
142
171
|
return chosen.map((it: any) =>
|
|
143
172
|
` ${it.name}: ${exampleValue(exampleEntity, exampleEntity.op[opname], it.name, 'example_' + it.name)},`)
|
|
144
173
|
}
|
|
@@ -175,8 +204,17 @@ const updated = await client.${eName}().update({${updateBody}})
|
|
|
175
204
|
`)
|
|
176
205
|
}
|
|
177
206
|
if (opnames.includes('remove')) {
|
|
207
|
+
// Every REQUIRED remove-match key: the id (off the created record
|
|
208
|
+
// when possible) plus parent keys like page_id.
|
|
209
|
+
const removeLines = opRequestShape(exampleEntity, 'remove').items
|
|
210
|
+
.filter((it: any) => !it.optional || it.name === idF)
|
|
211
|
+
.sort((a: any, b: any) =>
|
|
212
|
+
(a.name === idF ? 0 : 1) - (b.name === idF ? 0 : 1))
|
|
213
|
+
.map((it: any) => it.name === idF
|
|
214
|
+
? ` ${it.name}: ${idValueFor('remove')},`
|
|
215
|
+
: ` ${it.name}: ${exampleValue(exampleEntity, exampleEntity.op.remove, it.name, 'example_' + it.name)},`)
|
|
178
216
|
Content(`// Remove
|
|
179
|
-
await client.${eName}().remove(${
|
|
217
|
+
await client.${eName}().remove(${removeLines.length ? `{\n${removeLines.join('\n')}\n}` : ''})
|
|
180
218
|
`)
|
|
181
219
|
}
|
|
182
220
|
Content(`\`\`\`
|