Skip to content

Commit c11b41b

Browse files
♻️ Move block lexicon defs to reusable namespace (#48)
* ♻️ Move block lexicon defs to reusable namespace Place block type definitions in `pub.oxa.blocks.defs` and generate them at `lexicon/blocks/defs.json` instead of nesting them under the document lexicon. Update generated refs, conversion code, tests, docs, and examples to use the new block namespace.\n\nThis makes the block schema easier to reuse from other document lexicons rather than tying it to `pub.oxa.document.document`. For example, `pub.oxa.blocks.defs#block` can be referenced from an open `content` property in another record schema such as `site.standard.document`.\n\nCo-authored-by: Stencila <47797644+stencila[bot]@users.noreply.github.com> * chore(*): update formatted/generated files [skip ci] * ♻️ Simplify document lexicon id to `pub.oxa.document` Simplify the document record NSID from `pub.oxa.document.document` to `pub.oxa.document`, consistent with the naming convention used by `site.standard.document` and others. This is possible now that block type definitions have been moved to their own `pub.oxa.blocks.defs` namespace. Co-authored-by: Stencila <47797644+stencila[bot]@users.noreply.github.com> * chore(*): update formatted/generated files [skip ci] * Move generated file --------- Co-authored-by: Stencila <47797644+stencila[bot]@users.noreply.github.com>
1 parent 030d09e commit c11b41b

8 files changed

Lines changed: 63 additions & 66 deletions

File tree

docs/atproto-lexicon.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@ By defining a lexicon, OXA documents become first-class objects on the AT Protoc
1919

2020
## Lexicon structure
2121

22-
The OXA lexicon is organized into two namespaces:
22+
The OXA lexicon is organized into three namespaces:
2323

2424
| File | NSID | Purpose |
2525
| -------------------------------- | --------------------------- | --------------------------------------------------------------------------------------- |
26-
| `lexicon/document/document.json` | `pub.oxa.document.document` | The `Document` record type — the root object stored in a PDS |
27-
| `lexicon/document/defs.json` | `pub.oxa.document.defs` | Block-level type definitions (`paragraph`, `heading`, `richText`) and the `block` union |
26+
| `lexicon/document/document.json` | `pub.oxa.document` | The `Document` record type — the root object stored in a PDS |
27+
| `lexicon/blocks/defs.json` | `pub.oxa.blocks.defs` | Block-level type definitions (`paragraph`, `heading`, `richText`) and the `block` union |
2828
| `lexicon/richtext/facet.json` | `pub.oxa.richtext.facet` | Facet annotations for inline formatting (`emphasis`, `strong`, `byteSlice`) |
2929

3030
A `Document` record contains an array of `children` (blocks). Each block carries a `text` string and an optional `facets` array that annotates ranges of that text with formatting features.
@@ -112,7 +112,7 @@ AT Protocol [uses facets instead of a tree](https://www.pfrazee.com/blog/why-fac
112112
113113
```json
114114
{
115-
"$type": "pub.oxa.document.defs#paragraph",
115+
"$type": "pub.oxa.blocks.defs#paragraph",
116116
"text": "This is bold and italic text.",
117117
"facets": [
118118
{
@@ -201,16 +201,16 @@ Produces:
201201

202202
```json
203203
{
204-
"$type": "pub.oxa.document.document",
204+
"$type": "pub.oxa.document",
205205
"children": [
206206
{
207-
"$type": "pub.oxa.document.defs#heading",
207+
"$type": "pub.oxa.blocks.defs#heading",
208208
"level": 1,
209209
"text": "Hello",
210210
"facets": []
211211
},
212212
{
213-
"$type": "pub.oxa.document.defs#paragraph",
213+
"$type": "pub.oxa.blocks.defs#paragraph",
214214
"text": "Some emphasized text.",
215215
"facets": [
216216
{
@@ -265,8 +265,8 @@ The lexicon files are generated from the OXA YAML schema definitions by the code
265265
1. Loads the merged OXA JSON Schema.
266266
2. Classifies each type as inline or block based on the `Inline` and `Block` union definitions.
267267
3. Maps inline types to facet features in `pub.oxa.richtext.facet` (excluding `Text`, which becomes the plain text string).
268-
4. Maps block types to object definitions in `pub.oxa.document.defs`, replacing their inline `children` arrays with `text` + `facets` pairs.
269-
5. Emits the `Document` record type in `pub.oxa.document.document`.
268+
4. Maps block types to object definitions in `pub.oxa.blocks.defs`, replacing their inline `children` arrays with `text` + `facets` pairs.
269+
5. Emits the `Document` record type in `pub.oxa.document`.
270270

271271
To regenerate the lexicon after changing the schema:
272272

examples/rfc0003.atproto.json

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"$type": "pub.oxa.document.document",
2+
"$type": "pub.oxa.document",
33
"title": {
44
"text": "Water Dissociation: H2O → H+ + OH−",
55
"facets": [
@@ -19,13 +19,13 @@
1919
},
2020
"children": [
2121
{
22-
"$type": "pub.oxa.document.defs#heading",
22+
"$type": "pub.oxa.blocks.defs#heading",
2323
"text": "Introduction",
2424
"facets": [],
2525
"level": 1
2626
},
2727
{
28-
"$type": "pub.oxa.document.defs#paragraph",
28+
"$type": "pub.oxa.blocks.defs#paragraph",
2929
"text": "Water (H2O) undergoes autoionization, a process in which a water molecule donates a proton to another. The equilibrium constant for this reaction, Kw, is approximately 10−14 at 25 °C.",
3030
"facets": [
3131
{
@@ -50,15 +50,15 @@
5050
}
5151
]
5252
},
53-
{ "$type": "pub.oxa.document.defs#thematicBreak" },
53+
{ "$type": "pub.oxa.blocks.defs#thematicBreak" },
5454
{
55-
"$type": "pub.oxa.document.defs#heading",
55+
"$type": "pub.oxa.blocks.defs#heading",
5656
"text": "Computing the Equilibrium",
5757
"facets": [],
5858
"level": 2
5959
},
6060
{
61-
"$type": "pub.oxa.document.defs#paragraph",
61+
"$type": "pub.oxa.blocks.defs#paragraph",
6262
"text": "The following Python snippet computes Kw from ion concentrations:",
6363
"facets": [
6464
{
@@ -68,12 +68,12 @@
6868
]
6969
},
7070
{
71-
"$type": "pub.oxa.document.defs#code",
71+
"$type": "pub.oxa.blocks.defs#code",
7272
"value": "H_plus = 1e-7 # mol/L\nOH_minus = 1e-7 # mol/L\nKw = H_plus * OH_minus\nprint(f\"Kw = {Kw:.2e}\") # Kw = 1.00e-14",
7373
"language": "python"
7474
},
7575
{
76-
"$type": "pub.oxa.document.defs#paragraph",
76+
"$type": "pub.oxa.blocks.defs#paragraph",
7777
"text": "You can run this with python kw.py. The result confirms the well-known value of Kw.",
7878
"facets": [
7979
{
@@ -90,15 +90,15 @@
9090
}
9191
]
9292
},
93-
{ "$type": "pub.oxa.document.defs#thematicBreak" },
93+
{ "$type": "pub.oxa.blocks.defs#thematicBreak" },
9494
{
95-
"$type": "pub.oxa.document.defs#heading",
95+
"$type": "pub.oxa.blocks.defs#heading",
9696
"text": "Summary",
9797
"facets": [],
9898
"level": 2
9999
},
100100
{
101-
"$type": "pub.oxa.document.defs#paragraph",
101+
"$type": "pub.oxa.blocks.defs#paragraph",
102102
"text": "This example demonstrates every node type from RFC0003: Heading, Paragraph, Code, ThematicBreak, Text, Emphasis, Strong, Superscript, Subscript, and InlineCode.",
103103
"facets": [
104104
{
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"lexicon": 1,
3-
"id": "pub.oxa.document.defs",
3+
"id": "pub.oxa.blocks.defs",
44
"defs": {
55
"richText": {
66
"type": "object",
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"lexicon": 1,
3-
"id": "pub.oxa.document.document",
3+
"id": "pub.oxa.document",
44
"defs": {
55
"main": {
66
"type": "record",
@@ -26,13 +26,13 @@
2626
},
2727
"title": {
2828
"type": "ref",
29-
"ref": "pub.oxa.document.defs#richText"
29+
"ref": "pub.oxa.blocks.defs#richText"
3030
},
3131
"children": {
3232
"type": "array",
3333
"items": {
3434
"type": "ref",
35-
"ref": "pub.oxa.document.defs#block"
35+
"ref": "pub.oxa.blocks.defs#block"
3636
}
3737
},
3838
"createdAt": {

packages/oxa-core/src/convert.test.ts

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,11 @@ const lexiconFiles = {
2626
path: resolve(REPO_ROOT, "lexicon/richtext/facet.json"),
2727
},
2828
defs: {
29-
id: "pub.oxa.document.defs",
30-
path: resolve(REPO_ROOT, "lexicon/document/defs.json"),
29+
id: "pub.oxa.blocks.defs",
30+
path: resolve(REPO_ROOT, "lexicon/blocks/defs.json"),
3131
},
3232
document: {
33-
id: "pub.oxa.document.document",
33+
id: "pub.oxa.document",
3434
path: resolve(REPO_ROOT, "lexicon/document/document.json"),
3535
},
3636
} as const;
@@ -342,9 +342,9 @@ describe("ATProto lexicon structure", () => {
342342
expect(main.type).toBe("record");
343343
expect(main.key).toBe("tid");
344344
expect(record.required).toEqual(["children", "createdAt"]);
345-
expect(record.properties.title.ref).toBe("pub.oxa.document.defs#richText");
345+
expect(record.properties.title.ref).toBe("pub.oxa.blocks.defs#richText");
346346
expect(record.properties.children.items.ref).toBe(
347-
"pub.oxa.document.defs#block",
347+
"pub.oxa.blocks.defs#block",
348348
);
349349
expect(record.properties.createdAt).toEqual({
350350
type: "string",
@@ -380,7 +380,7 @@ describe("mapBlock", () => {
380380
);
381381

382382
await expect(map(block)).resolves.toEqual({
383-
$type: "pub.oxa.document.defs#paragraph",
383+
$type: "pub.oxa.blocks.defs#paragraph",
384384
id: "para-1",
385385
classes: ["lead"],
386386
data: { align: "left" },
@@ -405,7 +405,7 @@ describe("mapBlock", () => {
405405
});
406406

407407
await expect(map(block)).resolves.toEqual({
408-
$type: "pub.oxa.document.defs#heading",
408+
$type: "pub.oxa.blocks.defs#heading",
409409
id: "intro",
410410
classes: ["hero"],
411411
data: { section: true },
@@ -470,7 +470,7 @@ describe("oxaToAtproto", () => {
470470
);
471471

472472
await expect(convertDocument(document, { createdAt })).resolves.toEqual({
473-
$type: "pub.oxa.document.document",
473+
$type: "pub.oxa.document",
474474
title: {
475475
text: "Hello, World",
476476
facets: [],
@@ -481,13 +481,13 @@ describe("oxaToAtproto", () => {
481481
},
482482
children: [
483483
{
484-
$type: "pub.oxa.document.defs#heading",
484+
$type: "pub.oxa.blocks.defs#heading",
485485
level: 1,
486486
text: "Introduction",
487487
facets: [],
488488
},
489489
{
490-
$type: "pub.oxa.document.defs#paragraph",
490+
$type: "pub.oxa.blocks.defs#paragraph",
491491
text: "This is bold and italic text.",
492492
facets: [
493493
{
@@ -517,7 +517,7 @@ describe("oxaToAtproto", () => {
517517
await expect(
518518
convertDocument(documentNode([]), { createdAt }),
519519
).resolves.toEqual({
520-
$type: "pub.oxa.document.document",
520+
$type: "pub.oxa.document",
521521
children: [],
522522
createdAt,
523523
});
@@ -551,11 +551,11 @@ describe("oxaToAtproto", () => {
551551
);
552552

553553
expect(converted).toEqual({
554-
$type: "pub.oxa.document.document",
554+
$type: "pub.oxa.document",
555555
metadata,
556556
children: [
557557
{
558-
$type: "pub.oxa.document.defs#paragraph",
558+
$type: "pub.oxa.blocks.defs#paragraph",
559559
text: "Keep this paragraph",
560560
facets: [],
561561
},

packages/oxa-core/src/convert.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -96,23 +96,23 @@ interface RichText {
9696

9797
type AtprotoParagraph = RichText &
9898
BlockNodeBase & {
99-
$type: "pub.oxa.document.defs#paragraph";
99+
$type: "pub.oxa.blocks.defs#paragraph";
100100
};
101101

102102
type AtprotoHeading = RichText &
103103
BlockNodeBase & {
104-
$type: "pub.oxa.document.defs#heading";
104+
$type: "pub.oxa.blocks.defs#heading";
105105
level: number;
106106
};
107107

108108
type AtprotoCode = BlockNodeBase & {
109-
$type: "pub.oxa.document.defs#code";
109+
$type: "pub.oxa.blocks.defs#code";
110110
value: string;
111111
language?: string;
112112
};
113113

114114
type AtprotoThematicBreak = BlockNodeBase & {
115-
$type: "pub.oxa.document.defs#thematicBreak";
115+
$type: "pub.oxa.blocks.defs#thematicBreak";
116116
};
117117

118118
type AtprotoBlock =
@@ -122,7 +122,7 @@ type AtprotoBlock =
122122
| AtprotoThematicBreak;
123123

124124
type AtprotoDocument = {
125-
$type: "pub.oxa.document.document";
125+
$type: "pub.oxa.document";
126126
title?: RichText;
127127
metadata?: Record<string, unknown>;
128128
children: AtprotoBlock[];
@@ -184,10 +184,10 @@ export const compatibleFeatures: Record<
184184

185185
const formattingPropertyNames = ["id", "classes", "data"] as const;
186186
const blockPropertyNames = ["id", "classes", "data"] as const;
187-
const paragraphType = "pub.oxa.document.defs#paragraph" as const;
188-
const headingType = "pub.oxa.document.defs#heading" as const;
189-
const codeType = "pub.oxa.document.defs#code" as const;
190-
const thematicBreakType = "pub.oxa.document.defs#thematicBreak" as const;
187+
const paragraphType = "pub.oxa.blocks.defs#paragraph" as const;
188+
const headingType = "pub.oxa.blocks.defs#heading" as const;
189+
const codeType = "pub.oxa.blocks.defs#code" as const;
190+
const thematicBreakType = "pub.oxa.blocks.defs#thematicBreak" as const;
191191

192192
const encoder = new TextEncoder();
193193

@@ -421,7 +421,7 @@ export function oxaToAtproto(
421421
options: OxaToAtprotoOptions = {},
422422
): AtprotoDocument {
423423
return {
424-
$type: "pub.oxa.document.document",
424+
$type: "pub.oxa.document",
425425
...getOptionalDocumentFields(session, document),
426426
children: mapKnownBlocks(session, document.children),
427427
createdAt: options.createdAt ?? new Date().toISOString(),

packages/oxa/src/cli.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,15 +77,15 @@ describe("oxa cli", () => {
7777
describe("convert", () => {
7878
const createdAt = "2026-03-22T00:00:00.000Z";
7979
const expectedConverted = {
80-
$type: "pub.oxa.document.document",
80+
$type: "pub.oxa.document",
8181
title: {
8282
text: "CLI Example",
8383
facets: [],
8484
},
8585
metadata: { license: "CC-BY-4.0" },
8686
children: [
8787
{
88-
$type: "pub.oxa.document.defs#paragraph",
88+
$type: "pub.oxa.blocks.defs#paragraph",
8989
text: "Hello from CLI",
9090
facets: [],
9191
},

0 commit comments

Comments
 (0)