goscript 0.2.3 → 0.2.5

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 (160) hide show
  1. package/README.md +8 -8
  2. package/cmd/go_js_wasm_exec/main.go +1 -1
  3. package/cmd/go_js_wasm_exec/main_test.go +1 -1
  4. package/cmd/goscript/cmd-compile.go +9 -1
  5. package/cmd/goscript/cmd-test.go +1 -1
  6. package/cmd/goscript/cmd_compile_test.go +44 -0
  7. package/cmd/goscript/deps.go +1 -1
  8. package/cmd/goscript-wasm/main.go +2 -2
  9. package/compiler/compile-request.go +19 -0
  10. package/compiler/compile_bench_test.go +121 -0
  11. package/compiler/compliance_test.go +17 -1
  12. package/compiler/config.go +2 -0
  13. package/compiler/gotest/result.go +1 -1
  14. package/compiler/gotest/runner.go +2 -2
  15. package/compiler/gotest/runner_test.go +4 -7
  16. package/compiler/index.test.ts +28 -0
  17. package/compiler/index.ts +32 -16
  18. package/compiler/lowering.go +1236 -143
  19. package/compiler/lowering_bench_test.go +4 -0
  20. package/compiler/override-facts.go +1 -1
  21. package/compiler/override-registry_test.go +125 -0
  22. package/compiler/package-graph.go +92 -0
  23. package/compiler/package-graph_test.go +113 -0
  24. package/compiler/runtime-contract.go +1 -1
  25. package/compiler/semantic-model.go +32 -0
  26. package/compiler/skeleton_test.go +284 -11
  27. package/compiler/wasm/compile.go +1 -1
  28. package/compiler/wasm/compile_test.go +1 -1
  29. package/dist/compiler/index.d.ts +4 -0
  30. package/dist/compiler/index.js +26 -15
  31. package/dist/compiler/index.js.map +1 -1
  32. package/dist/gs/compress/gzip/index.d.ts +41 -0
  33. package/dist/gs/compress/gzip/index.js +235 -0
  34. package/dist/gs/compress/gzip/index.js.map +1 -0
  35. package/dist/gs/database/sql/driver/index.d.ts +165 -0
  36. package/dist/gs/database/sql/driver/index.js +432 -0
  37. package/dist/gs/database/sql/driver/index.js.map +1 -0
  38. package/dist/gs/encoding/binary/index.d.ts +71 -0
  39. package/dist/gs/encoding/binary/index.js +778 -0
  40. package/dist/gs/encoding/binary/index.js.map +1 -0
  41. package/dist/gs/fmt/fmt.js +156 -57
  42. package/dist/gs/fmt/fmt.js.map +1 -1
  43. package/dist/gs/github.com/klauspost/cpuid/v2/index.d.ts +11 -0
  44. package/dist/gs/github.com/klauspost/cpuid/v2/index.js +28 -0
  45. package/dist/gs/github.com/klauspost/cpuid/v2/index.js.map +1 -0
  46. package/dist/gs/github.com/pkg/errors/errors.d.ts +0 -2
  47. package/dist/gs/github.com/pkg/errors/errors.js.map +1 -1
  48. package/dist/gs/github.com/pkg/errors/index.d.ts +2 -1
  49. package/dist/gs/github.com/pkg/errors/index.js +1 -1
  50. package/dist/gs/github.com/pkg/errors/index.js.map +1 -1
  51. package/dist/gs/github.com/pkg/errors/stack.d.ts +8 -19
  52. package/dist/gs/github.com/pkg/errors/stack.js +26 -61
  53. package/dist/gs/github.com/pkg/errors/stack.js.map +1 -1
  54. package/dist/gs/golang.org/x/crypto/cryptobyte/asn1/index.d.ts +19 -0
  55. package/dist/gs/golang.org/x/crypto/cryptobyte/asn1/index.js +25 -0
  56. package/dist/gs/golang.org/x/crypto/cryptobyte/asn1/index.js.map +1 -0
  57. package/dist/gs/golang.org/x/crypto/cryptobyte/index.d.ts +104 -0
  58. package/dist/gs/golang.org/x/crypto/cryptobyte/index.js +1107 -0
  59. package/dist/gs/golang.org/x/crypto/cryptobyte/index.js.map +1 -0
  60. package/dist/gs/golang.org/x/crypto/internal/alias/index.d.ts +3 -0
  61. package/dist/gs/golang.org/x/crypto/internal/alias/index.js +39 -0
  62. package/dist/gs/golang.org/x/crypto/internal/alias/index.js.map +1 -0
  63. package/dist/gs/io/fs/glob.js +1 -1
  64. package/dist/gs/io/fs/glob.js.map +1 -1
  65. package/dist/gs/io/fs/readlink.d.ts +1 -1
  66. package/dist/gs/io/fs/readlink.js +2 -2
  67. package/dist/gs/io/fs/readlink.js.map +1 -1
  68. package/dist/gs/io/fs/stat.d.ts +4 -2
  69. package/dist/gs/io/fs/stat.js +12 -73
  70. package/dist/gs/io/fs/stat.js.map +1 -1
  71. package/dist/gs/io/fs/sub.d.ts +2 -2
  72. package/dist/gs/io/fs/sub.js +7 -7
  73. package/dist/gs/io/fs/sub.js.map +1 -1
  74. package/dist/gs/io/fs/walk.js +1 -1
  75. package/dist/gs/io/fs/walk.js.map +1 -1
  76. package/dist/gs/net/http/index.d.ts +18 -14
  77. package/dist/gs/net/http/index.js +44 -23
  78. package/dist/gs/net/http/index.js.map +1 -1
  79. package/dist/gs/net/http/pprof/index.d.ts +5 -5
  80. package/dist/gs/net/http/pprof/index.js +21 -21
  81. package/dist/gs/net/http/pprof/index.js.map +1 -1
  82. package/dist/gs/runtime/runtime.d.ts +6 -1
  83. package/dist/gs/runtime/runtime.js +15 -8
  84. package/dist/gs/runtime/runtime.js.map +1 -1
  85. package/dist/gs/runtime/trace/index.d.ts +8 -5
  86. package/dist/gs/runtime/trace/index.js +324 -23
  87. package/dist/gs/runtime/trace/index.js.map +1 -1
  88. package/dist/gs/slices/slices.d.ts +2 -1
  89. package/dist/gs/slices/slices.js +9 -3
  90. package/dist/gs/slices/slices.js.map +1 -1
  91. package/dist/gs/sort/search.gs.d.ts +3 -1
  92. package/dist/gs/sort/search.gs.js +18 -53
  93. package/dist/gs/sort/search.gs.js.map +1 -1
  94. package/dist/gs/sync/sync.d.ts +1 -1
  95. package/dist/gs/sync/sync.js +3 -0
  96. package/dist/gs/sync/sync.js.map +1 -1
  97. package/dist/gs/time/time.d.ts +22 -29
  98. package/dist/gs/time/time.js +111 -32
  99. package/dist/gs/time/time.js.map +1 -1
  100. package/dist/gs/unsafe/unsafe.d.ts +3 -2
  101. package/dist/gs/unsafe/unsafe.js.map +1 -1
  102. package/go.mod +7 -5
  103. package/go.sum +12 -26
  104. package/gs/builtin/runtime-contract.test.ts +25 -0
  105. package/gs/compress/gzip/index.test.ts +86 -0
  106. package/gs/compress/gzip/index.ts +297 -0
  107. package/gs/compress/gzip/meta.json +6 -0
  108. package/gs/compress/gzip/parity.json +45 -0
  109. package/gs/database/sql/driver/index.test.ts +88 -0
  110. package/gs/database/sql/driver/index.ts +675 -0
  111. package/gs/database/sql/driver/meta.json +3 -0
  112. package/gs/database/sql/driver/parity.json +144 -0
  113. package/gs/embed/index.test.ts +1 -1
  114. package/gs/encoding/binary/index.test.ts +239 -0
  115. package/gs/encoding/binary/index.ts +999 -0
  116. package/gs/encoding/binary/meta.json +9 -0
  117. package/gs/encoding/binary/parity.json +72 -0
  118. package/gs/fmt/fmt.test.ts +28 -0
  119. package/gs/fmt/fmt.ts +198 -61
  120. package/gs/fmt/meta.json +2 -1
  121. package/gs/github.com/klauspost/cpuid/v2/index.ts +38 -0
  122. package/gs/github.com/klauspost/cpuid/v2/meta.json +3 -0
  123. package/gs/github.com/pkg/errors/errors.ts +1 -2
  124. package/gs/github.com/pkg/errors/index.ts +2 -1
  125. package/gs/github.com/pkg/errors/stack.ts +34 -62
  126. package/gs/golang.org/x/crypto/cryptobyte/asn1/index.test.ts +19 -0
  127. package/gs/golang.org/x/crypto/cryptobyte/asn1/index.ts +29 -0
  128. package/gs/golang.org/x/crypto/cryptobyte/index.test.ts +255 -0
  129. package/gs/golang.org/x/crypto/cryptobyte/index.ts +1441 -0
  130. package/gs/golang.org/x/crypto/cryptobyte/meta.json +3 -0
  131. package/gs/golang.org/x/crypto/internal/alias/index.test.ts +40 -0
  132. package/gs/golang.org/x/crypto/internal/alias/index.ts +40 -0
  133. package/gs/io/fs/glob.ts +1 -1
  134. package/gs/io/fs/meta.json +3 -0
  135. package/gs/io/fs/readlink.test.ts +2 -2
  136. package/gs/io/fs/readlink.ts +5 -2
  137. package/gs/io/fs/stat.test.ts +79 -0
  138. package/gs/io/fs/stat.ts +24 -10
  139. package/gs/io/fs/sub.test.ts +93 -0
  140. package/gs/io/fs/sub.ts +9 -9
  141. package/gs/io/fs/walk.ts +1 -1
  142. package/gs/net/http/index.test.ts +207 -2
  143. package/gs/net/http/index.ts +68 -37
  144. package/gs/net/http/meta.json +3 -1
  145. package/gs/net/http/pprof/index.test.ts +4 -4
  146. package/gs/net/http/pprof/index.ts +30 -27
  147. package/gs/runtime/runtime.test.ts +16 -0
  148. package/gs/runtime/runtime.ts +17 -9
  149. package/gs/runtime/trace/index.test.ts +113 -14
  150. package/gs/runtime/trace/index.ts +384 -34
  151. package/gs/runtime/trace/meta.json +1 -0
  152. package/gs/slices/slices.test.ts +24 -1
  153. package/gs/slices/slices.ts +14 -4
  154. package/gs/sort/meta.json +1 -0
  155. package/gs/sort/search.gs.ts +20 -5
  156. package/gs/sync/sync.ts +4 -1
  157. package/gs/time/time.test.ts +79 -2
  158. package/gs/time/time.ts +133 -33
  159. package/gs/unsafe/unsafe.ts +4 -2
  160. package/package.json +2 -2
@@ -0,0 +1,3 @@
1
+ {
2
+ "dependencies": ["time"]
3
+ }
@@ -0,0 +1,40 @@
1
+ import { describe, expect, test } from 'vitest'
2
+
3
+ import { AnyOverlap, InexactOverlap } from './index.js'
4
+
5
+ describe('crypto/internal/alias override', () => {
6
+ test('reports no overlap for distinct backings', () => {
7
+ const x = new Uint8Array([1, 2, 3, 4])
8
+ const y = new Uint8Array([1, 2, 3, 4])
9
+ expect(AnyOverlap(x, y)).toBe(false)
10
+ expect(InexactOverlap(x, y)).toBe(false)
11
+ })
12
+
13
+ test('reports no overlap when either slice is empty', () => {
14
+ const buf = new Uint8Array(8)
15
+ expect(AnyOverlap(buf, new Uint8Array(0))).toBe(false)
16
+ expect(AnyOverlap(new Uint8Array(0), buf)).toBe(false)
17
+ })
18
+
19
+ test('detects overlap of views over a shared backing', () => {
20
+ const buf = new Uint8Array(8)
21
+ const head = buf.subarray(0, 4)
22
+ const tail = buf.subarray(4, 8)
23
+ const straddle = buf.subarray(2, 6)
24
+ // Disjoint halves of the same buffer do not overlap.
25
+ expect(AnyOverlap(head, tail)).toBe(false)
26
+ // A straddling view overlaps both halves.
27
+ expect(AnyOverlap(head, straddle)).toBe(true)
28
+ expect(AnyOverlap(tail, straddle)).toBe(true)
29
+ })
30
+
31
+ test('InexactOverlap ignores exactly aligned identical starts', () => {
32
+ const buf = new Uint8Array(8)
33
+ const a = buf.subarray(0, 4)
34
+ const b = buf.subarray(0, 4)
35
+ // Same start address: exact overlap, so InexactOverlap is false.
36
+ expect(InexactOverlap(a, b)).toBe(false)
37
+ // But AnyOverlap still reports the shared memory.
38
+ expect(AnyOverlap(a, b)).toBe(true)
39
+ })
40
+ })
@@ -0,0 +1,40 @@
1
+ import * as $ from '@goscript/builtin/index.js'
2
+
3
+ // AnyOverlap reports whether x and y share memory at any (not necessarily
4
+ // corresponding) index. The memory beyond the slice length is ignored.
5
+ //
6
+ // This mirrors the upstream pointer comparison without reflect or unsafe by
7
+ // using GoScript synthetic byte addresses. Those addresses are stride separated
8
+ // per backing array, so slices over distinct backings can never compare as
9
+ // overlapping and slices over a shared backing compare by element offset, which
10
+ // reproduces the Go semantics the crypto cipher modes rely on.
11
+ export function AnyOverlap(x: $.Bytes, y: $.Bytes): boolean {
12
+ const xLen = $.len(x)
13
+ const yLen = $.len(y)
14
+ if (xLen === 0 || yLen === 0) {
15
+ return false
16
+ }
17
+ const xFirst = $.indexByteAddress(x, 0, 1)
18
+ const xLast = $.indexByteAddress(x, xLen - 1, 1)
19
+ const yFirst = $.indexByteAddress(y, 0, 1)
20
+ const yLast = $.indexByteAddress(y, yLen - 1, 1)
21
+ return xFirst <= yLast && yFirst <= xLast
22
+ }
23
+
24
+ // InexactOverlap reports whether x and y share memory at any non-corresponding
25
+ // index. The memory beyond the slice length is ignored. Note that x and y can
26
+ // have different lengths and still not have any inexact overlap.
27
+ //
28
+ // InexactOverlap can be used to implement the requirements of the crypto/cipher
29
+ // AEAD, Block, BlockMode and Stream interfaces.
30
+ export function InexactOverlap(x: $.Bytes, y: $.Bytes): boolean {
31
+ const xLen = $.len(x)
32
+ const yLen = $.len(y)
33
+ if (xLen === 0 || yLen === 0) {
34
+ return false
35
+ }
36
+ if ($.indexByteAddress(x, 0, 1) === $.indexByteAddress(y, 0, 1)) {
37
+ return false
38
+ }
39
+ return AnyOverlap(x, y)
40
+ }
package/gs/io/fs/glob.ts CHANGED
@@ -96,7 +96,7 @@ export async function globWithLimit(
96
96
  }
97
97
  if (!hasMeta(pattern)) {
98
98
  {
99
- const [, statErr] = Stat(fsys, pattern)
99
+ const [, statErr] = await Stat(fsys, pattern)
100
100
  if (statErr != null) {
101
101
  return [null, null]
102
102
  }
@@ -1,7 +1,10 @@
1
1
  {
2
2
  "asyncFunctions": {
3
3
  "Glob": true,
4
+ "Lstat": true,
4
5
  "ReadDir": true,
6
+ "Stat": true,
7
+ "Sub": true,
5
8
  "WalkDir": true
6
9
  }
7
10
  }
@@ -35,8 +35,8 @@ describe('io/fs ReadLink', () => {
35
35
  expect(err?.Error()).toBe('readlink link: invalid argument')
36
36
  })
37
37
 
38
- it('uses Lstat from ReadLinkFS implementations', () => {
39
- const [, err] = Lstat(new linkFS(), 'link')
38
+ it('uses Lstat from ReadLinkFS implementations', async () => {
39
+ const [, err] = await Lstat(new linkFS(), 'link')
40
40
 
41
41
  expect(err).toBe(ErrInvalid)
42
42
  })
@@ -60,10 +60,13 @@ export function ReadLink(fsys: FS, name: string): [string, $.GoError] {
60
60
  return sym!.ReadLink(name)
61
61
  }
62
62
 
63
- export function Lstat(fsys: FS, name: string): [FileInfo, $.GoError] {
63
+ export async function Lstat(
64
+ fsys: FS,
65
+ name: string,
66
+ ): Promise<[FileInfo, $.GoError]> {
64
67
  const { value: sym, ok } = $.typeAssert<ReadLinkFS>(fsys, 'ReadLinkFS')
65
68
  if (!ok) {
66
- return Stat(fsys, name)
69
+ return await Stat(fsys, name)
67
70
  }
68
71
  return sym!.Lstat(name)
69
72
  }
@@ -0,0 +1,79 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import * as $ from '@goscript/builtin/index.js'
3
+
4
+ import { File, FileInfo, Stat } from './index.js'
5
+
6
+ function fileInfo(name: string, isDir = false): FileInfo {
7
+ return {
8
+ IsDir(): boolean {
9
+ return isDir
10
+ },
11
+ ModTime(): any {
12
+ return null
13
+ },
14
+ Mode(): number {
15
+ return isDir ? 0o040000 : 0
16
+ },
17
+ Name(): string {
18
+ return name
19
+ },
20
+ Size(): number {
21
+ return 0
22
+ },
23
+ Sys(): null {
24
+ return null
25
+ },
26
+ }
27
+ }
28
+
29
+ describe('io/fs Stat override', () => {
30
+ it('awaits async StatFS implementations', async () => {
31
+ const info = fileInfo('pkg', true)
32
+
33
+ const [got, err] = await Stat(
34
+ {
35
+ async Stat(_name: string): Promise<[FileInfo, $.GoError]> {
36
+ return [info, null]
37
+ },
38
+ Open(_name: string): [File, $.GoError] {
39
+ throw new Error('StatFS path should not open')
40
+ },
41
+ },
42
+ 'pkg',
43
+ )
44
+
45
+ expect(err).toBeNull()
46
+ expect(got).toBe(info)
47
+ })
48
+
49
+ it('awaits async Open, File.Stat, and Close fallback implementations', async () => {
50
+ const info = fileInfo('file.txt')
51
+ let closed = false
52
+ const file: File = {
53
+ async Close(): Promise<$.GoError> {
54
+ closed = true
55
+ return null
56
+ },
57
+ Read(_p0: Uint8Array): [number, $.GoError] {
58
+ return [0, null]
59
+ },
60
+ async Stat(): Promise<[FileInfo, $.GoError]> {
61
+ expect(closed).toBe(false)
62
+ return [info, null]
63
+ },
64
+ } as unknown as File
65
+
66
+ const [got, err] = await Stat(
67
+ {
68
+ async Open(_name: string): Promise<[File, $.GoError]> {
69
+ return [file, null]
70
+ },
71
+ } as any,
72
+ 'file.txt',
73
+ )
74
+
75
+ expect(err).toBeNull()
76
+ expect(got).toBe(info)
77
+ expect(closed).toBe(true)
78
+ })
79
+ })
package/gs/io/fs/stat.ts CHANGED
@@ -1,14 +1,25 @@
1
1
  import * as $ from '@goscript/builtin/index.js'
2
- import { FS, FileInfo } from './fs.js'
2
+ import { FS, File, FileInfo } from './fs.js'
3
+
4
+ type maybePromise<T> = T | Promise<T>
3
5
 
4
6
  export type StatFS =
5
7
  | null
6
8
  | ({
7
9
  // Stat returns a FileInfo describing the file.
8
10
  // If there is an error, it should be of type *PathError.
9
- Stat(name: string): [FileInfo, $.GoError]
11
+ Stat(name: string): maybePromise<[FileInfo, $.GoError]>
10
12
  } & FS)
11
13
 
14
+ type asyncFS = null | {
15
+ Open(name: string): maybePromise<[File, $.GoError]>
16
+ }
17
+
18
+ type asyncFile = null | {
19
+ Close(): maybePromise<$.GoError>
20
+ Stat(): maybePromise<[FileInfo, $.GoError]>
21
+ }
22
+
12
23
  $.registerInterfaceType(
13
24
  'StatFS',
14
25
  null, // Zero value for interface is null
@@ -42,21 +53,24 @@ $.registerInterfaceType(
42
53
  //
43
54
  // If fs implements [StatFS], Stat calls fs.Stat.
44
55
  // Otherwise, Stat opens the [File] to stat it.
45
- export function Stat(fsys: FS, name: string): [FileInfo, $.GoError] {
46
- using __defer = new $.DisposableStack()
56
+ export async function Stat(
57
+ fsys: FS,
58
+ name: string,
59
+ ): Promise<[FileInfo, $.GoError]> {
47
60
  {
48
61
  let { value: fsysTyped, ok: ok } = $.typeAssert<StatFS>(fsys, 'StatFS')
49
62
  if (ok) {
50
- return fsysTyped!.Stat(name)
63
+ return await fsysTyped!.Stat(name)
51
64
  }
52
65
  }
53
66
 
54
- let [file, err] = fsys!.Open(name)
67
+ let [file, err] = await (fsys as asyncFS)!.Open(name)
55
68
  if (err != null) {
56
69
  return [null, err]
57
70
  }
58
- __defer.defer(() => {
59
- file!.Close()
60
- })
61
- return file!.Stat()
71
+ try {
72
+ return await (file as asyncFile)!.Stat()
73
+ } finally {
74
+ await (file as asyncFile)!.Close()
75
+ }
62
76
  }
@@ -0,0 +1,93 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import * as $ from '@goscript/builtin/index.js'
3
+
4
+ import { File, FileInfo, Sub, Stat } from './index.js'
5
+
6
+ function fileInfo(name: string, isDir = false): FileInfo {
7
+ return {
8
+ IsDir(): boolean {
9
+ return isDir
10
+ },
11
+ ModTime(): any {
12
+ return null
13
+ },
14
+ Mode(): number {
15
+ return isDir ? 0o040000 : 0
16
+ },
17
+ Name(): string {
18
+ return name
19
+ },
20
+ Size(): number {
21
+ return 0
22
+ },
23
+ Sys(): null {
24
+ return null
25
+ },
26
+ }
27
+ }
28
+
29
+ describe('io/fs Sub override', () => {
30
+ it('preserves the backing filesystem and directory in fallback subFS', async () => {
31
+ const opened: string[] = []
32
+ const dirInfo = fileInfo('pkg', true)
33
+ const file: File = {
34
+ async Close(): Promise<$.GoError> {
35
+ return null
36
+ },
37
+ Read(_p0: Uint8Array): [number, $.GoError] {
38
+ return [0, null]
39
+ },
40
+ async Stat(): Promise<[FileInfo, $.GoError]> {
41
+ return [dirInfo, null]
42
+ },
43
+ } as unknown as File
44
+
45
+ const [sub, subErr] = await Sub(
46
+ {
47
+ async Open(name: string): Promise<[File, $.GoError]> {
48
+ opened.push(name)
49
+ return [file, null]
50
+ },
51
+ } as any,
52
+ 'pkg',
53
+ )
54
+ expect(subErr).toBeNull()
55
+
56
+ const [got, statErr] = await Stat(sub, '.')
57
+ expect(statErr).toBeNull()
58
+ expect(got).toBe(dirInfo)
59
+ expect(opened).toEqual(['pkg'])
60
+ })
61
+
62
+ it('joins nested fallback subFS directories', async () => {
63
+ const opened: string[] = []
64
+ const fileInfoValue = fileInfo('index.js')
65
+ const file: File = {
66
+ async Close(): Promise<$.GoError> {
67
+ return null
68
+ },
69
+ Read(_p0: Uint8Array): [number, $.GoError] {
70
+ return [0, null]
71
+ },
72
+ async Stat(): Promise<[FileInfo, $.GoError]> {
73
+ return [fileInfoValue, null]
74
+ },
75
+ } as unknown as File
76
+
77
+ const backingFS = {
78
+ async Open(name: string): Promise<[File, $.GoError]> {
79
+ opened.push(name)
80
+ return [file, null]
81
+ },
82
+ } as any
83
+ const [pkgFS, pkgErr] = await Sub(backingFS, '@scope')
84
+ expect(pkgErr).toBeNull()
85
+ const [clientFS, clientErr] = await Sub(pkgFS, 'client')
86
+ expect(clientErr).toBeNull()
87
+
88
+ const [got, statErr] = await Stat(clientFS, 'index.js')
89
+ expect(statErr).toBeNull()
90
+ expect(got).toBe(fileInfoValue)
91
+ expect(opened).toEqual(['@scope/client/index.js'])
92
+ })
93
+ })
package/gs/io/fs/sub.ts CHANGED
@@ -12,7 +12,7 @@ export type SubFS =
12
12
  | null
13
13
  | ({
14
14
  // Sub returns an FS corresponding to the subtree rooted at dir.
15
- Sub(dir: string): [FS, $.GoError]
15
+ Sub(dir: string): [FS, $.GoError] | Promise<[FS, $.GoError]>
16
16
  } & FS)
17
17
 
18
18
  $.registerInterfaceType(
@@ -56,7 +56,7 @@ $.registerInterfaceType(
56
56
  // does not check for symbolic links inside "/prefix" that point to
57
57
  // other directories. That is, [os.DirFS] is not a general substitute for a
58
58
  // chroot-style security mechanism, and Sub does not change that fact.
59
- export function Sub(fsys: FS, dir: string): [FS, $.GoError] {
59
+ export async function Sub(fsys: FS, dir: string): Promise<[FS, $.GoError]> {
60
60
  if (!ValidPath(dir)) {
61
61
  return [null, new PathError({ Err: ErrInvalid, Op: 'sub', Path: dir })]
62
62
  }
@@ -66,10 +66,10 @@ export function Sub(fsys: FS, dir: string): [FS, $.GoError] {
66
66
  {
67
67
  let { value: fsysTyped, ok: ok } = $.typeAssert<SubFS>(fsys, 'SubFS')
68
68
  if (ok) {
69
- return fsysTyped!.Sub(dir)
69
+ return await fsysTyped!.Sub(dir)
70
70
  }
71
71
  }
72
- return [new subFS({}), null]
72
+ return [new subFS({ fsys, dir }) as unknown as FS, null]
73
73
  }
74
74
 
75
75
  class subFS {
@@ -153,14 +153,14 @@ class subFS {
153
153
  return err
154
154
  }
155
155
 
156
- public Open(name: string): [File, $.GoError] {
156
+ public async Open(name: string): Promise<[File, $.GoError]> {
157
157
  const f = this
158
158
  let [full, err] = f!.fullName('open', name)
159
159
  if (err != null) {
160
160
  return [null, err]
161
161
  }
162
162
  let file: File
163
- ;[file, err] = f!.fsys!.Open(full)
163
+ ;[file, err] = await (f!.fsys as any)!.Open(full)
164
164
  return [file, f!.fixErr(err)]
165
165
  }
166
166
 
@@ -225,13 +225,13 @@ class subFS {
225
225
  public Sub(dir: string): [FS, $.GoError] {
226
226
  const f = this
227
227
  if (dir == '.') {
228
- return [f, null]
228
+ return [f as unknown as FS, null]
229
229
  }
230
- let [_full, err] = f!.fullName('sub', dir)
230
+ let [full, err] = f!.fullName('sub', dir)
231
231
  if (err != null) {
232
232
  return [null, err]
233
233
  }
234
- return [new subFS({}), null]
234
+ return [new subFS({ fsys: f.fsys, dir: full }) as unknown as FS, null]
235
235
  }
236
236
 
237
237
  // Register this type with the runtime type system
package/gs/io/fs/walk.ts CHANGED
@@ -88,7 +88,7 @@ export async function WalkDir(
88
88
  root: string,
89
89
  fn: WalkDirFunc,
90
90
  ): Promise<$.GoError> {
91
- let [info, err] = Stat(fsys, root)
91
+ let [info, err] = await Stat(fsys, root)
92
92
  if (err != null) {
93
93
  err = await fn!(root, null, err)
94
94
  } else {
@@ -20,6 +20,7 @@ import {
20
20
  ErrServerClosed,
21
21
  File,
22
22
  FileServer,
23
+ FileServerFS,
23
24
  FileSystem,
24
25
  FS,
25
26
  Get,
@@ -192,6 +193,36 @@ describe('net/http override', () => {
192
193
  expect(protocols.String()).toBe('{HTTP1,UnencryptedHTTP2}')
193
194
  })
194
195
 
196
+ it('accepts pointer-wrapped headers from generated ResponseWriter methods', () => {
197
+ const header = varRef(new Header())
198
+
199
+ Header_Set(header, 'content-length', '12')
200
+ Header_Add(header, 'content-type', 'text/plain')
201
+
202
+ expect(Header_Get(header, 'Content-Length')).toBe('12')
203
+ expect(Array.from(Header_Values(header, 'Content-Type') ?? [])).toEqual([
204
+ 'text/plain',
205
+ ])
206
+
207
+ const cloned = Header_Clone(header)
208
+ Header_Del(header, 'Content-Type')
209
+
210
+ expect(Header_Get(header, 'Content-Type')).toBe('')
211
+ expect(Header_Get(cloned, 'Content-Type')).toBe('text/plain')
212
+ })
213
+
214
+ it('accepts interface-boxed named headers from generated interface calls', () => {
215
+ const header = $.namedValueInterfaceValue(new Header(), 'http.Header', {})
216
+
217
+ Header_Set(header, 'content-length', '12')
218
+ Header_Add(header, 'content-type', 'text/plain')
219
+
220
+ expect(Header_Get(header, 'Content-Length')).toBe('12')
221
+ expect(Array.from(Header_Values(header, 'Content-Type') ?? [])).toEqual([
222
+ 'text/plain',
223
+ ])
224
+ })
225
+
195
226
  it('validates outgoing request construction', () => {
196
227
  const [req, reqErr] = NewRequestWithContext(
197
228
  context.Background(),
@@ -1171,7 +1202,7 @@ describe('net/http override', () => {
1171
1202
  ])
1172
1203
  })
1173
1204
 
1174
- it('formats Set-Cookie headers for browser bootstrap routes', () => {
1205
+ it('formats Set-Cookie headers for browser bootstrap routes', async () => {
1175
1206
  const header = new Header()
1176
1207
  const writer: ResponseWriter = {
1177
1208
  Header: () => header,
@@ -1179,7 +1210,7 @@ describe('net/http override', () => {
1179
1210
  WriteHeader: () => undefined,
1180
1211
  }
1181
1212
 
1182
- SetCookie(
1213
+ await SetCookie(
1183
1214
  writer,
1184
1215
  new Cookie({
1185
1216
  Name: 'spacewave_local_capability',
@@ -1314,6 +1345,7 @@ describe('net/http override', () => {
1314
1345
  expect(opened).toEqual(['file.txt'])
1315
1346
  expect(writes).toEqual(['status:200', 'hello'])
1316
1347
  expect(Header_Get(header, 'Content-Length')).toBe('5')
1348
+ expect(Header_Get(header, 'Content-Type')).toBe('text/plain; charset=utf-8')
1317
1349
 
1318
1350
  writes.length = 0
1319
1351
  opened.length = 0
@@ -1399,6 +1431,179 @@ describe('net/http override', () => {
1399
1431
  expect(closeCalls).toEqual(['async.txt'])
1400
1432
  })
1401
1433
 
1434
+ it('serves files through FS adapters backed by async io/fs Open', async () => {
1435
+ const opened: string[] = []
1436
+ const root = {
1437
+ async Open(name: string) {
1438
+ opened.push(name)
1439
+ const data = $.stringToBytes('fs-adapter')
1440
+ let offset = 0
1441
+ return [
1442
+ {
1443
+ Close: async () => null,
1444
+ Read: async (p: $.Slice<number>) => {
1445
+ if (offset >= data.length) {
1446
+ return [0, io.EOF] as [number, $.GoError]
1447
+ }
1448
+ const n = Math.min(p?.length ?? 0, data.length - offset)
1449
+ p?.set(data.subarray(offset, offset + n), 0)
1450
+ offset += n
1451
+ return [n, null] as [number, $.GoError]
1452
+ },
1453
+ Stat: async () => [
1454
+ {
1455
+ Name: () => name,
1456
+ Size: () => data.length,
1457
+ Mode: () => 0,
1458
+ ModTime: () => new time.Time(),
1459
+ IsDir: () => false,
1460
+ Sys: () => null,
1461
+ },
1462
+ null,
1463
+ ],
1464
+ },
1465
+ null,
1466
+ ]
1467
+ },
1468
+ }
1469
+ const writes: string[] = []
1470
+ const header = new Header()
1471
+ const writer: ResponseWriter = {
1472
+ Header: () => header,
1473
+ Write: (p) => {
1474
+ writes.push(Buffer.from(p ?? []).toString('utf8'))
1475
+ return [p?.length ?? 0, null]
1476
+ },
1477
+ WriteHeader: (code) => writes.push(`status:${code}`),
1478
+ }
1479
+ const [req] = NewRequest(
1480
+ MethodGet,
1481
+ 'http://example.invalid/dir/fs-adapter.txt',
1482
+ null,
1483
+ )
1484
+
1485
+ await FileServerFS(root).ServeHTTP(writer, req)
1486
+
1487
+ expect(opened).toEqual(['dir/fs-adapter.txt'])
1488
+ expect(writes).toEqual(['status:200', 'fs-adapter'])
1489
+ expect(Header_Get(header, 'Content-Length')).toBe('10')
1490
+ })
1491
+
1492
+ it('serves files through async response writer adapters', async () => {
1493
+ const root = {
1494
+ async Open(name: string) {
1495
+ const data = $.stringToBytes('async-adapter')
1496
+ return [
1497
+ {
1498
+ Read: (p: $.Slice<number>) => {
1499
+ p?.set(data.subarray(0, p.length))
1500
+ return [data.length, io.EOF] as [number, $.GoError]
1501
+ },
1502
+ Close: async () => null,
1503
+ Stat: async () => [
1504
+ {
1505
+ Name: () => name,
1506
+ Size: () => data.length,
1507
+ Mode: () => 0,
1508
+ ModTime: () => new time.Time(),
1509
+ IsDir: () => false,
1510
+ Sys: () => null,
1511
+ },
1512
+ null,
1513
+ ],
1514
+ },
1515
+ null,
1516
+ ] as [File, $.GoError]
1517
+ },
1518
+ }
1519
+ const writes: string[] = []
1520
+ const header = new Header()
1521
+ const writer: ResponseWriter = {
1522
+ Header: async () => header,
1523
+ Write: async (p) => {
1524
+ writes.push(Buffer.from(p ?? []).toString('utf8'))
1525
+ return [p?.length ?? 0, null]
1526
+ },
1527
+ WriteHeader: async (code) => {
1528
+ writes.push(`status:${code}`)
1529
+ },
1530
+ }
1531
+ const [req] = NewRequest(
1532
+ MethodGet,
1533
+ 'http://example.invalid/async-adapter.txt',
1534
+ null,
1535
+ )
1536
+
1537
+ await FileServer(root).ServeHTTP(writer, req)
1538
+
1539
+ expect(writes).toEqual(['status:200', 'async-adapter'])
1540
+ expect(Header_Get(header, 'Content-Length')).toBe('13')
1541
+ })
1542
+
1543
+ it('writes file server headers before reading response bodies', async () => {
1544
+ let headerWritten = false
1545
+ let readSawHeader = false
1546
+ let offset = 0
1547
+ const data = $.stringToBytes('streamed-body')
1548
+ const root = {
1549
+ async Open(name: string) {
1550
+ return [
1551
+ {
1552
+ Close: async () => null,
1553
+ Read: async (p: $.Slice<number>) => {
1554
+ readSawHeader = headerWritten
1555
+ if (offset >= data.length) {
1556
+ return [0, io.EOF] as [number, $.GoError]
1557
+ }
1558
+ const n = Math.min(p?.length ?? 0, data.length - offset)
1559
+ p?.set(data.subarray(offset, offset + n), 0)
1560
+ offset += n
1561
+ return [n, null] as [number, $.GoError]
1562
+ },
1563
+ Seek: async () => [0, null] as [number, $.GoError],
1564
+ Readdir: async () => [null, null] as [null, $.GoError],
1565
+ Stat: async () => [
1566
+ {
1567
+ Name: () => name,
1568
+ Size: () => data.length,
1569
+ Mode: () => 0,
1570
+ ModTime: () => null as never,
1571
+ IsDir: () => false,
1572
+ Sys: () => null,
1573
+ },
1574
+ null,
1575
+ ],
1576
+ },
1577
+ null,
1578
+ ] as [File, $.GoError]
1579
+ },
1580
+ }
1581
+ const writes: string[] = []
1582
+ const header = new Header()
1583
+ const writer: ResponseWriter = {
1584
+ Header: () => header,
1585
+ Write: (p) => {
1586
+ writes.push(Buffer.from(p ?? []).toString('utf8'))
1587
+ return [p?.length ?? 0, null]
1588
+ },
1589
+ WriteHeader: (code) => {
1590
+ headerWritten = true
1591
+ writes.push(`status:${code}`)
1592
+ },
1593
+ }
1594
+ const [req] = NewRequest(
1595
+ MethodGet,
1596
+ 'http://example.invalid/streamed.mjs',
1597
+ null,
1598
+ )
1599
+
1600
+ await FileServer(root).ServeHTTP(writer, req)
1601
+
1602
+ expect(readSawHeader).toBe(true)
1603
+ expect(writes).toEqual(['status:200', 'streamed-body'])
1604
+ expect(Header_Get(header, 'Content-Type')).toBe('text/javascript; charset=utf-8')
1605
+ })
1606
+
1402
1607
  it('awaits ServeContent writes before returning', async () => {
1403
1608
  const writes: string[] = []
1404
1609
  const writer: ResponseWriter = {