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.
- package/README.md +8 -8
- package/cmd/go_js_wasm_exec/main.go +1 -1
- package/cmd/go_js_wasm_exec/main_test.go +1 -1
- package/cmd/goscript/cmd-compile.go +9 -1
- package/cmd/goscript/cmd-test.go +1 -1
- package/cmd/goscript/cmd_compile_test.go +44 -0
- package/cmd/goscript/deps.go +1 -1
- package/cmd/goscript-wasm/main.go +2 -2
- package/compiler/compile-request.go +19 -0
- package/compiler/compile_bench_test.go +121 -0
- package/compiler/compliance_test.go +17 -1
- package/compiler/config.go +2 -0
- package/compiler/gotest/result.go +1 -1
- package/compiler/gotest/runner.go +2 -2
- package/compiler/gotest/runner_test.go +4 -7
- package/compiler/index.test.ts +28 -0
- package/compiler/index.ts +32 -16
- package/compiler/lowering.go +1236 -143
- package/compiler/lowering_bench_test.go +4 -0
- package/compiler/override-facts.go +1 -1
- package/compiler/override-registry_test.go +125 -0
- package/compiler/package-graph.go +92 -0
- package/compiler/package-graph_test.go +113 -0
- package/compiler/runtime-contract.go +1 -1
- package/compiler/semantic-model.go +32 -0
- package/compiler/skeleton_test.go +284 -11
- package/compiler/wasm/compile.go +1 -1
- package/compiler/wasm/compile_test.go +1 -1
- package/dist/compiler/index.d.ts +4 -0
- package/dist/compiler/index.js +26 -15
- package/dist/compiler/index.js.map +1 -1
- package/dist/gs/compress/gzip/index.d.ts +41 -0
- package/dist/gs/compress/gzip/index.js +235 -0
- package/dist/gs/compress/gzip/index.js.map +1 -0
- package/dist/gs/database/sql/driver/index.d.ts +165 -0
- package/dist/gs/database/sql/driver/index.js +432 -0
- package/dist/gs/database/sql/driver/index.js.map +1 -0
- package/dist/gs/encoding/binary/index.d.ts +71 -0
- package/dist/gs/encoding/binary/index.js +778 -0
- package/dist/gs/encoding/binary/index.js.map +1 -0
- package/dist/gs/fmt/fmt.js +156 -57
- package/dist/gs/fmt/fmt.js.map +1 -1
- package/dist/gs/github.com/klauspost/cpuid/v2/index.d.ts +11 -0
- package/dist/gs/github.com/klauspost/cpuid/v2/index.js +28 -0
- package/dist/gs/github.com/klauspost/cpuid/v2/index.js.map +1 -0
- package/dist/gs/github.com/pkg/errors/errors.d.ts +0 -2
- package/dist/gs/github.com/pkg/errors/errors.js.map +1 -1
- package/dist/gs/github.com/pkg/errors/index.d.ts +2 -1
- package/dist/gs/github.com/pkg/errors/index.js +1 -1
- package/dist/gs/github.com/pkg/errors/index.js.map +1 -1
- package/dist/gs/github.com/pkg/errors/stack.d.ts +8 -19
- package/dist/gs/github.com/pkg/errors/stack.js +26 -61
- package/dist/gs/github.com/pkg/errors/stack.js.map +1 -1
- package/dist/gs/golang.org/x/crypto/cryptobyte/asn1/index.d.ts +19 -0
- package/dist/gs/golang.org/x/crypto/cryptobyte/asn1/index.js +25 -0
- package/dist/gs/golang.org/x/crypto/cryptobyte/asn1/index.js.map +1 -0
- package/dist/gs/golang.org/x/crypto/cryptobyte/index.d.ts +104 -0
- package/dist/gs/golang.org/x/crypto/cryptobyte/index.js +1107 -0
- package/dist/gs/golang.org/x/crypto/cryptobyte/index.js.map +1 -0
- package/dist/gs/golang.org/x/crypto/internal/alias/index.d.ts +3 -0
- package/dist/gs/golang.org/x/crypto/internal/alias/index.js +39 -0
- package/dist/gs/golang.org/x/crypto/internal/alias/index.js.map +1 -0
- package/dist/gs/io/fs/glob.js +1 -1
- package/dist/gs/io/fs/glob.js.map +1 -1
- package/dist/gs/io/fs/readlink.d.ts +1 -1
- package/dist/gs/io/fs/readlink.js +2 -2
- package/dist/gs/io/fs/readlink.js.map +1 -1
- package/dist/gs/io/fs/stat.d.ts +4 -2
- package/dist/gs/io/fs/stat.js +12 -73
- package/dist/gs/io/fs/stat.js.map +1 -1
- package/dist/gs/io/fs/sub.d.ts +2 -2
- package/dist/gs/io/fs/sub.js +7 -7
- package/dist/gs/io/fs/sub.js.map +1 -1
- package/dist/gs/io/fs/walk.js +1 -1
- package/dist/gs/io/fs/walk.js.map +1 -1
- package/dist/gs/net/http/index.d.ts +18 -14
- package/dist/gs/net/http/index.js +44 -23
- package/dist/gs/net/http/index.js.map +1 -1
- package/dist/gs/net/http/pprof/index.d.ts +5 -5
- package/dist/gs/net/http/pprof/index.js +21 -21
- package/dist/gs/net/http/pprof/index.js.map +1 -1
- package/dist/gs/runtime/runtime.d.ts +6 -1
- package/dist/gs/runtime/runtime.js +15 -8
- package/dist/gs/runtime/runtime.js.map +1 -1
- package/dist/gs/runtime/trace/index.d.ts +8 -5
- package/dist/gs/runtime/trace/index.js +324 -23
- package/dist/gs/runtime/trace/index.js.map +1 -1
- package/dist/gs/slices/slices.d.ts +2 -1
- package/dist/gs/slices/slices.js +9 -3
- package/dist/gs/slices/slices.js.map +1 -1
- package/dist/gs/sort/search.gs.d.ts +3 -1
- package/dist/gs/sort/search.gs.js +18 -53
- package/dist/gs/sort/search.gs.js.map +1 -1
- package/dist/gs/sync/sync.d.ts +1 -1
- package/dist/gs/sync/sync.js +3 -0
- package/dist/gs/sync/sync.js.map +1 -1
- package/dist/gs/time/time.d.ts +22 -29
- package/dist/gs/time/time.js +111 -32
- package/dist/gs/time/time.js.map +1 -1
- package/dist/gs/unsafe/unsafe.d.ts +3 -2
- package/dist/gs/unsafe/unsafe.js.map +1 -1
- package/go.mod +7 -5
- package/go.sum +12 -26
- package/gs/builtin/runtime-contract.test.ts +25 -0
- package/gs/compress/gzip/index.test.ts +86 -0
- package/gs/compress/gzip/index.ts +297 -0
- package/gs/compress/gzip/meta.json +6 -0
- package/gs/compress/gzip/parity.json +45 -0
- package/gs/database/sql/driver/index.test.ts +88 -0
- package/gs/database/sql/driver/index.ts +675 -0
- package/gs/database/sql/driver/meta.json +3 -0
- package/gs/database/sql/driver/parity.json +144 -0
- package/gs/embed/index.test.ts +1 -1
- package/gs/encoding/binary/index.test.ts +239 -0
- package/gs/encoding/binary/index.ts +999 -0
- package/gs/encoding/binary/meta.json +9 -0
- package/gs/encoding/binary/parity.json +72 -0
- package/gs/fmt/fmt.test.ts +28 -0
- package/gs/fmt/fmt.ts +198 -61
- package/gs/fmt/meta.json +2 -1
- package/gs/github.com/klauspost/cpuid/v2/index.ts +38 -0
- package/gs/github.com/klauspost/cpuid/v2/meta.json +3 -0
- package/gs/github.com/pkg/errors/errors.ts +1 -2
- package/gs/github.com/pkg/errors/index.ts +2 -1
- package/gs/github.com/pkg/errors/stack.ts +34 -62
- package/gs/golang.org/x/crypto/cryptobyte/asn1/index.test.ts +19 -0
- package/gs/golang.org/x/crypto/cryptobyte/asn1/index.ts +29 -0
- package/gs/golang.org/x/crypto/cryptobyte/index.test.ts +255 -0
- package/gs/golang.org/x/crypto/cryptobyte/index.ts +1441 -0
- package/gs/golang.org/x/crypto/cryptobyte/meta.json +3 -0
- package/gs/golang.org/x/crypto/internal/alias/index.test.ts +40 -0
- package/gs/golang.org/x/crypto/internal/alias/index.ts +40 -0
- package/gs/io/fs/glob.ts +1 -1
- package/gs/io/fs/meta.json +3 -0
- package/gs/io/fs/readlink.test.ts +2 -2
- package/gs/io/fs/readlink.ts +5 -2
- package/gs/io/fs/stat.test.ts +79 -0
- package/gs/io/fs/stat.ts +24 -10
- package/gs/io/fs/sub.test.ts +93 -0
- package/gs/io/fs/sub.ts +9 -9
- package/gs/io/fs/walk.ts +1 -1
- package/gs/net/http/index.test.ts +207 -2
- package/gs/net/http/index.ts +68 -37
- package/gs/net/http/meta.json +3 -1
- package/gs/net/http/pprof/index.test.ts +4 -4
- package/gs/net/http/pprof/index.ts +30 -27
- package/gs/runtime/runtime.test.ts +16 -0
- package/gs/runtime/runtime.ts +17 -9
- package/gs/runtime/trace/index.test.ts +113 -14
- package/gs/runtime/trace/index.ts +384 -34
- package/gs/runtime/trace/meta.json +1 -0
- package/gs/slices/slices.test.ts +24 -1
- package/gs/slices/slices.ts +14 -4
- package/gs/sort/meta.json +1 -0
- package/gs/sort/search.gs.ts +20 -5
- package/gs/sync/sync.ts +4 -1
- package/gs/time/time.test.ts +79 -2
- package/gs/time/time.ts +133 -33
- package/gs/unsafe/unsafe.ts +4 -2
- package/package.json +2 -2
|
@@ -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
package/gs/io/fs/meta.json
CHANGED
|
@@ -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
|
})
|
package/gs/io/fs/readlink.ts
CHANGED
|
@@ -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(
|
|
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(
|
|
46
|
-
|
|
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
|
-
|
|
59
|
-
file!.
|
|
60
|
-
}
|
|
61
|
-
|
|
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 [
|
|
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
|
@@ -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 = {
|