@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.
Files changed (195) hide show
  1. package/LICENSE.md +172 -0
  2. package/README.md +111 -0
  3. package/actions/actions.ts +307 -0
  4. package/actions/draft.ts +105 -0
  5. package/actions/transaction.ts +164 -0
  6. package/assets.ts +149 -0
  7. package/authority/access.ts +820 -0
  8. package/authority/actors.ts +271 -0
  9. package/authority/agents.ts +95 -0
  10. package/authority/credentials.ts +144 -0
  11. package/authority/model.ts +71 -0
  12. package/authority/sharing.ts +89 -0
  13. package/browser-bundle.ts +61 -0
  14. package/canonical.ts +41 -0
  15. package/cli/dev.ts +134 -0
  16. package/cli/export.ts +74 -0
  17. package/cli/index.ts +52 -0
  18. package/cli/lint.ts +164 -0
  19. package/cli/mv.ts +30 -0
  20. package/cli/perms.ts +182 -0
  21. package/cli/plugin-packages.ts +159 -0
  22. package/cli/plugins.ts +95 -0
  23. package/cli/query.ts +225 -0
  24. package/cli/readme.md +95 -0
  25. package/cli/release.ts +75 -0
  26. package/cli/rm.ts +22 -0
  27. package/cli/serve.ts +100 -0
  28. package/cli/tree.ts +131 -0
  29. package/cli/update.ts +78 -0
  30. package/cli/utils.ts +53 -0
  31. package/core.ts +96 -0
  32. package/database.ts +189 -0
  33. package/dependencies.ts +60 -0
  34. package/dist/pathmx.js +1224 -0
  35. package/document.ts +194 -0
  36. package/environment.ts +110 -0
  37. package/environments/bun-plugins.ts +68 -0
  38. package/environments/bun-publish.ts +194 -0
  39. package/environments/bun.ts +223 -0
  40. package/globals.d.ts +14 -0
  41. package/graph.ts +153 -0
  42. package/host/compiler.ts +314 -0
  43. package/host/create.ts +66 -0
  44. package/host/engine.ts +630 -0
  45. package/host/plugin-host.ts +487 -0
  46. package/host/render.ts +102 -0
  47. package/host/response.ts +20 -0
  48. package/host/shell.html +15 -0
  49. package/host/source-sync.ts +254 -0
  50. package/html-attributes.ts +15 -0
  51. package/html.ts +48 -0
  52. package/icons/LICENSE +43 -0
  53. package/icons/lucide.ts +54 -0
  54. package/index.ts +75 -0
  55. package/invalidation.ts +85 -0
  56. package/log.ts +84 -0
  57. package/ops/href.ts +87 -0
  58. package/ops/index.ts +9 -0
  59. package/ops/move-source.ts +114 -0
  60. package/ops/plan.ts +47 -0
  61. package/ops/remove-source.ts +70 -0
  62. package/package.json +46 -0
  63. package/path.ts +75 -0
  64. package/pattern.ts +314 -0
  65. package/perf.ts +88 -0
  66. package/plugins/actor-path.ts +43 -0
  67. package/plugins/actor-profile.ts +55 -0
  68. package/plugins/agent-browsing.ts +183 -0
  69. package/plugins/app-component.ts +23 -0
  70. package/plugins/block-layout/index.ts +78 -0
  71. package/plugins/block-layout/theme.css +90 -0
  72. package/plugins/callout/index.ts +236 -0
  73. package/plugins/callout/theme.css +138 -0
  74. package/plugins/canonical-links.ts +144 -0
  75. package/plugins/code-highlight/client.ts +89 -0
  76. package/plugins/code-highlight/index.ts +147 -0
  77. package/plugins/code-highlight/theme.css +89 -0
  78. package/plugins/context.ts +131 -0
  79. package/plugins/datatype/Datatype[wdth,wght].woff2 +0 -0
  80. package/plugins/datatype/OFL.txt +96 -0
  81. package/plugins/datatype/index.ts +287 -0
  82. package/plugins/definition-list.ts +236 -0
  83. package/plugins/directive-definitions.ts +13 -0
  84. package/plugins/embedding/client.ts +88 -0
  85. package/plugins/embedding/index.ts +43 -0
  86. package/plugins/image/index.ts +68 -0
  87. package/plugins/image/theme.css +43 -0
  88. package/plugins/include.ts +119 -0
  89. package/plugins/index.ts +91 -0
  90. package/plugins/input/authoring.ts +236 -0
  91. package/plugins/input/config.ts +85 -0
  92. package/plugins/input/index.ts +359 -0
  93. package/plugins/input/model.ts +173 -0
  94. package/plugins/input/state.ts +272 -0
  95. package/plugins/layout.ts +313 -0
  96. package/plugins/literate/client.ts +208 -0
  97. package/plugins/literate/index.ts +742 -0
  98. package/plugins/math/index.ts +140 -0
  99. package/plugins/meta.ts +42 -0
  100. package/plugins/navigation/index.ts +158 -0
  101. package/plugins/navigation/model.ts +364 -0
  102. package/plugins/preset.ts +65 -0
  103. package/plugins/query/index.ts +322 -0
  104. package/plugins/rss/index.ts +46 -0
  105. package/plugins/runtime/actions.ts +62 -0
  106. package/plugins/runtime/client.ts +8 -0
  107. package/plugins/runtime/document.ts +506 -0
  108. package/plugins/runtime/error.ts +154 -0
  109. package/plugins/runtime/heading-links.ts +31 -0
  110. package/plugins/runtime/index.ts +104 -0
  111. package/plugins/runtime/live.ts +51 -0
  112. package/plugins/runtime/navigation.ts +113 -0
  113. package/plugins/runtime/pending.ts +53 -0
  114. package/plugins/runtime/publication.ts +19 -0
  115. package/plugins/runtime/start.ts +177 -0
  116. package/plugins/runtime/static-client.ts +6 -0
  117. package/plugins/runtime/static-live.ts +188 -0
  118. package/plugins/runtime/view-transition.ts +72 -0
  119. package/plugins/runtime/viewport.ts +110 -0
  120. package/plugins/schema.ts +24 -0
  121. package/plugins/scripts.ts +140 -0
  122. package/plugins/source-preview/client.ts +256 -0
  123. package/plugins/source-preview/index.ts +132 -0
  124. package/plugins/source-preview/model.ts +11 -0
  125. package/plugins/source-preview/preview.css +107 -0
  126. package/plugins/styles.ts +145 -0
  127. package/plugins/theme/components.ts +10 -0
  128. package/plugins/theme/foundation.css +358 -0
  129. package/plugins/theme/index.ts +333 -0
  130. package/plugins/theme/prose.css +284 -0
  131. package/plugins/theme/scheme.js +63 -0
  132. package/plugins/theme/toggle.css +44 -0
  133. package/plugins/theme/view-transition.css +68 -0
  134. package/plugins/toc/client.ts +146 -0
  135. package/plugins/toc/index.ts +232 -0
  136. package/plugins/toc/theme.css +172 -0
  137. package/plugins/types.ts +538 -0
  138. package/problem.ts +41 -0
  139. package/project.ts +6 -0
  140. package/publish/build.ts +402 -0
  141. package/publish/concurrent.ts +32 -0
  142. package/publish/identity.ts +63 -0
  143. package/publish/index.ts +11 -0
  144. package/publish/location.ts +167 -0
  145. package/publish/model.ts +58 -0
  146. package/publish/plan.ts +300 -0
  147. package/publish/validate.ts +150 -0
  148. package/query/candidate-index.ts +101 -0
  149. package/query/dependency-index.ts +99 -0
  150. package/query/fields.ts +216 -0
  151. package/query/invalidation.ts +48 -0
  152. package/query/kernel.ts +776 -0
  153. package/query/model.ts +79 -0
  154. package/repository.ts +374 -0
  155. package/runtime-profile.ts +34 -0
  156. package/schema/model.ts +92 -0
  157. package/schema/registry.ts +642 -0
  158. package/schema/standard.ts +226 -0
  159. package/server/context.ts +94 -0
  160. package/server/css-imports.ts +66 -0
  161. package/server/http.ts +333 -0
  162. package/server/page.ts +21 -0
  163. package/server/protocols.ts +12 -0
  164. package/server/representations.ts +66 -0
  165. package/server/router.ts +131 -0
  166. package/server/routes.ts +202 -0
  167. package/server/serve.ts +5 -0
  168. package/server/watch.ts +120 -0
  169. package/source/blocks.ts +71 -0
  170. package/source/format.ts +164 -0
  171. package/source/index.ts +67 -0
  172. package/source/interpolate.ts +63 -0
  173. package/source/markdown/blocks.ts +46 -0
  174. package/source/markdown/comments.ts +32 -0
  175. package/source/markdown/fences.ts +67 -0
  176. package/source/markdown/headings.ts +88 -0
  177. package/source/markdown/index.ts +34 -0
  178. package/source/markdown/inline-code.ts +6 -0
  179. package/source/markdown/links.ts +326 -0
  180. package/source/markdown/lists.ts +223 -0
  181. package/source/markdown/prose.ts +74 -0
  182. package/source/markdown/scan.ts +79 -0
  183. package/source/markdown/text.ts +82 -0
  184. package/source/meta.ts +87 -0
  185. package/source/source.ts +121 -0
  186. package/source/topmatter.ts +57 -0
  187. package/source/utils.ts +21 -0
  188. package/source-diagnostic.ts +63 -0
  189. package/text-file.ts +26 -0
  190. package/util.ts +35 -0
  191. package/view/components.ts +499 -0
  192. package/view/model.ts +91 -0
  193. package/view/projection.ts +162 -0
  194. package/view/session.ts +146 -0
  195. package/view/syntax.ts +372 -0
package/host/create.ts ADDED
@@ -0,0 +1,66 @@
1
+ import { Engine } from "./engine.ts"
2
+ import { ActorDirectory, ActorDirectoryPlugin } from "../authority/actors.ts"
3
+ import { SourceAccessIndex, SourceAccessPlugin } from "../authority/access.ts"
4
+ import type { PathMXEnvironment } from "../environment.ts"
5
+ import type { Log } from "../log.ts"
6
+ import { MetaPlugin } from "../plugins/meta.ts"
7
+ import { SchemaPlugin } from "../plugins/schema.ts"
8
+ import {
9
+ expandPluginContributions,
10
+ type PluginContribution,
11
+ } from "../plugins/preset.ts"
12
+ import { Repository } from "../repository.ts"
13
+ import type { RuntimeProfileOptions } from "../runtime-profile.ts"
14
+ import type { SourceDiagnosticMode } from "../source-diagnostic.ts"
15
+ import { projectDirectory } from "../project.ts"
16
+
17
+ export type CreateAppOptions = {
18
+ root: string
19
+ environment: PathMXEnvironment
20
+ actionsRoot?: string
21
+ stateRoot?: string
22
+ /** Compile every page at startup instead of caching pages on first request. */
23
+ buildCache?: boolean
24
+ plugins?: readonly PluginContribution[]
25
+ runtime?: RuntimeProfileOptions
26
+ diagnostics?: SourceDiagnosticMode
27
+ log?: Log
28
+ }
29
+
30
+ export async function createApp(options: CreateAppOptions) {
31
+ const repo = new Repository(options.root, options.environment)
32
+ const actors = new ActorDirectory()
33
+ const access = new SourceAccessIndex(repo, actors)
34
+ const app = new Engine(
35
+ repo,
36
+ options.environment,
37
+ actors,
38
+ actors.profiles,
39
+ access,
40
+ options.log,
41
+ options.runtime,
42
+ options.diagnostics,
43
+ options.stateRoot ?? projectDirectory(options.root, ".pathmx/state"),
44
+ )
45
+ if (options.actionsRoot) app.actionsRoot = options.actionsRoot
46
+ try {
47
+ app.registerPlugin(ActorDirectoryPlugin(actors))
48
+ app.registerPlugin(SourceAccessPlugin(access))
49
+ app.registerPlugin(MetaPlugin)
50
+ app.registerPlugin(SchemaPlugin)
51
+ for (const plugin of expandPluginContributions(options.plugins ?? [])) {
52
+ app.registerPlugin(plugin)
53
+ }
54
+ app.freezePlugins()
55
+ await app.setupPlugins()
56
+ await app.perf.measureAsync("repo.load", () => repo.load())
57
+ await app.indexAllNodes()
58
+ if (options.diagnostics !== "collect") access.validate()
59
+ await app.readyCredentials()
60
+ if (options.buildCache) await app.buildCache()
61
+ return app
62
+ } catch (error) {
63
+ await app.close().catch(() => undefined)
64
+ throw error
65
+ }
66
+ }
package/host/engine.ts ADDED
@@ -0,0 +1,630 @@
1
+ import type {
2
+ Actor,
3
+ ActorProfiles,
4
+ CredentialSession,
5
+ KnownActorIndex,
6
+ Principal,
7
+ } from "../authority/model"
8
+ import type {
9
+ AssetContext,
10
+ CompiledPage,
11
+ CompileContext,
12
+ DocumentAsset,
13
+ PageContext,
14
+ Plugin,
15
+ PluginFactory,
16
+ ViewContext,
17
+ } from "../plugins/types"
18
+ import {
19
+ type AccessDecision,
20
+ type ProposedSourceChange,
21
+ type ReadAccess,
22
+ type SourceAccessInspection,
23
+ type SourceCapability,
24
+ type SourcePermissionRecord,
25
+ type SourceAccessPolicy,
26
+ } from "../authority/access"
27
+ import {
28
+ indexSourceDocument,
29
+ type Source,
30
+ type SourceBlock,
31
+ } from "../source/index"
32
+ import { type Dependency, type DependencyContext } from "../dependencies"
33
+ import type { RouteInvalidation } from "../invalidation"
34
+ import { Graph } from "../graph"
35
+ import {
36
+ type CommitResult,
37
+ type SourceChangeSet,
38
+ Repository,
39
+ } from "../repository"
40
+ import type { BundledAsset, PathMXEnvironment } from "../environment"
41
+ import { Log } from "../log"
42
+ import { Perf } from "../perf"
43
+ import { defaultActionsRoot } from "../actions/actions"
44
+ import { indexRouteConflicts, publicPath, resolveRoute } from "../server/router"
45
+ import { ActorSourceError } from "../authority/actors.ts"
46
+ import { SourceAccessError } from "../authority/access.ts"
47
+ import {
48
+ runAgentJobWithIndex,
49
+ type AgentAdmission,
50
+ type AgentJob,
51
+ } from "../authority/agents.ts"
52
+ import {
53
+ createAssetBag,
54
+ documentAssetRank,
55
+ renderDocumentAssets,
56
+ } from "../document.ts"
57
+ import { GeneratedAssets } from "../assets.ts"
58
+ import { PluginHost } from "./plugin-host.ts"
59
+ import { PageCompiler } from "./compiler.ts"
60
+ import { SourceSynchronizer } from "./source-sync.ts"
61
+ import { PluginDatabases } from "../database.ts"
62
+ import { projectDirectory } from "../project.ts"
63
+ import { browserBundleAssets } from "../browser-bundle.ts"
64
+ import {
65
+ createRuntimeProfile,
66
+ type RuntimeProfile,
67
+ type RuntimeProfileOptions,
68
+ } from "../runtime-profile.ts"
69
+ import { createView } from "../view/session.ts"
70
+ import {
71
+ SourceDiagnostics,
72
+ type SourceDiagnostic,
73
+ type SourceDiagnosticMode,
74
+ } from "../source-diagnostic.ts"
75
+ import type { ApplicationSchema } from "../schema/model.ts"
76
+ import { Query } from "../query/kernel.ts"
77
+ import type { QueryContribution } from "../query/model.ts"
78
+
79
+ export { PluginRegistrationError } from "./plugin-host.ts"
80
+
81
+ export class Engine {
82
+ environment: PathMXEnvironment
83
+ readonly runtime: RuntimeProfile
84
+ repo: Repository
85
+ graph: Graph
86
+ readonly profiles: ActorProfiles
87
+ actionsRoot: string
88
+ perf = new Perf()
89
+ log: Log
90
+ private bundles = new Map<string, Promise<BundledAsset[]>>()
91
+ private assetRevisions = new Map<string, number>()
92
+ private generatedAssets: GeneratedAssets
93
+ private actorIndex: KnownActorIndex
94
+ private accessPolicy: SourceAccessPolicy
95
+ private authorityErrors = new Map<string, string>()
96
+ private sourceDiagnosticStore: SourceDiagnostics
97
+ private pluginHost: PluginHost
98
+ readonly queries: Query
99
+ private compiler: PageCompiler
100
+ private sourceSync: SourceSynchronizer
101
+
102
+ constructor(
103
+ repo: Repository,
104
+ environment: PathMXEnvironment,
105
+ actors: KnownActorIndex,
106
+ profiles: ActorProfiles,
107
+ access: SourceAccessPolicy,
108
+ log = new Log(),
109
+ runtime?: RuntimeProfileOptions,
110
+ diagnostics: SourceDiagnosticMode = "throw",
111
+ stateRoot = projectDirectory(repo.rootPath, ".pathmx/state"),
112
+ ) {
113
+ this.repo = repo
114
+ this.environment = environment
115
+ this.runtime = createRuntimeProfile(runtime)
116
+ this.generatedAssets =
117
+ this.runtime.target === "static"
118
+ ? new GeneratedAssets(Number.POSITIVE_INFINITY)
119
+ : new GeneratedAssets()
120
+ this.graph = new Graph(repo)
121
+ this.actorIndex = actors
122
+ this.profiles = profiles
123
+ this.accessPolicy = access
124
+ this.log = log
125
+ this.pluginHost = new PluginHost(
126
+ new PluginDatabases(environment.database, stateRoot),
127
+ )
128
+ this.queries = new Query(this)
129
+ this.compiler = new PageCompiler(this, this.pluginHost)
130
+ this.sourceSync = new SourceSynchronizer(
131
+ this,
132
+ this.pluginHost,
133
+ this.compiler,
134
+ )
135
+ this.sourceDiagnosticStore = new SourceDiagnostics(diagnostics, log)
136
+ this.actionsRoot = defaultActionsRoot(repo.rootPath)
137
+ }
138
+
139
+ registerPlugin(plugin: PluginFactory | Plugin) {
140
+ this.pluginHost.register(plugin)
141
+ }
142
+
143
+ freezePlugins() {
144
+ this.pluginHost.freeze()
145
+ }
146
+
147
+ setupPlugins() {
148
+ return this.pluginHost.setup(this)
149
+ }
150
+
151
+ appContext(plugin: string) {
152
+ return this.pluginHost.appContext(plugin, this)
153
+ }
154
+
155
+ get plugins() {
156
+ return this.pluginHost.plugins
157
+ }
158
+
159
+ get schema(): ApplicationSchema {
160
+ return this.pluginHost.schema
161
+ }
162
+
163
+ registeredQuery(reference: string | QueryContribution) {
164
+ return this.pluginHost.query(reference)
165
+ }
166
+
167
+ actor(id: string) {
168
+ return this.actorIndex.findActorById(id)
169
+ }
170
+
171
+ get pageCache() {
172
+ return this.compiler.pageCache
173
+ }
174
+
175
+ dependentsOf(dependency: string | Dependency) {
176
+ return this.compiler.dependentsOf(dependency)
177
+ }
178
+
179
+ dependenciesOf(path: string) {
180
+ return this.compiler.dependenciesOf(path)
181
+ }
182
+
183
+ /** Bundle a browser entrypoint once per Engine; concurrent calls share one build. */
184
+ bundle(entrypoint: string | URL) {
185
+ const key = String(entrypoint)
186
+ let pending = this.bundles.get(key)
187
+ if (!pending) {
188
+ pending = this.perf.measureAsync(
189
+ "bundle",
190
+ () => this.environment.bundle(entrypoint),
191
+ key,
192
+ )
193
+ this.bundles.set(key, pending)
194
+ }
195
+ return pending
196
+ }
197
+
198
+ /** @internal Create the asset collector shared by host compilation paths. */
199
+ documentAssetBag(assets: DocumentAsset[] = []) {
200
+ const bag = createAssetBag(assets)
201
+ const addBrowserBundle: AssetContext["addBrowserBundle"] = async (
202
+ entrypoint,
203
+ name,
204
+ ) => {
205
+ bag.addAsset(...(await browserBundleAssets(this, entrypoint, name)))
206
+ }
207
+ return { ...bag, addBrowserBundle }
208
+ }
209
+
210
+ async documentAssets(page?: CompiledPage) {
211
+ const { assets, addAsset, addBrowserBundle } = this.documentAssetBag()
212
+ const ctx = { page, addAsset, addBrowserBundle }
213
+ await this.pluginHost.documentAssets(ctx, this)
214
+ addAsset(...(page?.assets ?? []))
215
+ return assets
216
+ .map((asset) => this.resolveDocumentAsset(asset))
217
+ .sort((a, b) => documentAssetRank(a) - documentAssetRank(b))
218
+ }
219
+
220
+ servedAsset(pathname: string) {
221
+ return this.generatedAssets.get(pathname)
222
+ }
223
+
224
+ /** @internal Enumerate retained generated artifacts during static publication. */
225
+ generatedAssetEntries() {
226
+ return this.generatedAssets.entries()
227
+ }
228
+
229
+ /** Publish an immutable, content-hashed binary asset under `/.pmx/`. */
230
+ async publishAsset(name: string, blob: Blob, contentType = blob.type) {
231
+ return this.generatedAssets.publishBlob(name, blob, contentType)
232
+ }
233
+
234
+ onSync(listener: (event: RouteInvalidation) => void) {
235
+ return this.sourceSync.onSync(listener)
236
+ }
237
+
238
+ private resolveDocumentAsset(asset: DocumentAsset): DocumentAsset {
239
+ if (asset.type === "stylesheet" && "name" in asset) {
240
+ const href = this.storeNamed(
241
+ asset.name,
242
+ asset.text,
243
+ "text/css; charset=utf-8",
244
+ )
245
+ return {
246
+ type: "stylesheet",
247
+ href,
248
+ ...(asset.media ? { media: asset.media } : {}),
249
+ }
250
+ }
251
+ if (asset.type === "script" && "name" in asset) {
252
+ const href = this.storeNamed(
253
+ asset.name,
254
+ asset.text,
255
+ "text/javascript; charset=utf-8",
256
+ )
257
+ return { type: "script", href, module: asset.module }
258
+ }
259
+ if (asset.type === "script" && "source" in asset) {
260
+ const revision = this.assetRevisions.get(asset.source)
261
+ const href = revision ? `${asset.source}?pmx=${revision}` : asset.source
262
+ return { type: "script", href, module: asset.module }
263
+ }
264
+ return asset
265
+ }
266
+
267
+ bumpAssetRevisions(paths: Iterable<string>) {
268
+ for (const path of paths) {
269
+ const graphPath = this.repo.graphPath(path)
270
+ this.assetRevisions.set(
271
+ graphPath,
272
+ (this.assetRevisions.get(graphPath) ?? 0) + 1,
273
+ )
274
+ }
275
+ }
276
+
277
+ private storeNamed(name: string, text: string, contentType: string) {
278
+ return this.generatedAssets.publishText(name, text, contentType)
279
+ }
280
+
281
+ async documentHead(page?: CompiledPage) {
282
+ return renderDocumentAssets(await this.documentAssets(page))
283
+ }
284
+
285
+ resolve(pathname: string) {
286
+ const route = resolveRoute(this.repo, pathname)
287
+ return route?.type === "page" ? route.source : undefined
288
+ }
289
+
290
+ resolveCredentials(request: Request) {
291
+ return this.pluginHost.resolveCredentials(request, this.actorIndex, this)
292
+ }
293
+
294
+ /** Create a lightweight live View from a host-admitted credential session. */
295
+ view(session: CredentialSession) {
296
+ return createView(this, session)
297
+ }
298
+
299
+ credentialRequest(request: Request) {
300
+ return this.pluginHost.credentialRequest(request, this.actorIndex)
301
+ }
302
+
303
+ readyCredentials() {
304
+ return this.pluginHost.readyCredentials(this.actorIndex)
305
+ }
306
+
307
+ serverRoute(request: Request, url = new URL(request.url), publicUrl = url) {
308
+ return this.pluginHost.serverRoute(request, url, this, publicUrl)
309
+ }
310
+
311
+ /** @internal Test whether an optional behavior-owning Plugin is installed. */
312
+ hasPlugin(id: string) {
313
+ return this.pluginHost.has(id)
314
+ }
315
+
316
+ /** @internal Resolve a namespaced Action contributed by a Plugin. */
317
+ pluginAction(name: string) {
318
+ return this.pluginHost.action(name)
319
+ }
320
+
321
+ /** @internal Project contextual Actions registered by Plugins. */
322
+ actionAffordances(principal: Principal, target: Source) {
323
+ return this.pluginHost.actionAffordances(principal, target, this)
324
+ }
325
+
326
+ closeCredentials() {
327
+ return this.pluginHost.closeCredentials()
328
+ }
329
+
330
+ close() {
331
+ return this.pluginHost.close(this)
332
+ }
333
+
334
+ homeActorId(path: string) {
335
+ return this.actorIndex.findHomeOwner(path)?.id
336
+ }
337
+
338
+ /** Admit a configured Agent without exposing the private Actor directory. */
339
+ runAgentJob(admission: AgentAdmission, job: AgentJob) {
340
+ return runAgentJobWithIndex(this, this.actorIndex, admission, job)
341
+ }
342
+
343
+ readAccess(principal: Principal, target: string | Source): ReadAccess {
344
+ return this.accessPolicy.readAccess(principal, target)
345
+ }
346
+
347
+ inspectSourceAccess(
348
+ principal: Principal,
349
+ target: string | Source,
350
+ ): SourceAccessInspection {
351
+ return this.accessPolicy.inspect(principal, target)
352
+ }
353
+
354
+ /** Privileged repository inspection for administrative tools such as the CLI. */
355
+ inspectSourcePermissions(target: Source): readonly SourcePermissionRecord[] {
356
+ return [
357
+ ...this.actorIndex.all().map((actor) => ({
358
+ sourceId: target.id,
359
+ actorId: actor.id,
360
+ ...this.accessPolicy.inspect({ type: "actor", actor }, target),
361
+ })),
362
+ {
363
+ sourceId: target.id,
364
+ actorId: null,
365
+ ...this.accessPolicy.inspect({ type: "anonymous" }, target),
366
+ },
367
+ ]
368
+ }
369
+
370
+ /** Privileged repository inspection; unlike a credential session, this does not impersonate. */
371
+ inspectActorPermissions(
372
+ actorId: string,
373
+ ): readonly SourcePermissionRecord[] | undefined {
374
+ const actor = this.actorIndex.findActorById(actorId)
375
+ if (!actor) return
376
+ const principal = { type: "actor" as const, actor }
377
+ return this.repo
378
+ .all()
379
+ .sort((a, b) => a.id.localeCompare(b.id))
380
+ .map((source) => ({
381
+ sourceId: source.id,
382
+ actorId,
383
+ ...this.accessPolicy.inspect(principal, source),
384
+ }))
385
+ }
386
+
387
+ hasSourceCapability(
388
+ principal: Principal,
389
+ target: string | Source,
390
+ capability: SourceCapability,
391
+ ) {
392
+ return this.accessPolicy.hasCapability(principal, target, capability)
393
+ }
394
+
395
+ authorizeChange(actor: Actor, change: ProposedSourceChange): AccessDecision {
396
+ return this.accessPolicy.authorizeChange(actor, change)
397
+ }
398
+
399
+ canEmbed(container: string, dependency: string) {
400
+ return this.accessPolicy.canEmbed(container, dependency)
401
+ }
402
+
403
+ forgetNode(nodePath: string) {
404
+ this.compiler.unindexSource(nodePath)
405
+ this.sourceDiagnosticStore.delete(nodePath)
406
+ this.pluginHost.unindex(nodePath, this)
407
+ this.queries.unindexSource(nodePath)
408
+ }
409
+
410
+ /** @internal Clear a degraded authority record after its Source is removed. */
411
+ clearAuthorityError(sourceId: string) {
412
+ this.authorityErrors.delete(sourceId)
413
+ }
414
+
415
+ /** @internal Expected authoring failures collected while indexing. */
416
+ sourceDiagnostics(): readonly SourceDiagnostic[] {
417
+ return this.sourceDiagnosticStore.all()
418
+ }
419
+
420
+ indexSource(source: Source, tolerateAuthorityErrors = false) {
421
+ return this.perf.measure(
422
+ "index.source",
423
+ () => {
424
+ this.forgetNode(source.path)
425
+ this.compiler.indexSource(source)
426
+ this.authorityErrors.delete(source.id)
427
+ const capture = (error: unknown) => {
428
+ if (this.sourceDiagnosticStore.capture(error)) return true
429
+ if (
430
+ !tolerateAuthorityErrors ||
431
+ !(
432
+ error instanceof ActorSourceError ||
433
+ error instanceof SourceAccessError
434
+ )
435
+ ) {
436
+ return false
437
+ }
438
+ this.authorityErrors.set(source.id, error.message)
439
+ this.log.error("authority.degraded", {
440
+ source: source.id,
441
+ message: error.message,
442
+ })
443
+ return true
444
+ }
445
+ try {
446
+ indexSourceDocument(source, this.environment.yaml)
447
+ } catch (error) {
448
+ if (!capture(error)) throw error
449
+ return
450
+ }
451
+ this.pluginHost.index(source, this, capture)
452
+ this.queries.indexSource(source)
453
+ },
454
+ source.path,
455
+ )
456
+ }
457
+
458
+ async indexAllNodes() {
459
+ this.log.info("index", { sources: this.repo.all().length })
460
+ for (const source of this.repo.all()) this.indexSource(source)
461
+ for (const conflict of indexRouteConflicts(this.repo)) {
462
+ this.log.warn("route.index.conflict", {
463
+ dir: conflict.dir,
464
+ winner: conflict.winner.path,
465
+ shadowed: conflict.shadowed.map((source) => source.path),
466
+ rule: "first index source by id, codepoint order",
467
+ })
468
+ }
469
+ await this.perf.measureAsync("graph.rebuild", () => this.graph.rebuild())
470
+ }
471
+
472
+ authorityReadiness() {
473
+ const errors = [
474
+ ...[...this.authorityErrors].map(([sourceId, message]) => ({
475
+ sourceId,
476
+ message,
477
+ })),
478
+ ...(this.accessPolicy.diagnostics?.() ?? []),
479
+ ]
480
+ .filter(
481
+ (error, index, all) =>
482
+ all.findIndex(
483
+ (candidate) =>
484
+ candidate.sourceId === error.sourceId &&
485
+ candidate.message === error.message,
486
+ ) === index,
487
+ )
488
+ .sort(
489
+ (a, b) =>
490
+ a.sourceId.localeCompare(b.sourceId) ||
491
+ a.message.localeCompare(b.message),
492
+ )
493
+ return errors.length
494
+ ? {
495
+ type: "degraded" as const,
496
+ errors,
497
+ }
498
+ : { type: "ready" as const, errors: [] }
499
+ }
500
+
501
+ isAuthorized(source: Source, actor: Actor) {
502
+ return this.readAccess({ type: "actor", actor }, source) !== "denied"
503
+ }
504
+
505
+ isAuthorizedPath(path: string, actor: Actor) {
506
+ const source = this.repo.atPath(path) ?? this.repo.get(path)
507
+ if (source) return this.isAuthorized(source, actor)
508
+ return this.readAccess({ type: "actor", actor }, path) !== "denied"
509
+ }
510
+
511
+ compilePage(source: Source) {
512
+ return this.compiler.compilePage(source)
513
+ }
514
+
515
+ /** @internal Release state retained only for live recompilation. */
516
+ forgetCompiledPage(path: string) {
517
+ this.compiler.forgetCompiled(path)
518
+ }
519
+
520
+ ensurePage(source: Source) {
521
+ return this.compiler.ensurePage(source)
522
+ }
523
+
524
+ compileDocument(
525
+ source: Source,
526
+ dependencies: DependencyContext,
527
+ excludedPluginId: string,
528
+ ) {
529
+ return this.compiler.compileDocument(source, dependencies, excludedPluginId)
530
+ }
531
+
532
+ compileBlock(
533
+ source: Source,
534
+ block: SourceBlock,
535
+ dependencies?: DependencyContext,
536
+ options?: { id?: string },
537
+ scope?: CompileContext["scope"],
538
+ ) {
539
+ return this.compiler.compileBlock(
540
+ source,
541
+ block,
542
+ dependencies,
543
+ options,
544
+ scope,
545
+ )
546
+ }
547
+
548
+ isPage(source: Source) {
549
+ return this.compiler.isPage(source)
550
+ }
551
+
552
+ buildCache() {
553
+ return this.compiler.buildCache()
554
+ }
555
+
556
+ dropNode(nodePath: string) {
557
+ return this.sourceSync.dropNode(nodePath)
558
+ }
559
+
560
+ commit(changeSet: SourceChangeSet) {
561
+ return this.sourceSync.commit(changeSet)
562
+ }
563
+
564
+ commitPrepared(prepare: () => Promise<SourceChangeSet>) {
565
+ return this.sourceSync.commitPrepared(prepare)
566
+ }
567
+
568
+ refresh(
569
+ result: CommitResult,
570
+ before = new Map<string, Source | undefined>(),
571
+ ) {
572
+ return this.sourceSync.refresh(result, before)
573
+ }
574
+
575
+ syncSource(path: string) {
576
+ return this.sourceSync.syncSource(path)
577
+ }
578
+
579
+ invalidateAsset(path: string) {
580
+ return this.sourceSync.invalidateAsset(path)
581
+ }
582
+
583
+ invalidateAssets(paths: Iterable<string>) {
584
+ return this.sourceSync.invalidateAssets(paths)
585
+ }
586
+
587
+ syncFromStorage(relPath: string) {
588
+ return this.sourceSync.syncFromStorage(relPath)
589
+ }
590
+
591
+ rewrite(
592
+ session: CredentialSession,
593
+ html: string,
594
+ source: Source,
595
+ init?: ResponseInit,
596
+ ) {
597
+ return this.perf.measureAsync(
598
+ "rewrite",
599
+ () => {
600
+ const view = this.view(session)
601
+ return this.rewriteWithPlugins(
602
+ html,
603
+ {
604
+ view,
605
+ session,
606
+ principal: view.principal,
607
+ source,
608
+ route: publicPath(source),
609
+ },
610
+ init,
611
+ )
612
+ },
613
+ source.path,
614
+ )
615
+ }
616
+
617
+ private async rewriteWithPlugins(
618
+ html: string,
619
+ ctx: ViewContext,
620
+ init?: ResponseInit,
621
+ ) {
622
+ return this.pluginHost.renderView(
623
+ html,
624
+ this.compiler.planFor(ctx.source.path),
625
+ ctx,
626
+ this,
627
+ init,
628
+ )
629
+ }
630
+ }