@voxgig/sdkgen 1.3.4 → 1.3.6
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/cmp/ReadmeTop.js +58 -29
- package/dist/cmp/ReadmeTop.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/cmp/ReadmeTop.ts +60 -31
- package/src/helpers/naming.ts +32 -2
- package/src/helpers/opShape.ts +89 -0
- package/src/sdkgen.ts +3 -1
package/src/cmp/ReadmeErrors.ts
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
getModelPath
|
|
7
7
|
} from '../types'
|
|
8
8
|
|
|
9
|
-
import { entityIdField,
|
|
9
|
+
import { entityIdField, pickExampleEntity } from '../helpers/opShape'
|
|
10
10
|
import { primaryOpCall } from '../helpers/opExample'
|
|
11
11
|
import type { ExampleLang, PrimaryCall } from '../helpers/opExample'
|
|
12
12
|
import { safeVarName } from '../helpers/naming'
|
|
@@ -224,29 +224,28 @@ const ReadmeErrors = cmp(function ReadmeErrors(props: any) {
|
|
|
224
224
|
|
|
225
225
|
const lang = LANGS[target.name] || DEFAULT_LANG
|
|
226
226
|
|
|
227
|
-
//
|
|
228
|
-
//
|
|
227
|
+
// Pick a real example entity WITH a real op (prefer a read op) so the
|
|
228
|
+
// error-handling snippet never references a phantom entity or fabricates a
|
|
229
|
+
// `.load()` on an op-less one (e.g. Cloudsmith's `Abort`). primaryOp is null
|
|
230
|
+
// only when NO entity exposes any op — then the entity example is skipped
|
|
231
|
+
// and only the direct() error handling (which every SDK has) is shown.
|
|
229
232
|
const entity = getModelPath(model, `main.${KIT}.entity`, { only_active: false, required: false })
|
|
230
|
-
const ex =
|
|
231
|
-
const eName = ex ? (ex.Name || (ex.name[0].toUpperCase() + ex.name.slice(1))) : 'Entity'
|
|
232
|
-
// Sanitise the variable name against the target's reserved words (a `Delete`
|
|
233
|
-
// entity must not bind `const delete = ...`).
|
|
234
|
-
const eLower = safeVarName(eName.toLowerCase(), target.name)
|
|
235
|
-
|
|
236
|
-
// The entity's id-like key field name, or null when it has none.
|
|
237
|
-
const idF = entityIdField(ex)
|
|
238
|
-
// The entity's PRIMARY op — an op it actually exposes (prefer list/load, else
|
|
239
|
-
// create/update/remove). A create-only entity therefore never shows a
|
|
240
|
-
// phantom `.load()`.
|
|
241
|
-
const primaryOp = entityPrimaryOp(ex) || 'load'
|
|
242
|
-
const call = primaryOpCall(target.name as ExampleLang, eName, eLower, primaryOp, idF, ex)
|
|
233
|
+
const { entity: ex, primaryOp } = pickExampleEntity(entity || {})
|
|
243
234
|
|
|
244
235
|
Content(`
|
|
245
236
|
## Error handling
|
|
246
237
|
|
|
247
238
|
`)
|
|
248
239
|
|
|
249
|
-
|
|
240
|
+
if (ex && primaryOp) {
|
|
241
|
+
const eName = ex.Name || (ex.name[0].toUpperCase() + ex.name.slice(1))
|
|
242
|
+
// Sanitise the variable name against the target's reserved words (a
|
|
243
|
+
// `Delete` entity must not bind `const delete = ...`).
|
|
244
|
+
const eLower = safeVarName(eName.toLowerCase(), target.name)
|
|
245
|
+
const idF = entityIdField(ex)
|
|
246
|
+
const call = primaryOpCall(target.name as ExampleLang, eName, eLower, primaryOp, idF, ex)
|
|
247
|
+
Content(lang.entity(call, primaryOp))
|
|
248
|
+
}
|
|
250
249
|
|
|
251
250
|
Content(lang.direct)
|
|
252
251
|
})
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
|
|
9
9
|
import { requirePath } from '../utility'
|
|
10
10
|
|
|
11
|
-
import { entityIdField,
|
|
11
|
+
import { entityIdField, pickExampleEntity } from '../helpers/opShape'
|
|
12
12
|
import { idLiteral, matchArg, dataArg } from '../helpers/opExample'
|
|
13
13
|
import type { ExampleLang } from '../helpers/opExample'
|
|
14
14
|
import { safeVarName } from '../helpers/naming'
|
|
@@ -225,39 +225,40 @@ const ReadmeExplanation = cmp(function ReadmeExplanation(props: any) {
|
|
|
225
225
|
const feature = getModelPath(model, `main.${KIT}.feature`)
|
|
226
226
|
const lang = LANGS[target.name] || DEFAULT_LANG
|
|
227
227
|
|
|
228
|
-
//
|
|
229
|
-
//
|
|
230
|
-
//
|
|
228
|
+
// Pick a real example entity WITH a real op (prefer a read op) so the
|
|
229
|
+
// entity-state example never references a phantom entity or fabricates a
|
|
230
|
+
// `.load()` on an op-less one (e.g. Cloudsmith's `Abort`). primaryOp is null
|
|
231
|
+
// only when NO entity exposes any op — then the entity-state section is
|
|
232
|
+
// skipped (a direct()-only SDK has no entity op to illustrate).
|
|
231
233
|
const entity = getModelPath(model, `main.${KIT}.entity`, { only_active: false, required: false })
|
|
232
|
-
const ex =
|
|
233
|
-
const eName = ex ? (ex.Name || (ex.name[0].toUpperCase() + ex.name.slice(1))) : 'Entity'
|
|
234
|
-
// Sanitise against the target's reserved words (a `Delete` entity must not
|
|
235
|
-
// bind `const delete = ...`).
|
|
236
|
-
const eLower = safeVarName(eName.toLowerCase(), target.name)
|
|
234
|
+
const { entity: ex, primaryOp } = pickExampleEntity(entity || {})
|
|
237
235
|
const lname = target.name as ExampleLang
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
236
|
+
const hasEntityExample = !!(ex && primaryOp)
|
|
237
|
+
|
|
238
|
+
let eName = 'Entity', eLower = 'entity', stateArg = '', matchIdF: string | null = null, idLit = ''
|
|
239
|
+
if (hasEntityExample) {
|
|
240
|
+
eName = ex.Name || (ex.name[0].toUpperCase() + ex.name.slice(1))
|
|
241
|
+
// Sanitise against the target's reserved words (a `Delete` entity must
|
|
242
|
+
// not bind `const delete = ...`).
|
|
243
|
+
eLower = safeVarName(eName.toLowerCase(), target.name)
|
|
244
|
+
const idF = entityIdField(ex)
|
|
245
|
+
const isMatchOp = 'load' === primaryOp || 'remove' === primaryOp
|
|
246
|
+
// Type-correct example id literal (numeric when the id param is integer-
|
|
247
|
+
// typed), derived from the OP's param type so an id carried only in the
|
|
248
|
+
// match compiles.
|
|
249
|
+
idLit = idLiteral(ex, primaryOp as string, idF)
|
|
250
|
+
// Language-correct call argument for the primary op: a match for
|
|
251
|
+
// load/remove, a required-field body for create/update, nothing for list.
|
|
252
|
+
if ('list' === primaryOp) {
|
|
253
|
+
stateArg = 'go' === target.name ? 'nil' : ''
|
|
254
|
+
} else if (isMatchOp) {
|
|
255
|
+
stateArg = matchArg(lname, idF, idLit)
|
|
256
|
+
} else {
|
|
257
|
+
stateArg = dataArg(lname, ex, primaryOp as string, idF)
|
|
258
|
+
}
|
|
259
|
+
// Only a match op keys the `.match()` comment on `{ id: ... }`.
|
|
260
|
+
matchIdF = isMatchOp ? idF : null
|
|
258
261
|
}
|
|
259
|
-
// Only a match op keys the `.match()` comment on `{ id: ... }`.
|
|
260
|
-
const matchIdF = isMatchOp ? idF : null
|
|
261
262
|
|
|
262
263
|
Content(`
|
|
263
264
|
## Advanced
|
|
@@ -327,12 +328,13 @@ were added, so later features can override earlier ones.
|
|
|
327
328
|
}
|
|
328
329
|
|
|
329
330
|
|
|
330
|
-
// Entity state
|
|
331
|
-
|
|
331
|
+
// Entity state — only when the SDK actually has an entity op to show.
|
|
332
|
+
if (hasEntityExample) {
|
|
333
|
+
Content(`### Entity state
|
|
332
334
|
|
|
333
335
|
`)
|
|
334
|
-
|
|
335
|
-
|
|
336
|
+
Content(lang.entityState(eName, eLower, primaryOp as string, stateArg, matchIdF, idLit))
|
|
337
|
+
}
|
|
336
338
|
|
|
337
339
|
|
|
338
340
|
// Direct vs entity access
|
package/src/cmp/ReadmeTop.ts
CHANGED
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
const SDKGEN_REPO = 'https://github.com/voxgig/sdkgen'
|
|
28
|
+
const VOXGIG_SDK = 'https://voxgig.com/sdk/'
|
|
28
29
|
|
|
29
30
|
|
|
30
31
|
// A type-correct TS example literal for a model field, keyed off its canonical
|
|
@@ -97,6 +98,16 @@ const ReadmeTop = cmp(function ReadmeTop(props: any) {
|
|
|
97
98
|
`[${apiWebsite.replace(/^https?:\/\//, '').replace(/\/$/, '')}](${apiWebsite}).`
|
|
98
99
|
: ''
|
|
99
100
|
|
|
101
|
+
// Attribution for API metadata sourced from a third-party catalogue (e.g.
|
|
102
|
+
// freepublicapis.com). Rendered only when the model carries an
|
|
103
|
+
// `info.meta_source` URL, so first-party SDKs never show it. The link points
|
|
104
|
+
// back to the catalogue that kindly supplied the metadata.
|
|
105
|
+
const metaSource = (info.meta_source || '').trim()
|
|
106
|
+
const metaSourceLine = metaSource
|
|
107
|
+
? `Meta data kindly supplied by ` +
|
|
108
|
+
`[${metaSource.replace(/^https?:\/\//, '').replace(/\/$/, '')}](${metaSource}).`
|
|
109
|
+
: ''
|
|
110
|
+
|
|
100
111
|
const entity = getModelPath(model, `main.${KIT}.entity`)
|
|
101
112
|
const target = getModelPath(model, `main.${KIT}.target`)
|
|
102
113
|
const feature = getModelPath(model, `main.${KIT}.feature`)
|
|
@@ -118,16 +129,25 @@ const ReadmeTop = cmp(function ReadmeTop(props: any) {
|
|
|
118
129
|
const docsOrder: string[] = (getModelPath(model, `main.${KIT}.config.docs_order`,
|
|
119
130
|
{ only_active: false, required: false }) as any) || []
|
|
120
131
|
|
|
132
|
+
const orderOf = (name: string): number => {
|
|
133
|
+
const i = docsOrder.indexOf(name)
|
|
134
|
+
if (i !== -1) return i
|
|
135
|
+
// Unlisted targets go after the languages; keep the CLI then MCP last.
|
|
136
|
+
if (name === 'go-cli') return docsOrder.length + 1
|
|
137
|
+
if (name === 'go-mcp') return docsOrder.length + 2
|
|
138
|
+
return docsOrder.length
|
|
139
|
+
}
|
|
140
|
+
|
|
121
141
|
const sdkTargets = activeTargets
|
|
122
142
|
.filter((t: any) => t.name !== 'go-cli' && t.name !== 'go-mcp')
|
|
123
143
|
.slice()
|
|
124
|
-
.sort((a: any, b: any) =>
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
144
|
+
.sort((a: any, b: any) => orderOf(a.name) - orderOf(b.name))
|
|
145
|
+
|
|
146
|
+
// Every installable port for the Packages table — including the go-cli and
|
|
147
|
+
// go-mcp binaries, which publish as Go modules via git tags.
|
|
148
|
+
const pkgTargets = activeTargets
|
|
149
|
+
.slice()
|
|
150
|
+
.sort((a: any, b: any) => orderOf(a.name) - orderOf(b.name))
|
|
131
151
|
|
|
132
152
|
const langList = sdkTargets.map((t: any) => t.title).join(', ')
|
|
133
153
|
const leadTarget = pickLeadTarget(sdkTargets)
|
|
@@ -153,7 +173,14 @@ ${tagline}
|
|
|
153
173
|
}
|
|
154
174
|
Content(`${nonAffiliation(model)}
|
|
155
175
|
|
|
176
|
+
Learn more about Voxgig SDKs at [voxgig.com/sdk](${VOXGIG_SDK}).
|
|
177
|
+
|
|
178
|
+
`)
|
|
179
|
+
if (metaSourceLine) {
|
|
180
|
+
Content(`${metaSourceLine}
|
|
181
|
+
|
|
156
182
|
`)
|
|
183
|
+
}
|
|
157
184
|
|
|
158
185
|
// Positioning line, only when we actually have multiple SDK targets.
|
|
159
186
|
if (sdkTargets.length > 1) {
|
|
@@ -242,19 +269,43 @@ rather than reasoning about raw HTTP routes and query parameters.
|
|
|
242
269
|
`)
|
|
243
270
|
}
|
|
244
271
|
|
|
272
|
+
// 2c. Offline unit testing — a headline feature: every SDK ships a mock
|
|
273
|
+
// transport, so it belongs high up, right after the entity model.
|
|
274
|
+
if (sdkTargets.length > 0) {
|
|
275
|
+
Content(`## Offline unit testing
|
|
276
|
+
|
|
277
|
+
Every SDK ships a built-in **test mode** that swaps the HTTP transport for
|
|
278
|
+
an in-memory mock, so your unit tests run fully offline — no server, no
|
|
279
|
+
network, and no credentials:
|
|
280
|
+
|
|
281
|
+
`)
|
|
282
|
+
sdkTargets.forEach((tgt: any) => {
|
|
283
|
+
const Test =
|
|
284
|
+
requirePath(ctx$, `./cmp/${tgt.name}/ReadmeTopTest_${tgt.name}`, { ignore: true })
|
|
285
|
+
if (Test) {
|
|
286
|
+
Content(`### ${tgt.title}
|
|
287
|
+
|
|
288
|
+
`)
|
|
289
|
+
Test['ReadmeTopTest']({ target: tgt })
|
|
290
|
+
Content(`
|
|
291
|
+
`)
|
|
292
|
+
}
|
|
293
|
+
})
|
|
294
|
+
}
|
|
295
|
+
|
|
245
296
|
// 3. Packages — real published package name + install command per
|
|
246
297
|
// ecosystem. A package that is NOT yet live on its registry (the fleet
|
|
247
298
|
// default: 'pending') must NOT advertise a `npm install ...` that 404s —
|
|
248
299
|
// its Install cell links to the git-tag releases page instead. The go
|
|
249
300
|
// family resolves from the tag directly (`go get <mod>@latest`).
|
|
250
|
-
if (
|
|
301
|
+
if (pkgTargets.length > 0) {
|
|
251
302
|
const { releasesUrl } = repoInfo(model)
|
|
252
303
|
Content(`## Packages
|
|
253
304
|
|
|
254
305
|
| Language | Package | Install |
|
|
255
306
|
| --- | --- | --- |
|
|
256
307
|
`)
|
|
257
|
-
|
|
308
|
+
pkgTargets.forEach((tgt: any) => {
|
|
258
309
|
const state = registryState(model, tgt.name)
|
|
259
310
|
let cell: string
|
|
260
311
|
if ('active' === state) {
|
|
@@ -402,28 +453,6 @@ own list above for exactly which it supports.
|
|
|
402
453
|
})
|
|
403
454
|
}
|
|
404
455
|
|
|
405
|
-
// 9. Testing — keep, but slim
|
|
406
|
-
if (sdkTargets.length > 0) {
|
|
407
|
-
Content(`## Unit testing in offline mode
|
|
408
|
-
|
|
409
|
-
Every SDK ships a test mode that swaps the HTTP transport for an
|
|
410
|
-
in-memory mock, so unit tests run offline.
|
|
411
|
-
|
|
412
|
-
`)
|
|
413
|
-
sdkTargets.forEach((tgt: any) => {
|
|
414
|
-
const Test =
|
|
415
|
-
requirePath(ctx$, `./cmp/${tgt.name}/ReadmeTopTest_${tgt.name}`, { ignore: true })
|
|
416
|
-
if (Test) {
|
|
417
|
-
Content(`### ${tgt.title}
|
|
418
|
-
|
|
419
|
-
`)
|
|
420
|
-
Test['ReadmeTopTest']({ target: tgt })
|
|
421
|
-
Content(`
|
|
422
|
-
`)
|
|
423
|
-
}
|
|
424
|
-
})
|
|
425
|
-
}
|
|
426
|
-
|
|
427
456
|
// 10. Direct and prepare — a common everyday task (the low-level
|
|
428
457
|
// escape hatch for endpoints the entity model doesn't cover).
|
|
429
458
|
Content(`## Direct and prepare
|
package/src/helpers/naming.ts
CHANGED
|
@@ -11,8 +11,11 @@
|
|
|
11
11
|
// words a `const` binding trips on). Go lists its 25 keywords — a var named
|
|
12
12
|
// `type`/`func`/`range` fails `go build`; Go builtins like `delete`/`len` are
|
|
13
13
|
// NOT keywords and are legal identifiers, so they are intentionally omitted.
|
|
14
|
-
// py/
|
|
15
|
-
//
|
|
14
|
+
// rb/py/lua list the keywords that are illegal as a local-variable binding
|
|
15
|
+
// (e.g. a `Self` entity -> `self = client.Self` is a Ruby SyntaxError, an
|
|
16
|
+
// `End` entity -> `end = ...` a Lua one). PHP variables are `$`-prefixed so a
|
|
17
|
+
// lowercased entity name never collides (only `$this` is special, and no
|
|
18
|
+
// entity is named `this`), so PHP is not treated.
|
|
16
19
|
|
|
17
20
|
const JS_RESERVED = new Set<string>([
|
|
18
21
|
'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger',
|
|
@@ -32,10 +35,37 @@ const GO_RESERVED = new Set<string>([
|
|
|
32
35
|
'var',
|
|
33
36
|
])
|
|
34
37
|
|
|
38
|
+
// Ruby keywords — illegal as a local-variable name (e.g. `self`, `end`).
|
|
39
|
+
const RB_RESERVED = new Set<string>([
|
|
40
|
+
'alias', 'and', 'begin', 'break', 'case', 'class', 'def', 'defined?',
|
|
41
|
+
'do', 'else', 'elsif', 'end', 'ensure', 'false', 'for', 'if', 'in',
|
|
42
|
+
'module', 'next', 'nil', 'not', 'or', 'redo', 'rescue', 'retry', 'return',
|
|
43
|
+
'self', 'super', 'then', 'true', 'undef', 'unless', 'until', 'when',
|
|
44
|
+
'while', 'yield', '__FILE__', '__LINE__', '__ENCODING__', 'BEGIN', 'END',
|
|
45
|
+
])
|
|
46
|
+
|
|
47
|
+
// Python keywords — illegal as a variable name.
|
|
48
|
+
const PY_RESERVED = new Set<string>([
|
|
49
|
+
'False', 'None', 'True', 'and', 'as', 'assert', 'async', 'await', 'break',
|
|
50
|
+
'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'finally',
|
|
51
|
+
'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'nonlocal',
|
|
52
|
+
'not', 'or', 'pass', 'raise', 'return', 'try', 'while', 'with', 'yield',
|
|
53
|
+
])
|
|
54
|
+
|
|
55
|
+
// Lua keywords — illegal as a variable name. (`self` is NOT reserved in Lua.)
|
|
56
|
+
const LUA_RESERVED = new Set<string>([
|
|
57
|
+
'and', 'break', 'do', 'else', 'elseif', 'end', 'false', 'for', 'function',
|
|
58
|
+
'goto', 'if', 'in', 'local', 'nil', 'not', 'or', 'repeat', 'return',
|
|
59
|
+
'then', 'true', 'until', 'while',
|
|
60
|
+
])
|
|
61
|
+
|
|
35
62
|
const RESERVED: Record<string, Set<string>> = {
|
|
36
63
|
ts: JS_RESERVED,
|
|
37
64
|
js: JS_RESERVED,
|
|
38
65
|
go: GO_RESERVED,
|
|
66
|
+
rb: RB_RESERVED,
|
|
67
|
+
py: PY_RESERVED,
|
|
68
|
+
lua: LUA_RESERVED,
|
|
39
69
|
}
|
|
40
70
|
|
|
41
71
|
|
package/src/helpers/opShape.ts
CHANGED
|
@@ -238,6 +238,93 @@ function entityPrimaryOp(ent: any): string | null {
|
|
|
238
238
|
}
|
|
239
239
|
|
|
240
240
|
|
|
241
|
+
// Collision-free target-language CLASS name for an entity. The natural
|
|
242
|
+
// class name is `<Name>Entity`, but that can equal another entity's
|
|
243
|
+
// canonical DATA-type name `<Name'>` when Name' === Name + 'Entity' (e.g.
|
|
244
|
+
// GitLab's `project` -> class `ProjectEntity` collides with `project_entity`
|
|
245
|
+
// -> data type `ProjectEntity`), which redeclares a type in Go and merges/
|
|
246
|
+
// shadows it in ts/py/rb. This assigns each entity a class name that is
|
|
247
|
+
// unique across ALL emitted top-level type names (every entity's data type
|
|
248
|
+
// and per-op Match/Data types, plus already-assigned class names): the
|
|
249
|
+
// natural `<Name>Entity` when free, else `<Name>EntityClient`, `...Client2`,
|
|
250
|
+
// … The DATA type keeps its canonical `<Name>` — only the suffixed class
|
|
251
|
+
// yields. Deterministic (sorted-key iteration) and stable across runs.
|
|
252
|
+
//
|
|
253
|
+
// Memoised per entity-collection object so the O(n) assignment runs once.
|
|
254
|
+
const _classNameCache = new WeakMap<object, Record<string, string>>()
|
|
255
|
+
|
|
256
|
+
function entityClassNames(entityColl: any): Record<string, string> {
|
|
257
|
+
const cached = _classNameCache.get(entityColl)
|
|
258
|
+
if (null != cached) {
|
|
259
|
+
return cached
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const ents = each(entityColl).filter((e: any) => e && e.active !== false)
|
|
263
|
+
|
|
264
|
+
// 1. Every top-level DATA-type name the target emits.
|
|
265
|
+
const taken: Record<string, boolean> = {}
|
|
266
|
+
ents.forEach((e: any) => {
|
|
267
|
+
taken[e.Name] = true
|
|
268
|
+
for (const op of ['load', 'list', 'create', 'update', 'remove']) {
|
|
269
|
+
if (e.op && e.op[op]) {
|
|
270
|
+
taken[opTypeName(e.Name, op)] = true
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
// 2. Assign each class name, avoiding all data types and prior classes.
|
|
276
|
+
const out: Record<string, string> = {}
|
|
277
|
+
ents.forEach((e: any) => {
|
|
278
|
+
let name = e.Name + 'Entity'
|
|
279
|
+
if (taken[name]) {
|
|
280
|
+
const base = name + 'Client'
|
|
281
|
+
name = base
|
|
282
|
+
let n = 1
|
|
283
|
+
while (taken[name]) {
|
|
284
|
+
n++
|
|
285
|
+
name = base + n
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
taken[name] = true
|
|
289
|
+
out[e.name] = name
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
_classNameCache.set(entityColl, out)
|
|
293
|
+
return out
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
// The collision-free class name for one entity (see entityClassNames).
|
|
298
|
+
// `entityColl` is main.<KIT>.entity (the collection the entity belongs to).
|
|
299
|
+
function entityClassName(ent: any, entityColl: any): string {
|
|
300
|
+
if (null == ent) {
|
|
301
|
+
return ''
|
|
302
|
+
}
|
|
303
|
+
const map = entityClassNames(entityColl)
|
|
304
|
+
return map[ent.name] || (ent.Name + 'Entity')
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
// Pick a representative entity for a single illustrative snippet (e.g. the
|
|
309
|
+
// README's test-mode example): the first ACTIVE entity that exposes a read
|
|
310
|
+
// op (list/load) so the snippet is meaningful, else the first with ANY
|
|
311
|
+
// active op, else the first active entity. Returns { entity, primaryOp }
|
|
312
|
+
// where primaryOp is null only when NO entity has an op — so callers never
|
|
313
|
+
// fabricate an op the entity lacks (the cause of `.load()` on an op-less
|
|
314
|
+
// entity like Cloudsmith's `Abort`). Entities iterate in sorted-key order
|
|
315
|
+
// for deterministic output.
|
|
316
|
+
function pickExampleEntity(entity: any): { entity: any, primaryOp: string | null } {
|
|
317
|
+
const actives = each(entity).filter((e: any) => e && e.active !== false)
|
|
318
|
+
const readable = actives.find((e: any) => {
|
|
319
|
+
const op = entityPrimaryOp(e)
|
|
320
|
+
return 'list' === op || 'load' === op
|
|
321
|
+
})
|
|
322
|
+
const withOp = actives.find((e: any) => null != entityPrimaryOp(e))
|
|
323
|
+
const chosen = readable || withOp || actives[0] || null
|
|
324
|
+
return { entity: chosen, primaryOp: null == chosen ? null : entityPrimaryOp(chosen) }
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
|
|
241
328
|
// The id field on the entity's DATA type (its fields[]), or null. DISTINCT from
|
|
242
329
|
// entityIdField (the load-MATCH key): an API can model a load match that carries
|
|
243
330
|
// an `id` param while the response entity itself has no `id` field, so `.id`
|
|
@@ -267,6 +354,8 @@ export {
|
|
|
267
354
|
entityDataIdField,
|
|
268
355
|
entityOps,
|
|
269
356
|
entityPrimaryOp,
|
|
357
|
+
pickExampleEntity,
|
|
358
|
+
entityClassName,
|
|
270
359
|
}
|
|
271
360
|
|
|
272
361
|
export type {
|
package/src/sdkgen.ts
CHANGED
|
@@ -48,7 +48,7 @@ import { getMatchEntries } from './helpers/getMatchEntries'
|
|
|
48
48
|
import { collectDeps } from './helpers/collectDeps'
|
|
49
49
|
import type { DepEntry } from './helpers/collectDeps'
|
|
50
50
|
import { canonToType, canonKey } from './helpers/canonType'
|
|
51
|
-
import { OP_SUFFIX, opTypeName, opParams, opRequestShape, entityIdField, entityDataIdField, entityOps, entityPrimaryOp } from './helpers/opShape'
|
|
51
|
+
import { OP_SUFFIX, opTypeName, opParams, opRequestShape, entityIdField, entityDataIdField, entityOps, entityPrimaryOp, pickExampleEntity, entityClassName } from './helpers/opShape'
|
|
52
52
|
import { isReservedName, safeVarName } from './helpers/naming'
|
|
53
53
|
import {
|
|
54
54
|
packageName,
|
|
@@ -448,6 +448,8 @@ export {
|
|
|
448
448
|
entityDataIdField,
|
|
449
449
|
entityOps,
|
|
450
450
|
entityPrimaryOp,
|
|
451
|
+
pickExampleEntity,
|
|
452
|
+
entityClassName,
|
|
451
453
|
isReservedName,
|
|
452
454
|
safeVarName,
|
|
453
455
|
|