@omgbase/astro 0.1.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.
- package/LICENSE +21 -0
- package/README.md +142 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/links.d.ts +75 -0
- package/dist/links.d.ts.map +1 -0
- package/dist/links.js +198 -0
- package/dist/links.js.map +1 -0
- package/dist/loader.d.ts +70 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +75 -0
- package/dist/loader.js.map +1 -0
- package/dist/local.d.ts +31 -0
- package/dist/local.d.ts.map +1 -0
- package/dist/local.js +139 -0
- package/dist/local.js.map +1 -0
- package/dist/map.d.ts +41 -0
- package/dist/map.d.ts.map +1 -0
- package/dist/map.js +106 -0
- package/dist/map.js.map +1 -0
- package/dist/remote.d.ts +49 -0
- package/dist/remote.d.ts.map +1 -0
- package/dist/remote.js +155 -0
- package/dist/remote.js.map +1 -0
- package/dist/schema.d.ts +16 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +2 -0
- package/dist/schema.js.map +1 -0
- package/dist/server.d.ts +41 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +115 -0
- package/dist/server.js.map +1 -0
- package/dist/transport.d.ts +74 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +2 -0
- package/dist/transport.js.map +1 -0
- package/dist/watch.d.ts +38 -0
- package/dist/watch.d.ts.map +1 -0
- package/dist/watch.js +215 -0
- package/dist/watch.js.map +1 -0
- package/package.json +48 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# `@omgbase/astro`
|
|
2
|
+
|
|
3
|
+
Use an [omgbase](https://github.com/omgbase/omgbase) repository as the CMS for an [Astro](https://astro.build) static site.
|
|
4
|
+
|
|
5
|
+
The package provides:
|
|
6
|
+
|
|
7
|
+
- **`omgLoader`** — an Astro Content Layer loader driven by an **OQX** query
|
|
8
|
+
- **Local transport** — embedded `@omgbase/core`
|
|
9
|
+
- **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
|
+
- **`createMcpHttpServer`** — optional local Streamable HTTP MCP server for demos/CI
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pnpm add @omgbase/astro @omgbase/core
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Peer dependency: Astro 5+.
|
|
20
|
+
|
|
21
|
+
## Local loader
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
// src/content.config.ts
|
|
25
|
+
import { defineCollection, z } from "astro:content";
|
|
26
|
+
import { omgLoader } from "@omgbase/astro";
|
|
27
|
+
|
|
28
|
+
const posts = defineCollection({
|
|
29
|
+
loader: omgLoader({
|
|
30
|
+
workspace: "../content", // directory containing `.omgbase/`
|
|
31
|
+
repo: "content",
|
|
32
|
+
query: `from docs where $path.startsWith("posts/") && status == "published"`,
|
|
33
|
+
slug: ({ path }) => path.replace(/^posts\//, "").replace(/\.md$/i, ""),
|
|
34
|
+
href: ({ slug }) => `/blog/${slug}/`,
|
|
35
|
+
}),
|
|
36
|
+
schema: z.object({
|
|
37
|
+
title: z.string(),
|
|
38
|
+
status: z.string(),
|
|
39
|
+
path: z.string(),
|
|
40
|
+
docId: z.string(),
|
|
41
|
+
contentHash: z.string().nullable(),
|
|
42
|
+
slug: z.string(),
|
|
43
|
+
}),
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
export const collections = { posts };
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Entry **id** = omg document id. **`data.slug`** is for routing (default: path without `.md`).
|
|
50
|
+
|
|
51
|
+
### Inter-doc links
|
|
52
|
+
|
|
53
|
+
When the loader hydrates a set of docs, markdown bodies are rewritten so links that resolve to another hydrated doc use that doc’s site URL:
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
omgLoader({
|
|
57
|
+
workspace: "../content",
|
|
58
|
+
query: `from docs where status == "published"`,
|
|
59
|
+
slug: ({ path }) => path.replace(/^posts\//, "").replace(/\.md$/i, ""),
|
|
60
|
+
href: ({ slug }) => `/blog/${slug}/`, // match your routes
|
|
61
|
+
});
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Resolution is identity-aware: live omg out-edges (`$dst`) gate rewrites when available, and authored destinations are matched with the same path rules as the engine (`./` / `../`, root-relative, `.md` aliases). Fragments (`#sec`) are preserved. External URLs, pure `#anchors`, code spans/fences, and destinations that aren’t in this load are left alone. Set `href: false` to disable rewriting.
|
|
65
|
+
|
|
66
|
+
Query projections that return link URL *values* as columns are never rewritten — only markdown bodies going through `renderMarkdown`.
|
|
67
|
+
|
|
68
|
+
## Remote loader (Streamable HTTP MCP)
|
|
69
|
+
|
|
70
|
+
Point `url` at the same endpoint you’d pass to `omg query --server <url>` — including a stdio-mcp-to-http wrapper, secret path prefix, etc. Custom headers match CLI `-H`:
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
omgLoader({
|
|
74
|
+
url: process.env.OMG_URL!, // e.g. https://host/k/<secret>/mcp
|
|
75
|
+
headers: { "X-Env": "prod" }, // map form
|
|
76
|
+
headerLines: ["X-Request-Id: build-42"], // CLI -H "Name: value" form
|
|
77
|
+
token: process.env.OMG_TOKEN, // → Authorization: Bearer …
|
|
78
|
+
repo: "content",
|
|
79
|
+
query: `from docs where status == "published"`,
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Under the hood this uses `@omgbase/sync`’s `connectHttpEngine` (same client as the CLI).
|
|
84
|
+
|
|
85
|
+
### Live reload in `astro dev`
|
|
86
|
+
|
|
87
|
+
Enabled by default whenever Astro passes a `watcher` into the loader:
|
|
88
|
+
|
|
89
|
+
| Transport | Mechanism |
|
|
90
|
+
| --- | --- |
|
|
91
|
+
| Local | Vite FS watch on the vault root (same hook as `glob()`) |
|
|
92
|
+
| Remote | Poll MCP `changes_since` (default every 2s) |
|
|
93
|
+
|
|
94
|
+
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
|
+
```ts
|
|
97
|
+
omgLoader({
|
|
98
|
+
url: process.env.OMG_URL!,
|
|
99
|
+
query: `from docs where status == "published"`,
|
|
100
|
+
watch: { intervalMs: 1500 }, // or `watch: false` to disable
|
|
101
|
+
});
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Demo MCP HTTP server
|
|
105
|
+
|
|
106
|
+
If you don’t already have an HTTP MCP front-end, `createMcpHttpServer` wraps a local workspace:
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
import { createMcpHttpServer } from "@omgbase/astro/server";
|
|
110
|
+
|
|
111
|
+
const mcp = await createMcpHttpServer({
|
|
112
|
+
workspace: "./content",
|
|
113
|
+
repo: "content",
|
|
114
|
+
token: "secret",
|
|
115
|
+
port: 8787,
|
|
116
|
+
});
|
|
117
|
+
// mcp.url → http://127.0.0.1:8787/mcp
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Prefer your real wrapper/hosted endpoint for production builds.
|
|
121
|
+
|
|
122
|
+
## Identity & caching
|
|
123
|
+
|
|
124
|
+
- Astro entry **`id`** = omg document id (`d_…`)
|
|
125
|
+
- **`data.slug`** = routing key (default: path with `.md` stripped; override with `slug`)
|
|
126
|
+
- **`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
|
|
128
|
+
|
|
129
|
+
## Example
|
|
130
|
+
|
|
131
|
+
See [`examples/blog`](../../examples/blog):
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
pnpm install
|
|
135
|
+
pnpm --filter @omgbase/astro build
|
|
136
|
+
pnpm --filter @omgbase/example-blog dev:local
|
|
137
|
+
# or (demo MCP HTTP server)
|
|
138
|
+
pnpm --filter @omgbase/example-blog dev:remote
|
|
139
|
+
# or point at your wrapper:
|
|
140
|
+
OMG_URL=https://host/k/secret/mcp OMG_TOKEN=… pnpm --filter @omgbase/example-blog build:local
|
|
141
|
+
# with OMG_TRANSPORT=remote and OMG_URL set in the environment
|
|
142
|
+
```
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export { omgLoader } from "./loader.js";
|
|
2
|
+
export type { OmgLoaderOptions, OmgLoaderBaseOptions, OmgLoaderLocalOptions, OmgLoaderRemoteOptions, } from "./loader.js";
|
|
3
|
+
export type { Transport, QueryHit, HydratedDoc, QueryOptions, HydrateOptions, DocOutEdge } from "./transport.js";
|
|
4
|
+
export { createLocalTransport, LocalTransport } from "./local.js";
|
|
5
|
+
export type { LocalTransportOptions } from "./local.js";
|
|
6
|
+
export { createRemoteTransport, RemoteTransport, parseHeaderLine, resolveHeaders, } from "./remote.js";
|
|
7
|
+
export type { RemoteTransportOptions } from "./remote.js";
|
|
8
|
+
export type { WatchOption } from "./watch.js";
|
|
9
|
+
export { defaultSlug, stripFrontmatter, mapDoc, mergeHitProjections, contentHashOf } from "./map.js";
|
|
10
|
+
export type { SlugContext, MappedEntry } from "./map.js";
|
|
11
|
+
export type { OmgEntryIntrinsics } from "./schema.js";
|
|
12
|
+
export { defaultHref, buildHrefIndex, rewriteMarkdownLinks, resolveLinkHref, canonicalLinkPath, splitDestination, docDirOf, } from "./links.js";
|
|
13
|
+
export type { HrefContext, HrefEntry, HrefIndex, OutEdge } from "./links.js";
|
|
14
|
+
export { createMcpHttpServer, createPublicationServer, } from "./server.js";
|
|
15
|
+
export type { McpHttpServerOptions, McpHttpServer, PublicationServerOptions, PublicationServer, } from "./server.js";
|
|
16
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,YAAY,EACV,gBAAgB,EAChB,oBAAoB,EACpB,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,aAAa,CAAC;AAErB,YAAY,EAAE,SAAS,EAAE,QAAQ,EAAE,WAAW,EAAE,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjH,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAClE,YAAY,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AACxD,OAAO,EACL,qBAAqB,EACrB,eAAe,EACf,eAAe,EACf,cAAc,GACf,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAE1D,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACrG,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACzD,YAAY,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEtD,OAAO,EACL,WAAW,EACX,cAAc,EACd,oBAAoB,EACpB,eAAe,EACf,iBAAiB,EACjB,gBAAgB,EAChB,QAAQ,GACT,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,WAAW,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE7E,OAAO,EACL,mBAAmB,EACnB,uBAAuB,GACxB,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,oBAAoB,EACpB,aAAa,EACb,wBAAwB,EACxB,iBAAiB,GAClB,MAAM,aAAa,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { omgLoader } from "./loader.js";
|
|
2
|
+
export { createLocalTransport, LocalTransport } from "./local.js";
|
|
3
|
+
export { createRemoteTransport, RemoteTransport, parseHeaderLine, resolveHeaders, } from "./remote.js";
|
|
4
|
+
export { defaultSlug, stripFrontmatter, mapDoc, mergeHitProjections, contentHashOf } from "./map.js";
|
|
5
|
+
export { defaultHref, buildHrefIndex, rewriteMarkdownLinks, resolveLinkHref, canonicalLinkPath, splitDestination, docDirOf, } from "./links.js";
|
|
6
|
+
export { createMcpHttpServer, createPublicationServer, } from "./server.js";
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AASxC,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAElE,OAAO,EACL,qBAAqB,EACrB,eAAe,EACf,eAAe,EACf,cAAc,GACf,MAAM,aAAa,CAAC;AAKrB,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAIrG,OAAO,EACL,WAAW,EACX,cAAc,EACd,oBAAoB,EACpB,eAAe,EACf,iBAAiB,EACjB,gBAAgB,EAChB,QAAQ,GACT,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,mBAAmB,EACnB,uBAAuB,GACxB,MAAM,aAAa,CAAC"}
|
package/dist/links.d.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identity-aware markdown link rewriting for rendered omg bodies.
|
|
3
|
+
*
|
|
4
|
+
* When the loader has hydrated docs, each link destination that resolves to one
|
|
5
|
+
* of those docs is rewritten to that doc's site `href` — keyed by omg doc id /
|
|
6
|
+
* path, not by string-guessing alone. Link URL columns / projections are never
|
|
7
|
+
* touched; only markdown bodies passed through {@link rewriteMarkdownLinks}.
|
|
8
|
+
*/
|
|
9
|
+
export interface HrefEntry {
|
|
10
|
+
docId: string;
|
|
11
|
+
path: string;
|
|
12
|
+
href: string;
|
|
13
|
+
}
|
|
14
|
+
export interface HrefIndex {
|
|
15
|
+
byDocId: Map<string, string>;
|
|
16
|
+
/** Canonical repo-relative paths (and `.md`-stripped aliases) → href. */
|
|
17
|
+
byPath: Map<string, string>;
|
|
18
|
+
/** Canonical path → doc id (for edge gating). */
|
|
19
|
+
docIdByPath: Map<string, string>;
|
|
20
|
+
}
|
|
21
|
+
/** Live out-edge to a document (from OQX `edges` or local `docLinks`). */
|
|
22
|
+
export interface OutEdge {
|
|
23
|
+
src: string;
|
|
24
|
+
dst: string;
|
|
25
|
+
dstPath: string | null;
|
|
26
|
+
}
|
|
27
|
+
export interface HrefContext {
|
|
28
|
+
path: string;
|
|
29
|
+
docId: string;
|
|
30
|
+
slug: string;
|
|
31
|
+
properties: Record<string, Record<string, unknown>>;
|
|
32
|
+
}
|
|
33
|
+
/** Default site href: `/${slug}`. Override to match your routes (e.g. `/blog/${slug}/`). */
|
|
34
|
+
export declare function defaultHref({ slug }: HrefContext): string;
|
|
35
|
+
/** Directory prefix (`""` or `"a/b/"`) of a repo-relative doc path. */
|
|
36
|
+
export declare function docDirOf(docPath: string): string;
|
|
37
|
+
/** Split authored destination into path + fragment (`#…` / `^…`, marker kept). */
|
|
38
|
+
export declare function splitDestination(dest: string): {
|
|
39
|
+
path: string;
|
|
40
|
+
fragment: string;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Canonical repo-relative path for a link as authored in `docDir`
|
|
44
|
+
* (mirrors omgbase `canonicalLinkPath`).
|
|
45
|
+
*/
|
|
46
|
+
export declare function canonicalLinkPath(path: string, docDir?: string): string;
|
|
47
|
+
/** Build path/id indexes for every hydrated doc we may link to. */
|
|
48
|
+
export declare function buildHrefIndex(entries: HrefEntry[]): HrefIndex;
|
|
49
|
+
/** Stable fingerprint of the href map (for digests when rewrites are enabled). */
|
|
50
|
+
export declare function hrefIndexFingerprint(index: HrefIndex): string;
|
|
51
|
+
/**
|
|
52
|
+
* Resolve an authored destination against the href index.
|
|
53
|
+
* Returns null for external, pure-fragment, dangling, or unknown targets.
|
|
54
|
+
*/
|
|
55
|
+
export declare function resolveLinkHref(dest: string, srcPath: string, index: HrefIndex, allowedDstIds?: Set<string>): string | null;
|
|
56
|
+
/**
|
|
57
|
+
* Rewrite link destinations in markdown that resolve to hydrated omg docs.
|
|
58
|
+
* Fenced code blocks and inline code spans are left untouched.
|
|
59
|
+
*/
|
|
60
|
+
export declare function rewriteMarkdownLinks(body: string, opts: {
|
|
61
|
+
srcPath: string;
|
|
62
|
+
hrefIndex: HrefIndex;
|
|
63
|
+
/**
|
|
64
|
+
* When set, only rewrite destinations whose resolved doc id appears in this
|
|
65
|
+
* set (typically live out-edge `$dst` values that are also in the index).
|
|
66
|
+
* When omitted, any destination that resolves to a hydrated doc is rewritten.
|
|
67
|
+
*/
|
|
68
|
+
allowedDstIds?: Set<string>;
|
|
69
|
+
}): string;
|
|
70
|
+
/**
|
|
71
|
+
* Allowed destination doc ids for a source, from live out-edges ∩ href index.
|
|
72
|
+
* Phantoms / externals are dropped. Empty set means "no edge-gated targets".
|
|
73
|
+
*/
|
|
74
|
+
export declare function allowedDstsFor(srcDocId: string, edges: OutEdge[], index: HrefIndex): Set<string>;
|
|
75
|
+
//# sourceMappingURL=links.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"links.d.ts","sourceRoot":"","sources":["../src/links.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,yEAAyE;IACzE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5B,iDAAiD;IACjD,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED,0EAA0E;AAC1E,MAAM,WAAW,OAAO;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CACrD;AAED,4FAA4F;AAC5F,wBAAgB,WAAW,CAAC,EAAE,IAAI,EAAE,EAAE,WAAW,GAAG,MAAM,CAEzD;AAED,uEAAuE;AACvE,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAGhD;AAED,kFAAkF;AAClF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAMjF;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,SAAK,GAAG,MAAM,CAcnE;AAOD,mEAAmE;AACnE,wBAAgB,cAAc,CAAC,OAAO,EAAE,SAAS,EAAE,GAAG,SAAS,CAoB9D;AAED,kFAAkF;AAClF,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,CAM7D;AAYD;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,SAAS,EAChB,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAC1B,MAAM,GAAG,IAAI,CAaf;AASD;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE;IACJ,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,SAAS,CAAC;IACrB;;;;OAIG;IACH,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CAC7B,GACA,MAAM,CAkER;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,OAAO,EAAE,EAChB,KAAK,EAAE,SAAS,GACf,GAAG,CAAC,MAAM,CAAC,CASb"}
|
package/dist/links.js
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identity-aware markdown link rewriting for rendered omg bodies.
|
|
3
|
+
*
|
|
4
|
+
* When the loader has hydrated docs, each link destination that resolves to one
|
|
5
|
+
* of those docs is rewritten to that doc's site `href` — keyed by omg doc id /
|
|
6
|
+
* path, not by string-guessing alone. Link URL columns / projections are never
|
|
7
|
+
* touched; only markdown bodies passed through {@link rewriteMarkdownLinks}.
|
|
8
|
+
*/
|
|
9
|
+
/** Default site href: `/${slug}`. Override to match your routes (e.g. `/blog/${slug}/`). */
|
|
10
|
+
export function defaultHref({ slug }) {
|
|
11
|
+
return `/${slug}`;
|
|
12
|
+
}
|
|
13
|
+
/** Directory prefix (`""` or `"a/b/"`) of a repo-relative doc path. */
|
|
14
|
+
export function docDirOf(docPath) {
|
|
15
|
+
const i = docPath.lastIndexOf("/");
|
|
16
|
+
return i < 0 ? "" : docPath.slice(0, i + 1);
|
|
17
|
+
}
|
|
18
|
+
/** Split authored destination into path + fragment (`#…` / `^…`, marker kept). */
|
|
19
|
+
export function splitDestination(dest) {
|
|
20
|
+
const caret = dest.indexOf("^");
|
|
21
|
+
if (caret >= 0)
|
|
22
|
+
return { path: dest.slice(0, caret), fragment: dest.slice(caret) };
|
|
23
|
+
const hash = dest.indexOf("#");
|
|
24
|
+
if (hash >= 0)
|
|
25
|
+
return { path: dest.slice(0, hash), fragment: dest.slice(hash) };
|
|
26
|
+
return { path: dest, fragment: "" };
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Canonical repo-relative path for a link as authored in `docDir`
|
|
30
|
+
* (mirrors omgbase `canonicalLinkPath`).
|
|
31
|
+
*/
|
|
32
|
+
export function canonicalLinkPath(path, docDir = "") {
|
|
33
|
+
if (path.startsWith("./") || path.startsWith("../")) {
|
|
34
|
+
const out = [];
|
|
35
|
+
for (const p of (docDir + path).split("/")) {
|
|
36
|
+
if (p === "." || p === "")
|
|
37
|
+
continue;
|
|
38
|
+
if (p === "..") {
|
|
39
|
+
out.pop();
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
out.push(p);
|
|
43
|
+
}
|
|
44
|
+
return out.join("/");
|
|
45
|
+
}
|
|
46
|
+
return path.replace(/^\//, "");
|
|
47
|
+
}
|
|
48
|
+
function isExternalDest(path) {
|
|
49
|
+
if (!path)
|
|
50
|
+
return false;
|
|
51
|
+
return /^[a-z][a-z0-9+.-]*:/i.test(path);
|
|
52
|
+
}
|
|
53
|
+
/** Build path/id indexes for every hydrated doc we may link to. */
|
|
54
|
+
export function buildHrefIndex(entries) {
|
|
55
|
+
const byDocId = new Map();
|
|
56
|
+
const byPath = new Map();
|
|
57
|
+
const docIdByPath = new Map();
|
|
58
|
+
const rememberPath = (path, docId, href) => {
|
|
59
|
+
byPath.set(path, href);
|
|
60
|
+
docIdByPath.set(path, docId);
|
|
61
|
+
if (path.toLowerCase().endsWith(".md")) {
|
|
62
|
+
const stripped = path.slice(0, -3);
|
|
63
|
+
byPath.set(stripped, href);
|
|
64
|
+
docIdByPath.set(stripped, docId);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
for (const e of entries) {
|
|
68
|
+
byDocId.set(e.docId, e.href);
|
|
69
|
+
rememberPath(e.path, e.docId, e.href);
|
|
70
|
+
}
|
|
71
|
+
return { byDocId, byPath, docIdByPath };
|
|
72
|
+
}
|
|
73
|
+
/** Stable fingerprint of the href map (for digests when rewrites are enabled). */
|
|
74
|
+
export function hrefIndexFingerprint(index) {
|
|
75
|
+
const parts = [];
|
|
76
|
+
for (const [id, href] of [...index.byDocId.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {
|
|
77
|
+
parts.push(`${id}=${href}`);
|
|
78
|
+
}
|
|
79
|
+
return parts.join("|");
|
|
80
|
+
}
|
|
81
|
+
function lookupHref(canonical, index) {
|
|
82
|
+
const href = index.byPath.get(canonical);
|
|
83
|
+
const docId = index.docIdByPath.get(canonical);
|
|
84
|
+
if (href && docId)
|
|
85
|
+
return { href, docId };
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Resolve an authored destination against the href index.
|
|
90
|
+
* Returns null for external, pure-fragment, dangling, or unknown targets.
|
|
91
|
+
*/
|
|
92
|
+
export function resolveLinkHref(dest, srcPath, index, allowedDstIds) {
|
|
93
|
+
const { path, fragment } = splitDestination(dest);
|
|
94
|
+
if (!path)
|
|
95
|
+
return null; // same-doc `#heading` / `^ref` — leave alone
|
|
96
|
+
if (isExternalDest(path))
|
|
97
|
+
return null;
|
|
98
|
+
const canonical = canonicalLinkPath(path, docDirOf(srcPath));
|
|
99
|
+
const hit = lookupHref(canonical, index);
|
|
100
|
+
if (!hit)
|
|
101
|
+
return null;
|
|
102
|
+
if (allowedDstIds && !allowedDstIds.has(hit.docId))
|
|
103
|
+
return null;
|
|
104
|
+
// Avoid `href/#frag` when href already ends with `/` → `href#frag` is fine;
|
|
105
|
+
// fragment already includes the marker.
|
|
106
|
+
return `${hit.href}${fragment}`;
|
|
107
|
+
}
|
|
108
|
+
// Destination-bearing syntaxes (prefix, dest, suffix) — same shapes as omgbase.
|
|
109
|
+
const MD_LINK = /(!?\[[^\]]*\]\()([^)\s]+)((?:\s+"[^"]*")?\))/g;
|
|
110
|
+
const WIKILINK = /(!?\[\[)([^\]|]+)((?:\|[^\]]*)?\]\])/g;
|
|
111
|
+
const CODE_SPAN = /(`+)[^`][\s\S]*?\1/g;
|
|
112
|
+
const FENCE_OPEN = /^[ \t]*(`{3,}|~{3,})/;
|
|
113
|
+
const FENCE_CLOSE = /^[ \t]*(`{3,}|~{3,})[ \t]*$/;
|
|
114
|
+
/**
|
|
115
|
+
* Rewrite link destinations in markdown that resolve to hydrated omg docs.
|
|
116
|
+
* Fenced code blocks and inline code spans are left untouched.
|
|
117
|
+
*/
|
|
118
|
+
export function rewriteMarkdownLinks(body, opts) {
|
|
119
|
+
const { srcPath, hrefIndex, allowedDstIds } = opts;
|
|
120
|
+
if (hrefIndex.byDocId.size === 0)
|
|
121
|
+
return body;
|
|
122
|
+
const replaceDest = (dest) => resolveLinkHref(dest, srcPath, hrefIndex, allowedDstIds);
|
|
123
|
+
const rewriteProse = (seg) => {
|
|
124
|
+
let out = seg.replace(MD_LINK, (whole, pre, dest, post) => {
|
|
125
|
+
const next = replaceDest(dest);
|
|
126
|
+
return next === null ? whole : pre + next + post;
|
|
127
|
+
});
|
|
128
|
+
out = out.replace(WIKILINK, (whole, pre, dest, post) => {
|
|
129
|
+
const next = replaceDest(dest);
|
|
130
|
+
return next === null ? whole : pre + next + post;
|
|
131
|
+
});
|
|
132
|
+
return out;
|
|
133
|
+
};
|
|
134
|
+
/** Rewrite outside inline code spans. */
|
|
135
|
+
const rewriteOutsideSpans = (seg) => {
|
|
136
|
+
let out = "";
|
|
137
|
+
let last = 0;
|
|
138
|
+
for (const m of seg.matchAll(CODE_SPAN)) {
|
|
139
|
+
const start = m.index ?? 0;
|
|
140
|
+
out += rewriteProse(seg.slice(last, start)) + m[0];
|
|
141
|
+
last = start + m[0].length;
|
|
142
|
+
}
|
|
143
|
+
return out + rewriteProse(seg.slice(last));
|
|
144
|
+
};
|
|
145
|
+
// Split on fenced code blocks (line-oriented), rewrite only the prose regions.
|
|
146
|
+
const lines = body.split("\n");
|
|
147
|
+
const outLines = [];
|
|
148
|
+
let fence = null;
|
|
149
|
+
let proseBuf = [];
|
|
150
|
+
const flushProse = () => {
|
|
151
|
+
if (proseBuf.length === 0)
|
|
152
|
+
return;
|
|
153
|
+
outLines.push(rewriteOutsideSpans(proseBuf.join("\n")));
|
|
154
|
+
proseBuf = [];
|
|
155
|
+
};
|
|
156
|
+
for (const line of lines) {
|
|
157
|
+
if (fence) {
|
|
158
|
+
outLines.push(line);
|
|
159
|
+
const m = FENCE_CLOSE.exec(line);
|
|
160
|
+
if (m && m[1][0] === fence.ch && m[1].length >= fence.len)
|
|
161
|
+
fence = null;
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
const open = FENCE_OPEN.exec(line);
|
|
165
|
+
if (open) {
|
|
166
|
+
const fenceMark = open[1];
|
|
167
|
+
if (fenceMark[0] === "`" && line.slice(open[0].length).includes("`")) {
|
|
168
|
+
proseBuf.push(line);
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
flushProse();
|
|
172
|
+
outLines.push(line);
|
|
173
|
+
fence = { ch: fenceMark[0], len: fenceMark.length };
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
proseBuf.push(line);
|
|
177
|
+
}
|
|
178
|
+
flushProse();
|
|
179
|
+
return outLines.join("\n");
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Allowed destination doc ids for a source, from live out-edges ∩ href index.
|
|
183
|
+
* Phantoms / externals are dropped. Empty set means "no edge-gated targets".
|
|
184
|
+
*/
|
|
185
|
+
export function allowedDstsFor(srcDocId, edges, index) {
|
|
186
|
+
const out = new Set();
|
|
187
|
+
for (const e of edges) {
|
|
188
|
+
if (e.src !== srcDocId)
|
|
189
|
+
continue;
|
|
190
|
+
if (!e.dst || e.dst.startsWith("phantom:") || e.dst.startsWith("x_"))
|
|
191
|
+
continue;
|
|
192
|
+
if (!index.byDocId.has(e.dst))
|
|
193
|
+
continue;
|
|
194
|
+
out.add(e.dst);
|
|
195
|
+
}
|
|
196
|
+
return out;
|
|
197
|
+
}
|
|
198
|
+
//# sourceMappingURL=links.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"links.js","sourceRoot":"","sources":["../src/links.ts"],"names":[],"mappings":"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"}
|
package/dist/loader.d.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { Loader } from "astro/loaders";
|
|
2
|
+
import type { Transport } from "./transport.js";
|
|
3
|
+
import { type SlugContext } from "./map.js";
|
|
4
|
+
import { type HrefContext } from "./links.js";
|
|
5
|
+
import { type WatchOption } from "./watch.js";
|
|
6
|
+
export interface OmgLoaderBaseOptions {
|
|
7
|
+
/** OQX query selecting documents for this collection. Must return doc hits. */
|
|
8
|
+
query: string;
|
|
9
|
+
/** Optional result limit passed to OQX / the MCP `query` tool. */
|
|
10
|
+
limit?: number;
|
|
11
|
+
/** Repo slug (local workspace or remote multi-repo server). */
|
|
12
|
+
repo?: string;
|
|
13
|
+
/** Override slug generation. Default: path with `.md` stripped. */
|
|
14
|
+
slug?: (ctx: SlugContext) => string;
|
|
15
|
+
/**
|
|
16
|
+
* Site URL for a hydrated doc (used to rewrite markdown links that resolve to
|
|
17
|
+
* other docs in this load). Default: `/${slug}`. Match your routes, e.g.
|
|
18
|
+
* `({ slug }) => `/blog/${slug}/``.
|
|
19
|
+
*
|
|
20
|
+
* Set `href: false` to leave authored destinations unchanged.
|
|
21
|
+
*/
|
|
22
|
+
href?: false | ((ctx: HrefContext) => string);
|
|
23
|
+
/**
|
|
24
|
+
* Live-reload in `astro dev` (default true when Astro provides a watcher).
|
|
25
|
+
* Local: Vite FS watch on the vault. Remote: poll MCP `changes_since`.
|
|
26
|
+
* Store updates use the same data-store write path as glob() → existing HMR.
|
|
27
|
+
*/
|
|
28
|
+
watch?: WatchOption;
|
|
29
|
+
/** Inject a pre-built transport (tests). */
|
|
30
|
+
transport?: Transport;
|
|
31
|
+
}
|
|
32
|
+
export type OmgLoaderLocalOptions = OmgLoaderBaseOptions & {
|
|
33
|
+
/** Path to the omgbase workspace root (directory containing `.omgbase/`). */
|
|
34
|
+
workspace: string;
|
|
35
|
+
url?: undefined;
|
|
36
|
+
token?: undefined;
|
|
37
|
+
headers?: undefined;
|
|
38
|
+
headerLines?: undefined;
|
|
39
|
+
stale?: boolean;
|
|
40
|
+
};
|
|
41
|
+
export type OmgLoaderRemoteOptions = OmgLoaderBaseOptions & {
|
|
42
|
+
/**
|
|
43
|
+
* Streamable HTTP MCP endpoint (same as `omg … --server <url>`).
|
|
44
|
+
* When set, remote MCP transport is used.
|
|
45
|
+
*/
|
|
46
|
+
url: string;
|
|
47
|
+
/** Extra headers on every MCP request (CLI `-H` as a map). */
|
|
48
|
+
headers?: Record<string, string>;
|
|
49
|
+
/** Extra headers as CLI `-H` strings: `["X-Foo: bar"]`. */
|
|
50
|
+
headerLines?: string[];
|
|
51
|
+
/** Convenience bearer token → `Authorization: Bearer …` if unset. */
|
|
52
|
+
token?: string;
|
|
53
|
+
workspace?: undefined;
|
|
54
|
+
stale?: undefined;
|
|
55
|
+
};
|
|
56
|
+
export type OmgLoaderOptions = OmgLoaderLocalOptions | OmgLoaderRemoteOptions | OmgLoaderBaseOptions;
|
|
57
|
+
/**
|
|
58
|
+
* Astro Content Layer loader backed by omgbase.
|
|
59
|
+
*
|
|
60
|
+
* Local: opens a workspace via `@omgbase/core`, runs OQX, hydrates Markdown.
|
|
61
|
+
* Remote: Streamable HTTP MCP (`query` + `docs_get_many`), same as CLI `--server`.
|
|
62
|
+
* In `astro dev`, keeps the collection fresh (FS watch or `changes_since` poll)
|
|
63
|
+
* through Astro's normal content-store → HMR path.
|
|
64
|
+
*
|
|
65
|
+
* Entry `id` is the omg document id. `data.slug` is derived from path (overridable).
|
|
66
|
+
* Markdown bodies are rewritten so links to other hydrated docs use each doc's
|
|
67
|
+
* site `href` (identity via omg out-edges ∩ path resolution).
|
|
68
|
+
*/
|
|
69
|
+
export declare function omgLoader(opts: OmgLoaderOptions): Loader;
|
|
70
|
+
//# sourceMappingURL=loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../src/loader.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAiB,MAAM,eAAe,CAAC;AAC3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAGhD,OAAO,EAAe,KAAK,WAAW,EAAE,MAAM,UAAU,CAAC;AACzD,OAAO,EAAe,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAC3D,OAAO,EAAsC,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAElF,MAAM,WAAW,oBAAoB;IACnC,+EAA+E;IAC/E,KAAK,EAAE,MAAM,CAAC;IACd,kEAAkE;IAClE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+DAA+D;IAC/D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mEAAmE;IACnE,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,WAAW,KAAK,MAAM,CAAC;IACpC;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC,GAAG,EAAE,WAAW,KAAK,MAAM,CAAC,CAAC;IAC9C;;;;OAIG;IACH,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,4CAA4C;IAC5C,SAAS,CAAC,EAAE,SAAS,CAAC;CACvB;AAED,MAAM,MAAM,qBAAqB,GAAG,oBAAoB,GAAG;IACzD,6EAA6E;IAC7E,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,SAAS,CAAC;IAChB,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG,oBAAoB,GAAG;IAC1D;;;OAGG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ,8DAA8D;IAC9D,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,2DAA2D;IAC3D,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,qEAAqE;IACrE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,KAAK,CAAC,EAAE,SAAS,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG,qBAAqB,GAAG,sBAAsB,GAAG,oBAAoB,CAAC;AA2BrG;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,gBAAgB,GAAG,MAAM,CAsCxD"}
|
package/dist/loader.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { createLocalTransport } from "./local.js";
|
|
2
|
+
import { createRemoteTransport } from "./remote.js";
|
|
3
|
+
import { defaultSlug } from "./map.js";
|
|
4
|
+
import { defaultHref } from "./links.js";
|
|
5
|
+
import { startWatch, stopWatch, syncEntries } from "./watch.js";
|
|
6
|
+
function resolveTransport(opts) {
|
|
7
|
+
if (opts.transport)
|
|
8
|
+
return opts.transport;
|
|
9
|
+
if ("url" in opts && opts.url) {
|
|
10
|
+
const remote = {
|
|
11
|
+
url: opts.url,
|
|
12
|
+
...(opts.token !== undefined ? { token: opts.token } : {}),
|
|
13
|
+
...(opts.headers !== undefined ? { headers: opts.headers } : {}),
|
|
14
|
+
...(opts.headerLines !== undefined ? { headerLines: opts.headerLines } : {}),
|
|
15
|
+
...(opts.repo !== undefined ? { repo: opts.repo } : {}),
|
|
16
|
+
};
|
|
17
|
+
return createRemoteTransport(remote);
|
|
18
|
+
}
|
|
19
|
+
if ("workspace" in opts && opts.workspace) {
|
|
20
|
+
const local = {
|
|
21
|
+
workspace: opts.workspace,
|
|
22
|
+
...(opts.repo !== undefined ? { repo: opts.repo } : {}),
|
|
23
|
+
...(opts.stale !== undefined ? { stale: opts.stale } : {}),
|
|
24
|
+
};
|
|
25
|
+
return createLocalTransport(local);
|
|
26
|
+
}
|
|
27
|
+
throw new Error("omgLoader requires either `workspace` (local) or `url` (Streamable HTTP MCP), or an explicit `transport`");
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Astro Content Layer loader backed by omgbase.
|
|
31
|
+
*
|
|
32
|
+
* Local: opens a workspace via `@omgbase/core`, runs OQX, hydrates Markdown.
|
|
33
|
+
* Remote: Streamable HTTP MCP (`query` + `docs_get_many`), same as CLI `--server`.
|
|
34
|
+
* In `astro dev`, keeps the collection fresh (FS watch or `changes_since` poll)
|
|
35
|
+
* through Astro's normal content-store → HMR path.
|
|
36
|
+
*
|
|
37
|
+
* Entry `id` is the omg document id. `data.slug` is derived from path (overridable).
|
|
38
|
+
* Markdown bodies are rewritten so links to other hydrated docs use each doc's
|
|
39
|
+
* site `href` (identity via omg out-edges ∩ path resolution).
|
|
40
|
+
*/
|
|
41
|
+
export function omgLoader(opts) {
|
|
42
|
+
const slugFn = opts.slug ?? defaultSlug;
|
|
43
|
+
const hrefFn = opts.href === false ? false : (opts.href ?? defaultHref);
|
|
44
|
+
const watchOpt = opts.watch ?? true;
|
|
45
|
+
return {
|
|
46
|
+
name: "@omgbase/astro",
|
|
47
|
+
async load(context) {
|
|
48
|
+
await stopWatch(context.collection);
|
|
49
|
+
const transport = resolveTransport(opts);
|
|
50
|
+
const syncOpts = {
|
|
51
|
+
query: opts.query,
|
|
52
|
+
...(opts.limit !== undefined ? { limit: opts.limit } : {}),
|
|
53
|
+
...(opts.repo !== undefined ? { repo: opts.repo } : {}),
|
|
54
|
+
slug: slugFn,
|
|
55
|
+
href: hrefFn,
|
|
56
|
+
};
|
|
57
|
+
try {
|
|
58
|
+
context.logger.info(`querying omgbase (${transport.kind})`);
|
|
59
|
+
const { seen } = await syncEntries(context, transport, syncOpts);
|
|
60
|
+
context.logger.info(`loaded ${seen} entries from omgbase`);
|
|
61
|
+
if (context.watcher && watchOpt !== false) {
|
|
62
|
+
await startWatch(context, transport, { ...syncOpts, watch: watchOpt });
|
|
63
|
+
// Watch owns the transport lifetime.
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
await transport.close?.();
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
await transport.close?.();
|
|
70
|
+
throw err;
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.js","sourceRoot":"","sources":["../src/loader.ts"],"names":[],"mappings":"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"}
|