@omgbase/astro 0.1.0 → 0.3.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 (3) hide show
  1. package/README.md +95 -2
  2. package/package.json +9 -8
  3. package/LICENSE +0 -21
package/README.md CHANGED
@@ -5,9 +5,10 @@ Use an [omgbase](https://github.com/omgbase/omgbase) repository as the CMS for a
5
5
  The package provides:
6
6
 
7
7
  - **`omgLoader`** — an Astro Content Layer loader driven by an **OQX** query
8
+ - **`omgLiveLoader`** — an Astro **live collection** loader for SSR: query and hydrate omg at request time, no build-time data store
8
9
  - **Local transport** — embedded `@omgbase/core`
9
10
  - **Remote transport** — Streamable HTTP MCP (`query` + `docs_get_many`), same as `omg … --server <url>`
10
- - **Dev live-reload** — in `astro dev`, local vaults use Vite's FS watcher; remote polls MCP `changes_since`. Both update Astro's content data store so the normal HMR path fires — no custom integration required
11
+ - **Dev live-reload** — in `astro dev`, local vaults use Vite's FS watcher; remote polls MCP `changes_since` and reconnects on its own when the server restarts. Both update Astro's content data store so the normal HMR path fires — no custom integration required
11
12
  - **`createMcpHttpServer`** — optional local Streamable HTTP MCP server for demos/CI
12
13
 
13
14
  ## Install
@@ -93,6 +94,8 @@ Enabled by default whenever Astro passes a `watcher` into the loader:
93
94
 
94
95
  On change, the loader re-queries/hydrates into the content data store. Astro already watches that store file and hot-reloads pages — we don’t invent a second HMR channel.
95
96
 
97
+ If the remote server restarts (or the Streamable HTTP session expires), the transport drops its stale session, reconnects, and retries the call once — polling resumes without restarting `astro dev`. While the server is down, the loader logs one warning and then a single info line when it comes back.
98
+
96
99
  ```ts
97
100
  omgLoader({
98
101
  url: process.env.OMG_URL!,
@@ -119,12 +122,102 @@ const mcp = await createMcpHttpServer({
119
122
 
120
123
  Prefer your real wrapper/hosted endpoint for production builds.
121
124
 
125
+ ## Live collections (SSR)
126
+
127
+ `omgLiveLoader` is an Astro [live collection](https://docs.astro.build/en/guides/content-collections/) loader (experimental in Astro 5.10+, stable in Astro 7). Nothing is loaded at build time: every `getLiveCollection` / `getLiveEntry` call runs an OQX query and/or `docs_get_many` against omg when the request comes in. Your site must render on the server (`output: "server"` in `astro.config.mjs`, with an adapter).
128
+
129
+ ```ts
130
+ // src/live.config.ts
131
+ import { defineLiveCollection } from "astro:content";
132
+ import { omgLiveLoader } from "@omgbase/astro";
133
+
134
+ const notes = defineLiveCollection({
135
+ loader: omgLiveLoader({
136
+ url: process.env.OMG_URL!, // or `workspace: "../content"` for the local transport
137
+ token: process.env.OMG_TOKEN,
138
+ repo: "notes",
139
+ query: "select $path, $title, $updated_at from docs",
140
+ href: ({ slug }) => `/note/${slug}/`, // match your routes
141
+ }),
142
+ });
143
+
144
+ export const collections = { notes };
145
+ ```
146
+
147
+ ```astro
148
+ ---
149
+ // src/pages/notes/index.astro
150
+ import { getLiveCollection } from "astro:content";
151
+
152
+ const { entries, error } = await getLiveCollection("notes", {
153
+ query: 'select $path, $title, $updated_at from docs where status == "published"',
154
+ limit: 50,
155
+ });
156
+ if (error) throw error;
157
+ ---
158
+ <ul>
159
+ {entries.map((n) => <li><a href={`/note/${n.data.slug}/`}>{n.data.title}</a></li>)}
160
+ </ul>
161
+ ```
162
+
163
+ ```astro
164
+ ---
165
+ // src/pages/note/[...slug].astro
166
+ import { getLiveEntry, render } from "astro:content";
167
+
168
+ const { entry, error } = await getLiveEntry("notes", { slug: Astro.params.slug! });
169
+ if (error) throw error;
170
+ if (!entry) return Astro.redirect("/404");
171
+ const { Content } = await render(entry);
172
+ ---
173
+ <h1>{entry.data.title}</h1>
174
+ <Content />
175
+ ```
176
+
177
+ ### Filters
178
+
179
+ | Call | Filter | Notes |
180
+ | --- | --- | --- |
181
+ | `getLiveCollection(name, filter?)` | `{ query?, limit?, hydrate? }` | Each field falls back to the loader option. `query` must return document hits |
182
+ | `getLiveEntry(name, filter)` | `{ id }` **or** `{ path }` **or** `{ slug }` | Exactly one key. `path` is repo-relative (a leading `/` is stripped); `slug` is mapped back to a path with `pathForSlug` (default `` `${slug}.md` ``) and looked up via `$path == "…"` |
183
+
184
+ A missing entry resolves to `entry: undefined`; every other failure comes back as `error`, an `OmgLiveError` with a `code` of `invalid_filter`, `query_failed`, `hydrate_failed` or `render_failed` — the loader never throws.
185
+
186
+ ### Lean vs hydrated
187
+
188
+ By default a collection is **lean**: one `query` call, and each entry's `data` holds the query's projections (plus `path`, `docId`, `slug`, and `updatedAt` / `title` aliases of `$updated_at` / `$title` when projected). There is no `rendered` content and no `body`, so a list page costs a single round trip.
189
+
190
+ `getLiveEntry` always **hydrates**: `docs_get_many` for the doc, `data` = frontmatter + intrinsics (`path`, `docId`, `slug`, `contentHash`) + the lean projections + `body` (markdown after frontmatter), and `rendered.html`. Markdown links are rewritten to `href(...)` for the docs omg's out-edges say this doc links to; links to anything else are left as authored. Set `href: false` to skip rewriting.
191
+
192
+ Pass `hydrate: true` (loader option or collection filter) to get the hydrated shape for every collection entry — one `query`, one `docs_get_many`, one edge lookup.
193
+
194
+ `cacheHint.lastModified` is set from `$updated_at` (the newest hit for a collection) whenever your query projects it, so project it if you cache responses.
195
+
196
+ ### Rendering
197
+
198
+ Bodies render through `@astrojs/markdown-remark`'s `createMarkdownProcessor`, created once per loader. Pass `markdown` to configure it, or `render` to replace it:
199
+
200
+ ```ts
201
+ omgLiveLoader({
202
+ url: process.env.OMG_URL!,
203
+ query: "select $path, $title, $updated_at from docs",
204
+ markdown: { shikiConfig: { theme: "github-dark" } },
205
+ // render: async (markdown, { docId, path }) => ({ html: myRenderer(markdown) }),
206
+ });
207
+ ```
208
+
209
+ ### Connections
210
+
211
+ The loader connects lazily on the first request and keeps that transport for the life of the server process; the remote transport reconnects by itself when the MCP session is lost. Pass `transport` to share a connection between loaders or to inject a fake in tests.
212
+
122
213
  ## Identity & caching
123
214
 
124
215
  - Astro entry **`id`** = omg document id (`d_…`)
125
216
  - **`data.slug`** = routing key (default: path with `.md` stripped; override with `slug`)
126
217
  - **`href`** = site URL used when rewriting markdown links between hydrated docs (default `/${slug}`; override to match routes)
127
- - Digests use omg `contentHash` (plus an href-map fingerprint when rewriting) so unchanged docs skip rewrite on reload
218
+ - **Hash-first sync.** Every load projects `$content_hash` onto your query, so a hit carries the server's whole-file hash (frontmatter included). Docs whose hash, path and hit projections match the stored entry are reused without `docs_get_many`; only new/changed docs are fetched, mapped and rendered. A no-change reload costs one query and nothing else — an all-docs collection is cheap to keep loaded
219
+ - **Link-aware invalidation.** Each entry's digest also pins the hrefs of the docs it links to (via omg out-edges). Adding, removing or moving a doc re-fetches only its linkers, not the whole collection. The edge scan itself is skipped when nothing changed
220
+ - **Fallbacks.** If the server rejects the `$content_hash` projection (older omg), the loader warns once and hydrates everything as before; the digest compare still avoids redundant re-renders. Entries stored under the pre-0.2 digest format are re-hydrated once and migrated
128
221
 
129
222
  ## Example
130
223
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omgbase/astro",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Astro Content Loader and helpers to use omgbase as a CMS",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -26,7 +26,14 @@
26
26
  "dist",
27
27
  "README.md"
28
28
  ],
29
+ "scripts": {
30
+ "build": "tsc -p tsconfig.json",
31
+ "typecheck": "tsc -p tsconfig.json --noEmit",
32
+ "test": "vitest run",
33
+ "lint": "echo 'no lint yet'"
34
+ },
29
35
  "dependencies": {
36
+ "@astrojs/markdown-remark": "^7.3.0",
30
37
  "@modelcontextprotocol/sdk": "^1.30.0",
31
38
  "@omgbase/core": "^0.5.0",
32
39
  "@omgbase/sync": "^0.4.2"
@@ -38,11 +45,5 @@
38
45
  "astro": "^7.3.5",
39
46
  "typescript": "^5.7.2",
40
47
  "vitest": "^2.1.8"
41
- },
42
- "scripts": {
43
- "build": "tsc -p tsconfig.json",
44
- "typecheck": "tsc -p tsconfig.json --noEmit",
45
- "test": "vitest run",
46
- "lint": "echo 'no lint yet'"
47
48
  }
48
- }
49
+ }
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 omgbase
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.