@voxgig/sdkgen 4.7.0 → 4.8.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.
Files changed (50) hide show
  1. package/bin/voxgig-sdkgen +1 -1
  2. package/dist/helpers/opShape.js +8 -11
  3. package/dist/helpers/opShape.js.map +1 -1
  4. package/dist/helpers/pointPath.d.ts +10 -0
  5. package/dist/helpers/pointPath.js +54 -0
  6. package/dist/helpers/pointPath.js.map +1 -0
  7. package/dist/sdkgen.d.ts +4 -2
  8. package/dist/sdkgen.js +8 -3
  9. package/dist/sdkgen.js.map +1 -1
  10. package/dist/tsconfig.tsbuildinfo +1 -1
  11. package/dist/utility.js +26 -1
  12. package/dist/utility.js.map +1 -1
  13. package/package.json +2 -2
  14. package/project/.sdk/src/cmp/c/Config_c.ts +7 -8
  15. package/project/.sdk/src/cmp/c/TestDirect_c.ts +4 -3
  16. package/project/.sdk/src/cmp/cpp/TestDirect_cpp.ts +4 -3
  17. package/project/.sdk/src/cmp/csharp/TestDirect_csharp.ts +5 -4
  18. package/project/.sdk/src/cmp/dart/Config_dart.ts +7 -8
  19. package/project/.sdk/src/cmp/dart/TestDirect_dart.ts +6 -5
  20. package/project/.sdk/src/cmp/elixir/Config_elixir.ts +9 -8
  21. package/project/.sdk/src/cmp/go/Config_go.ts +1 -7
  22. package/project/.sdk/src/cmp/go/TestDirect_go.ts +5 -4
  23. package/project/.sdk/src/cmp/java/Config_java.ts +7 -7
  24. package/project/.sdk/src/cmp/java/TestDirect_java.ts +5 -4
  25. package/project/.sdk/src/cmp/js/Config_js.ts +7 -8
  26. package/project/.sdk/src/cmp/js/TestDirect_js.ts +5 -4
  27. package/project/.sdk/src/cmp/kotlin/Config_kotlin.ts +7 -7
  28. package/project/.sdk/src/cmp/kotlin/TestDirect_kotlin.ts +5 -4
  29. package/project/.sdk/src/cmp/lean/Config_lean.ts +9 -8
  30. package/project/.sdk/src/cmp/lua/Config_lua.ts +1 -7
  31. package/project/.sdk/src/cmp/lua/TestDirect_lua.ts +5 -4
  32. package/project/.sdk/src/cmp/perl/TestDirect_perl.ts +5 -4
  33. package/project/.sdk/src/cmp/php/Config_php.ts +1 -7
  34. package/project/.sdk/src/cmp/php/TestDirect_php.ts +5 -4
  35. package/project/.sdk/src/cmp/py/Config_py.ts +1 -7
  36. package/project/.sdk/src/cmp/py/TestDirect_py.ts +5 -4
  37. package/project/.sdk/src/cmp/rb/Config_rb.ts +1 -7
  38. package/project/.sdk/src/cmp/rb/TestDirect_rb.ts +5 -4
  39. package/project/.sdk/src/cmp/rust/TestDirect_rust.ts +5 -4
  40. package/project/.sdk/src/cmp/scala/Config_scala.ts +9 -7
  41. package/project/.sdk/src/cmp/scala/TestDirect_scala.ts +4 -3
  42. package/project/.sdk/src/cmp/seneca-provider/Extras_seneca-provider.ts +3 -1
  43. package/project/.sdk/src/cmp/seneca-provider/Main_seneca-provider.ts +9 -5
  44. package/project/.sdk/src/cmp/ts/Config_ts.ts +7 -8
  45. package/project/.sdk/src/cmp/ts/TestDirect_ts.ts +6 -5
  46. package/project/sdkgen-package.json +1 -1
  47. package/src/helpers/opShape.ts +9 -14
  48. package/src/helpers/pointPath.ts +88 -0
  49. package/src/sdkgen.ts +11 -0
  50. package/src/utility.ts +31 -1
@@ -13,7 +13,8 @@ import {
13
13
  snakify,
14
14
  isAuthActive, envName, envToken,
15
15
  serverVarEnv,
16
- serverVariables
16
+ serverVariables,
17
+ pointParts,
17
18
  } from '@voxgig/sdkgen'
18
19
 
19
20
  import { formatLuaValue } from './utility_lua'
@@ -99,7 +100,7 @@ const TestDirect = cmp(function TestDirect(props: any) {
99
100
  const listOp = entity.op?.list
100
101
 
101
102
  const loadPoint = loadOp?.points?.[0]
102
- const loadPath = loadPoint ? normalizePathParams(loadPoint.parts || [], loadPoint?.args?.params || [], loadPoint?.rename?.param) : ''
103
+ const loadPath = loadPoint ? normalizePathParams(pointParts(loadPoint), loadPoint?.args?.params || [], loadPoint?.rename?.param) : ''
103
104
  const allLoadParams = loadPoint?.args?.params || []
104
105
  // Some upstream OpenAPI specs declare a parameter as `in: path` even when
105
106
  // that path has no `{name}` placeholder for it. Only path params that
@@ -107,7 +108,7 @@ const TestDirect = cmp(function TestDirect(props: any) {
107
108
  // setup and URL-substitution asserts; otherwise the SDK silently drops
108
109
  // them and the URL-includes assert fails.
109
110
  const _pathPlaceholders = new Set<string>()
110
- for (const part of (loadPoint?.parts || [])) {
111
+ for (const part of pointParts(loadPoint)) {
111
112
  if (typeof part === 'string' && part.startsWith('{') && part.endsWith('}')) {
112
113
  _pathPlaceholders.add(part.slice(1, -1))
113
114
  }
@@ -124,7 +125,7 @@ const TestDirect = cmp(function TestDirect(props: any) {
124
125
  _renamedPlaceholders.has(p.name) || _renamedPlaceholders.has(p.orig))
125
126
 
126
127
  const listPoint = listOp?.points?.[0]
127
- const listPath = listPoint ? normalizePathParams(listPoint.parts || [], listPoint?.args?.params || [], listPoint?.rename?.param) : ''
128
+ const listPath = listPoint ? normalizePathParams(pointParts(listPoint), listPoint?.args?.params || [], listPoint?.rename?.param) : ''
128
129
  const listParams = listPoint?.args?.params || []
129
130
 
130
131
  // Required query params with spec-provided examples — needed in live mode.
@@ -13,7 +13,8 @@ import {
13
13
  snakify,
14
14
  isAuthActive, envName, envToken,
15
15
  serverVarEnv,
16
- serverVariables
16
+ serverVariables,
17
+ pointParts,
17
18
  } from '@voxgig/sdkgen'
18
19
 
19
20
  import { perlStringLiteral } from './utility_perl'
@@ -109,12 +110,12 @@ const TestDirect = cmp(function TestDirect(props: any) {
109
110
  const listOp = entity.op?.list
110
111
 
111
112
  const loadPoint = loadOp?.points?.[0]
112
- const loadPath = loadPoint ? normalizePathParams(loadPoint.parts || [], loadPoint?.args?.params || [], loadPoint?.rename?.param) : ''
113
+ const loadPath = loadPoint ? normalizePathParams(pointParts(loadPoint), loadPoint?.args?.params || [], loadPoint?.rename?.param) : ''
113
114
  const allLoadParams = loadPoint?.args?.params || []
114
115
  // Only path params that actually appear in the URL template drive direct-
115
116
  // test path-param setup and URL-substitution asserts (see TestDirect_rb).
116
117
  const _pathPlaceholders = new Set<string>()
117
- for (const part of (loadPoint?.parts || [])) {
118
+ for (const part of pointParts(loadPoint)) {
118
119
  if (typeof part === 'string' && part.startsWith('{') && part.endsWith('}')) {
119
120
  _pathPlaceholders.add(part.slice(1, -1))
120
121
  }
@@ -131,7 +132,7 @@ const TestDirect = cmp(function TestDirect(props: any) {
131
132
  _renamedPlaceholders.has(p.name) || _renamedPlaceholders.has(p.orig))
132
133
 
133
134
  const listPoint = listOp?.points?.[0]
134
- const listPath = listPoint ? normalizePathParams(listPoint.parts || [], listPoint?.args?.params || [], listPoint?.rename?.param) : ''
135
+ const listPath = listPoint ? normalizePathParams(pointParts(listPoint), listPoint?.args?.params || [], listPoint?.rename?.param) : ''
135
136
  const listParams = listPoint?.args?.params || []
136
137
 
137
138
  // Required query params with spec-provided examples - needed in live mode.
@@ -29,7 +29,6 @@ import {
29
29
 
30
30
 
31
31
  import {
32
- clean,
33
32
  formatPhpArray,
34
33
  } from './utility_php'
35
34
 
@@ -239,12 +238,7 @@ ${serverBlock}${authBlock} "headers" => ${formatPhpArray(headers,
239
238
  Content(` ],
240
239
  ],
241
240
  "entity" => ${formatPhpArray(
242
- Object.values(entity).reduce((a: any, n: any) => (a[n.name] = clean({
243
- fields: n.fields,
244
- name: n.name,
245
- op: n.op,
246
- relations: n.relations,
247
- }, true), a), {}), 3)},
241
+ configDef.entity, 3)},
248
242
  ];
249
243
  `)
250
244
  }
@@ -13,7 +13,8 @@ import {
13
13
  snakify,
14
14
  isAuthActive, envName, envToken,
15
15
  serverVarEnv,
16
- serverVariables
16
+ serverVariables,
17
+ pointParts,
17
18
  } from '@voxgig/sdkgen'
18
19
 
19
20
  import { formatPhpValue } from './utility_php'
@@ -99,7 +100,7 @@ const TestDirect = cmp(function TestDirect(props: any) {
99
100
  const listOp = entity.op?.list
100
101
 
101
102
  const loadPoint = loadOp?.points?.[0]
102
- const loadPath = loadPoint ? normalizePathParams(loadPoint.parts || [], loadPoint?.args?.params || [], loadPoint?.rename?.param) : ''
103
+ const loadPath = loadPoint ? normalizePathParams(pointParts(loadPoint), loadPoint?.args?.params || [], loadPoint?.rename?.param) : ''
103
104
  const allLoadParams = loadPoint?.args?.params || []
104
105
  // Some upstream OpenAPI specs declare a parameter as `in: path` even when
105
106
  // that path has no `{name}` placeholder for it. Only path params that
@@ -107,7 +108,7 @@ const TestDirect = cmp(function TestDirect(props: any) {
107
108
  // setup and URL-substitution asserts; otherwise the SDK silently drops
108
109
  // them and the URL-includes assert fails.
109
110
  const _pathPlaceholders = new Set<string>()
110
- for (const part of (loadPoint?.parts || [])) {
111
+ for (const part of pointParts(loadPoint)) {
111
112
  if (typeof part === 'string' && part.startsWith('{') && part.endsWith('}')) {
112
113
  _pathPlaceholders.add(part.slice(1, -1))
113
114
  }
@@ -124,7 +125,7 @@ const TestDirect = cmp(function TestDirect(props: any) {
124
125
  _renamedPlaceholders.has(p.name) || _renamedPlaceholders.has(p.orig))
125
126
 
126
127
  const listPoint = listOp?.points?.[0]
127
- const listPath = listPoint ? normalizePathParams(listPoint.parts || [], listPoint?.args?.params || [], listPoint?.rename?.param) : ''
128
+ const listPath = listPoint ? normalizePathParams(pointParts(listPoint), listPoint?.args?.params || [], listPoint?.rename?.param) : ''
128
129
  const listParams = listPoint?.args?.params || []
129
130
 
130
131
  // Required query params with spec-provided examples — needed in live mode.
@@ -28,7 +28,6 @@ import {
28
28
 
29
29
 
30
30
  import {
31
- clean,
32
31
  formatPyDict,
33
32
  } from './utility_py'
34
33
 
@@ -180,12 +179,7 @@ ${serverBlock}${authBlock} "headers": ${formatPyDict(headers, 3)},
180
179
  Content(` },
181
180
  },
182
181
  "entity": ${formatPyDict(
183
- Object.values(entity).reduce((a: any, n: any) => (a[n.name] = clean({
184
- fields: n.fields,
185
- name: n.name,
186
- op: n.op,
187
- relations: n.relations,
188
- }, true), a), {}), 2)},
182
+ configDef.entity, 2)},
189
183
  }
190
184
  `)
191
185
  })
@@ -13,7 +13,8 @@ import {
13
13
  snakify,
14
14
  isAuthActive,
15
15
  serverVarEnv,
16
- serverVariables, envName, envToken
16
+ serverVariables, envName, envToken,
17
+ pointParts,
17
18
  } from '@voxgig/sdkgen'
18
19
 
19
20
 
@@ -98,7 +99,7 @@ const TestDirect = cmp(function TestDirect(props: any) {
98
99
  const listOp = entity.op?.list
99
100
 
100
101
  const loadPoint = loadOp?.points?.[0]
101
- const loadPath = loadPoint ? normalizePathParams(loadPoint.parts || [], loadPoint?.args?.params || [], loadPoint?.rename?.param) : ''
102
+ const loadPath = loadPoint ? normalizePathParams(pointParts(loadPoint), loadPoint?.args?.params || [], loadPoint?.rename?.param) : ''
102
103
  const allLoadParams = loadPoint?.args?.params || []
103
104
  // Some upstream OpenAPI specs declare a parameter as `in: path` even when
104
105
  // that path has no `{name}` placeholder for it. Only path params that
@@ -106,7 +107,7 @@ const TestDirect = cmp(function TestDirect(props: any) {
106
107
  // setup and URL-substitution asserts; otherwise the SDK silently drops
107
108
  // them and the URL-includes assert fails.
108
109
  const _pathPlaceholders = new Set<string>()
109
- for (const part of (loadPoint?.parts || [])) {
110
+ for (const part of pointParts(loadPoint)) {
110
111
  if (typeof part === 'string' && part.startsWith('{') && part.endsWith('}')) {
111
112
  _pathPlaceholders.add(part.slice(1, -1))
112
113
  }
@@ -123,7 +124,7 @@ const TestDirect = cmp(function TestDirect(props: any) {
123
124
  _renamedPlaceholders.has(p.name) || _renamedPlaceholders.has(p.orig))
124
125
 
125
126
  const listPoint = listOp?.points?.[0]
126
- const listPath = listPoint ? normalizePathParams(listPoint.parts || [], listPoint?.args?.params || [], listPoint?.rename?.param) : ''
127
+ const listPath = listPoint ? normalizePathParams(pointParts(listPoint), listPoint?.args?.params || [], listPoint?.rename?.param) : ''
127
128
  const listParams = listPoint?.args?.params || []
128
129
 
129
130
  // Required query params with spec-provided examples — needed in live mode
@@ -29,7 +29,6 @@ import {
29
29
 
30
30
 
31
31
  import {
32
- clean,
33
32
  formatRubyHash,
34
33
  } from './utility_rb'
35
34
 
@@ -165,12 +164,7 @@ ${serverBlock}${authBlock} "headers" => ${formatRubyHash(headers, 4)},
165
164
  Content(` },
166
165
  },
167
166
  "entity" => ${formatRubyHash(
168
- Object.values(entity).reduce((a: any, n: any) => (a[n.name] = clean({
169
- fields: n.fields,
170
- name: n.name,
171
- op: n.op,
172
- relations: n.relations,
173
- }, true), a), {}), 3)},
167
+ configDef.entity, 3)},
174
168
  }
175
169
  end
176
170
  `)
@@ -13,7 +13,8 @@ import {
13
13
  snakify,
14
14
  isAuthActive, envName, envToken,
15
15
  serverVarEnv,
16
- serverVariables
16
+ serverVariables,
17
+ pointParts,
17
18
  } from '@voxgig/sdkgen'
18
19
 
19
20
  import { formatRubyValue } from './utility_rb'
@@ -99,7 +100,7 @@ const TestDirect = cmp(function TestDirect(props: any) {
99
100
  const listOp = entity.op?.list
100
101
 
101
102
  const loadPoint = loadOp?.points?.[0]
102
- const loadPath = loadPoint ? normalizePathParams(loadPoint.parts || [], loadPoint?.args?.params || [], loadPoint?.rename?.param) : ''
103
+ const loadPath = loadPoint ? normalizePathParams(pointParts(loadPoint), loadPoint?.args?.params || [], loadPoint?.rename?.param) : ''
103
104
  const allLoadParams = loadPoint?.args?.params || []
104
105
  // Some upstream OpenAPI specs declare a parameter as `in: path` even when
105
106
  // that path has no `{name}` placeholder for it. Only path params that
@@ -107,7 +108,7 @@ const TestDirect = cmp(function TestDirect(props: any) {
107
108
  // setup and URL-substitution asserts; otherwise the SDK silently drops
108
109
  // them and the URL-includes assert fails.
109
110
  const _pathPlaceholders = new Set<string>()
110
- for (const part of (loadPoint?.parts || [])) {
111
+ for (const part of pointParts(loadPoint)) {
111
112
  if (typeof part === 'string' && part.startsWith('{') && part.endsWith('}')) {
112
113
  _pathPlaceholders.add(part.slice(1, -1))
113
114
  }
@@ -124,7 +125,7 @@ const TestDirect = cmp(function TestDirect(props: any) {
124
125
  _renamedPlaceholders.has(p.name) || _renamedPlaceholders.has(p.orig))
125
126
 
126
127
  const listPoint = listOp?.points?.[0]
127
- const listPath = listPoint ? normalizePathParams(listPoint.parts || [], listPoint?.args?.params || [], listPoint?.rename?.param) : ''
128
+ const listPath = listPoint ? normalizePathParams(pointParts(listPoint), listPoint?.args?.params || [], listPoint?.rename?.param) : ''
128
129
  const listParams = listPoint?.args?.params || []
129
130
 
130
131
  // Required query params with spec-provided examples — needed in live mode.
@@ -13,7 +13,8 @@ import {
13
13
  snakify,
14
14
  isAuthActive, envName, envToken,
15
15
  serverVarEnv,
16
- serverVariables
16
+ serverVariables,
17
+ pointParts,
17
18
  } from '@voxgig/sdkgen'
18
19
 
19
20
 
@@ -104,12 +105,12 @@ const TestDirect = cmp(function TestDirect(props: any) {
104
105
 
105
106
  // Load point info.
106
107
  const loadPoint = loadOp?.points?.[0]
107
- const loadPath = loadPoint ? normalizePathParams(loadPoint.parts || [], loadPoint?.args?.params || [], loadPoint?.rename?.param) : ''
108
+ const loadPath = loadPoint ? normalizePathParams(pointParts(loadPoint), loadPoint?.args?.params || [], loadPoint?.rename?.param) : ''
108
109
  const allLoadParams = loadPoint?.args?.params || []
109
110
  // Only path params that actually appear in the URL template drive the
110
111
  // direct-test path-param setup and URL-substitution asserts.
111
112
  const _pathPlaceholders = new Set<string>()
112
- for (const part of (loadPoint?.parts || [])) {
113
+ for (const part of pointParts(loadPoint)) {
113
114
  if (typeof part === 'string' && part.startsWith('{') && part.endsWith('}')) {
114
115
  _pathPlaceholders.add(part.slice(1, -1))
115
116
  }
@@ -127,7 +128,7 @@ const TestDirect = cmp(function TestDirect(props: any) {
127
128
 
128
129
  // List point info.
129
130
  const listPoint = listOp?.points?.[0]
130
- const listPath = listPoint ? normalizePathParams(listPoint.parts || [], listPoint?.args?.params || [], listPoint?.rename?.param) : ''
131
+ const listPath = listPoint ? normalizePathParams(pointParts(listPoint), listPoint?.args?.params || [], listPoint?.rename?.param) : ''
131
132
  const listParams = listPoint?.args?.params || []
132
133
 
133
134
  const entidEnvVar = `${PROJECTNAME}_TEST_${envToken(entity.name)}_ENTID`
@@ -7,6 +7,7 @@ import {
7
7
  isAuthActive,
8
8
  resolveAuthPrefix,
9
9
  targetFeatures,
10
+ configDefinition,
10
11
  } from '@voxgig/sdkgen'
11
12
 
12
13
 
@@ -36,6 +37,7 @@ const Config = cmp(async function Config(props: any) {
36
37
  const scalapackage = scalaPackage(model)
37
38
 
38
39
  const entity = getModelPath(model, `main.${KIT}.entity`)
40
+ const { def: configDef } = configDefinition(model, target.name)
39
41
  // Gated by the applicability tags, so this target never imports or
40
42
  // registers a feature it has no source for. One rule, one place:
41
43
  // helpers/applicability.
@@ -68,13 +70,13 @@ const Config = cmp(async function Config(props: any) {
68
70
  options.headers = headers
69
71
  options.entity = optionsEntity
70
72
 
71
- const entityConfig = Object.values(entity).reduce((a: any, n: any) => (
72
- a[n.name] = cleanModel({
73
- fields: n.fields,
74
- name: n.name,
75
- op: n.op,
76
- relations: n.relations,
77
- }, true), a), {})
73
+ // configDefinition's `def.entity` verbatim, NOT rebuilt here. This reduce
74
+ // was one of fourteen copies of that function's entityDefs loop, and when
75
+ // configDefinition started reconstructing a point's `parts` from apidef's
76
+ // segment vector (its ADR-003), only the copies that read `configDef` got
77
+ // it — this target's literal config emitted paths with no parts at all
78
+ // while its data config had them. One rule, one place.
79
+ const entityConfig = configDef.entity
78
80
 
79
81
  const config = {
80
82
  main: { name: model.const.Name },
@@ -11,6 +11,7 @@ import {
11
11
  File,
12
12
  cmp,
13
13
  snakify,
14
+ pointParts,
14
15
  } from '@voxgig/sdkgen'
15
16
 
16
17
 
@@ -79,13 +80,13 @@ const TestDirect = cmp(function TestDirect(props: any) {
79
80
  // Load point info.
80
81
  const loadPoint = loadOp?.points?.[0]
81
82
  const loadPath = loadPoint
82
- ? normalizePathParams(loadPoint.parts || [], loadPoint?.args?.params || [], loadPoint?.rename?.param)
83
+ ? normalizePathParams(pointParts(loadPoint), loadPoint?.args?.params || [], loadPoint?.rename?.param)
83
84
  : ''
84
85
  const allLoadParams = loadPoint?.args?.params || []
85
86
  // Only path params that actually appear in the URL template drive the
86
87
  // direct-test path-param setup and URL-substitution asserts.
87
88
  const _pathPlaceholders = new Set<string>()
88
- for (const part of (loadPoint?.parts || [])) {
89
+ for (const part of pointParts(loadPoint)) {
89
90
  if (typeof part === 'string' && part.startsWith('{') && part.endsWith('}')) {
90
91
  _pathPlaceholders.add(part.slice(1, -1))
91
92
  }
@@ -104,7 +105,7 @@ const TestDirect = cmp(function TestDirect(props: any) {
104
105
  // List point info.
105
106
  const listPoint = listOp?.points?.[0]
106
107
  const listPath = listPoint
107
- ? normalizePathParams(listPoint.parts || [], listPoint?.args?.params || [], listPoint?.rename?.param)
108
+ ? normalizePathParams(pointParts(listPoint), listPoint?.args?.params || [], listPoint?.rename?.param)
108
109
  : ''
109
110
  const listParams = listPoint?.args?.params || []
110
111
 
@@ -2,6 +2,7 @@ import {
2
2
  cmp, each,
3
3
  File, Content, Folder,
4
4
  jsKey, jsProp,
5
+ pointSegments,
5
6
  } from '@voxgig/sdkgen'
6
7
 
7
8
 
@@ -22,7 +23,8 @@ import {
22
23
  function loadHasKey(ent: any): boolean {
23
24
  const point = (ent.op && ent.op.load && ent.op.load.points || [])[0]
24
25
  if (null == point) return false
25
- const hasPathParam = (point.parts || []).some((p: string) => p.startsWith('{'))
26
+ // apidef states which segments are variables (its ADR-003) no brace test.
27
+ const hasPathParam = pointSegments(point).some((seg: any) => null != seg.var)
26
28
  const hasQueryParam = (point.args && point.args.query || [])
27
29
  .some((q: any) => false !== q.reqd)
28
30
  return hasPathParam || hasQueryParam
@@ -7,6 +7,7 @@ import {
7
7
  authorInfo, contributorList, isAuthActive, isHttpBasicAuth, jsKey, jsProp,
8
8
  SdkGenError,
9
9
  PUBLISHER, PUBLISHER_URL,
10
+ pointSegments,
10
11
  } from '@voxgig/sdkgen'
11
12
 
12
13
  import {
@@ -102,11 +103,14 @@ function recordKey(ent: any): string {
102
103
  const canonical = op.points.filter((pt: any) =>
103
104
  null == (pt && pt.select && pt.select['$action']))
104
105
  const point = ownPoint(0 < canonical.length ? canonical : op.points)
105
- const parts: string[] = (point && point.parts) || []
106
- const lastParam = [...parts].reverse().find((p: string) => p.startsWith('{'))
107
-
108
- if (null != lastParam) {
109
- return lastParam.slice(1, -1)
106
+ // The LAST variable segment names the record's key. apidef states which
107
+ // segments are variables (its ADR-003), so this reads the name off the
108
+ // vector rather than finding a `{` and slicing the braces back off.
109
+ const vars = pointSegments(point)
110
+ .filter((seg: any) => null != seg.var)
111
+
112
+ if (0 < vars.length) {
113
+ return String(vars[vars.length - 1].var)
110
114
  }
111
115
 
112
116
  // No path param at all (e.g. GET /scan/async/result?trace_id=...): the
@@ -8,7 +8,6 @@ import {
8
8
  Fragment,
9
9
  Line,
10
10
  cmp,
11
- clean,
12
11
  configDefinition,
13
12
  configReprSetting,
14
13
  each,
@@ -170,13 +169,13 @@ const Config = cmp(async function Config(props: any) {
170
169
  `)
171
170
  }),
172
171
 
173
- "'ENTITYMAP'": formatJson(Object.values(entity)
174
- .reduce((a: any, n: any) => (a[n.name] = clean({
175
- fields: n.fields,
176
- name: n.name,
177
- op: n.op,
178
- relations: n.relations,
179
- }, true), a), {}), { margin: 2 }).trim(),
172
+ // configDefinition's `def.entity` verbatim, NOT rebuilt here. This
173
+ // reduce was a second copy of that function's entityDefs loop, and
174
+ // when configDefinition started reconstructing a point's `parts`
175
+ // from apidef's segment vector (its ADR-003), only the data
176
+ // representation got it — the literal one emitted empty paths. The
177
+ // config-repr equivalence test caught it, which is what it is for.
178
+ "'ENTITYMAP'": formatJson(configDef.entity, { margin: 2 }).trim(),
180
179
  }
181
180
  })
182
181
  })
@@ -22,7 +22,8 @@ import {
22
22
  isHttpBasicAuth,
23
23
  jsProp,
24
24
  jsOptProp, envName, envToken, liveStrict,
25
- jsKey
25
+ jsKey,
26
+ pointParts,
26
27
  } from '@voxgig/sdkgen'
27
28
 
28
29
 
@@ -295,7 +296,7 @@ function generateDirectLoad(model: Model, entity: ModelEntity, strict: boolean)
295
296
  }
296
297
 
297
298
  const allLoadParams = loadPoint.args?.params || []
298
- const loadPath = normalizePathParams(loadPoint.parts || [], allLoadParams, loadPoint.rename?.param)
299
+ const loadPath = normalizePathParams(pointParts(loadPoint), allLoadParams, loadPoint.rename?.param)
299
300
 
300
301
  // Some upstream OpenAPI specs declare a parameter as `in: path` even when
301
302
  // that path has no `{name}` placeholder for it. Only path params that
@@ -303,7 +304,7 @@ function generateDirectLoad(model: Model, entity: ModelEntity, strict: boolean)
303
304
  // setup and URL-substitution asserts; otherwise the SDK silently drops
304
305
  // them and the URL-includes assert fails.
305
306
  const pathPlaceholders = new Set<string>()
306
- for (const part of (loadPoint.parts || [])) {
307
+ for (const part of pointParts(loadPoint)) {
307
308
  if (typeof part === 'string' && part.startsWith('{') && part.endsWith('}')) {
308
309
  pathPlaceholders.add(part.slice(1, -1))
309
310
  }
@@ -337,7 +338,7 @@ function generateDirectLoad(model: Model, entity: ModelEntity, strict: boolean)
337
338
  const listOp = entity.op?.list
338
339
  const listPoint = listOp?.points?.[0]
339
340
  const listParams = listPoint?.args?.params || []
340
- const listPath = listPoint ? normalizePathParams(listPoint.parts || [], listParams, listPoint.rename?.param) : ''
341
+ const listPath = listPoint ? normalizePathParams(pointParts(listPoint), listParams, listPoint.rename?.param) : ''
341
342
  const hasList = null != listPoint
342
343
 
343
344
  // Ancestor params (not 'id') for live mode
@@ -530,7 +531,7 @@ function generateDirectList(model: Model, entity: ModelEntity, strict: boolean)
530
531
  }
531
532
 
532
533
  const listParams = listPoint.args?.params || []
533
- const listPath = normalizePathParams(listPoint.parts || [], listParams, listPoint.rename?.param)
534
+ const listPath = normalizePathParams(pointParts(listPoint), listParams, listPoint.rename?.param)
534
535
 
535
536
  // Required query params with spec-provided examples — needed to satisfy
536
537
  // the API contract in live mode (see generateDirectLoad for rationale).
@@ -3,7 +3,7 @@
3
3
  "package": 1
4
4
  },
5
5
  "name": "@voxgig/sdkgen",
6
- "version": "4.7.0",
6
+ "version": "4.8.0",
7
7
  "provides": {
8
8
  "target": [
9
9
  "c",
@@ -29,6 +29,8 @@
29
29
  import { each, names } from 'jostraca'
30
30
  import { KIT, getModelPath } from '@voxgig/apidef'
31
31
 
32
+ import { pointSegments, pointTerminalParam, pointPathKey } from './pointPath'
33
+
32
34
 
33
35
  // THE entity collection for a model — resolved once, with a stable identity.
34
36
  //
@@ -217,25 +219,19 @@ function entityPath(entity: any): string {
217
219
  // no point's `select.exist` matches. It cannot be shared with them — a
218
220
  // template ships standalone, outside this package — so it is written twice on
219
221
  // purpose, and both sides must move together.
220
- function terminalParam(point: any): boolean {
221
- const parts: any[] = (point && point.parts) || []
222
- const last = 0 < parts.length ? parts[parts.length - 1] : ''
223
- return 'string' === typeof last && last.startsWith('{')
224
- }
225
-
226
-
227
222
  function ownPoint(points: any[]): any {
228
223
  let best = points[0]
229
224
 
230
225
  for (const pt of points) {
231
- if (null == pt || null == pt.parts || null == best || null == best.parts) {
226
+ if (null == pt || null == pt.segments || null == best || null == best.segments) {
232
227
  continue
233
228
  }
234
229
 
235
- const ptterm = terminalParam(pt)
236
- const bestterm = terminalParam(best)
230
+ const ptterm = pointTerminalParam(pt)
231
+ const bestterm = pointTerminalParam(best)
237
232
 
238
- if (ptterm !== bestterm ? ptterm : pt.parts.length < best.parts.length) {
233
+ if (ptterm !== bestterm ?
234
+ ptterm : pointSegments(pt).length < pointSegments(best).length) {
239
235
  best = pt
240
236
  }
241
237
  }
@@ -248,10 +244,9 @@ function ownPoint(points: any[]): any {
248
244
  // selectors on one endpoint (the same path, chosen by different query
249
245
  // params), not cross-references to different resources.
250
246
  function samePath(points: any[]): boolean {
251
- const first = ((points[0] && points[0].parts) || []).join('/')
247
+ const first = pointPathKey(points[0])
252
248
 
253
- return points.every((pt: any) =>
254
- first === ((pt && pt.parts) || []).join('/'))
249
+ return points.every((pt: any) => first === pointPathKey(pt))
255
250
  }
256
251
 
257
252
 
@@ -0,0 +1,88 @@
1
+ /* Copyright (c) 2024-2025 Voxgig, MIT License */
2
+
3
+ // A point's path, as apidef resolves it.
4
+ //
5
+ // apidef ADR-003: the model carries the path as a typed segment vector —
6
+ // `[{ lit: 'element' }, { var: 'id' }]` — and no brace-templated string. A
7
+ // `var` names one of the point's `args.params` directly, so a consumer walks
8
+ // the vector instead of parsing anything.
9
+ //
10
+ // The generated SDK runtimes still speak the older braced-string form
11
+ // (`['element', '{id}']`), which this file reconstructs in ONE place. Every
12
+ // generation-time consumer and the embedded config go through here, so when
13
+ // the runtimes move onto segments there is a single call site to retire —
14
+ // rather than the twenty-odd hand-written brace parsers ADR-003 is about.
15
+
16
+
17
+ type PathSegment = {
18
+ lit?: string
19
+ var?: string
20
+ }
21
+
22
+
23
+ // The segment vector, defensively. GraphQL points address the single endpoint
24
+ // and carry no path, so an empty vector is normal, not a fault.
25
+ function pointSegments(point: any): PathSegment[] {
26
+ const segments = point && point.segments
27
+ return Array.isArray(segments) ? segments : []
28
+ }
29
+
30
+
31
+ // The braced-string form the SDK runtimes still consume. A `lit` is emitted
32
+ // verbatim — including one that contains braces, which apidef leaves literal
33
+ // (a compound element like `{a}.{b}` names no single parameter). That is the
34
+ // lossiness ADR-003 removed from the model, and it survives here only because
35
+ // this is the OLD representation being reconstructed on the way out.
36
+ function pointParts(point: any): string[] {
37
+ return pointSegments(point).map((seg: PathSegment) =>
38
+ null == seg.var ? String(seg.lit ?? '') : '{' + seg.var + '}')
39
+ }
40
+
41
+
42
+ // Does the path end in a parameter?
43
+ //
44
+ // DELIBERATELY asked of the reconstructed part, not of the vector, even
45
+ // though the vector states it directly and more accurately.
46
+ //
47
+ // The same rule runs at RUNTIME, in all 21 languages' makePoint template
48
+ // (`0 === last.indexOf('{')` over `parts`), to pick a fallback route when no
49
+ // point's `select.exist` matches. Those templates ship standalone, outside
50
+ // this package, so the rule is written twice on purpose and BOTH SIDES MUST
51
+ // MOVE TOGETHER — see the note in opShape.ts and in each template.
52
+ //
53
+ // Reading the vector here would break that. A path ending in a LITERAL that
54
+ // contains braces (`/reports/{id}.json`) is not a terminal parameter by the
55
+ // vector, but every runtime still says it is, because from the reconstructed
56
+ // string it cannot tell. Generation-time `ownPoint` would then pick a
57
+ // different route than the SDK picks at request time — for the same model.
58
+ //
59
+ // So this stays bug-compatible with the runtimes until they move onto
60
+ // segments, at which point this becomes `null != last.var` and all 21 change
61
+ // with it.
62
+ function pointTerminalParam(point: any): boolean {
63
+ const parts = pointParts(point)
64
+ const last = 0 < parts.length ? parts[parts.length - 1] : ''
65
+ return 0 === last.indexOf('{')
66
+ }
67
+
68
+
69
+ // Do two points describe the same route? Compares the vectors, so a literal
70
+ // containing braces cannot be mistaken for a parameter of the same spelling.
71
+ function pointPathKey(point: any): string {
72
+ return pointSegments(point)
73
+ .map((seg: PathSegment) =>
74
+ null == seg.var ? 'l:' + String(seg.lit ?? '') : 'v:' + seg.var)
75
+ .join('/')
76
+ }
77
+
78
+
79
+ export type {
80
+ PathSegment,
81
+ }
82
+
83
+ export {
84
+ pointSegments,
85
+ pointParts,
86
+ pointTerminalParam,
87
+ pointPathKey,
88
+ }