@abide/abide 0.123.1 → 0.123.3
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/CHANGELOG.md +33 -0
- package/bin/abide.ts +36 -65
- package/docs/REGISTRY.md +2 -2
- package/docs/RULEBOOK.md +2 -2
- package/docs/abide.md +2 -2
- package/package.json +1 -1
- package/src/compiler/Diagnostics.ts +198 -0
- package/src/compiler/KeptChecker.ts +97 -0
- package/src/compiler/Lowering.ts +1 -1
- package/src/compiler/PositionedRefusal.ts +1 -1
- package/src/compiler/TypeSession.ts +62 -85
- package/src/compiler/appLayout.ts +28 -20
- package/src/compiler/argsDefaultsIn.ts +1 -1
- package/src/compiler/checkApp.ts +64 -68
- package/src/compiler/checkedShadow.ts +1 -1
- package/src/compiler/compileApp.ts +101 -70
- package/src/compiler/declaredExports.ts +2 -4
- package/src/compiler/emitClient.ts +1 -1
- package/src/compiler/formatAbide.ts +2 -2
- package/src/compiler/handlersUnder.ts +17 -29
- package/src/compiler/lineOf.ts +0 -19
- package/src/compiler/matchersOf.ts +3 -3
- package/src/compiler/mountsOf.ts +3 -3
- package/src/compiler/readSources.ts +16 -18
- package/src/compiler/readsIn.ts +1 -1
- package/src/compiler/serveLanguage.ts +23 -57
- package/src/compiler/sharedModuleWrites.ts +1 -1
- package/src/compiler/templateWarnings.ts +1 -2
- package/src/compiler/trackingWarnings.ts +1 -1
- package/src/server/CLIENT_BUILD.ts +4 -32
- package/src/server/RESUME_MILLISECONDS.ts +2 -0
- package/src/server/answerRequest.ts +11 -300
- package/src/server/answerRpc.ts +12 -54
- package/src/server/answerSocket.ts +8 -35
- package/src/server/appBuild.ts +377 -0
- package/src/server/appPlugins.ts +15 -17
- package/src/server/appTree.ts +6 -5
- package/src/server/argsContract.ts +209 -0
- package/src/server/bridgeRelay.ts +153 -65
- package/src/server/claudeCode.ts +30 -127
- package/src/server/config.ts +6 -7
- package/src/server/createApp.ts +30 -64
- package/src/server/devWorker.ts +11 -15
- package/src/server/handlerFileMount.ts +40 -0
- package/src/server/index.ts +2 -2
- package/src/server/packageDirectory.ts +14 -0
- package/src/server/preload.ts +3 -2
- package/src/server/principal.ts +4 -3
- package/src/server/remote.ts +1 -1
- package/src/server/runCompiled.ts +1 -1
- package/src/server/runDesktop.ts +1 -1
- package/src/server/seedBuffer.ts +1 -1
- package/src/server/serveDev.ts +24 -58
- package/src/server/servedApp.ts +390 -0
- package/src/server/signInChanged.ts +20 -0
- package/src/server/surfaceOf.ts +2 -1
- package/src/server/versionOf.ts +6 -15
- package/src/shared/applyPatch.ts +1 -1
- package/src/shared/muxFrames.ts +1 -1
- package/src/shared/reactive/Keyed.ts +1 -1
- package/src/shared/reactive/Reactive.ts +21 -8
- package/src/shared/reactive/argsContract.ts +131 -4
- package/src/shared/reactive/index.ts +1 -0
- package/src/shared/reactive/patchLog.ts +79 -0
- package/src/shared/reactive/patchMarks.ts +34 -0
- package/src/shared/reactive/recordWrites.ts +11 -14
- package/src/shared/reactive/share.ts +4 -3
- package/src/shared/rpc.ts +12 -19
- package/src/shared/validateJson.ts +14 -3
- package/src/ui/ArmRegion.ts +10 -4
- package/src/ui/Level.ts +11 -20
- package/src/ui/LevelChain.ts +272 -0
- package/src/ui/Router.ts +54 -245
- package/src/ui/Scope.ts +17 -3
- package/src/ui/TryRegion.ts +22 -23
- package/src/ui/bindSpread.ts +2 -4
- package/src/ui/listenForRoutes.ts +3 -10
- package/src/ui/outboxQueue.ts +21 -3
- package/src/ui/reconcile.ts +4 -6
- package/src/ui/routing.ts +2 -1
- package/src/ui/rows.ts +2 -2
- package/src/ui/setAttribute.ts +12 -3
- package/src/ui/socketWire.ts +26 -11
- package/src/compiler/Finding.ts +0 -2
- package/src/compiler/Refusals.ts +0 -65
- package/src/compiler/formatDiagnosis.ts +0 -20
- package/src/server/appDirectories.ts +0 -9
- package/src/server/argsFor.ts +0 -39
- package/src/server/buildDirectory.ts +0 -10
- package/src/server/coerceArgs.ts +0 -109
- package/src/server/derivedArgs.ts +0 -95
- package/src/server/runBuild.ts +0 -24
- package/src/server/serveBuild.ts +0 -144
- package/src/server/testBuild.ts +0 -220
- package/src/shared/reactive/FOLLOWED.ts +0 -8
- package/src/shared/reactive/PATCHES.ts +0 -11
- package/src/shared/reactive/PATCH_SINKS.ts +0 -26
- package/src/shared/reactive/canonicalKey.ts +0 -95
package/src/server/testBuild.ts
DELETED
|
@@ -1,220 +0,0 @@
|
|
|
1
|
-
// 42.16 — UNDER `bun test`, `createApp` BUILDS THE APP rather than reading whatever `abide build` last
|
|
2
|
-
// left, ONCE PER STATE OF ITS SOURCES: the build's directory is named after a hash of what made it,
|
|
3
|
-
// so a build either matches the sources or does not exist, and every test process — every
|
|
4
|
-
// `--parallel` worker, every spawned child — reuses the one that does. Eight workers each building
|
|
5
|
-
// their own copy of a 1.3 s build waited 7–10 s apiece.
|
|
6
|
-
|
|
7
|
-
import { mkdir, rename, rm, utimes, writeFile } from 'node:fs/promises' // bun exposes no directory creation, rename, removal, mtime write or exclusive create of its own
|
|
8
|
-
import { dirname } from 'node:path' // bun exposes no path helpers of its own
|
|
9
|
-
|
|
10
|
-
import { namesUnder } from './namesUnder.ts'
|
|
11
|
-
import { runBuild } from './runBuild.ts'
|
|
12
|
-
|
|
13
|
-
// The framework's own source is part of what made a build: a compiler change is a new build.
|
|
14
|
-
const FRAMEWORK = Bun.fileURLToPath(new URL('../..', import.meta.url))
|
|
15
|
-
const POLL_MS = 50
|
|
16
|
-
// A build no test process has reused for this long is swept by the next one that builds.
|
|
17
|
-
const UNUSED_FOR_MS = 24 * 60 * 60 * 1000
|
|
18
|
-
|
|
19
|
-
// One lookup per app per test process, however many files in it call `createApp`.
|
|
20
|
-
const TEST_BUILDS = new Map<string, Promise<string>>()
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* The build of the app under `root` for its sources as they stand, made if no process has made it.
|
|
24
|
-
*
|
|
25
|
-
* UNDER `node_modules`, so a bare import in the built modules resolves as it does from the app, and
|
|
26
|
-
* so it is ignored wherever `node_modules` is.
|
|
27
|
-
*/
|
|
28
|
-
export function testBuild(root: string): Promise<string> {
|
|
29
|
-
let built = TEST_BUILDS.get(root)
|
|
30
|
-
if (built === undefined) {
|
|
31
|
-
built = sourceHash(root).then((key) =>
|
|
32
|
-
sharedBuild(
|
|
33
|
-
`${dirname(root)}/node_modules/.cache/abide`,
|
|
34
|
-
`build-${key}`,
|
|
35
|
-
(out) => runBuild(root, out),
|
|
36
|
-
),
|
|
37
|
-
)
|
|
38
|
-
TEST_BUILDS.set(root, built)
|
|
39
|
-
}
|
|
40
|
-
return built
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* `builds/key`, made by `build` in exactly one process however many ask at once.
|
|
45
|
-
*
|
|
46
|
-
* THE FIRST TO CREATE `key.lock` BUILDS — an exclusive create is atomic, so one process wins — into
|
|
47
|
-
* a staging directory renamed into place, so nothing reads a half-written build. The rest wait for
|
|
48
|
-
* it, and take the lock over if the process holding it has ended.
|
|
49
|
-
*/
|
|
50
|
-
export async function sharedBuild(
|
|
51
|
-
builds: string,
|
|
52
|
-
key: string,
|
|
53
|
-
build: (out: string) => Promise<void>,
|
|
54
|
-
): Promise<string> {
|
|
55
|
-
const out = `${builds}/${key}`
|
|
56
|
-
const lock = `${out}.lock`
|
|
57
|
-
await mkdir(builds, { recursive: true })
|
|
58
|
-
for (;;) {
|
|
59
|
-
if (await isDirectory(out)) {
|
|
60
|
-
const now = new Date()
|
|
61
|
-
await utimes(out, now, now)
|
|
62
|
-
return out
|
|
63
|
-
}
|
|
64
|
-
if (await claim(lock)) {
|
|
65
|
-
try {
|
|
66
|
-
if (!(await isDirectory(out))) await buildInto(out, build)
|
|
67
|
-
} finally {
|
|
68
|
-
await rm(lock, { force: true })
|
|
69
|
-
}
|
|
70
|
-
await sweep(builds, key)
|
|
71
|
-
return out
|
|
72
|
-
}
|
|
73
|
-
if (await holderEnded(lock)) await rm(lock, { force: true })
|
|
74
|
-
else await Bun.sleep(POLL_MS)
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
async function buildInto(
|
|
79
|
-
out: string,
|
|
80
|
-
build: (out: string) => Promise<void>,
|
|
81
|
-
): Promise<void> {
|
|
82
|
-
const staging = `${out}.tmp-${process.pid}`
|
|
83
|
-
await rm(staging, { recursive: true, force: true })
|
|
84
|
-
try {
|
|
85
|
-
await build(staging)
|
|
86
|
-
await rename(staging, out)
|
|
87
|
-
} catch (thrown) {
|
|
88
|
-
await rm(staging, { recursive: true, force: true })
|
|
89
|
-
// TWO HOLDERS, where one took over a lock the other had just taken over: the first rename
|
|
90
|
-
// stands, and it is the same build.
|
|
91
|
-
if (await isDirectory(out)) return
|
|
92
|
-
throw thrown
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* A hash of everything a build of the app under `root` is made from.
|
|
98
|
-
*
|
|
99
|
-
* The app's files, the package and lockfile above it, the framework's own source, and `root`
|
|
100
|
-
* itself, since a build carries the paths it was made at.
|
|
101
|
-
*/
|
|
102
|
-
export async function sourceHash(root: string): Promise<string> {
|
|
103
|
-
const app = dirname(root)
|
|
104
|
-
const paths = [
|
|
105
|
-
...filesUnder(root),
|
|
106
|
-
...filesUnder(`${FRAMEWORK}src`),
|
|
107
|
-
...filesUnder(`${FRAMEWORK}bin`),
|
|
108
|
-
`${app}/package.json`,
|
|
109
|
-
`${app}/tsconfig.json`,
|
|
110
|
-
]
|
|
111
|
-
const lockfile = await lockfileAbove(app)
|
|
112
|
-
if (lockfile !== undefined) paths.push(lockfile)
|
|
113
|
-
const contents = await Promise.all(
|
|
114
|
-
paths.map((path) =>
|
|
115
|
-
Bun.file(path)
|
|
116
|
-
.bytes()
|
|
117
|
-
.catch(() => undefined),
|
|
118
|
-
),
|
|
119
|
-
)
|
|
120
|
-
const hasher = new Bun.CryptoHasher('sha256')
|
|
121
|
-
hasher.update(root)
|
|
122
|
-
for (let at = 0; at < paths.length; at += 1) {
|
|
123
|
-
const bytes = contents[at]
|
|
124
|
-
if (bytes === undefined) continue
|
|
125
|
-
hasher.update(paths[at] as string)
|
|
126
|
-
hasher.update(bytes)
|
|
127
|
-
}
|
|
128
|
-
return hasher.digest('hex').slice(0, 16)
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Sorted, so the hash does not depend on the order a directory lists in. Dot entries and
|
|
132
|
-
// `node_modules` are skipped: neither is the app's source, and the builds live in one.
|
|
133
|
-
function filesUnder(directory: string): string[] {
|
|
134
|
-
const found: string[] = []
|
|
135
|
-
for (const name of namesUnder(directory))
|
|
136
|
-
if (!`/${name}`.includes('/node_modules/'))
|
|
137
|
-
found.push(`${directory}/${name}`)
|
|
138
|
-
return found.sort()
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
async function lockfileAbove(directory: string): Promise<string | undefined> {
|
|
142
|
-
for (let at = directory; ; at = dirname(at)) {
|
|
143
|
-
for (const name of ['bun.lock', 'bun.lockb'])
|
|
144
|
-
if (await Bun.file(`${at}/${name}`).exists()) return `${at}/${name}`
|
|
145
|
-
if (dirname(at) === at) return undefined
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
async function claim(lock: string): Promise<boolean> {
|
|
150
|
-
try {
|
|
151
|
-
await writeFile(lock, String(process.pid), { flag: 'wx' })
|
|
152
|
-
return true
|
|
153
|
-
} catch (thrown) {
|
|
154
|
-
if ((thrown as { code?: string }).code === 'EEXIST') return false
|
|
155
|
-
throw thrown
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
async function holderEnded(lock: string): Promise<boolean> {
|
|
160
|
-
const text = await Bun.file(lock)
|
|
161
|
-
.text()
|
|
162
|
-
.catch(() => undefined)
|
|
163
|
-
// Gone is not ended: the holder finished, and the next pass finds its build.
|
|
164
|
-
if (text === undefined) return false
|
|
165
|
-
// Empty is a holder between creating the lock and writing its pid.
|
|
166
|
-
return text !== '' && !isRunning(Number(text))
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/** Removes builds unused for a day, and what an ended process left half-made. */
|
|
170
|
-
async function sweep(builds: string, kept: string): Promise<void> {
|
|
171
|
-
const names = new Bun.Glob('*').scanSync({ cwd: builds, onlyFiles: false })
|
|
172
|
-
const removals: Promise<void>[] = []
|
|
173
|
-
const now = Date.now()
|
|
174
|
-
for (const name of names) {
|
|
175
|
-
if (name === kept) continue
|
|
176
|
-
const staged = /\.tmp-(\d+)$/.exec(name)
|
|
177
|
-
if (staged !== null) {
|
|
178
|
-
if (!isRunning(Number(staged[1])))
|
|
179
|
-
removals.push(
|
|
180
|
-
rm(`${builds}/${name}`, { recursive: true, force: true }),
|
|
181
|
-
)
|
|
182
|
-
continue
|
|
183
|
-
}
|
|
184
|
-
if (name.endsWith('.lock')) continue
|
|
185
|
-
removals.push(
|
|
186
|
-
Bun.file(`${builds}/${name}`)
|
|
187
|
-
.stat()
|
|
188
|
-
.then((found) =>
|
|
189
|
-
now - found.mtimeMs > UNUSED_FOR_MS
|
|
190
|
-
? rm(`${builds}/${name}`, {
|
|
191
|
-
recursive: true,
|
|
192
|
-
force: true,
|
|
193
|
-
})
|
|
194
|
-
: undefined,
|
|
195
|
-
)
|
|
196
|
-
.catch(() => undefined),
|
|
197
|
-
)
|
|
198
|
-
}
|
|
199
|
-
await Promise.all(removals)
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
async function isDirectory(path: string): Promise<boolean> {
|
|
203
|
-
return (
|
|
204
|
-
(
|
|
205
|
-
await Bun.file(path)
|
|
206
|
-
.stat()
|
|
207
|
-
.catch(() => undefined)
|
|
208
|
-
)?.isDirectory() === true
|
|
209
|
-
)
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
function isRunning(pid: number): boolean {
|
|
213
|
-
try {
|
|
214
|
-
process.kill(pid, 0)
|
|
215
|
-
return true
|
|
216
|
-
} catch (thrown) {
|
|
217
|
-
// EPERM is a process that exists and is someone else's.
|
|
218
|
-
return (thrown as { code?: string }).code === 'EPERM'
|
|
219
|
-
}
|
|
220
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* What an invalidate of a value the server feeds over the mux sends on, by the cell its past is on.
|
|
3
|
-
*
|
|
4
|
-
* 7.10. The core calls these for every cell it invalidates, however the trigger was spelled — the
|
|
5
|
-
* value's own, a `Keyed`'s, an `Rpc`'s or a tag's — and only a value a browser follows has any, so
|
|
6
|
-
* every other invalidate is one miss. A leaf, for `PATCH_SINKS`'s reason.
|
|
7
|
-
*/
|
|
8
|
-
export const FOLLOWED = new WeakMap<object, Set<() => void>>()
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Which objects an `s.patch` wrote to, and when.
|
|
3
|
-
*
|
|
4
|
-
* 32.33. `epoch` moves once per patch and `written` holds the epoch an object was last written at,
|
|
5
|
-
* so a row that took its item at epoch `e` is stale exactly where its item's entry is past `e`. A leaf, because
|
|
6
|
-
* `reconcile` reads it from the `@abide/abide/runtime` barrel.
|
|
7
|
-
*/
|
|
8
|
-
export const PATCHES: { epoch: number; written: WeakMap<object, number> } = {
|
|
9
|
-
epoch: 0,
|
|
10
|
-
written: new WeakMap(),
|
|
11
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* One JSON Patch operation (RFC 6902), its `path` and `from` JSON Pointers (RFC 6901).
|
|
3
|
-
*
|
|
4
|
-
* A patch abide records is `add`, `remove` and `replace` alone; `move`, `copy` and `test` are
|
|
5
|
-
* applied where a client sends them.
|
|
6
|
-
*/
|
|
7
|
-
export type PatchOp =
|
|
8
|
-
| { op: 'add'; path: string; value: unknown }
|
|
9
|
-
| { op: 'remove'; path: string }
|
|
10
|
-
| { op: 'replace'; path: string; value: unknown }
|
|
11
|
-
| { op: 'move'; from: string; path: string }
|
|
12
|
-
| { op: 'copy'; from: string; path: string }
|
|
13
|
-
| { op: 'test'; path: string; value: unknown }
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Where a patch's writes are kept as data, by the cell they were made to.
|
|
17
|
-
*
|
|
18
|
-
* 18.11. Only a value the server follows over the mux has one, so every other patch records
|
|
19
|
-
* nothing but its marks (32.33). `lost` where a write could not be kept as data, and then the
|
|
20
|
-
* change is sent as the value (18.13). `fed` is how many followers the server is feeding it, and
|
|
21
|
-
* the sink goes with the last. A leaf, because the reactive core writes it and the server reads it.
|
|
22
|
-
*/
|
|
23
|
-
export const PATCH_SINKS = new WeakMap<
|
|
24
|
-
object,
|
|
25
|
-
{ ops: PatchOp[]; lost: boolean; fed: number }
|
|
26
|
-
>()
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import { putOwn } from '../putOwn.ts'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* The entry key for an `Args` value: its canonical wire form, as a string.
|
|
5
|
-
*
|
|
6
|
-
* RULEBOOK 11.24: members sorted by name, `undefined` members dropped, `Date` written ISO.
|
|
7
|
-
*
|
|
8
|
-
* 11.26: the key is taken BEFORE any gate runs, because a browser holds no copy of the gate — a
|
|
9
|
-
* key taken after normalizing is computed one way on a server and another in a browser, and every
|
|
10
|
-
* handler with a normalizing schema would miss its seed buffer and re-issue the call on hydration.
|
|
11
|
-
*
|
|
12
|
-
* 11.64: WHAT THE WIRE FORM CANNOT WRITE IS REFUSED AT THE REACH. `JSON.stringify` does not fail
|
|
13
|
-
* on any of it — a `Set`, a `Map` and a class instance each have no own enumerable keys and
|
|
14
|
-
* canonicalize to `{}`, and a non-finite number is written `null` — so two different reaches
|
|
15
|
-
* produced one key and 1.8 joined the second to the first's standing work. The value was right
|
|
16
|
-
* for the first caller and wrong for every other one, with nothing to see.
|
|
17
|
-
*/
|
|
18
|
-
export function canonicalKey(args: unknown): string {
|
|
19
|
-
if (args === undefined) return ''
|
|
20
|
-
return JSON.stringify(canonical(args, []))
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// THE PATH IS A STACK OF STEPS, joined only when a refusal names it. Built as a string per member
|
|
24
|
-
// it was a concatenation for every element and every nested key on every reach, for a message the
|
|
25
|
-
// success path never reads.
|
|
26
|
-
type Steps = (string | number)[]
|
|
27
|
-
|
|
28
|
-
function canonical(value: unknown, path: Steps): unknown {
|
|
29
|
-
if (value === null) return value
|
|
30
|
-
const kind = typeof value
|
|
31
|
-
if (kind === 'number') {
|
|
32
|
-
if (!Number.isFinite(value)) throw outside(String(value), path)
|
|
33
|
-
return value
|
|
34
|
-
}
|
|
35
|
-
if (kind !== 'object') {
|
|
36
|
-
if (kind === 'boolean' || kind === 'string') return value
|
|
37
|
-
// `undefined` reaches here from an ARRAY element alone — a member is dropped above.
|
|
38
|
-
throw outside(kind === 'undefined' ? 'undefined' : `a ${kind}`, path)
|
|
39
|
-
}
|
|
40
|
-
if (value instanceof Date) return value.toISOString()
|
|
41
|
-
if (Array.isArray(value)) {
|
|
42
|
-
const out: unknown[] = []
|
|
43
|
-
for (let index = 0; index < value.length; index += 1) {
|
|
44
|
-
path.push(index)
|
|
45
|
-
out.push(canonical(value[index], path))
|
|
46
|
-
path.pop()
|
|
47
|
-
}
|
|
48
|
-
return out
|
|
49
|
-
}
|
|
50
|
-
const prototype = Object.getPrototypeOf(value)
|
|
51
|
-
if (prototype !== Object.prototype && prototype !== null)
|
|
52
|
-
throw outside(named(value as object), path)
|
|
53
|
-
const source = value as Record<string, unknown>
|
|
54
|
-
const keys = Object.keys(source).sort()
|
|
55
|
-
// AN ORDINARY OBJECT, AND `putOwn` FOR THE ONE KEY THAT NEEDS IT: `__proto__` is a SETTER on
|
|
56
|
-
// `Object.prototype`, so `out[key] = …` for that key creates no own property and
|
|
57
|
-
// `JSON.stringify` never sees it — `{id: 1, __proto__: {role: 'admin'}}` and `{id: 1}` then
|
|
58
|
-
// canonicalize to one string, and 1.8 joins the second reach to the first's standing work.
|
|
59
|
-
// `Object.create(null)` closed that too, and V8 builds it in DICTIONARY mode: the shipped key
|
|
60
|
-
// took 49-56% of the time without it there, and a standing keyed reach 53%, where JSC reads
|
|
61
|
-
// level to 110% (`abide/bench/sharedCases.ts`, two runs per engine).
|
|
62
|
-
const out: Record<string, unknown> = {}
|
|
63
|
-
for (let index = 0; index < keys.length; index += 1) {
|
|
64
|
-
const key = keys[index] as string
|
|
65
|
-
const member = source[key]
|
|
66
|
-
if (member === undefined) continue
|
|
67
|
-
path.push(key)
|
|
68
|
-
putOwn(out, key, canonical(member, path))
|
|
69
|
-
path.pop()
|
|
70
|
-
}
|
|
71
|
-
return out
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function named(value: object): string {
|
|
75
|
-
const from = value.constructor as { name?: unknown } | undefined
|
|
76
|
-
return typeof from?.name === 'string' && from.name !== ''
|
|
77
|
-
? `a ${from.name}`
|
|
78
|
-
: 'a class instance'
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function outside(what: string, steps: Steps): Error {
|
|
82
|
-
let path = ''
|
|
83
|
-
for (const step of steps)
|
|
84
|
-
path +=
|
|
85
|
-
typeof step === 'number'
|
|
86
|
-
? `[${step}]`
|
|
87
|
-
: path === ''
|
|
88
|
-
? step
|
|
89
|
-
: `.${step}`
|
|
90
|
-
return new Error(
|
|
91
|
-
`An Args key cannot carry ${what} at ${
|
|
92
|
-
path === '' ? 'the arguments themselves' : `"${path}"`
|
|
93
|
-
}: the canonical wire form writes it as another entry's key (RULEBOOK 11.64).`,
|
|
94
|
-
)
|
|
95
|
-
}
|