@voxgig/sdkgen 0.40.1 → 0.41.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 (47) hide show
  1. package/bin/voxgig-sdkgen +1 -1
  2. package/dist/helpers/buildIdNames.d.ts +11 -0
  3. package/dist/helpers/buildIdNames.js +56 -0
  4. package/dist/helpers/buildIdNames.js.map +1 -0
  5. package/dist/helpers/collectDeps.d.ts +9 -0
  6. package/dist/helpers/collectDeps.js +50 -0
  7. package/dist/helpers/collectDeps.js.map +1 -0
  8. package/dist/helpers/getMatchEntries.d.ts +2 -0
  9. package/dist/helpers/getMatchEntries.js +13 -0
  10. package/dist/helpers/getMatchEntries.js.map +1 -0
  11. package/dist/sdkgen.d.ts +6 -2
  12. package/dist/sdkgen.js +7 -1
  13. package/dist/sdkgen.js.map +1 -1
  14. package/dist/tsconfig.tsbuildinfo +1 -1
  15. package/package.json +1 -1
  16. package/project/.sdk/model/target/lua.jsonic +1 -1
  17. package/project/.sdk/model/target/php.jsonic +2 -3
  18. package/project/.sdk/model/target/py.jsonic +1 -1
  19. package/project/.sdk/src/cmp/go/Entity_go.ts +15 -3
  20. package/project/.sdk/src/cmp/go/Main_go.ts +3 -2
  21. package/project/.sdk/src/cmp/go/Package_go.ts +8 -29
  22. package/project/.sdk/src/cmp/go/TestDirect_go.ts +14 -7
  23. package/project/.sdk/src/cmp/go/TestEntity_go.ts +43 -30
  24. package/project/.sdk/src/cmp/js/TestDirect_js.ts +6 -3
  25. package/project/.sdk/src/cmp/lua/Package_lua.ts +5 -27
  26. package/project/.sdk/src/cmp/lua/TestDirect_lua.ts +6 -3
  27. package/project/.sdk/src/cmp/lua/TestEntity_lua.ts +5 -17
  28. package/project/.sdk/src/cmp/php/Config_php.ts +21 -0
  29. package/project/.sdk/src/cmp/php/MainEntity_php.ts +11 -1
  30. package/project/.sdk/src/cmp/php/Package_php.ts +5 -31
  31. package/project/.sdk/src/cmp/php/TestDirect_php.ts +6 -3
  32. package/project/.sdk/src/cmp/php/TestEntity_php.ts +25 -29
  33. package/project/.sdk/src/cmp/py/Main_py.ts +5 -5
  34. package/project/.sdk/src/cmp/py/Package_py.ts +11 -28
  35. package/project/.sdk/src/cmp/py/TestDirect_py.ts +9 -6
  36. package/project/.sdk/src/cmp/py/TestEntity_py.ts +7 -19
  37. package/project/.sdk/src/cmp/rb/Package_rb.ts +8 -47
  38. package/project/.sdk/src/cmp/rb/TestDirect_rb.ts +6 -3
  39. package/project/.sdk/src/cmp/rb/TestEntity_rb.ts +5 -17
  40. package/project/.sdk/src/cmp/ts/TestDirect_ts.ts +6 -3
  41. package/project/.sdk/tm/go/core/helpers.go +10 -0
  42. package/project/.sdk/tm/lua/Makefile +1 -1
  43. package/project/.sdk/tm/php/Makefile +1 -1
  44. package/src/helpers/buildIdNames.ts +70 -0
  45. package/src/helpers/collectDeps.ts +70 -0
  46. package/src/helpers/getMatchEntries.ts +14 -0
  47. package/src/sdkgen.ts +10 -0
@@ -3,14 +3,12 @@ import {
3
3
  Content,
4
4
  File,
5
5
  cmp,
6
- each,
6
+ collectDeps,
7
7
  } from '@voxgig/sdkgen'
8
8
 
9
9
 
10
- import {
11
- KIT,
10
+ import type {
12
11
  Model,
13
- getModelPath,
14
12
  } from '@voxgig/apidef'
15
13
 
16
14
 
@@ -20,7 +18,8 @@ const Package = cmp(async function Package(props: any) {
20
18
 
21
19
  const model: Model = ctx$.model
22
20
 
23
- const feature = getModelPath(model, `main.${KIT}.feature`)
21
+ const versionOf = (d: { version: string; source: 'feature' | 'target' }) =>
22
+ d.source === 'target' ? (d.version || '0.0') : d.version
24
23
 
25
24
  // Generate Gemfile
26
25
  File({ name: 'Gemfile' }, () => {
@@ -30,28 +29,9 @@ gemspec
30
29
 
31
30
  `)
32
31
 
33
- // Collect dependencies from features
34
- each(feature, (f: any) => {
35
- const rbDeps = f.deps?.rb
36
- if (rbDeps) {
37
- each(rbDeps, (dep: any) => {
38
- if (dep.active) {
39
- Content(`gem "${dep.key$}", "~> ${dep.version}"
40
- `)
41
- }
42
- })
43
- }
44
- })
45
-
46
- // Add target-level deps
47
- const targetDeps = target.deps
48
- if (targetDeps) {
49
- each(targetDeps, (dep: any) => {
50
- if (dep.active !== false) {
51
- Content(`gem "${dep.key$}", "~> ${dep.version || '0.0'}"
32
+ for (const d of collectDeps(model, target.name, target.deps)) {
33
+ Content(`gem "${d.name}", "~> ${versionOf(d)}"
52
34
  `)
53
- }
54
- })
55
35
  }
56
36
  })
57
37
 
@@ -73,28 +53,9 @@ gemspec
73
53
  spec.add_dependency "json"
74
54
  `)
75
55
 
76
- // Collect dependencies from features
77
- each(feature, (f: any) => {
78
- const rbDeps = f.deps?.rb
79
- if (rbDeps) {
80
- each(rbDeps, (dep: any) => {
81
- if (dep.active) {
82
- Content(` spec.add_dependency "${dep.key$}", "~> ${dep.version}"
83
- `)
84
- }
85
- })
86
- }
87
- })
88
-
89
- // Add target-level deps
90
- const targetDeps = target.deps
91
- if (targetDeps) {
92
- each(targetDeps, (dep: any) => {
93
- if (dep.active !== false) {
94
- Content(` spec.add_dependency "${dep.key$}", "~> ${dep.version || '0.0'}"
56
+ for (const d of collectDeps(model, target.name, target.deps)) {
57
+ Content(` spec.add_dependency "${d.name}", "~> ${versionOf(d)}"
95
58
  `)
96
- }
97
- })
98
59
  }
99
60
 
100
61
  Content(`
@@ -20,10 +20,13 @@ function normalizePathParams(
20
20
  return part.replace(/\{([^}]+)\}/g, (match: string, rawName: string) => {
21
21
  const snaked = snakify(rawName)
22
22
  const depluralized = depluralize(snaked)
23
+ // Prefer exact name match — orig matches can collide when one param's
24
+ // original name was renamed to another param's current name (e.g. badge
25
+ // load: param 'group_id' has orig 'id', and another param has name 'id').
23
26
  const param = params.find((p: any) =>
24
- p.orig === snaked || p.name === snaked ||
25
- p.orig === depluralized || p.name === depluralized
26
- )
27
+ p.name === snaked || p.name === depluralized) ||
28
+ params.find((p: any) =>
29
+ p.orig === snaked || p.orig === depluralized)
27
30
  if (param) return '{' + param.name + '}'
28
31
 
29
32
  if (rename) {
@@ -16,6 +16,8 @@ import {
16
16
  File,
17
17
  cmp,
18
18
  each,
19
+ buildIdNames,
20
+ getMatchEntries,
19
21
  } from '@voxgig/sdkgen'
20
22
 
21
23
 
@@ -45,15 +47,7 @@ const TestEntity = cmp(function TestEntity(props: any) {
45
47
 
46
48
  const PROJUPPER = model.const.Name.toUpperCase().replace(/[^A-Z_]/g, '_')
47
49
 
48
- const ancestors = (entity.relations?.ancestors || []).flat()
49
-
50
- // Build idmap names
51
- const idnames: string[] = []
52
- for (let i = 1; i <= 3; i++) idnames.push(`${entity.name}0${i}`)
53
- for (const anc of ancestors) {
54
- for (let i = 1; i <= 3; i++) idnames.push(`${anc}0${i}`)
55
- }
56
-
50
+ const idnames = buildIdNames(entity, basicflow)
57
51
  const idnamesStr = idnames.map(n => `"${n}"`).join(', ')
58
52
 
59
53
  const allSteps = Object.values(basicflow.step) as any[]
@@ -92,7 +86,7 @@ class ${entity.Name}EntityTest < Minitest::Test
92
86
  if (!flowHasCreate) {
93
87
  Content(` # Bootstrap entity data from existing test data.
94
88
  ${entity.name}_ref01_data_raw = Vs.items(Helpers.to_map(
95
- Vs.getprop(setup[:data], "existing.${entity.name}")))
89
+ Vs.getpath(setup[:data], "existing.${entity.name}")))
96
90
  ${entity.name}_ref01_data = nil
97
91
  if ${entity.name}_ref01_data_raw.length > 0
98
92
  ${entity.name}_ref01_data = Helpers.to_map(${entity.name}_ref01_data_raw[0][1])
@@ -191,12 +185,6 @@ end
191
185
  })
192
186
 
193
187
 
194
- function getMatchEntries(step: any): [string, any][] {
195
- if (!step?.match) return []
196
- return Object.entries(step.match).filter(([k]: any) => !k.endsWith('$'))
197
- }
198
-
199
-
200
188
  const generateCreate: OpGen = (ctx, step, index) => {
201
189
  const { entity, flow } = ctx
202
190
  const ref = step.input?.ref ?? entity.name + '_ref01'
@@ -417,7 +405,7 @@ const generateLoad: OpGen = (ctx, step, index) => {
417
405
  }
418
406
  if (!hasSrcData) {
419
407
  Content(` ${srcdatavar}_raw = Vs.items(Helpers.to_map(
420
- Vs.getprop(setup[:data], "existing.${entity.name}")))
408
+ Vs.getpath(setup[:data], "existing.${entity.name}")))
421
409
  ${srcdatavar} = nil
422
410
  if ${srcdatavar}_raw.length > 0
423
411
  ${srcdatavar} = Helpers.to_map(${srcdatavar}_raw[0][1])
@@ -302,10 +302,13 @@ function normalizePathParams(
302
302
  return part.replace(/\{([^}]+)\}/g, (match: string, rawName: string) => {
303
303
  const snaked = snakify(rawName)
304
304
  const depluralized = depluralize(snaked)
305
+ // Prefer exact name match — orig matches can collide when one param's
306
+ // original name was renamed to another param's current name (e.g. badge
307
+ // load: param 'group_id' has orig 'id', and another param has name 'id').
305
308
  const param = params.find((p: any) =>
306
- p.orig === snaked || p.name === snaked ||
307
- p.orig === depluralized || p.name === depluralized
308
- )
309
+ p.name === snaked || p.name === depluralized) ||
310
+ params.find((p: any) =>
311
+ p.orig === snaked || p.orig === depluralized)
309
312
  if (param) return '{' + param.name + '}'
310
313
 
311
314
  // Reverse-lookup through rename mapping: if rawName is a renamed value
@@ -1,5 +1,15 @@
1
1
  package core
2
2
 
3
+ import "fmt"
4
+
5
+ // UnsupportedOp is returned by entity stub methods for operations the
6
+ // underlying API spec doesn't define. The static ProjectNameEntity interface
7
+ // requires every CRUD method on every entity, so absent ops must still be
8
+ // callable — they error at runtime instead of failing to compile.
9
+ func UnsupportedOp(opname, entityname string) (any, error) {
10
+ return nil, fmt.Errorf("operation '%s' not supported by entity '%s'", opname, entityname)
11
+ }
12
+
3
13
  func ToMapAny(v any) map[string]any {
4
14
  if v == nil {
5
15
  return nil
@@ -1,7 +1,7 @@
1
1
  .PHONY: test build clean
2
2
 
3
3
  test:
4
- busted test/
4
+ busted -p _test test/
5
5
 
6
6
  build:
7
7
  @echo "No build step for Lua"
@@ -1,7 +1,7 @@
1
1
  .PHONY: test build clean
2
2
 
3
3
  test:
4
- ./vendor/bin/phpunit
4
+ ./vendor/bin/phpunit test
5
5
 
6
6
  build:
7
7
  @echo "No build step for PHP"
@@ -0,0 +1,70 @@
1
+ // Build the list of placeholder ID names that the test setup populates into
2
+ // `setup.idmap`. The set is the union of:
3
+ // - the entity's own ids (`<entity>01..03`)
4
+ // - every ancestor entity's ids (`<anc>01..03`)
5
+ // - every literal string value referenced by `step.match` across the flow
6
+ // (e.g. path-parameter aliases like `year01` for `/{year}/domain` — apidef
7
+ // doesn't always populate `relations.ancestors` when the parent in the
8
+ // path is a bare parameter rather than an entity, so we have to mine the
9
+ // flow steps directly to avoid KeyError-ing the idmap from generated test
10
+ // code)
11
+ //
12
+ // Identical helper was previously inlined in TestEntity_{go,py,lua,rb,php}.ts.
13
+
14
+ const COUNT = 3 // 3 ids per name: <name>01, <name>02, <name>03
15
+
16
+ type FlowLike = {
17
+ step?: Record<string, any> | any[]
18
+ }
19
+
20
+ type EntityLike = {
21
+ name: string
22
+ relations?: {
23
+ ancestors?: any
24
+ }
25
+ }
26
+
27
+ function buildIdNames(entity: EntityLike, flow: FlowLike): string[] {
28
+ const idnames: string[] = []
29
+ const seen = new Set<string>()
30
+ const push = (n: string) => {
31
+ if (!seen.has(n)) {
32
+ seen.add(n)
33
+ idnames.push(n)
34
+ }
35
+ }
36
+
37
+ for (let i = 1; i <= COUNT; i++) push(`${entity.name}0${i}`)
38
+
39
+ const ancestors: string[] = (entity.relations?.ancestors || []).flat()
40
+ for (const anc of ancestors) {
41
+ for (let i = 1; i <= COUNT; i++) push(`${anc}0${i}`)
42
+ }
43
+
44
+ const steps = Array.isArray(flow?.step)
45
+ ? flow.step
46
+ : Object.values(flow?.step || {})
47
+ for (const step of steps) {
48
+ if (step?.match) {
49
+ for (const v of Object.values(step.match)) {
50
+ if (typeof v === 'string' && v && !v.endsWith('$')) push(v)
51
+ }
52
+ }
53
+ // step.data values can also be aliased via setup (e.g. update step.data
54
+ // = {data_type_id: 'data_type01'} → setup adds idmap[data_type_id] =
55
+ // idmap[data_type01]). The right-hand side `data_type01` must be in the
56
+ // idmap or the alias resolves to undefined.
57
+ if (step?.data) {
58
+ for (const v of Object.values(step.data)) {
59
+ if (typeof v === 'string' && v && !v.endsWith('$')) push(v)
60
+ }
61
+ }
62
+ }
63
+
64
+ return idnames
65
+ }
66
+
67
+
68
+ export {
69
+ buildIdNames,
70
+ }
@@ -0,0 +1,70 @@
1
+ // Collect target-language dependencies from features and from the target's
2
+ // own `deps` block, applying the active-flag semantics that every Package_*.ts
3
+ // template was hand-rolling identically:
4
+ //
5
+ // - feature deps : included when `dep.active === true` (default off)
6
+ // - target deps : included when `dep.active !== false` (default on)
7
+ //
8
+ // The two sources are kept distinct via the `source` field so callers can
9
+ // apply their own version defaults / formatting (e.g. go uses `v0.0.0`,
10
+ // python `0.0`). The original dep object is exposed as `raw` for callers that
11
+ // need extra fields like `dep.replace` (go module replace directives) or
12
+ // `dep.kind` (prod/dev/peer).
13
+
14
+ import { each } from 'jostraca'
15
+ import { KIT, getModelPath } from '@voxgig/apidef'
16
+
17
+ type DepEntry = {
18
+ name: string
19
+ version: string
20
+ source: 'feature' | 'target'
21
+ raw: any
22
+ }
23
+
24
+ function collectDeps(
25
+ model: any,
26
+ targetName: string,
27
+ targetDeps: any,
28
+ ): DepEntry[] {
29
+ const out: DepEntry[] = []
30
+ const feature = getModelPath(model, `main.${KIT}.feature`)
31
+
32
+ each(feature, (f: any) => {
33
+ const langDeps = f?.deps?.[targetName]
34
+ if (!langDeps) return
35
+ each(langDeps, (dep: any) => {
36
+ if (dep?.active) {
37
+ out.push({
38
+ name: dep.key$,
39
+ version: dep.version,
40
+ source: 'feature',
41
+ raw: dep,
42
+ })
43
+ }
44
+ })
45
+ })
46
+
47
+ if (targetDeps) {
48
+ each(targetDeps, (dep: any) => {
49
+ if (dep?.active !== false) {
50
+ out.push({
51
+ name: dep.key$,
52
+ version: dep.version,
53
+ source: 'target',
54
+ raw: dep,
55
+ })
56
+ }
57
+ })
58
+ }
59
+
60
+ return out
61
+ }
62
+
63
+
64
+ export type {
65
+ DepEntry,
66
+ }
67
+
68
+ export {
69
+ collectDeps,
70
+ }
@@ -0,0 +1,14 @@
1
+ // Return the user-facing entries of a flow step's `match` object. Keys ending
2
+ // in `$` are jostraca/aontu metadata sentinels and are skipped.
3
+ //
4
+ // Identical helper was previously inlined in TestEntity_*.ts and TestDirect_*.ts.
5
+
6
+ function getMatchEntries(step: any): [string, any][] {
7
+ if (!step?.match) return []
8
+ return Object.entries(step.match).filter(([k]: any) => !k.endsWith('$'))
9
+ }
10
+
11
+
12
+ export {
13
+ getMatchEntries,
14
+ }
package/src/sdkgen.ts CHANGED
@@ -38,6 +38,11 @@ import { ReadmeExplanation } from './cmp/ReadmeExplanation'
38
38
  import { ReadmeRef } from './cmp/ReadmeRef'
39
39
  import { FeatureHook } from './cmp/FeatureHook'
40
40
 
41
+ import { buildIdNames } from './helpers/buildIdNames'
42
+ import { getMatchEntries } from './helpers/getMatchEntries'
43
+ import { collectDeps } from './helpers/collectDeps'
44
+ import type { DepEntry } from './helpers/collectDeps'
45
+
41
46
 
42
47
  import {
43
48
  action_target,
@@ -330,6 +335,7 @@ function clear(path: string) {
330
335
 
331
336
  export type {
332
337
  SdkGenOptions,
338
+ DepEntry,
333
339
  }
334
340
 
335
341
 
@@ -389,4 +395,8 @@ export {
389
395
  SdkGen,
390
396
 
391
397
  requirePath,
398
+
399
+ buildIdNames,
400
+ getMatchEntries,
401
+ collectDeps,
392
402
  }