@voxgig/sdkgen 4.4.1 → 4.5.0
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/FeatureDocs.d.ts +16 -0
- package/dist/cmp/FeatureDocs.js +72 -0
- package/dist/cmp/FeatureDocs.js.map +1 -0
- package/dist/cmp/Readme.js +2 -0
- package/dist/cmp/Readme.js.map +1 -1
- package/dist/cmp/ReadmeFeatures.d.ts +2 -0
- package/dist/cmp/ReadmeFeatures.js +95 -0
- package/dist/cmp/ReadmeFeatures.js.map +1 -0
- package/dist/cmp/ReadmeRef.js +4 -0
- package/dist/cmp/ReadmeRef.js.map +1 -1
- package/dist/cmp/ReadmeRefFeatures.d.ts +2 -0
- package/dist/cmp/ReadmeRefFeatures.js +130 -0
- package/dist/cmp/ReadmeRefFeatures.js.map +1 -0
- package/dist/cmp/ReadmeTop.js +46 -0
- package/dist/cmp/ReadmeTop.js.map +1 -1
- package/dist/helpers/opShape.js +5 -1
- package/dist/helpers/opShape.js.map +1 -1
- package/dist/sdkgen.d.ts +3 -2
- package/dist/sdkgen.js +30 -10
- package/dist/sdkgen.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utility.d.ts +2 -1
- package/dist/utility.js +17 -1
- package/dist/utility.js.map +1 -1
- package/package.json +1 -1
- package/project/.sdk/src/cmp/c/ReadmeRef_c.ts +5 -0
- package/project/.sdk/src/cmp/clojure/ReadmeRef_clojure.ts +5 -0
- package/project/.sdk/src/cmp/cpp/ReadmeRef_cpp.ts +5 -0
- package/project/.sdk/src/cmp/csharp/ReadmeRef_csharp.ts +5 -0
- package/project/.sdk/src/cmp/dart/ReadmeRef_dart.ts +5 -0
- package/project/.sdk/src/cmp/elixir/ReadmeRef_elixir.ts +5 -0
- package/project/.sdk/src/cmp/go/ReadmeRef_go.ts +5 -0
- package/project/.sdk/src/cmp/go/TestDirect_go.ts +123 -2
- package/project/.sdk/src/cmp/java/ReadmeRef_java.ts +5 -0
- package/project/.sdk/src/cmp/js/ReadmeRef_js.ts +5 -0
- package/project/.sdk/src/cmp/js/TestDirect_js.ts +62 -0
- package/project/.sdk/src/cmp/kotlin/ReadmeRef_kotlin.ts +5 -0
- package/project/.sdk/src/cmp/lean/ReadmeRef_lean.ts +27 -3
- package/project/.sdk/src/cmp/lua/ReadmeRef_lua.ts +5 -0
- package/project/.sdk/src/cmp/ocaml/ReadmeRef_ocaml.ts +5 -0
- package/project/.sdk/src/cmp/perl/ReadmeRef_perl.ts +5 -0
- package/project/.sdk/src/cmp/php/ReadmeRef_php.ts +5 -0
- package/project/.sdk/src/cmp/py/ReadmeRef_py.ts +5 -0
- package/project/.sdk/src/cmp/rb/ReadmeRef_rb.ts +5 -0
- package/project/.sdk/src/cmp/rust/ReadmeRef_rust.ts +5 -0
- package/project/.sdk/src/cmp/scala/ReadmeRef_scala.ts +5 -0
- package/project/.sdk/src/cmp/seneca-provider/Extras_seneca-provider.ts +19 -1
- package/project/.sdk/src/cmp/seneca-provider/Main_seneca-provider.ts +27 -2
- package/project/.sdk/src/cmp/swift/ReadmeRef_swift.ts +5 -0
- package/project/.sdk/src/cmp/ts/Config_ts.ts +4 -1
- package/project/.sdk/src/cmp/ts/ReadmeHowto_ts.ts +9 -4
- package/project/.sdk/src/cmp/ts/ReadmeModel_ts.ts +8 -3
- package/project/.sdk/src/cmp/ts/ReadmeQuick_ts.ts +3 -2
- package/project/.sdk/src/cmp/ts/ReadmeRef_ts.ts +7 -2
- package/project/.sdk/src/cmp/ts/ReadmeTopQuick_ts.ts +3 -2
- package/project/.sdk/src/cmp/ts/TestDirect_ts.ts +5 -2
- package/project/.sdk/src/cmp/ts/TestEntity_ts.ts +5 -2
- package/project/.sdk/src/cmp/zig/ReadmeRef_zig.ts +5 -0
- package/project/.sdk/tm/clojure/src/sdk/features.clj +175 -0
- package/project/.sdk/tm/dart/test/feature_test.dart +54 -0
- package/project/.sdk/tm/go/feature/test_feature.go +17 -6
- package/project/.sdk/tm/go/test/feature_corpus_test.go +10 -3
- package/project/.sdk/tm/java/test/FeatureCorpusTest.java +5 -4
- package/project/.sdk/tm/js/src/feature/test/TestFeature.js +8 -1
- package/project/.sdk/tm/js/src/utility/MakeOptionsUtility.js +3 -1
- package/project/.sdk/tm/js/test/feature/Corpus.test.js +13 -3
- package/project/.sdk/tm/js/test/feature.test.js +19 -9
- package/project/.sdk/tm/lean/src/SdkFeatures.lean +186 -1
- package/project/.sdk/tm/lua/feature/test_feature.lua +21 -7
- package/project/.sdk/tm/lua/test/feature_test.lua +18 -18
- package/project/.sdk/tm/lua/utility/make_options.lua +2 -0
- package/project/.sdk/tm/ocaml/sdk_features.ml +5 -0
- package/project/.sdk/tm/perl/t/feature_corpus.t +5 -4
- package/project/.sdk/tm/php/feature/TestFeature.php +19 -8
- package/project/.sdk/tm/php/test/FeatureCorpusTest.php +7 -4
- package/project/.sdk/tm/php/utility/MakeOptions.php +2 -1
- package/project/.sdk/tm/py/pkg/feature/test_feature.py +15 -4
- package/project/.sdk/tm/py/pkg/utility/make_options.py +2 -0
- package/project/.sdk/tm/py/test/test_feature.py +40 -0
- package/project/.sdk/tm/py/test/test_feature_corpus.py +14 -3
- package/project/.sdk/tm/rb/test/feature_corpus_test.rb +12 -4
- package/project/.sdk/tm/ts/src/feature/test/TestFeature.ts +15 -1
- package/project/.sdk/tm/ts/src/utility/MakeOptionsUtility.ts +3 -1
- package/project/.sdk/tm/ts/src/utility/PrepareAuthUtility.ts +20 -0
- package/project/.sdk/tm/ts/test/feature/Corpus.test.ts +13 -3
- package/project/.sdk/tm/ts/test/feature.test.ts +19 -9
- package/project/sdkgen-package.json +1 -1
- package/src/cmp/FeatureDocs.ts +100 -0
- package/src/cmp/Readme.ts +2 -0
- package/src/cmp/ReadmeFeatures.ts +113 -0
- package/src/cmp/ReadmeRef.ts +4 -0
- package/src/cmp/ReadmeRefFeatures.ts +149 -0
- package/src/cmp/ReadmeTop.ts +48 -0
- package/src/helpers/opShape.ts +5 -1
- package/src/sdkgen.ts +28 -7
- package/src/utility.ts +21 -1
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { each } from 'jostraca'
|
|
2
|
+
|
|
3
|
+
import { KIT, getModelPath } from '../types'
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
// Shared feature facts, derived from the model, for the three doc surfaces
|
|
7
|
+
// that describe features: the root README summary, the per-target README
|
|
8
|
+
// section, and REFERENCE.md.
|
|
9
|
+
//
|
|
10
|
+
// ONE SOURCE. The feature set, its defaults and its ordering constraints all
|
|
11
|
+
// come from `main.kit.feature`, so a feature added to the model documents
|
|
12
|
+
// itself everywhere rather than in whichever file someone remembered.
|
|
13
|
+
|
|
14
|
+
type FeatureDoc = {
|
|
15
|
+
name: string
|
|
16
|
+
Name: string
|
|
17
|
+
title: string
|
|
18
|
+
transport: string
|
|
19
|
+
wraps: boolean
|
|
20
|
+
options: Array<{ name: string, value: string }>
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
// `transport` says how a feature attaches, and that is the whole of the
|
|
25
|
+
// ordering story:
|
|
26
|
+
// wrap wraps the transport chain — activation order IS nesting order
|
|
27
|
+
// base installs the base transport others wrap (test)
|
|
28
|
+
// none pipeline hooks only; order does not affect it
|
|
29
|
+
function isWrapping(feat: any): boolean {
|
|
30
|
+
return 'wrap' === feat.transport
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
function renderValue(v: any): string {
|
|
35
|
+
if (null == v) { return '' }
|
|
36
|
+
if (Array.isArray(v)) { return '[' + v.map((x) => renderValue(x)).join(', ') + ']' }
|
|
37
|
+
if ('object' === typeof v) {
|
|
38
|
+
const keys = Object.keys(v)
|
|
39
|
+
return 0 === keys.length ? '{}' :
|
|
40
|
+
'{' + keys.map((k) => k + ': ' + renderValue(v[k])).join(', ') + '}'
|
|
41
|
+
}
|
|
42
|
+
if ('string' === typeof v) { return `'${v}'` }
|
|
43
|
+
return String(v)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
// Every feature the model declares active, in a stable order, with its
|
|
48
|
+
// options and their defaults.
|
|
49
|
+
function featureDocs(model: any): FeatureDoc[] {
|
|
50
|
+
const feature = getModelPath(model, `main.${KIT}.feature`)
|
|
51
|
+
|
|
52
|
+
return each(feature)
|
|
53
|
+
.filter((f: any) => false !== f.active && 'base' !== f.name)
|
|
54
|
+
.map((f: any) => {
|
|
55
|
+
const opts = (f.config && f.config.options) || {}
|
|
56
|
+
const options = Object.keys(opts).sort().map((k) => ({
|
|
57
|
+
name: k,
|
|
58
|
+
value: renderValue(opts[k]),
|
|
59
|
+
}))
|
|
60
|
+
return {
|
|
61
|
+
name: f.name,
|
|
62
|
+
Name: f.Name || f.name,
|
|
63
|
+
title: f.title || '',
|
|
64
|
+
transport: f.transport || 'none',
|
|
65
|
+
wraps: isWrapping(f),
|
|
66
|
+
options,
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
.sort((a: FeatureDoc, b: FeatureDoc) => a.name.localeCompare(b.name))
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
// Targets that compose transport features in a FIXED catalog order rather
|
|
76
|
+
// than the order the caller activates them in.
|
|
77
|
+
//
|
|
78
|
+
// Every other target derives `__derived__.featureorder` from the options and
|
|
79
|
+
// adds features in that order, so an ordered activation list is what fixes
|
|
80
|
+
// nesting. lean has no featureorder at all — SdkFeatures.featureNames is a
|
|
81
|
+
// fixed array — and its resolveFeatureOpts accepts only a map, silently
|
|
82
|
+
// replacing a list with an empty one. Telling a lean reader to activate
|
|
83
|
+
// features as an ordered list would therefore disable every feature they
|
|
84
|
+
// asked for.
|
|
85
|
+
//
|
|
86
|
+
// Verify with: grep -rl featureorder tm/<target>
|
|
87
|
+
const FIXED_ORDER_TARGETS = ['lean']
|
|
88
|
+
|
|
89
|
+
function honoursActivationOrder(target: any): boolean {
|
|
90
|
+
return !FIXED_ORDER_TARGETS.includes(target?.name)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
export {
|
|
95
|
+
featureDocs,
|
|
96
|
+
renderValue,
|
|
97
|
+
honoursActivationOrder,
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export type { FeatureDoc }
|
package/src/cmp/Readme.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { ReadmeEntity } from './ReadmeEntity'
|
|
|
13
13
|
import { ReadmeUnions } from './ReadmeUnions'
|
|
14
14
|
import { ReadmeHowto } from './ReadmeHowto'
|
|
15
15
|
import { ReadmeExplanation } from './ReadmeExplanation'
|
|
16
|
+
import { ReadmeFeatures } from './ReadmeFeatures'
|
|
16
17
|
import { ReadmeRef } from './ReadmeRef'
|
|
17
18
|
|
|
18
19
|
|
|
@@ -31,6 +32,7 @@ const Readme = cmp(function Readme(props: any) {
|
|
|
31
32
|
ReadmeModel({ target })
|
|
32
33
|
ReadmeOptions({ target })
|
|
33
34
|
ReadmeEntity({ target })
|
|
35
|
+
ReadmeFeatures({ target })
|
|
34
36
|
ReadmeUnions({ target })
|
|
35
37
|
ReadmeExplanation({ target })
|
|
36
38
|
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { cmp, Content } from 'jostraca'
|
|
2
|
+
|
|
3
|
+
import { requirePath } from '../utility'
|
|
4
|
+
import { featureDocs, honoursActivationOrder } from './FeatureDocs'
|
|
5
|
+
import type { FeatureDoc } from './FeatureDocs'
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
// The `## Features` section of a target README: one subsection per feature
|
|
9
|
+
// the SDK actually ships.
|
|
10
|
+
//
|
|
11
|
+
// WHY IT IS SHARED AND NOT PER-LANGUAGE. Every target activates features the
|
|
12
|
+
// same way — a `feature` entry in the client options — and the options and
|
|
13
|
+
// their defaults come from the model, so the substance is identical in all
|
|
14
|
+
// twenty-five languages. Only the literal syntax of the options map differs,
|
|
15
|
+
// and that is already shown once, in the Options section above. A per-target
|
|
16
|
+
// `ReadmeFeatures_<lang>.ts` may override this to add idiomatic snippets;
|
|
17
|
+
// none is required for the section to be correct.
|
|
18
|
+
const ReadmeFeatures = cmp(function ReadmeFeatures(props: any) {
|
|
19
|
+
const { target, ctx$ } = props
|
|
20
|
+
const { model } = ctx$
|
|
21
|
+
|
|
22
|
+
const override =
|
|
23
|
+
requirePath(ctx$, `./cmp/${target.name}/ReadmeFeatures_${target.name}`, { ignore: true })
|
|
24
|
+
|
|
25
|
+
if (override) {
|
|
26
|
+
override['ReadmeFeatures']({ target })
|
|
27
|
+
return
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const features: FeatureDoc[] = featureDocs(model)
|
|
31
|
+
|
|
32
|
+
if (0 === features.length) {
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
Content(`## Features
|
|
37
|
+
|
|
38
|
+
This SDK ships ${features.length} optional features. Each is **inactive until you
|
|
39
|
+
switch it on**, so an SDK you have not configured behaves exactly as if none of
|
|
40
|
+
them existed — no retries, no cache, no logging, no measurable overhead.
|
|
41
|
+
|
|
42
|
+
Activate a feature by name in the client options, alongside the options shown
|
|
43
|
+
above:
|
|
44
|
+
|
|
45
|
+
| Feature | What it does |
|
|
46
|
+
|---|---|
|
|
47
|
+
`)
|
|
48
|
+
|
|
49
|
+
for (const f of features) {
|
|
50
|
+
Content(`| [\`${f.name}\`](#${f.name}) | ${f.title} |
|
|
51
|
+
`)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
Content(`
|
|
55
|
+
`)
|
|
56
|
+
|
|
57
|
+
const wrapping = features.filter((f) => f.wraps).map((f) => '`' + f.name + '`')
|
|
58
|
+
if (1 < wrapping.length) {
|
|
59
|
+
if (honoursActivationOrder(target)) {
|
|
60
|
+
Content(`> **Order matters for ${wrapping.join(', ')}.** These wrap the
|
|
61
|
+
> transport, so each one wraps whatever is already installed: the order you
|
|
62
|
+
> activate them in IS the nesting order. Activating them as an ordered list
|
|
63
|
+
> rather than a map is what fixes that order.
|
|
64
|
+
|
|
65
|
+
`)
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
Content(`> **${wrapping.join(', ')} wrap the transport**, so each one wraps
|
|
69
|
+
> whatever is already installed. This SDK composes them in a fixed catalog
|
|
70
|
+
> order, not the order you activate them in, and takes \`feature\` as a map.
|
|
71
|
+
|
|
72
|
+
`)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
for (const f of features) {
|
|
77
|
+
Content(`### ${f.name}
|
|
78
|
+
|
|
79
|
+
${f.title}.
|
|
80
|
+
|
|
81
|
+
`)
|
|
82
|
+
|
|
83
|
+
if (0 < f.options.length) {
|
|
84
|
+
Content(`| Option | Default |
|
|
85
|
+
|---|---|
|
|
86
|
+
`)
|
|
87
|
+
for (const o of f.options) {
|
|
88
|
+
Content(`| \`${o.name}\` | \`${o.value}\` |
|
|
89
|
+
`)
|
|
90
|
+
}
|
|
91
|
+
Content(`
|
|
92
|
+
`)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
Content(`Set \`feature.${f.name}.active\` to enable it${
|
|
96
|
+
0 < f.options.length ? ', then override any of the options above' : ''}.
|
|
97
|
+
|
|
98
|
+
`)
|
|
99
|
+
|
|
100
|
+
if (f.wraps) {
|
|
101
|
+
Content(`\`${f.name}\` wraps the transport, so its position among the other
|
|
102
|
+
transport features decides what it sees. A feature activated later wraps one
|
|
103
|
+
activated earlier.
|
|
104
|
+
|
|
105
|
+
`)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
export {
|
|
112
|
+
ReadmeFeatures
|
|
113
|
+
}
|
package/src/cmp/ReadmeRef.ts
CHANGED
|
@@ -15,6 +15,10 @@ const ReadmeRef = cmp(function ReadmeRef(props: any) {
|
|
|
15
15
|
requirePath(ctx$, `./cmp/${target.name}/ReadmeRef_${target.name}`, { ignore: true })
|
|
16
16
|
|
|
17
17
|
if (ReadmeRef_sdk) {
|
|
18
|
+
// The per-language component owns the REFERENCE.md File, so anything
|
|
19
|
+
// appended out here lands outside it and silently vanishes. The shared
|
|
20
|
+
// feature reference is therefore called from INSIDE each
|
|
21
|
+
// ReadmeRef_<lang>.ts, at the end of its own features section.
|
|
18
22
|
ReadmeRef_sdk['ReadmeRef']({ target })
|
|
19
23
|
}
|
|
20
24
|
})
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { cmp, Content } from 'jostraca'
|
|
2
|
+
|
|
3
|
+
import { featureDocs, honoursActivationOrder } from './FeatureDocs'
|
|
4
|
+
import type { FeatureDoc } from './FeatureDocs'
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
// The feature reference, appended to every REFERENCE.md.
|
|
8
|
+
//
|
|
9
|
+
// REFERENCE.md is otherwise generated per-language, because the constructor
|
|
10
|
+
// signature, op spelling and code fence differ. Feature CONFIGURATION does
|
|
11
|
+
// not: the option names and defaults are model facts, identical in every
|
|
12
|
+
// target. So this section is written once and appended to all of them, rather
|
|
13
|
+
// than copied into twenty-five ReadmeRef_<lang>.ts files where it would drift.
|
|
14
|
+
//
|
|
15
|
+
// Covers what a reference has to cover and a README does not: every option
|
|
16
|
+
// with its default, what the feature does at runtime, and the considerations
|
|
17
|
+
// — ordering, interaction, and cost — that decide whether to switch it on.
|
|
18
|
+
const ReadmeRefFeatures = cmp(function ReadmeRefFeatures(props: any) {
|
|
19
|
+
const { target, ctx$ } = props
|
|
20
|
+
const { model } = ctx$
|
|
21
|
+
|
|
22
|
+
const features: FeatureDoc[] = featureDocs(model)
|
|
23
|
+
|
|
24
|
+
if (0 === features.length) {
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Emitted INSIDE the per-language `## Features` section, after its summary
|
|
29
|
+
// table and activation snippet, so these are subsections of it rather than
|
|
30
|
+
// a competing heading.
|
|
31
|
+
Content(`
|
|
32
|
+
### Configuring features
|
|
33
|
+
|
|
34
|
+
Each feature is inactive until switched on, and an SDK with no feature
|
|
35
|
+
configured does no feature work at all. Every option below keeps its default
|
|
36
|
+
unless you name it.
|
|
37
|
+
|
|
38
|
+
${honoursActivationOrder(target) ?
|
|
39
|
+
'The array form of \\`feature\\` is significant: several features wrap the\ntransport, and the order you list them in is the order they nest.' :
|
|
40
|
+
'This SDK takes \\`feature\\` as a map and composes the transport-wrapping\nfeatures in a fixed catalog order, so activation order does not change\nnesting here.'}
|
|
41
|
+
|
|
42
|
+
`)
|
|
43
|
+
|
|
44
|
+
const wrapping = features.filter((f) => f.wraps)
|
|
45
|
+
const hooked = features.filter((f) => !f.wraps)
|
|
46
|
+
|
|
47
|
+
if (0 < wrapping.length) {
|
|
48
|
+
Content(`#### Ordering
|
|
49
|
+
|
|
50
|
+
${wrapping.map((f) => '`' + f.name + '`').join(', ')} wrap the transport. Each
|
|
51
|
+
wraps whatever is already installed${honoursActivationOrder(target) ?
|
|
52
|
+
', so **activation order is nesting order**:\na feature activated later sits OUTSIDE one activated earlier, and sees the call\nfirst.' :
|
|
53
|
+
'. This SDK fixes that order in its own\ncatalog rather than taking it from the caller.'}
|
|
54
|
+
|
|
55
|
+
${(features.some((f) => 'cost' === f.name) && features.some((f) => 'cache' === f.name)) ?
|
|
56
|
+
`That decides behaviour, not just sequence. \\\`cost\\\` activated before \\\`cache\\\`
|
|
57
|
+
sits inside it, so a response served from the cache never reaches \\\`cost\\\` and is
|
|
58
|
+
correctly charged nothing; reverse them and every cache hit is billed for money
|
|
59
|
+
that was never spent.
|
|
60
|
+
` : `That decides behaviour, not just sequence: a feature that short-circuits the
|
|
61
|
+
call, such as a cache serving a hit, stops every feature nested inside it from
|
|
62
|
+
ever seeing that call.
|
|
63
|
+
`}
|
|
64
|
+
${hooked.map((f) => '`' + f.name + '`').join(', ')} attach to pipeline hooks
|
|
65
|
+
rather than the transport, so their order does not affect what they observe.
|
|
66
|
+
|
|
67
|
+
`)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
for (const f of features) {
|
|
71
|
+
Content(`#### \`${f.name}\`
|
|
72
|
+
|
|
73
|
+
${f.title}.
|
|
74
|
+
|
|
75
|
+
`)
|
|
76
|
+
|
|
77
|
+
Content(`**Configuration**
|
|
78
|
+
|
|
79
|
+
`)
|
|
80
|
+
|
|
81
|
+
// THE TABLE IS THE MODEL'S OPTIONS, AND THE MODEL IS INCOMPLETE. Several
|
|
82
|
+
// features accept runtime-only options the model never declares — cost
|
|
83
|
+
// reads `actor` and `sink`, audit reads `sink` — so a table derived from
|
|
84
|
+
// `config.options` cannot list them. Deriving from a second hand-written
|
|
85
|
+
// list would reintroduce exactly the drift this module exists to avoid,
|
|
86
|
+
// so the honest fix is upstream: declare them in the feature model. Until
|
|
87
|
+
// then, say so rather than imply the table is exhaustive.
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
if (0 < f.options.length) {
|
|
91
|
+
Content(`| Option | Default |
|
|
92
|
+
|---|---|
|
|
93
|
+
`)
|
|
94
|
+
for (const o of f.options) {
|
|
95
|
+
Content(`| \`${o.name}\` | \`${o.value}\` |
|
|
96
|
+
`)
|
|
97
|
+
}
|
|
98
|
+
Content(`
|
|
99
|
+
`)
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
Content(`\`active\` only — this feature takes no further options.
|
|
103
|
+
|
|
104
|
+
`)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
Content(`Options above are those the model carries a default for. A feature may
|
|
108
|
+
also accept callback options — a \`sink\` to receive each record, for
|
|
109
|
+
instance — which have no default and are covered in the full feature
|
|
110
|
+
reference.
|
|
111
|
+
|
|
112
|
+
**Usage**
|
|
113
|
+
|
|
114
|
+
Set \`feature.${f.name}.active\` to true in the client options${
|
|
115
|
+
0 < f.options.length ?
|
|
116
|
+
', and override any option above in the same entry' : ''}. Every option keeps
|
|
117
|
+
its default unless you name it.
|
|
118
|
+
|
|
119
|
+
**Considerations**
|
|
120
|
+
|
|
121
|
+
`)
|
|
122
|
+
|
|
123
|
+
if (f.wraps) {
|
|
124
|
+
Content(`- Wraps the transport: its place in the activation order decides what it
|
|
125
|
+
sees. See [Ordering](#ordering) above.
|
|
126
|
+
`)
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
Content(`- Attaches to pipeline hooks, not the transport, so activation order does
|
|
130
|
+
not change what it observes.
|
|
131
|
+
`)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if ('base' === f.transport) {
|
|
135
|
+
Content(`- Installs the BASE transport that the wrapping features wrap, so it must be
|
|
136
|
+
activated before them.
|
|
137
|
+
`)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
Content(`- Inactive by default: leaving it out costs nothing at runtime.
|
|
141
|
+
|
|
142
|
+
`)
|
|
143
|
+
}
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
export {
|
|
148
|
+
ReadmeRefFeatures
|
|
149
|
+
}
|
package/src/cmp/ReadmeTop.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
} from '../types'
|
|
8
8
|
|
|
9
9
|
import { requirePath } from '../utility'
|
|
10
|
+
import { featureDocs } from './FeatureDocs'
|
|
10
11
|
|
|
11
12
|
import {
|
|
12
13
|
entityPrimaryOp, entityIdField, opRequestShape, entityPath, entityActions,
|
|
@@ -206,6 +207,20 @@ Learn more about Voxgig SDKs at [voxgig.com/sdk](${VOXGIG_SDK}).
|
|
|
206
207
|
: surfaces[0]
|
|
207
208
|
Content(`> ${surfaceList} — all generated from one OpenAPI spec by [@voxgig/sdkgen](${SDKGEN_REPO}).
|
|
208
209
|
|
|
210
|
+
`)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// FEATURES BELONG BESIDE THE TARGETS. What an SDK can do is as much a
|
|
214
|
+
// reason to choose it as which language it is in, and a reader who has to
|
|
215
|
+
// scroll past every language section to discover that retries, caching and
|
|
216
|
+
// tracing are built in has already decided it is a thin HTTP wrapper.
|
|
217
|
+
// Named here, in one line, with the detail left to each target's README.
|
|
218
|
+
const features = featureDocs(model)
|
|
219
|
+
if (0 < features.length) {
|
|
220
|
+
Content(`> **Features:** ${features.map((f: any) => '`' + f.name + '`').join(', ')} — opt-in,
|
|
221
|
+
> inactive until switched on, and configured per client. See the Features
|
|
222
|
+
> section of any SDK README below for what each one does.
|
|
223
|
+
|
|
209
224
|
`)
|
|
210
225
|
}
|
|
211
226
|
|
|
@@ -561,6 +576,39 @@ forking the SDK.
|
|
|
561
576
|
Content(`
|
|
562
577
|
Pass custom features via the \`extend\` option at construction time.
|
|
563
578
|
|
|
579
|
+
`)
|
|
580
|
+
|
|
581
|
+
// 11c. Customization — the generator's whole story for "the output is
|
|
582
|
+
// not quite right": model-driven declarations, in-repo templates and
|
|
583
|
+
// components, merge-aware regeneration, and packages for custom targets
|
|
584
|
+
// and features. Every SDK repo carries its own generator, so this
|
|
585
|
+
// belongs in every README.
|
|
586
|
+
Content(`## Customizing this SDK
|
|
587
|
+
|
|
588
|
+
This repository contains its own generator (\`.sdk/\`), so the SDK is
|
|
589
|
+
customizable without forking any upstream tool:
|
|
590
|
+
|
|
591
|
+
- **The model** (\`.sdk/model/\`) declares everything this project owns:
|
|
592
|
+
package names, versions, active features, per-target settings. It is
|
|
593
|
+
written in [aontu](https://github.com/aontu-lang/aontu), a JSON-based
|
|
594
|
+
specification language designed for building ontologies: easy to edit
|
|
595
|
+
by hand, and files unify rather than override, so small declarations
|
|
596
|
+
compose into one model. Regeneration re-reads it every time.
|
|
597
|
+
- **Templates** (\`.sdk/tm/\`) and **components** (\`.sdk/src/cmp/\`) are
|
|
598
|
+
the two layers of generation, copied into this repo: templates are the
|
|
599
|
+
literal per-language source, components generate the API-shaped parts.
|
|
600
|
+
- **Regeneration merges.** By default, newly generated content is
|
|
601
|
+
three-way merged into existing files, so generator updates and local
|
|
602
|
+
edits usually converge without manual conflict handling. A project can
|
|
603
|
+
opt for plain overwrite instead.
|
|
604
|
+
- **Custom features and entire custom targets** arrive through sdkgen
|
|
605
|
+
packages (\`voxgig-sdkgen package add\`), on the same rails as the
|
|
606
|
+
bundled languages, and \`voxgig-sdkgen doctor\` reports any drift from
|
|
607
|
+
what a resync would write.
|
|
608
|
+
|
|
609
|
+
How-to: [customize and propagate templates](https://github.com/voxgig/sdkgen/blob/main/docs/how-to/customize-and-propagate-templates.md).
|
|
610
|
+
The full story: [voxgig.com/sdk/custom](https://voxgig.com/sdk/custom).
|
|
611
|
+
|
|
564
612
|
`)
|
|
565
613
|
|
|
566
614
|
// 12. Per-language docs links
|
package/src/helpers/opShape.ts
CHANGED
|
@@ -269,7 +269,11 @@ function opParams(op: any): any[] {
|
|
|
269
269
|
const out: any[] = []
|
|
270
270
|
|
|
271
271
|
points.forEach((pt: any, pointIndex: number) => {
|
|
272
|
-
|
|
272
|
+
// Path AND query: a path-param-only read misses e.g. GET /result?trace_id=,
|
|
273
|
+
// which has no path param at all but still addresses one record.
|
|
274
|
+
const pathParams = pt && pt.args && pt.args.params ? each(pt.args.params) : []
|
|
275
|
+
const queryParams = pt && pt.args && pt.args.query ? each(pt.args.query) : []
|
|
276
|
+
const params = [...pathParams, ...queryParams]
|
|
273
277
|
const requiredHere: Record<string, boolean> = {}
|
|
274
278
|
params.forEach((p: any) => {
|
|
275
279
|
if (p && null != p.name) {
|
package/src/sdkgen.ts
CHANGED
|
@@ -17,7 +17,7 @@ import type {
|
|
|
17
17
|
ActionResult,
|
|
18
18
|
} from './types'
|
|
19
19
|
|
|
20
|
-
import { SdkGenError, requirePath, isAuthActive, resolveAuthPrefix,
|
|
20
|
+
import { SdkGenError, requirePath, isAuthActive, resolveAuthPrefix, isHttpBasicAuth,
|
|
21
21
|
CONFIG_DATA_THRESHOLD, CONFIG_REPR_VALUES, isConfigData, configRepr,
|
|
22
22
|
configReprSetting, configDefinition, clean, rawStringLiteral } from './utility'
|
|
23
23
|
|
|
@@ -49,6 +49,7 @@ import { ReadmeEntity } from './cmp/ReadmeEntity'
|
|
|
49
49
|
import { ReadmeHowto } from './cmp/ReadmeHowto'
|
|
50
50
|
import { ReadmeExplanation } from './cmp/ReadmeExplanation'
|
|
51
51
|
import { ReadmeRef } from './cmp/ReadmeRef'
|
|
52
|
+
import { ReadmeRefFeatures } from './cmp/ReadmeRefFeatures'
|
|
52
53
|
import { FeatureHook } from './cmp/FeatureHook'
|
|
53
54
|
import { registerComponent } from './cmp/Registered'
|
|
54
55
|
import type { RegisterOptions } from './cmp/Registered'
|
|
@@ -392,10 +393,20 @@ function SdkGen(opts: SdkGenOptions) {
|
|
|
392
393
|
// A skipped external target is still removed from the in-tree model
|
|
393
394
|
// above: neither `active: false` nor a missing `output.create: false`
|
|
394
395
|
// destination may relocate it into `<sdk-repo>/<target>/`.
|
|
395
|
-
|
|
396
|
+
// The snapshot is deliberately not re-taken to ADMIT an item: a
|
|
397
|
+
// destination that appeared since would be written without ever having
|
|
398
|
+
// been ownership-validated. Re-taking it to SKIP one is the opposite
|
|
399
|
+
// direction and cannot open that hole. Without this, an optional
|
|
400
|
+
// destination deleted or moved between the snapshot and this pass is
|
|
401
|
+
// recreated by jostraca's ensureDir — exactly what `output.create:
|
|
402
|
+
// false` promises not to do.
|
|
403
|
+
const gone = false === ext.target.output?.create && !fs.existsSync(ext.folder)
|
|
404
|
+
|
|
405
|
+
if (null != ext.skip || gone) {
|
|
396
406
|
log.info({
|
|
397
407
|
point: 'generate-external-skip', target: ext.name, folder: ext.folder,
|
|
398
|
-
note: ext.skip
|
|
408
|
+
note: ext.skip ?? (ext.name + ' output folder disappeared after ' +
|
|
409
|
+
'preflight and output.create=false, not generated')
|
|
399
410
|
})
|
|
400
411
|
continue
|
|
401
412
|
}
|
|
@@ -794,10 +805,18 @@ function checkExternalFolders(external: ExternalPlan[], root: string, fs: any) {
|
|
|
794
805
|
const claimed: Record<string, string> = {}
|
|
795
806
|
|
|
796
807
|
for (const ext of external) {
|
|
797
|
-
//
|
|
798
|
-
//
|
|
799
|
-
//
|
|
800
|
-
|
|
808
|
+
// An INACTIVE item writes nothing and claims nothing: it is out of the
|
|
809
|
+
// model for this run.
|
|
810
|
+
//
|
|
811
|
+
// An item skipped only because its optional destination is absent is a
|
|
812
|
+
// different case. Path containment and the duplicate claim are model
|
|
813
|
+
// invariants — they do not depend on folder contents — so skipping them
|
|
814
|
+
// here let two items claim one absent path: the ordinary one generated
|
|
815
|
+
// there, and the NEXT identical run then saw the folder, detected the
|
|
816
|
+
// duplicate, and refused everything. The failure appeared one run after
|
|
817
|
+
// the mistake. Validate the path; the `existsSync` guard below still
|
|
818
|
+
// skips every check that reads the folder.
|
|
819
|
+
if (!ext.active) continue
|
|
801
820
|
|
|
802
821
|
const label = ext.kind.charAt(0).toUpperCase() + ext.kind.slice(1)
|
|
803
822
|
|
|
@@ -1067,6 +1086,7 @@ export {
|
|
|
1067
1086
|
ReadmeHowto,
|
|
1068
1087
|
ReadmeExplanation,
|
|
1069
1088
|
ReadmeRef,
|
|
1089
|
+
ReadmeRefFeatures,
|
|
1070
1090
|
FeatureHook,
|
|
1071
1091
|
registerComponent,
|
|
1072
1092
|
|
|
@@ -1076,6 +1096,7 @@ export {
|
|
|
1076
1096
|
requirePath,
|
|
1077
1097
|
isAuthActive,
|
|
1078
1098
|
resolveAuthPrefix,
|
|
1099
|
+
isHttpBasicAuth,
|
|
1079
1100
|
CONFIG_DATA_THRESHOLD,
|
|
1080
1101
|
CONFIG_REPR_VALUES,
|
|
1081
1102
|
isConfigData,
|
package/src/utility.ts
CHANGED
|
@@ -62,6 +62,24 @@ function resolveAuthPrefix(model: any): string {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
|
|
65
|
+
// True when the spec's security scheme is genuine HTTP Basic Auth (two
|
|
66
|
+
// credentials, base64-joined) rather than a single bearer-style token with
|
|
67
|
+
// a prefix. Priority order mirrors resolveAuthPrefix:
|
|
68
|
+
// 1. main.kit.config.auth.basic (per-SDK user override)
|
|
69
|
+
// 2. main.kit.info.security, spec-derived: type 'http' + a 'basic' prefix
|
|
70
|
+
// (apidef sets prefix from the OpenAPI `scheme: basic` value, title-cased)
|
|
71
|
+
function isHttpBasicAuth(model: any): boolean {
|
|
72
|
+
const auth = getModelPath(model, `main.${KIT}.config.auth`,
|
|
73
|
+
{ only_active: false, required: false })
|
|
74
|
+
if (null != auth && null != auth.basic) return Boolean(auth.basic)
|
|
75
|
+
|
|
76
|
+
const security = getModelPath(model, `main.${KIT}.info.security`,
|
|
77
|
+
{ only_active: false, required: false })
|
|
78
|
+
return null != security && 'http' === security.type &&
|
|
79
|
+
'basic' === String(security.prefix || '').toLowerCase()
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
|
|
65
83
|
function requirePath(ctx$: any, path: string, flags?: { ignore?: boolean }): any {
|
|
66
84
|
const fullpath = resolvePath(ctx$, path)
|
|
67
85
|
const ignore = null == flags?.ignore ? false : flags.ignore
|
|
@@ -98,6 +116,7 @@ export {
|
|
|
98
116
|
requirePath,
|
|
99
117
|
isAuthActive,
|
|
100
118
|
resolveAuthPrefix,
|
|
119
|
+
isHttpBasicAuth,
|
|
101
120
|
SdkGenError,
|
|
102
121
|
CONFIG_DATA_THRESHOLD,
|
|
103
122
|
CONFIG_REPR_VALUES,
|
|
@@ -298,6 +317,7 @@ function configDefinition(model: any, targetname?: string): { def: any, json: st
|
|
|
298
317
|
|
|
299
318
|
const authActive = isAuthActive(model)
|
|
300
319
|
const authPrefix = resolveAuthPrefix(model)
|
|
320
|
+
const authBasic = isHttpBasicAuth(model)
|
|
301
321
|
|
|
302
322
|
let baseUrl = ''
|
|
303
323
|
try { baseUrl = getModelPath(model, `main.${KIT}.info.servers.0.url`) } catch (_e) { }
|
|
@@ -340,7 +360,7 @@ function configDefinition(model: any, targetname?: string): { def: any, json: st
|
|
|
340
360
|
options.server = svars.reduce((a: any, v: any) => (a[v.name] = v.dflt, a), {})
|
|
341
361
|
}
|
|
342
362
|
if (authActive) {
|
|
343
|
-
options.auth = { prefix: authPrefix }
|
|
363
|
+
options.auth = authBasic ? { prefix: authPrefix, basic: true } : { prefix: authPrefix }
|
|
344
364
|
}
|
|
345
365
|
options.headers = headers
|
|
346
366
|
options.entity = entityStubs
|