@voxgig/sdkgen 1.0.1 → 1.2.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 CHANGED
@@ -8,7 +8,7 @@ const { Shape, One } = require('shape')
8
8
 
9
9
  const { SdkGen } = require('../dist/sdkgen.js')
10
10
 
11
- const VERSION = '1.0.1'
11
+ const VERSION = '1.2.0'
12
12
  const KONSOLE = console
13
13
 
14
14
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voxgig/sdkgen",
3
- "version": "1.0.1",
3
+ "version": "1.2.0",
4
4
  "main": "dist/sdkgen.js",
5
5
  "type": "commonjs",
6
6
  "types": "dist/sdkgen.d.ts",
@@ -41,7 +41,7 @@
41
41
  ],
42
42
  "devDependencies": {
43
43
  "@types/js-yaml": "4.0.9",
44
- "@types/node": "25.7.0",
44
+ "@types/node": "25.8.0",
45
45
  "json-schema-to-ts": "3.1.1",
46
46
  "memfs": "4.57.2",
47
47
  "typescript": "6.0.3"
@@ -593,16 +593,14 @@ const generateRemove: OpGen = (ctx, step, index) => {
593
593
  // otherwise reuse the `err` from a prior op step.
594
594
  const errOp = needsEnt ? ':=' : '='
595
595
 
596
- const hasEntIdR = null != entity.id
597
-
598
596
  Content(` // REMOVE
599
597
  `)
600
598
  if (needsEnt) {
601
599
  Content(` ${entvar} := client.${entity.Name}(nil)
602
600
  `)
603
601
  }
604
- if (hasEntIdR) {
605
- Content(` ${matchvar} := map[string]any{
602
+ // Always match the prior-created entity by id to avoid mock-order flakes.
603
+ Content(` ${matchvar} := map[string]any{
606
604
  "id": ${srcdatavar}["id"],
607
605
  }
608
606
  _, err ${errOp} ${entvar}.Remove(${matchvar}, nil)
@@ -610,15 +608,6 @@ const generateRemove: OpGen = (ctx, step, index) => {
610
608
  t.Fatalf("remove failed: %v", err)
611
609
  }
612
610
  `)
613
- }
614
- else {
615
- Content(` ${matchvar} := map[string]any{}
616
- _, err ${errOp} ${entvar}.Remove(${matchvar}, nil)
617
- if err != nil {
618
- t.Fatalf("remove failed: %v", err)
619
- }
620
- `)
621
- }
622
611
  }
623
612
 
624
613
 
@@ -502,28 +502,19 @@ const generateRemove: OpGen = (ctx, step, index) => {
502
502
  const needsEnt = !priorSteps.some((s: any) =>
503
503
  ['create', 'list', 'load', 'update', 'remove'].includes(s.op))
504
504
 
505
- const hasEntIdR = null != entity.id
506
-
507
505
  Content(` -- REMOVE
508
506
  `)
509
507
  if (needsEnt) {
510
508
  Content(` local ${entvar} = client:${entity.Name}(nil)
511
509
  `)
512
510
  }
513
- if (hasEntIdR) {
514
- Content(` local ${matchvar} = {
511
+ // Always match the prior-created entity by id to avoid mock-order flakes.
512
+ Content(` local ${matchvar} = {
515
513
  id = ${srcdatavar}["id"],
516
514
  }
517
515
  local _, err = ${entvar}:remove(${matchvar}, nil)
518
516
  assert.is_nil(err)
519
517
  `)
520
- }
521
- else {
522
- Content(` local ${matchvar} = {}
523
- local _, err = ${entvar}:remove(${matchvar}, nil)
524
- assert.is_nil(err)
525
- `)
526
- }
527
518
  }
528
519
 
529
520
 
@@ -507,28 +507,19 @@ const generateRemove: OpGen = (ctx, step, index) => {
507
507
  const needsEnt = !priorSteps.some((s: any) =>
508
508
  ['create', 'list', 'load', 'update', 'remove'].includes(s.op))
509
509
 
510
- const hasEntIdR = null != entity.id
511
-
512
510
  Content(` // REMOVE
513
511
  `)
514
512
  if (needsEnt) {
515
513
  Content(` $${entvar} = $client->${accessor}(null);
516
514
  `)
517
515
  }
518
- if (hasEntIdR) {
519
- Content(` $${matchvar} = [
516
+ // Always match the prior-created entity by id to avoid mock-order flakes.
517
+ Content(` $${matchvar} = [
520
518
  "id" => $${srcdatavar}["id"],
521
519
  ];
522
520
  [$_, $err] = $${entvar}->remove($${matchvar}, null);
523
521
  $this->assertNull($err);
524
522
  `)
525
- }
526
- else {
527
- Content(` $${matchvar} = [];
528
- [$_, $err] = $${entvar}->remove($${matchvar}, null);
529
- $this->assertNull($err);
530
- `)
531
- }
532
523
  }
533
524
 
534
525
 
@@ -500,28 +500,19 @@ const generateRemove: OpGen = (ctx, step, index) => {
500
500
  const needsEnt = !priorSteps.some((s: any) =>
501
501
  ['create', 'list', 'load', 'update', 'remove'].includes(s.op))
502
502
 
503
- const hasEntIdR = null != entity.id
504
-
505
503
  Content(` # REMOVE
506
504
  `)
507
505
  if (needsEnt) {
508
506
  Content(` ${entvar} = client.${entity.Name}(None)
509
507
  `)
510
508
  }
511
- if (hasEntIdR) {
512
- Content(` ${matchvar} = {
509
+ // Always match the prior-created entity by id to avoid mock-order flakes.
510
+ Content(` ${matchvar} = {
513
511
  "id": ${srcdatavar}["id"],
514
512
  }
515
513
  _, err = ${entvar}.remove(${matchvar}, None)
516
514
  assert err is None
517
515
  `)
518
- }
519
- else {
520
- Content(` ${matchvar} = {}
521
- _, err = ${entvar}.remove(${matchvar}, None)
522
- assert err is None
523
- `)
524
- }
525
516
  }
526
517
 
527
518
 
@@ -493,28 +493,19 @@ const generateRemove: OpGen = (ctx, step, index) => {
493
493
  const needsEnt = !priorSteps.some((s: any) =>
494
494
  ['create', 'list', 'load', 'update', 'remove'].includes(s.op))
495
495
 
496
- const hasEntIdR = null != entity.id
497
-
498
496
  Content(` # REMOVE
499
497
  `)
500
498
  if (needsEnt) {
501
499
  Content(` ${entvar} = client.${entity.Name}(nil)
502
500
  `)
503
501
  }
504
- if (hasEntIdR) {
505
- Content(` ${matchvar} = {
502
+ // Always match the prior-created entity by id to avoid mock-order flakes.
503
+ Content(` ${matchvar} = {
506
504
  "id" => ${srcdatavar}["id"],
507
505
  }
508
506
  _, err = ${entvar}.remove(${matchvar}, nil)
509
507
  assert_nil err
510
508
  `)
511
- }
512
- else {
513
- Content(` ${matchvar} = {}
514
- _, err = ${entvar}.remove(${matchvar}, nil)
515
- assert_nil err
516
- `)
517
- }
518
509
  }
519
510
 
520
511
 
@@ -516,8 +516,6 @@ const generateRemove: OpGen = (ctx, step, index) => {
516
516
  const needsEnt = !priorSteps.some(s =>
517
517
  ['create', 'list', 'load', 'update', 'remove'].includes(s.op))
518
518
 
519
- const hasEntIdR = null != entity.id
520
-
521
519
  Content(`
522
520
  // REMOVE
523
521
  `)
@@ -525,13 +523,11 @@ const generateRemove: OpGen = (ctx, step, index) => {
525
523
  Content(` const ${entvar} = client.${nom(entity, 'Name')}()
526
524
  `)
527
525
  }
528
- Content(` const ${matchvar}: any = {}
529
- `)
530
- if (hasEntIdR) {
531
- Content(` ${matchvar}.id = ${srcdatavar}.id
532
- `)
533
- }
534
- Content(` await ${entvar}.remove(${matchvar})
526
+ // Always match the prior-created entity by id. The mock test feature
527
+ // removes the first match in entmap, so without a specific id the
528
+ // result depends on hash-sort order and flakes (see cheapshark).
529
+ Content(` const ${matchvar}: any = { id: ${srcdatavar}.id }
530
+ await ${entvar}.remove(${matchvar})
535
531
  `)
536
532
  }
537
533
 
@@ -114,8 +114,10 @@ func (f *TestFeature) Init(ctx *core.Context, options map[string]any) {
114
114
  // Match the existing entity by id only (or its alias). Reqdata
115
115
  // also contains the new field values, which would otherwise
116
116
  // cause Select to filter out the entity we want to update.
117
- // Falls back to first entity when no match found, mirroring
118
- // the TS mock.
117
+ // When reqdata has no id, fall back to the id the entity
118
+ // client carries from a prior create/load (in ctx.Match /
119
+ // ctx.Data), mirroring the TS mock where param(ctx,'id')
120
+ // resolves from accumulated state.
119
121
  updateMatch := map[string]any{}
120
122
  if ctx.Reqdata != nil {
121
123
  if v, has := ctx.Reqdata["id"]; has {
@@ -131,6 +133,9 @@ func (f *TestFeature) Init(ctx *core.Context, options map[string]any) {
131
133
  }
132
134
  }
133
135
  }
136
+ if len(updateMatch) == 0 {
137
+ updateMatch = resolveMatch(map[string]any{})
138
+ }
134
139
  args := self.buildArgs(ctx, op, updateMatch)
135
140
  found := vs.Select(entmap, args)
136
141
  ent := vs.GetElem(found, 0)
@@ -160,9 +165,8 @@ func (f *TestFeature) Init(ctx *core.Context, options map[string]any) {
160
165
  args := self.buildArgs(ctx, op, resolveMatch(ctx.Reqmatch))
161
166
  found := vs.Select(entmap, args)
162
167
  ent := vs.GetElem(found, 0)
163
- if ent == nil {
164
- return respond(404, nil, map[string]any{"statusText": "Not found"}), nil
165
- }
168
+ // Remove only the first matched entity. If nothing matches,
169
+ // succeed as a no-op rather than erroring.
166
170
  if entm, ok := ent.(map[string]any); ok {
167
171
  id := vs.GetProp(entm, "id")
168
172
  vs.DelProp(entmap, id)
@@ -109,8 +109,10 @@ function TestFeature:init(ctx, options)
109
109
  elseif op.name == "update" then
110
110
  -- Match the existing entity by id only (or its alias). reqdata also
111
111
  -- contains the new field values, which would otherwise cause select
112
- -- to filter out the entity we want to update. Falls back to first
113
- -- entity when no match found, mirroring the TS mock.
112
+ -- to filter out the entity we want to update. When reqdata has no id,
113
+ -- fall back to the id the entity client carries from a prior
114
+ -- create/load (in fctx.match / fctx.data), mirroring the TS mock
115
+ -- where param(ctx,'id') resolves from accumulated state.
114
116
  local update_match = {}
115
117
  if type(fctx.reqdata) == "table" then
116
118
  if fctx.reqdata["id"] ~= nil then update_match["id"] = fctx.reqdata["id"] end
@@ -121,6 +123,9 @@ function TestFeature:init(ctx, options)
121
123
  end
122
124
  end
123
125
  end
126
+ if next(update_match) == nil then
127
+ update_match = resolve_match({})
128
+ end
124
129
  local args = test_self:build_args(fctx, op, update_match)
125
130
  local found = vs.select(entmap, args)
126
131
  local ent = vs.getelem(found, 0)
@@ -148,9 +153,8 @@ function TestFeature:init(ctx, options)
148
153
  local args = test_self:build_args(fctx, op, resolve_match(fctx.reqmatch))
149
154
  local found = vs.select(entmap, args)
150
155
  local ent = vs.getelem(found, 0)
151
- if ent == nil then
152
- return respond(404, nil, { statusText = "Not found" })
153
- end
156
+ -- Remove only the first matched entity. If nothing matches,
157
+ -- succeed as a no-op rather than erroring.
154
158
  if type(ent) == "table" then
155
159
  local id = vs.getprop(ent, "id")
156
160
  vs.delprop(entmap, id)
@@ -162,6 +162,10 @@ class ProjectNameTestFeature extends ProjectNameBaseFeature
162
162
  // Match the existing entity by id only (or its alias). reqdata
163
163
  // also contains the new field values, which would otherwise
164
164
  // cause find_first to filter out the entity we want to update.
165
+ // When reqdata has no id, fall back to the id the entity
166
+ // client carries from a prior create/load (in $fctx->match /
167
+ // $fctx->data), mirroring the TS mock where param(ctx,'id')
168
+ // resolves from accumulated state.
165
169
  $update_match = [];
166
170
  if (is_array($fctx->reqdata)) {
167
171
  if (array_key_exists('id', $fctx->reqdata)) {
@@ -172,6 +176,9 @@ class ProjectNameTestFeature extends ProjectNameBaseFeature
172
176
  $update_match[$id_alias] = $fctx->reqdata[$id_alias];
173
177
  }
174
178
  }
179
+ if (empty($update_match)) {
180
+ $update_match = $resolve_match([]);
181
+ }
175
182
  $ent = $find_first($entmap, $update_match, $alias);
176
183
  if ($ent === null) {
177
184
  return $respond(404, null, ['statusText' => 'Not found']);
@@ -192,9 +199,8 @@ class ProjectNameTestFeature extends ProjectNameBaseFeature
192
199
 
193
200
  } elseif ($op->name === 'remove') {
194
201
  $ent = $find_first($entmap, $resolve_match($fctx->reqmatch), $alias);
195
- if ($ent === null) {
196
- return $respond(404, null, ['statusText' => 'Not found']);
197
- }
202
+ // Remove only the first matched entity. If nothing matches,
203
+ // succeed as a no-op rather than erroring.
198
204
  $id = is_array($ent) ? ($ent['id'] ?? null) : null;
199
205
  if ($id !== null) {
200
206
  unset($entmap[$id]);
@@ -90,8 +90,10 @@ class ProjectNameTestFeature(ProjectNameBaseFeature):
90
90
  # Match the existing entity by id only (or its alias). reqdata
91
91
  # also contains the new field values, which would otherwise
92
92
  # cause select to filter out the entity we want to update.
93
- # Falls back to first entity when no match found, mirroring
94
- # the TS mock.
93
+ # When reqdata has no id, fall back to the id the entity
94
+ # client carries from a prior create/load (in fctx.match /
95
+ # fctx.data), mirroring the TS mock where param(ctx,'id')
96
+ # resolves from accumulated state.
95
97
  update_match = {}
96
98
  if isinstance(fctx.reqdata, dict):
97
99
  if "id" in fctx.reqdata:
@@ -101,6 +103,8 @@ class ProjectNameTestFeature(ProjectNameBaseFeature):
101
103
  alias_id = vs.getprop(alias_map, "id")
102
104
  if alias_id is not None and alias_id in fctx.reqdata:
103
105
  update_match[alias_id] = fctx.reqdata[alias_id]
106
+ if not update_match:
107
+ update_match = _resolve_match({})
104
108
  args = test_self.build_args(fctx, op, update_match)
105
109
  found = vs.select(entmap, args)
106
110
  ent = vs.getelem(found, 0)
@@ -124,8 +128,8 @@ class ProjectNameTestFeature(ProjectNameBaseFeature):
124
128
  args = test_self.build_args(fctx, op, _resolve_match(fctx.reqmatch))
125
129
  found = vs.select(entmap, args)
126
130
  ent = vs.getelem(found, 0)
127
- if ent is None:
128
- return respond(404, None, {"statusText": "Not found"})
131
+ # Remove only the first matched entity. If nothing matches,
132
+ # succeed as a no-op rather than erroring.
129
133
  if isinstance(ent, dict):
130
134
  eid = vs.getprop(ent, "id")
131
135
  vs.delprop(entmap, eid)
@@ -84,10 +84,10 @@ class ProjectNameTestFeature < ProjectNameBaseFeature
84
84
  elsif op.name == "update"
85
85
  # Match the existing entity by id only (or its alias). reqdata also
86
86
  # contains the new field values, which would otherwise cause select
87
- # to filter out the entity we want to update. Falls back to first
88
- # entity when no id present and no match found, mirroring the TS
89
- # mock's empirical behavior where param(undef) collapses to "no
90
- # constraint" and select returns all.
87
+ # to filter out the entity we want to update. When reqdata has no id,
88
+ # fall back to the id the entity client carries from a prior
89
+ # create/load (in fctx.match / fctx.data), mirroring the TS mock
90
+ # where param(ctx,'id') resolves from accumulated state.
91
91
  update_match = {}
92
92
  if fctx.reqdata.is_a?(Hash)
93
93
  update_match["id"] = fctx.reqdata["id"] if fctx.reqdata.key?("id")
@@ -98,6 +98,7 @@ class ProjectNameTestFeature < ProjectNameBaseFeature
98
98
  end
99
99
  end
100
100
  end
101
+ update_match = resolve_match.call({}) if update_match.empty?
101
102
  args = test_self.build_args(fctx, op, update_match)
102
103
  found = VoxgigStruct.select(entmap, args)
103
104
  ent = VoxgigStruct.getelem(found, 0)
@@ -116,7 +117,8 @@ class ProjectNameTestFeature < ProjectNameBaseFeature
116
117
  args = test_self.build_args(fctx, op, resolve_match.call(fctx.reqmatch))
117
118
  found = VoxgigStruct.select(entmap, args)
118
119
  ent = VoxgigStruct.getelem(found, 0)
119
- return respond.call(404, nil, { "statusText" => "Not found" }) unless ent
120
+ # Remove only the first matched entity. If nothing matches,
121
+ # succeed as a no-op rather than erroring.
120
122
  if ent.is_a?(Hash)
121
123
  id = VoxgigStruct.getprop(ent, "id")
122
124
  VoxgigStruct.delprop(entmap, id)
@@ -124,13 +124,12 @@ class TestFeature extends BaseFeature {
124
124
  const args = self.buildArgs(ctx, op, ctx.reqmatch)
125
125
  const found = select(entmap, args)
126
126
  const ent = getelem(found, 0)
127
- if (null == ent) {
128
- return respond(404, undefined, { statusText: S_NOT_FOUND })
129
- }
130
- else {
127
+ // Remove only the first matched entity. If nothing matches,
128
+ // succeed as a no-op rather than erroring.
129
+ if (null != ent) {
131
130
  delprop(entmap, getprop(ent, 'id'))
132
- return respond(200)
133
131
  }
132
+ return respond(200)
134
133
  }
135
134
  else if ('create' === op.name) {
136
135
  const args = self.buildArgs(ctx, op, ctx.reqdata)