@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/database.ts ADDED
@@ -0,0 +1,189 @@
1
+ import type { PathMXDatabaseEnvironment } from "./environment.ts"
2
+
3
+ export type SqlValue = string | number | bigint | Uint8Array | null
4
+ export type SqlParams = readonly SqlValue[] | Readonly<Record<string, SqlValue>>
5
+
6
+ export type SqlResult = Readonly<{
7
+ changes: number
8
+ lastInsertRowId?: number | bigint
9
+ }>
10
+
11
+ export type SqlDatabase = Readonly<{
12
+ all<Row>(sql: string, params?: SqlParams): Promise<readonly Row[]>
13
+ get<Row>(sql: string, params?: SqlParams): Promise<Row | undefined>
14
+ run(sql: string, params?: SqlParams): Promise<SqlResult>
15
+ exec(sql: string): Promise<void>
16
+ transaction<T>(run: (database: SqlDatabase) => Promise<T>): Promise<T>
17
+ }>
18
+
19
+ export type SqlConnection = SqlDatabase & Readonly<{ close(): Promise<void> }>
20
+
21
+ export type DatabaseMigration = Readonly<{
22
+ id: string
23
+ sql: string
24
+ }>
25
+
26
+ export type PluginDatabase = Readonly<{
27
+ migrations?: readonly DatabaseMigration[]
28
+ }>
29
+
30
+ const MIGRATION_ID = /^[a-z0-9]+(?:[-_.][a-z0-9]+)*$/
31
+ const MIGRATIONS_TABLE = "_pathmx_migrations"
32
+
33
+ async function checksum(text: string) {
34
+ const bytes = new TextEncoder().encode(text)
35
+ const digest = await crypto.subtle.digest("SHA-256", bytes)
36
+ return [...new Uint8Array(digest)]
37
+ .map((value) => value.toString(16).padStart(2, "0"))
38
+ .join("")
39
+ }
40
+
41
+ function validateMigrations(migrations: readonly DatabaseMigration[]) {
42
+ const ids = new Set<string>()
43
+ for (const migration of migrations) {
44
+ if (!MIGRATION_ID.test(migration.id)) {
45
+ throw new Error(`Invalid database migration id: ${migration.id}`)
46
+ }
47
+ if (ids.has(migration.id)) {
48
+ throw new Error(`Duplicate database migration id: ${migration.id}`)
49
+ }
50
+ if (!migration.sql.trim()) {
51
+ throw new Error(`Database migration ${migration.id} is empty.`)
52
+ }
53
+ ids.add(migration.id)
54
+ }
55
+ }
56
+
57
+ async function migrate(
58
+ plugin: string,
59
+ database: SqlConnection,
60
+ migrations: readonly DatabaseMigration[],
61
+ ) {
62
+ validateMigrations(migrations)
63
+ await database.transaction(async (transaction) => {
64
+ await transaction.exec(`
65
+ CREATE TABLE IF NOT EXISTS ${MIGRATIONS_TABLE} (
66
+ id TEXT PRIMARY KEY,
67
+ checksum TEXT NOT NULL,
68
+ applied_at TEXT NOT NULL
69
+ )
70
+ `)
71
+ const applied = await transaction.all<{ id: string; checksum: string }>(
72
+ `SELECT id, checksum FROM ${MIGRATIONS_TABLE} ORDER BY rowid`,
73
+ )
74
+ for (const [index, current] of applied.entries()) {
75
+ const migration = migrations[index]
76
+ if (!migration || migration.id !== current.id) {
77
+ throw new Error(`Database migration history changed: ${plugin}`)
78
+ }
79
+ const expected = await checksum(migration.sql)
80
+ if (current.checksum !== expected) {
81
+ throw new Error(
82
+ `Applied database migration changed: ${plugin}/${migration.id}`,
83
+ )
84
+ }
85
+ }
86
+ for (const migration of migrations.slice(applied.length)) {
87
+ const expected = await checksum(migration.sql)
88
+ await transaction.exec(migration.sql)
89
+ await transaction.run(
90
+ `INSERT INTO ${MIGRATIONS_TABLE} (id, checksum, applied_at) VALUES (?, ?, ?)`,
91
+ [migration.id, expected, new Date().toISOString()],
92
+ )
93
+ }
94
+ })
95
+ }
96
+
97
+ function lazyDatabase(open: () => Promise<SqlConnection>): SqlDatabase {
98
+ return Object.freeze({
99
+ all: <Row>(sql: string, params?: SqlParams) =>
100
+ open().then((database) => database.all<Row>(sql, params)),
101
+ get: <Row>(sql: string, params?: SqlParams) =>
102
+ open().then((database) => database.get<Row>(sql, params)),
103
+ run: (sql: string, params?: SqlParams) =>
104
+ open().then((database) => database.run(sql, params)),
105
+ exec: (sql: string) => open().then((database) => database.exec(sql)),
106
+ transaction: <T>(run: (database: SqlDatabase) => Promise<T>) =>
107
+ open().then((database) => database.transaction(run)),
108
+ })
109
+ }
110
+
111
+ type RegisteredDatabase = {
112
+ handle: SqlDatabase
113
+ connection(): Promise<SqlConnection> | undefined
114
+ }
115
+
116
+ /** Own lazy, isolated plugin databases and their migration lifecycle. */
117
+ export class PluginDatabases {
118
+ private registered = new Map<string, RegisteredDatabase>()
119
+ private closing?: Promise<void>
120
+
121
+ constructor(
122
+ private environment: PathMXDatabaseEnvironment | undefined,
123
+ private stateRoot: string,
124
+ ) {}
125
+
126
+ prepare(definition: PluginDatabase) {
127
+ const migrations = (definition.migrations ?? []).map((migration) =>
128
+ Object.freeze({ ...migration }),
129
+ )
130
+ validateMigrations(migrations)
131
+ return Object.freeze({ migrations: Object.freeze(migrations) })
132
+ }
133
+
134
+ register(plugin: string, definition: PluginDatabase) {
135
+ const prepared = this.prepare(definition)
136
+ if (this.registered.has(plugin)) {
137
+ throw new Error(`Duplicate plugin database: ${plugin}`)
138
+ }
139
+ let connection: Promise<SqlConnection> | undefined
140
+ const registered: RegisteredDatabase = {
141
+ handle: lazyDatabase(
142
+ () => (connection ??= this.create(plugin, prepared)),
143
+ ),
144
+ connection: () => connection,
145
+ }
146
+ this.registered.set(plugin, registered)
147
+ }
148
+
149
+ get(plugin: string) {
150
+ return this.registered.get(plugin)?.handle
151
+ }
152
+
153
+ private async create(plugin: string, definition: PluginDatabase) {
154
+ if (!this.environment) {
155
+ throw new Error(
156
+ `Plugin ${plugin} requires SQL database support in this environment.`,
157
+ )
158
+ }
159
+ const connection = await this.environment.open(
160
+ this.environment.path(this.stateRoot, `${plugin}.sqlite`),
161
+ )
162
+ try {
163
+ await migrate(plugin, connection, definition.migrations ?? [])
164
+ return connection
165
+ } catch (error) {
166
+ await connection.close().catch(() => undefined)
167
+ throw error
168
+ }
169
+ }
170
+
171
+ private async closeAll() {
172
+ const connections = [...this.registered.values()]
173
+ .map((registered) => registered.connection())
174
+ .filter(
175
+ (connection): connection is Promise<SqlConnection> => !!connection,
176
+ )
177
+ const settled = await Promise.allSettled(
178
+ connections.map(async (connection) => (await connection).close()),
179
+ )
180
+ const rejected = settled.find(
181
+ (result): result is PromiseRejectedResult => result.status === "rejected",
182
+ )
183
+ if (rejected) throw rejected.reason
184
+ }
185
+
186
+ close() {
187
+ return (this.closing ??= this.closeAll())
188
+ }
189
+ }
@@ -0,0 +1,60 @@
1
+ export type Dependency =
2
+ | Readonly<{ type: "source"; path: string }>
3
+ | Readonly<{ type: "plugin"; plugin: string; key: string }>
4
+
5
+ export type DependencyContext = {
6
+ depend(dependency: Dependency): void
7
+ }
8
+
9
+ export function dependencyKey(dependency: Dependency) {
10
+ if (dependency.type === "source") return dependency.path
11
+ return `plugin:${dependency.plugin}:${dependency.key}`
12
+ }
13
+
14
+ export class PageDependencies implements DependencyContext {
15
+ readonly keys = new Set<string>()
16
+
17
+ depend = (dependency: Dependency) => {
18
+ this.keys.add(dependencyKey(dependency))
19
+ }
20
+ }
21
+
22
+ /** Bidirectional compile-time dependency index. Replacement is authoritative. */
23
+ export class DependencyIndex {
24
+ private outgoing = new Map<string, Set<string>>()
25
+ private incoming = new Map<string, Set<string>>()
26
+
27
+ replaceDependent(dependent: string, dependencies: Iterable<string>) {
28
+ this.deleteDependent(dependent)
29
+ const deps = new Set(dependencies)
30
+ if (!deps.size) return
31
+ this.outgoing.set(dependent, deps)
32
+ for (const dependency of deps) {
33
+ let dependents = this.incoming.get(dependency)
34
+ if (!dependents) {
35
+ dependents = new Set()
36
+ this.incoming.set(dependency, dependents)
37
+ }
38
+ dependents.add(dependent)
39
+ }
40
+ }
41
+
42
+ deleteDependent(dependent: string) {
43
+ const deps = this.outgoing.get(dependent)
44
+ if (!deps) return
45
+ for (const dependency of deps) {
46
+ const dependents = this.incoming.get(dependency)
47
+ dependents?.delete(dependent)
48
+ if (dependents && dependents.size === 0) this.incoming.delete(dependency)
49
+ }
50
+ this.outgoing.delete(dependent)
51
+ }
52
+
53
+ dependentsOf(dependency: string) {
54
+ return this.incoming.get(dependency) ?? new Set<string>()
55
+ }
56
+
57
+ dependenciesOf(dependent: string) {
58
+ return this.outgoing.get(dependent) ?? new Set<string>()
59
+ }
60
+ }