@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
@@ -0,0 +1,164 @@
1
+ import type { ProposedSourceChange } from "../authority/access.ts"
2
+ import { AccessDeniedError } from "../authority/access.ts"
3
+ import type { Engine } from "../host/engine.ts"
4
+ import type { SourceChange, SourceChangeSet } from "../repository.ts"
5
+ import { createSource } from "../source/index.ts"
6
+ import type { Actor } from "../authority/model.ts"
7
+ import { canonicalSourceId } from "../canonical.ts"
8
+ import { createSourceDraft, type SourceDraft } from "./draft.ts"
9
+
10
+ export type SourceTransaction = {
11
+ create(path: string, content: string): Promise<void>
12
+ update(path: string, content: string): Promise<void>
13
+ delete(path: string): Promise<void>
14
+ read(path: string): Promise<string>
15
+ exists(path: string): Promise<boolean>
16
+ source(path: string): SourceDraft
17
+ }
18
+
19
+ export type StagedTransaction = SourceTransaction & {
20
+ changeSet(): Promise<SourceChangeSet>
21
+ }
22
+
23
+ type TransactionAccess = Readonly<{
24
+ readable(path: string): boolean
25
+ authorize(change: ProposedSourceChange): void
26
+ }>
27
+
28
+ function createStagedTransaction(
29
+ engine: Engine,
30
+ access: TransactionAccess,
31
+ ): StagedTransaction {
32
+ const repo = engine.repo
33
+ const overlay = new Map<string, string | null>()
34
+ const drafts = new Map<string, ReturnType<typeof createSourceDraft>>()
35
+
36
+ const keyFor = (sourceId: string) => {
37
+ const normalized = canonicalSourceId(sourceId)
38
+ return repo.get(normalized)?.path ?? `${normalized}.md`
39
+ }
40
+
41
+ const existsKey = async (key: string) => {
42
+ if (!access.readable(key)) return false
43
+ if (overlay.has(key)) return overlay.get(key) !== null
44
+ return Boolean(repo.atPath(key) ?? repo.get(repo.pathToId(key)))
45
+ }
46
+
47
+ const readKey = async (key: string) => {
48
+ if (!access.readable(key)) throw new AccessDeniedError()
49
+ if (overlay.has(key)) {
50
+ const pending = overlay.get(key)
51
+ if (pending == null) throw new Error(`Path does not exist: ${key}`)
52
+ return pending
53
+ }
54
+ const source = repo.atPath(key) ?? repo.get(repo.pathToId(key))
55
+ if (source) return source.content
56
+ throw new Error(`Path does not exist: ${key}`)
57
+ }
58
+
59
+ const createKey = async (key: string, content: string) => {
60
+ if (await existsKey(key)) throw new Error(`Path already exists: ${key}`)
61
+ overlay.set(key, content)
62
+ }
63
+
64
+ const updateKey = async (key: string, content: string) => {
65
+ if (!access.readable(key)) throw new AccessDeniedError()
66
+ if (!(await existsKey(key))) throw new Error(`Path does not exist: ${key}`)
67
+ overlay.set(key, content)
68
+ }
69
+
70
+ const tx: StagedTransaction = {
71
+ create(sourceId, content) {
72
+ return createKey(keyFor(sourceId), content)
73
+ },
74
+ update(sourceId, content) {
75
+ return updateKey(keyFor(sourceId), content)
76
+ },
77
+ async delete(path) {
78
+ const key = keyFor(path)
79
+ if (!access.readable(key)) throw new AccessDeniedError()
80
+ if (!(await existsKey(key)))
81
+ throw new Error(`Path does not exist: ${key}`)
82
+ overlay.set(key, null)
83
+ },
84
+ read(sourceId) {
85
+ return readKey(keyFor(sourceId))
86
+ },
87
+ exists(sourceId) {
88
+ return existsKey(keyFor(sourceId))
89
+ },
90
+ source(path) {
91
+ const key = keyFor(path)
92
+ let draft = drafts.get(key)
93
+ if (!draft) {
94
+ draft = createSourceDraft(
95
+ key,
96
+ { create: createKey, update: updateKey, read: readKey },
97
+ engine.environment.yaml,
98
+ )
99
+ drafts.set(key, draft)
100
+ }
101
+ return draft
102
+ },
103
+ async changeSet() {
104
+ for (const draft of drafts.values()) await draft.flush()
105
+ const changes: SourceChange[] = []
106
+ for (const [path, content] of overlay) {
107
+ const before = repo.atPath(path)
108
+ const sourceId = before?.id ?? repo.pathToId(path)
109
+ const proposed =
110
+ content == null
111
+ ? before
112
+ ? { type: "delete" as const, sourceId, before }
113
+ : undefined
114
+ : (() => {
115
+ const after = createSource(
116
+ path,
117
+ content,
118
+ engine.environment.yaml,
119
+ )
120
+ if (!before)
121
+ return { type: "create" as const, sourceId: after.id, after }
122
+ return {
123
+ type: "update" as const,
124
+ operation: "content" as const,
125
+ sourceId: before.id,
126
+ before,
127
+ after,
128
+ }
129
+ })()
130
+ if (!proposed) throw new Error(`Path does not exist: ${sourceId}`)
131
+ access.authorize(proposed)
132
+ if (content == null) changes.push({ type: "delete", path })
133
+ else
134
+ changes.push({ type: before ? "replace" : "create", path, content })
135
+ }
136
+ overlay.clear()
137
+ drafts.clear()
138
+ return { changes }
139
+ },
140
+ }
141
+ return tx
142
+ }
143
+
144
+ export function createTransaction(
145
+ engine: Engine,
146
+ actor: Actor,
147
+ ): StagedTransaction {
148
+ return createStagedTransaction(engine, {
149
+ readable(path) {
150
+ return (
151
+ engine.readAccess(
152
+ { type: "actor", actor },
153
+ engine.repo.atPath(path) ?? engine.repo.pathToId(path),
154
+ ) !== "denied"
155
+ )
156
+ },
157
+ authorize(change) {
158
+ const decision = engine.authorizeChange(actor, change)
159
+ if (decision.type === "denied") {
160
+ throw new AccessDeniedError(decision.code)
161
+ }
162
+ },
163
+ })
164
+ }
package/assets.ts ADDED
@@ -0,0 +1,149 @@
1
+ const ASSET_NAME = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/
2
+ export const GENERATED_ASSET_LIMIT = 128
3
+ const encoder = new TextEncoder()
4
+
5
+ export type ServedAsset = Readonly<{
6
+ body: string | Blob
7
+ contentType: string
8
+ }>
9
+
10
+ type StoredAsset = {
11
+ name: string
12
+ asset: ServedAsset
13
+ }
14
+
15
+ function avalanche(value: number) {
16
+ value ^= value >>> 16
17
+ value = Math.imul(value, 0x85ebca6b)
18
+ value ^= value >>> 13
19
+ value = Math.imul(value, 0xc2b2ae35)
20
+ return (value ^ (value >>> 16)) >>> 0
21
+ }
22
+
23
+ /** Fast streaming 64-bit fingerprint; cache identity, not authentication. */
24
+ class AssetFingerprint {
25
+ private fnv = 0x811c9dc5
26
+ private djb = 0x1505
27
+
28
+ update(bytes: Uint8Array) {
29
+ for (let index = 0; index < bytes.length; index++) {
30
+ const byte = bytes[index]!
31
+ this.fnv = Math.imul(this.fnv ^ byte, 0x01000193)
32
+ this.djb = Math.imul(this.djb, 33) ^ byte
33
+ }
34
+ }
35
+
36
+ digest() {
37
+ return [avalanche(this.fnv), avalanche(this.djb)]
38
+ .map((value) => value.toString(16).padStart(8, "0"))
39
+ .join("")
40
+ }
41
+ }
42
+
43
+ function fingerprintHeader(fingerprint: AssetFingerprint, contentType: string) {
44
+ fingerprint.update(encoder.encode(contentType))
45
+ fingerprint.update(new Uint8Array([0]))
46
+ }
47
+
48
+ function textFingerprint(text: string, contentType: string) {
49
+ const fingerprint = new AssetFingerprint()
50
+ fingerprintHeader(fingerprint, contentType)
51
+ fingerprint.update(encoder.encode(text))
52
+ return fingerprint.digest()
53
+ }
54
+
55
+ export async function blobFingerprint(blob: Blob, contentType: string) {
56
+ const fingerprint = new AssetFingerprint()
57
+ fingerprintHeader(fingerprint, contentType)
58
+ const reader = blob.stream().getReader()
59
+ try {
60
+ while (true) {
61
+ const chunk = await reader.read()
62
+ if (chunk.done) return fingerprint.digest()
63
+ fingerprint.update(chunk.value)
64
+ }
65
+ } finally {
66
+ reader.releaseLock()
67
+ }
68
+ }
69
+
70
+ function validateAssetName(name: string) {
71
+ if (!ASSET_NAME.test(name)) {
72
+ throw new Error(`Invalid generated asset name: ${name}`)
73
+ }
74
+ }
75
+
76
+ function assetHref(name: string, hash: string) {
77
+ const dot = name.lastIndexOf(".")
78
+ const stem = dot === -1 ? name : name.slice(0, dot)
79
+ const ext = dot === -1 ? "" : name.slice(dot)
80
+ return `/.pmx/${stem}-${hash}${ext}`
81
+ }
82
+
83
+ /**
84
+ * Publishes generated assets into the local bounded cache. A future CDN or
85
+ * object-storage backend should extract byte retention behind a general
86
+ * `AssetStore` (`put`, `get`, `href`) while keeping fingerprinting and key
87
+ * policy here. Repository assets may share that store, but keep their own
88
+ * routing and access rules; this LRU must not be their durable CDN origin.
89
+ */
90
+ export class GeneratedAssets {
91
+ private assets = new Map<string, StoredAsset>()
92
+ private latest = new Map<string, ServedAsset & { href: string }>()
93
+
94
+ constructor(private limit = GENERATED_ASSET_LIMIT) {}
95
+
96
+ get(href: string) {
97
+ const stored = this.assets.get(href)
98
+ if (!stored) return
99
+ this.assets.delete(href)
100
+ this.assets.set(href, stored)
101
+ return stored.asset
102
+ }
103
+
104
+ entries() {
105
+ return [...this.assets].map(
106
+ ([href, stored]) => [href, stored.asset] as const,
107
+ )
108
+ }
109
+
110
+ publishText(name: string, text: string, contentType: string) {
111
+ validateAssetName(name)
112
+ const current = this.latest.get(name)
113
+ if (current?.body === text && current.contentType === contentType) {
114
+ this.get(current.href)
115
+ return current.href
116
+ }
117
+ const href = assetHref(name, textFingerprint(text, contentType))
118
+ this.retain(name, href, { body: text, contentType })
119
+ return href
120
+ }
121
+
122
+ async publishBlob(name: string, blob: Blob, contentType = blob.type) {
123
+ validateAssetName(name)
124
+ contentType ||= "application/octet-stream"
125
+ const current = this.latest.get(name)
126
+ if (current?.body === blob && current.contentType === contentType) {
127
+ this.get(current.href)
128
+ return current.href
129
+ }
130
+ const href = assetHref(name, await blobFingerprint(blob, contentType))
131
+ this.retain(name, href, { body: blob, contentType })
132
+ return href
133
+ }
134
+
135
+ private retain(name: string, href: string, asset: ServedAsset) {
136
+ this.assets.delete(href)
137
+ this.assets.set(href, { name, asset })
138
+ this.latest.set(name, { ...asset, href })
139
+
140
+ while (this.assets.size > this.limit) {
141
+ const oldestHref = this.assets.keys().next().value!
142
+ const { name: oldestName } = this.assets.get(oldestHref)!
143
+ this.assets.delete(oldestHref)
144
+ if (this.latest.get(oldestName)?.href === oldestHref) {
145
+ this.latest.delete(oldestName)
146
+ }
147
+ }
148
+ }
149
+ }