├── .npmrc
├── experiments
├── hugo
│ ├── page.md
│ ├── posts
│ │ ├── slug_test.md
│ │ ├── url_test.md
│ │ ├── post.md
│ │ └── post_bundle
│ │ │ └── index.md
│ ├── tags
│ │ └── tag
│ │ │ └── _index.md
│ └── index.md
├── demo-astro
│ ├── .gitignore
│ ├── tsconfig.json
│ ├── src
│ │ ├── styles
│ │ │ └── global.css
│ │ ├── env.d.ts
│ │ ├── components
│ │ │ ├── Prose.astro
│ │ │ ├── Graph.astro
│ │ │ └── graphRenderer.ts
│ │ ├── pages
│ │ │ ├── index.astro
│ │ │ ├── facet.json.ts
│ │ │ └── notes
│ │ │ │ └── [...slug].astro
│ │ ├── layouts
│ │ │ └── main.astro
│ │ ├── content
│ │ │ └── config.ts
│ │ └── lib
│ │ │ ├── graph.ts
│ │ │ └── braindb.mjs
│ ├── tailwind.config.mjs
│ ├── package.json
│ ├── public
│ │ └── favicon.svg
│ ├── astro.config.mjs
│ ├── braindb.config.ts
│ └── README.md
├── obsidian
│ ├── subfolder
│ │ └── Three laws of motion.md
│ └── Simple Note.md
├── astro
│ └── mdx-page.mdx
└── cli
│ ├── README.md
│ ├── tsconfig.json
│ ├── package.json
│ └── src
│ ├── config.ts
│ └── index.ts
├── .tool-versions
├── packages
├── astro
│ ├── .gitignore
│ ├── env.d.ts
│ ├── tsconfig.json
│ ├── tsup.config.ts
│ ├── package.json
│ ├── src
│ │ ├── remarkWikiLink.ts
│ │ └── index.ts
│ └── README.md
├── docs
│ ├── README.md
│ ├── tsconfig.json
│ ├── src
│ │ ├── env.d.ts
│ │ ├── content
│ │ │ ├── config.ts
│ │ │ └── docs
│ │ │ │ ├── index.md
│ │ │ │ └── notes
│ │ │ │ ├── alternatives.md
│ │ │ │ ├── parallel.md
│ │ │ │ ├── slug.md
│ │ │ │ ├── unsorted.md
│ │ │ │ ├── use-cases.md
│ │ │ │ ├── remark-wiki-link.md
│ │ │ │ ├── astro-integration.md
│ │ │ │ ├── content-query.md
│ │ │ │ ├── dataview.md
│ │ │ │ ├── metadata.md
│ │ │ │ ├── frontmatter-schema.md
│ │ │ │ └── todo.md
│ │ └── styles
│ │ │ └── custom.css
│ ├── .gitignore
│ ├── package.json
│ └── astro.config.mjs
├── core
│ ├── drizzle
│ │ ├── 0006_icy_iron_monger.sql
│ │ ├── 0002_worthless_silk_fever.sql
│ │ ├── 0003_jazzy_epoch.sql
│ │ ├── 0005_stormy_luckman.sql
│ │ ├── 0001_petite_fixer.sql
│ │ ├── 0004_ambitious_starbolt.sql
│ │ ├── 0000_same_talon.sql
│ │ ├── 0007_peaceful_stepford_cuckoos.sql
│ │ └── meta
│ │ │ ├── _journal.json
│ │ │ ├── 0000_snapshot.json
│ │ │ ├── 0002_snapshot.json
│ │ │ ├── 0001_snapshot.json
│ │ │ ├── 0003_snapshot.json
│ │ │ └── 0006_snapshot.json
│ ├── tsconfig.json
│ ├── drizzle.config.ts
│ ├── README.md
│ ├── src
│ │ ├── parser.ts
│ │ ├── deleteDocument.ts
│ │ ├── defaults.ts
│ │ ├── toText.ts
│ │ ├── toDot.ts
│ │ ├── utils.ts
│ │ ├── toJson.ts
│ │ ├── Link.ts
│ │ ├── types.ts
│ │ ├── Task.ts
│ │ ├── db.ts
│ │ ├── query.ts
│ │ ├── resolveLinks.ts
│ │ ├── getMarkdown.ts
│ │ ├── Document.ts
│ │ └── schema.ts
│ └── package.json
├── remark-wiki-link
│ ├── vitest.config.ts
│ ├── tsconfig.json
│ ├── src
│ │ └── index.ts
│ ├── LICENSE
│ ├── package.json
│ ├── README.md
│ └── test
│ │ └── index.test.ts
├── mdast-util-wiki-link
│ ├── vitest.config.ts
│ ├── src
│ │ ├── index.ts
│ │ ├── to-markdown.ts
│ │ └── from-markdown.ts
│ ├── tsconfig.json
│ ├── package.json
│ ├── README.md
│ └── test
│ │ └── index.test.ts
├── micromark-extension-wiki-link
│ ├── vitest.config.ts
│ ├── src
│ │ ├── index.ts
│ │ ├── html.ts
│ │ └── syntax.ts
│ ├── tsconfig.json
│ ├── package.json
│ ├── README.md
│ └── test
│ │ └── micromark.test.js
└── remark-dataview
│ ├── tsconfig.json
│ ├── package.json
│ ├── src
│ ├── sqlUtils.test.ts
│ └── index.ts
│ └── README.md
├── .vscode
├── extensions.json
└── launch.json
├── pnpm-workspace.yaml
├── turbo.json
├── package.json
├── tsconfig.json
├── README.md
└── .gitignore
/.npmrc:
--------------------------------------------------------------------------------
1 | node-linker=hoisted
--------------------------------------------------------------------------------
/experiments/hugo/page.md:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.tool-versions:
--------------------------------------------------------------------------------
1 | nodejs 18.20.5
2 |
--------------------------------------------------------------------------------
/packages/astro/.gitignore:
--------------------------------------------------------------------------------
1 | dist
--------------------------------------------------------------------------------
/packages/docs/README.md:
--------------------------------------------------------------------------------
1 | # BrainDB docs
2 |
--------------------------------------------------------------------------------
/experiments/demo-astro/.gitignore:
--------------------------------------------------------------------------------
1 | .braindb
2 | src/content/notes
--------------------------------------------------------------------------------
/packages/astro/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
4 |
7 |
t[[\nt
"); 58 | }); 59 | 60 | it("handles open wiki links at end of file", () => { 61 | let serialized = micromark("t [[", { 62 | extensions: [syntax()], 63 | htmlExtensions: [html()], 64 | }); 65 | 66 | expect(serialized).toEqual("t [[
"); 67 | }); 68 | 69 | it("handles open wiki links with partial data", () => { 70 | let serialized = micromark("t [[tt\nt", { 71 | extensions: [syntax()], 72 | htmlExtensions: [html()], 73 | }); 74 | 75 | expect(serialized).toEqual("t [[tt\nt
"); 76 | }); 77 | 78 | it("handles open wiki links with partial alias divider", () => { 79 | let serialized = micromark("[[t|\nt", { 80 | extensions: [syntax({ aliasDivider: "||" })], 81 | htmlExtensions: [html()], 82 | }); 83 | 84 | expect(serialized).toEqual("[[t|\nt
"); 85 | }); 86 | 87 | it("handles open wiki links with partial alias", () => { 88 | let serialized = micromark("[[t:\nt", { 89 | extensions: [syntax()], 90 | htmlExtensions: [html()], 91 | }); 92 | 93 | expect(serialized).toEqual("[[t:\nt
"); 94 | }); 95 | }); 96 | 97 | describe("configuration options", () => { 98 | it("uses pageResolver", () => { 99 | let identity = (name) => [name]; 100 | 101 | let serialized = micromark("[[A Page]]", { 102 | extensions: [syntax()], 103 | htmlExtensions: [ 104 | html({ 105 | pageResolver: identity, 106 | permalinks: ["A Page"], 107 | }), 108 | ], 109 | }); 110 | 111 | expect(serialized).toEqual( 112 | '' 113 | ); 114 | }); 115 | 116 | it("uses newClassName", () => { 117 | let serialized = micromark("[[A Page]]", { 118 | extensions: [syntax()], 119 | htmlExtensions: [ 120 | html({ 121 | newClassName: "new_page", 122 | }), 123 | ], 124 | }); 125 | 126 | expect(serialized).toEqual( 127 | '' 128 | ); 129 | }); 130 | 131 | it("uses hrefTemplate", () => { 132 | let hrefTemplate = (permalink) => permalink; 133 | let serialized = micromark("[[A Page]]", { 134 | extensions: [syntax()], 135 | htmlExtensions: [ 136 | html({ 137 | hrefTemplate: hrefTemplate, 138 | }), 139 | ], 140 | }); 141 | 142 | expect(serialized).toEqual( 143 | '' 144 | ); 145 | }); 146 | 147 | it("uses wikiLinkClassName", () => { 148 | let serialized = micromark("[[A Page]]", { 149 | extensions: [syntax()], 150 | htmlExtensions: [ 151 | html({ 152 | wikiLinkClassName: "wiki_link", 153 | permalinks: ["a_page"], 154 | }), 155 | ], 156 | }); 157 | 158 | expect(serialized).toEqual( 159 | '' 160 | ); 161 | }); 162 | }); 163 | }); 164 | -------------------------------------------------------------------------------- /packages/micromark-extension-wiki-link/src/syntax.ts: -------------------------------------------------------------------------------- 1 | import { type Tokenizer, type Code } from "micromark-util-types"; 2 | 3 | export type WikiLinkSyntaxOptions = { 4 | aliasDivider?: string; 5 | }; 6 | 7 | const codes = { 8 | horizontalTab: -2, 9 | virtualSpace: -1, 10 | nul: 0, 11 | eof: null, 12 | space: 32, 13 | }; 14 | 15 | function markdownLineEndingOrSpace(code: Code) { 16 | return code !== codes.eof && (code < codes.nul || code === codes.space); 17 | } 18 | 19 | function markdownLineEnding(code: Code) { 20 | return code !== codes.eof && (code === null || code < codes.horizontalTab); 21 | } 22 | 23 | export function syntax(opts: WikiLinkSyntaxOptions = {}) { 24 | const aliasDivider = opts.aliasDivider || "|"; 25 | 26 | const aliasMarker = aliasDivider; 27 | const startMarker = "[["; 28 | const endMarker = "]]"; 29 | 30 | const tokenize: Tokenizer = (effects, ok, nok) => { 31 | let data: boolean; 32 | let alias: boolean; 33 | 34 | let aliasCursor = 0; 35 | let startMarkerCursor = 0; 36 | let endMarkerCursor = 0; 37 | 38 | return start; 39 | 40 | function start(code: Code) { 41 | if (code !== startMarker.charCodeAt(startMarkerCursor)) return nok(code); 42 | 43 | // @ts-expect-error 44 | effects.enter("wikiLink"); 45 | // @ts-expect-error 46 | effects.enter("wikiLinkMarker"); 47 | 48 | return consumeStart(code); 49 | } 50 | 51 | function consumeStart(code: Code) { 52 | if (startMarkerCursor === startMarker.length) { 53 | // @ts-expect-error 54 | effects.exit("wikiLinkMarker"); 55 | return consumeData(code); 56 | } 57 | 58 | if (code !== startMarker.charCodeAt(startMarkerCursor)) { 59 | return nok(code); 60 | } 61 | 62 | effects.consume(code); 63 | startMarkerCursor++; 64 | 65 | return consumeStart; 66 | } 67 | 68 | function consumeData(code: Code) { 69 | if (markdownLineEnding(code) || code === codes.eof) { 70 | return nok(code); 71 | } 72 | 73 | // @ts-expect-error 74 | effects.enter("wikiLinkData"); 75 | // @ts-expect-error 76 | effects.enter("wikiLinkTarget"); 77 | return consumeTarget(code); 78 | } 79 | 80 | function consumeTarget(code: Code) { 81 | if (code === aliasMarker.charCodeAt(aliasCursor)) { 82 | if (!data) return nok(code); 83 | // @ts-expect-error 84 | effects.exit("wikiLinkTarget"); 85 | // @ts-expect-error 86 | effects.enter("wikiLinkAliasMarker"); 87 | return consumeAliasMarker(code); 88 | } 89 | 90 | if (code === endMarker.charCodeAt(endMarkerCursor)) { 91 | if (!data) return nok(code); 92 | // @ts-expect-error 93 | effects.exit("wikiLinkTarget"); 94 | // @ts-expect-error 95 | effects.exit("wikiLinkData"); 96 | // @ts-expect-error 97 | effects.enter("wikiLinkMarker"); 98 | return consumeEnd(code); 99 | } 100 | 101 | if (markdownLineEnding(code) || code === codes.eof) { 102 | return nok(code); 103 | } 104 | 105 | if (!markdownLineEndingOrSpace(code)) { 106 | data = true; 107 | } 108 | 109 | effects.consume(code); 110 | 111 | return consumeTarget; 112 | } 113 | 114 | function consumeAliasMarker(code: Code) { 115 | if (aliasCursor === aliasMarker.length) { 116 | // @ts-expect-error 117 | effects.exit("wikiLinkAliasMarker"); 118 | // @ts-expect-error 119 | effects.enter("wikiLinkAlias"); 120 | return consumeAlias(code); 121 | } 122 | 123 | if (code !== aliasMarker.charCodeAt(aliasCursor)) { 124 | return nok(code); 125 | } 126 | 127 | effects.consume(code); 128 | aliasCursor++; 129 | 130 | return consumeAliasMarker; 131 | } 132 | 133 | function consumeAlias(code: Code) { 134 | if (code === endMarker.charCodeAt(endMarkerCursor)) { 135 | if (!alias) return nok(code); 136 | // @ts-expect-error 137 | effects.exit("wikiLinkAlias"); 138 | // @ts-expect-error 139 | effects.exit("wikiLinkData"); 140 | // @ts-expect-error 141 | effects.enter("wikiLinkMarker"); 142 | return consumeEnd(code); 143 | } 144 | 145 | if (markdownLineEnding(code) || code === codes.eof) { 146 | return nok(code); 147 | } 148 | 149 | if (!markdownLineEndingOrSpace(code)) { 150 | alias = true; 151 | } 152 | 153 | effects.consume(code); 154 | 155 | return consumeAlias; 156 | } 157 | 158 | function consumeEnd(code: Code) { 159 | if (endMarkerCursor === endMarker.length) { 160 | // @ts-expect-error 161 | effects.exit("wikiLinkMarker"); 162 | // @ts-expect-error 163 | effects.exit("wikiLink"); 164 | return ok(code); 165 | } 166 | 167 | if (code !== endMarker.charCodeAt(endMarkerCursor)) { 168 | return nok(code); 169 | } 170 | 171 | effects.consume(code); 172 | endMarkerCursor++; 173 | 174 | return consumeEnd; 175 | } 176 | }; 177 | 178 | return { 179 | text: { 91: { tokenize: tokenize } }, // left square bracket 180 | }; 181 | } 182 | -------------------------------------------------------------------------------- /packages/docs/src/content/docs/notes/frontmatter-schema.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Frontmatter schema 3 | --- 4 | 5 | Main source of inspiration - [Astro Content Collections](https://docs.astro.build/en/guides/content-collections/) 6 | 7 | Related: 8 | 9 | - https://contentlayer.dev/docs/sources/files/mapping-document-types-bf100a10 10 | 11 | Basic idea: 12 | 13 | - folder represents table/collection 14 | - folder name represents table name, or tag in disjoint union 15 | - file represents row/document/tupple 16 | - file path represents global unique ID 17 | - slug represents per table unique ID 18 | 19 | For each selected folder we can define schema. 20 | 21 | ## Error behaviour 22 | 23 | What to do if there is an error in one of documents? 24 | 25 | - Report error 26 | - then either: 27 | - block whole database until error would be resolved 28 | - exclude document with error from the index (until error would be resolved) 29 | 30 | ## Extra fields 31 | 32 | Options: 33 | 34 | - allow fields that are not in schema 35 | - **remove all extra fields** that are not present in schema 36 | - optionally print warning when that happens 37 | 38 | ### Shall schema be optional? 39 | 40 | - On one side it is easier to get started if schema is optional 41 | - On the other side one can always use `z.any()` 42 | - Astro uses convention of one schema per top level folder. But in our case this is not convinient 43 | - Use one schema for root, with default type `undefined` and default schema `z.any()` 44 | - top level folder = type, but optional. If there is no config it would use default one (from root) 45 | - shall we allow arbitrary paths (glob) for schemas 46 | - what to do if more than one schema matches for file? Throw an error? 47 | 48 | ## Typescript 49 | 50 | [Runtime type validators](https://stereobooster.com/posts/runtime-type-validators/) 51 | 52 | Let's say we have collections: 53 | 54 | ```ts 55 | const A = defineCollection({ 56 | type: "A", 57 | schema: z.object({ 58 | a: z.string(), 59 | }), 60 | }); 61 | 62 | const B = defineCollection({ 63 | type: "B", 64 | schema: z.object({ 65 | b: z.number(), 66 | }), 67 | }); 68 | 69 | export const collections = { 70 | A: A, 71 | B: B, 72 | }; 73 | ``` 74 | 75 | Then `frontmatter()` can be either: 76 | 77 | ```ts 78 | { 79 | a?: string, 80 | b?: number, 81 | } 82 | ``` 83 | 84 | or: 85 | 86 | ```ts 87 | { 88 | A?: { a: string }, 89 | B?: { b: number }, 90 | } 91 | ``` 92 | 93 | or: 94 | 95 | ```ts 96 | { type: 'A', A: { a: string } } | 97 | { type: 'B', B: { b: number } } 98 | ``` 99 | 100 | theoretically it could also be: 101 | 102 | ```ts 103 | { type: 'A', a: string } | 104 | { type: 'B', b: number } 105 | ``` 106 | 107 | But in this case field we can't use field `type` in orginial schemas. 108 | 109 | ### Class 110 | 111 | ```ts 112 | class Document