@voxgig/sdkgen 4.2.3 → 4.2.4

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, Skip } = require('shape')
8
8
 
9
9
  const { SdkGen } = require('../dist/sdkgen.js')
10
10
 
11
- const VERSION = '4.2.3'
11
+ const VERSION = '4.2.4'
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": "4.2.3",
3
+ "version": "4.2.4",
4
4
  "main": "dist/sdkgen.js",
5
5
  "type": "commonjs",
6
6
  "engines": {
@@ -6,7 +6,7 @@ import {
6
6
  getModelPath,
7
7
  } from '@voxgig/apidef'
8
8
 
9
- import { rustVarName } from './utility_rust'
9
+ import { rustVarName, rustMethodName } from './utility_rust'
10
10
 
11
11
 
12
12
  // Type names come from the shared canonToType 'rust' column (single source of truth).
@@ -56,7 +56,7 @@ const ReadmeEntity = cmp(function ReadmeEntity(props: any) {
56
56
  const fields = entity.fields || []
57
57
  const idF = entityIdField(entity)
58
58
  const eVar = rustVarName(entity.name)
59
- const method = rustVarName(entity.name)
59
+ const method = rustMethodName(entity.name)
60
60
 
61
61
  Content(`
62
62
  ### ${entity.Name}
@@ -7,7 +7,7 @@ import {
7
7
  nom,
8
8
  } from '@voxgig/apidef'
9
9
 
10
- import { crateIdent, rustVarName } from './utility_rust'
10
+ import { crateIdent, rustVarName, rustMethodName } from './utility_rust'
11
11
 
12
12
 
13
13
  // A type-correct rust expression constructing a voxgig struct Value.
@@ -31,7 +31,7 @@ const ReadmeHowto = cmp(function ReadmeHowto(props: any) {
31
31
  const { entity: exampleEntity, primaryOp } = pickExampleEntity(entity)
32
32
  const eName = exampleEntity ? nom(exampleEntity, 'Name') : 'Entity'
33
33
  const eVar = exampleEntity ? rustVarName(exampleEntity.name) : 'entity'
34
- const method = exampleEntity ? rustVarName(exampleEntity.name) : 'entity'
34
+ const method = exampleEntity ? rustMethodName(exampleEntity.name) : 'entity'
35
35
  const idF = exampleEntity ? entityIdField(exampleEntity) : null
36
36
  const isMatchOp = 'load' === primaryOp || 'remove' === primaryOp
37
37
  let testArg = 'Value::Noval'
@@ -6,7 +6,7 @@ import {
6
6
  getModelPath,
7
7
  } from '@voxgig/apidef'
8
8
 
9
- import { rustVarName } from './utility_rust'
9
+ import { rustVarName, rustMethodName } from './utility_rust'
10
10
 
11
11
 
12
12
  const ReadmeIntro = cmp(function ReadmeIntro(props: any) {
@@ -30,7 +30,7 @@ The ${target.title} SDK for the ${model.Name} API — an entity-oriented client
30
30
  const exampleEntity = Object.values(entity || {})
31
31
  .find((e: any) => e.active !== false) as any
32
32
  const example = exampleEntity
33
- ? `for example \`client.${rustVarName(exampleEntity.name)}(Value::Noval)\` — each`
33
+ ? `for example \`client.${rustMethodName(exampleEntity.name)}(Value::Noval)\` — each`
34
34
  : 'each'
35
35
 
36
36
  // Model-driven op list — only the operations the active entities actually
@@ -6,7 +6,7 @@ import {
6
6
  getModelPath,
7
7
  } from '@voxgig/apidef'
8
8
 
9
- import { crateIdent, rustVarName } from './utility_rust'
9
+ import { crateIdent, rustVarName, rustMethodName } from './utility_rust'
10
10
 
11
11
 
12
12
  const ReadmeModel = cmp(function ReadmeModel(props: any) {
@@ -77,7 +77,7 @@ Creates a test-mode client with mock transport. Both arguments may be
77
77
 
78
78
  each(entityList, (ent: any) => {
79
79
  const article = /^[aeiou]/i.test(ent.Name) ? 'an' : 'a'
80
- Content(`| \`${rustVarName(ent.name)}\` | \`(entopts: Value) -> Rc<${ent.Name}Entity>\` | Create ${article} ${ent.Name} entity instance. |
80
+ Content(`| \`${rustMethodName(ent.name)}\` | \`(entopts: Value) -> Rc<${ent.Name}Entity>\` | Create ${article} ${ent.Name} entity instance. |
81
81
  `)
82
82
  })
83
83
 
@@ -7,7 +7,7 @@ import {
7
7
  nom,
8
8
  } from '@voxgig/apidef'
9
9
 
10
- import { crateIdent, rustVarName } from './utility_rust'
10
+ import { crateIdent, rustVarName, rustMethodName } from './utility_rust'
11
11
 
12
12
 
13
13
  const ReadmeQuick = cmp(function ReadmeQuick(props: any) {
@@ -58,7 +58,7 @@ let client = ${ctor};
58
58
  const eName = nom(exampleEntity, 'Name')
59
59
  const article = /^[aeiou]/i.test(eName) ? 'an' : 'a'
60
60
  const eVar = rustVarName(exampleEntity.name)
61
- const method = rustVarName(exampleEntity.name)
61
+ const method = rustMethodName(exampleEntity.name)
62
62
  const opnames = entityOps(exampleEntity)
63
63
  // Model-driven id key: `idF` is the entity's id-like MATCH field name, or
64
64
  // null. `dataIdF` is the id on the RETURNED record's data type.
@@ -91,7 +91,7 @@ match client.${method}(Value::Noval).list(Value::Noval, Value::Noval) {
91
91
  const neName = nom(nestedEntity, 'Name')
92
92
  const neArticle = /^[aeiou]/i.test(neName) ? 'an' : 'a'
93
93
  const neVar = rustVarName(nestedEntity.name)
94
- const neMethod = rustVarName(nestedEntity.name)
94
+ const neMethod = rustMethodName(nestedEntity.name)
95
95
 
96
96
  const neIdF = entityIdField(nestedEntity)
97
97
  const neRequired = opRequestShape(nestedEntity, 'load').items
@@ -6,7 +6,7 @@ import {
6
6
  getModelPath,
7
7
  } from '@voxgig/apidef'
8
8
 
9
- import { crateIdent, rustVarName } from './utility_rust'
9
+ import { crateIdent, rustVarName, rustMethodName } from './utility_rust'
10
10
 
11
11
 
12
12
  // Type names come from the shared canonToType 'rust' column (single source of truth).
@@ -120,7 +120,7 @@ let client = test_sdk(Value::Noval, Value::Noval);
120
120
 
121
121
  // Entity factory methods
122
122
  publishedEntities.map((ent: any) => {
123
- Content(`#### \`${rustVarName(ent.name)}(entopts: Value) -> Rc<${ent.Name}Entity>\`
123
+ Content(`#### \`${rustMethodName(ent.name)}(entopts: Value) -> Rc<${ent.Name}Entity>\`
124
124
 
125
125
  Create a new \`${ent.Name}Entity\` instance. Pass \`Value::Noval\` for no
126
126
  initial options.
@@ -168,7 +168,7 @@ Prepare a fetch definition without sending. Returns the fetchdef on \`Ok\`.
168
168
  const fields = ent.fields || []
169
169
  const idF = entityIdField(ent)
170
170
  const eVar = rustVarName(ent.name)
171
- const method = rustVarName(ent.name)
171
+ const method = rustMethodName(ent.name)
172
172
 
173
173
  Content(`
174
174
  ---
@@ -7,7 +7,7 @@ import {
7
7
  nom,
8
8
  } from '@voxgig/apidef'
9
9
 
10
- import { crateIdent, rustVarName } from './utility_rust'
10
+ import { crateIdent, rustVarName, rustMethodName } from './utility_rust'
11
11
 
12
12
 
13
13
  // A type-correct rust expression constructing a voxgig struct Value for a
@@ -46,7 +46,7 @@ let client = ${ctor};
46
46
  if (exampleEntity) {
47
47
  const eName = nom(exampleEntity, 'Name')
48
48
  const eVar = rustVarName(exampleEntity.name)
49
- const method = rustVarName(exampleEntity.name)
49
+ const method = rustMethodName(exampleEntity.name)
50
50
  const opnames = Object.keys(exampleEntity.op || {})
51
51
  // Model-driven id key: null when the entity has no id-like field, in which
52
52
  // case the load example takes no match argument.
@@ -7,7 +7,7 @@ import {
7
7
  nom,
8
8
  } from '@voxgig/apidef'
9
9
 
10
- import { crateIdent, rustVarName } from './utility_rust'
10
+ import { crateIdent, rustVarName, rustMethodName } from './utility_rust'
11
11
 
12
12
 
13
13
  // A type-correct rust expression constructing a voxgig struct Value for a
@@ -40,7 +40,7 @@ let client = test_sdk(Value::Noval, Value::Noval);
40
40
 
41
41
  if (exampleEntity && primaryOp) {
42
42
  const eName = nom(exampleEntity, 'Name')
43
- const method = rustVarName(exampleEntity.name)
43
+ const method = rustMethodName(exampleEntity.name)
44
44
  const idF = entityIdField(exampleEntity)
45
45
  const isMatchOp = 'load' === primaryOp || 'remove' === primaryOp
46
46
  let arg = 'Value::Noval'
@@ -21,7 +21,7 @@ import {
21
21
  } from '@voxgig/sdkgen'
22
22
 
23
23
 
24
- import { rustVarName } from './utility_rust'
24
+ import { rustVarName, rustMethodName } from './utility_rust'
25
25
 
26
26
 
27
27
  // Rust's GenCtx mirrors the shared shape (see TestEntity_ts.ts) plus the
@@ -100,7 +100,7 @@ const TestEntity = cmp(function TestEntity(props: any) {
100
100
  }
101
101
  `
102
102
 
103
- const method = rustVarName(entity.name)
103
+ const method = rustMethodName(entity.name)
104
104
  const evar = rustVarName(entity.name)
105
105
 
106
106
  File({ name: entity.name + '_entity_test.' + target.ext }, () => {
@@ -330,7 +330,7 @@ const generateCreate: OpGen = (ctx, step, index) => {
330
330
  Content(` // CREATE
331
331
  `)
332
332
  if (needsEnt) {
333
- Content(` let ${entvar} = client.${rustVarName(entity.name)}(Value::Noval);
333
+ Content(` let ${entvar} = client.${rustMethodName(entity.name)}(Value::Noval);
334
334
  `)
335
335
  }
336
336
 
@@ -381,7 +381,7 @@ const generateList: OpGen = (ctx, step, index) => {
381
381
  Content(` // LIST
382
382
  `)
383
383
  if (needsEnt) {
384
- Content(` let ${entvar} = client.${rustVarName(entity.name)}(Value::Noval);
384
+ Content(` let ${entvar} = client.${rustMethodName(entity.name)}(Value::Noval);
385
385
  `)
386
386
  }
387
387
 
@@ -464,7 +464,7 @@ const generateUpdate: OpGen = (ctx, step, index) => {
464
464
  Content(` // UPDATE
465
465
  `)
466
466
  if (needsEnt) {
467
- Content(` let ${entvar} = client.${rustVarName(entity.name)}(Value::Noval);
467
+ Content(` let ${entvar} = client.${rustMethodName(entity.name)}(Value::Noval);
468
468
  `)
469
469
  }
470
470
  Content(` let ${datavar}_up = Value::empty_map();
@@ -568,7 +568,7 @@ const generateLoad: OpGen = (ctx, step, index) => {
568
568
  Content(` // LOAD
569
569
  `)
570
570
  if (!hasEntVar) {
571
- Content(` let ${entvar} = client.${rustVarName(entity.name)}(Value::Noval);
571
+ Content(` let ${entvar} = client.${rustMethodName(entity.name)}(Value::Noval);
572
572
  `)
573
573
  }
574
574
  if (!hasSrcData && hasEntId) {
@@ -626,7 +626,7 @@ const generateRemove: OpGen = (ctx, step, index) => {
626
626
  Content(` // REMOVE
627
627
  `)
628
628
  if (needsEnt) {
629
- Content(` let ${entvar} = client.${rustVarName(entity.name)}(Value::Noval);
629
+ Content(` let ${entvar} = client.${rustMethodName(entity.name)}(Value::Noval);
630
630
  `)
631
631
  }
632
632
  // Always match the prior-created entity by id to avoid mock-order flakes.
@@ -26,17 +26,74 @@ defmodule Voxgig.Struct do
26
26
  # ---------------------------------------------------------------------------
27
27
 
28
28
  @heap :vox_struct_heap
29
-
29
+ @heap_owner :vox_struct_heap_owner
30
+
31
+ # A named ETS table is owned by the process that created it and dies with
32
+ # that process. The values here are HANDLES into it - {:vmap, id} - and they
33
+ # are passed across process boundaries freely: ExUnit runs every test in its
34
+ # own process, and a generated SDK caches its config in :persistent_term,
35
+ # which outlives all of them. So the table has to outlive its creator, or a
36
+ # handle that is still very much alive resolves into a fresh empty table.
37
+ #
38
+ # Adding ensure_heap to hget stopped that raising ArgumentError, but a silent
39
+ # nil turned out to be worse than the raise: the generated config's staleness
40
+ # check (`usable?`) was written to read the raise as "rebuild me", so once the
41
+ # raise stopped, a dead handle was cached as good and clone/1 crashed further
42
+ # down with "Enumerable not implemented for nil of type Atom" - nothing in the
43
+ # message pointing at the heap.
44
+ #
45
+ # The table is therefore owned by a dedicated process that never exits, and
46
+ # every id stays valid for the life of the VM.
30
47
  defp ensure_heap do
31
48
  case :ets.whereis(@heap) do
32
- :undefined ->
49
+ :undefined -> start_heap()
50
+ _ -> :ok
51
+ end
52
+ end
53
+
54
+ defp start_heap do
55
+ caller = self()
56
+
57
+ spawn(fn ->
58
+ owner =
33
59
  try do
34
- :ets.new(@heap, [:set, :public, :named_table])
60
+ Process.register(self(), @heap_owner)
61
+ true
35
62
  rescue
36
- ArgumentError -> :ok
63
+ ArgumentError -> false
37
64
  end
38
65
 
39
- :ok
66
+ if owner do
67
+ case :ets.whereis(@heap) do
68
+ :undefined -> :ets.new(@heap, [:set, :public, :named_table])
69
+ _ -> :ok
70
+ end
71
+ end
72
+
73
+ send(caller, :vox_heap_ready)
74
+
75
+ # Hold the table for the life of the VM. This process does nothing else.
76
+ if owner, do: Process.sleep(:infinity)
77
+ end)
78
+
79
+ receive do
80
+ :vox_heap_ready -> :ok
81
+ after
82
+ 5_000 -> :ok
83
+ end
84
+
85
+ await_heap(1_000)
86
+ end
87
+
88
+ # The loser of a registration race reports ready before the winner has
89
+ # necessarily reached :ets.new, so wait on the table, not on the message.
90
+ defp await_heap(0), do: :ok
91
+
92
+ defp await_heap(n) do
93
+ case :ets.whereis(@heap) do
94
+ :undefined ->
95
+ Process.sleep(1)
96
+ await_heap(n - 1)
40
97
 
41
98
  _ ->
42
99
  :ok
@@ -50,13 +107,10 @@ defmodule Voxgig.Struct do
50
107
  id
51
108
  end
52
109
 
53
- # ensure_heap here too, not only in alloc. A named ETS table belongs to the
54
- # process that created it and dies with it and ExUnit runs each test in its
55
- # own process, so a table created by an alloc in one test is gone by the
56
- # next. hget then raised ArgumentError ("the table identifier does not refer
57
- # to an existing ETS table") from whichever unrelated test happened to run
58
- # second, which is why this surfaced as a pipeline/netsim failure rather than
59
- # as anything to do with the heap.
110
+ # ensure_heap here too, not only in alloc, so a read can never hit a missing
111
+ # table. With a permanent owner (above) that should not arise; before there
112
+ # was one, it raised ArgumentError from whichever unrelated test happened to
113
+ # run second.
60
114
  defp hget(id) do
61
115
  ensure_heap()
62
116
 
@@ -8,10 +8,30 @@ build:
8
8
  # tests pass, due to *MapRef/*ListRef cross-references in arena teardown. We
9
9
  # filter the output: if "N/N tests passed" with N==total, treat as success.
10
10
  # `--summary all` is required so the count is printed.
11
+ #
12
+ # A TEST THAT DID NOT COMPILE IS NOT IN THE TALLY. The tolerance above is for a
13
+ # crash AFTER the tests ran, and it must never cover a build that failed: a
14
+ # compile error means those test binaries never ran, so they never reach
15
+ # "total", and "60/66 passed; 6 skipped" reads as a clean 66/66 while five of
16
+ # thirteen build steps are red. That is exactly how a `.undef` tag that does
17
+ # not exist in JsonValue shipped in 4.2.2 and stayed green in every sweep
18
+ # afterwards. So: if any build step failed, this target fails, whatever the
19
+ # test counts say.
11
20
  test:
12
21
  @out=$$(zig build test --summary all 2>&1); code=$$?; \
13
22
  echo "$$out"; \
14
23
  if [ $$code -eq 0 ]; then exit 0; fi; \
24
+ steps=$$(echo "$$out" | grep -oE '[0-9]+/[0-9]+ steps succeeded' | tail -1); \
25
+ sdone=$$(echo "$$steps" | sed -E 's#([0-9]+)/([0-9]+).*#\1#'); \
26
+ stotal=$$(echo "$$steps" | sed -E 's#([0-9]+)/([0-9]+).*#\2#'); \
27
+ if [ -n "$$steps" ] && [ "$$sdone" != "$$stotal" ]; then \
28
+ echo "zig: $$steps — a build step failed, so the test tally is incomplete"; \
29
+ exit 1; \
30
+ fi; \
31
+ if echo "$$out" | grep -q 'error: the following command failed with .* compilation errors'; then \
32
+ echo "zig: compilation errors — not a teardown crash"; \
33
+ exit 1; \
34
+ fi; \
15
35
  summary=$$(echo "$$out" | grep -oE '[0-9]+/[0-9]+ tests passed(; [0-9]+ skipped)?' | tail -1); \
16
36
  passed=$$(echo "$$summary" | sed -E 's#([0-9]+)/([0-9]+).*#\1#'); \
17
37
  total=$$(echo "$$summary" | sed -E 's#([0-9]+)/([0-9]+).*#\2#'); \
@@ -105,7 +105,7 @@ fn fixIds(_: Allocator, key: ?[]const u8, val: Value, _: Value, path: []const []
105
105
  // while the fixture plainly held two. Mirrors the go/ts/lua/php mocks, which
106
106
  // already wrap; rust, c and zig were the three that did not.
107
107
  fn envelope(ctx: *Context, data: Value) Value {
108
- if (data == .undef or data == .null) return data;
108
+ if (data == .null) return data;
109
109
  const restf = h.getpath(&.{ "transform", "res" }, ctx.point);
110
110
  if (restf != .string) return data;
111
111
  const spec = restf.string;
@@ -3,7 +3,7 @@
3
3
  "package": 1
4
4
  },
5
5
  "name": "@voxgig/sdkgen",
6
- "version": "4.2.3",
6
+ "version": "4.2.4",
7
7
  "provides": {
8
8
  "target": [
9
9
  "c",