@seedprotocol/feed-hyper 0.6.10 → 0.7.1

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/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Publish Seed Protocol RSS/Atom/JSON feeds into a **Hyperdrive**, announce them on **Hyperswarm**, and serve them to ordinary RSS readers over localhost HTTP.
4
4
 
5
- `seed feed serve` accepts both drive paths (`/posts/rss.xml`) and historical HTTP paths (`/posts/rss`).
5
+ `seed-feed serve` accepts both drive paths (`/posts/rss.xml`) and historical HTTP paths (`/posts/rss`).
6
6
 
7
7
  Generation stays in [`@seedprotocol/feed`](../feed). This package is transport only (Node 20+). It pulls in Holepunch native modules (`sodium-native`, etc.).
8
8
 
@@ -10,7 +10,7 @@ Generation stays in [`@seedprotocol/feed`](../feed). This package is transport o
10
10
 
11
11
  - A feed’s identity is its **Hyperdrive public key** (z32), not a hostname.
12
12
  - Bytes are signed by that key. Peers and HTTP gateways are interchangeable mirrors.
13
- - `https://feed.seedprotocol.io` (or any `seed feed serve` process) is a **convenience gateway**, not the source of truth.
13
+ - `https://feed.seedprotocol.io` (or any `seed-feed serve` process) is a **convenience gateway**, not the source of truth.
14
14
  - Official Seed feeds will use `DEFAULT_SEED_FEED_HYPER_KEY` once ops publish and back up the first keypair (empty string until then).
15
15
 
16
16
  ## Drive layout
@@ -72,12 +72,12 @@ console.log(localFeedUrl(gateway.baseUrl, 'post', 'rss'))
72
72
 
73
73
  Pass `fixtureContents: { '/posts/rss.xml': '...' }` to `publishFeed` to skip EAS generation.
74
74
 
75
- ## CLI (via `@seedprotocol/cli`)
75
+ ## CLI (`seed-feed`)
76
76
 
77
77
  ```bash
78
- seed feed publish --schema post --format rss,atom,json --store .seed/feed-store
79
- seed feed seed <key> --store .seed/feed-store
80
- seed feed serve <key> --port 8080 --host 127.0.0.1
78
+ seed-feed publish --schema post --format rss,atom,json --store .seed/feed-store
79
+ seed-feed seed <key> --store .seed/feed-store
80
+ seed-feed serve <key> --port 8080 --host 127.0.0.1
81
81
  ```
82
82
 
83
83
  Use `--no-announce` on publish/serve to stay offline (local store only).
@@ -85,8 +85,8 @@ Use `--no-announce` on publish/serve to stay offline (local store only).
85
85
  ## Operator notes
86
86
 
87
87
  1. Back up the Corestore directory for any key you care about (especially the official key).
88
- 2. Run at least one always-on `seed feed seed <key>` (or leave `publish` announcing) so cold feeds resolve.
89
- 3. Point RSS apps at `http://127.0.0.1:8080/posts/rss.xml` or `http://127.0.0.1:8080/posts/rss` after `seed feed serve`.
88
+ 2. Run at least one always-on `seed-feed seed <key>` (or leave `publish` announcing) so cold feeds resolve.
89
+ 3. Point RSS apps at `http://127.0.0.1:8080/posts/rss.xml` or `http://127.0.0.1:8080/posts/rss` after `seed-feed serve`.
90
90
  4. Unpublish/revocation: regenerating omits revoked items from the **current** drive version; Hypercore history retains prior versions.
91
91
 
92
92
  See [docs/FEED_HYPER.md](../../docs/FEED_HYPER.md) for the full runbook and manual validation checklist.
package/dist/bin.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=bin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":""}
package/dist/bin.js ADDED
@@ -0,0 +1,118 @@
1
+ #!/usr/bin/env node
2
+ import { a as e, i as t, r as n, s as r } from "./src-CTwmvagY.js";
3
+ import i from "node:path";
4
+ import a from "node:process";
5
+ import { parseArgs as o } from "node:util";
6
+ //#region src/bin.ts
7
+ var s = "Usage:\n seed-feed publish --schema <names> [--format rss,atom,json] [--store <path>]\n [--drive-name <name>] [--page-size <n>] [--site-url <url>] [--no-announce]\n Generate feeds and write them into a Hyperdrive, then announce on Hyperswarm.\n\n seed-feed seed <key> [--store <path>]\n Replicate a feed Hyperdrive (no generation).\n\n seed-feed serve <key> [--store <path>] [--port 8080] [--host 127.0.0.1] [--no-announce]\n Seed a feed and expose it over localhost HTTP for RSS readers.\n\nDefault --store: ./.seed/feed-store";
8
+ function c(e) {
9
+ console.error(`[Seed Protocol] ${e}`), console.error(s), a.exit(1);
10
+ }
11
+ function l(e) {
12
+ let t = [
13
+ "rss",
14
+ "atom",
15
+ "json"
16
+ ], n = e.split(",").map((e) => e.trim().toLowerCase()).filter((e) => t.includes(e));
17
+ return n.length || c(`Invalid --format (expected rss,atom,json): ${e}`), n;
18
+ }
19
+ function u(e) {
20
+ let t = (e ?? "").split(",").map((e) => e.trim()).filter(Boolean);
21
+ return t.length || c("At least one --schema is required"), t;
22
+ }
23
+ function d(e, t) {
24
+ return parseInt(e, 10) || t;
25
+ }
26
+ function f() {
27
+ return new Promise((e) => {
28
+ let t = () => {
29
+ a.off("SIGINT", t), a.off("SIGTERM", t), e();
30
+ };
31
+ a.on("SIGINT", t), a.on("SIGTERM", t);
32
+ });
33
+ }
34
+ async function p(a) {
35
+ let [p, ...m] = a;
36
+ if (!p || p === "--help" || p === "-h" || p === "help") {
37
+ console.log(s);
38
+ return;
39
+ }
40
+ let { values: h, positionals: g } = o({
41
+ args: m,
42
+ allowPositionals: !0,
43
+ options: {
44
+ schema: { type: "string" },
45
+ format: {
46
+ type: "string",
47
+ default: "rss,atom,json"
48
+ },
49
+ store: {
50
+ type: "string",
51
+ default: i.join(".seed", "feed-store")
52
+ },
53
+ "drive-name": {
54
+ type: "string",
55
+ default: "seed-feed"
56
+ },
57
+ "page-size": {
58
+ type: "string",
59
+ default: "25"
60
+ },
61
+ "site-url": { type: "string" },
62
+ port: {
63
+ type: "string",
64
+ default: "8080"
65
+ },
66
+ host: {
67
+ type: "string",
68
+ default: "127.0.0.1"
69
+ },
70
+ "no-announce": {
71
+ type: "boolean",
72
+ default: !1
73
+ }
74
+ }
75
+ }), _ = i.resolve(h.store), v = !h["no-announce"];
76
+ if (p === "publish") {
77
+ let e = u(h.schema), t = l(h.format);
78
+ console.log(`[Seed Protocol] Publishing feeds for schemas: ${e.join(", ")}`), console.log(`[Seed Protocol] Formats: ${t.join(", ")}`), console.log(`[Seed Protocol] Store: ${_}`);
79
+ let n = await r({
80
+ schemas: e,
81
+ formats: t,
82
+ storePath: _,
83
+ driveName: h["drive-name"],
84
+ pageSize: d(h["page-size"], 25),
85
+ announce: v,
86
+ siteUrl: h["site-url"]
87
+ });
88
+ console.log(`[Seed Protocol] Drive key (z32): ${n.key}`), console.log(`[Seed Protocol] hyper URL: ${n.hyperUrl}`), console.log(`[Seed Protocol] Version: ${n.version}`), console.log("[Seed Protocol] Paths:");
89
+ for (let e of n.paths) console.log(` ${e}`);
90
+ v ? (console.log("[Seed Protocol] Announcing on Hyperswarm. Press Ctrl+C to stop."), await f(), await n.close(), console.log("[Seed Protocol] Publisher stopped.")) : await n.close();
91
+ } else if (p === "seed" || p === "serve") {
92
+ let r = g[0];
93
+ if (r || c(`seed-feed ${p} requires a Hyperdrive key (z32 or hex)`), p === "seed") {
94
+ console.log(`[Seed Protocol] Seeding feed ${r}`), console.log(`[Seed Protocol] Store: ${_}`);
95
+ let t = await e({
96
+ key: r,
97
+ storePath: _
98
+ });
99
+ console.log(`[Seed Protocol] Seeding ${t.key}. Press Ctrl+C to stop.`), await f(), await t.close(), console.log("[Seed Protocol] Seeder stopped.");
100
+ return;
101
+ }
102
+ console.log(`[Seed Protocol] Serving feed ${r}`);
103
+ let i = await t({
104
+ key: r,
105
+ storePath: _,
106
+ port: d(h.port, 8080),
107
+ host: h.host,
108
+ announce: v
109
+ });
110
+ console.log(`[Seed Protocol] HTTP gateway: ${i.baseUrl}`), console.log(`[Seed Protocol] Example RSS URL: ${n(i.baseUrl, "post", "rss")}`), console.log("[Seed Protocol] Press Ctrl+C to stop."), await f(), await i.close(), console.log("[Seed Protocol] Serve stopped.");
111
+ } else c(`Unknown command: ${p}`);
112
+ }
113
+ p(a.argv.slice(2)).catch((e) => {
114
+ console.error("[Seed Protocol]", e instanceof Error ? e.message : e), a.exit(1);
115
+ });
116
+ //#endregion
117
+
118
+ //# sourceMappingURL=bin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.js","names":["seedFeed","serveFeed","localFeedUrl","publishFeed","path","process","USAGE","fail","message","parseFormats","allowed","formats","raw","s","p","parseSchemas","schemas","parseIntOr","fallback","waitForSignal","onStop","resolve","main","command","rest","argv","parseArgs","storePath","values","announce","session","key","positionals","err"],"sources":["../src/bin.ts"],"sourcesContent":["/**\n * `seed-feed publish|seed|serve` — operator CLI for Seed feeds over Hyperdrive / Hyperswarm.\n * Run under Node (Holepunch natives may crash under Bun).\n */\nimport path from 'node:path'\nimport process from 'node:process'\nimport { parseArgs } from 'node:util'\nimport type { FeedFormat } from '@seedprotocol/feed'\nimport { localFeedUrl, publishFeed, seedFeed, serveFeed } from './index'\n\nconst USAGE = `Usage:\n seed-feed publish --schema <names> [--format rss,atom,json] [--store <path>]\n [--drive-name <name>] [--page-size <n>] [--site-url <url>] [--no-announce]\n Generate feeds and write them into a Hyperdrive, then announce on Hyperswarm.\n\n seed-feed seed <key> [--store <path>]\n Replicate a feed Hyperdrive (no generation).\n\n seed-feed serve <key> [--store <path>] [--port 8080] [--host 127.0.0.1] [--no-announce]\n Seed a feed and expose it over localhost HTTP for RSS readers.\n\nDefault --store: ./.seed/feed-store`\n\nfunction fail(message: string): never {\n console.error(`[Seed Protocol] ${message}`)\n console.error(USAGE)\n process.exit(1)\n}\n\nfunction parseFormats(raw: string): FeedFormat[] {\n const allowed: FeedFormat[] = ['rss', 'atom', 'json']\n const formats = raw\n .split(',')\n .map((s) => s.trim().toLowerCase())\n .filter((p): p is FeedFormat => (allowed as string[]).includes(p))\n if (!formats.length) fail(`Invalid --format (expected rss,atom,json): ${raw}`)\n return formats\n}\n\nfunction parseSchemas(raw: string | undefined): string[] {\n const schemas = (raw ?? '')\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean)\n if (!schemas.length) fail('At least one --schema is required')\n return schemas\n}\n\nfunction parseIntOr(raw: string, fallback: number): number {\n return parseInt(raw, 10) || fallback\n}\n\nfunction waitForSignal(): Promise<void> {\n return new Promise((resolve) => {\n const onStop = () => {\n process.off('SIGINT', onStop)\n process.off('SIGTERM', onStop)\n resolve()\n }\n process.on('SIGINT', onStop)\n process.on('SIGTERM', onStop)\n })\n}\n\nasync function main(argv: string[]): Promise<void> {\n const [command, ...rest] = argv\n if (!command || command === '--help' || command === '-h' || command === 'help') {\n console.log(USAGE)\n return\n }\n\n const { values, positionals } = parseArgs({\n args: rest,\n allowPositionals: true,\n options: {\n schema: { type: 'string' },\n format: { type: 'string', default: 'rss,atom,json' },\n store: { type: 'string', default: path.join('.seed', 'feed-store') },\n 'drive-name': { type: 'string', default: 'seed-feed' },\n 'page-size': { type: 'string', default: '25' },\n 'site-url': { type: 'string' },\n port: { type: 'string', default: '8080' },\n host: { type: 'string', default: '127.0.0.1' },\n 'no-announce': { type: 'boolean', default: false },\n },\n })\n const storePath = path.resolve(values.store)\n const announce = !values['no-announce']\n\n if (command === 'publish') {\n const schemas = parseSchemas(values.schema)\n const formats = parseFormats(values.format)\n console.log(`[Seed Protocol] Publishing feeds for schemas: ${schemas.join(', ')}`)\n console.log(`[Seed Protocol] Formats: ${formats.join(', ')}`)\n console.log(`[Seed Protocol] Store: ${storePath}`)\n\n const session = await publishFeed({\n schemas,\n formats,\n storePath,\n driveName: values['drive-name'],\n pageSize: parseIntOr(values['page-size'], 25),\n announce,\n siteUrl: values['site-url'],\n })\n\n console.log(`[Seed Protocol] Drive key (z32): ${session.key}`)\n console.log(`[Seed Protocol] hyper URL: ${session.hyperUrl}`)\n console.log(`[Seed Protocol] Version: ${session.version}`)\n console.log('[Seed Protocol] Paths:')\n for (const p of session.paths) console.log(` ${p}`)\n\n if (announce) {\n console.log('[Seed Protocol] Announcing on Hyperswarm. Press Ctrl+C to stop.')\n await waitForSignal()\n await session.close()\n console.log('[Seed Protocol] Publisher stopped.')\n } else {\n await session.close()\n }\n return\n }\n\n if (command === 'seed' || command === 'serve') {\n const key = positionals[0]\n if (!key) fail(`seed-feed ${command} requires a Hyperdrive key (z32 or hex)`)\n\n if (command === 'seed') {\n console.log(`[Seed Protocol] Seeding feed ${key}`)\n console.log(`[Seed Protocol] Store: ${storePath}`)\n const session = await seedFeed({ key, storePath })\n console.log(`[Seed Protocol] Seeding ${session.key}. Press Ctrl+C to stop.`)\n await waitForSignal()\n await session.close()\n console.log('[Seed Protocol] Seeder stopped.')\n return\n }\n\n console.log(`[Seed Protocol] Serving feed ${key}`)\n const session = await serveFeed({\n key,\n storePath,\n port: parseIntOr(values.port, 8080),\n host: values.host,\n announce,\n })\n console.log(`[Seed Protocol] HTTP gateway: ${session.baseUrl}`)\n console.log(`[Seed Protocol] Example RSS URL: ${localFeedUrl(session.baseUrl, 'post', 'rss')}`)\n console.log('[Seed Protocol] Press Ctrl+C to stop.')\n await waitForSignal()\n await session.close()\n console.log('[Seed Protocol] Serve stopped.')\n return\n }\n\n fail(`Unknown command: ${command}`)\n}\n\nmain(process.argv.slice(2)).catch((err) => {\n console.error('[Seed Protocol]', err instanceof Error ? err.message : err)\n process.exit(1)\n})\n"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,272 +1 @@
1
- import e from "pluralize";
2
- import t from "corestore";
3
- import n from "hyperdrive";
4
- import r from "hyperswarm";
5
- import i from "hypercore-id-encoding";
6
- import { createFeed as a, getFeedItemsBySchemaName as o } from "@seedprotocol/feed";
7
- import s from "node:http";
8
- import c from "serve-drive";
9
- import { DEFAULT_SEED_FEED_HYPER_KEY as l } from "@seedprotocol/arweave";
10
- //#region src/paths.ts
11
- function u(t, n, r) {
12
- let i = e(t.toLowerCase()), a = n === "json" ? "feed.json" : n === "atom" ? "atom.xml" : "rss.xml";
13
- return r ? `/${i}/archive/${r.year}/${r.month}/${a}` : `/${i}/${a}`;
14
- }
15
- var d = "/registry.json";
16
- function f(t, n) {
17
- return `/${e(t.toLowerCase())}/${n}`;
18
- }
19
- function p(e) {
20
- let t = e.startsWith("/") ? e : `/${e}`, n = t.length > 1 && t.endsWith("/") ? t.slice(0, -1) : t;
21
- if (/\.(xml|json)$/i.test(n)) return n;
22
- let r = n.match(/^(\/[^/]+(?:\/archive\/\d+\/\d+)?)\/(rss|atom|json)$/i);
23
- if (!r) return n;
24
- let i = r[1], a = r[2].toLowerCase();
25
- return a === "json" ? `${i}/feed.json` : a === "atom" ? `${i}/atom.xml` : `${i}/rss.xml`;
26
- }
27
- function m(e) {
28
- return `hyper://${e}`;
29
- }
30
- //#endregion
31
- //#region src/store.ts
32
- function h(e) {
33
- return Buffer.from(i.decode(e));
34
- }
35
- function g(e) {
36
- return i.normalize(e);
37
- }
38
- async function _(e) {
39
- let i = new t(e.storePath);
40
- await i.ready();
41
- let a = e.key ? new n(i, h(e.key)) : new n(i, { name: e.driveName ?? "seed-feed" });
42
- await a.ready();
43
- let o = null;
44
- return e.announce !== !1 && (o = new r(), o.on("connection", (e) => {
45
- i.replicate(e);
46
- }), o.join(a.discoveryKey, {
47
- server: !0,
48
- client: !0
49
- }), await o.flush()), {
50
- store: i,
51
- drive: a,
52
- swarm: o
53
- };
54
- }
55
- async function v(e) {
56
- e.swarm && await e.swarm.destroy(), await e.drive.close(), await e.store.close();
57
- }
58
- function y(e, t) {
59
- let n = e.replicate(!0), r = t.replicate(!1);
60
- return n.pipe(r).pipe(n), () => {
61
- n.destroy?.(), r.destroy?.();
62
- };
63
- }
64
- function b(e) {
65
- return typeof e == "string" ? h(e) : Buffer.from(e);
66
- }
67
- //#endregion
68
- //#region src/publishFeed.ts
69
- var x = "https://seedprotocol.io", S = "Seed Protocol";
70
- async function C(e) {
71
- try {
72
- let t = await import("@seedprotocol/feed"), n = typeof t.getSiteConfig == "function" ? t.getSiteConfig() : null;
73
- return {
74
- siteUrl: e?.siteUrl ?? n?.siteUrl ?? x,
75
- title: e?.title ?? n?.title ?? S
76
- };
77
- } catch {
78
- return {
79
- siteUrl: e?.siteUrl ?? x,
80
- title: e?.title ?? S
81
- };
82
- }
83
- }
84
- async function w(e, t, n) {
85
- await e.put(t, Buffer.from(n, "utf-8"));
86
- }
87
- async function T(e, t, n) {
88
- let r = m(t), i = n.pageSize ?? 25, s = [], c = [];
89
- if (n.fixtureContents) {
90
- for (let [t, r] of Object.entries(n.fixtureContents)) await w(e, t, r), s.push(t);
91
- c.push({
92
- schema: n.schemas[0],
93
- formats: n.formats,
94
- paths: [...s]
95
- });
96
- } else {
97
- let { siteUrl: t, title: l } = await C({ siteUrl: n.siteUrl });
98
- for (let d of n.schemas) {
99
- let p = [], m = await o(d, {
100
- limit: i,
101
- skip: 0
102
- }), h = m.length === i;
103
- for (let o of n.formats) {
104
- let n = u(d, o), c = `${r}${f(d, o)}`;
105
- await w(e, n, await a(m, d, o, void 0, {
106
- page: 1,
107
- pageSize: i,
108
- hasNext: h,
109
- baseUrl: c
110
- }, void 0, !1, void 0, {
111
- feedUrl: r,
112
- siteUrl: t,
113
- title: l
114
- })), s.push(n), p.push(n);
115
- }
116
- if (n.includeArchives) {
117
- let i = /* @__PURE__ */ new Date(), o = i.getFullYear(), c = i.getMonth() + 1;
118
- for (let i of n.formats) {
119
- let n = u(d, i, {
120
- year: o,
121
- month: c
122
- });
123
- await w(e, n, await a(m, d, i, void 0, void 0, [{
124
- rel: "current",
125
- href: `${r}${f(d, i)}`
126
- }], !0, void 0, {
127
- feedUrl: r,
128
- siteUrl: t
129
- })), s.push(n), p.push(n);
130
- }
131
- }
132
- c.push({
133
- schema: d,
134
- formats: [...n.formats],
135
- paths: p
136
- });
137
- }
138
- }
139
- let l = {
140
- key: t,
141
- version: e.version,
142
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
143
- schemas: c
144
- };
145
- return await w(e, d, JSON.stringify(l, null, 2)), s.push(d), {
146
- paths: s,
147
- hyperUrl: r
148
- };
149
- }
150
- async function E(e) {
151
- if (!e.schemas.length) throw Error("publishFeed: schemas must be non-empty");
152
- if (!e.formats.length) throw Error("publishFeed: formats must be non-empty");
153
- let t = e.announce !== !1, n = await _({
154
- storePath: e.storePath,
155
- driveName: e.driveName,
156
- announce: t
157
- }), r = g(n.drive.key), { paths: i, hyperUrl: a } = await T(n.drive, r, e), o = n.drive.version, s = !1, c = async () => {
158
- s || (s = !0, await v(n));
159
- };
160
- return t || await c(), {
161
- key: r,
162
- version: o,
163
- paths: i,
164
- hyperUrl: a,
165
- close: c
166
- };
167
- }
168
- //#endregion
169
- //#region src/openFeed.ts
170
- async function D(e) {
171
- let t = await _({
172
- storePath: e.storePath,
173
- key: e.key,
174
- announce: e.announce !== !1
175
- }), n = e.syncTimeoutMs ?? 15e3;
176
- t.drive.core.length === 0 && n > 0 && await Promise.race([t.drive.core.update({ wait: !0 }), new Promise((e) => setTimeout(e, n))]).catch(() => {});
177
- let r = async (e) => {
178
- let n = await t.drive.get(e);
179
- return n ? Buffer.from(n).toString("utf-8") : null;
180
- };
181
- return {
182
- key: g(t.drive.key),
183
- version: t.drive.version,
184
- drive: t.drive,
185
- get: r,
186
- getRegistry: async () => {
187
- let e = await r(d);
188
- if (!e) return null;
189
- try {
190
- return JSON.parse(e);
191
- } catch {
192
- return null;
193
- }
194
- },
195
- close: () => v(t)
196
- };
197
- }
198
- //#endregion
199
- //#region src/seedFeed.ts
200
- async function O(e) {
201
- let t = await _({
202
- storePath: e.storePath,
203
- key: e.key,
204
- announce: !0
205
- });
206
- try {
207
- await t.drive.core.update({ wait: !1 });
208
- } catch {}
209
- return {
210
- key: g(t.drive.key),
211
- close: () => v(t)
212
- };
213
- }
214
- //#endregion
215
- //#region src/serveFeed.ts
216
- function k(e) {
217
- e.on("request", (e) => {
218
- if (!e.url) return;
219
- let t = e.url.indexOf("?"), n = t === -1 ? e.url : e.url.slice(0, t), r = t === -1 ? "" : e.url.slice(t), i = p(n);
220
- i !== n && (e.url = i + r);
221
- });
222
- }
223
- async function A(e) {
224
- let t = e.host ?? "127.0.0.1", n = e.port ?? 8080, r = await _({
225
- storePath: e.storePath,
226
- key: e.key,
227
- announce: e.announce !== !1
228
- });
229
- try {
230
- await r.drive.core.update({ wait: !1 });
231
- } catch {}
232
- let i = g(r.drive.key), a = s.createServer();
233
- k(a);
234
- let o = new c({
235
- server: a,
236
- port: n,
237
- host: t,
238
- anyPort: n === 0,
239
- token: !1,
240
- get: async ({ key: e }) => e && !e.equals(r.drive.key) ? null : r.drive
241
- });
242
- await o.ready();
243
- let l = typeof o.address == "function" ? o.address()?.port ?? n : n;
244
- return {
245
- key: i,
246
- port: l,
247
- host: t,
248
- baseUrl: `http://${t}:${l}`,
249
- close: async () => {
250
- typeof o.close == "function" ? await o.close() : typeof o.suspend == "function" && await o.suspend(), await v(r);
251
- }
252
- };
253
- }
254
- function j(e, t, n) {
255
- return `${e.replace(/\/$/, "")}${u(t, n)}`;
256
- }
257
- //#endregion
258
- //#region src/watchFeed.ts
259
- function M(e, t) {
260
- let n = e.core, r = !1, i = () => {
261
- r || Promise.resolve(t.onUpdate(e.version)).catch((e) => {
262
- console.error("[feed-hyper] watchFeed onUpdate error:", e);
263
- });
264
- };
265
- return n.on("append", i), () => {
266
- r = !0, n.off("append", i);
267
- };
268
- }
269
- //#endregion
270
- export { l as DEFAULT_SEED_FEED_HYPER_KEY, d as REGISTRY_PATH, v as closeFeedStore, g as encodeKey, u as feedDrivePath, f as feedHttpPath, m as hyperFeedUrl, b as keyToBuffer, j as localFeedUrl, D as openFeed, _ as openFeedStore, E as publishFeed, y as replicateStores, p as resolveHttpToDrivePath, O as seedFeed, A as serveFeed, M as watchFeed };
271
-
272
- //# sourceMappingURL=index.js.map
1
+ export { t as DEFAULT_SEED_FEED_HYPER_KEY, p as REGISTRY_PATH, c as closeFeedStore, l as encodeKey, m as feedDrivePath, h as feedHttpPath, g as hyperFeedUrl, u as keyToBuffer, r as localFeedUrl, o as openFeed, d as openFeedStore, s as publishFeed, f as replicateStores, _ as resolveHttpToDrivePath, a as seedFeed, i as serveFeed, n as watchFeed } from "./src-CTwmvagY.js";
@@ -0,0 +1,272 @@
1
+ import e from "pluralize";
2
+ import t from "corestore";
3
+ import n from "hyperdrive";
4
+ import r from "hyperswarm";
5
+ import i from "hypercore-id-encoding";
6
+ import { createFeed as a, getFeedItemsBySchemaName as o } from "@seedprotocol/feed";
7
+ import s from "node:http";
8
+ import c from "serve-drive";
9
+ import { DEFAULT_SEED_FEED_HYPER_KEY as l } from "@seedprotocol/arweave";
10
+ //#region src/paths.ts
11
+ function u(t, n, r) {
12
+ let i = e(t.toLowerCase()), a = n === "json" ? "feed.json" : n === "atom" ? "atom.xml" : "rss.xml";
13
+ return r ? `/${i}/archive/${r.year}/${r.month}/${a}` : `/${i}/${a}`;
14
+ }
15
+ var d = "/registry.json";
16
+ function f(t, n) {
17
+ return `/${e(t.toLowerCase())}/${n}`;
18
+ }
19
+ function p(e) {
20
+ let t = e.startsWith("/") ? e : `/${e}`, n = t.length > 1 && t.endsWith("/") ? t.slice(0, -1) : t;
21
+ if (/\.(xml|json)$/i.test(n)) return n;
22
+ let r = n.match(/^(\/[^/]+(?:\/archive\/\d+\/\d+)?)\/(rss|atom|json)$/i);
23
+ if (!r) return n;
24
+ let i = r[1], a = r[2].toLowerCase();
25
+ return a === "json" ? `${i}/feed.json` : a === "atom" ? `${i}/atom.xml` : `${i}/rss.xml`;
26
+ }
27
+ function m(e) {
28
+ return `hyper://${e}`;
29
+ }
30
+ //#endregion
31
+ //#region src/store.ts
32
+ function h(e) {
33
+ return Buffer.from(i.decode(e));
34
+ }
35
+ function g(e) {
36
+ return i.normalize(e);
37
+ }
38
+ async function _(e) {
39
+ let i = new t(e.storePath);
40
+ await i.ready();
41
+ let a = e.key ? new n(i, h(e.key)) : new n(i, { name: e.driveName ?? "seed-feed" });
42
+ await a.ready();
43
+ let o = null;
44
+ return e.announce !== !1 && (o = new r(), o.on("connection", (e) => {
45
+ i.replicate(e);
46
+ }), o.join(a.discoveryKey, {
47
+ server: !0,
48
+ client: !0
49
+ }), await o.flush()), {
50
+ store: i,
51
+ drive: a,
52
+ swarm: o
53
+ };
54
+ }
55
+ async function v(e) {
56
+ e.swarm && await e.swarm.destroy(), await e.drive.close(), await e.store.close();
57
+ }
58
+ function y(e, t) {
59
+ let n = e.replicate(!0), r = t.replicate(!1);
60
+ return n.pipe(r).pipe(n), () => {
61
+ n.destroy?.(), r.destroy?.();
62
+ };
63
+ }
64
+ function b(e) {
65
+ return typeof e == "string" ? h(e) : Buffer.from(e);
66
+ }
67
+ //#endregion
68
+ //#region src/publishFeed.ts
69
+ var x = "https://seedprotocol.io", S = "Seed Protocol";
70
+ async function C(e) {
71
+ try {
72
+ let t = await import("@seedprotocol/feed"), n = typeof t.getSiteConfig == "function" ? t.getSiteConfig() : null;
73
+ return {
74
+ siteUrl: e?.siteUrl ?? n?.siteUrl ?? x,
75
+ title: e?.title ?? n?.title ?? S
76
+ };
77
+ } catch {
78
+ return {
79
+ siteUrl: e?.siteUrl ?? x,
80
+ title: e?.title ?? S
81
+ };
82
+ }
83
+ }
84
+ async function w(e, t, n) {
85
+ await e.put(t, Buffer.from(n, "utf-8"));
86
+ }
87
+ async function T(e, t, n) {
88
+ let r = m(t), i = n.pageSize ?? 25, s = [], c = [];
89
+ if (n.fixtureContents) {
90
+ for (let [t, r] of Object.entries(n.fixtureContents)) await w(e, t, r), s.push(t);
91
+ c.push({
92
+ schema: n.schemas[0],
93
+ formats: n.formats,
94
+ paths: [...s]
95
+ });
96
+ } else {
97
+ let { siteUrl: t, title: l } = await C({ siteUrl: n.siteUrl });
98
+ for (let d of n.schemas) {
99
+ let p = [], m = await o(d, {
100
+ limit: i,
101
+ skip: 0
102
+ }), h = m.length === i;
103
+ for (let o of n.formats) {
104
+ let n = u(d, o), c = `${r}${f(d, o)}`;
105
+ await w(e, n, await a(m, d, o, void 0, {
106
+ page: 1,
107
+ pageSize: i,
108
+ hasNext: h,
109
+ baseUrl: c
110
+ }, void 0, !1, void 0, {
111
+ feedUrl: r,
112
+ siteUrl: t,
113
+ title: l
114
+ })), s.push(n), p.push(n);
115
+ }
116
+ if (n.includeArchives) {
117
+ let i = /* @__PURE__ */ new Date(), o = i.getFullYear(), c = i.getMonth() + 1;
118
+ for (let i of n.formats) {
119
+ let n = u(d, i, {
120
+ year: o,
121
+ month: c
122
+ });
123
+ await w(e, n, await a(m, d, i, void 0, void 0, [{
124
+ rel: "current",
125
+ href: `${r}${f(d, i)}`
126
+ }], !0, void 0, {
127
+ feedUrl: r,
128
+ siteUrl: t
129
+ })), s.push(n), p.push(n);
130
+ }
131
+ }
132
+ c.push({
133
+ schema: d,
134
+ formats: [...n.formats],
135
+ paths: p
136
+ });
137
+ }
138
+ }
139
+ let l = {
140
+ key: t,
141
+ version: e.version,
142
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
143
+ schemas: c
144
+ };
145
+ return await w(e, d, JSON.stringify(l, null, 2)), s.push(d), {
146
+ paths: s,
147
+ hyperUrl: r
148
+ };
149
+ }
150
+ async function E(e) {
151
+ if (!e.schemas.length) throw Error("publishFeed: schemas must be non-empty");
152
+ if (!e.formats.length) throw Error("publishFeed: formats must be non-empty");
153
+ let t = e.announce !== !1, n = await _({
154
+ storePath: e.storePath,
155
+ driveName: e.driveName,
156
+ announce: t
157
+ }), r = g(n.drive.key), { paths: i, hyperUrl: a } = await T(n.drive, r, e), o = n.drive.version, s = !1, c = async () => {
158
+ s || (s = !0, await v(n));
159
+ };
160
+ return t || await c(), {
161
+ key: r,
162
+ version: o,
163
+ paths: i,
164
+ hyperUrl: a,
165
+ close: c
166
+ };
167
+ }
168
+ //#endregion
169
+ //#region src/openFeed.ts
170
+ async function D(e) {
171
+ let t = await _({
172
+ storePath: e.storePath,
173
+ key: e.key,
174
+ announce: e.announce !== !1
175
+ }), n = e.syncTimeoutMs ?? 15e3;
176
+ t.drive.core.length === 0 && n > 0 && await Promise.race([t.drive.core.update({ wait: !0 }), new Promise((e) => setTimeout(e, n))]).catch(() => {});
177
+ let r = async (e) => {
178
+ let n = await t.drive.get(e);
179
+ return n ? Buffer.from(n).toString("utf-8") : null;
180
+ };
181
+ return {
182
+ key: g(t.drive.key),
183
+ version: t.drive.version,
184
+ drive: t.drive,
185
+ get: r,
186
+ getRegistry: async () => {
187
+ let e = await r(d);
188
+ if (!e) return null;
189
+ try {
190
+ return JSON.parse(e);
191
+ } catch {
192
+ return null;
193
+ }
194
+ },
195
+ close: () => v(t)
196
+ };
197
+ }
198
+ //#endregion
199
+ //#region src/seedFeed.ts
200
+ async function O(e) {
201
+ let t = await _({
202
+ storePath: e.storePath,
203
+ key: e.key,
204
+ announce: !0
205
+ });
206
+ try {
207
+ await t.drive.core.update({ wait: !1 });
208
+ } catch {}
209
+ return {
210
+ key: g(t.drive.key),
211
+ close: () => v(t)
212
+ };
213
+ }
214
+ //#endregion
215
+ //#region src/serveFeed.ts
216
+ function k(e) {
217
+ e.on("request", (e) => {
218
+ if (!e.url) return;
219
+ let t = e.url.indexOf("?"), n = t === -1 ? e.url : e.url.slice(0, t), r = t === -1 ? "" : e.url.slice(t), i = p(n);
220
+ i !== n && (e.url = i + r);
221
+ });
222
+ }
223
+ async function A(e) {
224
+ let t = e.host ?? "127.0.0.1", n = e.port ?? 8080, r = await _({
225
+ storePath: e.storePath,
226
+ key: e.key,
227
+ announce: e.announce !== !1
228
+ });
229
+ try {
230
+ await r.drive.core.update({ wait: !1 });
231
+ } catch {}
232
+ let i = g(r.drive.key), a = s.createServer();
233
+ k(a);
234
+ let o = new c({
235
+ server: a,
236
+ port: n,
237
+ host: t,
238
+ anyPort: n === 0,
239
+ token: !1,
240
+ get: async ({ key: e }) => e && !e.equals(r.drive.key) ? null : r.drive
241
+ });
242
+ await o.ready();
243
+ let l = typeof o.address == "function" ? o.address()?.port ?? n : n;
244
+ return {
245
+ key: i,
246
+ port: l,
247
+ host: t,
248
+ baseUrl: `http://${t}:${l}`,
249
+ close: async () => {
250
+ typeof o.close == "function" ? await o.close() : typeof o.suspend == "function" && await o.suspend(), await v(r);
251
+ }
252
+ };
253
+ }
254
+ function j(e, t, n) {
255
+ return `${e.replace(/\/$/, "")}${u(t, n)}`;
256
+ }
257
+ //#endregion
258
+ //#region src/watchFeed.ts
259
+ function M(e, t) {
260
+ let n = e.core, r = !1, i = () => {
261
+ r || Promise.resolve(t.onUpdate(e.version)).catch((e) => {
262
+ console.error("[feed-hyper] watchFeed onUpdate error:", e);
263
+ });
264
+ };
265
+ return n.on("append", i), () => {
266
+ r = !0, n.off("append", i);
267
+ };
268
+ }
269
+ //#endregion
270
+ export { p as _, O as a, v as c, _ as d, y as f, m as g, f as h, A as i, g as l, u as m, M as n, D as o, d as p, j as r, E as s, l as t, b as u };
271
+
272
+ //# sourceMappingURL=src-CTwmvagY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"src-CTwmvagY.js","names":["pluralize","Corestore","Hyperdrive","Hyperswarm","ID","http","ServeDrive","feedDrivePath","collection","schemaName","file","format","archive","REGISTRY_PATH","feedHttpPath","resolveHttpToDrivePath","path","pathname","trimmed","match","base","hyperFeedUrl","keyZ32","decodeKey","key","encodeKey","openFeedStore","store","options","drive","swarm","conn","closeFeedStore","handles","replicateStores","s1","a","s2","b","keyToBuffer","DEFAULT_SITE_URL","DEFAULT_FEED_TITLE","resolveSiteDefaults","feed","cfg","overrides","putUtf8","content","writeFeedsToDrive","hyperUrl","pageSize","paths","registrySchemas","schema","schemaPaths","items","getFeedItemsBySchemaName","hasNext","drivePath","baseUrl","createFeed","now","year","month","archivePath","registry","publishFeed","announce","version","closed","close","openFeed","syncTimeoutMs","resolve","get","buf","raw","seedFeed","attachFeedPathRewrite","req","q","query","resolved","serveFeed","host","port","server","serve","requestKey","boundPort","localFeedUrl","watchFeed","core","stopped","onAppend","err","DEFAULT_SEED_FEED_HYPER_KEY"],"sources":["../src/paths.ts","../src/store.ts","../src/publishFeed.ts","../src/openFeed.ts","../src/seedFeed.ts","../src/serveFeed.ts","../src/watchFeed.ts"],"sourcesContent":["import pluralize from 'pluralize'\nimport type { FeedFormat } from '@seedprotocol/feed'\n\nexport type ArchivePathOptions = {\n year: number\n month: number\n}\n\n/**\n * Map schema + format (+ optional archive) to a Hyperdrive path.\n * Layout mirrors feed HTTP routes with file extensions for Content-Type detection:\n * /posts/rss.xml\n * /posts/atom.xml\n * /posts/feed.json\n * /posts/archive/2024/2/rss.xml\n */\nexport function feedDrivePath(\n schemaName: string,\n format: FeedFormat,\n archive?: ArchivePathOptions,\n): string {\n const collection = pluralize(schemaName.toLowerCase())\n const file =\n format === 'json' ? 'feed.json' : format === 'atom' ? 'atom.xml' : 'rss.xml'\n\n if (archive) {\n return `/${collection}/archive/${archive.year}/${archive.month}/${file}`\n }\n return `/${collection}/${file}`\n}\n\n/** Registry manifest path on every published feed drive. */\nexport const REGISTRY_PATH = '/registry.json'\n\n/**\n * HTTP path segment for createFeed / channel self-links (no file extension),\n * matching historical feed.seedprotocol.io routes: /posts/rss\n */\nexport function feedHttpPath(schemaName: string, format: FeedFormat): string {\n const collection = pluralize(schemaName.toLowerCase())\n return `/${collection}/${format}`\n}\n\n/**\n * Map a gateway request pathname to the on-drive file path.\n * Accepts both historical HTTP shapes (`/posts/rss`) and drive shapes (`/posts/rss.xml`).\n * Unknown paths pass through unchanged.\n */\nexport function resolveHttpToDrivePath(pathname: string): string {\n const path = pathname.startsWith('/') ? pathname : `/${pathname}`\n const trimmed = path.length > 1 && path.endsWith('/') ? path.slice(0, -1) : path\n\n // Already a drive file path with a known feed extension\n if (/\\.(xml|json)$/i.test(trimmed)) return trimmed\n\n // /{collection}/rss|atom|json\n // /{collection}/archive/{year}/{month}/rss|atom|json\n const match = trimmed.match(\n /^(\\/[^/]+(?:\\/archive\\/\\d+\\/\\d+)?)\\/(rss|atom|json)$/i,\n )\n if (!match) return trimmed\n\n const base = match[1]!\n const format = match[2]!.toLowerCase()\n if (format === 'json') return `${base}/feed.json`\n if (format === 'atom') return `${base}/atom.xml`\n return `${base}/rss.xml`\n}\n\nexport function hyperFeedUrl(keyZ32: string): string {\n return `hyper://${keyZ32}`\n}\n","import Corestore from 'corestore'\nimport Hyperdrive from 'hyperdrive'\nimport Hyperswarm from 'hyperswarm'\nimport ID from 'hypercore-id-encoding'\n\nexport type FeedStoreHandles = {\n store: Corestore\n drive: Hyperdrive\n swarm: Hyperswarm | null\n}\n\nexport type OpenStoreOptions = {\n storePath: string\n /** Named core for a writable publisher drive (default: seed-feed). Ignored when `key` is set. */\n driveName?: string\n /** z32 or hex public key for a readonly / seeder drive */\n key?: string\n /** Join Hyperswarm and announce/lookup the drive discovery key (default: true) */\n announce?: boolean\n}\n\nfunction decodeKey(key: string): Buffer {\n return Buffer.from(ID.decode(key))\n}\n\nexport function encodeKey(key: Buffer | Uint8Array): string {\n return ID.normalize(key)\n}\n\n/**\n * Open a Corestore + Hyperdrive. With `key`, opens that drive (replicate).\n * Without `key`, opens/creates a named writable drive.\n */\nexport async function openFeedStore(options: OpenStoreOptions): Promise<FeedStoreHandles> {\n const store = new Corestore(options.storePath)\n await store.ready()\n\n const drive = options.key\n ? new Hyperdrive(store, decodeKey(options.key))\n : new Hyperdrive(store, { name: options.driveName ?? 'seed-feed' })\n\n await drive.ready()\n\n let swarm: Hyperswarm | null = null\n if (options.announce !== false) {\n swarm = new Hyperswarm()\n swarm.on('connection', (conn: unknown) => {\n store.replicate(conn as Parameters<Corestore['replicate']>[0])\n })\n swarm.join(drive.discoveryKey, { server: true, client: true })\n await swarm.flush()\n }\n\n return { store, drive, swarm }\n}\n\nexport async function closeFeedStore(handles: FeedStoreHandles): Promise<void> {\n if (handles.swarm) {\n await handles.swarm.destroy()\n }\n await handles.drive.close()\n await handles.store.close()\n}\n\n/** In-process duplex replicate between two corestores (for tests / tooling). */\nexport function replicateStores(a: Corestore, b: Corestore): () => void {\n const s1 = a.replicate(true) as NodeJS.ReadWriteStream & { destroy?: () => void }\n const s2 = b.replicate(false) as NodeJS.ReadWriteStream & { destroy?: () => void }\n s1.pipe(s2).pipe(s1)\n return () => {\n s1.destroy?.()\n s2.destroy?.()\n }\n}\n\nexport function keyToBuffer(key: string | Buffer | Uint8Array): Buffer {\n if (typeof key === 'string') return decodeKey(key)\n return Buffer.from(key)\n}\n\nexport { ID }\n","import {\n createFeed,\n getFeedItemsBySchemaName,\n type GraphQLItem,\n} from '@seedprotocol/feed'\nimport {\n REGISTRY_PATH,\n feedDrivePath,\n feedHttpPath,\n hyperFeedUrl,\n} from './paths'\nimport { closeFeedStore, encodeKey, openFeedStore, type FeedStoreHandles } from './store'\nimport type { FeedRegistry, PublishFeedOptions, PublishFeedResult } from './types'\n\nconst DEFAULT_SITE_URL = 'https://seedprotocol.io'\nconst DEFAULT_FEED_TITLE = 'Seed Protocol'\n\nasync function resolveSiteDefaults(overrides?: {\n siteUrl?: string\n title?: string\n}): Promise<{ siteUrl: string; title: string }> {\n try {\n const feed = await import('@seedprotocol/feed')\n const cfg =\n typeof feed.getSiteConfig === 'function' ? feed.getSiteConfig() : null\n return {\n siteUrl: overrides?.siteUrl ?? cfg?.siteUrl ?? DEFAULT_SITE_URL,\n title: overrides?.title ?? cfg?.title ?? DEFAULT_FEED_TITLE,\n }\n } catch {\n return {\n siteUrl: overrides?.siteUrl ?? DEFAULT_SITE_URL,\n title: overrides?.title ?? DEFAULT_FEED_TITLE,\n }\n }\n}\n\nasync function putUtf8(\n drive: { put: (path: string, buf: Buffer) => Promise<unknown> },\n path: string,\n content: string,\n): Promise<void> {\n await drive.put(path, Buffer.from(content, 'utf-8'))\n}\n\nasync function writeFeedsToDrive(\n drive: FeedStoreHandles['drive'],\n key: string,\n options: PublishFeedOptions,\n): Promise<{ paths: string[]; hyperUrl: string }> {\n const hyperUrl = hyperFeedUrl(key)\n const pageSize = options.pageSize ?? 25\n const paths: string[] = []\n const registrySchemas: FeedRegistry['schemas'] = []\n\n if (options.fixtureContents) {\n for (const [path, content] of Object.entries(options.fixtureContents)) {\n await putUtf8(drive, path, content)\n paths.push(path)\n }\n registrySchemas.push({\n schema: options.schemas[0]!,\n formats: options.formats,\n paths: [...paths],\n })\n } else {\n const { siteUrl, title } = await resolveSiteDefaults({\n siteUrl: options.siteUrl,\n })\n for (const schema of options.schemas) {\n const schemaPaths: string[] = []\n const items = (await getFeedItemsBySchemaName(schema, {\n limit: pageSize,\n skip: 0,\n })) as GraphQLItem[]\n const hasNext = items.length === pageSize\n\n for (const format of options.formats) {\n const drivePath = feedDrivePath(schema, format)\n const baseUrl = `${hyperUrl}${feedHttpPath(schema, format)}`\n const content = await createFeed(\n items,\n schema,\n format,\n undefined,\n {\n page: 1,\n pageSize,\n hasNext,\n baseUrl,\n },\n undefined,\n false,\n undefined,\n {\n feedUrl: hyperUrl,\n siteUrl,\n title,\n },\n )\n await putUtf8(drive, drivePath, content)\n paths.push(drivePath)\n schemaPaths.push(drivePath)\n }\n\n if (options.includeArchives) {\n const now = new Date()\n const year = now.getFullYear()\n const month = now.getMonth() + 1\n for (const format of options.formats) {\n const archivePath = feedDrivePath(schema, format, { year, month })\n const archiveContent = await createFeed(\n items,\n schema,\n format,\n undefined,\n undefined,\n [\n {\n rel: 'current',\n href: `${hyperUrl}${feedHttpPath(schema, format)}`,\n },\n ],\n true,\n undefined,\n { feedUrl: hyperUrl, siteUrl },\n )\n await putUtf8(drive, archivePath, archiveContent)\n paths.push(archivePath)\n schemaPaths.push(archivePath)\n }\n }\n\n registrySchemas.push({\n schema,\n formats: [...options.formats],\n paths: schemaPaths,\n })\n }\n }\n\n const registry: FeedRegistry = {\n key,\n version: drive.version,\n updatedAt: new Date().toISOString(),\n schemas: registrySchemas,\n }\n await putUtf8(drive, REGISTRY_PATH, JSON.stringify(registry, null, 2))\n paths.push(REGISTRY_PATH)\n\n return { paths, hyperUrl }\n}\n\nexport type PublishFeedSession = PublishFeedResult & {\n /** Close Corestore / Hyperdrive / Hyperswarm. No-op if already closed. */\n close: () => Promise<void>\n}\n\n/**\n * Generate feeds via `@seedprotocol/feed` and write them into a Hyperdrive.\n *\n * - `announce: false` — write, close store, return (one-shot).\n * - `announce: true` (default) — write, keep swarm joined until `close()`.\n */\nexport async function publishFeed(\n options: PublishFeedOptions,\n): Promise<PublishFeedSession> {\n if (!options.schemas.length) {\n throw new Error('publishFeed: schemas must be non-empty')\n }\n if (!options.formats.length) {\n throw new Error('publishFeed: formats must be non-empty')\n }\n\n const announce = options.announce !== false\n const handles = await openFeedStore({\n storePath: options.storePath,\n driveName: options.driveName,\n announce,\n })\n\n const key = encodeKey(handles.drive.key)\n const { paths, hyperUrl } = await writeFeedsToDrive(handles.drive, key, options)\n const version = handles.drive.version\n\n let closed = false\n const close = async () => {\n if (closed) return\n closed = true\n await closeFeedStore(handles)\n }\n\n if (!announce) {\n await close()\n }\n\n return {\n key,\n version,\n paths,\n hyperUrl,\n close,\n }\n}\n","import { REGISTRY_PATH } from './paths'\nimport { closeFeedStore, encodeKey, openFeedStore, type FeedStoreHandles } from './store'\nimport type { FeedRegistry, OpenFeedOptions } from './types'\n\nexport type OpenedFeed = {\n key: string\n version: number\n drive: FeedStoreHandles['drive']\n get: (path: string) => Promise<string | null>\n getRegistry: () => Promise<FeedRegistry | null>\n close: () => Promise<void>\n}\n\n/**\n * Open a feed Hyperdrive by public key, join the swarm, and optionally wait for data.\n */\nexport async function openFeed(options: OpenFeedOptions): Promise<OpenedFeed> {\n const handles = await openFeedStore({\n storePath: options.storePath,\n key: options.key,\n announce: options.announce !== false,\n })\n\n const syncTimeoutMs = options.syncTimeoutMs ?? 15_000\n if (handles.drive.core.length === 0 && syncTimeoutMs > 0) {\n await Promise.race([\n handles.drive.core.update({ wait: true }),\n new Promise<void>((resolve) => setTimeout(resolve, syncTimeoutMs)),\n ]).catch(() => {\n /* timeout or update failure — caller may still read if peers arrive later */\n })\n }\n\n const get = async (path: string): Promise<string | null> => {\n const buf = await handles.drive.get(path)\n if (!buf) return null\n return Buffer.from(buf).toString('utf-8')\n }\n\n const getRegistry = async (): Promise<FeedRegistry | null> => {\n const raw = await get(REGISTRY_PATH)\n if (!raw) return null\n try {\n return JSON.parse(raw) as FeedRegistry\n } catch {\n return null\n }\n }\n\n return {\n key: encodeKey(handles.drive.key),\n version: handles.drive.version,\n drive: handles.drive,\n get,\n getRegistry,\n close: () => closeFeedStore(handles),\n }\n}\n","import { closeFeedStore, encodeKey, openFeedStore } from './store'\nimport type { SeedFeedOptions } from './types'\n\nexport type SeedFeedSession = {\n key: string\n close: () => Promise<void>\n}\n\n/**\n * Join the swarm and replicate a feed drive until `close()` (or process exit).\n * Does not generate feed content.\n */\nexport async function seedFeed(options: SeedFeedOptions): Promise<SeedFeedSession> {\n const handles = await openFeedStore({\n storePath: options.storePath,\n key: options.key,\n announce: true,\n })\n\n // Prefer longer cores from peers\n try {\n await handles.drive.core.update({ wait: false })\n } catch {\n /* ignore */\n }\n\n return {\n key: encodeKey(handles.drive.key),\n close: () => closeFeedStore(handles),\n }\n}\n","import http from 'node:http'\nimport ServeDrive from 'serve-drive'\nimport { feedDrivePath, resolveHttpToDrivePath } from './paths'\nimport { closeFeedStore, encodeKey, openFeedStore } from './store'\nimport type { ServeFeedOptions, ServeFeedResult } from './types'\n\n/**\n * Rewrite extensionless feed URLs (`/posts/rss`) to drive paths (`/posts/rss.xml`)\n * so serve-drive looks up the real file and gets the correct Content-Type.\n */\nfunction attachFeedPathRewrite(server: http.Server): void {\n server.on('request', (req) => {\n if (!req.url) return\n const q = req.url.indexOf('?')\n const pathname = q === -1 ? req.url : req.url.slice(0, q)\n const query = q === -1 ? '' : req.url.slice(q)\n const resolved = resolveHttpToDrivePath(pathname)\n if (resolved !== pathname) {\n req.url = resolved + query\n }\n })\n}\n\n/**\n * Seed a feed drive and expose it over localhost HTTP via serve-drive.\n * Serves both drive paths (`/posts/rss.xml`) and historical HTTP paths (`/posts/rss`).\n */\nexport async function serveFeed(options: ServeFeedOptions): Promise<ServeFeedResult> {\n const host = options.host ?? '127.0.0.1'\n const port = options.port ?? 8080\n\n const handles = await openFeedStore({\n storePath: options.storePath,\n key: options.key,\n announce: options.announce !== false,\n })\n\n try {\n await handles.drive.core.update({ wait: false })\n } catch {\n /* ignore */\n }\n\n const key = encodeKey(handles.drive.key)\n\n const server = http.createServer()\n // Must register before ServeDrive attaches its handler so rewrite runs first.\n attachFeedPathRewrite(server)\n\n const serve = new ServeDrive({\n server,\n port,\n host,\n anyPort: port === 0,\n // Local RSS gateways need unauthenticated GETs; enable token for public hosts via env later\n token: false,\n get: async ({ key: requestKey }: { key: Buffer | null; filename: string; version: number }) => {\n if (requestKey && !requestKey.equals(handles.drive.key)) {\n return null\n }\n return handles.drive\n },\n })\n\n await serve.ready()\n\n const boundPort =\n typeof serve.address === 'function'\n ? (serve.address()?.port ?? port)\n : port\n\n const baseUrl = `http://${host}:${boundPort}`\n\n return {\n key,\n port: boundPort,\n host,\n baseUrl,\n close: async () => {\n if (typeof serve.close === 'function') {\n await serve.close()\n } else if (typeof (serve as { suspend?: () => Promise<void> }).suspend === 'function') {\n await (serve as { suspend: () => Promise<void> }).suspend()\n }\n await closeFeedStore(handles)\n },\n }\n}\n\n/** Example local URL for a schema/format after serveFeed (drive path with extension). */\nexport function localFeedUrl(\n baseUrl: string,\n schemaName: string,\n format: 'rss' | 'atom' | 'json',\n): string {\n return `${baseUrl.replace(/\\/$/, '')}${feedDrivePath(schemaName, format)}`\n}\n","import type Hyperdrive from 'hyperdrive'\n\nexport type WatchFeedOptions = {\n /** Called whenever the drive version increases. */\n onUpdate: (version: number) => void | Promise<void>\n}\n\n/**\n * Watch a Hyperdrive for new versions (append-only length growth).\n * Returns an unsubscribe function.\n */\nexport function watchFeed(\n drive: Hyperdrive,\n options: WatchFeedOptions,\n): () => void {\n const core = drive.core\n let stopped = false\n\n const onAppend = () => {\n if (stopped) return\n void Promise.resolve(options.onUpdate(drive.version)).catch((err) => {\n console.error('[feed-hyper] watchFeed onUpdate error:', err)\n })\n }\n\n core.on('append', onAppend)\n\n return () => {\n stopped = true\n core.off('append', onAppend)\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,9 +1,12 @@
1
1
  {
2
2
  "name": "@seedprotocol/feed-hyper",
3
- "version": "0.6.10",
3
+ "version": "0.7.1",
4
4
  "description": "Hyperdrive / Hyperswarm distribution for Seed Protocol feeds",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
+ "bin": {
8
+ "seed-feed": "./dist/bin.js"
9
+ },
7
10
  "exports": {
8
11
  ".": {
9
12
  "import": {
@@ -29,8 +32,8 @@
29
32
  "prepublishOnly": "bun run build && node ../../scripts/sync-versions.js"
30
33
  },
31
34
  "dependencies": {
32
- "@seedprotocol/arweave": "0.6.10",
33
- "@seedprotocol/feed": "0.6.10",
35
+ "@seedprotocol/arweave": "0.7.1",
36
+ "@seedprotocol/feed": "0.7.1",
34
37
  "corestore": "^7.12.0",
35
38
  "hypercore-id-encoding": "^1.3.0",
36
39
  "hyperdrive": "^13.0.0",
@@ -44,7 +47,7 @@
44
47
  "rimraf": "^6.0.1",
45
48
  "typescript": "~5.9.3",
46
49
  "vite": "^8.0.0",
47
- "vitest": "^3.0.0"
50
+ "vitest": "^4.1.11"
48
51
  },
49
52
  "author": "Keith Axline <keith@journodao.xyz>",
50
53
  "license": "MIT",
package/dist/index.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/paths.ts","../src/store.ts","../src/publishFeed.ts","../src/openFeed.ts","../src/seedFeed.ts","../src/serveFeed.ts","../src/watchFeed.ts"],"sourcesContent":["import pluralize from 'pluralize'\nimport type { FeedFormat } from '@seedprotocol/feed'\n\nexport type ArchivePathOptions = {\n year: number\n month: number\n}\n\n/**\n * Map schema + format (+ optional archive) to a Hyperdrive path.\n * Layout mirrors feed HTTP routes with file extensions for Content-Type detection:\n * /posts/rss.xml\n * /posts/atom.xml\n * /posts/feed.json\n * /posts/archive/2024/2/rss.xml\n */\nexport function feedDrivePath(\n schemaName: string,\n format: FeedFormat,\n archive?: ArchivePathOptions,\n): string {\n const collection = pluralize(schemaName.toLowerCase())\n const file =\n format === 'json' ? 'feed.json' : format === 'atom' ? 'atom.xml' : 'rss.xml'\n\n if (archive) {\n return `/${collection}/archive/${archive.year}/${archive.month}/${file}`\n }\n return `/${collection}/${file}`\n}\n\n/** Registry manifest path on every published feed drive. */\nexport const REGISTRY_PATH = '/registry.json'\n\n/**\n * HTTP path segment for createFeed / channel self-links (no file extension),\n * matching historical feed.seedprotocol.io routes: /posts/rss\n */\nexport function feedHttpPath(schemaName: string, format: FeedFormat): string {\n const collection = pluralize(schemaName.toLowerCase())\n return `/${collection}/${format}`\n}\n\n/**\n * Map a gateway request pathname to the on-drive file path.\n * Accepts both historical HTTP shapes (`/posts/rss`) and drive shapes (`/posts/rss.xml`).\n * Unknown paths pass through unchanged.\n */\nexport function resolveHttpToDrivePath(pathname: string): string {\n const path = pathname.startsWith('/') ? pathname : `/${pathname}`\n const trimmed = path.length > 1 && path.endsWith('/') ? path.slice(0, -1) : path\n\n // Already a drive file path with a known feed extension\n if (/\\.(xml|json)$/i.test(trimmed)) return trimmed\n\n // /{collection}/rss|atom|json\n // /{collection}/archive/{year}/{month}/rss|atom|json\n const match = trimmed.match(\n /^(\\/[^/]+(?:\\/archive\\/\\d+\\/\\d+)?)\\/(rss|atom|json)$/i,\n )\n if (!match) return trimmed\n\n const base = match[1]!\n const format = match[2]!.toLowerCase()\n if (format === 'json') return `${base}/feed.json`\n if (format === 'atom') return `${base}/atom.xml`\n return `${base}/rss.xml`\n}\n\nexport function hyperFeedUrl(keyZ32: string): string {\n return `hyper://${keyZ32}`\n}\n","import Corestore from 'corestore'\nimport Hyperdrive from 'hyperdrive'\nimport Hyperswarm from 'hyperswarm'\nimport ID from 'hypercore-id-encoding'\n\nexport type FeedStoreHandles = {\n store: Corestore\n drive: Hyperdrive\n swarm: Hyperswarm | null\n}\n\nexport type OpenStoreOptions = {\n storePath: string\n /** Named core for a writable publisher drive (default: seed-feed). Ignored when `key` is set. */\n driveName?: string\n /** z32 or hex public key for a readonly / seeder drive */\n key?: string\n /** Join Hyperswarm and announce/lookup the drive discovery key (default: true) */\n announce?: boolean\n}\n\nfunction decodeKey(key: string): Buffer {\n return Buffer.from(ID.decode(key))\n}\n\nexport function encodeKey(key: Buffer | Uint8Array): string {\n return ID.normalize(key)\n}\n\n/**\n * Open a Corestore + Hyperdrive. With `key`, opens that drive (replicate).\n * Without `key`, opens/creates a named writable drive.\n */\nexport async function openFeedStore(options: OpenStoreOptions): Promise<FeedStoreHandles> {\n const store = new Corestore(options.storePath)\n await store.ready()\n\n const drive = options.key\n ? new Hyperdrive(store, decodeKey(options.key))\n : new Hyperdrive(store, { name: options.driveName ?? 'seed-feed' })\n\n await drive.ready()\n\n let swarm: Hyperswarm | null = null\n if (options.announce !== false) {\n swarm = new Hyperswarm()\n swarm.on('connection', (conn: unknown) => {\n store.replicate(conn as Parameters<Corestore['replicate']>[0])\n })\n swarm.join(drive.discoveryKey, { server: true, client: true })\n await swarm.flush()\n }\n\n return { store, drive, swarm }\n}\n\nexport async function closeFeedStore(handles: FeedStoreHandles): Promise<void> {\n if (handles.swarm) {\n await handles.swarm.destroy()\n }\n await handles.drive.close()\n await handles.store.close()\n}\n\n/** In-process duplex replicate between two corestores (for tests / tooling). */\nexport function replicateStores(a: Corestore, b: Corestore): () => void {\n const s1 = a.replicate(true) as NodeJS.ReadWriteStream & { destroy?: () => void }\n const s2 = b.replicate(false) as NodeJS.ReadWriteStream & { destroy?: () => void }\n s1.pipe(s2).pipe(s1)\n return () => {\n s1.destroy?.()\n s2.destroy?.()\n }\n}\n\nexport function keyToBuffer(key: string | Buffer | Uint8Array): Buffer {\n if (typeof key === 'string') return decodeKey(key)\n return Buffer.from(key)\n}\n\nexport { ID }\n","import {\n createFeed,\n getFeedItemsBySchemaName,\n type GraphQLItem,\n} from '@seedprotocol/feed'\nimport {\n REGISTRY_PATH,\n feedDrivePath,\n feedHttpPath,\n hyperFeedUrl,\n} from './paths'\nimport { closeFeedStore, encodeKey, openFeedStore, type FeedStoreHandles } from './store'\nimport type { FeedRegistry, PublishFeedOptions, PublishFeedResult } from './types'\n\nconst DEFAULT_SITE_URL = 'https://seedprotocol.io'\nconst DEFAULT_FEED_TITLE = 'Seed Protocol'\n\nasync function resolveSiteDefaults(overrides?: {\n siteUrl?: string\n title?: string\n}): Promise<{ siteUrl: string; title: string }> {\n try {\n const feed = await import('@seedprotocol/feed')\n const cfg =\n typeof feed.getSiteConfig === 'function' ? feed.getSiteConfig() : null\n return {\n siteUrl: overrides?.siteUrl ?? cfg?.siteUrl ?? DEFAULT_SITE_URL,\n title: overrides?.title ?? cfg?.title ?? DEFAULT_FEED_TITLE,\n }\n } catch {\n return {\n siteUrl: overrides?.siteUrl ?? DEFAULT_SITE_URL,\n title: overrides?.title ?? DEFAULT_FEED_TITLE,\n }\n }\n}\n\nasync function putUtf8(\n drive: { put: (path: string, buf: Buffer) => Promise<unknown> },\n path: string,\n content: string,\n): Promise<void> {\n await drive.put(path, Buffer.from(content, 'utf-8'))\n}\n\nasync function writeFeedsToDrive(\n drive: FeedStoreHandles['drive'],\n key: string,\n options: PublishFeedOptions,\n): Promise<{ paths: string[]; hyperUrl: string }> {\n const hyperUrl = hyperFeedUrl(key)\n const pageSize = options.pageSize ?? 25\n const paths: string[] = []\n const registrySchemas: FeedRegistry['schemas'] = []\n\n if (options.fixtureContents) {\n for (const [path, content] of Object.entries(options.fixtureContents)) {\n await putUtf8(drive, path, content)\n paths.push(path)\n }\n registrySchemas.push({\n schema: options.schemas[0]!,\n formats: options.formats,\n paths: [...paths],\n })\n } else {\n const { siteUrl, title } = await resolveSiteDefaults({\n siteUrl: options.siteUrl,\n })\n for (const schema of options.schemas) {\n const schemaPaths: string[] = []\n const items = (await getFeedItemsBySchemaName(schema, {\n limit: pageSize,\n skip: 0,\n })) as GraphQLItem[]\n const hasNext = items.length === pageSize\n\n for (const format of options.formats) {\n const drivePath = feedDrivePath(schema, format)\n const baseUrl = `${hyperUrl}${feedHttpPath(schema, format)}`\n const content = await createFeed(\n items,\n schema,\n format,\n undefined,\n {\n page: 1,\n pageSize,\n hasNext,\n baseUrl,\n },\n undefined,\n false,\n undefined,\n {\n feedUrl: hyperUrl,\n siteUrl,\n title,\n },\n )\n await putUtf8(drive, drivePath, content)\n paths.push(drivePath)\n schemaPaths.push(drivePath)\n }\n\n if (options.includeArchives) {\n const now = new Date()\n const year = now.getFullYear()\n const month = now.getMonth() + 1\n for (const format of options.formats) {\n const archivePath = feedDrivePath(schema, format, { year, month })\n const archiveContent = await createFeed(\n items,\n schema,\n format,\n undefined,\n undefined,\n [\n {\n rel: 'current',\n href: `${hyperUrl}${feedHttpPath(schema, format)}`,\n },\n ],\n true,\n undefined,\n { feedUrl: hyperUrl, siteUrl },\n )\n await putUtf8(drive, archivePath, archiveContent)\n paths.push(archivePath)\n schemaPaths.push(archivePath)\n }\n }\n\n registrySchemas.push({\n schema,\n formats: [...options.formats],\n paths: schemaPaths,\n })\n }\n }\n\n const registry: FeedRegistry = {\n key,\n version: drive.version,\n updatedAt: new Date().toISOString(),\n schemas: registrySchemas,\n }\n await putUtf8(drive, REGISTRY_PATH, JSON.stringify(registry, null, 2))\n paths.push(REGISTRY_PATH)\n\n return { paths, hyperUrl }\n}\n\nexport type PublishFeedSession = PublishFeedResult & {\n /** Close Corestore / Hyperdrive / Hyperswarm. No-op if already closed. */\n close: () => Promise<void>\n}\n\n/**\n * Generate feeds via `@seedprotocol/feed` and write them into a Hyperdrive.\n *\n * - `announce: false` — write, close store, return (one-shot).\n * - `announce: true` (default) — write, keep swarm joined until `close()`.\n */\nexport async function publishFeed(\n options: PublishFeedOptions,\n): Promise<PublishFeedSession> {\n if (!options.schemas.length) {\n throw new Error('publishFeed: schemas must be non-empty')\n }\n if (!options.formats.length) {\n throw new Error('publishFeed: formats must be non-empty')\n }\n\n const announce = options.announce !== false\n const handles = await openFeedStore({\n storePath: options.storePath,\n driveName: options.driveName,\n announce,\n })\n\n const key = encodeKey(handles.drive.key)\n const { paths, hyperUrl } = await writeFeedsToDrive(handles.drive, key, options)\n const version = handles.drive.version\n\n let closed = false\n const close = async () => {\n if (closed) return\n closed = true\n await closeFeedStore(handles)\n }\n\n if (!announce) {\n await close()\n }\n\n return {\n key,\n version,\n paths,\n hyperUrl,\n close,\n }\n}\n","import { REGISTRY_PATH } from './paths'\nimport { closeFeedStore, encodeKey, openFeedStore, type FeedStoreHandles } from './store'\nimport type { FeedRegistry, OpenFeedOptions } from './types'\n\nexport type OpenedFeed = {\n key: string\n version: number\n drive: FeedStoreHandles['drive']\n get: (path: string) => Promise<string | null>\n getRegistry: () => Promise<FeedRegistry | null>\n close: () => Promise<void>\n}\n\n/**\n * Open a feed Hyperdrive by public key, join the swarm, and optionally wait for data.\n */\nexport async function openFeed(options: OpenFeedOptions): Promise<OpenedFeed> {\n const handles = await openFeedStore({\n storePath: options.storePath,\n key: options.key,\n announce: options.announce !== false,\n })\n\n const syncTimeoutMs = options.syncTimeoutMs ?? 15_000\n if (handles.drive.core.length === 0 && syncTimeoutMs > 0) {\n await Promise.race([\n handles.drive.core.update({ wait: true }),\n new Promise<void>((resolve) => setTimeout(resolve, syncTimeoutMs)),\n ]).catch(() => {\n /* timeout or update failure — caller may still read if peers arrive later */\n })\n }\n\n const get = async (path: string): Promise<string | null> => {\n const buf = await handles.drive.get(path)\n if (!buf) return null\n return Buffer.from(buf).toString('utf-8')\n }\n\n const getRegistry = async (): Promise<FeedRegistry | null> => {\n const raw = await get(REGISTRY_PATH)\n if (!raw) return null\n try {\n return JSON.parse(raw) as FeedRegistry\n } catch {\n return null\n }\n }\n\n return {\n key: encodeKey(handles.drive.key),\n version: handles.drive.version,\n drive: handles.drive,\n get,\n getRegistry,\n close: () => closeFeedStore(handles),\n }\n}\n","import { closeFeedStore, encodeKey, openFeedStore } from './store'\nimport type { SeedFeedOptions } from './types'\n\nexport type SeedFeedSession = {\n key: string\n close: () => Promise<void>\n}\n\n/**\n * Join the swarm and replicate a feed drive until `close()` (or process exit).\n * Does not generate feed content.\n */\nexport async function seedFeed(options: SeedFeedOptions): Promise<SeedFeedSession> {\n const handles = await openFeedStore({\n storePath: options.storePath,\n key: options.key,\n announce: true,\n })\n\n // Prefer longer cores from peers\n try {\n await handles.drive.core.update({ wait: false })\n } catch {\n /* ignore */\n }\n\n return {\n key: encodeKey(handles.drive.key),\n close: () => closeFeedStore(handles),\n }\n}\n","import http from 'node:http'\nimport ServeDrive from 'serve-drive'\nimport { feedDrivePath, resolveHttpToDrivePath } from './paths'\nimport { closeFeedStore, encodeKey, openFeedStore } from './store'\nimport type { ServeFeedOptions, ServeFeedResult } from './types'\n\n/**\n * Rewrite extensionless feed URLs (`/posts/rss`) to drive paths (`/posts/rss.xml`)\n * so serve-drive looks up the real file and gets the correct Content-Type.\n */\nfunction attachFeedPathRewrite(server: http.Server): void {\n server.on('request', (req) => {\n if (!req.url) return\n const q = req.url.indexOf('?')\n const pathname = q === -1 ? req.url : req.url.slice(0, q)\n const query = q === -1 ? '' : req.url.slice(q)\n const resolved = resolveHttpToDrivePath(pathname)\n if (resolved !== pathname) {\n req.url = resolved + query\n }\n })\n}\n\n/**\n * Seed a feed drive and expose it over localhost HTTP via serve-drive.\n * Serves both drive paths (`/posts/rss.xml`) and historical HTTP paths (`/posts/rss`).\n */\nexport async function serveFeed(options: ServeFeedOptions): Promise<ServeFeedResult> {\n const host = options.host ?? '127.0.0.1'\n const port = options.port ?? 8080\n\n const handles = await openFeedStore({\n storePath: options.storePath,\n key: options.key,\n announce: options.announce !== false,\n })\n\n try {\n await handles.drive.core.update({ wait: false })\n } catch {\n /* ignore */\n }\n\n const key = encodeKey(handles.drive.key)\n\n const server = http.createServer()\n // Must register before ServeDrive attaches its handler so rewrite runs first.\n attachFeedPathRewrite(server)\n\n const serve = new ServeDrive({\n server,\n port,\n host,\n anyPort: port === 0,\n // Local RSS gateways need unauthenticated GETs; enable token for public hosts via env later\n token: false,\n get: async ({ key: requestKey }: { key: Buffer | null; filename: string; version: number }) => {\n if (requestKey && !requestKey.equals(handles.drive.key)) {\n return null\n }\n return handles.drive\n },\n })\n\n await serve.ready()\n\n const boundPort =\n typeof serve.address === 'function'\n ? (serve.address()?.port ?? port)\n : port\n\n const baseUrl = `http://${host}:${boundPort}`\n\n return {\n key,\n port: boundPort,\n host,\n baseUrl,\n close: async () => {\n if (typeof serve.close === 'function') {\n await serve.close()\n } else if (typeof (serve as { suspend?: () => Promise<void> }).suspend === 'function') {\n await (serve as { suspend: () => Promise<void> }).suspend()\n }\n await closeFeedStore(handles)\n },\n }\n}\n\n/** Example local URL for a schema/format after serveFeed (drive path with extension). */\nexport function localFeedUrl(\n baseUrl: string,\n schemaName: string,\n format: 'rss' | 'atom' | 'json',\n): string {\n return `${baseUrl.replace(/\\/$/, '')}${feedDrivePath(schemaName, format)}`\n}\n","import type Hyperdrive from 'hyperdrive'\n\nexport type WatchFeedOptions = {\n /** Called whenever the drive version increases. */\n onUpdate: (version: number) => void | Promise<void>\n}\n\n/**\n * Watch a Hyperdrive for new versions (append-only length growth).\n * Returns an unsubscribe function.\n */\nexport function watchFeed(\n drive: Hyperdrive,\n options: WatchFeedOptions,\n): () => void {\n const core = drive.core\n let stopped = false\n\n const onAppend = () => {\n if (stopped) return\n void Promise.resolve(options.onUpdate(drive.version)).catch((err) => {\n console.error('[feed-hyper] watchFeed onUpdate error:', err)\n })\n }\n\n core.on('append', onAppend)\n\n return () => {\n stopped = true\n core.off('append', onAppend)\n }\n}\n"],"mappings":"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"}