@abide/abide 0.117.2 → 0.119.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/CHANGELOG.md +22 -0
- package/bin/abide.ts +74 -11
- package/docs/REGISTRY.md +18 -15
- package/docs/RULEBOOK.md +71 -10
- package/docs/abide.md +584 -274
- package/package.json +1 -1
- package/scaffold/AGENTS.md +1 -1
- package/scaffold/src/getGreeting.test.ts +2 -1
- package/scaffold/src/index.abide +5 -2
- package/scaffold/src/index.test.ts +2 -0
- package/scaffold/src/layout.abide +6 -6
- package/src/compiler/Lowering.ts +20 -2
- package/src/compiler/PositionTable.ts +1 -0
- package/src/compiler/Refusals.ts +1 -0
- package/src/compiler/TypeSession.ts +49 -13
- package/src/compiler/aliasedSymbol.ts +1 -1
- package/src/compiler/ambientReadsIn.ts +1 -0
- package/src/compiler/appLayout.ts +55 -3
- package/src/compiler/appMap.ts +15 -0
- package/src/compiler/argsDefaultsIn.ts +1 -0
- package/src/compiler/argsSchemaOf.ts +1 -0
- package/src/compiler/checkApp.ts +3 -1
- package/src/compiler/checkedShadow.ts +2 -1
- package/src/compiler/classifyRead.ts +1 -0
- package/src/compiler/classifyTemplate.ts +1 -0
- package/src/compiler/compileApp.ts +5 -3
- package/src/compiler/compiledEntry.ts +2 -1
- package/src/compiler/componentGraph.ts +1 -0
- package/src/compiler/declaredExports.ts +4 -2
- package/src/compiler/emitClient.ts +19 -14
- package/src/compiler/emitComponent.ts +1 -0
- package/src/compiler/emitSource.ts +2 -1
- package/src/compiler/formatAbide.ts +857 -0
- package/src/compiler/formatDiagnosis.ts +1 -0
- package/src/compiler/handlersUnder.ts +7 -3
- package/src/compiler/hookCalls.ts +1 -0
- package/src/compiler/lineOf.ts +17 -0
- package/src/compiler/matchersOf.ts +157 -0
- package/src/compiler/mountsOf.ts +5 -0
- package/src/compiler/paramFile.ts +125 -0
- package/src/compiler/parseAbide.ts +48 -28
- package/src/compiler/projectShadow.ts +11 -4
- package/src/compiler/reactiveOptions.ts +1 -0
- package/src/compiler/reactiveTypes.ts +2 -1
- package/src/compiler/readsIn.ts +3 -2
- package/src/compiler/renderMarkup.ts +3 -2
- package/src/compiler/resolverIn.ts +2 -0
- package/src/compiler/sameTemplate.ts +150 -0
- package/src/compiler/scaffold.ts +4 -11
- package/src/compiler/serveLanguage.ts +48 -21
- package/src/compiler/sharedModuleWrites.ts +2 -1
- package/src/compiler/sourceMapOf.ts +1 -0
- package/src/compiler/splitImports.ts +2 -0
- package/src/compiler/templateWarnings.ts +2 -2
- package/src/compiler/trackingWarnings.ts +1 -0
- package/src/compiler/writeDefaultProject.ts +5 -1
- package/src/server/BRIDGE_VERSION.ts +1 -1
- package/src/server/abideModules.ts +2 -0
- package/src/server/answerFailure.ts +1 -0
- package/src/server/answerRequest.ts +57 -19
- package/src/server/answerRpc.ts +6 -2
- package/src/server/answerSocket.ts +2 -1
- package/src/server/answerValue.ts +1 -0
- package/src/server/answerWorker.ts +1 -0
- package/src/server/appTree.ts +18 -9
- package/src/server/argsFor.ts +1 -0
- package/src/server/assistant.ts +2 -1
- package/src/server/banner.ts +2 -0
- package/src/server/bridgeRelay.ts +1 -0
- package/src/server/callerOf.ts +1 -0
- package/src/server/claudeApi.ts +1 -0
- package/src/server/claudeCode.ts +1 -0
- package/src/server/clientBundle.ts +1 -0
- package/src/server/coerceArgs.ts +1 -2
- package/src/server/commands.ts +9 -0
- package/src/server/compressAnswer.ts +2 -1
- package/src/server/config.ts +2 -0
- package/src/server/createApp.ts +4 -2
- package/src/server/crossingOrigin.ts +1 -0
- package/src/server/csp.ts +1 -0
- package/src/server/deferredBuilds.ts +1 -0
- package/src/server/derivedArgs.ts +1 -0
- package/src/server/devWorker.ts +2 -1
- package/src/server/faceOf.ts +2 -7
- package/src/server/generatedSurfaces.ts +79 -7
- package/src/server/hooks.ts +1 -0
- package/src/server/iconAccent.ts +1 -1
- package/src/server/installServer.ts +1 -0
- package/src/server/installSourceMaps.ts +1 -0
- package/src/server/launcherPage.ts +2 -2
- package/src/server/manifestOf.ts +1 -0
- package/src/server/muxUpgrade.ts +1 -0
- package/src/server/nativeMenu.ts +2 -0
- package/src/server/placement.ts +12 -2
- package/src/server/principal.ts +2 -1
- package/src/server/remote.ts +7 -2
- package/src/server/render.ts +3 -3
- package/src/server/renderComponent.ts +1 -0
- package/src/server/reportHealth.ts +1 -0
- package/src/server/requestScope.ts +3 -1
- package/src/server/runDesktop.ts +3 -2
- package/src/server/seedBuffer.ts +1 -0
- package/src/server/serveDev.ts +3 -2
- package/src/server/shell.ts +2 -0
- package/src/server/streamEvent.ts +1 -0
- package/src/server/surfaceOf.ts +6 -1
- package/src/server/testBuild.ts +1 -0
- package/src/server/testMount.ts +1 -0
- package/src/server/trace.ts +2 -0
- package/src/server/withAbideSources.ts +1 -0
- package/src/shared/Attributes.ts +12 -8
- package/src/shared/cookies.ts +1 -1
- package/src/shared/escapeText.ts +1 -1
- package/src/shared/filledPath.ts +16 -10
- package/src/shared/index.ts +1 -5
- package/src/shared/jsonSchema.ts +1 -3
- package/src/shared/matchRoute.ts +16 -4
- package/src/shared/menuBridge.ts +1 -2
- package/src/shared/outbox.ts +1 -1
- package/src/shared/reactive/Keyed.ts +2 -2
- package/src/shared/reactive/Reactive.ts +12 -16
- package/src/shared/reactive/index.ts +3 -4
- package/src/shared/reactive/isReactive.ts +1 -1
- package/src/shared/reactive/scoped.ts +2 -2
- package/src/shared/reactive/share.ts +1 -1
- package/src/shared/reactive/watch.ts +4 -4
- package/src/shared/refuse.ts +1 -1
- package/src/shared/routePattern.ts +66 -14
- package/src/shared/rpc.ts +8 -11
- package/src/shared/url.ts +24 -7
- package/src/shared/validateJson.ts +1 -1
- package/src/ui/ArmRegion.ts +1 -0
- package/src/ui/Level.ts +1 -0
- package/src/ui/List.ts +1 -0
- package/src/ui/RawRun.ts +1 -0
- package/src/ui/Router.ts +1 -0
- package/src/ui/TryRegion.ts +1 -0
- package/src/ui/adoptWalk.ts +2 -1
- package/src/ui/bindElement.ts +1 -0
- package/src/ui/bindFiles.ts +1 -0
- package/src/ui/bindGroup.ts +1 -0
- package/src/ui/bindProperty.ts +1 -0
- package/src/ui/bindSpread.ts +1 -0
- package/src/ui/index.ts +1 -6
- package/src/ui/outboxQueue.ts +1 -1
- package/src/ui/parseMarkup.ts +2 -1
- package/src/ui/reconcile.ts +2 -1
- package/src/ui/reconcileHead.ts +1 -0
- package/src/ui/regions.ts +2 -1
- package/src/ui/registerWorker.ts +1 -0
- package/src/ui/rows.ts +1 -0
- package/src/ui/setMarkup.ts +1 -0
- package/src/ui/setText.ts +1 -0
- package/src/ui/socketWire.ts +4 -3
- package/src/ui/wire.ts +2 -1
- /package/scaffold/src/{getGreeting.ts → getGreeting.handler.ts} +0 -0
package/package.json
CHANGED
package/scaffold/AGENTS.md
CHANGED
|
@@ -36,7 +36,7 @@ before writing code, rather than guessing from Svelte, Vue or React. It installs
|
|
|
36
36
|
- A file's path below `src/` is its address. `src/invoices/index.abide` answers `/invoices`,
|
|
37
37
|
`src/invoices/[id].abide` answers `/invoices/123`, and `layout.abide` wraps the pages below it;
|
|
38
38
|
the outermost one writes `<html>`. Run `bun run routes` to see every address and the file behind it.
|
|
39
|
-
- A
|
|
39
|
+
- A `<name>.handler.ts` beside the pages is a handler: `src/invoices/getInvoice.handler.ts` holds
|
|
40
40
|
`export default GET(…)` and answers `/invoices/getInvoice`. It is the app's public API, for pages,
|
|
41
41
|
scripts, the CLI and MCP alike.
|
|
42
42
|
- A `.abide` cannot export. A `<script server>` in one holds that file's private handlers
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { expect, test } from 'bun:test'
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
import getGreeting from './getGreeting.handler.ts'
|
|
3
4
|
|
|
4
5
|
test('the handler answers with the name it is given', async () => {
|
|
5
6
|
expect(await getGreeting({ name: 'Ada' })).toBe('Hello, Ada')
|
package/scaffold/src/index.abide
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script>
|
|
2
2
|
import { reactive } from '@abide/abide'
|
|
3
|
-
|
|
3
|
+
|
|
4
|
+
import getGreeting from '#src/getGreeting.handler.ts'
|
|
4
5
|
|
|
5
6
|
let count = reactive(0)
|
|
6
7
|
const greeting = reactive(() => getGreeting({ name: 'world' }))
|
|
@@ -11,5 +12,7 @@ const greeting = reactive(() => getGreeting({ name: 'world' }))
|
|
|
11
12
|
<main>
|
|
12
13
|
<h1>{greeting}</h1>
|
|
13
14
|
<p>Clicked {count} times.</p>
|
|
14
|
-
<button onclick={() => {
|
|
15
|
+
<button onclick={() => {
|
|
16
|
+
count = count + 1
|
|
17
|
+
}}>Click</button>
|
|
15
18
|
</main>
|
|
@@ -8,11 +8,11 @@ onStart(async (start) => {
|
|
|
8
8
|
</script>
|
|
9
9
|
|
|
10
10
|
<html lang="en">
|
|
11
|
-
<head>
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
</head>
|
|
11
|
+
<head>
|
|
12
|
+
<meta charset="utf-8" />
|
|
13
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
14
|
+
</head>
|
|
15
15
|
<body>
|
|
16
|
-
|
|
17
|
-
</body>
|
|
16
|
+
<slot />
|
|
17
|
+
</body>
|
|
18
18
|
</html>
|
package/src/compiler/Lowering.ts
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { SyntaxKind } from 'typescript/unstable/ast'
|
|
2
|
+
|
|
2
3
|
import type { CheckerAnswer } from './CheckerAnswer.ts'
|
|
3
4
|
import type { Read } from './classifyRead.ts'
|
|
4
5
|
import { holdsSlot } from './classifyTemplate.ts'
|
|
5
6
|
import type { Finding } from './Finding.ts'
|
|
6
|
-
import type
|
|
7
|
+
import { type AbideFile, scriptBodies } from './parseAbide.ts'
|
|
7
8
|
import {
|
|
8
9
|
type Origin,
|
|
9
10
|
type Projection,
|
|
10
11
|
projectShadow,
|
|
11
12
|
type Span,
|
|
12
13
|
} from './projectShadow.ts'
|
|
14
|
+
import { importedNames } from './splitImports.ts'
|
|
13
15
|
import type { InlineComponent, Setup, TemplateNode } from './TemplateNode.ts'
|
|
14
16
|
import { nesting, type Token, tokenize } from './tokenize.ts'
|
|
15
17
|
|
|
@@ -82,6 +84,11 @@ export class Lowering {
|
|
|
82
84
|
* rather than called, whose forwarding arrow takes an untyped rest.
|
|
83
85
|
*/
|
|
84
86
|
readonly checking: boolean
|
|
87
|
+
/**
|
|
88
|
+
* Whether a script imports abide's own `raw`, which a checked lowering then leaves a template's
|
|
89
|
+
* `raw(x)` calling — the same function, and the import read where the author reads it.
|
|
90
|
+
*/
|
|
91
|
+
private readonly importsRaw: boolean
|
|
85
92
|
/**
|
|
86
93
|
* The names a checked lowering read, each with whether it was read through a props accessor.
|
|
87
94
|
*
|
|
@@ -133,6 +140,7 @@ export class Lowering {
|
|
|
133
140
|
this.answer = answer
|
|
134
141
|
this.holdsSlot = holdsSlot(file.template)
|
|
135
142
|
this.checking = checking
|
|
143
|
+
this.importsRaw = importsAbideRaw(file)
|
|
136
144
|
this.chunks = chunks
|
|
137
145
|
this.routed = routed
|
|
138
146
|
}
|
|
@@ -804,7 +812,9 @@ export class Lowering {
|
|
|
804
812
|
// template are the same object. The read exists only where a template expression held
|
|
805
813
|
// the call (`expressionFacts`), so a `<script>`'s `raw` stays the file's.
|
|
806
814
|
case 'raw':
|
|
807
|
-
return this.
|
|
815
|
+
return this.checking && this.importsRaw
|
|
816
|
+
? undefined
|
|
817
|
+
: this.use('raw')
|
|
808
818
|
// 27.13 — the call bound to a name, for the checker to read the defaults' type off.
|
|
809
819
|
case 'config':
|
|
810
820
|
return this.checking
|
|
@@ -897,6 +907,14 @@ export class Lowering {
|
|
|
897
907
|
}
|
|
898
908
|
}
|
|
899
909
|
|
|
910
|
+
function importsAbideRaw(file: AbideFile): boolean {
|
|
911
|
+
for (const body of scriptBodies(file))
|
|
912
|
+
for (const { name, specifier } of importedNames(body))
|
|
913
|
+
if (name === 'raw' && specifier.startsWith('@abide/abide'))
|
|
914
|
+
return true
|
|
915
|
+
return false
|
|
916
|
+
}
|
|
917
|
+
|
|
900
918
|
function isRawCallee(read: Read): boolean {
|
|
901
919
|
return read.shape === 'raw'
|
|
902
920
|
}
|
package/src/compiler/Refusals.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type Dirent, readdirSync, realpathSync, statSync } from 'node:fs' // Bun exposes no sync directory listing or realpath of its own
|
|
2
2
|
import { basename, dirname, join, resolve } from 'node:path' // Bun exposes no path helpers of its own
|
|
3
|
+
|
|
3
4
|
import {
|
|
4
5
|
getTouchingPropertyName,
|
|
5
6
|
type Node,
|
|
@@ -8,7 +9,6 @@ import {
|
|
|
8
9
|
} from 'typescript/unstable/ast'
|
|
9
10
|
import {
|
|
10
11
|
API,
|
|
11
|
-
type Symbol as CheckedSymbol,
|
|
12
12
|
type Checker,
|
|
13
13
|
type CompletionEntry,
|
|
14
14
|
type CompletionInfo,
|
|
@@ -17,30 +17,34 @@ import {
|
|
|
17
17
|
type Project,
|
|
18
18
|
SignatureKind,
|
|
19
19
|
type Snapshot,
|
|
20
|
+
type Symbol as CheckedSymbol,
|
|
20
21
|
SymbolFlags,
|
|
21
22
|
type Type,
|
|
22
23
|
} from 'typescript/unstable/async'
|
|
24
|
+
|
|
23
25
|
import { placeOf } from '#server/placement.ts'
|
|
26
|
+
import { parseRoutePattern } from '#shared/routePattern.ts'
|
|
27
|
+
|
|
24
28
|
import { aliasedSymbol } from './aliasedSymbol.ts'
|
|
25
29
|
import { AMBIENT_SOURCES, ambientReadsIn } from './ambientReadsIn.ts'
|
|
26
|
-
import type { AppLayout, PlacedSource } from './appLayout.ts'
|
|
30
|
+
import type { AppLayout, AppRoute, PlacedSource } from './appLayout.ts'
|
|
27
31
|
import { argsDefaultsIn } from './argsDefaultsIn.ts'
|
|
28
|
-
import { type CheckerAnswer, UNCHECKED } from './CheckerAnswer.ts'
|
|
29
32
|
import {
|
|
33
|
+
checkedShadow,
|
|
30
34
|
COMPONENT_MODULE,
|
|
31
35
|
COMPONENT_STAND_IN,
|
|
32
|
-
checkedShadow,
|
|
33
36
|
moduleConfigShadow,
|
|
34
37
|
} from './checkedShadow.ts'
|
|
38
|
+
import { type CheckerAnswer, UNCHECKED } from './CheckerAnswer.ts'
|
|
35
39
|
import type { Finding } from './Finding.ts'
|
|
36
40
|
import { isIgnored } from './isIgnored.ts'
|
|
37
|
-
import { CHECKED_VALUES } from './Lowering.ts'
|
|
38
41
|
import { diagnosisAt, lineOf } from './lineOf.ts'
|
|
42
|
+
import { CHECKED_VALUES } from './Lowering.ts'
|
|
39
43
|
import type { AbideFile } from './parseAbide.ts'
|
|
40
44
|
import { projectShadow, type Span } from './projectShadow.ts'
|
|
41
45
|
import { type Abide, abideIn, isReactiveType } from './reactiveTypes.ts'
|
|
42
|
-
import type { ParsedSource, ReadSource } from './readSources.ts'
|
|
43
46
|
import { readsIn, type ShadowSpans } from './readsIn.ts'
|
|
47
|
+
import type { ParsedSource, ReadSource } from './readSources.ts'
|
|
44
48
|
import {
|
|
45
49
|
CHECKED_SUFFIX,
|
|
46
50
|
mapForward,
|
|
@@ -974,7 +978,7 @@ export class TypeSession {
|
|
|
974
978
|
Partial<Pick<PlacedSource, 'kind' | 'route'>> & {
|
|
975
979
|
quiet?: boolean
|
|
976
980
|
})[],
|
|
977
|
-
app: Pick<AppLayout, 'root' | 'addresses' | 'handlers'>,
|
|
981
|
+
app: Pick<AppLayout, 'root' | 'addresses' | 'handlers' | 'routes'>,
|
|
978
982
|
scripts = true,
|
|
979
983
|
): Promise<{ diagnoses: Diagnosis[]; refused: Set<string> }> {
|
|
980
984
|
using _held = this.hold()
|
|
@@ -1033,7 +1037,7 @@ export class TypeSession {
|
|
|
1033
1037
|
// module augments one.
|
|
1034
1038
|
this.register(
|
|
1035
1039
|
`${this.canonical(within)}/${ROUTES_DECLARATION}`,
|
|
1036
|
-
routesDeclaration(app.addresses),
|
|
1040
|
+
routesDeclaration(app.addresses, app.routes),
|
|
1037
1041
|
created,
|
|
1038
1042
|
changed,
|
|
1039
1043
|
)
|
|
@@ -1570,13 +1574,46 @@ function isFile(path: string): boolean {
|
|
|
1570
1574
|
const ROUTES_DECLARATION = 'abide-routes.d.ts'
|
|
1571
1575
|
const URL_SOURCE = Bun.fileURLToPath(new URL('../shared/url', import.meta.url))
|
|
1572
1576
|
|
|
1573
|
-
/**
|
|
1574
|
-
|
|
1577
|
+
/**
|
|
1578
|
+
* 30.26 — each of `addresses` as a key of abide's `Routes`, and 23.9, 30.3 — a page route's params
|
|
1579
|
+
* narrowed where its matcher is an alternation of literals, which is the one matcher a type can say.
|
|
1580
|
+
*/
|
|
1581
|
+
function routesDeclaration(
|
|
1582
|
+
addresses: readonly string[],
|
|
1583
|
+
routes: readonly AppRoute[],
|
|
1584
|
+
): string {
|
|
1585
|
+
const narrowed = new Map<string, string>()
|
|
1586
|
+
for (const route of routes) {
|
|
1587
|
+
let members = ''
|
|
1588
|
+
for (const segment of parseRoutePattern(route.pattern)) {
|
|
1589
|
+
if (segment.kind !== 'required' && segment.kind !== 'optional')
|
|
1590
|
+
continue
|
|
1591
|
+
const literals = literalsOf(
|
|
1592
|
+
segment.matcher === undefined
|
|
1593
|
+
? undefined
|
|
1594
|
+
: route.matchers[segment.matcher],
|
|
1595
|
+
)
|
|
1596
|
+
if (literals === undefined) continue
|
|
1597
|
+
members += ` ${JSON.stringify(segment.name)}${segment.kind === 'optional' ? '?' : ''}: ${literals};`
|
|
1598
|
+
}
|
|
1599
|
+
if (members !== '') narrowed.set(route.pattern, `{${members} }`)
|
|
1600
|
+
}
|
|
1575
1601
|
let keys = ''
|
|
1576
1602
|
for (const route of addresses)
|
|
1577
|
-
keys += ` ${JSON.stringify(route)}: true\n`
|
|
1603
|
+
keys += ` ${JSON.stringify(route)}: ${narrowed.get(route) ?? 'true'}\n`
|
|
1578
1604
|
return `export {}\ndeclare module ${JSON.stringify(URL_SOURCE)} {\n interface Routes {\n${keys} }\n}\n`
|
|
1579
1605
|
}
|
|
1606
|
+
|
|
1607
|
+
/** The union of the literals an anchored matcher source alternates between, or `undefined`. */
|
|
1608
|
+
function literalsOf(source: string | undefined): string | undefined {
|
|
1609
|
+
const body = source?.match(/^\^\(\?!\\\.\\\.\?\$\)\(\?:(.*)\)\$$/)?.[1]
|
|
1610
|
+
if (body === undefined || !/^[\w~-]+(?:\|[\w~-]+)*$/.test(body))
|
|
1611
|
+
return undefined
|
|
1612
|
+
return body
|
|
1613
|
+
.split('|')
|
|
1614
|
+
.map((literal) => JSON.stringify(literal))
|
|
1615
|
+
.join(' | ')
|
|
1616
|
+
}
|
|
1580
1617
|
const AMBIENT_DECLARATIONS = `declare module '*.css' {}
|
|
1581
1618
|
declare module '*.md' {
|
|
1582
1619
|
const text: string
|
|
@@ -1769,8 +1806,7 @@ const CATEGORIES = ['warning', 'error', 'suggestion', 'message'] as const
|
|
|
1769
1806
|
function flatten(diagnostic: {
|
|
1770
1807
|
text: string
|
|
1771
1808
|
messageChain?:
|
|
1772
|
-
|
|
1773
|
-
| undefined
|
|
1809
|
+
readonly { text: string; messageChain?: unknown }[] | undefined
|
|
1774
1810
|
}): string {
|
|
1775
1811
|
let message = diagnostic.text
|
|
1776
1812
|
const chain = diagnostic.messageChain ?? []
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { relative as relativeTo } from 'node:path' // bun exposes no path helpers of its own
|
|
2
|
+
|
|
2
3
|
import { appFiles } from '#server/appTree.ts'
|
|
3
4
|
import { type Placement, placement, routeOf } from '#server/placement.ts'
|
|
4
5
|
import {
|
|
@@ -13,13 +14,15 @@ import {
|
|
|
13
14
|
parseRoutePattern,
|
|
14
15
|
type Segment,
|
|
15
16
|
} from '#shared/routePattern.ts'
|
|
17
|
+
|
|
16
18
|
import { abideImportsOf, componentGraph } from './componentGraph.ts'
|
|
17
19
|
import type { HandlerFile } from './handlersUnder.ts'
|
|
18
20
|
import { hookCalls, hooksIn } from './hookCalls.ts'
|
|
21
|
+
import { matchersOf, type MountedTree } from './matchersOf.ts'
|
|
19
22
|
import { mountsOf } from './mountsOf.ts'
|
|
20
23
|
import { type AbideFile, scriptBodies } from './parseAbide.ts'
|
|
21
|
-
import { Refusals } from './Refusals.ts'
|
|
22
24
|
import { type ParsedSource, parseSources } from './readSources.ts'
|
|
25
|
+
import { Refusals } from './Refusals.ts'
|
|
23
26
|
import { resolverIn } from './resolverIn.ts'
|
|
24
27
|
import { attributedImports } from './splitImports.ts'
|
|
25
28
|
import type { Diagnosis } from './TypeSession.ts'
|
|
@@ -49,6 +52,8 @@ export type PlacedHandler = HandlerFile & {
|
|
|
49
52
|
scope: string
|
|
50
53
|
text: string
|
|
51
54
|
hooks: string[]
|
|
55
|
+
/** 23.26 — each matcher its route names, to the anchored source the route tables carry. */
|
|
56
|
+
matchers: Record<string, string>
|
|
52
57
|
}
|
|
53
58
|
|
|
54
59
|
/** A page or an error page and the layouts it renders inside. */
|
|
@@ -64,6 +69,8 @@ export type AppRoute = {
|
|
|
64
69
|
* cannot be declared twice.
|
|
65
70
|
*/
|
|
66
71
|
layouts: string[]
|
|
72
|
+
/** 23.26 — each matcher its pattern names, to the anchored source the route tables carry. */
|
|
73
|
+
matchers: Record<string, string>
|
|
67
74
|
}
|
|
68
75
|
|
|
69
76
|
/** The app at a root, laid out (`appLayout`). */
|
|
@@ -105,7 +112,10 @@ export async function appLayout(root: string): Promise<AppLayout> {
|
|
|
105
112
|
// COLLECTED, whoever asks: a layout is kept across checks (`AppChecker`), and a build adopts
|
|
106
113
|
// what it found (`Refusals.adopt`).
|
|
107
114
|
const refusals = new Refusals(root, true)
|
|
108
|
-
const { abide, handlers, modules, published } = appFiles(
|
|
115
|
+
const { abide, handlers, params, modules, published } = appFiles(
|
|
116
|
+
root,
|
|
117
|
+
refusals,
|
|
118
|
+
)
|
|
109
119
|
await refuseModuleHooks(root, modules, refusals)
|
|
110
120
|
const resolve = resolverIn()
|
|
111
121
|
const unparsed = new Refusals(root, true)
|
|
@@ -149,10 +159,43 @@ export async function appLayout(root: string): Promise<AppLayout> {
|
|
|
149
159
|
scope: placement(file.relative, file.position).scope,
|
|
150
160
|
text,
|
|
151
161
|
hooks: hooksIn([text]),
|
|
162
|
+
matchers: {},
|
|
152
163
|
})
|
|
153
164
|
}
|
|
154
165
|
refuseStrayLauncher(sources, placedHandlers, refusals)
|
|
155
166
|
const { routes, errors } = routesOf(sources, refusals)
|
|
167
|
+
// 23.26 — every routed file's matchers, a layout's among them for 23.27's sake though no table
|
|
168
|
+
// carries a layout's own.
|
|
169
|
+
const routed: {
|
|
170
|
+
path: string
|
|
171
|
+
relative: string
|
|
172
|
+
position: string
|
|
173
|
+
route: string
|
|
174
|
+
}[] = []
|
|
175
|
+
const relativeToPath = new Map<string, string>()
|
|
176
|
+
for (const source of sources) {
|
|
177
|
+
if (source.route === undefined) continue
|
|
178
|
+
relativeToPath.set(source.relative, source.path)
|
|
179
|
+
routed.push({
|
|
180
|
+
path: source.path,
|
|
181
|
+
relative: source.relative,
|
|
182
|
+
position: source.position,
|
|
183
|
+
route: source.route,
|
|
184
|
+
})
|
|
185
|
+
}
|
|
186
|
+
for (const handler of placedHandlers) routed.push(handler)
|
|
187
|
+
const matchers = await matchersOf(
|
|
188
|
+
root,
|
|
189
|
+
routed,
|
|
190
|
+
params.map((path) => `${root}/${path}`),
|
|
191
|
+
found.trees,
|
|
192
|
+
refusals,
|
|
193
|
+
)
|
|
194
|
+
for (const handler of placedHandlers)
|
|
195
|
+
handler.matchers = matchers.get(handler.path) ?? {}
|
|
196
|
+
for (const route of [...routes, ...errors])
|
|
197
|
+
route.matchers =
|
|
198
|
+
matchers.get(relativeToPath.get(route.source) ?? '') ?? {}
|
|
156
199
|
const addresses = new Set<string>()
|
|
157
200
|
for (const route of routes) addresses.add(route.pattern)
|
|
158
201
|
for (const address of published.values()) addresses.add(address)
|
|
@@ -215,6 +258,8 @@ export async function withDependencies(
|
|
|
215
258
|
handlers: HandlerFile[]
|
|
216
259
|
/** 23.19 — each mounted `.abide`'s position, by its path; one at its own path is not here. */
|
|
217
260
|
positions: Map<string, string>
|
|
261
|
+
/** 23.26 — each mounted directory, where it mounts, and the param files under it. */
|
|
262
|
+
trees: MountedTree[]
|
|
218
263
|
}> {
|
|
219
264
|
const all = [...parsed]
|
|
220
265
|
const handlers: HandlerFile[] = []
|
|
@@ -231,6 +276,7 @@ export async function withDependencies(
|
|
|
231
276
|
const positions = new Map<string, string>()
|
|
232
277
|
// 23.20 — a directory mounts once, and by whom.
|
|
233
278
|
const mountedBy = new Map<string, string>()
|
|
279
|
+
const trees: MountedTree[] = []
|
|
234
280
|
const add = async (path: string): Promise<void> => {
|
|
235
281
|
let text: string
|
|
236
282
|
try {
|
|
@@ -268,6 +314,11 @@ export async function withDependencies(
|
|
|
268
314
|
continue
|
|
269
315
|
}
|
|
270
316
|
mountedBy.set(mount.directory, position)
|
|
317
|
+
trees.push({
|
|
318
|
+
directory: mount.directory,
|
|
319
|
+
at: mount.at,
|
|
320
|
+
params: mount.params,
|
|
321
|
+
})
|
|
271
322
|
for (const placed of mount.handlers) {
|
|
272
323
|
// Two mounts can overlap, one directory inside the other; a file is placed once.
|
|
273
324
|
if (handled.has(placed.path)) continue
|
|
@@ -292,7 +343,7 @@ export async function withDependencies(
|
|
|
292
343
|
if (!byPath.has(target)) await add(target)
|
|
293
344
|
}
|
|
294
345
|
}
|
|
295
|
-
return { sources: all, handlers, positions }
|
|
346
|
+
return { sources: all, handlers, positions, trees }
|
|
296
347
|
}
|
|
297
348
|
|
|
298
349
|
/** Whether a page or an error page has anything to paint: a template, or a head. 23.3, 35.14. */
|
|
@@ -450,6 +501,7 @@ function routesOf(
|
|
|
450
501
|
pattern,
|
|
451
502
|
source: source.relative,
|
|
452
503
|
layouts: around,
|
|
504
|
+
matchers: {},
|
|
453
505
|
})
|
|
454
506
|
}
|
|
455
507
|
return { routes, errors }
|
package/src/compiler/appMap.ts
CHANGED
|
@@ -14,6 +14,8 @@ type MappedRoute = {
|
|
|
14
14
|
address: string
|
|
15
15
|
file: string
|
|
16
16
|
kind: 'page' | 'handler' | 'private'
|
|
17
|
+
/** 23.26 — each matcher its address names, to the anchored pattern it takes a part by. */
|
|
18
|
+
matchers: Record<string, string>
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
/** One hook call, the route it is filed at, and the file making it. */
|
|
@@ -39,6 +41,7 @@ export async function appMap(
|
|
|
39
41
|
address: route.pattern,
|
|
40
42
|
file: route.source,
|
|
41
43
|
kind: 'page',
|
|
44
|
+
matchers: route.matchers,
|
|
42
45
|
})
|
|
43
46
|
for (const mount of app.mounts)
|
|
44
47
|
routes.push({
|
|
@@ -46,6 +49,7 @@ export async function appMap(
|
|
|
46
49
|
address: mount.address,
|
|
47
50
|
file: mount.source,
|
|
48
51
|
kind: mount.private ? 'private' : 'handler',
|
|
52
|
+
matchers: mount.matchers,
|
|
49
53
|
})
|
|
50
54
|
routes.sort((one, two) =>
|
|
51
55
|
one.address === two.address
|
|
@@ -79,6 +83,17 @@ export function mapText(map: Awaited<ReturnType<typeof appMap>>): string {
|
|
|
79
83
|
lines.push(
|
|
80
84
|
`${route.method.padEnd(methods)} ${route.address.padEnd(addresses)} ${route.file}${route.kind === 'private' ? ' (private)' : ''}`,
|
|
81
85
|
)
|
|
86
|
+
// 23.26 — each matcher once, whichever routes name it: what a part must match to be taken.
|
|
87
|
+
const matchers = new Map<string, string>()
|
|
88
|
+
for (const route of map.routes)
|
|
89
|
+
for (const name in route.matchers)
|
|
90
|
+
matchers.set(name, route.matchers[name] as string)
|
|
91
|
+
if (matchers.size > 0) {
|
|
92
|
+
lines.push('', 'matchers')
|
|
93
|
+
const names = width([...matchers.keys()])
|
|
94
|
+
for (const [name, pattern] of matchers)
|
|
95
|
+
lines.push(` ${name.padEnd(names)} /${pattern}/`)
|
|
96
|
+
}
|
|
82
97
|
if (map.hooks.length > 0) {
|
|
83
98
|
lines.push('', 'hooks')
|
|
84
99
|
const hooks = width(map.hooks.map((hook) => hook.hook))
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
isStringLiteral,
|
|
17
17
|
} from 'typescript/unstable/ast/is'
|
|
18
18
|
import { type Checker, SymbolFlags, type Type } from 'typescript/unstable/async'
|
|
19
|
+
|
|
19
20
|
import type { Finding } from './Finding.ts'
|
|
20
21
|
import { optionNamed, reactiveOptions } from './reactiveOptions.ts'
|
|
21
22
|
|
package/src/compiler/checkApp.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { relative } from 'node:path' // bun exposes no path helpers of its own
|
|
2
|
+
|
|
2
3
|
import { type Placement, placement } from '#server/placement.ts'
|
|
4
|
+
|
|
3
5
|
import type { PlacedSource } from './appLayout.ts'
|
|
4
6
|
import { type AppChecker, compiled } from './compileApp.ts'
|
|
5
7
|
import { abideImportsOf } from './componentGraph.ts'
|
|
6
8
|
import { hooksIn } from './hookCalls.ts'
|
|
7
9
|
import { parseAbide, scriptBodies } from './parseAbide.ts'
|
|
8
|
-
import { Refusals } from './Refusals.ts'
|
|
9
10
|
import { parseSources, readSources } from './readSources.ts'
|
|
11
|
+
import { Refusals } from './Refusals.ts'
|
|
10
12
|
import type { Diagnosis } from './TypeSession.ts'
|
|
11
13
|
|
|
12
14
|
/**
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { SERVER_SPECIFIER } from '#shared/SPECIFIERS.ts'
|
|
2
|
+
|
|
2
3
|
import type { CheckerAnswer } from './CheckerAnswer.ts'
|
|
3
4
|
import type { Finding } from './Finding.ts'
|
|
4
5
|
import { hookCalls } from './hookCalls.ts'
|
|
@@ -10,8 +11,8 @@ import {
|
|
|
10
11
|
Lowering,
|
|
11
12
|
type Routed,
|
|
12
13
|
} from './Lowering.ts'
|
|
13
|
-
import { holdsReactive } from './PositionTable.ts'
|
|
14
14
|
import type { AbideFile } from './parseAbide.ts'
|
|
15
|
+
import { holdsReactive } from './PositionTable.ts'
|
|
15
16
|
import { NO_PROPS, type Origin, type Span } from './projectShadow.ts'
|
|
16
17
|
import { CHECKED_SUFFIX } from './ShadowMap.ts'
|
|
17
18
|
import { abideImports } from './splitImports.ts'
|
|
@@ -3,6 +3,7 @@ import { escapeAttribute, escapeQuote } from '#shared/escapeText.ts'
|
|
|
3
3
|
import { isEventName } from '#shared/isEventName.ts'
|
|
4
4
|
import { safeUrl } from '#shared/safeUrl.ts'
|
|
5
5
|
import { URL_ATTRIBUTES } from '#shared/URL_ATTRIBUTES.ts'
|
|
6
|
+
|
|
6
7
|
import { decodeCharacterReferences } from './decodeCharacterReferences.ts'
|
|
7
8
|
import {
|
|
8
9
|
type Attribute,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { realpathSync } from 'node:fs' // bun exposes no sync realpath of its own
|
|
2
2
|
import { dirname, posix } from 'node:path' // bun exposes no path helpers of its own
|
|
3
|
+
|
|
3
4
|
import {
|
|
4
5
|
type Compilation,
|
|
5
6
|
type CompiledFile,
|
|
@@ -13,10 +14,11 @@ import { VIRTUAL_DIRECTORY } from '#server/VIRTUAL_DIRECTORY.ts'
|
|
|
13
14
|
import { CLIENT_ADDRESS } from '#shared/ADDRESSES.ts'
|
|
14
15
|
import type { JsonValue } from '#shared/JsonValue.ts'
|
|
15
16
|
import { ROUTING_SPECIFIER, WIRE_SPECIFIER } from '#shared/SPECIFIERS.ts'
|
|
17
|
+
|
|
16
18
|
import {
|
|
17
19
|
type AppLayout,
|
|
18
|
-
type AppRoute,
|
|
19
20
|
appLayout,
|
|
21
|
+
type AppRoute,
|
|
20
22
|
type PlacedSource,
|
|
21
23
|
shapeOf,
|
|
22
24
|
} from './appLayout.ts'
|
|
@@ -25,8 +27,8 @@ import { handlersUnder } from './handlersUnder.ts'
|
|
|
25
27
|
import { isAbideSpecifier } from './hookCalls.ts'
|
|
26
28
|
import { nearest } from './nearest.ts'
|
|
27
29
|
import { projectShadow } from './projectShadow.ts'
|
|
28
|
-
import { Refusals } from './Refusals.ts'
|
|
29
30
|
import { type ReadSource, readSources } from './readSources.ts'
|
|
31
|
+
import { Refusals } from './Refusals.ts'
|
|
30
32
|
import { respecified } from './splitImports.ts'
|
|
31
33
|
import { type Diagnosis, TypeSession } from './TypeSession.ts'
|
|
32
34
|
import { writeDefaultProject } from './writeDefaultProject.ts'
|
|
@@ -514,7 +516,7 @@ function tableOf(
|
|
|
514
516
|
(layout) => `() => import(${tableImport(layout)})`,
|
|
515
517
|
)
|
|
516
518
|
rows.push(
|
|
517
|
-
` { pattern: ${JSON.stringify(route.pattern)}, load: () => import(${tableImport(route.source)}), layouts: [${wrapping.join(', ')}]${column(route)} },`,
|
|
519
|
+
` { pattern: ${JSON.stringify(route.pattern)}, load: () => import(${tableImport(route.source)}), layouts: [${wrapping.join(', ')}], matchers: ${JSON.stringify(route.matchers)}${column(route)} },`,
|
|
518
520
|
)
|
|
519
521
|
}
|
|
520
522
|
return rows.join('\n')
|
|
@@ -2,6 +2,7 @@ import { tablePath } from '#server/abideModules.ts'
|
|
|
2
2
|
import type { AppPackage } from '#server/config.ts'
|
|
3
3
|
import type { SourceMaps } from '#server/installSourceMaps.ts'
|
|
4
4
|
import { SERVER_TABLE } from '#server/TABLES.ts'
|
|
5
|
+
|
|
5
6
|
import abidePackage from '../../package.json' with { type: 'json' }
|
|
6
7
|
|
|
7
8
|
// What the executable starts from: `abide compile`'s, and `abide bundle`'s window over it (38.70).
|
|
@@ -120,7 +121,7 @@ import * as table from ${JSON.stringify(tablePath(app.root, SERVER_TABLE))}
|
|
|
120
121
|
return `// Written by \`abide compile\`. The whole app, as what one executable holds.
|
|
121
122
|
${imports}
|
|
122
123
|
// 38.56 — a file declaring handlers as a script names it through the app's one alias:
|
|
123
|
-
// \`#src/invoices/getInvoice.ts\`, to the module it is.
|
|
124
|
+
// \`#src/invoices/getInvoice.handler.ts\`, to the module it is.
|
|
124
125
|
const declared = {}
|
|
125
126
|
for (const row of table.handlers) declared[\`#src/\${row.source}\`] = row.load
|
|
126
127
|
|
|
@@ -14,18 +14,19 @@ import {
|
|
|
14
14
|
isVariableDeclaration,
|
|
15
15
|
} from 'typescript/unstable/ast/is'
|
|
16
16
|
import {
|
|
17
|
-
type Symbol as CheckedSymbol,
|
|
18
17
|
type Checker,
|
|
19
18
|
ObjectFlags,
|
|
20
19
|
type ObjectType,
|
|
21
20
|
type Project,
|
|
22
21
|
SignatureKind,
|
|
22
|
+
type Symbol as CheckedSymbol,
|
|
23
23
|
SymbolFlags,
|
|
24
24
|
type Type,
|
|
25
25
|
TypeFlags,
|
|
26
26
|
type TypeReference,
|
|
27
27
|
type UnionOrIntersectionType,
|
|
28
28
|
} from 'typescript/unstable/async'
|
|
29
|
+
|
|
29
30
|
import type { Mount } from '#server/answerRpc.ts'
|
|
30
31
|
import type { CopyOptions } from '#shared/CopyOptions.ts'
|
|
31
32
|
import {
|
|
@@ -33,6 +34,7 @@ import {
|
|
|
33
34
|
defaultsInSource,
|
|
34
35
|
} from '#shared/reactive/parameterDefaults.ts'
|
|
35
36
|
import type { Method } from '#shared/rpc.ts'
|
|
37
|
+
|
|
36
38
|
import { aliasedSymbol } from './aliasedSymbol.ts'
|
|
37
39
|
import { defaultedMembers, parameterPattern } from './argsDefaultsIn.ts'
|
|
38
40
|
import {
|
|
@@ -42,7 +44,6 @@ import {
|
|
|
42
44
|
type Walk,
|
|
43
45
|
} from './argsSchemaOf.ts'
|
|
44
46
|
import { diagnosisAt } from './lineOf.ts'
|
|
45
|
-
import type { Refusals } from './Refusals.ts'
|
|
46
47
|
import { isReactiveCall, METHODS, optionNamed } from './reactiveOptions.ts'
|
|
47
48
|
import {
|
|
48
49
|
abideIn,
|
|
@@ -50,6 +51,7 @@ import {
|
|
|
50
51
|
isReactiveMember,
|
|
51
52
|
valueTypeOf,
|
|
52
53
|
} from './reactiveTypes.ts'
|
|
54
|
+
import type { Refusals } from './Refusals.ts'
|
|
53
55
|
import type { Diagnosis } from './TypeSession.ts'
|
|
54
56
|
|
|
55
57
|
/**
|