goscript 0.1.3 → 0.1.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.
Files changed (117) hide show
  1. package/cmd/goscript/cmd_compile.go +28 -8
  2. package/cmd/goscript/cmd_compile_test.go +105 -6
  3. package/compiler/build-flags.go +9 -10
  4. package/compiler/gotest/runner_test.go +127 -0
  5. package/compiler/lowering.go +596 -136
  6. package/compiler/lowering_bench_test.go +350 -0
  7. package/compiler/package-graph.go +61 -4
  8. package/compiler/package-graph_test.go +30 -0
  9. package/compiler/semantic-model-types.go +8 -0
  10. package/compiler/semantic-model.go +447 -22
  11. package/compiler/semantic-model_test.go +138 -0
  12. package/compiler/skeleton_test.go +948 -14
  13. package/compiler/typescript-emitter.go +19 -2
  14. package/dist/gs/builtin/builtin.d.ts +2 -2
  15. package/dist/gs/builtin/builtin.js +20 -0
  16. package/dist/gs/builtin/builtin.js.map +1 -1
  17. package/dist/gs/builtin/slice.js +5 -0
  18. package/dist/gs/builtin/slice.js.map +1 -1
  19. package/dist/gs/builtin/type.d.ts +1 -1
  20. package/dist/gs/builtin/type.js +72 -5
  21. package/dist/gs/builtin/type.js.map +1 -1
  22. package/dist/gs/compress/zlib/index.d.ts +3 -3
  23. package/dist/gs/compress/zlib/index.js +88 -26
  24. package/dist/gs/compress/zlib/index.js.map +1 -1
  25. package/dist/gs/crypto/sha1/index.js +2 -5
  26. package/dist/gs/crypto/sha1/index.js.map +1 -1
  27. package/dist/gs/crypto/sha256/index.js +2 -5
  28. package/dist/gs/crypto/sha256/index.js.map +1 -1
  29. package/dist/gs/crypto/sha512/index.js +2 -5
  30. package/dist/gs/crypto/sha512/index.js.map +1 -1
  31. package/dist/gs/embed/index.d.ts +6 -0
  32. package/dist/gs/embed/index.js +210 -5
  33. package/dist/gs/embed/index.js.map +1 -1
  34. package/dist/gs/fmt/fmt.d.ts +3 -3
  35. package/dist/gs/fmt/fmt.js +29 -16
  36. package/dist/gs/fmt/fmt.js.map +1 -1
  37. package/dist/gs/github.com/aperturerobotics/starpc/srpc/index.js +118 -6
  38. package/dist/gs/github.com/aperturerobotics/starpc/srpc/index.js.map +1 -1
  39. package/dist/gs/github.com/go-git/go-billy/v6/osfs/index.d.ts +45 -0
  40. package/dist/gs/github.com/go-git/go-billy/v6/osfs/index.js +229 -0
  41. package/dist/gs/github.com/go-git/go-billy/v6/osfs/index.js.map +1 -0
  42. package/dist/gs/io/fs/readdir.js +5 -3
  43. package/dist/gs/io/fs/readdir.js.map +1 -1
  44. package/dist/gs/io/io.d.ts +10 -6
  45. package/dist/gs/io/io.js +87 -42
  46. package/dist/gs/io/io.js.map +1 -1
  47. package/dist/gs/math/bits/index.d.ts +26 -5
  48. package/dist/gs/math/bits/index.js +13 -24
  49. package/dist/gs/math/bits/index.js.map +1 -1
  50. package/dist/gs/net/http/index.d.ts +3 -1
  51. package/dist/gs/net/http/index.js +18 -1
  52. package/dist/gs/net/http/index.js.map +1 -1
  53. package/dist/gs/os/types_js.gs.d.ts +6 -2
  54. package/dist/gs/os/types_js.gs.js +169 -8
  55. package/dist/gs/os/types_js.gs.js.map +1 -1
  56. package/dist/gs/reflect/type.d.ts +1 -0
  57. package/dist/gs/reflect/type.js +80 -51
  58. package/dist/gs/reflect/type.js.map +1 -1
  59. package/dist/gs/strings/reader.d.ts +1 -1
  60. package/dist/gs/strings/reader.js +2 -2
  61. package/dist/gs/strings/reader.js.map +1 -1
  62. package/dist/gs/sync/sync.d.ts +2 -1
  63. package/dist/gs/sync/sync.js +37 -16
  64. package/dist/gs/sync/sync.js.map +1 -1
  65. package/dist/gs/syscall/js/index.js +9 -0
  66. package/dist/gs/syscall/js/index.js.map +1 -1
  67. package/dist/gs/testing/testing.js +8 -6
  68. package/dist/gs/testing/testing.js.map +1 -1
  69. package/gs/builtin/builtin.ts +25 -2
  70. package/gs/builtin/runtime-contract.test.ts +45 -0
  71. package/gs/builtin/slice.ts +7 -0
  72. package/gs/builtin/type.ts +85 -5
  73. package/gs/compress/zlib/index.test.ts +97 -0
  74. package/gs/compress/zlib/index.ts +117 -27
  75. package/gs/compress/zlib/meta.json +4 -1
  76. package/gs/crypto/sha1/index.test.ts +19 -2
  77. package/gs/crypto/sha1/index.ts +3 -6
  78. package/gs/crypto/sha256/index.test.ts +14 -2
  79. package/gs/crypto/sha256/index.ts +3 -6
  80. package/gs/crypto/sha512/index.test.ts +17 -2
  81. package/gs/crypto/sha512/index.ts +3 -6
  82. package/gs/embed/index.test.ts +87 -0
  83. package/gs/embed/index.ts +229 -5
  84. package/gs/fmt/fmt.test.ts +41 -3
  85. package/gs/fmt/fmt.ts +40 -17
  86. package/gs/fmt/meta.json +6 -1
  87. package/gs/github.com/aperturerobotics/starpc/srpc/index.test.ts +8 -1
  88. package/gs/github.com/aperturerobotics/starpc/srpc/index.ts +139 -11
  89. package/gs/github.com/go-git/go-billy/v6/osfs/index.test.ts +110 -0
  90. package/gs/github.com/go-git/go-billy/v6/osfs/index.ts +280 -0
  91. package/gs/github.com/go-git/go-billy/v6/osfs/meta.json +8 -0
  92. package/gs/io/fs/readdir.test.ts +38 -0
  93. package/gs/io/fs/readdir.ts +7 -3
  94. package/gs/io/io.test.ts +77 -6
  95. package/gs/io/io.ts +114 -52
  96. package/gs/io/meta.json +7 -1
  97. package/gs/math/bits/index.ts +52 -28
  98. package/gs/net/http/index.test.ts +16 -0
  99. package/gs/net/http/index.ts +19 -2
  100. package/gs/os/file_unix_js.test.ts +52 -0
  101. package/gs/os/meta.json +4 -0
  102. package/gs/os/readdir.test.ts +56 -0
  103. package/gs/os/types_js.gs.ts +169 -8
  104. package/gs/reflect/deepequal.test.ts +10 -1
  105. package/gs/reflect/type.ts +91 -56
  106. package/gs/reflect/typefor.test.ts +31 -1
  107. package/gs/strings/meta.json +5 -2
  108. package/gs/strings/reader.test.ts +2 -2
  109. package/gs/strings/reader.ts +2 -2
  110. package/gs/sync/meta.json +1 -0
  111. package/gs/sync/sync.test.ts +41 -1
  112. package/gs/sync/sync.ts +41 -16
  113. package/gs/syscall/js/index.test.ts +18 -0
  114. package/gs/syscall/js/index.ts +12 -0
  115. package/gs/testing/testing.test.ts +32 -3
  116. package/gs/testing/testing.ts +13 -10
  117. package/package.json +1 -1
@@ -0,0 +1,110 @@
1
+ import * as $ from '@goscript/builtin/index.js'
2
+ import * as os from '@goscript/os/index.js'
3
+ import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
4
+ import { tmpdir } from 'node:os'
5
+ import { join } from 'node:path'
6
+ import { afterEach, describe, expect, it } from 'vitest'
7
+
8
+ import { New } from './index.js'
9
+
10
+ const roots: string[] = []
11
+
12
+ afterEach(() => {
13
+ for (const root of roots.splice(0)) {
14
+ rmSync(root, { force: true, recursive: true })
15
+ }
16
+ })
17
+
18
+ function tempRoot(): string {
19
+ const root = mkdtempSync(join(tmpdir(), 'goscript-billy-osfs-'))
20
+ roots.push(root)
21
+ return root
22
+ }
23
+
24
+ describe('go-billy osfs override', () => {
25
+ it('writes through to the host directory used by os.DirFS', () => {
26
+ const root = tempRoot()
27
+ const fsys = New(root)
28
+
29
+ const [file, openErr] = fsys.OpenFile(
30
+ 'README.md',
31
+ os.O_WRONLY | os.O_CREATE | os.O_TRUNC,
32
+ 0o666,
33
+ )
34
+ expect(openErr).toBeNull()
35
+ const [n, writeErr] = file!.Write($.stringToBytes('hello\n'))
36
+ expect(writeErr).toBeNull()
37
+ expect(n).toBe(6)
38
+ expect(file!.Close()).toBeNull()
39
+
40
+ expect(readFileSync(join(root, 'README.md'), 'utf8')).toBe('hello\n')
41
+ })
42
+
43
+ it('keeps chrooted writes under the child host directory', () => {
44
+ const root = tempRoot()
45
+ const fsys = New(root)
46
+ const [child, chrootErr] = fsys.Chroot('sub')
47
+ expect(chrootErr).toBeNull()
48
+
49
+ const [file, openErr] = child!.Create('nested/file.txt')
50
+ expect(openErr).toBeNull()
51
+ const [, writeErr] = file!.Write($.stringToBytes('child'))
52
+ expect(writeErr).toBeNull()
53
+ expect(file!.Close()).toBeNull()
54
+
55
+ expect(readFileSync(join(root, 'sub', 'nested', 'file.txt'), 'utf8')).toBe(
56
+ 'child',
57
+ )
58
+ })
59
+
60
+ it('rejects parent traversal outside the base directory', () => {
61
+ const root = tempRoot()
62
+ const fsys = New(root)
63
+
64
+ const [file, err] = fsys.Open('../escape.txt')
65
+
66
+ expect(file).toBeNull()
67
+ expect(err).not.toBeNull()
68
+ })
69
+
70
+ it('accepts absolute paths inside the base directory', () => {
71
+ const root = tempRoot()
72
+ const fsys = New(root)
73
+ const absolute = join(root, 'inside.txt')
74
+
75
+ const [file, openErr] = fsys.Create(absolute)
76
+ expect(openErr).toBeNull()
77
+ const [, writeErr] = file!.Write($.stringToBytes('inside'))
78
+ expect(writeErr).toBeNull()
79
+ expect(file!.Close()).toBeNull()
80
+
81
+ expect(readFileSync(absolute, 'utf8')).toBe('inside')
82
+ })
83
+
84
+ it('rejects absolute paths outside the base directory', () => {
85
+ const root = tempRoot()
86
+ const outside = tempRoot()
87
+ const fsys = New(root)
88
+
89
+ const [file, err] = fsys.Create(join(outside, 'outside.txt'))
90
+
91
+ expect(file).toBeNull()
92
+ expect(err).not.toBeNull()
93
+ })
94
+
95
+ it('reads host directory entries', () => {
96
+ const root = tempRoot()
97
+ writeFileSync(join(root, 'one.txt'), 'one')
98
+ writeFileSync(join(root, 'two.txt'), 'two')
99
+ const fsys = New(root)
100
+
101
+ const [entries, err] = fsys.ReadDir('.')
102
+
103
+ expect(err).toBeNull()
104
+ expect(
105
+ Array.from(entries ?? [])
106
+ .map((entry) => entry.Name())
107
+ .sort(),
108
+ ).toEqual(['one.txt', 'two.txt'])
109
+ })
110
+ })
@@ -0,0 +1,280 @@
1
+ import * as $ from '@goscript/builtin/index.js'
2
+ import * as fs from '@goscript/io/fs/index.js'
3
+ import * as os from '@goscript/os/index.js'
4
+ import * as filepath from '@goscript/path/filepath/index.js'
5
+
6
+ export type Option = (o: options) => void
7
+ export type Type = number
8
+
9
+ export const BoundOSFS: Type = 0
10
+
11
+ const allCapabilities = 127
12
+ const defaultCreateMode = 0o666
13
+ const defaultDirectoryMode = 0o777
14
+ const ErrCrossedBoundary = $.newError('chroot boundary crossed')
15
+ const ErrBaseDirCannotBeRemoved = $.newError('base dir cannot be removed')
16
+ const ErrBaseDirCannotBeRenamed = $.newError('base dir cannot be renamed')
17
+
18
+ type JoinElement = string | $.Slice<string>
19
+
20
+ class options {
21
+ public Type: Type = BoundOSFS
22
+ public mmap = false
23
+ }
24
+
25
+ export function New(
26
+ baseDir: string,
27
+ ...opts: Array<Option | $.Slice<Option> | undefined>
28
+ ): BoundOS {
29
+ const o = new options()
30
+ for (const opt of normalizeOptions(opts)) {
31
+ opt(o)
32
+ }
33
+ return new BoundOS({ baseDir, mmap: o.mmap })
34
+ }
35
+
36
+ function normalizeOptions(
37
+ opts: Array<Option | $.Slice<Option> | undefined>,
38
+ ): Option[] {
39
+ if (opts.length === 0) {
40
+ return []
41
+ }
42
+ if (opts.length === 1 && typeof opts[0] !== 'function') {
43
+ const slice = opts[0] ?? null
44
+ const out: Option[] = []
45
+ for (let i = 0; i < $.len(slice); i++) {
46
+ out.push(slice![i])
47
+ }
48
+ return out
49
+ }
50
+ return opts.filter((opt): opt is Option => typeof opt === 'function')
51
+ }
52
+
53
+ export function WithBoundOS(): Option {
54
+ return (o: options): void => {
55
+ o.Type = BoundOSFS
56
+ }
57
+ }
58
+
59
+ export function WithMmap(): Option {
60
+ return (o: options): void => {
61
+ o.mmap = true
62
+ }
63
+ }
64
+
65
+ export class BoundOS {
66
+ public baseDir: string
67
+ public mmap: boolean
68
+
69
+ constructor(init?: Partial<{ baseDir?: string; mmap?: boolean }>) {
70
+ const baseDir = init?.baseDir ?? '/'
71
+ this.baseDir = baseDir === '' ? '/' : filepath.Clean(baseDir)
72
+ this.mmap = init?.mmap ?? false
73
+ }
74
+
75
+ public clone(): BoundOS {
76
+ return new BoundOS({ baseDir: this.baseDir, mmap: this.mmap })
77
+ }
78
+
79
+ public Capabilities(): number {
80
+ return allCapabilities
81
+ }
82
+
83
+ public Create(name: string): [os.File | null, $.GoError] {
84
+ return this.OpenFile(
85
+ name,
86
+ os.O_RDWR | os.O_CREATE | os.O_TRUNC,
87
+ defaultCreateMode,
88
+ )
89
+ }
90
+
91
+ public Open(name: string): [os.File | null, $.GoError] {
92
+ return this.OpenFile(name, os.O_RDONLY, 0)
93
+ }
94
+
95
+ public OpenFile(
96
+ name: string,
97
+ flag: number,
98
+ perm: fs.FileMode,
99
+ ): [os.File | null, $.GoError] {
100
+ const [full, err] = this.abs(name)
101
+ if (err !== null) {
102
+ return [null, err]
103
+ }
104
+ if ((flag & os.O_CREATE) !== 0) {
105
+ const dir = filepath.Dir(full)
106
+ if (dir !== '.' && dir !== '') {
107
+ const mkdirErr = os.MkdirAll(dir, defaultDirectoryMode)
108
+ if (mkdirErr !== null) {
109
+ return [null, mkdirErr]
110
+ }
111
+ }
112
+ }
113
+ return os.OpenFile(full, flag, perm)
114
+ }
115
+
116
+ public Stat(name: string): [fs.FileInfo, $.GoError] {
117
+ const [full, err] = this.abs(name)
118
+ if (err !== null) {
119
+ return [null, err]
120
+ }
121
+ return os.Stat(full)
122
+ }
123
+
124
+ public Lstat(name: string): [fs.FileInfo, $.GoError] {
125
+ const [full, err] = this.abs(name)
126
+ if (err !== null) {
127
+ return [null, err]
128
+ }
129
+ return os.Lstat(full)
130
+ }
131
+
132
+ public ReadDir(name: string): [$.Slice<fs.DirEntry>, $.GoError] {
133
+ const [full, err] = this.abs(name)
134
+ if (err !== null) {
135
+ return [null, err]
136
+ }
137
+ const [file, openErr] = os.Open(full)
138
+ if (openErr !== null) {
139
+ return [null, openErr]
140
+ }
141
+ const [entries, readErr] = file!.ReadDir(-1)
142
+ const closeErr = file!.Close()
143
+ return [entries, readErr ?? closeErr]
144
+ }
145
+
146
+ public Rename(from: string, to: string): $.GoError {
147
+ if (this.isRoot(from)) {
148
+ return ErrBaseDirCannotBeRenamed
149
+ }
150
+ const [fromFull, fromErr] = this.abs(from)
151
+ if (fromErr !== null) {
152
+ return fromErr
153
+ }
154
+ const [toFull, toErr] = this.abs(to)
155
+ if (toErr !== null) {
156
+ return toErr
157
+ }
158
+ const mkdirErr = os.MkdirAll(filepath.Dir(toFull), defaultDirectoryMode)
159
+ if (mkdirErr !== null) {
160
+ return mkdirErr
161
+ }
162
+ return os.Rename(fromFull, toFull)
163
+ }
164
+
165
+ public Remove(name: string): $.GoError {
166
+ if (this.isRoot(name)) {
167
+ return ErrBaseDirCannotBeRemoved
168
+ }
169
+ const [full, err] = this.abs(name)
170
+ if (err !== null) {
171
+ return err
172
+ }
173
+ return os.Remove(full)
174
+ }
175
+
176
+ public RemoveAll(name: string): $.GoError {
177
+ if (this.isRoot(name)) {
178
+ return ErrBaseDirCannotBeRemoved
179
+ }
180
+ const [full, err] = this.abs(name)
181
+ if (err !== null) {
182
+ return err
183
+ }
184
+ return os.RemoveAll(full)
185
+ }
186
+
187
+ public MkdirAll(name: string, perm: fs.FileMode): $.GoError {
188
+ const [full, err] = this.abs(name)
189
+ if (err !== null) {
190
+ return err
191
+ }
192
+ return os.MkdirAll(full, perm)
193
+ }
194
+
195
+ public Symlink(target: string, link: string): $.GoError {
196
+ const [full, err] = this.abs(link)
197
+ if (err !== null) {
198
+ return err
199
+ }
200
+ const mkdirErr = os.MkdirAll(filepath.Dir(full), defaultDirectoryMode)
201
+ if (mkdirErr !== null) {
202
+ return mkdirErr
203
+ }
204
+ return os.Symlink(target, full)
205
+ }
206
+
207
+ public Readlink(link: string): [string, $.GoError] {
208
+ const [full, err] = this.abs(link)
209
+ if (err !== null) {
210
+ return ['', err]
211
+ }
212
+ return os.Readlink(full)
213
+ }
214
+
215
+ public TempFile(dir: string, prefix: string): [os.File | null, $.GoError] {
216
+ const targetDir = dir === '' ? '.tmp' : dir
217
+ const [fullDir, err] = this.abs(targetDir)
218
+ if (err !== null) {
219
+ return [null, err]
220
+ }
221
+ const mkdirErr = os.MkdirAll(fullDir, defaultDirectoryMode)
222
+ if (mkdirErr !== null) {
223
+ return [null, mkdirErr]
224
+ }
225
+ return os.CreateTemp(fullDir, prefix === '' ? 'tmp-*' : prefix + '*')
226
+ }
227
+
228
+ public Join(...elem: JoinElement[]): string {
229
+ if (elem.length === 1 && typeof elem[0] !== 'string') {
230
+ return filepath.Join(elem[0])
231
+ }
232
+ return filepath.Join(...(elem as string[]))
233
+ }
234
+
235
+ public Chroot(path: string): [BoundOS | null, $.GoError] {
236
+ const [full, err] = this.abs(path)
237
+ if (err !== null) {
238
+ return [null, err]
239
+ }
240
+ return [new BoundOS({ baseDir: full, mmap: this.mmap }), null]
241
+ }
242
+
243
+ public Root(): string {
244
+ return this.baseDir
245
+ }
246
+
247
+ private isRoot(name: string): boolean {
248
+ return name === '' || name === '.'
249
+ }
250
+
251
+ private abs(name: string): [string, $.GoError] {
252
+ if (this.isRoot(name)) {
253
+ return [this.baseDir, null]
254
+ }
255
+ const normalized = filepath.Clean(filepath.ToSlash(name))
256
+ if (filepath.IsAbs(normalized)) {
257
+ const full = filepath.Clean(filepath.FromSlash(normalized))
258
+ if (!this.insideBase(full)) {
259
+ return ['', ErrCrossedBoundary]
260
+ }
261
+ return [full, null]
262
+ }
263
+ if (normalized === '..' || normalized.startsWith('../')) {
264
+ return ['', ErrCrossedBoundary]
265
+ }
266
+ return [filepath.Join(this.baseDir, filepath.FromSlash(normalized)), null]
267
+ }
268
+
269
+ private insideBase(path: string): boolean {
270
+ const full = filepath.Clean(path)
271
+ if (full === this.baseDir) {
272
+ return true
273
+ }
274
+ return full.startsWith(
275
+ this.baseDir.endsWith('/') ? this.baseDir : this.baseDir + '/',
276
+ )
277
+ }
278
+ }
279
+
280
+ export const Default = New('/')
@@ -0,0 +1,8 @@
1
+ {
2
+ "dependencies": [
3
+ "io/fs",
4
+ "os",
5
+ "path/filepath"
6
+ ],
7
+ "asyncMethods": {}
8
+ }
@@ -0,0 +1,38 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import * as $ from '@goscript/builtin/index.js'
3
+
4
+ import { File, FileInfo, ReadDir } from './index.js'
5
+
6
+ describe('io/fs ReadDir override', () => {
7
+ it('returns a nil entry slice without sorting when directory read fails', () => {
8
+ const readErr = $.newError('read failed')
9
+ const file: File & {
10
+ ReadDir(n: number): [null, $.GoError]
11
+ } = {
12
+ Close(): $.GoError {
13
+ return null
14
+ },
15
+ Read(_p0: Uint8Array): [number, $.GoError] {
16
+ return [0, readErr]
17
+ },
18
+ Stat(): [FileInfo, $.GoError] {
19
+ return [null, readErr]
20
+ },
21
+ ReadDir(_n: number): [null, $.GoError] {
22
+ return [null, readErr]
23
+ },
24
+ }
25
+
26
+ const [list, err] = ReadDir(
27
+ {
28
+ Open(_name: string): [File, $.GoError] {
29
+ return [file, null]
30
+ },
31
+ },
32
+ 'dir',
33
+ )
34
+
35
+ expect(list).toBeNull()
36
+ expect(err).toBe(readErr)
37
+ })
38
+ })
@@ -92,9 +92,13 @@ export function ReadDir(
92
92
 
93
93
  let list: $.Slice<DirEntry>
94
94
  ;[list, err] = dir!.ReadDir(-1)
95
- list!.sort((a: DirEntry, b: DirEntry): number => {
96
- return a!.Name().localeCompare(b!.Name())
97
- })
95
+ if (list) {
96
+ list.sort((a: DirEntry, b: DirEntry): number => {
97
+ return $.pointerValue<Exclude<DirEntry, null>>(a).Name().localeCompare(
98
+ $.pointerValue<Exclude<DirEntry, null>>(b).Name(),
99
+ )
100
+ })
101
+ }
98
102
  return [list, err]
99
103
  }
100
104
 
package/gs/io/io.test.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as $ from '@goscript/builtin/index.js'
2
- import { LimitedReader, MultiWriter, TeeReader } from './index.js'
2
+ import { LimitedReader, MultiWriter, NopCloser, Pipe, TeeReader } from './index.js'
3
3
  import { describe, expect, test } from 'vitest'
4
4
 
5
5
  class sliceReader {
@@ -9,7 +9,7 @@ class sliceReader {
9
9
  const n = Math.min($.len(p), this.data.length)
10
10
  p!.set(this.data.subarray(0, n), 0)
11
11
  this.data = this.data.subarray(n)
12
- return [n, n === 0 ? new Error('EOF') as $.GoError : null]
12
+ return [n, n === 0 ? (new Error('EOF') as $.GoError) : null]
13
13
  }
14
14
  }
15
15
 
@@ -37,28 +37,99 @@ describe('io override', () => {
37
37
  expect(Buffer.from(buf.subarray(0, n)).toString('utf8')).toBe('abc')
38
38
  })
39
39
 
40
- test('TeeReader accepts nullable generated interface values', () => {
40
+ test('TeeReader accepts nullable generated interface values', async () => {
41
41
  const writer = new captureWriter()
42
42
  const reader = TeeReader(new sliceReader($.stringToBytes('abc')), writer)
43
43
  const buf = new Uint8Array(4)
44
44
 
45
- const [n, err] = reader.Read(buf)
45
+ const [n, err] = await reader.Read(buf)
46
46
 
47
47
  expect(err).toBeNull()
48
48
  expect(n).toBe(3)
49
49
  expect(Buffer.from(writer.chunks).toString('utf8')).toBe('abc')
50
50
  })
51
51
 
52
- test('MultiWriter accepts nullable generated interface values', () => {
52
+ test('NopCloser accepts nullable generated interface values', () => {
53
+ const reader: sliceReader | null = new sliceReader($.stringToBytes('abc'))
54
+ const body = NopCloser(reader)
55
+ const buf = new Uint8Array(4)
56
+
57
+ const [n, err] = body.Read(buf)
58
+
59
+ expect(err).toBeNull()
60
+ expect(n).toBe(3)
61
+ expect(body.Close()).toBeNull()
62
+ })
63
+
64
+ test('TeeReader awaits async readers and writers', async () => {
65
+ const chunks: number[] = []
66
+ const reader = TeeReader(
67
+ {
68
+ async Read(p: $.Bytes): Promise<[number, $.GoError]> {
69
+ await Promise.resolve()
70
+ p!.set($.stringToBytes('abc'), 0)
71
+ return [3, null]
72
+ },
73
+ } as any,
74
+ {
75
+ async Write(p: $.Bytes): Promise<[number, $.GoError]> {
76
+ await Promise.resolve()
77
+ chunks.push(...Array.from(p ?? []))
78
+ return [$.len(p), null]
79
+ },
80
+ } as any,
81
+ )
82
+ const buf = new Uint8Array(4)
83
+
84
+ const [n, err] = await reader.Read(buf)
85
+
86
+ expect(err).toBeNull()
87
+ expect(n).toBe(3)
88
+ expect(Buffer.from(chunks).toString('utf8')).toBe('abc')
89
+ })
90
+
91
+ test('MultiWriter accepts nullable generated interface values', async () => {
53
92
  const first = new captureWriter()
54
93
  const second = new captureWriter()
55
94
  const writer = MultiWriter(first, second)
56
95
 
57
- const [n, err] = writer.Write($.stringToBytes('abc'))
96
+ const [n, err] = await writer.Write($.stringToBytes('abc'))
58
97
 
59
98
  expect(err).toBeNull()
60
99
  expect(n).toBe(3)
61
100
  expect(Buffer.from(first.chunks).toString('utf8')).toBe('abc')
62
101
  expect(Buffer.from(second.chunks).toString('utf8')).toBe('abc')
63
102
  })
103
+
104
+ test('MultiWriter awaits async generated writers', async () => {
105
+ const chunks: number[] = []
106
+ const writer = MultiWriter({
107
+ async Write(p: $.Bytes): Promise<[number, $.GoError]> {
108
+ await Promise.resolve()
109
+ chunks.push(...Array.from(p ?? []))
110
+ return [$.len(p), null]
111
+ },
112
+ })
113
+
114
+ const [n, err] = await writer.Write($.stringToBytes('abc'))
115
+
116
+ expect(err).toBeNull()
117
+ expect(n).toBe(3)
118
+ expect(Buffer.from(chunks).toString('utf8')).toBe('abc')
119
+ })
120
+
121
+ test('PipeReader waits for a later write', async () => {
122
+ const [reader, writer] = Pipe()
123
+ const buf = new Uint8Array(5)
124
+
125
+ const read = reader.Read(buf)
126
+ const [written, writeErr] = await writer.Write($.stringToBytes('later'))
127
+ const [readBytes, readErr] = await read
128
+
129
+ expect(writeErr).toBeNull()
130
+ expect(written).toBe(5)
131
+ expect(readErr).toBeNull()
132
+ expect(readBytes).toBe(5)
133
+ expect(Buffer.from(buf).toString('utf8')).toBe('later')
134
+ })
64
135
  })