goscript 0.1.4 → 0.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.
Files changed (270) hide show
  1. package/README.md +5 -2
  2. package/cmd/go_js_wasm_exec/main.go +201 -0
  3. package/cmd/go_js_wasm_exec/main_test.go +83 -0
  4. package/cmd/goscript/{cmd_compile.go → cmd-compile.go} +7 -0
  5. package/cmd/goscript/cmd-test.go +14 -0
  6. package/cmd/goscript/cmd-test_test.go +1 -1
  7. package/compiler/compile-request.go +12 -9
  8. package/compiler/compliance_test.go +0 -1
  9. package/compiler/config.go +2 -0
  10. package/compiler/gotest/request.go +28 -0
  11. package/compiler/gotest/runner.go +353 -27
  12. package/compiler/gotest/runner_test.go +273 -1
  13. package/compiler/gotest/testdata/browserapi/browserapi_test.go +20 -0
  14. package/compiler/gotest/testdata/browserapi/go.mod +3 -0
  15. package/compiler/lowered-program.go +24 -17
  16. package/compiler/lowering.go +392 -127
  17. package/compiler/lowering_bench_test.go +41 -27
  18. package/compiler/override-facts.go +15 -0
  19. package/compiler/override-parity-verifier.go +450 -0
  20. package/compiler/override-parity.go +122 -0
  21. package/compiler/override-registry_test.go +559 -0
  22. package/compiler/protobuf-ts-binding.go +514 -0
  23. package/compiler/protobuf-ts-binding_test.go +172 -0
  24. package/compiler/semantic-model-types.go +9 -4
  25. package/compiler/semantic-model.go +282 -70
  26. package/compiler/semantic-model_test.go +82 -1
  27. package/compiler/service.go +20 -1
  28. package/compiler/skeleton_test.go +62 -8
  29. package/compiler/typescript-emitter.go +128 -13
  30. package/dist/gs/builtin/slice.d.ts +2 -1
  31. package/dist/gs/builtin/slice.js +29 -4
  32. package/dist/gs/builtin/slice.js.map +1 -1
  33. package/dist/gs/builtin/type.d.ts +13 -5
  34. package/dist/gs/builtin/type.js +153 -60
  35. package/dist/gs/builtin/type.js.map +1 -1
  36. package/dist/gs/builtin/varRef.d.ts +11 -0
  37. package/dist/gs/builtin/varRef.js +57 -2
  38. package/dist/gs/builtin/varRef.js.map +1 -1
  39. package/dist/gs/bytes/buffer.gs.js +1 -1
  40. package/dist/gs/bytes/buffer.gs.js.map +1 -1
  41. package/dist/gs/bytes/reader.gs.js +1 -1
  42. package/dist/gs/bytes/reader.gs.js.map +1 -1
  43. package/dist/gs/compress/zlib/index.d.ts +10 -3
  44. package/dist/gs/compress/zlib/index.js +50 -16
  45. package/dist/gs/compress/zlib/index.js.map +1 -1
  46. package/dist/gs/encoding/json/index.d.ts +114 -0
  47. package/dist/gs/encoding/json/index.js +544 -36
  48. package/dist/gs/encoding/json/index.js.map +1 -1
  49. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.d.ts +100 -0
  50. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js +564 -0
  51. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js.map +1 -1
  52. package/dist/gs/github.com/pkg/errors/errors.js +54 -30
  53. package/dist/gs/github.com/pkg/errors/errors.js.map +1 -1
  54. package/dist/gs/go/scanner/index.d.ts +2 -0
  55. package/dist/gs/go/scanner/index.js +29 -5
  56. package/dist/gs/go/scanner/index.js.map +1 -1
  57. package/dist/gs/go/token/index.js +22 -6
  58. package/dist/gs/go/token/index.js.map +1 -1
  59. package/dist/gs/hash/index.d.ts +6 -0
  60. package/dist/gs/hash/index.js +20 -0
  61. package/dist/gs/hash/index.js.map +1 -1
  62. package/dist/gs/internal/goarch/index.d.ts +43 -3
  63. package/dist/gs/internal/goarch/index.js +42 -10
  64. package/dist/gs/internal/goarch/index.js.map +1 -1
  65. package/dist/gs/io/fs/fs.js +26 -14
  66. package/dist/gs/io/fs/fs.js.map +1 -1
  67. package/dist/gs/io/fs/readdir.js +4 -2
  68. package/dist/gs/io/fs/readdir.js.map +1 -1
  69. package/dist/gs/io/fs/sub.js +8 -1
  70. package/dist/gs/io/fs/sub.js.map +1 -1
  71. package/dist/gs/io/io.d.ts +2 -0
  72. package/dist/gs/io/io.js.map +1 -1
  73. package/dist/gs/math/bits/index.d.ts +5 -0
  74. package/dist/gs/math/bits/index.js +16 -4
  75. package/dist/gs/math/bits/index.js.map +1 -1
  76. package/dist/gs/mime/index.d.ts +16 -0
  77. package/dist/gs/mime/index.js +315 -6
  78. package/dist/gs/mime/index.js.map +1 -1
  79. package/dist/gs/net/http/httptest/index.d.ts +12 -0
  80. package/dist/gs/net/http/httptest/index.js +85 -6
  81. package/dist/gs/net/http/httptest/index.js.map +1 -1
  82. package/dist/gs/net/http/index.d.ts +300 -5
  83. package/dist/gs/net/http/index.js +1598 -58
  84. package/dist/gs/net/http/index.js.map +1 -1
  85. package/dist/gs/os/dir_unix.gs.js +1 -1
  86. package/dist/gs/os/dir_unix.gs.js.map +1 -1
  87. package/dist/gs/os/error.gs.js +1 -1
  88. package/dist/gs/os/error.gs.js.map +1 -1
  89. package/dist/gs/os/exec.gs.d.ts +1 -0
  90. package/dist/gs/os/exec.gs.js +4 -8
  91. package/dist/gs/os/exec.gs.js.map +1 -1
  92. package/dist/gs/os/exec_posix.gs.js +1 -1
  93. package/dist/gs/os/exec_posix.gs.js.map +1 -1
  94. package/dist/gs/os/index.d.ts +1 -1
  95. package/dist/gs/os/index.js +1 -1
  96. package/dist/gs/os/index.js.map +1 -1
  97. package/dist/gs/os/proc.gs.d.ts +4 -0
  98. package/dist/gs/os/proc.gs.js +12 -6
  99. package/dist/gs/os/proc.gs.js.map +1 -1
  100. package/dist/gs/os/root_js.gs.js +1 -1
  101. package/dist/gs/os/root_js.gs.js.map +1 -1
  102. package/dist/gs/os/types.gs.js +1 -1
  103. package/dist/gs/os/types.gs.js.map +1 -1
  104. package/dist/gs/os/types_js.gs.js +1 -1
  105. package/dist/gs/os/types_js.gs.js.map +1 -1
  106. package/dist/gs/os/types_unix.gs.js +1 -1
  107. package/dist/gs/os/types_unix.gs.js.map +1 -1
  108. package/dist/gs/path/path.js +11 -7
  109. package/dist/gs/path/path.js.map +1 -1
  110. package/dist/gs/reflect/index.d.ts +5 -4
  111. package/dist/gs/reflect/index.js +4 -3
  112. package/dist/gs/reflect/index.js.map +1 -1
  113. package/dist/gs/reflect/map.js +15 -0
  114. package/dist/gs/reflect/map.js.map +1 -1
  115. package/dist/gs/reflect/type.d.ts +25 -6
  116. package/dist/gs/reflect/type.js +1418 -228
  117. package/dist/gs/reflect/type.js.map +1 -1
  118. package/dist/gs/reflect/types.d.ts +14 -6
  119. package/dist/gs/reflect/types.js +35 -1
  120. package/dist/gs/reflect/types.js.map +1 -1
  121. package/dist/gs/reflect/value.d.ts +1 -0
  122. package/dist/gs/reflect/value.js +83 -41
  123. package/dist/gs/reflect/value.js.map +1 -1
  124. package/dist/gs/reflect/visiblefields.js +4 -140
  125. package/dist/gs/reflect/visiblefields.js.map +1 -1
  126. package/dist/gs/runtime/pprof/index.d.ts +8 -2
  127. package/dist/gs/runtime/pprof/index.js +50 -30
  128. package/dist/gs/runtime/pprof/index.js.map +1 -1
  129. package/dist/gs/runtime/runtime.js +5 -4
  130. package/dist/gs/runtime/runtime.js.map +1 -1
  131. package/dist/gs/runtime/trace/index.js +5 -19
  132. package/dist/gs/runtime/trace/index.js.map +1 -1
  133. package/dist/gs/strconv/atoi.gs.js +1 -1
  134. package/dist/gs/strconv/atoi.gs.js.map +1 -1
  135. package/dist/gs/strconv/complex.gs.d.ts +3 -0
  136. package/dist/gs/strconv/complex.gs.js +148 -0
  137. package/dist/gs/strconv/complex.gs.js.map +1 -0
  138. package/dist/gs/strconv/index.d.ts +1 -0
  139. package/dist/gs/strconv/index.js +1 -0
  140. package/dist/gs/strconv/index.js.map +1 -1
  141. package/dist/gs/strings/builder.js +1 -1
  142. package/dist/gs/strings/reader.js +9 -5
  143. package/dist/gs/strings/reader.js.map +1 -1
  144. package/dist/gs/strings/replace.js +15 -7
  145. package/dist/gs/strings/replace.js.map +1 -1
  146. package/dist/gs/strings/strings.d.ts +5 -0
  147. package/dist/gs/strings/strings.js +57 -5
  148. package/dist/gs/strings/strings.js.map +1 -1
  149. package/dist/gs/sync/atomic/type.gs.js +9 -9
  150. package/dist/gs/sync/atomic/type.gs.js.map +1 -1
  151. package/dist/gs/sync/atomic/value.gs.js +2 -2
  152. package/dist/gs/sync/atomic/value.gs.js.map +1 -1
  153. package/dist/gs/syscall/env.js +22 -14
  154. package/dist/gs/syscall/env.js.map +1 -1
  155. package/dist/gs/testing/testing.js +55 -13
  156. package/dist/gs/testing/testing.js.map +1 -1
  157. package/dist/gs/time/time.d.ts +24 -1
  158. package/dist/gs/time/time.js +43 -3
  159. package/dist/gs/time/time.js.map +1 -1
  160. package/dist/gs/unique/index.js +7 -1
  161. package/dist/gs/unique/index.js.map +1 -1
  162. package/go.mod +3 -3
  163. package/go.sum +16 -0
  164. package/gs/builtin/runtime-contract.test.ts +218 -21
  165. package/gs/builtin/slice.ts +44 -4
  166. package/gs/builtin/type.ts +226 -59
  167. package/gs/builtin/varRef.ts +85 -2
  168. package/gs/bytes/buffer.gs.ts +1 -1
  169. package/gs/bytes/reader.gs.ts +1 -1
  170. package/gs/compress/zlib/index.test.ts +62 -1
  171. package/gs/compress/zlib/index.ts +53 -16
  172. package/gs/compress/zlib/parity.json +51 -0
  173. package/gs/encoding/json/index.test.ts +360 -6
  174. package/gs/encoding/json/index.ts +679 -38
  175. package/gs/encoding/json/parity.json +81 -0
  176. package/gs/github.com/aperturerobotics/protobuf-go-lite/index.test.ts +211 -3
  177. package/gs/github.com/aperturerobotics/protobuf-go-lite/index.ts +857 -1
  178. package/gs/github.com/pkg/errors/errors.ts +54 -30
  179. package/gs/go/scanner/index.test.ts +39 -56
  180. package/gs/go/scanner/index.ts +33 -5
  181. package/gs/go/scanner/parity.json +27 -0
  182. package/gs/go/token/index.ts +22 -6
  183. package/gs/hash/index.test.ts +20 -33
  184. package/gs/hash/index.ts +28 -0
  185. package/gs/hash/parity.json +21 -0
  186. package/gs/internal/goarch/index.test.ts +32 -0
  187. package/gs/internal/goarch/index.ts +45 -13
  188. package/gs/internal/goarch/parity.json +144 -0
  189. package/gs/io/fs/fs.ts +26 -14
  190. package/gs/io/fs/readdir.ts +4 -4
  191. package/gs/io/fs/sub.ts +8 -1
  192. package/gs/io/io.ts +1 -0
  193. package/gs/io/parity.json +162 -0
  194. package/gs/math/bits/index.test.ts +14 -1
  195. package/gs/math/bits/index.ts +23 -4
  196. package/gs/math/bits/parity.json +156 -0
  197. package/gs/mime/index.test.ts +90 -0
  198. package/gs/mime/index.ts +369 -6
  199. package/gs/mime/parity.json +36 -0
  200. package/gs/net/http/httptest/index.test.ts +98 -2
  201. package/gs/net/http/httptest/index.ts +101 -6
  202. package/gs/net/http/httptest/parity.json +15 -0
  203. package/gs/net/http/index.test.ts +781 -12
  204. package/gs/net/http/index.ts +1860 -139
  205. package/gs/net/http/meta.json +16 -1
  206. package/gs/net/http/parity.json +193 -0
  207. package/gs/os/dir_unix.gs.ts +1 -1
  208. package/gs/os/error.gs.ts +1 -1
  209. package/gs/os/exec.gs.ts +4 -8
  210. package/gs/os/exec_posix.gs.ts +1 -1
  211. package/gs/os/index.test.ts +9 -0
  212. package/gs/os/index.ts +1 -0
  213. package/gs/os/parity.json +9 -0
  214. package/gs/os/proc.gs.ts +18 -5
  215. package/gs/os/proc.test.ts +26 -0
  216. package/gs/os/root_js.gs.ts +1 -1
  217. package/gs/os/types.gs.ts +1 -1
  218. package/gs/os/types_js.gs.ts +1 -1
  219. package/gs/os/types_unix.gs.ts +1 -1
  220. package/gs/path/path.ts +11 -7
  221. package/gs/reflect/field.test.ts +37 -15
  222. package/gs/reflect/function-types.test.ts +518 -22
  223. package/gs/reflect/index.ts +8 -6
  224. package/gs/reflect/map.ts +20 -0
  225. package/gs/reflect/meta.json +6 -4
  226. package/gs/reflect/parity.json +234 -0
  227. package/gs/reflect/sliceat.test.ts +156 -0
  228. package/gs/reflect/structof.test.ts +401 -0
  229. package/gs/reflect/type.ts +1897 -317
  230. package/gs/reflect/typefor.test.ts +510 -10
  231. package/gs/reflect/types.ts +43 -18
  232. package/gs/reflect/value.ts +105 -45
  233. package/gs/reflect/visiblefields.ts +5 -168
  234. package/gs/runtime/parity.json +24 -0
  235. package/gs/runtime/pprof/index.test.ts +29 -7
  236. package/gs/runtime/pprof/index.ts +56 -30
  237. package/gs/runtime/pprof/parity.json +27 -0
  238. package/gs/runtime/runtime.test.ts +3 -1
  239. package/gs/runtime/runtime.ts +4 -3
  240. package/gs/runtime/trace/index.test.ts +5 -3
  241. package/gs/runtime/trace/index.ts +8 -20
  242. package/gs/runtime/trace/parity.json +36 -0
  243. package/gs/strconv/atoi.gs.ts +1 -1
  244. package/gs/strconv/complex.gs.ts +174 -0
  245. package/gs/strconv/complex.test.ts +65 -0
  246. package/gs/strconv/index.ts +1 -0
  247. package/gs/strconv/parity.json +120 -0
  248. package/gs/strings/builder.ts +1 -1
  249. package/gs/strings/parity.json +186 -0
  250. package/gs/strings/reader.ts +9 -5
  251. package/gs/strings/replace.ts +15 -7
  252. package/gs/strings/strings.test.ts +22 -2
  253. package/gs/strings/strings.ts +64 -6
  254. package/gs/sync/atomic/type.gs.ts +9 -9
  255. package/gs/sync/atomic/value.gs.ts +2 -2
  256. package/gs/syscall/env.ts +29 -14
  257. package/gs/testing/testing.test.ts +67 -0
  258. package/gs/testing/testing.ts +87 -19
  259. package/gs/time/parity.json +225 -0
  260. package/gs/time/time.test.ts +20 -2
  261. package/gs/time/time.ts +49 -7
  262. package/gs/unique/index.ts +7 -1
  263. package/package.json +4 -2
  264. package/dist/gs/github.com/aperturerobotics/starpc/srpc/index.d.ts +0 -217
  265. package/dist/gs/github.com/aperturerobotics/starpc/srpc/index.js +0 -926
  266. package/dist/gs/github.com/aperturerobotics/starpc/srpc/index.js.map +0 -1
  267. package/gs/github.com/aperturerobotics/starpc/srpc/index.test.ts +0 -38
  268. package/gs/github.com/aperturerobotics/starpc/srpc/index.ts +0 -1361
  269. package/gs/github.com/aperturerobotics/starpc/srpc/meta.json +0 -46
  270. /package/compiler/{wasm_api.go → wasm-api.go} +0 -0
@@ -4,9 +4,43 @@ import * as $ from '@goscript/builtin/index.js'
4
4
  import * as bytes from '@goscript/bytes/index.js'
5
5
  import * as io from '@goscript/io/index.js'
6
6
 
7
- import { NewReader, NewWriter } from './index.js'
7
+ import {
8
+ BestCompression,
9
+ BestSpeed,
10
+ DefaultCompression,
11
+ ErrChecksum,
12
+ ErrDictionary,
13
+ HuffmanOnly,
14
+ NewReader,
15
+ NewReaderDict,
16
+ NewWriter,
17
+ NewWriterLevel,
18
+ NewWriterLevelDict,
19
+ NoCompression,
20
+ } from './index.js'
8
21
 
9
22
  describe('compress/zlib override', () => {
23
+ test('exports flate compression level constants', () => {
24
+ expect(NoCompression).toBe(0)
25
+ expect(BestSpeed).toBe(1)
26
+ expect(BestCompression).toBe(9)
27
+ expect(DefaultCompression).toBe(-1)
28
+ expect(HuffmanOnly).toBe(-2)
29
+ })
30
+
31
+ test('rejects invalid compression levels', () => {
32
+ const buf = $.markAsStructValue(new bytes.Buffer())
33
+
34
+ expect(NewWriterLevel(buf, HuffmanOnly)[1]).toBeNull()
35
+ expect(NewWriterLevel(buf, BestCompression)[1]).toBeNull()
36
+ expect(NewWriterLevel(buf, HuffmanOnly - 1)[1]?.Error()).toBe(
37
+ 'zlib: invalid compression level: -3',
38
+ )
39
+ expect(NewWriterLevelDict(buf, BestCompression + 1, null)[1]?.Error()).toBe(
40
+ 'zlib: invalid compression level: 10',
41
+ )
42
+ })
43
+
10
44
  test('round trips bytes through writer and reader', async () => {
11
45
  const input = $.stringToBytes('hello compressed world')
12
46
  const buf = $.markAsStructValue(new bytes.Buffer())
@@ -82,6 +116,33 @@ describe('compress/zlib override', () => {
82
116
  expect($.bytesToString(secondOut)).toBe('second stream')
83
117
  })
84
118
 
119
+ test('writer and reader honor preset dictionaries', async () => {
120
+ const dict = $.stringToBytes('hello dictionary')
121
+ const compressed = $.markAsStructValue(new bytes.Buffer())
122
+ const [writer, writerErr] = NewWriterLevelDict(compressed, DefaultCompression, dict)
123
+ expect(writerErr).toBeNull()
124
+ expect(writer!.Write($.stringToBytes('hello dictionary payload'))[1]).toBeNull()
125
+ expect(await writer!.Close()).toBeNull()
126
+
127
+ const [missingDictReader, missingDictErr] = NewReader(bytes.NewReader(compressed.Bytes()))
128
+ expect(missingDictReader).toBeNull()
129
+ expect(missingDictErr).toBe(ErrDictionary)
130
+
131
+ const [reader, readerErr] = NewReaderDict(bytes.NewReader(compressed.Bytes()), dict)
132
+ expect(readerErr).toBeNull()
133
+ const [out, readErr] = await io.ReadAll(reader!)
134
+ expect(readErr).toBeNull()
135
+ expect($.bytesToString(out)).toBe('hello dictionary payload')
136
+
137
+ const [, wrongDictErr] = NewReaderDict(bytes.NewReader(compressed.Bytes()), $.stringToBytes('wrong dictionary'))
138
+ expect(wrongDictErr).toBe(ErrDictionary)
139
+
140
+ const corrupt = Uint8Array.from(compressed.Bytes())
141
+ corrupt[corrupt.length - 1] ^= 0xff
142
+ const [, corruptErr] = NewReaderDict(bytes.NewReader(corrupt), dict)
143
+ expect(corruptErr).toBe(ErrChecksum)
144
+ })
145
+
85
146
  test('reader reset accepts async generated readers', async () => {
86
147
  const compressed = $.markAsStructValue(new bytes.Buffer())
87
148
  const writer = NewWriter(compressed)
@@ -10,10 +10,18 @@ type maybeAsyncWriter = {
10
10
  Write(p: $.Bytes): [number, $.GoError] | Promise<[number, $.GoError]>
11
11
  }
12
12
 
13
+ export const NoCompression = 0
14
+ export const BestSpeed = 1
15
+ export const BestCompression = 9
16
+ export const DefaultCompression = -1
17
+ export const HuffmanOnly = -2
18
+
13
19
  export let ErrChecksum = errors.New('zlib: invalid checksum')
14
20
  export let ErrDictionary = errors.New('zlib: invalid dictionary')
15
21
  export let ErrHeader = errors.New('zlib: invalid header')
16
22
 
23
+ type nodeZlibError = Error & { code?: unknown }
24
+
17
25
  export function __goscript_set_ErrChecksum(value: $.GoError): void {
18
26
  ErrChecksum = value
19
27
  }
@@ -89,7 +97,11 @@ export class Writer {
89
97
  private chunks: Uint8Array[] = []
90
98
  private closed = false
91
99
 
92
- constructor(private w: io.Writer | null) {}
100
+ constructor(
101
+ private w: io.Writer | null,
102
+ private dict: $.Bytes | null = null,
103
+ private level = DefaultCompression,
104
+ ) {}
93
105
 
94
106
  Write(p: $.Bytes): [number, $.GoError] {
95
107
  if (this.closed) {
@@ -108,7 +120,7 @@ export class Writer {
108
120
  if (this.w == null) {
109
121
  return errors.New('zlib: nil writer')
110
122
  }
111
- const compressed = deflate(concat(this.chunks))
123
+ const compressed = deflate(concat(this.chunks), this.dict, this.level)
112
124
  const writer = $.pointerValue<maybeAsyncWriter>(this.w)
113
125
  const [, err] = await writer.Write(compressed)
114
126
  return err
@@ -131,17 +143,23 @@ export function NewWriter(w: io.Writer | null): Writer {
131
143
 
132
144
  export function NewWriterLevel(
133
145
  w: io.Writer | null,
134
- _level: number,
146
+ level: number,
135
147
  ): [Writer | null, $.GoError] {
136
- return [new Writer(w), null]
148
+ if (level < HuffmanOnly || level > BestCompression) {
149
+ return [null, errors.New(`zlib: invalid compression level: ${level}`)]
150
+ }
151
+ return [new Writer(w, null, level), null]
137
152
  }
138
153
 
139
154
  export function NewWriterLevelDict(
140
155
  w: io.Writer | null,
141
- _level: number,
156
+ level: number,
142
157
  _dict: $.Bytes | null,
143
158
  ): [Writer | null, $.GoError] {
144
- return [new Writer(w), null]
159
+ if (level < HuffmanOnly || level > BestCompression) {
160
+ return [null, errors.New(`zlib: invalid compression level: ${level}`)]
161
+ }
162
+ return [new Writer(w, _dict, level), null]
145
163
  }
146
164
 
147
165
  export function NewReader(
@@ -162,14 +180,25 @@ export function NewReaderDict(
162
180
  return [reader as any, null]
163
181
  }
164
182
 
165
- function deflate(data: Uint8Array): Uint8Array {
183
+ function deflate(data: Uint8Array, dict: $.Bytes | null, level: number): Uint8Array {
166
184
  const zlib = nodeZlib()
167
- return new Uint8Array(zlib.deflateSync(data))
185
+ const opts: Record<string, unknown> = {}
186
+ if (dict != null && $.len(dict) > 0) {
187
+ opts.dictionary = $.bytesToUint8Array(dict)
188
+ }
189
+ if (level === HuffmanOnly) {
190
+ opts.strategy = zlib.constants?.Z_HUFFMAN_ONLY
191
+ opts.level = DefaultCompression
192
+ } else {
193
+ opts.level = level
194
+ }
195
+ return new Uint8Array(zlib.deflateSync(data, opts))
168
196
  }
169
197
 
170
- function inflate(data: Uint8Array): Uint8Array {
198
+ function inflate(data: Uint8Array, dict: $.Bytes | null): Uint8Array {
171
199
  const zlib = nodeZlib()
172
- return new Uint8Array(zlib.inflateSync(data))
200
+ const opts = dict != null && $.len(dict) > 0 ? { dictionary: $.bytesToUint8Array(dict) } : undefined
201
+ return new Uint8Array(zlib.inflateSync(data, opts))
173
202
  }
174
203
 
175
204
  function nodeZlib(): any {
@@ -266,13 +295,21 @@ function recordCompressedBytes(
266
295
  }
267
296
  const compressed = new Uint8Array(chunks)
268
297
  try {
269
- return { data: inflate(compressed), err: null }
270
- } catch {
271
- if (dict != null && $.len(dict) > 0) {
272
- return { data: null, err: ErrDictionary }
273
- }
274
- return { data: null, err: ErrHeader }
298
+ return { data: inflate(compressed, dict), err: null }
299
+ } catch (err) {
300
+ return { data: null, err: classifyInflateError(err) }
301
+ }
302
+ }
303
+
304
+ function classifyInflateError(err: unknown): $.GoError {
305
+ const zerr = err instanceof Error ? err as nodeZlibError : null
306
+ if (zerr?.code === 'Z_NEED_DICT') {
307
+ return ErrDictionary
308
+ }
309
+ if (zerr?.code === 'Z_DATA_ERROR' && zerr.message.includes('incorrect data check')) {
310
+ return ErrChecksum
275
311
  }
312
+ return ErrHeader
276
313
  }
277
314
 
278
315
  function concat(chunks: Uint8Array[]): Uint8Array {
@@ -0,0 +1,51 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "strict": true,
4
+ "symbols": {
5
+ "BestCompression": {
6
+ "status": "real"
7
+ },
8
+ "BestSpeed": {
9
+ "status": "real"
10
+ },
11
+ "DefaultCompression": {
12
+ "status": "real"
13
+ },
14
+ "ErrChecksum": {
15
+ "status": "real"
16
+ },
17
+ "ErrDictionary": {
18
+ "status": "real"
19
+ },
20
+ "ErrHeader": {
21
+ "status": "real"
22
+ },
23
+ "HuffmanOnly": {
24
+ "status": "real"
25
+ },
26
+ "NewReader": {
27
+ "status": "real"
28
+ },
29
+ "NewReaderDict": {
30
+ "status": "real"
31
+ },
32
+ "NewWriter": {
33
+ "status": "real"
34
+ },
35
+ "NewWriterLevel": {
36
+ "status": "real"
37
+ },
38
+ "NewWriterLevelDict": {
39
+ "status": "real"
40
+ },
41
+ "NoCompression": {
42
+ "status": "real"
43
+ },
44
+ "Resetter": {
45
+ "status": "real"
46
+ },
47
+ "Writer": {
48
+ "status": "real"
49
+ }
50
+ }
51
+ }
@@ -1,8 +1,34 @@
1
1
  import { describe, expect, it } from 'vitest'
2
2
 
3
3
  import * as $ from '@goscript/builtin/index.js'
4
+ import * as bytes from '@goscript/bytes/index.js'
4
5
 
5
- import { Marshal, MarshalIndent, Unmarshal, type Unmarshaler } from './index.js'
6
+ import {
7
+ Compact,
8
+ HTMLEscape,
9
+ Indent,
10
+ InvalidUTF8Error,
11
+ InvalidUnmarshalError,
12
+ MarshalerError,
13
+ Marshal,
14
+ MarshalIndent,
15
+ NewDecoder,
16
+ NewEncoder,
17
+ Number_Float64,
18
+ Number_Int64,
19
+ Number_String,
20
+ RawMessage_MarshalJSON,
21
+ RawMessage_UnmarshalJSON,
22
+ SyntaxError as JSONSyntaxError,
23
+ UnmarshalFieldError,
24
+ Unmarshal,
25
+ UnmarshalTypeError,
26
+ UnsupportedTypeError,
27
+ UnsupportedValueError,
28
+ Valid,
29
+ type Marshaler,
30
+ type Unmarshaler,
31
+ } from './index.js'
6
32
 
7
33
  class Person {
8
34
  public _fields = {
@@ -16,37 +42,92 @@ class Person {
16
42
  new Person(),
17
43
  [],
18
44
  Person,
19
- {
20
- Name: {
45
+ [
46
+ {
47
+ name: 'Name',
48
+ key: 'Name',
21
49
  type: { kind: $.TypeKind.Basic, name: 'string' },
22
50
  tag: 'json:"name"',
23
51
  },
24
- Age: { type: { kind: $.TypeKind.Basic, name: 'int' }, tag: 'json:"age"' },
25
- Active: {
52
+ {
53
+ name: 'Age',
54
+ key: 'Age',
55
+ type: { kind: $.TypeKind.Basic, name: 'int' },
56
+ tag: 'json:"age"',
57
+ },
58
+ {
59
+ name: 'Active',
60
+ key: 'Active',
26
61
  type: { kind: $.TypeKind.Basic, name: 'bool' },
27
62
  tag: 'json:"active"',
28
63
  },
29
- },
64
+ ],
65
+ )
66
+ }
67
+
68
+ class FieldAlias {
69
+ public _fields = {
70
+ Name: $.varRef(''),
71
+ }
72
+
73
+ static __typeInfo = $.registerStructType(
74
+ 'test.FieldAlias',
75
+ new FieldAlias(),
76
+ [],
77
+ FieldAlias,
78
+ [
79
+ {
80
+ name: 'FullName',
81
+ key: 'Name',
82
+ type: { kind: $.TypeKind.Basic, name: 'string' },
83
+ },
84
+ ],
30
85
  )
31
86
  }
32
87
 
33
88
  describe('encoding/json override', () => {
34
89
  it('registers the Unmarshaler interface shape', () => {
90
+ class CustomMarshaler implements Marshaler {
91
+ MarshalJSON(): [$.Slice<number>, $.GoError] {
92
+ return [$.stringToBytes('{"ok":true}'), null]
93
+ }
94
+ }
95
+
35
96
  class CustomUnmarshaler implements Unmarshaler {
36
97
  UnmarshalJSON(_data: $.Slice<number>): $.GoError {
37
98
  return null
38
99
  }
39
100
  }
40
101
 
102
+ const [, marshalOK] = $.typeAssertTuple<Marshaler>(
103
+ new CustomMarshaler(),
104
+ 'json.Marshaler',
105
+ )
41
106
  const [value, ok] = $.typeAssertTuple<Unmarshaler>(
42
107
  new CustomUnmarshaler(),
43
108
  'json.Unmarshaler',
44
109
  )
45
110
 
111
+ expect(marshalOK).toBe(true)
46
112
  expect(ok).toBe(true)
47
113
  expect(value.UnmarshalJSON($.stringToBytes('{}'))).toBeNull()
48
114
  })
49
115
 
116
+ it('exposes JSON error structs as Go errors', () => {
117
+ for (const err of [
118
+ new JSONSyntaxError(),
119
+ new InvalidUTF8Error({ S: 'bad' }),
120
+ new InvalidUnmarshalError(),
121
+ new MarshalerError({ Err: $.newError('bad marshal') }),
122
+ new UnmarshalFieldError({ Key: 'field' }),
123
+ new UnmarshalTypeError({ Value: 'string' }),
124
+ new UnsupportedTypeError(),
125
+ new UnsupportedValueError({ Str: 'NaN' }),
126
+ ]) {
127
+ expect(err.Error()).toBe(err.message)
128
+ }
129
+ })
130
+
50
131
  it('marshals struct fields through json tags', () => {
51
132
  const person = new Person()
52
133
  person._fields.Name.value = 'Alice'
@@ -61,6 +142,187 @@ describe('encoding/json override', () => {
61
142
  )
62
143
  })
63
144
 
145
+ it('uses descriptor names separately from storage keys', () => {
146
+ const alias = new FieldAlias()
147
+ alias._fields.Name.value = 'Ada'
148
+
149
+ const [data, err] = Marshal(alias)
150
+ expect(err).toBeNull()
151
+ expect($.bytesToString(data)).toBe('{"FullName":"Ada"}')
152
+
153
+ const target = $.varRef(new FieldAlias())
154
+ expect(
155
+ Unmarshal($.stringToBytes('{"FullName":"Grace"}'), target),
156
+ ).toBeNull()
157
+ expect(target.value._fields.Name.value).toBe('Grace')
158
+ })
159
+
160
+ it('rejects unsupported values and invalid unmarshal targets', () => {
161
+ const [nanData, nanErr] = Marshal(Number.NaN)
162
+ expect(nanData).toBeNull()
163
+ expect(nanErr).toBeInstanceOf(UnsupportedValueError)
164
+
165
+ const [htmlData, htmlErr] = Marshal(new Map([['text', '<tag>&']]))
166
+ expect(htmlErr).toBeNull()
167
+ expect($.bytesToString(htmlData)).toBe(
168
+ '{"text":"\\u003ctag\\u003e\\u0026"}',
169
+ )
170
+
171
+ expect(Unmarshal($.stringToBytes('{}'), null)).toBeInstanceOf(
172
+ InvalidUnmarshalError,
173
+ )
174
+ expect(Unmarshal($.stringToBytes('{}'), 1)).toBeInstanceOf(
175
+ InvalidUnmarshalError,
176
+ )
177
+ })
178
+
179
+ it('uses custom marshal and unmarshal hooks', () => {
180
+ class CustomJSON implements Marshaler, Unmarshaler {
181
+ public Text = ''
182
+
183
+ MarshalJSON(): [$.Slice<number>, $.GoError] {
184
+ return [$.stringToBytes('{"hook":true}'), null]
185
+ }
186
+
187
+ UnmarshalJSON(data: $.Slice<number>): $.GoError {
188
+ this.Text = $.bytesToString(data)
189
+ return null
190
+ }
191
+ }
192
+
193
+ class RawEnvelope {
194
+ public _fields = {
195
+ Raw: $.varRef($.stringToBytes('{"embedded":true}')),
196
+ }
197
+
198
+ static __typeInfo = $.registerStructType(
199
+ 'test.RawEnvelope',
200
+ new RawEnvelope(),
201
+ [],
202
+ RawEnvelope,
203
+ [
204
+ {
205
+ name: 'Raw',
206
+ key: 'Raw',
207
+ type: 'json.RawMessage',
208
+ tag: 'json:"raw"',
209
+ },
210
+ ],
211
+ )
212
+ }
213
+
214
+ class Envelope {
215
+ public _fields = {
216
+ Person: $.varRef(new Person()),
217
+ Data: $.varRef(new Uint8Array(0)),
218
+ Raw: $.varRef(new Uint8Array(0)),
219
+ Hook: $.varRef(new CustomJSON()),
220
+ }
221
+
222
+ static __typeInfo = $.registerStructType(
223
+ 'test.Envelope',
224
+ new Envelope(),
225
+ [],
226
+ Envelope,
227
+ [
228
+ {
229
+ name: 'Person',
230
+ key: 'Person',
231
+ type: { kind: $.TypeKind.Struct, name: 'test.Person' },
232
+ tag: 'json:"person"',
233
+ },
234
+ {
235
+ name: 'Data',
236
+ key: 'Data',
237
+ type: {
238
+ kind: $.TypeKind.Slice,
239
+ elemType: { kind: $.TypeKind.Basic, name: 'uint8' },
240
+ },
241
+ tag: 'json:"data"',
242
+ },
243
+ {
244
+ name: 'Raw',
245
+ key: 'Raw',
246
+ type: {
247
+ kind: $.TypeKind.Slice,
248
+ elemType: { kind: $.TypeKind.Basic, name: 'uint8' },
249
+ typeName: 'json.RawMessage',
250
+ },
251
+ tag: 'json:"raw"',
252
+ },
253
+ {
254
+ name: 'Hook',
255
+ key: 'Hook',
256
+ type: { kind: $.TypeKind.Struct, name: 'test.CustomJSON' },
257
+ tag: 'json:"hook"',
258
+ },
259
+ ],
260
+ )
261
+ }
262
+
263
+ const [data, err] = Marshal(new CustomJSON())
264
+ expect(err).toBeNull()
265
+ expect($.bytesToString(data)).toBe('{"hook":true}')
266
+
267
+ const [indented, indentErr] = MarshalIndent(new CustomJSON(), '', ' ')
268
+ expect(indentErr).toBeNull()
269
+ expect($.bytesToString(indented)).toBe('{\n "hook": true\n}')
270
+
271
+ const target = $.varRef(new CustomJSON())
272
+ expect(Unmarshal($.stringToBytes('{"input":1}'), target)).toBeNull()
273
+ expect(target.value.Text).toBe('{"input":1}')
274
+
275
+ const raw = $.namedValueInterfaceValue<unknown>(
276
+ $.stringToBytes('{"raw":true}'),
277
+ 'json.RawMessage',
278
+ { MarshalJSON: RawMessage_MarshalJSON },
279
+ )
280
+ const [rawData, rawErr] = Marshal(raw)
281
+ expect(rawErr).toBeNull()
282
+ expect($.bytesToString(rawData)).toBe('{"raw":true}')
283
+
284
+ const [byteData, byteErr] = Marshal($.stringToBytes('x'))
285
+ expect(byteErr).toBeNull()
286
+ expect($.bytesToString(byteData)).toBe('"eA=="')
287
+
288
+ const [rawEnvelopeData, rawEnvelopeErr] = Marshal(new RawEnvelope())
289
+ expect(rawEnvelopeErr).toBeNull()
290
+ expect($.bytesToString(rawEnvelopeData)).toBe('{"raw":{"embedded":true}}')
291
+
292
+ const rawEnvelopeTarget = $.varRef(new RawEnvelope())
293
+ expect(
294
+ Unmarshal($.stringToBytes('{"raw":{"next":1}}'), rawEnvelopeTarget),
295
+ ).toBeNull()
296
+ expect($.bytesToString(rawEnvelopeTarget.value._fields.Raw.value)).toBe(
297
+ '{"next":1}',
298
+ )
299
+
300
+ const byteTarget = $.varRef(new Uint8Array(0))
301
+ expect(Unmarshal($.stringToBytes('"eA=="'), byteTarget)).toBeNull()
302
+ expect($.bytesToString(byteTarget.value)).toBe('x')
303
+
304
+ const envelope = $.varRef(new Envelope())
305
+ expect(
306
+ Unmarshal(
307
+ $.stringToBytes(
308
+ '{"person":{"name":"Eve","age":31},"data":"eA==","raw":{"keep":true},"hook":{"nested":true}}',
309
+ ),
310
+ envelope,
311
+ ),
312
+ ).toBeNull()
313
+ expect(envelope.value._fields.Person.value._fields.Name.value).toBe('Eve')
314
+ expect(envelope.value._fields.Person.value._fields.Age.value).toBe(31)
315
+ expect($.bytesToString(envelope.value._fields.Data.value)).toBe('x')
316
+ expect($.bytesToString(envelope.value._fields.Raw.value)).toBe(
317
+ '{"keep":true}',
318
+ )
319
+ expect(envelope.value._fields.Hook.value.Text).toBe('{"nested":true}')
320
+
321
+ const [envelopeData, envelopeErr] = Marshal(envelope.value)
322
+ expect(envelopeErr).toBeNull()
323
+ expect($.bytesToString(envelopeData)).toContain('"raw":{"keep":true}')
324
+ })
325
+
64
326
  it('marshals indented JSON with a line prefix', () => {
65
327
  const person = new Person()
66
328
  person._fields.Name.value = 'Alice'
@@ -98,4 +360,96 @@ describe('encoding/json override', () => {
98
360
  expect(mapRef.value?.get('age')).toBe(22)
99
361
  expect(mapRef.value?.get('active')).toBe(true)
100
362
  })
363
+
364
+ it('encodes JSON to an io.Writer with newline, indentation, and HTML escaping', () => {
365
+ const chunks: string[] = []
366
+ const writer = {
367
+ Write(p: $.Bytes): [number, $.GoError] {
368
+ chunks.push($.bytesToString(p))
369
+ return [$.len(p), null]
370
+ },
371
+ }
372
+
373
+ const encoder = NewEncoder(writer)
374
+ expect(encoder.Encode(new Map([['text', '<tag>&']]))).toBeNull()
375
+ encoder.SetEscapeHTML(false)
376
+ encoder.SetIndent('', ' ')
377
+ expect(encoder.Encode(new Map([['text', '<tag>&']]))).toBeNull()
378
+
379
+ expect(chunks).toEqual([
380
+ '{"text":"\\u003ctag\\u003e\\u0026"}\n',
381
+ '{\n "text": "<tag>&"\n}\n',
382
+ ])
383
+ })
384
+
385
+ it('validates, compacts, indents, and escapes JSON bytes', () => {
386
+ expect(Valid($.stringToBytes('{"ok":true}'))).toBe(true)
387
+ expect(Valid($.stringToBytes('{'))).toBe(false)
388
+
389
+ const compact = new bytes.Buffer()
390
+ expect(Compact(compact, $.stringToBytes('{ "ok" : true }'))).toBeNull()
391
+ expect(compact.String()).toBe('{"ok":true}')
392
+
393
+ const indented = new bytes.Buffer()
394
+ expect(
395
+ Indent(indented, $.stringToBytes('{"ok":true}'), '> ', ' '),
396
+ ).toBeNull()
397
+ expect(indented.String()).toBe('{\n> "ok": true\n> }')
398
+
399
+ const escaped = new bytes.Buffer()
400
+ HTMLEscape(escaped, $.stringToBytes('"<tag>&"'))
401
+ expect(escaped.String()).toBe('"\\u003ctag\\u003e\\u0026"')
402
+ })
403
+
404
+ it('decodes from readers and exposes raw message and number helpers', () => {
405
+ const reader = bytes.NewBufferString('{"name":"Dana","age":28}')!
406
+ const decoder = NewDecoder(reader)
407
+ const target = $.varRef(new Person())
408
+
409
+ expect(decoder.Decode(target)).toBeNull()
410
+ expect(target.value._fields.Name.value).toBe('Dana')
411
+ expect(target.value._fields.Age.value).toBe(28)
412
+ expect(decoder.InputOffset()).toBeGreaterThan(0)
413
+
414
+ const raw = $.stringToBytes('{"raw":true}')
415
+ const [marshaled, marshalErr] = RawMessage_MarshalJSON(raw)
416
+ expect(marshalErr).toBeNull()
417
+ expect($.bytesToString(marshaled)).toBe('{"raw":true}')
418
+
419
+ const rawRef = $.varRef<$.Bytes>(null)
420
+ expect(
421
+ RawMessage_UnmarshalJSON(rawRef, $.stringToBytes('[1,2]')),
422
+ ).toBeNull()
423
+ expect($.bytesToString(rawRef.value)).toBe('[1,2]')
424
+
425
+ expect(Number_String('42')).toBe('42')
426
+ expect(Number_Int64('42')).toEqual([42, null])
427
+ expect(Number_Float64('3.5')).toEqual([3.5, null])
428
+ expect(Number.isNaN(Number_Float64('NaN')[0])).toBe(true)
429
+ expect(Number_Float64('-Inf')).toEqual([-Infinity, null])
430
+ expect(Number_Float64('1x')[1]?.Error()).toContain('invalid syntax')
431
+ expect(Number_Float64('1e999')[1]?.Error()).toContain('value out of range')
432
+ expect(Number_Int64('9223372036854775808')[1]?.Error()).toContain(
433
+ 'value out of range',
434
+ )
435
+ })
436
+
437
+ it('applies decoder UseNumber and DisallowUnknownFields options', () => {
438
+ const numberReader = bytes.NewBufferString('{"n":12}')!
439
+ const numberDecoder = NewDecoder(numberReader)
440
+ numberDecoder.UseNumber()
441
+ const numberTarget = $.varRef<Map<string, unknown> | null>(null)
442
+
443
+ expect(numberDecoder.Decode(numberTarget)).toBeNull()
444
+ expect(numberTarget.value?.get('n')).toBe('12')
445
+
446
+ const strictReader = bytes.NewBufferString('{"name":"Ada","extra":true}')!
447
+ const strictDecoder = NewDecoder(strictReader)
448
+ strictDecoder.DisallowUnknownFields()
449
+ const strictTarget = $.varRef(new Person())
450
+
451
+ expect(strictDecoder.Decode(strictTarget)?.Error()).toBe(
452
+ 'json: unknown field "extra"',
453
+ )
454
+ })
101
455
  })