@pathmx/core 0.5.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/LICENSE.md +172 -0
- package/README.md +111 -0
- package/actions/actions.ts +307 -0
- package/actions/draft.ts +105 -0
- package/actions/transaction.ts +164 -0
- package/assets.ts +149 -0
- package/authority/access.ts +820 -0
- package/authority/actors.ts +271 -0
- package/authority/agents.ts +95 -0
- package/authority/credentials.ts +144 -0
- package/authority/model.ts +71 -0
- package/authority/sharing.ts +89 -0
- package/browser-bundle.ts +61 -0
- package/canonical.ts +41 -0
- package/cli/dev.ts +134 -0
- package/cli/export.ts +74 -0
- package/cli/index.ts +52 -0
- package/cli/lint.ts +164 -0
- package/cli/mv.ts +30 -0
- package/cli/perms.ts +182 -0
- package/cli/plugin-packages.ts +159 -0
- package/cli/plugins.ts +95 -0
- package/cli/query.ts +225 -0
- package/cli/readme.md +95 -0
- package/cli/release.ts +75 -0
- package/cli/rm.ts +22 -0
- package/cli/serve.ts +100 -0
- package/cli/tree.ts +131 -0
- package/cli/update.ts +78 -0
- package/cli/utils.ts +53 -0
- package/core.ts +96 -0
- package/database.ts +189 -0
- package/dependencies.ts +60 -0
- package/dist/pathmx.js +1224 -0
- package/document.ts +194 -0
- package/environment.ts +110 -0
- package/environments/bun-plugins.ts +68 -0
- package/environments/bun-publish.ts +194 -0
- package/environments/bun.ts +223 -0
- package/globals.d.ts +14 -0
- package/graph.ts +153 -0
- package/host/compiler.ts +314 -0
- package/host/create.ts +66 -0
- package/host/engine.ts +630 -0
- package/host/plugin-host.ts +487 -0
- package/host/render.ts +102 -0
- package/host/response.ts +20 -0
- package/host/shell.html +15 -0
- package/host/source-sync.ts +254 -0
- package/html-attributes.ts +15 -0
- package/html.ts +48 -0
- package/icons/LICENSE +43 -0
- package/icons/lucide.ts +54 -0
- package/index.ts +75 -0
- package/invalidation.ts +85 -0
- package/log.ts +84 -0
- package/ops/href.ts +87 -0
- package/ops/index.ts +9 -0
- package/ops/move-source.ts +114 -0
- package/ops/plan.ts +47 -0
- package/ops/remove-source.ts +70 -0
- package/package.json +46 -0
- package/path.ts +75 -0
- package/pattern.ts +314 -0
- package/perf.ts +88 -0
- package/plugins/actor-path.ts +43 -0
- package/plugins/actor-profile.ts +55 -0
- package/plugins/agent-browsing.ts +183 -0
- package/plugins/app-component.ts +23 -0
- package/plugins/block-layout/index.ts +78 -0
- package/plugins/block-layout/theme.css +90 -0
- package/plugins/callout/index.ts +236 -0
- package/plugins/callout/theme.css +138 -0
- package/plugins/canonical-links.ts +144 -0
- package/plugins/code-highlight/client.ts +89 -0
- package/plugins/code-highlight/index.ts +147 -0
- package/plugins/code-highlight/theme.css +89 -0
- package/plugins/context.ts +131 -0
- package/plugins/datatype/Datatype[wdth,wght].woff2 +0 -0
- package/plugins/datatype/OFL.txt +96 -0
- package/plugins/datatype/index.ts +287 -0
- package/plugins/definition-list.ts +236 -0
- package/plugins/directive-definitions.ts +13 -0
- package/plugins/embedding/client.ts +88 -0
- package/plugins/embedding/index.ts +43 -0
- package/plugins/image/index.ts +68 -0
- package/plugins/image/theme.css +43 -0
- package/plugins/include.ts +119 -0
- package/plugins/index.ts +91 -0
- package/plugins/input/authoring.ts +236 -0
- package/plugins/input/config.ts +85 -0
- package/plugins/input/index.ts +359 -0
- package/plugins/input/model.ts +173 -0
- package/plugins/input/state.ts +272 -0
- package/plugins/layout.ts +313 -0
- package/plugins/literate/client.ts +208 -0
- package/plugins/literate/index.ts +742 -0
- package/plugins/math/index.ts +140 -0
- package/plugins/meta.ts +42 -0
- package/plugins/navigation/index.ts +158 -0
- package/plugins/navigation/model.ts +364 -0
- package/plugins/preset.ts +65 -0
- package/plugins/query/index.ts +322 -0
- package/plugins/rss/index.ts +46 -0
- package/plugins/runtime/actions.ts +62 -0
- package/plugins/runtime/client.ts +8 -0
- package/plugins/runtime/document.ts +506 -0
- package/plugins/runtime/error.ts +154 -0
- package/plugins/runtime/heading-links.ts +31 -0
- package/plugins/runtime/index.ts +104 -0
- package/plugins/runtime/live.ts +51 -0
- package/plugins/runtime/navigation.ts +113 -0
- package/plugins/runtime/pending.ts +53 -0
- package/plugins/runtime/publication.ts +19 -0
- package/plugins/runtime/start.ts +177 -0
- package/plugins/runtime/static-client.ts +6 -0
- package/plugins/runtime/static-live.ts +188 -0
- package/plugins/runtime/view-transition.ts +72 -0
- package/plugins/runtime/viewport.ts +110 -0
- package/plugins/schema.ts +24 -0
- package/plugins/scripts.ts +140 -0
- package/plugins/source-preview/client.ts +256 -0
- package/plugins/source-preview/index.ts +132 -0
- package/plugins/source-preview/model.ts +11 -0
- package/plugins/source-preview/preview.css +107 -0
- package/plugins/styles.ts +145 -0
- package/plugins/theme/components.ts +10 -0
- package/plugins/theme/foundation.css +358 -0
- package/plugins/theme/index.ts +333 -0
- package/plugins/theme/prose.css +284 -0
- package/plugins/theme/scheme.js +63 -0
- package/plugins/theme/toggle.css +44 -0
- package/plugins/theme/view-transition.css +68 -0
- package/plugins/toc/client.ts +146 -0
- package/plugins/toc/index.ts +232 -0
- package/plugins/toc/theme.css +172 -0
- package/plugins/types.ts +538 -0
- package/problem.ts +41 -0
- package/project.ts +6 -0
- package/publish/build.ts +402 -0
- package/publish/concurrent.ts +32 -0
- package/publish/identity.ts +63 -0
- package/publish/index.ts +11 -0
- package/publish/location.ts +167 -0
- package/publish/model.ts +58 -0
- package/publish/plan.ts +300 -0
- package/publish/validate.ts +150 -0
- package/query/candidate-index.ts +101 -0
- package/query/dependency-index.ts +99 -0
- package/query/fields.ts +216 -0
- package/query/invalidation.ts +48 -0
- package/query/kernel.ts +776 -0
- package/query/model.ts +79 -0
- package/repository.ts +374 -0
- package/runtime-profile.ts +34 -0
- package/schema/model.ts +92 -0
- package/schema/registry.ts +642 -0
- package/schema/standard.ts +226 -0
- package/server/context.ts +94 -0
- package/server/css-imports.ts +66 -0
- package/server/http.ts +333 -0
- package/server/page.ts +21 -0
- package/server/protocols.ts +12 -0
- package/server/representations.ts +66 -0
- package/server/router.ts +131 -0
- package/server/routes.ts +202 -0
- package/server/serve.ts +5 -0
- package/server/watch.ts +120 -0
- package/source/blocks.ts +71 -0
- package/source/format.ts +164 -0
- package/source/index.ts +67 -0
- package/source/interpolate.ts +63 -0
- package/source/markdown/blocks.ts +46 -0
- package/source/markdown/comments.ts +32 -0
- package/source/markdown/fences.ts +67 -0
- package/source/markdown/headings.ts +88 -0
- package/source/markdown/index.ts +34 -0
- package/source/markdown/inline-code.ts +6 -0
- package/source/markdown/links.ts +326 -0
- package/source/markdown/lists.ts +223 -0
- package/source/markdown/prose.ts +74 -0
- package/source/markdown/scan.ts +79 -0
- package/source/markdown/text.ts +82 -0
- package/source/meta.ts +87 -0
- package/source/source.ts +121 -0
- package/source/topmatter.ts +57 -0
- package/source/utils.ts +21 -0
- package/source-diagnostic.ts +63 -0
- package/text-file.ts +26 -0
- package/util.ts +35 -0
- package/view/components.ts +499 -0
- package/view/model.ts +91 -0
- package/view/projection.ts +162 -0
- package/view/session.ts +146 -0
- package/view/syntax.ts +372 -0
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
import type { Engine } from "./engine.ts"
|
|
2
|
+
import {
|
|
3
|
+
anonymousCredentialHost,
|
|
4
|
+
type CredentialHost,
|
|
5
|
+
} from "../authority/credentials.ts"
|
|
6
|
+
import {
|
|
7
|
+
ViewComponentRegistry,
|
|
8
|
+
type CompiledViewPlan,
|
|
9
|
+
} from "../view/components.ts"
|
|
10
|
+
import {
|
|
11
|
+
canonicalPluginId,
|
|
12
|
+
type AppContext,
|
|
13
|
+
type CompileContext,
|
|
14
|
+
type DocumentAssetContext,
|
|
15
|
+
type PageContext,
|
|
16
|
+
type Plugin,
|
|
17
|
+
type PluginContext,
|
|
18
|
+
type PluginFactory,
|
|
19
|
+
type PluginInvalidation,
|
|
20
|
+
type SourceTransition,
|
|
21
|
+
type ViewContext,
|
|
22
|
+
} from "../plugins/types.ts"
|
|
23
|
+
import type { Source } from "../source/index.ts"
|
|
24
|
+
import type { KnownActorIndex, Principal } from "../authority/model.ts"
|
|
25
|
+
import { PluginDatabases } from "../database.ts"
|
|
26
|
+
import { createAppContext, createPluginContext } from "../plugins/context.ts"
|
|
27
|
+
import { ServerRouteRegistry } from "../server/routes.ts"
|
|
28
|
+
import {
|
|
29
|
+
describeAction,
|
|
30
|
+
type ActionAffordance,
|
|
31
|
+
type ActionContribution,
|
|
32
|
+
} from "../actions/actions.ts"
|
|
33
|
+
import { applicationSchemaView, SchemaRegistry } from "../schema/registry.ts"
|
|
34
|
+
import { instantiatePlugin } from "../plugins/preset.ts"
|
|
35
|
+
import type { HtmlRewriteRule } from "../environment.ts"
|
|
36
|
+
import type { QueryContribution } from "../query/model.ts"
|
|
37
|
+
|
|
38
|
+
export type { CompiledViewPlan }
|
|
39
|
+
|
|
40
|
+
export class PluginRegistrationError extends Error {
|
|
41
|
+
constructor(message: string) {
|
|
42
|
+
super(message)
|
|
43
|
+
this.name = "PluginRegistrationError"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
type HookErrorHandler = (error: unknown) => boolean
|
|
48
|
+
|
|
49
|
+
function validatePluginRequirements(plugins: readonly Plugin[]) {
|
|
50
|
+
const positions = new Map(plugins.map((plugin, index) => [plugin.id, index]))
|
|
51
|
+
for (const plugin of plugins) {
|
|
52
|
+
if (plugin.requires === undefined) continue
|
|
53
|
+
if (!Array.isArray(plugin.requires)) {
|
|
54
|
+
throw new PluginRegistrationError(
|
|
55
|
+
`Plugin ${plugin.id} requires must be a list of plugin ids.`,
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
const seen = new Set<string>()
|
|
59
|
+
for (const dependency of plugin.requires) {
|
|
60
|
+
try {
|
|
61
|
+
canonicalPluginId(dependency)
|
|
62
|
+
} catch (error) {
|
|
63
|
+
throw new PluginRegistrationError(
|
|
64
|
+
error instanceof Error ? error.message : String(error),
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
if (dependency === plugin.id) {
|
|
68
|
+
throw new PluginRegistrationError(
|
|
69
|
+
`Plugin ${plugin.id} cannot require itself.`,
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
if (seen.has(dependency)) {
|
|
73
|
+
throw new PluginRegistrationError(
|
|
74
|
+
`Plugin ${plugin.id} requires ${dependency} more than once.`,
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
seen.add(dependency)
|
|
78
|
+
const dependencyPosition = positions.get(dependency)
|
|
79
|
+
if (dependencyPosition === undefined) {
|
|
80
|
+
throw new PluginRegistrationError(
|
|
81
|
+
`Plugin ${plugin.id} requires missing plugin ${dependency}.`,
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
if (dependencyPosition > positions.get(plugin.id)!) {
|
|
85
|
+
throw new PluginRegistrationError(
|
|
86
|
+
`Plugin ${plugin.id} requires plugin ${dependency} to be registered first.`,
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Own plugin installation, View Components, routes, and ordered hook phases. */
|
|
94
|
+
export class PluginHost {
|
|
95
|
+
private installed: Plugin[] = []
|
|
96
|
+
private ids = new Set<string>()
|
|
97
|
+
private frozen = false
|
|
98
|
+
private credentialHost: CredentialHost = anonymousCredentialHost()
|
|
99
|
+
private credentialOwner?: string
|
|
100
|
+
private credentialClosing?: Promise<void>
|
|
101
|
+
private closing?: Promise<void>
|
|
102
|
+
private views = new ViewComponentRegistry()
|
|
103
|
+
private routes = new ServerRouteRegistry()
|
|
104
|
+
private actions = new Map<string, ActionContribution>()
|
|
105
|
+
private lifecycleContexts = new Map<string, PluginContext>()
|
|
106
|
+
private appContexts = new Map<string, AppContext>()
|
|
107
|
+
private schemaRegistry = new SchemaRegistry()
|
|
108
|
+
readonly schema = applicationSchemaView(this.schemaRegistry)
|
|
109
|
+
|
|
110
|
+
constructor(
|
|
111
|
+
private databases = new PluginDatabases(undefined, ".pathmx/state"),
|
|
112
|
+
) {}
|
|
113
|
+
|
|
114
|
+
get plugins(): readonly Plugin[] {
|
|
115
|
+
return this.installed
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
has(id: string) {
|
|
119
|
+
return this.ids.has(id)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
register(plugin: PluginFactory | Plugin) {
|
|
123
|
+
if (this.frozen) {
|
|
124
|
+
throw new PluginRegistrationError(
|
|
125
|
+
"Plugin registration is frozen after the engine is ready.",
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const instance = instantiatePlugin(plugin)
|
|
130
|
+
canonicalPluginId(instance.id)
|
|
131
|
+
if (this.ids.has(instance.id)) {
|
|
132
|
+
throw new PluginRegistrationError(`Duplicate plugin id: ${instance.id}`)
|
|
133
|
+
}
|
|
134
|
+
if (instance.credentials && this.credentialOwner) {
|
|
135
|
+
throw new PluginRegistrationError(
|
|
136
|
+
`Credential provider already registered by plugin ${this.credentialOwner}.`,
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
let routes: ReturnType<ServerRouteRegistry["prepare"]>
|
|
140
|
+
let actions: readonly (readonly [string, ActionContribution])[]
|
|
141
|
+
let schema: ReturnType<SchemaRegistry["prepare"]>
|
|
142
|
+
let database
|
|
143
|
+
try {
|
|
144
|
+
database = instance.database
|
|
145
|
+
? this.databases.prepare(instance.database)
|
|
146
|
+
: undefined
|
|
147
|
+
routes = this.routes.prepare(instance)
|
|
148
|
+
schema = this.schemaRegistry.prepare(instance.id, instance.schema)
|
|
149
|
+
actions = this.prepareActions(schema.actions)
|
|
150
|
+
this.views.register(instance)
|
|
151
|
+
} catch (error) {
|
|
152
|
+
throw new PluginRegistrationError(
|
|
153
|
+
error instanceof Error ? error.message : String(error),
|
|
154
|
+
)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (database) this.databases.register(instance.id, database)
|
|
158
|
+
this.routes.register(routes)
|
|
159
|
+
for (const [name, action] of actions) this.actions.set(name, action)
|
|
160
|
+
this.schemaRegistry.register(schema)
|
|
161
|
+
if (instance.credentials) {
|
|
162
|
+
this.credentialHost = instance.credentials
|
|
163
|
+
this.credentialOwner = instance.id
|
|
164
|
+
}
|
|
165
|
+
this.ids.add(instance.id)
|
|
166
|
+
this.installed.push(instance)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
private prepareActions(
|
|
170
|
+
actions: readonly (readonly [string, ActionContribution])[],
|
|
171
|
+
) {
|
|
172
|
+
for (const [name, action] of actions) {
|
|
173
|
+
if (this.actions.has(name)) {
|
|
174
|
+
throw new Error(`Duplicate Plugin Action: ${name}`)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return actions
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
action(name: string) {
|
|
181
|
+
return this.actions.get(name)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async actionAffordances(
|
|
185
|
+
principal: Principal,
|
|
186
|
+
target: Source,
|
|
187
|
+
env: Engine,
|
|
188
|
+
): Promise<readonly ActionAffordance[]> {
|
|
189
|
+
const described = await Promise.all(
|
|
190
|
+
[...this.actions].map(([name, action]) =>
|
|
191
|
+
describeAction(name, action, {
|
|
192
|
+
principal,
|
|
193
|
+
target,
|
|
194
|
+
yaml: env.environment.yaml,
|
|
195
|
+
can: (capability) =>
|
|
196
|
+
env.hasSourceCapability(principal, target, capability),
|
|
197
|
+
}),
|
|
198
|
+
),
|
|
199
|
+
)
|
|
200
|
+
return Object.freeze(
|
|
201
|
+
described.filter((item): item is ActionAffordance => item !== undefined),
|
|
202
|
+
)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
freeze() {
|
|
206
|
+
validatePluginRequirements(this.installed)
|
|
207
|
+
this.frozen = true
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
private lifecycleContext(plugin: Plugin | string, engine: Engine) {
|
|
211
|
+
const id = typeof plugin === "string" ? plugin : plugin.id
|
|
212
|
+
const existing = this.lifecycleContexts.get(id)
|
|
213
|
+
if (existing) return existing
|
|
214
|
+
const context = createPluginContext(id, engine, this.databases.get(id))
|
|
215
|
+
this.lifecycleContexts.set(id, context)
|
|
216
|
+
return context
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
appContext(plugin: Plugin | string, engine: Engine) {
|
|
220
|
+
const id = typeof plugin === "string" ? plugin : plugin.id
|
|
221
|
+
const existing = this.appContexts.get(id)
|
|
222
|
+
if (existing) return existing
|
|
223
|
+
const context = createAppContext(
|
|
224
|
+
this.lifecycleContext(id, engine),
|
|
225
|
+
engine,
|
|
226
|
+
this.componentContext(id, engine),
|
|
227
|
+
)
|
|
228
|
+
this.appContexts.set(id, context)
|
|
229
|
+
return context
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
private componentContext(
|
|
233
|
+
plugin: string,
|
|
234
|
+
engine: Engine,
|
|
235
|
+
): AppContext["components"] {
|
|
236
|
+
return Object.freeze({
|
|
237
|
+
claim: (sourcePath, tags) =>
|
|
238
|
+
this.replaceAuthoredComponents(sourcePath, tags),
|
|
239
|
+
release: (sourcePath) => this.deleteAuthoredComponents(sourcePath),
|
|
240
|
+
has: (tag) => this.hasView(tag),
|
|
241
|
+
replaceData: (sourcePath, components) =>
|
|
242
|
+
this.replaceAuthoredDataComponents(plugin, sourcePath, components),
|
|
243
|
+
prepareData: (tag, props, origin, ctx) =>
|
|
244
|
+
this.prepareDataComponent(tag, props, origin, ctx, engine),
|
|
245
|
+
renderData: (prepared, data, ctx) =>
|
|
246
|
+
this.renderDataComponent(prepared, data, ctx, engine),
|
|
247
|
+
})
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async setup(env: Engine) {
|
|
251
|
+
for (const plugin of this.installed) {
|
|
252
|
+
await plugin.setup?.(this.lifecycleContext(plugin, env))
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
private async shutdown(env: Engine) {
|
|
257
|
+
const errors: unknown[] = []
|
|
258
|
+
try {
|
|
259
|
+
await this.closeCredentials()
|
|
260
|
+
} catch (error) {
|
|
261
|
+
errors.push(error)
|
|
262
|
+
}
|
|
263
|
+
for (const plugin of [...this.installed].reverse()) {
|
|
264
|
+
try {
|
|
265
|
+
await plugin.close?.(this.lifecycleContext(plugin, env))
|
|
266
|
+
} catch (error) {
|
|
267
|
+
errors.push(error)
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
try {
|
|
271
|
+
await this.databases.close()
|
|
272
|
+
} catch (error) {
|
|
273
|
+
errors.push(error)
|
|
274
|
+
}
|
|
275
|
+
if (errors.length)
|
|
276
|
+
throw new AggregateError(errors, "Plugin shutdown failed.")
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
close(env: Engine) {
|
|
280
|
+
return (this.closing ??= this.shutdown(env))
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
private async measure<T>(
|
|
284
|
+
plugin: Plugin,
|
|
285
|
+
hook: string,
|
|
286
|
+
label: string | undefined,
|
|
287
|
+
env: Engine,
|
|
288
|
+
run: () => T | Promise<T>,
|
|
289
|
+
) {
|
|
290
|
+
const start = performance.now()
|
|
291
|
+
try {
|
|
292
|
+
return await run()
|
|
293
|
+
} finally {
|
|
294
|
+
env.perf.record(
|
|
295
|
+
`${plugin.name ?? "plugin"}.${hook}`,
|
|
296
|
+
performance.now() - start,
|
|
297
|
+
label,
|
|
298
|
+
)
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
index(source: Source, env: Engine, onError?: HookErrorHandler) {
|
|
303
|
+
this.schemaRegistry.index(source, onError)
|
|
304
|
+
for (const plugin of this.installed) {
|
|
305
|
+
try {
|
|
306
|
+
plugin.index?.(source, this.appContext(plugin, env))
|
|
307
|
+
} catch (error) {
|
|
308
|
+
if (!onError?.(error)) throw error
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
unindex(sourcePath: string, env: Engine) {
|
|
314
|
+
for (const plugin of this.installed) {
|
|
315
|
+
plugin.unindex?.(sourcePath, this.appContext(plugin, env))
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
async precompile(markdown: string, ctx: CompileContext, env: Engine) {
|
|
320
|
+
for (const plugin of this.installed) {
|
|
321
|
+
if (!plugin.precompile) continue
|
|
322
|
+
markdown =
|
|
323
|
+
(await this.measure(plugin, "precompile", ctx.source.path, env, () =>
|
|
324
|
+
plugin.precompile!(markdown, ctx, this.appContext(plugin, env)),
|
|
325
|
+
)) ?? markdown
|
|
326
|
+
}
|
|
327
|
+
return this.views.compileSyntax(markdown, ctx)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
compileVariables(markdown: string, ctx: CompileContext) {
|
|
331
|
+
return this.views.compileVariables(markdown, ctx)
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
async postcompile(html: string, ctx: CompileContext, env: Engine) {
|
|
335
|
+
html = this.views.restoreSyntax(html, ctx)
|
|
336
|
+
if (this.views.hasAny(ctx.elements)) {
|
|
337
|
+
html = this.views.stampOrigins(
|
|
338
|
+
html,
|
|
339
|
+
ctx.source.path,
|
|
340
|
+
env.environment.rewriteHtml,
|
|
341
|
+
)
|
|
342
|
+
}
|
|
343
|
+
for (const plugin of this.installed) {
|
|
344
|
+
if (!plugin.postcompile) continue
|
|
345
|
+
html =
|
|
346
|
+
(await this.measure(plugin, "postcompile", ctx.source.path, env, () =>
|
|
347
|
+
plugin.postcompile!(html, ctx, this.appContext(plugin, env)),
|
|
348
|
+
)) ?? html
|
|
349
|
+
}
|
|
350
|
+
return html
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
async page(
|
|
354
|
+
html: string,
|
|
355
|
+
ctx: PageContext,
|
|
356
|
+
env: Engine,
|
|
357
|
+
excludedPluginId?: string,
|
|
358
|
+
) {
|
|
359
|
+
for (const plugin of this.installed) {
|
|
360
|
+
if (!plugin.page || plugin.id === excludedPluginId) continue
|
|
361
|
+
html =
|
|
362
|
+
(await this.measure(plugin, "page", ctx.source.path, env, () =>
|
|
363
|
+
plugin.page!(html, ctx, this.appContext(plugin, env)),
|
|
364
|
+
)) ?? html
|
|
365
|
+
}
|
|
366
|
+
return html
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
invalidations(transition: SourceTransition, env: Engine) {
|
|
370
|
+
const invalidations: PluginInvalidation[] = []
|
|
371
|
+
for (const plugin of this.installed) {
|
|
372
|
+
const invalidation = plugin.invalidate?.(
|
|
373
|
+
transition,
|
|
374
|
+
this.appContext(plugin, env),
|
|
375
|
+
)
|
|
376
|
+
if (invalidation) invalidations.push(invalidation)
|
|
377
|
+
}
|
|
378
|
+
return invalidations
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
async documentAssets(ctx: DocumentAssetContext, env: Engine) {
|
|
382
|
+
for (const plugin of this.installed) {
|
|
383
|
+
await plugin.documentAssets?.(ctx, this.appContext(plugin, env))
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
replaceAuthoredComponents(sourcePath: string, tags: Iterable<string>) {
|
|
388
|
+
this.views.replaceAuthored(sourcePath, tags)
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
replaceAuthoredDataComponents(
|
|
392
|
+
plugin: string,
|
|
393
|
+
sourcePath: string,
|
|
394
|
+
components: Parameters<ViewComponentRegistry["replaceData"]>[2],
|
|
395
|
+
) {
|
|
396
|
+
this.views.replaceData(plugin, sourcePath, components)
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
deleteAuthoredComponents(sourcePath: string) {
|
|
400
|
+
this.views.deleteAuthored(sourcePath)
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
hasView(tag: string) {
|
|
404
|
+
return this.views.has(tag)
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
prepareDataComponent(
|
|
408
|
+
tag: string,
|
|
409
|
+
props: Readonly<Record<string, string>>,
|
|
410
|
+
origin: Source,
|
|
411
|
+
ctx: PageContext,
|
|
412
|
+
env: Engine,
|
|
413
|
+
) {
|
|
414
|
+
return this.views.prepareData(tag, props, origin, ctx, env)
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
renderDataComponent(
|
|
418
|
+
prepared: unknown,
|
|
419
|
+
data: unknown,
|
|
420
|
+
ctx: ViewContext,
|
|
421
|
+
env: Engine,
|
|
422
|
+
) {
|
|
423
|
+
return this.views.renderData(prepared, data, ctx, env)
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
query(reference: string | QueryContribution) {
|
|
427
|
+
return this.schemaRegistry.registeredQuery(reference)
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
queries() {
|
|
431
|
+
return this.schemaRegistry.queries()
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
compileViewComponents(
|
|
435
|
+
html: string,
|
|
436
|
+
source: Source,
|
|
437
|
+
ctx: PageContext,
|
|
438
|
+
env: Engine,
|
|
439
|
+
) {
|
|
440
|
+
return this.views.compile(html, source, ctx, env)
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
async renderView(
|
|
444
|
+
html: string,
|
|
445
|
+
plan: CompiledViewPlan | undefined,
|
|
446
|
+
ctx: ViewContext,
|
|
447
|
+
env: Engine,
|
|
448
|
+
init?: ResponseInit,
|
|
449
|
+
) {
|
|
450
|
+
const segments = await this.views.renderSegments(html, plan, ctx, env)
|
|
451
|
+
const rules: HtmlRewriteRule[] = []
|
|
452
|
+
for (const plugin of this.installed) {
|
|
453
|
+
if (!plugin.streamSelectors) continue
|
|
454
|
+
for (const [selector, handler] of Object.entries(
|
|
455
|
+
plugin.streamSelectors(ctx, this.appContext(plugin, env)),
|
|
456
|
+
)) {
|
|
457
|
+
rules.push([selector, handler])
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
const body = new Blob(segments, { type: "text/html; charset=utf-8" })
|
|
461
|
+
return env.environment.rewriteHtml(new Response(body, init), rules)
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
resolveCredentials(request: Request, actors: KnownActorIndex, env: Engine) {
|
|
465
|
+
return env.perf.measureAsync(
|
|
466
|
+
"credentials.resolve",
|
|
467
|
+
() => this.credentialHost.resolve(request, actors),
|
|
468
|
+
new URL(request.url).pathname,
|
|
469
|
+
)
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
credentialRequest(request: Request, actors: KnownActorIndex) {
|
|
473
|
+
return this.credentialHost.fetch(request, actors)
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
readyCredentials(actors: KnownActorIndex) {
|
|
477
|
+
return this.credentialHost.ready(actors)
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
closeCredentials() {
|
|
481
|
+
return (this.credentialClosing ??= this.credentialHost.close())
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
serverRoute(request: Request, url: URL, env: Engine, publicUrl = url) {
|
|
485
|
+
return this.routes.dispatch(request, url, env, publicUrl)
|
|
486
|
+
}
|
|
487
|
+
}
|
package/host/render.ts
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { CredentialSession, Principal } from "../authority/model.ts"
|
|
2
|
+
import { interpolateText, type Source } from "../source/index.ts"
|
|
3
|
+
import { escapeHtml } from "../util.ts"
|
|
4
|
+
import type { CompiledPage } from "../plugins/types.ts"
|
|
5
|
+
import type { Engine } from "./engine.ts"
|
|
6
|
+
import { withReadCache } from "./response.ts"
|
|
7
|
+
import shellHtml from "./shell.html" with { type: "text" }
|
|
8
|
+
|
|
9
|
+
// Bun types `*.html` as HTMLBundle; `type: "text"` inlines the file as a string.
|
|
10
|
+
const shell = shellHtml as unknown as string
|
|
11
|
+
|
|
12
|
+
export type PageRenderOptions = Readonly<{
|
|
13
|
+
status?: number
|
|
14
|
+
headers?: HeadersInit
|
|
15
|
+
scheme?: "light" | "dark"
|
|
16
|
+
}>
|
|
17
|
+
|
|
18
|
+
export function pageNotFound() {
|
|
19
|
+
return new Response(
|
|
20
|
+
"# Not Found\n\nThe requested resource does not exist or is not available. Start at [the site root](/) or browse the public index in [/llms.txt](/llms.txt).\n",
|
|
21
|
+
{
|
|
22
|
+
status: 404,
|
|
23
|
+
headers: { "Content-Type": "text/markdown; charset=utf-8" },
|
|
24
|
+
},
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function renderCompiledPage(
|
|
29
|
+
app: Engine,
|
|
30
|
+
session: CredentialSession,
|
|
31
|
+
principal: Principal,
|
|
32
|
+
source: Source,
|
|
33
|
+
page: CompiledPage,
|
|
34
|
+
options: PageRenderOptions = {},
|
|
35
|
+
) {
|
|
36
|
+
const assets = await app.documentHead(page)
|
|
37
|
+
const htmlAttrs = options.scheme
|
|
38
|
+
? ` data-pmx-scheme="${options.scheme}" style="color-scheme: ${options.scheme}"`
|
|
39
|
+
: ""
|
|
40
|
+
const actorId = principal.type === "actor" ? principal.actor.id : ""
|
|
41
|
+
const html = interpolateText(shell, {
|
|
42
|
+
head: assets,
|
|
43
|
+
html_attrs: htmlAttrs,
|
|
44
|
+
route: escapeHtml(page.route),
|
|
45
|
+
actor: escapeHtml(actorId),
|
|
46
|
+
content: page.html,
|
|
47
|
+
})
|
|
48
|
+
const headers = new Headers(options.headers)
|
|
49
|
+
if (!headers.has("Content-Type")) {
|
|
50
|
+
headers.set("Content-Type", "text/html; charset=utf-8")
|
|
51
|
+
}
|
|
52
|
+
return app.rewrite(session, html, source, {
|
|
53
|
+
status: options.status,
|
|
54
|
+
headers,
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Render one Source through the canonical cached server pipeline. */
|
|
59
|
+
export async function renderPage(
|
|
60
|
+
app: Engine,
|
|
61
|
+
session: CredentialSession,
|
|
62
|
+
principal: Principal,
|
|
63
|
+
source: Source,
|
|
64
|
+
options: PageRenderOptions = {},
|
|
65
|
+
) {
|
|
66
|
+
const access = app.readAccess(principal, source)
|
|
67
|
+
if (access === "denied") return pageNotFound()
|
|
68
|
+
const response = await renderCompiledPage(
|
|
69
|
+
app,
|
|
70
|
+
session,
|
|
71
|
+
principal,
|
|
72
|
+
source,
|
|
73
|
+
await app.ensurePage(source),
|
|
74
|
+
options,
|
|
75
|
+
)
|
|
76
|
+
return withReadCache(response, access, session)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Render once without retaining live recompilation state. */
|
|
80
|
+
export async function renderUncachedPage(
|
|
81
|
+
app: Engine,
|
|
82
|
+
session: CredentialSession,
|
|
83
|
+
principal: Principal,
|
|
84
|
+
source: Source,
|
|
85
|
+
options: PageRenderOptions = {},
|
|
86
|
+
) {
|
|
87
|
+
const access = app.readAccess(principal, source)
|
|
88
|
+
if (access === "denied") return pageNotFound()
|
|
89
|
+
try {
|
|
90
|
+
const response = await renderCompiledPage(
|
|
91
|
+
app,
|
|
92
|
+
session,
|
|
93
|
+
principal,
|
|
94
|
+
source,
|
|
95
|
+
await app.compilePage(source),
|
|
96
|
+
options,
|
|
97
|
+
)
|
|
98
|
+
return withReadCache(response, access, session)
|
|
99
|
+
} finally {
|
|
100
|
+
app.forgetCompiledPage(source.path)
|
|
101
|
+
}
|
|
102
|
+
}
|
package/host/response.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { CredentialSession } from "../authority/model.ts"
|
|
2
|
+
|
|
3
|
+
export function privateResponse(response: Response) {
|
|
4
|
+
response.headers.set("Cache-Control", "private, no-store")
|
|
5
|
+
const vary = response.headers.get("Vary")
|
|
6
|
+
if (!vary?.split(",").some((value) => value.trim() === "Cookie")) {
|
|
7
|
+
response.headers.append("Vary", "Cookie")
|
|
8
|
+
}
|
|
9
|
+
return response
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function withReadCache(
|
|
13
|
+
response: Response,
|
|
14
|
+
access: "public" | "private",
|
|
15
|
+
session: CredentialSession,
|
|
16
|
+
) {
|
|
17
|
+
return access === "private" || session.type === "authenticated"
|
|
18
|
+
? privateResponse(response)
|
|
19
|
+
: response
|
|
20
|
+
}
|
package/host/shell.html
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en" data-pmx-document="1" {{ html_attrs }}>
|
|
3
|
+
<head>
|
|
4
|
+
{{ head }}
|
|
5
|
+
</head>
|
|
6
|
+
<body>
|
|
7
|
+
<div
|
|
8
|
+
data-pmx-view
|
|
9
|
+
data-pmx-route="{{ route }}"
|
|
10
|
+
data-pmx-actor="{{ actor }}"
|
|
11
|
+
>
|
|
12
|
+
{{ content }}
|
|
13
|
+
</div>
|
|
14
|
+
</body>
|
|
15
|
+
</html>
|