functionalscript 0.46.1 → 0.47.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.
- package/fjs/basen/cbase32/module.f.mjs +18 -10
- package/fjs/basen/cbase32/proof.f.d.mts +3 -0
- package/fjs/basen/cbase32/proof.f.mjs +24 -2
- package/fjs/basen/proof.f.mjs +8 -16
- package/fjs/bnf/ll1/proof.f.d.mts +2 -0
- package/fjs/bnf/ll1/proof.f.mjs +33 -18
- package/fjs/bnf/module.f.d.mts +2 -2
- package/fjs/bnf/module.f.mjs +2 -2
- package/fjs/cas/proof.f.mjs +4 -26
- package/fjs/ci/common/module.f.mjs +6 -0
- package/fjs/ci/config/module.f.d.mts +7 -7
- package/fjs/ci/config/module.f.mjs +7 -7
- package/fjs/cli/module.f.mjs +14 -10
- package/fjs/crypto/vdf/module.f.d.mts +0 -1
- package/fjs/crypto/vdf/module.f.mjs +3 -14
- package/fjs/dev/module.f.mjs +10 -20
- package/fjs/djs/parser/module.f.d.mts +101 -53
- package/fjs/djs/parser/module.f.mjs +732 -525
- package/fjs/djs/parser/proof.f.d.mts +6 -0
- package/fjs/djs/parser/proof.f.mjs +315 -11
- package/fjs/djs/parser/types.d.ts +29 -2
- package/fjs/djs/tokenizer/module.f.mjs +122 -105
- package/fjs/djs/tokenizer/proof.f.mjs +17 -0
- package/fjs/djs/transpiler/module.f.mjs +4 -5
- package/fjs/djs/transpiler/proof.f.d.mts +2 -0
- package/fjs/djs/transpiler/proof.f.mjs +25 -0
- package/fjs/edag/amnesia/module.f.d.mts +20 -0
- package/fjs/edag/amnesia/module.f.mjs +303 -0
- package/fjs/edag/amnesia/proof.f.d.mts +79 -0
- package/fjs/edag/amnesia/proof.f.mjs +573 -0
- package/fjs/edag/amnesia/types.d.ts +15 -0
- package/fjs/edag/module.f.d.mts +410 -0
- package/fjs/edag/module.f.mjs +559 -0
- package/fjs/edag/proof.f.d.mts +116 -0
- package/fjs/edag/proof.f.mjs +686 -0
- package/fjs/edag/types.d.ts +55 -0
- package/fjs/effects/node/memory/module.d.mts +37 -3
- package/fjs/effects/node/memory/module.mjs +38 -4
- package/fjs/effects/node/memory/proof.d.mts +5 -2
- package/fjs/effects/node/memory/proof.mjs +36 -6
- package/fjs/effects/node/module.d.mts +9 -1
- package/fjs/effects/node/module.f.d.mts +39 -0
- package/fjs/effects/node/module.f.mjs +46 -0
- package/fjs/effects/node/module.mjs +234 -32
- package/fjs/effects/node/types.d.ts +28 -2
- package/fjs/effects/node/virtual/module.f.d.mts +19 -10
- package/fjs/effects/node/virtual/module.f.mjs +246 -32
- package/fjs/effects/node/virtual/proof.f.d.mts +17 -2
- package/fjs/effects/node/virtual/proof.f.mjs +232 -8
- package/fjs/effects/node/virtual/types.d.ts +57 -1
- package/fjs/emergent_testing/browser/proof.d.mts +55 -0
- package/fjs/emergent_testing/browser/proof.mjs +352 -0
- package/fjs/emergent_testing/browser/species.proof.d.mts +4 -0
- package/fjs/emergent_testing/browser/species.proof.mjs +45 -0
- package/fjs/emergent_testing/browser.d.mts +79 -0
- package/fjs/emergent_testing/browser.mjs +432 -0
- package/fjs/emergent_testing/proof.f.d.mts +1 -1
- package/fjs/emergent_testing/proof.f.mjs +2 -2
- package/fjs/js/string_escape/module.f.d.mts +47 -0
- package/fjs/js/string_escape/module.f.mjs +95 -0
- package/fjs/js/string_escape/proof.f.d.mts +8 -0
- package/fjs/js/string_escape/proof.f.mjs +68 -0
- package/fjs/js/tokenizer/module.f.mjs +14 -12
- package/fjs/mcp/cas/module.f.d.mts +1 -7
- package/fjs/mcp/cas/module.f.mjs +24 -15
- package/fjs/mcp/evo/module.f.mjs +9 -24
- package/fjs/mcp/module.f.d.mts +3 -1
- package/fjs/mcp/module.f.mjs +4 -2
- package/fjs/media/json/rtti/module.f.d.mts +2 -2
- package/fjs/media/json/rtti/module.f.mjs +1 -1
- package/fjs/media/json/schema/module.f.d.mts +15 -5
- package/fjs/media/json/schema/module.f.mjs +42 -15
- package/fjs/media/json/schema/proof.f.d.mts +10 -1
- package/fjs/media/json/schema/proof.f.mjs +88 -16
- package/fjs/media/json/serializer/module.f.mjs +14 -26
- package/fjs/media/json/types.d.ts +1 -1
- package/fjs/media/lock/module.f.d.mts +8 -3
- package/fjs/media/lock/module.f.mjs +9 -3
- package/fjs/media/lock/proof.f.d.mts +1 -1
- package/fjs/media/lock/proof.f.mjs +2 -2
- package/fjs/media/lock/types.d.ts +1 -3
- package/fjs/media/module.f.d.mts +20 -13
- package/fjs/media/module.f.mjs +29 -16
- package/fjs/media/note/module.f.d.mts +14 -7
- package/fjs/media/note/module.f.mjs +14 -7
- package/fjs/media/note/proof.f.mjs +1 -1
- package/fjs/media/proof.f.mjs +5 -5
- package/fjs/media/revision/module.f.d.mts +8 -2
- package/fjs/media/revision/module.f.mjs +9 -3
- package/fjs/media/revision/proof.f.mjs +1 -1
- package/fjs/media/revision/types.d.ts +1 -1
- package/fjs/media/type/module.f.d.mts +20 -0
- package/fjs/media/type/module.f.mjs +74 -0
- package/fjs/media/type/proof.f.d.mts +5 -0
- package/fjs/media/type/proof.f.mjs +24 -1
- package/fjs/module.f.mjs +6 -0
- package/fjs/path/module.f.d.mts +60 -3
- package/fjs/path/module.f.mjs +138 -14
- package/fjs/path/proof.f.d.mts +3 -0
- package/fjs/path/proof.f.mjs +298 -2
- package/fjs/protocol/json_rpc/module.f.d.mts +65 -28
- package/fjs/protocol/json_rpc/module.f.mjs +49 -13
- package/fjs/protocol/json_rpc/proof.f.d.mts +4 -0
- package/fjs/protocol/json_rpc/proof.f.mjs +29 -0
- package/fjs/protocol/json_rpc/types.d.ts +12 -2
- package/fjs/protocol/mcp/module.f.d.mts +89 -54
- package/fjs/protocol/mcp/module.f.mjs +110 -56
- package/fjs/protocol/mcp/proof.f.d.mts +13 -0
- package/fjs/protocol/mcp/proof.f.mjs +107 -7
- package/fjs/protocol/mcp/stdio/module.f.mjs +3 -4
- package/fjs/protocol/mcp/stdio/proof.f.mjs +2 -2
- package/fjs/protocol/mcp/types.d.ts +14 -2
- package/fjs/text/utf8/module.f.mjs +27 -1
- package/fjs/text/utf8/proof.f.mjs +37 -0
- package/fjs/types/array/module.f.mjs +18 -11
- package/fjs/types/array/proof.f.mjs +27 -1
- package/fjs/types/bigfloat/module.f.d.mts +48 -9
- package/fjs/types/bigfloat/module.f.mjs +147 -59
- package/fjs/types/bigfloat/proof.f.d.mts +2 -0
- package/fjs/types/bigfloat/proof.f.mjs +136 -4
- package/fjs/types/bigfloat/types.d.ts +17 -0
- package/fjs/types/function/module.f.d.mts +11 -2
- package/fjs/types/function/module.f.mjs +20 -2
- package/fjs/types/function/proof.f.mjs +5 -1
- package/fjs/types/phantom/types.d.ts +21 -0
- package/fjs/types/result/module.f.d.mts +4 -4
- package/fjs/types/result/module.f.mjs +2 -2
- package/fjs/types/result/proof.f.d.mts +6 -2
- package/fjs/types/result/proof.f.mjs +17 -1
- package/fjs/types/rtti/common/module.f.d.mts +78 -5
- package/fjs/types/rtti/common/module.f.mjs +168 -3
- package/fjs/types/rtti/common/proof.f.d.mts +14 -0
- package/fjs/types/rtti/common/proof.f.mjs +61 -2
- package/fjs/types/rtti/common/types.d.ts +23 -1
- package/fjs/types/rtti/data/module.f.d.mts +57 -1
- package/fjs/types/rtti/data/module.f.mjs +241 -47
- package/fjs/types/rtti/data/proof.f.d.mts +10 -0
- package/fjs/types/rtti/data/proof.f.mjs +272 -39
- package/fjs/types/rtti/data/types.d.ts +14 -7
- package/fjs/types/rtti/host.proof.d.mts +33 -0
- package/fjs/types/rtti/host.proof.mjs +170 -0
- package/fjs/types/rtti/module.f.d.mts +51 -6
- package/fjs/types/rtti/module.f.mjs +50 -3
- package/fjs/types/rtti/parse/module.f.d.mts +31 -24
- package/fjs/types/rtti/parse/module.f.mjs +197 -59
- package/fjs/types/rtti/parse/proof.f.d.mts +37 -2
- package/fjs/types/rtti/parse/proof.f.mjs +171 -47
- package/fjs/types/rtti/proof.f.d.mts +6 -0
- package/fjs/types/rtti/proof.f.mjs +42 -1
- package/fjs/types/rtti/ts/module.f.d.mts +9 -3
- package/fjs/types/rtti/ts/module.f.mjs +68 -15
- package/fjs/types/rtti/ts/proof.f.d.mts +42 -2
- package/fjs/types/rtti/ts/proof.f.mjs +139 -6
- package/fjs/types/rtti/ts/types.d.ts +164 -16
- package/fjs/types/rtti/types.d.ts +31 -3
- package/fjs/types/rtti/validate/module.f.d.mts +42 -25
- package/fjs/types/rtti/validate/module.f.mjs +187 -53
- package/fjs/types/rtti/validate/proof.f.d.mts +28 -2
- package/fjs/types/rtti/validate/proof.f.mjs +430 -75
- package/fjs/types/ts/types.d.ts +4 -0
- package/fjs/web/module.f.d.mts +115 -0
- package/fjs/web/module.f.mjs +581 -0
- package/fjs/web/proof.f.d.mts +39 -0
- package/fjs/web/proof.f.mjs +413 -0
- package/fjs/web/types.d.ts +39 -0
- package/fjs/website/browser-prepare.d.mts +5 -0
- package/fjs/website/browser-prepare.mjs +115 -0
- package/fjs/website/browser-source.d.mts +37 -0
- package/fjs/website/browser-source.mjs +202 -0
- package/fjs/website/browser-source.proof.d.mts +31 -0
- package/fjs/website/browser-source.proof.mjs +162 -0
- package/fjs/website/browser.d.mts +5 -0
- package/fjs/website/browser.mjs +8 -0
- package/fjs/website/module.f.d.mts +3 -3
- package/fjs/website/module.f.mjs +62 -9
- package/fjs/website/proof.f.d.mts +4 -0
- package/fjs/website/proof.f.mjs +18 -3
- package/package.json +2 -3
|
@@ -1,20 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @import { Dir } from './types.ts'
|
|
3
|
-
* @import { NodeOp } from '../types.ts'
|
|
2
|
+
* @import { Dir, State } from './types.ts'
|
|
3
|
+
* @import { IncomingMessage, NodeOp, RequestListener } from '../types.ts'
|
|
4
4
|
* @import { Effect } from '../../types.ts'
|
|
5
5
|
* @import { IoChannel } from '../types.ts'
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { assert, assertEq } from '../../../asserts/module.f.mjs'
|
|
9
|
-
import { access, awaitIfPromise, exec, fetch, log, rm, writeFile, readFile, readdir, import_, rename, readBytes, writeBytes, stat, createExclusive } from '../module.f.mjs'
|
|
9
|
+
import { access, awaitIfPromise, exec, fetch, log, rm, writeFile, readFile, readdir, import_, rename, readBytes, writeBytes, stat, createExclusive, createServer, forever, listen } from '../module.f.mjs'
|
|
10
10
|
import { empty, length, maxLengthBytes, vec, vec8 } from '../../../types/bit_vec/module.f.mjs'
|
|
11
|
+
import { history, historyStep, pureOk, step } from '../../module.f.mjs'
|
|
12
|
+
import { utf8, utf8ToString } from '../../../text/module.f.mjs'
|
|
11
13
|
import { emptyState, virtual } from './module.f.mjs'
|
|
12
14
|
import { do_ } from '../../module.f.mjs'
|
|
13
15
|
import { catchStep } from '../../module.f.mjs'
|
|
14
16
|
|
|
15
17
|
/**
|
|
16
|
-
* Asserts that a channel error is a host failure carrying `
|
|
18
|
+
* Asserts that a channel error is a host failure carrying `code` — the
|
|
17
19
|
* normalized shape every runner reports, virtual and Node alike.
|
|
20
|
+
* @type {(e: IoChannel, code: string) => void}
|
|
21
|
+
*/
|
|
22
|
+
const assertIoCode = (e, code) => {
|
|
23
|
+
assert(e[0] === 'ioError', e)
|
|
24
|
+
assertEq(e[1].code, code)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Asserts that a channel error is a host failure carrying `message`.
|
|
18
29
|
* @type {(e: IoChannel, message: string) => void}
|
|
19
30
|
*/
|
|
20
31
|
const assertIoMessage = (e, message) => {
|
|
@@ -334,6 +345,18 @@ export const proof = {
|
|
|
334
345
|
const [, result] = virtual({ ...emptyState, root })(rename('myfile', 'mydir'))
|
|
335
346
|
assert(result[0] === 'error')
|
|
336
347
|
},
|
|
348
|
+
renameForeignArrayFileOntoDirectory: () => {
|
|
349
|
+
// An array with another constructor's prototype models the observable
|
|
350
|
+
// instanceof behavior of an array received from another realm.
|
|
351
|
+
const foreignFile = Reflect.construct(Array, [], Object)
|
|
352
|
+
assert(Array.isArray(foreignFile))
|
|
353
|
+
assert(!(foreignFile instanceof Array))
|
|
354
|
+
/** @type {Dir} */
|
|
355
|
+
const root = { 'myfile': foreignFile, 'mydir': {} }
|
|
356
|
+
const [, result] = virtual({ ...emptyState, root })(rename('myfile', 'mydir'))
|
|
357
|
+
assert(result[0] === 'error')
|
|
358
|
+
assertIoMessage(result[1], "'mydir' is a directory")
|
|
359
|
+
},
|
|
337
360
|
readFileTooLarge: () => {
|
|
338
361
|
// a file stored as two max-size chunks exceeds the limit; readFile must return an error
|
|
339
362
|
const chunk0 = vec(maxLengthBytes * 8n)(0n)
|
|
@@ -396,14 +419,38 @@ export const proof = {
|
|
|
396
419
|
const [, result] = virtual({ ...emptyState, root })(writeBytes('file', 5, vec8(0x43n)))
|
|
397
420
|
assert(result[0] === 'error')
|
|
398
421
|
},
|
|
422
|
+
statOnDirectory: () => {
|
|
423
|
+
// A host stats a directory successfully and says it is not a file. The
|
|
424
|
+
// empty remaining path is how one arrives here: `operation` has already
|
|
425
|
+
// descended into it.
|
|
426
|
+
/** @type {Dir} */
|
|
427
|
+
const root = { docs: { 'index.html': [vec8(0x41n)] } }
|
|
428
|
+
const [, result] = virtual({ ...emptyState, root })(stat('docs'))
|
|
429
|
+
assert(result[0] === 'ok', result)
|
|
430
|
+
assertEq(result[1].isFile, false)
|
|
431
|
+
},
|
|
432
|
+
statOnEmptyPath: () => {
|
|
433
|
+
// An empty path is not the root, though `parse` collapses both to no
|
|
434
|
+
// segments at all. A host answers `ENOENT`, so this does.
|
|
435
|
+
const [, result] = virtual(emptyState)(stat(''))
|
|
436
|
+
assert(result[0] === 'error', result)
|
|
437
|
+
assertIoCode(result[1], 'ENOENT')
|
|
438
|
+
// `.` *is* the root, and stats as the directory it is.
|
|
439
|
+
const [, root] = virtual(emptyState)(stat('.'))
|
|
440
|
+
assert(root[0] === 'ok', root)
|
|
441
|
+
assertEq(root[1].isFile, false)
|
|
442
|
+
},
|
|
399
443
|
statOnJsModule: () => {
|
|
400
|
-
//
|
|
401
|
-
//
|
|
444
|
+
// A `JsModule` entry is this file system's non-regular name: it exists
|
|
445
|
+
// and stats fine, and says it is not a file — the shape a host reports
|
|
446
|
+
// for a FIFO or a device, and what a caller's guard against reading one
|
|
447
|
+
// has to be able to see.
|
|
402
448
|
/** @type {Dir} */
|
|
403
449
|
const root = { 'a.f.ts': () => ({}) }
|
|
404
450
|
const [, result] = virtual({ ...emptyState, root })(stat('a.f.ts'))
|
|
405
|
-
assert(result[0] === '
|
|
406
|
-
|
|
451
|
+
assert(result[0] === 'ok', result)
|
|
452
|
+
assertEq(result[1].isFile, false)
|
|
453
|
+
assertEq(result[1].size, 0)
|
|
407
454
|
},
|
|
408
455
|
largeFileReadBytes: () => {
|
|
409
456
|
// A file stored as two 128 KiB chunks is larger than maxLengthBytes.
|
|
@@ -416,4 +463,181 @@ export const proof = {
|
|
|
416
463
|
const [, result] = virtual({ ...emptyState, root })(readBytes('large', chunkSize, 1))
|
|
417
464
|
assert(result[0] === 'ok')
|
|
418
465
|
},
|
|
466
|
+
// A server without a socket: `createServer` stores the listener and
|
|
467
|
+
// `listen` hands it the requests the fixture queued, which is what makes a
|
|
468
|
+
// request-in / response-out proof possible here at all.
|
|
469
|
+
http: {
|
|
470
|
+
answersQueuedRequests: () => {
|
|
471
|
+
/** @type {(url: string) => IncomingMessage} */
|
|
472
|
+
const get = url => ({ method: 'GET', url, headers: {}, body: empty })
|
|
473
|
+
/** @type {RequestListener<never>} */
|
|
474
|
+
const listener = ({ url }) =>
|
|
475
|
+
pureOk({ status: 200, headers: {}, body: utf8(`echo ${url}`) })
|
|
476
|
+
const e = step(createServer(listener), server => listen(server, 8080, '127.0.0.1'))
|
|
477
|
+
/** @type {State} */
|
|
478
|
+
const state = { ...emptyState, requests: [get('/a'), get('/b')] }
|
|
479
|
+
const [s, result] = virtual(state)(e)
|
|
480
|
+
assert(result[0] === 'ok', result)
|
|
481
|
+
assertEq(s.listening.map(b => b.address).join(), '127.0.0.1:8080')
|
|
482
|
+
// The queue is emptied, so a second `listen` cannot answer the same
|
|
483
|
+
// request twice.
|
|
484
|
+
assertEq(s.requests.length, 0)
|
|
485
|
+
assertEq(s.responses.map(r => utf8ToString(r.body)).join(', '), 'echo /a, echo /b')
|
|
486
|
+
},
|
|
487
|
+
// Two servers in one program are two servers here, as they are on a
|
|
488
|
+
// host: `listen` answers with the listener its *handle* carries, not
|
|
489
|
+
// with whichever was created last.
|
|
490
|
+
dispatchesThroughTheHandle: () => {
|
|
491
|
+
/** @type {(name: string) => RequestListener<never>} */
|
|
492
|
+
const named = name => () => pureOk({ status: 200, headers: {}, body: utf8(name) })
|
|
493
|
+
// Flat, because the third link needs the *first* one's value: a
|
|
494
|
+
// history carries `a` forward instead of a nested continuation
|
|
495
|
+
// closing over it.
|
|
496
|
+
const created = history(createServer(named('a')))
|
|
497
|
+
const both = historyStep(created, () => createServer(named('b')))
|
|
498
|
+
const first = step(both, ([, a]) => listen(a, 8080, '127.0.0.1'))
|
|
499
|
+
/** @type {State} */
|
|
500
|
+
const state = {
|
|
501
|
+
...emptyState,
|
|
502
|
+
requests: [{ method: 'GET', url: '/', headers: {}, body: empty }],
|
|
503
|
+
}
|
|
504
|
+
const [s, result] = virtual(state)(first)
|
|
505
|
+
assert(result[0] === 'ok', result)
|
|
506
|
+
assertEq(utf8ToString(s.responses[0].body), 'a')
|
|
507
|
+
},
|
|
508
|
+
// A port a host would refuse is refused here, or a program that cannot
|
|
509
|
+
// run anywhere could still be proven.
|
|
510
|
+
badPort: () => {
|
|
511
|
+
/** @type {RequestListener<never>} */
|
|
512
|
+
const listener = () => pureOk({ status: 200, headers: {}, body: empty })
|
|
513
|
+
/** @type {(port: number) => void} */
|
|
514
|
+
const rejects = port => {
|
|
515
|
+
const e = step(createServer(listener), server => listen(server, port, '127.0.0.1'))
|
|
516
|
+
const [s, result] = virtual(emptyState)(e)
|
|
517
|
+
assert(result[0] === 'error', result)
|
|
518
|
+
assertIoCode(result[1], 'ERR_SOCKET_BAD_PORT')
|
|
519
|
+
// The message is Node's own, type and all — the shape is the
|
|
520
|
+
// claim this runner makes.
|
|
521
|
+
assertIoMessage(
|
|
522
|
+
result[1],
|
|
523
|
+
`options.port should be >= 0 and < 65536. Received type number (${port}).`)
|
|
524
|
+
assertEq(s.listening.length, 0)
|
|
525
|
+
}
|
|
526
|
+
rejects(-1)
|
|
527
|
+
rejects(1.5)
|
|
528
|
+
rejects(65536)
|
|
529
|
+
rejects(NaN)
|
|
530
|
+
},
|
|
531
|
+
// Port `0` names no port: two servers asking the host for a free one
|
|
532
|
+
// both get one, so refusing the second would reject a program that runs.
|
|
533
|
+
ephemeralPorts: () => {
|
|
534
|
+
/** @type {RequestListener<never>} */
|
|
535
|
+
const listener = () => pureOk({ status: 200, headers: {}, body: empty })
|
|
536
|
+
const first = history(createServer(listener))
|
|
537
|
+
const second = historyStep(first, () => createServer(listener))
|
|
538
|
+
const bound = historyStep(second, b => listen(b, 0, '127.0.0.1'))
|
|
539
|
+
const e = step(bound, ([, , a]) => listen(a, 0, '127.0.0.1'))
|
|
540
|
+
const [s, result] = virtual(emptyState)(e)
|
|
541
|
+
assert(result[0] === 'ok', result)
|
|
542
|
+
assertEq(s.listening.length, 2)
|
|
543
|
+
},
|
|
544
|
+
// Binding fails here the way it fails on a host, which is the whole
|
|
545
|
+
// point of `Listen` being fallible: a program that mishandles either
|
|
546
|
+
// failure must not look correct against this runner.
|
|
547
|
+
addressInUse: () => {
|
|
548
|
+
/** @type {RequestListener<never>} */
|
|
549
|
+
const listener = () => pureOk({ status: 200, headers: {}, body: empty })
|
|
550
|
+
const first = history(createServer(listener))
|
|
551
|
+
const second = historyStep(first, () => createServer(listener))
|
|
552
|
+
// `historyStep` spreads the history over its continuation, newest
|
|
553
|
+
// first: `b` here is the second server, and `a` the first.
|
|
554
|
+
const bound = historyStep(second, b => listen(b, 8080, '127.0.0.1'))
|
|
555
|
+
const e = step(bound, ([, , a]) => listen(a, 8080, '127.0.0.1'))
|
|
556
|
+
const [s, result] = virtual(emptyState)(e)
|
|
557
|
+
assert(result[0] === 'error', result)
|
|
558
|
+
assertIoCode(result[1], 'EADDRINUSE')
|
|
559
|
+
// The first server keeps the address it took.
|
|
560
|
+
assertEq(s.listening.length, 1)
|
|
561
|
+
},
|
|
562
|
+
// A DNS name is case-insensitive, so `LOCALHOST` takes the address
|
|
563
|
+
// `localhost` then asks for — checked on Linux with Node 22.22.2 and on
|
|
564
|
+
// Darwin with Node 23.11.0, where the second bind is `EADDRINUSE`.
|
|
565
|
+
addressInUseIgnoresCase: () => {
|
|
566
|
+
/** @type {RequestListener<never>} */
|
|
567
|
+
const listener = () => pureOk({ status: 200, headers: {}, body: empty })
|
|
568
|
+
const first = history(createServer(listener))
|
|
569
|
+
const second = historyStep(first, () => createServer(listener))
|
|
570
|
+
const bound = historyStep(second, b => listen(b, 8080, 'LOCALHOST'))
|
|
571
|
+
const e = step(bound, ([, , a]) => listen(a, 8080, 'localhost'))
|
|
572
|
+
const [s, result] = virtual(emptyState)(e)
|
|
573
|
+
assert(result[0] === 'error', result)
|
|
574
|
+
assertIoCode(result[1], 'EADDRINUSE')
|
|
575
|
+
assertIoMessage(
|
|
576
|
+
result[1],
|
|
577
|
+
'listen EADDRINUSE: address already in use localhost:8080')
|
|
578
|
+
// Recorded lower-cased, whichever case asked for it.
|
|
579
|
+
assertEq(s.listening.length, 1)
|
|
580
|
+
assertEq(s.listening[0].address, 'localhost:8080')
|
|
581
|
+
},
|
|
582
|
+
// `''` is the host a program did not state, and Node binds every
|
|
583
|
+
// interface for it — so both runners refuse it rather than forward it.
|
|
584
|
+
emptyHostRefused: () => {
|
|
585
|
+
/** @type {RequestListener<never>} */
|
|
586
|
+
const listener = () => pureOk({ status: 200, headers: {}, body: empty })
|
|
587
|
+
const created = history(createServer(listener))
|
|
588
|
+
const e = step(created, ([server]) => listen(server, 8080, ''))
|
|
589
|
+
const [s, result] = virtual(emptyState)(e)
|
|
590
|
+
assert(result[0] === 'error', result)
|
|
591
|
+
assertIoCode(result[1], 'ERR_INVALID_ARG_VALUE')
|
|
592
|
+
assertIoMessage(
|
|
593
|
+
result[1],
|
|
594
|
+
`The argument 'host' must not be empty. Received ''`)
|
|
595
|
+
// Nothing bound.
|
|
596
|
+
assertEq(s.listening.length, 0)
|
|
597
|
+
},
|
|
598
|
+
// And it is refused *first*: a server already listening, retried with
|
|
599
|
+
// an empty host, names the host and not the state. Node has no order of
|
|
600
|
+
// its own to copy here — it binds `''` — so the two runners have only
|
|
601
|
+
// to agree, and the Node runner asks this before it touches the socket.
|
|
602
|
+
emptyHostBeatsAlreadyListening: () => {
|
|
603
|
+
/** @type {RequestListener<never>} */
|
|
604
|
+
const listener = () => pureOk({ status: 200, headers: {}, body: empty })
|
|
605
|
+
const created = history(createServer(listener))
|
|
606
|
+
const bound = historyStep(created, server => listen(server, 8080, '127.0.0.1'))
|
|
607
|
+
const e = step(bound, ([, server]) => listen(server, 9090, ''))
|
|
608
|
+
const [, result] = virtual(emptyState)(e)
|
|
609
|
+
assert(result[0] === 'error', result)
|
|
610
|
+
assertIoCode(result[1], 'ERR_INVALID_ARG_VALUE')
|
|
611
|
+
},
|
|
612
|
+
alreadyListening: () => {
|
|
613
|
+
/** @type {RequestListener<never>} */
|
|
614
|
+
const listener = () => pureOk({ status: 200, headers: {}, body: empty })
|
|
615
|
+
/** @type {(second: number) => IoChannel} */
|
|
616
|
+
const again = second => {
|
|
617
|
+
const created = history(createServer(listener))
|
|
618
|
+
const bound = historyStep(created, server => listen(server, 8080, '127.0.0.1'))
|
|
619
|
+
const e = step(bound, ([, server]) => listen(server, second, '127.0.0.1'))
|
|
620
|
+
const [, result] = virtual(emptyState)(e)
|
|
621
|
+
assert(result[0] === 'error', result)
|
|
622
|
+
return result[1]
|
|
623
|
+
}
|
|
624
|
+
assertIoCode(again(9090), 'ERR_SERVER_ALREADY_LISTEN')
|
|
625
|
+
// And it is asked before the port is: a server already listening
|
|
626
|
+
// reports this for a port no server could take, where the same
|
|
627
|
+
// value on a fresh server is `ERR_SOCKET_BAD_PORT`. That is the
|
|
628
|
+
// order Node asks in, checked on Linux with Node 22.22.2 and on
|
|
629
|
+
// Darwin with Node 23.11.0.
|
|
630
|
+
assertIoCode(again(-1), 'ERR_SERVER_ALREADY_LISTEN')
|
|
631
|
+
assertIoCode(again(65536), 'ERR_SERVER_ALREADY_LISTEN')
|
|
632
|
+
assertIoCode(again(NaN), 'ERR_SERVER_ALREADY_LISTEN')
|
|
633
|
+
},
|
|
634
|
+
// `forever` is the operation this runner cannot answer — its result
|
|
635
|
+
// type leaves it nothing but `notImplemented` to return — so a server
|
|
636
|
+
// program run here ends where it would otherwise have blocked.
|
|
637
|
+
foreverIsNotImplemented: () => {
|
|
638
|
+
const [, result] = virtual(emptyState)(forever())
|
|
639
|
+
assert(result[0] === 'error', result)
|
|
640
|
+
assertEq(result[1][1], 'forever')
|
|
641
|
+
},
|
|
642
|
+
},
|
|
419
643
|
}
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* @module
|
|
6
6
|
*/
|
|
7
7
|
import type { Vec } from '../../../types/bit_vec/types.ts';
|
|
8
|
-
import type {
|
|
8
|
+
import type { Effect } from '../../types.ts';
|
|
9
|
+
import type { IncomingMessage, Module, NodeOp, ServerResponse } from '../types.ts';
|
|
9
10
|
/**
|
|
10
11
|
* In-memory JS module entry. When `import_` is called on the path, the
|
|
11
12
|
* function is invoked and its return value is the module value (with a
|
|
@@ -20,6 +21,48 @@ export type _Entity = readonly Vec[] | Dir | JsModule;
|
|
|
20
21
|
export type Dir = {
|
|
21
22
|
readonly [name in string]?: _Entity;
|
|
22
23
|
};
|
|
24
|
+
/**
|
|
25
|
+
* The listener a virtual `Server` handle carries, at the operation set this
|
|
26
|
+
* runner can actually run it with. `CreateServer` declares its listener over
|
|
27
|
+
* `Operation` — an unresolved type parameter would leak into every consumer of
|
|
28
|
+
* `Server` — so the handler narrows it, exactly as the Node runner does before
|
|
29
|
+
* handing a request to it. It rides in the handle rather than in the state, so
|
|
30
|
+
* that two servers in one program are two servers here too.
|
|
31
|
+
*
|
|
32
|
+
* @internal
|
|
33
|
+
*/
|
|
34
|
+
export type _VirtualListener = (request: IncomingMessage) => Effect<NodeOp, ServerResponse, never>;
|
|
35
|
+
/**
|
|
36
|
+
* What a virtual `Server` handle carries.
|
|
37
|
+
*
|
|
38
|
+
* It is a record rather than the listener itself so that each `createServer`
|
|
39
|
+
* gets its own identity: on a host, creating two servers from one listener
|
|
40
|
+
* gives two servers, and a handle that *was* the listener would make them the
|
|
41
|
+
* same one — which `listen` would then read as the same server listening twice.
|
|
42
|
+
*
|
|
43
|
+
* @internal
|
|
44
|
+
*/
|
|
45
|
+
export type _VirtualServer = {
|
|
46
|
+
readonly listener: _VirtualListener;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* A server that is listening, and the address it took.
|
|
50
|
+
*
|
|
51
|
+
* The *server* is what is recorded, not its listener: two servers built from one
|
|
52
|
+
* listener are two servers, and only the second `listen` on the same **server**
|
|
53
|
+
* is the one Node refuses as already listening.
|
|
54
|
+
*
|
|
55
|
+
* @internal
|
|
56
|
+
*/
|
|
57
|
+
export type _Binding = {
|
|
58
|
+
/**
|
|
59
|
+
* `host:port`, with the host lower-cased — a name is case-insensitive, and
|
|
60
|
+
* so is the hexadecimal of an IPv6 literal, so `LOCALHOST` and `localhost`
|
|
61
|
+
* are one address here as they are on a host.
|
|
62
|
+
*/
|
|
63
|
+
readonly address: string;
|
|
64
|
+
readonly server: _VirtualServer;
|
|
65
|
+
};
|
|
23
66
|
export type State = {
|
|
24
67
|
stdout: string;
|
|
25
68
|
stderr: string;
|
|
@@ -36,4 +79,17 @@ export type State = {
|
|
|
36
79
|
};
|
|
37
80
|
/** Monotonically increasing counter returned by `randomInt`; starts at 0. */
|
|
38
81
|
randomNext: number;
|
|
82
|
+
/**
|
|
83
|
+
* What is listening, oldest first. An address appears once: a second
|
|
84
|
+
* `listen` on one that is taken fails, as it does on a host.
|
|
85
|
+
*/
|
|
86
|
+
listening: readonly _Binding[];
|
|
87
|
+
/**
|
|
88
|
+
* The requests a fixture queues for the server to answer. `listen` delivers
|
|
89
|
+
* every one of them to the {@link _VirtualListener} its handle carries, and
|
|
90
|
+
* empties the queue — the virtual counterpart of accepting connections.
|
|
91
|
+
*/
|
|
92
|
+
requests: readonly IncomingMessage[];
|
|
93
|
+
/** What the listener answered, oldest first. */
|
|
94
|
+
responses: readonly ServerResponse[];
|
|
39
95
|
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Proofs for the browser runner.
|
|
3
|
+
*
|
|
4
|
+
* The runner reaches the page only through the root element it is handed, so
|
|
5
|
+
* the DOM stand-in below is enough to drive every rendering branch from Node —
|
|
6
|
+
* no headless browser, and no global `window`/`document` for these proofs to
|
|
7
|
+
* install and unset.
|
|
8
|
+
*/
|
|
9
|
+
export type _Element = {
|
|
10
|
+
readonly tag: string;
|
|
11
|
+
readonly attributes: Map<string, string>;
|
|
12
|
+
readonly ownerDocument: _Document;
|
|
13
|
+
textContent: string;
|
|
14
|
+
children: readonly _Element[];
|
|
15
|
+
readonly setAttribute: (name: string, value: string) => void;
|
|
16
|
+
readonly querySelector: (selector: string) => _Element | null;
|
|
17
|
+
readonly replaceChildren: (...nodes: readonly _Element[]) => void;
|
|
18
|
+
readonly append: (node: _Element) => void;
|
|
19
|
+
};
|
|
20
|
+
export type _Document = {
|
|
21
|
+
defaultView: _View | null;
|
|
22
|
+
readonly createElement: (tag: string) => _Element;
|
|
23
|
+
};
|
|
24
|
+
export type _View = {
|
|
25
|
+
events: readonly CustomEvent[];
|
|
26
|
+
readonly dispatchEvent: (event: Event) => boolean;
|
|
27
|
+
fjsBrowserTestReport?: Promise<unknown>;
|
|
28
|
+
};
|
|
29
|
+
export declare const proof: {
|
|
30
|
+
namedThrow: () => Promise<void>;
|
|
31
|
+
path: () => Promise<void>;
|
|
32
|
+
arbitraryThrow: () => Promise<void>;
|
|
33
|
+
errorFields: () => Promise<void>;
|
|
34
|
+
errorAccessorThrows: () => Promise<void>;
|
|
35
|
+
revokedErrorProxy: () => Promise<void>;
|
|
36
|
+
crossRealmError: () => Promise<void>;
|
|
37
|
+
errorWithoutStack: () => Promise<void>;
|
|
38
|
+
expectedThrow: () => Promise<void>;
|
|
39
|
+
crossRealmPromise: () => Promise<void>;
|
|
40
|
+
spoofedPromiseTag: () => Promise<void>;
|
|
41
|
+
frozenPromiseTag: () => Promise<void>;
|
|
42
|
+
exportedTreeThrows: () => Promise<void>;
|
|
43
|
+
returnedTreeThrows: () => Promise<void>;
|
|
44
|
+
speciesResultIsNotAPromise: () => Promise<void>;
|
|
45
|
+
reportingThrows: () => Promise<void>;
|
|
46
|
+
thenIsATestName: () => Promise<void>;
|
|
47
|
+
batches: () => Promise<void>;
|
|
48
|
+
render: () => Promise<void>;
|
|
49
|
+
renderWithoutView: () => Promise<void>;
|
|
50
|
+
renderReport: () => void;
|
|
51
|
+
sources: () => Promise<void>;
|
|
52
|
+
sourcesProgress: () => Promise<void>;
|
|
53
|
+
sourcesImporterThrows: () => Promise<void>;
|
|
54
|
+
sourcesLoadFailure: () => Promise<void>;
|
|
55
|
+
};
|