@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,642 @@
1
+ import type { ActionContribution } from "../actions/actions.ts"
2
+ import type {
3
+ ApplicationSchema,
4
+ BlockType,
5
+ DataProp,
6
+ NodeSchema,
7
+ NodeType,
8
+ NodeTypeDefinition,
9
+ QuerySchema,
10
+ SchemaContribution,
11
+ SourceType,
12
+ } from "./model.ts"
13
+ import type { QueryContribution } from "../query/model.ts"
14
+ import {
15
+ formatSchemaIssues,
16
+ requireStandardJsonSchema,
17
+ type PathMXDataSchema,
18
+ type StandardSchemaV1,
19
+ } from "./standard.ts"
20
+ import type { Source, SourceNode } from "../source/index.ts"
21
+ import { SourceDiagnosticError } from "../source-diagnostic.ts"
22
+
23
+ const NODE_TYPE_ID = /^[a-z0-9]+(?:[/-][a-z0-9]+)*$/
24
+ const MEMBER_ID = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/
25
+
26
+ type JsonSchema = Readonly<Record<string, unknown>>
27
+
28
+ type TypeContribution = Readonly<{
29
+ type: string
30
+ plugin: string
31
+ definition: NodeTypeDefinition
32
+ inputJsonSchema?: JsonSchema
33
+ outputJsonSchema?: JsonSchema
34
+ }>
35
+
36
+ type PropContribution = DataProp
37
+
38
+ export type RegisteredQuery = Readonly<{
39
+ id: string
40
+ name: string
41
+ plugin: string
42
+ definition: QueryContribution
43
+ input?: JsonSchema
44
+ output: JsonSchema
45
+ }>
46
+
47
+ export type PreparedSchema = Readonly<{
48
+ sources: readonly TypeContribution[]
49
+ blocks: readonly TypeContribution[]
50
+ props: readonly PropContribution[]
51
+ queries: readonly RegisteredQuery[]
52
+ actions: readonly (readonly [string, ActionContribution])[]
53
+ }>
54
+
55
+ function compareText(left: string, right: string) {
56
+ return left < right ? -1 : left > right ? 1 : 0
57
+ }
58
+
59
+ function titleCase(type: string) {
60
+ const name = type.split("/").at(-1) ?? type
61
+ return name
62
+ .replace(/[-_]+/g, " ")
63
+ .replace(/\b\w/g, (character) => character.toUpperCase())
64
+ }
65
+
66
+ function normalizedText(value: unknown, field: string, owner: string) {
67
+ if (typeof value !== "string" || !value.trim()) {
68
+ throw new Error(`${owner} ${field} must be a non-empty string.`)
69
+ }
70
+ return value.trim()
71
+ }
72
+
73
+ function prepareType(
74
+ node: "Source" | "Block",
75
+ plugin: string,
76
+ type: string,
77
+ definition: NodeTypeDefinition,
78
+ ): TypeContribution {
79
+ if (!NODE_TYPE_ID.test(type)) {
80
+ throw new Error(`Plugin ${plugin} has an invalid ${node} type: ${type}`)
81
+ }
82
+ if (!definition || typeof definition !== "object") {
83
+ throw new Error(`Plugin ${plugin} ${node} type ${type} must be an object.`)
84
+ }
85
+ const owner = `${plugin}:${type}`
86
+ const schemaSubject = `Plugin ${plugin} ${node} type ${type} schema`
87
+ const input = definition.schema
88
+ ? requireStandardJsonSchema(definition.schema, "input", schemaSubject)
89
+ : undefined
90
+ const output = definition.schema
91
+ ? requireStandardJsonSchema(definition.schema, "output", schemaSubject)
92
+ : undefined
93
+ const normalized = Object.freeze({
94
+ ...(definition.label !== undefined
95
+ ? { label: normalizedText(definition.label, "label", owner) }
96
+ : {}),
97
+ ...(definition.description !== undefined
98
+ ? {
99
+ description: normalizedText(
100
+ definition.description,
101
+ "description",
102
+ owner,
103
+ ),
104
+ }
105
+ : {}),
106
+ ...(definition.icon !== undefined
107
+ ? { icon: normalizedText(definition.icon, "icon", owner) }
108
+ : {}),
109
+ ...(definition.color !== undefined
110
+ ? { color: normalizedText(definition.color, "color", owner) }
111
+ : {}),
112
+ ...(definition.schema ? { schema: definition.schema } : {}),
113
+ })
114
+ if (!Object.keys(normalized).length) {
115
+ throw new Error(
116
+ `Plugin ${plugin} ${node} type ${type} must contribute presentation or a schema.`,
117
+ )
118
+ }
119
+ return Object.freeze({
120
+ type,
121
+ plugin,
122
+ definition: normalized,
123
+ ...(input ? { inputJsonSchema: input } : {}),
124
+ ...(output ? { outputJsonSchema: output } : {}),
125
+ })
126
+ }
127
+
128
+ function prepareProp(
129
+ plugin: string,
130
+ name: string,
131
+ schema: PathMXDataSchema,
132
+ ): PropContribution {
133
+ if (!MEMBER_ID.test(name)) {
134
+ throw new Error(`Plugin ${plugin} has an invalid data prop: ${name}`)
135
+ }
136
+ const subject = `Plugin ${plugin} data prop ${name}`
137
+ const input = requireStandardJsonSchema(schema, "input", subject)
138
+ const output = requireStandardJsonSchema(schema, "output", subject)
139
+ return Object.freeze({
140
+ name,
141
+ plugin,
142
+ schema,
143
+ inputJsonSchema: input,
144
+ outputJsonSchema: output,
145
+ })
146
+ }
147
+
148
+ function prepareQuery(
149
+ plugin: string,
150
+ name: string,
151
+ definition: QueryContribution,
152
+ ): RegisteredQuery {
153
+ if (!MEMBER_ID.test(name)) {
154
+ throw new Error(`Plugin ${plugin} has an invalid query name: ${name}`)
155
+ }
156
+ if (!definition || typeof definition !== "object") {
157
+ throw new Error(`Plugin query ${plugin}:${name} must be an object.`)
158
+ }
159
+ if (typeof definition.resolve !== "function") {
160
+ throw new Error(`Plugin query ${plugin}:${name} must define resolve().`)
161
+ }
162
+ const id = `${plugin}:${name}`
163
+ const output = requireStandardJsonSchema(
164
+ definition.output,
165
+ "output",
166
+ `Plugin query ${id} output`,
167
+ )
168
+ const input = definition.input
169
+ ? requireStandardJsonSchema(
170
+ definition.input,
171
+ "input",
172
+ `Plugin query ${id} input`,
173
+ )
174
+ : undefined
175
+ return Object.freeze({
176
+ id,
177
+ name,
178
+ plugin,
179
+ definition,
180
+ ...(input ? { input } : {}),
181
+ output,
182
+ })
183
+ }
184
+
185
+ function prepareAction(
186
+ plugin: string,
187
+ name: string,
188
+ action: ActionContribution,
189
+ ) {
190
+ if (!MEMBER_ID.test(name)) {
191
+ throw new Error(`Plugin ${plugin} has an invalid Action name: ${name}`)
192
+ }
193
+ if (typeof action !== "function") {
194
+ throw new Error(`Plugin Action ${plugin}:${name} must be a function.`)
195
+ }
196
+ if (action.input) {
197
+ requireStandardJsonSchema(
198
+ action.input,
199
+ "input",
200
+ `Plugin Action ${plugin}:${name} input`,
201
+ )
202
+ }
203
+ if (action.output) {
204
+ requireStandardJsonSchema(
205
+ action.output,
206
+ "output",
207
+ `Plugin Action ${plugin}:${name} output`,
208
+ )
209
+ }
210
+ return Object.freeze([`${plugin}:${name}`, action] as const)
211
+ }
212
+
213
+ function isPromiseLike(value: unknown): value is PromiseLike<unknown> {
214
+ return (
215
+ value != null &&
216
+ (typeof value === "object" || typeof value === "function") &&
217
+ typeof (value as { then?: unknown }).then === "function"
218
+ )
219
+ }
220
+
221
+ function validate(
222
+ source: Source,
223
+ node: SourceNode,
224
+ plugin: string,
225
+ subject: string,
226
+ schema: StandardSchemaV1,
227
+ value: unknown,
228
+ ) {
229
+ const result = schema["~standard"].validate(value)
230
+ const pending = isPromiseLike(result)
231
+ if (pending) void Promise.resolve(result).catch(() => undefined)
232
+ const message = pending
233
+ ? "application schemas must validate authored data synchronously"
234
+ : result.issues?.length
235
+ ? formatSchemaIssues(result.issues)
236
+ : undefined
237
+ return message
238
+ ? new SourceDiagnosticError({
239
+ source: source.path,
240
+ plugin,
241
+ message: `Invalid ${subject} on ${node.path}: ${message}`,
242
+ })
243
+ : undefined
244
+ }
245
+
246
+ function report(
247
+ error: SourceDiagnosticError | undefined,
248
+ onError?: (error: unknown) => boolean,
249
+ ) {
250
+ if (error && !onError?.(error)) throw error
251
+ }
252
+
253
+ /** Ordered Plugin Schema registry and canonical Source/Block data validator. */
254
+ export class SchemaRegistry {
255
+ private sourceContributions = new Map<string, TypeContribution[]>()
256
+ private blockContributions = new Map<string, TypeContribution[]>()
257
+ private sourceDescriptors = new Map<string, SourceType>()
258
+ private blockDescriptors = new Map<string, BlockType>()
259
+ private observedSourceDescriptors = new Map<string, SourceType>()
260
+ private observedBlockDescriptors = new Map<string, BlockType>()
261
+ private propContributions = new Map<string, PropContribution>()
262
+ private queryContributions = new Map<string, RegisteredQuery>()
263
+ private queryReferences = new WeakMap<object, RegisteredQuery>()
264
+ private queryDescriptions = new Map<string, QuerySchema>()
265
+
266
+ prepare(plugin: string, contribution?: SchemaContribution): PreparedSchema {
267
+ const sources = Object.entries(contribution?.sources ?? {}).map(
268
+ ([type, definition]) => prepareType("Source", plugin, type, definition),
269
+ )
270
+ const blocks = Object.entries(contribution?.blocks ?? {}).map(
271
+ ([type, definition]) => prepareType("Block", plugin, type, definition),
272
+ )
273
+ const props = Object.entries(contribution?.props ?? {}).map(
274
+ ([name, schema]) => prepareProp(plugin, name, schema),
275
+ )
276
+ const queries = Object.entries(contribution?.query ?? {}).map(
277
+ ([name, definition]) => prepareQuery(plugin, name, definition),
278
+ )
279
+ const actions = Object.entries(contribution?.actions ?? {}).map(
280
+ ([name, action]) => prepareAction(plugin, name, action),
281
+ )
282
+ for (const prop of props) {
283
+ const existing = this.propContributions.get(prop.name)
284
+ if (existing) {
285
+ throw new Error(
286
+ `Data prop ${prop.name} is already owned by Plugin ${existing.plugin}.`,
287
+ )
288
+ }
289
+ }
290
+ for (const query of queries) {
291
+ if (this.queryContributions.has(query.id)) {
292
+ throw new Error(`Duplicate Plugin query: ${query.id}`)
293
+ }
294
+ }
295
+ return Object.freeze({
296
+ sources: Object.freeze(sources),
297
+ blocks: Object.freeze(blocks),
298
+ props: Object.freeze(props),
299
+ queries: Object.freeze(queries),
300
+ actions: Object.freeze(actions),
301
+ })
302
+ }
303
+
304
+ register(prepared: PreparedSchema) {
305
+ this.registerTypes(
306
+ prepared.sources,
307
+ this.sourceContributions,
308
+ this.sourceDescriptors,
309
+ )
310
+ this.registerTypes(
311
+ prepared.blocks,
312
+ this.blockContributions,
313
+ this.blockDescriptors,
314
+ )
315
+ for (const contribution of prepared.props) {
316
+ this.propContributions.set(contribution.name, contribution)
317
+ }
318
+ for (const query of prepared.queries) {
319
+ this.queryContributions.set(query.id, query)
320
+ this.queryReferences.set(query.definition, query)
321
+ this.queryDescriptions.set(
322
+ query.id,
323
+ Object.freeze({
324
+ id: query.id,
325
+ plugin: query.plugin,
326
+ ...(query.definition.input
327
+ ? {
328
+ input: query.definition.input,
329
+ inputJsonSchema: query.input,
330
+ }
331
+ : {}),
332
+ output: query.definition.output,
333
+ outputJsonSchema: query.output,
334
+ }),
335
+ )
336
+ }
337
+ }
338
+
339
+ private registerTypes(
340
+ contributions: readonly TypeContribution[],
341
+ byType: Map<string, TypeContribution[]>,
342
+ descriptors: Map<string, NodeType>,
343
+ ) {
344
+ for (const contribution of contributions) {
345
+ const definitions = byType.get(contribution.type)
346
+ if (definitions) definitions.push(contribution)
347
+ else byType.set(contribution.type, [contribution])
348
+ descriptors.delete(contribution.type)
349
+ }
350
+ }
351
+
352
+ sourceType(sourceOrType: SourceNode | string) {
353
+ const type =
354
+ typeof sourceOrType === "string" ? sourceOrType : sourceOrType.type
355
+ return this.typeDescriptor(
356
+ type,
357
+ this.sourceContributions,
358
+ this.sourceDescriptors,
359
+ this.observedSourceDescriptors,
360
+ )
361
+ }
362
+
363
+ sourceTypes() {
364
+ return this.typeDescriptors(
365
+ this.sourceContributions,
366
+ this.observedSourceDescriptors,
367
+ (type) => this.sourceType(type),
368
+ )
369
+ }
370
+
371
+ blockType(blockOrType: SourceNode | string) {
372
+ const type =
373
+ typeof blockOrType === "string" ? blockOrType : blockOrType.type
374
+ return this.typeDescriptor(
375
+ type,
376
+ this.blockContributions,
377
+ this.blockDescriptors,
378
+ this.observedBlockDescriptors,
379
+ )
380
+ }
381
+
382
+ blockTypes() {
383
+ return this.typeDescriptors(
384
+ this.blockContributions,
385
+ this.observedBlockDescriptors,
386
+ (type) => this.blockType(type),
387
+ )
388
+ }
389
+
390
+ private typeDescriptor(
391
+ type: string,
392
+ contributionsByType: Map<string, TypeContribution[]>,
393
+ descriptors: Map<string, NodeType>,
394
+ observed: Map<string, NodeType>,
395
+ ) {
396
+ const contributions = contributionsByType.get(type)
397
+ if (!contributions?.length) return this.observedType(type, observed)
398
+ let descriptor = descriptors.get(type)
399
+ if (descriptor) return descriptor
400
+ let label: string | undefined
401
+ let description: string | undefined
402
+ let icon: string | undefined
403
+ let color: string | undefined
404
+ for (const { definition } of contributions) {
405
+ label = definition.label ?? label
406
+ description = definition.description ?? description
407
+ icon = definition.icon ?? icon
408
+ color = definition.color ?? color
409
+ }
410
+ descriptor = Object.freeze({
411
+ type,
412
+ label: label ?? titleCase(type),
413
+ ...(description ? { description } : {}),
414
+ icon: icon ?? "file-text",
415
+ color: color ?? "slate",
416
+ origin: "defined",
417
+ plugins: Object.freeze([
418
+ ...new Set(contributions.map((contribution) => contribution.plugin)),
419
+ ]),
420
+ })
421
+ descriptors.set(type, descriptor)
422
+ return descriptor
423
+ }
424
+
425
+ private typeDescriptors(
426
+ contributions: Map<string, TypeContribution[]>,
427
+ observed: Map<string, NodeType>,
428
+ resolve: (type: string) => NodeType,
429
+ ) {
430
+ const types = new Set([...contributions.keys(), ...observed.keys()])
431
+ return Object.freeze([...types].sort(compareText).map(resolve))
432
+ }
433
+
434
+ sourceSchemas(sourceOrType: SourceNode | string) {
435
+ const type =
436
+ typeof sourceOrType === "string" ? sourceOrType : sourceOrType.type
437
+ return this.schemas(type, this.sourceContributions)
438
+ }
439
+
440
+ blockSchemas(blockOrType: SourceNode | string) {
441
+ const type =
442
+ typeof blockOrType === "string" ? blockOrType : blockOrType.type
443
+ return this.schemas(type, this.blockContributions)
444
+ }
445
+
446
+ private schemas(
447
+ type: string,
448
+ contributionsByType: Map<string, TypeContribution[]>,
449
+ ) {
450
+ return Object.freeze(
451
+ (contributionsByType.get(type) ?? []).flatMap((contribution) =>
452
+ contribution.definition.schema
453
+ ? [
454
+ Object.freeze({
455
+ plugin: contribution.plugin,
456
+ schema: contribution.definition.schema,
457
+ inputJsonSchema: contribution.inputJsonSchema!,
458
+ outputJsonSchema: contribution.outputJsonSchema!,
459
+ }) satisfies NodeSchema,
460
+ ]
461
+ : [],
462
+ ),
463
+ )
464
+ }
465
+
466
+ prop(name: string) {
467
+ return this.propContributions.get(name)
468
+ }
469
+
470
+ props(node?: SourceNode) {
471
+ const definitions = node
472
+ ? Object.keys(node.data).flatMap((key) => {
473
+ const prop = this.propContributions.get(key)
474
+ return prop ? [prop] : []
475
+ })
476
+ : [...this.propContributions.values()]
477
+ return Object.freeze(
478
+ definitions.sort((left, right) => compareText(left.name, right.name)),
479
+ )
480
+ }
481
+
482
+ has(node: SourceNode, prop: string) {
483
+ return this.propContributions.has(prop) && Object.hasOwn(node.data, prop)
484
+ }
485
+
486
+ value(node: SourceNode, prop: string) {
487
+ return this.has(node, prop) ? node.data[prop] : undefined
488
+ }
489
+
490
+ registeredQuery(reference: string | QueryContribution) {
491
+ return typeof reference === "string"
492
+ ? this.queryContributions.get(reference)
493
+ : this.queryReferences.get(reference)
494
+ }
495
+
496
+ queries() {
497
+ return Object.freeze(
498
+ [...this.queryContributions.values()].sort((left, right) =>
499
+ compareText(left.id, right.id),
500
+ ),
501
+ )
502
+ }
503
+
504
+ querySchema(id: string) {
505
+ return this.queryDescriptions.get(id)
506
+ }
507
+
508
+ querySchemas() {
509
+ return Object.freeze(
510
+ [...this.queryDescriptions.values()].sort((left, right) =>
511
+ compareText(left.id, right.id),
512
+ ),
513
+ )
514
+ }
515
+
516
+ sourceTypeContributions(type?: string) {
517
+ return type
518
+ ? Object.freeze([...(this.sourceContributions.get(type) ?? [])])
519
+ : Object.freeze([...this.sourceContributions.values()].flat())
520
+ }
521
+
522
+ blockTypeContributions(type?: string) {
523
+ return type
524
+ ? Object.freeze([...(this.blockContributions.get(type) ?? [])])
525
+ : Object.freeze([...this.blockContributions.values()].flat())
526
+ }
527
+
528
+ sourceDataSchemas(type?: string) {
529
+ return Object.freeze(
530
+ this.sourceTypeContributions(type).flatMap((entry) =>
531
+ entry.outputJsonSchema ? [entry.outputJsonSchema] : [],
532
+ ),
533
+ )
534
+ }
535
+
536
+ blockDataSchemas(type?: string) {
537
+ return Object.freeze(
538
+ this.blockTypeContributions(type).flatMap((entry) =>
539
+ entry.outputJsonSchema ? [entry.outputJsonSchema] : [],
540
+ ),
541
+ )
542
+ }
543
+
544
+ propDataSchema(name: string) {
545
+ return this.propContributions.get(name)?.outputJsonSchema
546
+ }
547
+
548
+ index(source: Source, onError?: (error: unknown) => boolean) {
549
+ this.sourceType(source)
550
+ for (const { plugin, definition } of this.sourceContributions.get(
551
+ source.type,
552
+ ) ?? []) {
553
+ if (!definition.schema) continue
554
+ report(
555
+ validate(
556
+ source,
557
+ source,
558
+ plugin,
559
+ `${source.type} Source`,
560
+ definition.schema,
561
+ source.data,
562
+ ),
563
+ onError,
564
+ )
565
+ }
566
+
567
+ for (const block of source.blocks) {
568
+ this.blockType(block)
569
+ for (const { plugin, definition } of this.blockContributions.get(
570
+ block.type,
571
+ ) ?? []) {
572
+ if (!definition.schema) continue
573
+ report(
574
+ validate(
575
+ source,
576
+ block,
577
+ plugin,
578
+ `${block.type} Block`,
579
+ definition.schema,
580
+ block.data,
581
+ ),
582
+ onError,
583
+ )
584
+ }
585
+ }
586
+
587
+ if (!this.propContributions.size) return
588
+ for (const node of [source, ...source.blocks]) {
589
+ for (const key of Object.keys(node.data)) {
590
+ const prop = this.propContributions.get(key)
591
+ if (!prop) continue
592
+ report(
593
+ validate(
594
+ source,
595
+ node,
596
+ prop.plugin,
597
+ `${key} prop`,
598
+ prop.schema,
599
+ node.data[key],
600
+ ),
601
+ onError,
602
+ )
603
+ }
604
+ }
605
+ }
606
+
607
+ private observedType(type: string, observed: Map<string, NodeType>) {
608
+ let descriptor = observed.get(type)
609
+ if (!descriptor) {
610
+ descriptor = Object.freeze({
611
+ type,
612
+ label: titleCase(type),
613
+ icon: "file-text",
614
+ color: "slate",
615
+ origin: "observed",
616
+ plugins: Object.freeze([]),
617
+ })
618
+ observed.set(type, descriptor)
619
+ }
620
+ return descriptor
621
+ }
622
+ }
623
+
624
+ /** @internal Expose only stable Schema queries outside the Plugin host. */
625
+ export function applicationSchemaView(
626
+ registry: SchemaRegistry,
627
+ ): ApplicationSchema {
628
+ return Object.freeze({
629
+ sourceType: registry.sourceType.bind(registry),
630
+ sourceTypes: registry.sourceTypes.bind(registry),
631
+ blockType: registry.blockType.bind(registry),
632
+ blockTypes: registry.blockTypes.bind(registry),
633
+ sourceSchemas: registry.sourceSchemas.bind(registry),
634
+ blockSchemas: registry.blockSchemas.bind(registry),
635
+ query: registry.querySchema.bind(registry),
636
+ queries: registry.querySchemas.bind(registry),
637
+ prop: registry.prop.bind(registry),
638
+ props: registry.props.bind(registry),
639
+ has: registry.has.bind(registry),
640
+ value: registry.value.bind(registry),
641
+ })
642
+ }