notefeed 0.5.0 → 0.7.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/README.md CHANGED
@@ -8,6 +8,8 @@ export NOTEFEED_URL=https://notes.example.com NOTEFEED_FEED=homelab-7f3k2q9x4m8w
8
8
  npx notefeed post "# Backup finished"
9
9
  backup.sh 2>&1 | npx notefeed post -
10
10
  npx notefeed notes --limit 5
11
+ npx notefeed edit 20260929T140512Z-backup-finished "# Backup finished, verified"
12
+ npx notefeed delete 20260929T140512Z-backup-finished
11
13
  ```
12
14
 
13
15
  ```js
@@ -19,6 +21,16 @@ console.log(created.url, created.read_url);
19
21
  for await (const note of client.notes()) console.log(note.created_at, note.title); // newest first, page by page
20
22
  ```
21
23
 
24
+ `read_url` is `null` for a feed without a read link (rare; see the operations docs).
25
+
22
26
  `Client.fromEnv()` reads `NOTEFEED_URL` / `NOTEFEED_FEED` / `NOTEFEED_PASSWORD`; so does the `notefeed` command, unless given `--url` / `--feed` / `--password`.
23
27
 
28
+ A feed can have its own password, which gates reading, listing and posting to it. Pass `feedPassword: "..."` to `new Client(...)` (or per call, e.g. `client.post(text, { feedPassword })`), or set `NOTEFEED_FEED_PASSWORD`, which `fromEnv()` and the `notefeed` command both read (there is deliberately no flag, so it stays out of shell history). It is sent as `X-Feed-Password`; `Authorization` stays the instance password. A feed password is 1 to 256 printable ASCII characters with no space at the start or end; an empty one is the same as none.
29
+
30
+ A posted note can be changed or removed: `await client.edit(id, markdown)` replaces its text and returns the note (its id and URLs stay), and `await client.delete(id)` removes it. Both take the same `{ feed, feedPassword }` options as `post`, and a missing note is a `NotFoundError`. `notefeed edit <id> <text | - | --file PATH>` prints the note URL; `notefeed delete <id>` prints nothing. Anyone who can post to a feed can edit and delete its notes, and deleting is permanent.
31
+
32
+ A feed can have a title and a description, and can be deleted: `await client.feedInfo()` returns `{ name, title, description, protected, read_url }`, `await client.updateFeed({ title, description })` replaces both (an empty string clears one) and returns the feed, and `await client.deleteFeed()` removes the feed with all its notes for good. All three take the same `{ feed, feedPassword }` options as `post`, and a feed that doesn't exist is a `NotFoundError`. Anyone who can post to a feed can do this, and deleting is permanent; there are no `notefeed` commands for it.
33
+
34
+ An image can be uploaded to an existing feed and then used in a note: `const { file, url, markdown } = await client.uploadImage(bytes)` takes a `Uint8Array` or a `Blob` (PNG, JPEG, GIF or WebP; the server recognizes the format by the bytes), takes the same `{ feed, feedPassword }` options as `post`, and returns the image's `url` and `markdown` (`![](url)`), which you put in a note. The feed must already have a note, and an image that is too large is a `NoteTooLargeError`. `await client.updateFeed({ title, description, image: file })` makes an uploaded image the feed's title image (`image: ""` removes it; leaving `image` out keeps it). `notefeed image <PATH>` uploads a file and prints the markdown. Images are public to anyone with the feed's read link, and metadata such as EXIF is not removed.
35
+
24
36
  Full documentation: https://docs.notefeed.me/clients/
package/dist/cli.js CHANGED
@@ -1,4 +1,4 @@
1
- /** `notefeed post ...` and `notefeed notes ...`: post and read notes from the command line. */
1
+ /** `notefeed post`, `edit`, `delete`, `notes` and `image`: post, change, remove and read notes, and upload images, from the command line. */
2
2
  import { readFileSync } from "node:fs";
3
3
  import { readFile } from "node:fs/promises";
4
4
  import { parseArgs } from "node:util";
@@ -6,8 +6,11 @@ import { Client, ConfigError, NotefeedError } from "./client.js";
6
6
  class UsageError extends Error {
7
7
  }
8
8
  const USAGE = [
9
- "usage: notefeed post <text | - | --file PATH> [--url URL] [--feed FEED] [--password PASSWORD]",
10
- " notefeed notes [--limit N] [--json] [--url URL] [--feed FEED] [--password PASSWORD]",
9
+ "usage: notefeed post <text | - | --file PATH> [--tag TAG]... [--url URL] [--feed FEED] [--password PASSWORD]",
10
+ " notefeed edit <id> <text | - | --file PATH> [--url URL] [--feed FEED] [--password PASSWORD]",
11
+ " notefeed delete <id> [--url URL] [--feed FEED] [--password PASSWORD]",
12
+ " notefeed image <PATH> [--url URL] [--feed FEED] [--password PASSWORD]",
13
+ " notefeed notes [--limit N] [--tag TAG] [--json] [--url URL] [--feed FEED] [--password PASSWORD]",
11
14
  ].join("\n");
12
15
  /** Returns the exit code: 0 ok, 1 server/network error, 2 usage/config error. */
13
16
  export async function main(argv, io = process) {
@@ -20,6 +23,7 @@ export async function main(argv, io = process) {
20
23
  feed: { type: "string" },
21
24
  password: { type: "string" },
22
25
  file: { type: "string" },
26
+ tag: { type: "string", multiple: true },
23
27
  limit: { type: "string" },
24
28
  json: { type: "boolean" },
25
29
  version: { type: "boolean" },
@@ -38,16 +42,36 @@ export async function main(argv, io = process) {
38
42
  const [command, text, ...rest] = positionals;
39
43
  if (command === "post" && !rest.length) {
40
44
  const markdown = await read(text, values.file, io);
41
- const note = await client(values).post(markdown);
45
+ const note = await client(values).post(markdown, { tags: values.tag });
42
46
  io.stdout.write(`${note.url}\n`);
43
47
  return 0;
44
48
  }
49
+ if (command === "edit" && text !== undefined && rest.length <= 1) {
50
+ const note = await client(values).edit(text, await read(rest[0], values.file, io));
51
+ io.stdout.write(`${note.url}\n`);
52
+ return 0;
53
+ }
54
+ if (command === "delete" && text !== undefined && !rest.length) {
55
+ await client(values).delete(text);
56
+ return 0;
57
+ }
58
+ if (command === "image" && text !== undefined && !rest.length) {
59
+ let bytes;
60
+ try {
61
+ bytes = await readFile(text);
62
+ }
63
+ catch (e) {
64
+ throw new UsageError(`cannot read ${text}: ${e.code ?? e.message}`);
65
+ }
66
+ io.stdout.write(`${(await client(values).uploadImage(bytes)).markdown}\n`);
67
+ return 0;
68
+ }
45
69
  if (command === "notes" && text === undefined) {
46
70
  const limit = Number(values.limit ?? 20);
47
71
  if (!Number.isInteger(limit) || limit < 1)
48
72
  throw new UsageError("--limit must be a whole number, 1 or more");
49
73
  let left = limit;
50
- for await (const n of client(values).notes({ pageSize: Math.min(limit, 100) })) {
74
+ for await (const n of client(values).notes({ pageSize: Math.min(limit, 100), tag: values.tag?.[0] })) {
51
75
  // To the second, UTC: the same form as the Python CLI, so scripts read either the same way.
52
76
  const when = new Date(n.created_at).toISOString().replace(/\.\d+Z$/, "Z");
53
77
  // --json: exactly the documented fields, like the Python CLI, even if the server adds more.
@@ -69,7 +93,7 @@ export async function main(argv, io = process) {
69
93
  return 2; // parseArgs rejects unknown options with a TypeError
70
94
  }
71
95
  }
72
- // Flags win over NOTEFEED_URL, NOTEFEED_FEED and NOTEFEED_PASSWORD.
96
+ // Flags win over NOTEFEED_URL, NOTEFEED_FEED and NOTEFEED_PASSWORD. NOTEFEED_FEED_PASSWORD has no flag: it would end up in shell history.
73
97
  function client(values) {
74
98
  const url = values.url || process.env.NOTEFEED_URL;
75
99
  const feed = values.feed || process.env.NOTEFEED_FEED;
@@ -77,7 +101,12 @@ function client(values) {
77
101
  throw new UsageError("no URL given; pass --url or set NOTEFEED_URL");
78
102
  if (!feed)
79
103
  throw new UsageError("no feed given; pass --feed or set NOTEFEED_FEED");
80
- return new Client({ url, feed, password: values.password || process.env.NOTEFEED_PASSWORD });
104
+ return new Client({
105
+ url,
106
+ feed,
107
+ password: values.password || process.env.NOTEFEED_PASSWORD,
108
+ feedPassword: process.env.NOTEFEED_FEED_PASSWORD,
109
+ });
81
110
  }
82
111
  async function read(text, file, io) {
83
112
  if (file) {
package/dist/client.d.ts CHANGED
@@ -1,12 +1,13 @@
1
- import type { Created, Error as ApiError, Note } from "./generated/types.gen.js";
1
+ import type { Created, Error as ApiError, Feed, FeedSettings, ImageUploaded, Note } from "./generated/types.gen.js";
2
2
  /** The stable error codes the API answers with. */
3
3
  export type ErrorCode = NonNullable<ApiError["code"]>;
4
- export type { Created, Note };
4
+ export type { Created, Feed, FeedSettings, ImageUploaded, Note };
5
5
  /** `timeoutMs` (default 10000) limits each whole request, including reading the answer. */
6
6
  export type ClientOptions = {
7
7
  url: string;
8
8
  feed?: string;
9
9
  password?: string;
10
+ feedPassword?: string;
10
11
  timeoutMs?: number;
11
12
  };
12
13
  /** Any failure talking to notefeed. `status` and `code` are null when there was no API answer. */
@@ -29,10 +30,10 @@ export declare class RateLimitedError extends NotefeedError {
29
30
  /** No such note, or a malformed read id. */
30
31
  export declare class NotFoundError extends NotefeedError {
31
32
  }
32
- /** The instance's feed or note limit is reached. */
33
+ /** The instance's feed or note limit, or the feed's image limit, is reached. */
33
34
  export declare class LimitReachedError extends NotefeedError {
34
35
  }
35
- /** The note is over the server's size limit. */
36
+ /** The note or image is over the server's size limit. */
36
37
  export declare class NoteTooLargeError extends NotefeedError {
37
38
  }
38
39
  /** The server refused the request itself: invalid or reserved feed, empty note, bad body or parameter. */
@@ -43,24 +44,62 @@ export declare class Client {
43
44
  readonly feed: string | null;
44
45
  private readonly api;
45
46
  private readonly timeoutMs;
47
+ private readonly feedPassword;
46
48
  /** A notefeed server. The feed set here is the default for every call; each call can override it. */
47
49
  constructor(options: ClientOptions);
48
- /** NOTEFEED_URL, NOTEFEED_FEED and NOTEFEED_PASSWORD. */
50
+ /** NOTEFEED_URL, NOTEFEED_FEED, NOTEFEED_PASSWORD and NOTEFEED_FEED_PASSWORD. */
49
51
  static fromEnv(env?: Record<string, string | undefined>): Client;
52
+ /** `feedPassword` overrides the client's, for a feed that has its own password. `tags` label the note (at most 10, each 1 to 32 characters of letters, digits, `-`, `_`, `.`, `:`; not verified). */
50
53
  post(markdown: string, options?: {
51
54
  feed?: string;
55
+ feedPassword?: string;
56
+ tags?: string[];
52
57
  }): Promise<Created>;
53
- /** Every note in the feed, newest first, fetched a page at a time; stop iterating whenever you like. */
58
+ /** Upload a PNG, JPEG, GIF or WebP image to an existing feed. The server decides the format by the bytes, so no content type is needed. `markdown` in the answer is `![](url)`, to put in a note. Same options as post(). */
59
+ uploadImage(data: Uint8Array | Blob, options?: {
60
+ feed?: string;
61
+ feedPassword?: string;
62
+ }): Promise<ImageUploaded>;
63
+ /** Replace a note's markdown; its id and URLs stay. Same options as post(). */
64
+ edit(id: string, markdown: string, options?: {
65
+ feed?: string;
66
+ feedPassword?: string;
67
+ }): Promise<Note>;
68
+ /** Remove a note for good. The feed stays, even with no notes left. Same options as post(). */
69
+ delete(id: string, options?: {
70
+ feed?: string;
71
+ feedPassword?: string;
72
+ }): Promise<void>;
73
+ /** The feed's name, title, description, whether it has a password, and its read link (null while it has no notes). Same options as post(). */
74
+ feedInfo(options?: {
75
+ feed?: string;
76
+ feedPassword?: string;
77
+ }): Promise<Feed>;
78
+ /** Replace the feed's title and description (both; an empty string clears one), and set (`image`: a file name from uploadImage) or clear (`""`) the title image; leave `image` out to keep it. The feed must already exist. Same options as post(). */
79
+ updateFeed(settings: FeedSettings, options?: {
80
+ feed?: string;
81
+ feedPassword?: string;
82
+ }): Promise<Feed>;
83
+ /** Delete the feed with all its notes, settings and password, for good; its name is free again. Same options as post(). */
84
+ deleteFeed(options?: {
85
+ feed?: string;
86
+ feedPassword?: string;
87
+ }): Promise<void>;
88
+ /** Every note in the feed, newest first, fetched a page at a time; stop iterating whenever you like. `tag`: only notes carrying it. */
54
89
  notes(options?: {
55
90
  feed?: string;
91
+ feedPassword?: string;
56
92
  pageSize?: number;
93
+ tag?: string;
57
94
  }): AsyncGenerator<Note>;
58
95
  note(id: string, options?: {
59
96
  feed?: string;
97
+ feedPassword?: string;
60
98
  }): Promise<Note>;
61
99
  /** Like notes(), by the feed's read id: public, read-only, needs no password. */
62
100
  readNotes(readId: string, options?: {
63
101
  pageSize?: number;
102
+ tag?: string;
64
103
  }): AsyncGenerator<Note>;
65
104
  readNote(readId: string, id: string): Promise<Note>;
66
105
  private feedFor;
package/dist/client.js CHANGED
@@ -3,7 +3,7 @@
3
3
  * OpenAPI description (./generated, `npm run generate` in the repo). No runtime dependencies.
4
4
  */
5
5
  import { createClient, createConfig } from "./generated/client/index.js";
6
- import { getNote, getReadNote, listNotes, listReadNotes, postNote } from "./generated/sdk.gen.js";
6
+ import { deleteFeed, deleteNote, editNote, getFeed, getNote, getReadNote, listNotes, listReadNotes, postNote, updateFeed, uploadImage } from "./generated/sdk.gen.js";
7
7
  /** Any failure talking to notefeed. `status` and `code` are null when there was no API answer. */
8
8
  export class NotefeedError extends Error {
9
9
  status;
@@ -32,10 +32,10 @@ export class RateLimitedError extends NotefeedError {
32
32
  /** No such note, or a malformed read id. */
33
33
  export class NotFoundError extends NotefeedError {
34
34
  }
35
- /** The instance's feed or note limit is reached. */
35
+ /** The instance's feed or note limit, or the feed's image limit, is reached. */
36
36
  export class LimitReachedError extends NotefeedError {
37
37
  }
38
- /** The note is over the server's size limit. */
38
+ /** The note or image is over the server's size limit. */
39
39
  export class NoteTooLargeError extends NotefeedError {
40
40
  }
41
41
  /** The server refused the request itself: invalid or reserved feed, empty note, bad body or parameter. */
@@ -46,12 +46,14 @@ const BY_CODE = {
46
46
  not_found: NotFoundError,
47
47
  feed_limit: LimitReachedError,
48
48
  note_limit: LimitReachedError,
49
+ image_limit: LimitReachedError,
49
50
  too_large: NoteTooLargeError,
50
51
  invalid_feed: InvalidRequestError,
51
52
  reserved_feed: InvalidRequestError,
52
53
  empty_note: InvalidRequestError,
53
54
  invalid_body: InvalidRequestError,
54
55
  invalid_request: InvalidRequestError,
56
+ feed_exists: InvalidRequestError,
55
57
  unsupported_type: InvalidRequestError,
56
58
  };
57
59
  const FEED_RE = /^[a-z0-9_-]{1,64}$/; // same rule as the server; reserved names still come back as a 400
@@ -60,9 +62,10 @@ export class Client {
60
62
  feed;
61
63
  api;
62
64
  timeoutMs;
65
+ feedPassword;
63
66
  /** A notefeed server. The feed set here is the default for every call; each call can override it. */
64
67
  constructor(options) {
65
- const { url, feed, password, timeoutMs } = options ?? {};
68
+ const { url, feed, password, feedPassword, timeoutMs } = options ?? {};
66
69
  if (!url)
67
70
  throw new ConfigError("no url given");
68
71
  this.url = url.replace(/\/+$/, "");
@@ -71,29 +74,70 @@ export class Client {
71
74
  // Never echo the value: it would end up in terminals and CI logs.
72
75
  if (pw && /[\x00-\x1f\x7f]/.test(pw))
73
76
  throw new ConfigError("password contains invalid characters");
77
+ this.feedPassword = checkFeedPassword(feedPassword);
74
78
  this.timeoutMs = timeoutMs ?? 10_000;
75
79
  this.api = createClient(createConfig({ baseUrl: this.url, headers: pw ? { Authorization: `Bearer ${pw}` } : {} }));
76
80
  }
77
- /** NOTEFEED_URL, NOTEFEED_FEED and NOTEFEED_PASSWORD. */
81
+ /** NOTEFEED_URL, NOTEFEED_FEED, NOTEFEED_PASSWORD and NOTEFEED_FEED_PASSWORD. */
78
82
  static fromEnv(env = process.env) {
79
- return new Client({ url: env.NOTEFEED_URL ?? "", feed: env.NOTEFEED_FEED || undefined, password: env.NOTEFEED_PASSWORD });
83
+ return new Client({
84
+ url: env.NOTEFEED_URL ?? "",
85
+ feed: env.NOTEFEED_FEED || undefined,
86
+ password: env.NOTEFEED_PASSWORD,
87
+ feedPassword: env.NOTEFEED_FEED_PASSWORD,
88
+ });
80
89
  }
90
+ /** `feedPassword` overrides the client's, for a feed that has its own password. `tags` label the note (at most 10, each 1 to 32 characters of letters, digits, `-`, `_`, `.`, `:`; not verified). */
81
91
  async post(markdown, options = {}) {
82
92
  const feed = this.feedFor(options.feed);
83
- return this.call(postNote({ client: this.api, path: { feed }, body: { markdown }, ...this.opts() }));
93
+ const body = { markdown, ...(options.tags?.length && { tags: options.tags }) };
94
+ return this.call(postNote({ client: this.api, path: { feed }, body, ...this.opts(options.feedPassword) }));
84
95
  }
85
- /** Every note in the feed, newest first, fetched a page at a time; stop iterating whenever you like. */
96
+ /** Upload a PNG, JPEG, GIF or WebP image to an existing feed. The server decides the format by the bytes, so no content type is needed. `markdown` in the answer is `![](url)`, to put in a note. Same options as post(). */
97
+ async uploadImage(data, options = {}) {
98
+ const feed = this.feedFor(options.feed);
99
+ const body = data instanceof Blob ? data : new Blob([data]);
100
+ const headers = { ...this.opts(options.feedPassword).headers, "Content-Type": "application/octet-stream" };
101
+ return this.call(uploadImage({ client: this.api, path: { feed }, body, signal: AbortSignal.timeout(this.timeoutMs), headers }));
102
+ }
103
+ /** Replace a note's markdown; its id and URLs stay. Same options as post(). */
104
+ async edit(id, markdown, options = {}) {
105
+ const feed = this.feedFor(options.feed);
106
+ return this.call(editNote({ client: this.api, path: { feed, id }, body: { markdown }, ...this.opts(options.feedPassword) }));
107
+ }
108
+ /** Remove a note for good. The feed stays, even with no notes left. Same options as post(). */
109
+ async delete(id, options = {}) {
110
+ const feed = this.feedFor(options.feed);
111
+ await this.call(deleteNote({ client: this.api, path: { feed, id }, ...this.opts(options.feedPassword) }));
112
+ }
113
+ /** The feed's name, title, description, whether it has a password, and its read link (null while it has no notes). Same options as post(). */
114
+ async feedInfo(options = {}) {
115
+ const feed = this.feedFor(options.feed);
116
+ return this.call(getFeed({ client: this.api, path: { feed }, ...this.opts(options.feedPassword) }));
117
+ }
118
+ /** Replace the feed's title and description (both; an empty string clears one), and set (`image`: a file name from uploadImage) or clear (`""`) the title image; leave `image` out to keep it. The feed must already exist. Same options as post(). */
119
+ async updateFeed(settings, options = {}) {
120
+ const feed = this.feedFor(options.feed);
121
+ const body = { title: settings.title, description: settings.description, ...(settings.image === undefined ? {} : { image: settings.image }) };
122
+ return this.call(updateFeed({ client: this.api, path: { feed }, body, ...this.opts(options.feedPassword) }));
123
+ }
124
+ /** Delete the feed with all its notes, settings and password, for good; its name is free again. Same options as post(). */
125
+ async deleteFeed(options = {}) {
126
+ const feed = this.feedFor(options.feed);
127
+ await this.call(deleteFeed({ client: this.api, path: { feed }, ...this.opts(options.feedPassword) }));
128
+ }
129
+ /** Every note in the feed, newest first, fetched a page at a time; stop iterating whenever you like. `tag`: only notes carrying it. */
86
130
  notes(options = {}) {
87
131
  const feed = this.feedFor(options.feed);
88
- return this.pages((before) => listNotes({ client: this.api, path: { feed }, query: { limit: options.pageSize, before }, ...this.opts() }));
132
+ return this.pages((before) => listNotes({ client: this.api, path: { feed }, query: { limit: options.pageSize, before, tag: options.tag }, ...this.opts(options.feedPassword) }));
89
133
  }
90
134
  async note(id, options = {}) {
91
135
  const feed = this.feedFor(options.feed);
92
- return this.call(getNote({ client: this.api, path: { feed, id }, ...this.opts() }));
136
+ return this.call(getNote({ client: this.api, path: { feed, id }, ...this.opts(options.feedPassword) }));
93
137
  }
94
138
  /** Like notes(), by the feed's read id: public, read-only, needs no password. */
95
139
  readNotes(readId, options = {}) {
96
- return this.pages((before) => listReadNotes({ client: this.api, path: { readId }, query: { limit: options.pageSize, before }, ...this.opts() }));
140
+ return this.pages((before) => listReadNotes({ client: this.api, path: { readId }, query: { limit: options.pageSize, before, tag: options.tag }, ...this.opts() }));
97
141
  }
98
142
  async readNote(readId, id) {
99
143
  return this.call(getReadNote({ client: this.api, path: { readId, id }, ...this.opts() }));
@@ -104,8 +148,9 @@ export class Client {
104
148
  throw new ConfigError("no feed given");
105
149
  return checkFeed(f);
106
150
  }
107
- opts() {
108
- return { signal: AbortSignal.timeout(this.timeoutMs) };
151
+ opts(feedPassword) {
152
+ const fp = checkFeedPassword(feedPassword) ?? this.feedPassword;
153
+ return { signal: AbortSignal.timeout(this.timeoutMs), headers: fp ? { "X-Feed-Password": fp } : {} };
109
154
  }
110
155
  // `before` is the previous page's `next`: older notes only, so notes posted meanwhile never repeat.
111
156
  async *pages(fetchPage) {
@@ -128,6 +173,8 @@ export class Client {
128
173
  if (response.redirected) {
129
174
  throw new NotefeedError(`${this.url} redirected to ${new URL(response.url).origin}: use that address as the URL`, response.status);
130
175
  }
176
+ if (response.status === 204 && error === undefined)
177
+ return undefined; // delete: no body
131
178
  if (response.ok) {
132
179
  if (error === undefined && isObject(data))
133
180
  return data;
@@ -155,3 +202,10 @@ function checkFeed(feed) {
155
202
  throw new ConfigError("invalid feed name: use 1-64 of a-z, 0-9, _ and -");
156
203
  return feed;
157
204
  }
205
+ function checkFeedPassword(value) {
206
+ const v = value?.trim() || null;
207
+ // Never echo the value: it would end up in terminals and CI logs.
208
+ if (v && /[\x00-\x1f\x7f]/.test(v))
209
+ throw new ConfigError("feed password contains invalid characters");
210
+ return v;
211
+ }
@@ -1,2 +1,2 @@
1
- export { getNote, getOpenApi, getReadNote, listNotes, listReadNotes, type Options, postNote } from './sdk.gen.js';
2
- export type { ClientOptions, Created, Error, GetNoteData, GetNoteError, GetNoteErrors, GetNoteResponse, GetNoteResponses, GetOpenApiData, GetOpenApiResponse, GetOpenApiResponses, GetReadNoteData, GetReadNoteError, GetReadNoteErrors, GetReadNoteResponse, GetReadNoteResponses, ListNotesData, ListNotesError, ListNotesErrors, ListNotesResponse, ListNotesResponses, ListReadNotesData, ListReadNotesError, ListReadNotesErrors, ListReadNotesResponse, ListReadNotesResponses, Note, NoteList, PostForm, PostJson, PostNoteData, PostNoteError, PostNoteErrors, PostNoteResponse, PostNoteResponses } from './types.gen.js';
1
+ export { changeFeedPassword, deleteFeed, deleteNote, editNote, getFeed, getNote, getOpenApi, getReadFeed, getReadNote, listNotes, listReadNotes, type Options, postNote, removeFeedPassword, updateFeed, uploadImage } from './sdk.gen.js';
2
+ export type { ChangeFeedPasswordData, ChangeFeedPasswordError, ChangeFeedPasswordErrors, ChangeFeedPasswordResponse, ChangeFeedPasswordResponses, ClientOptions, Created, DeleteFeedData, DeleteFeedError, DeleteFeedErrors, DeleteFeedResponse, DeleteFeedResponses, DeleteNoteData, DeleteNoteError, DeleteNoteErrors, DeleteNoteResponse, DeleteNoteResponses, EditNoteData, EditNoteError, EditNoteErrors, EditNoteResponse, EditNoteResponses, Error, Feed, FeedSettings, GetFeedData, GetFeedError, GetFeedErrors, GetFeedResponse, GetFeedResponses, GetNoteData, GetNoteError, GetNoteErrors, GetNoteResponse, GetNoteResponses, GetOpenApiData, GetOpenApiResponse, GetOpenApiResponses, GetReadFeedData, GetReadFeedError, GetReadFeedErrors, GetReadFeedResponse, GetReadFeedResponses, GetReadNoteData, GetReadNoteError, GetReadNoteErrors, GetReadNoteResponse, GetReadNoteResponses, ImageUploaded, ListNotesData, ListNotesError, ListNotesErrors, ListNotesResponse, ListNotesResponses, ListReadNotesData, ListReadNotesError, ListReadNotesErrors, ListReadNotesResponse, ListReadNotesResponses, Note, NoteList, PasswordJson, PostForm, PostJson, PostNoteData, PostNoteError, PostNoteErrors, PostNoteResponse, PostNoteResponses, ReadFeed, RemoveFeedPasswordData, RemoveFeedPasswordError, RemoveFeedPasswordErrors, RemoveFeedPasswordResponse, RemoveFeedPasswordResponses, UpdateFeedData, UpdateFeedError, UpdateFeedErrors, UpdateFeedResponse, UpdateFeedResponses, UploadImageData, UploadImageError, UploadImageErrors, UploadImageResponse, UploadImageResponses } from './types.gen.js';
@@ -1,2 +1,2 @@
1
1
  // This file is auto-generated by @hey-api/openapi-ts
2
- export { getNote, getOpenApi, getReadNote, listNotes, listReadNotes, postNote } from './sdk.gen.js';
2
+ export { changeFeedPassword, deleteFeed, deleteNote, editNote, getFeed, getNote, getOpenApi, getReadFeed, getReadNote, listNotes, listReadNotes, postNote, removeFeedPassword, updateFeed, uploadImage } from './sdk.gen.js';
@@ -1,5 +1,5 @@
1
1
  import type { Client, ClientMeta, Options as Options2, RequestResult, TDataShape } from './client/index.js';
2
- import type { GetNoteData, GetNoteErrors, GetNoteResponses, GetOpenApiData, GetOpenApiResponses, GetReadNoteData, GetReadNoteErrors, GetReadNoteResponses, ListNotesData, ListNotesErrors, ListNotesResponses, ListReadNotesData, ListReadNotesErrors, ListReadNotesResponses, PostNoteData, PostNoteErrors, PostNoteResponses } from './types.gen.js';
2
+ import type { ChangeFeedPasswordData, ChangeFeedPasswordErrors, ChangeFeedPasswordResponses, DeleteFeedData, DeleteFeedErrors, DeleteFeedResponses, DeleteNoteData, DeleteNoteErrors, DeleteNoteResponses, EditNoteData, EditNoteErrors, EditNoteResponses, GetFeedData, GetFeedErrors, GetFeedResponses, GetNoteData, GetNoteErrors, GetNoteResponses, GetOpenApiData, GetOpenApiResponses, GetReadFeedData, GetReadFeedErrors, GetReadFeedResponses, GetReadNoteData, GetReadNoteErrors, GetReadNoteResponses, ListNotesData, ListNotesErrors, ListNotesResponses, ListReadNotesData, ListReadNotesErrors, ListReadNotesResponses, PostNoteData, PostNoteErrors, PostNoteResponses, RemoveFeedPasswordData, RemoveFeedPasswordErrors, RemoveFeedPasswordResponses, UpdateFeedData, UpdateFeedErrors, UpdateFeedResponses, UploadImageData, UploadImageErrors, UploadImageResponses } from './types.gen.js';
3
3
  export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean, TResponse = unknown> = Options2<TData, ThrowOnError, TResponse> & {
4
4
  /**
5
5
  * You can provide a client instance returned by `createClient()` instead of
@@ -22,13 +22,67 @@ export declare const listNotes: <ThrowOnError extends boolean = false>(options:
22
22
  /**
23
23
  * Post a note
24
24
  *
25
- * Creates the feed with its first note. Also served at `POST /{feed}`, the short form the client packages and curl one-liners use. The body is at most 102400 bytes and must be UTF-8. `application/x-www-form-urlencoded` (what `curl -d` sends) is read as raw markdown, not as form fields.
25
+ * Creates the feed with its first note, optionally protected by its own password (`X-Feed-Password` header or a `password` field in the JSON or form body; 1 to 256 printable ASCII characters, with no space at the start or end). Posting to a protected feed needs that password. Also served at `POST /{feed}`, the short form the client packages and curl one-liners use. The body is at most 102400 bytes and must be UTF-8. `application/x-www-form-urlencoded` (what `curl -d` sends) is read as raw markdown, not as form fields. Tags (at most 10 tags, each 1 to 32 characters of letters, digits, `-`, `_`, `.` and `:`; case is folded to lowercase, duplicates are removed) go in the JSON `tags` array, a repeated `tags` form field, or, for a raw body, the `X-Note-Tags` header.
26
26
  */
27
27
  export declare const postNote: <ThrowOnError extends boolean = false>(options: Options<PostNoteData, ThrowOnError>) => RequestResult<PostNoteResponses, PostNoteErrors, ThrowOnError>;
28
+ /**
29
+ * Delete a note
30
+ *
31
+ * Needs the feed's password if it has one, and counts against the post rate limit. The feed stays, even with no notes left. Read links can't delete.
32
+ */
33
+ export declare const deleteNote: <ThrowOnError extends boolean = false>(options: Options<DeleteNoteData, ThrowOnError>) => RequestResult<DeleteNoteResponses, DeleteNoteErrors, ThrowOnError>;
28
34
  /**
29
35
  * Get one note
30
36
  */
31
37
  export declare const getNote: <ThrowOnError extends boolean = false>(options: Options<GetNoteData, ThrowOnError>) => RequestResult<GetNoteResponses, GetNoteErrors, ThrowOnError>;
38
+ /**
39
+ * Edit a note
40
+ *
41
+ * Replaces the note's markdown; its id and creation time stay, the title follows the new text. Needs the feed's password if it has one, and counts against the post rate limit. Read links can't edit. The body is as for posting: at most 102400 bytes, UTF-8, a `password` field is ignored.
42
+ */
43
+ export declare const editNote: <ThrowOnError extends boolean = false>(options: Options<EditNoteData, ThrowOnError>) => RequestResult<EditNoteResponses, EditNoteErrors, ThrowOnError>;
44
+ /**
45
+ * Upload an image
46
+ *
47
+ * The body is the image itself: PNG, JPEG, GIF or WebP, recognized by its first bytes, whatever `Content-Type` is sent (SVG is refused). Stored byte-for-byte, with no resizing and no metadata stripped (EXIF such as GPS position stays in the file), as the first 32 hex characters of its SHA-256 plus an extension: the same bytes always give the same URL. The URL is under the feed's read id, so it works in the feed page, the read-only view and RSS readers without any password. Needs the same credentials as posting and counts against the post rate limit. The feed must exist: it is created by its first note. The size limit is NOTEFEED_MAX_IMAGE_BYTES (default 5 MiB); images deleted only with the feed.
48
+ */
49
+ export declare const uploadImage: <ThrowOnError extends boolean = false>(options: Options<UploadImageData, ThrowOnError>) => RequestResult<UploadImageResponses, UploadImageErrors, ThrowOnError>;
50
+ /**
51
+ * Delete a feed
52
+ *
53
+ * Deletes the feed with all its notes, its settings, its password and its read link, for good: there is no undo. The name is free again; a feed created there later gets a new read link, and the old one answers like an unknown one. Needs the feed's password if it has one, and counts against the post rate limit.
54
+ */
55
+ export declare const deleteFeed: <ThrowOnError extends boolean = false>(options: Options<DeleteFeedData, ThrowOnError>) => RequestResult<DeleteFeedResponses, DeleteFeedErrors, ThrowOnError>;
56
+ /**
57
+ * Get a feed's settings
58
+ *
59
+ * The title and description, the title image, whether the feed is protected, and its read link (null while it has no notes). A feed exists once its first note is posted.
60
+ */
61
+ export declare const getFeed: <ThrowOnError extends boolean = false>(options: Options<GetFeedData, ThrowOnError>) => RequestResult<GetFeedResponses, GetFeedErrors, ThrowOnError>;
62
+ /**
63
+ * Change a feed's settings
64
+ *
65
+ * Replaces both the title (at most 100 characters) and the description (at most 500); surrounding whitespace is trimmed and control characters are refused. `show_sender` (default true) shows who posted each note to readers; omitted leaves it as it is. `image` is the file name `uploadImage` returned for this feed (title image), empty to remove it, or omitted to leave it as it is; any other value is a 400. Needs the feed's password if it has one, and counts against the post rate limit. Only on a feed that exists: it is created by its first note. Read links can't change settings.
66
+ */
67
+ export declare const updateFeed: <ThrowOnError extends boolean = false>(options: Options<UpdateFeedData, ThrowOnError>) => RequestResult<UpdateFeedResponses, UpdateFeedErrors, ThrowOnError>;
68
+ /**
69
+ * Remove a feed's password
70
+ *
71
+ * Needs the current password in `X-Feed-Password`. The feed stays, open to anyone who knows its name. An open feed answers 409.
72
+ */
73
+ export declare const removeFeedPassword: <ThrowOnError extends boolean = false>(options: Options<RemoveFeedPasswordData, ThrowOnError>) => RequestResult<RemoveFeedPasswordResponses, RemoveFeedPasswordErrors, ThrowOnError>;
74
+ /**
75
+ * Change a feed's password
76
+ *
77
+ * Needs the current password in `X-Feed-Password`. A feed can only get a password when it is created, so an open feed answers 409.
78
+ */
79
+ export declare const changeFeedPassword: <ThrowOnError extends boolean = false>(options: Options<ChangeFeedPasswordData, ThrowOnError>) => RequestResult<ChangeFeedPasswordResponses, ChangeFeedPasswordErrors, ThrowOnError>;
80
+ /**
81
+ * Get a feed's title and description by its read id
82
+ *
83
+ * Public, even on an instance with a password, and never reveals the feed's name. An unknown read id has an empty title and description, so read ids can't be probed.
84
+ */
85
+ export declare const getReadFeed: <ThrowOnError extends boolean = false>(options: Options<GetReadFeedData, ThrowOnError>) => RequestResult<GetReadFeedResponses, GetReadFeedErrors, ThrowOnError>;
32
86
  /**
33
87
  * List a feed's notes by its read id
34
88
  *
@@ -13,7 +13,7 @@ export const listNotes = (options) => (options.client ?? client).get({
13
13
  /**
14
14
  * Post a note
15
15
  *
16
- * Creates the feed with its first note. Also served at `POST /{feed}`, the short form the client packages and curl one-liners use. The body is at most 102400 bytes and must be UTF-8. `application/x-www-form-urlencoded` (what `curl -d` sends) is read as raw markdown, not as form fields.
16
+ * Creates the feed with its first note, optionally protected by its own password (`X-Feed-Password` header or a `password` field in the JSON or form body; 1 to 256 printable ASCII characters, with no space at the start or end). Posting to a protected feed needs that password. Also served at `POST /{feed}`, the short form the client packages and curl one-liners use. The body is at most 102400 bytes and must be UTF-8. `application/x-www-form-urlencoded` (what `curl -d` sends) is read as raw markdown, not as form fields. Tags (at most 10 tags, each 1 to 32 characters of letters, digits, `-`, `_`, `.` and `:`; case is folded to lowercase, duplicates are removed) go in the JSON `tags` array, a repeated `tags` form field, or, for a raw body, the `X-Note-Tags` header.
17
17
  */
18
18
  export const postNote = (options) => (options.client ?? client).post({
19
19
  security: [{ scheme: 'bearer', type: 'http' }],
@@ -24,6 +24,16 @@ export const postNote = (options) => (options.client ?? client).post({
24
24
  ...options.headers
25
25
  }
26
26
  });
27
+ /**
28
+ * Delete a note
29
+ *
30
+ * Needs the feed's password if it has one, and counts against the post rate limit. The feed stays, even with no notes left. Read links can't delete.
31
+ */
32
+ export const deleteNote = (options) => (options.client ?? client).delete({
33
+ security: [{ scheme: 'bearer', type: 'http' }],
34
+ url: '/api/v1/feeds/{feed}/notes/{id}',
35
+ ...options
36
+ });
27
37
  /**
28
38
  * Get one note
29
39
  */
@@ -32,6 +42,99 @@ export const getNote = (options) => (options.client ?? client).get({
32
42
  url: '/api/v1/feeds/{feed}/notes/{id}',
33
43
  ...options
34
44
  });
45
+ /**
46
+ * Edit a note
47
+ *
48
+ * Replaces the note's markdown; its id and creation time stay, the title follows the new text. Needs the feed's password if it has one, and counts against the post rate limit. Read links can't edit. The body is as for posting: at most 102400 bytes, UTF-8, a `password` field is ignored.
49
+ */
50
+ export const editNote = (options) => (options.client ?? client).put({
51
+ security: [{ scheme: 'bearer', type: 'http' }],
52
+ url: '/api/v1/feeds/{feed}/notes/{id}',
53
+ ...options,
54
+ headers: {
55
+ 'Content-Type': 'application/json',
56
+ ...options.headers
57
+ }
58
+ });
59
+ /**
60
+ * Upload an image
61
+ *
62
+ * The body is the image itself: PNG, JPEG, GIF or WebP, recognized by its first bytes, whatever `Content-Type` is sent (SVG is refused). Stored byte-for-byte, with no resizing and no metadata stripped (EXIF such as GPS position stays in the file), as the first 32 hex characters of its SHA-256 plus an extension: the same bytes always give the same URL. The URL is under the feed's read id, so it works in the feed page, the read-only view and RSS readers without any password. Needs the same credentials as posting and counts against the post rate limit. The feed must exist: it is created by its first note. The size limit is NOTEFEED_MAX_IMAGE_BYTES (default 5 MiB); images deleted only with the feed.
63
+ */
64
+ export const uploadImage = (options) => (options.client ?? client).post({
65
+ bodySerializer: null,
66
+ security: [{ scheme: 'bearer', type: 'http' }],
67
+ url: '/api/v1/feeds/{feed}/images',
68
+ ...options,
69
+ headers: {
70
+ 'Content-Type': 'image/png',
71
+ ...options.headers
72
+ }
73
+ });
74
+ /**
75
+ * Delete a feed
76
+ *
77
+ * Deletes the feed with all its notes, its settings, its password and its read link, for good: there is no undo. The name is free again; a feed created there later gets a new read link, and the old one answers like an unknown one. Needs the feed's password if it has one, and counts against the post rate limit.
78
+ */
79
+ export const deleteFeed = (options) => (options.client ?? client).delete({
80
+ security: [{ scheme: 'bearer', type: 'http' }],
81
+ url: '/api/v1/feeds/{feed}',
82
+ ...options
83
+ });
84
+ /**
85
+ * Get a feed's settings
86
+ *
87
+ * The title and description, the title image, whether the feed is protected, and its read link (null while it has no notes). A feed exists once its first note is posted.
88
+ */
89
+ export const getFeed = (options) => (options.client ?? client).get({
90
+ security: [{ scheme: 'bearer', type: 'http' }],
91
+ url: '/api/v1/feeds/{feed}',
92
+ ...options
93
+ });
94
+ /**
95
+ * Change a feed's settings
96
+ *
97
+ * Replaces both the title (at most 100 characters) and the description (at most 500); surrounding whitespace is trimmed and control characters are refused. `show_sender` (default true) shows who posted each note to readers; omitted leaves it as it is. `image` is the file name `uploadImage` returned for this feed (title image), empty to remove it, or omitted to leave it as it is; any other value is a 400. Needs the feed's password if it has one, and counts against the post rate limit. Only on a feed that exists: it is created by its first note. Read links can't change settings.
98
+ */
99
+ export const updateFeed = (options) => (options.client ?? client).put({
100
+ security: [{ scheme: 'bearer', type: 'http' }],
101
+ url: '/api/v1/feeds/{feed}',
102
+ ...options,
103
+ headers: {
104
+ 'Content-Type': 'application/json',
105
+ ...options.headers
106
+ }
107
+ });
108
+ /**
109
+ * Remove a feed's password
110
+ *
111
+ * Needs the current password in `X-Feed-Password`. The feed stays, open to anyone who knows its name. An open feed answers 409.
112
+ */
113
+ export const removeFeedPassword = (options) => (options.client ?? client).delete({
114
+ security: [{ scheme: 'bearer', type: 'http' }],
115
+ url: '/api/v1/feeds/{feed}/password',
116
+ ...options
117
+ });
118
+ /**
119
+ * Change a feed's password
120
+ *
121
+ * Needs the current password in `X-Feed-Password`. A feed can only get a password when it is created, so an open feed answers 409.
122
+ */
123
+ export const changeFeedPassword = (options) => (options.client ?? client).put({
124
+ security: [{ scheme: 'bearer', type: 'http' }],
125
+ url: '/api/v1/feeds/{feed}/password',
126
+ ...options,
127
+ headers: {
128
+ 'Content-Type': 'application/json',
129
+ ...options.headers
130
+ }
131
+ });
132
+ /**
133
+ * Get a feed's title and description by its read id
134
+ *
135
+ * Public, even on an instance with a password, and never reveals the feed's name. An unknown read id has an empty title and description, so read ids can't be probed.
136
+ */
137
+ export const getReadFeed = (options) => (options.client ?? client).get({ url: '/api/v1/read/{readId}', ...options });
35
138
  /**
36
139
  * List a feed's notes by its read id
37
140
  *
@@ -22,6 +22,14 @@ export type Note = {
22
22
  * The note's page in the web UI
23
23
  */
24
24
  url: string;
25
+ /**
26
+ * Verified sign-in name of the poster; absent when the note was posted without a sign-in
27
+ */
28
+ sender?: string | null;
29
+ /**
30
+ * Labels the poster gave the note (not verified, and shown to readers like the note itself); empty when none
31
+ */
32
+ tags: Array<string>;
25
33
  };
26
34
  export type NoteList = {
27
35
  /**
@@ -44,9 +52,23 @@ export type Created = {
44
52
  */
45
53
  feed_url: string;
46
54
  /**
47
- * The feed's read-only RSS link, safe to share
55
+ * The feed's read-only RSS link, safe to share; null only if the server can't read the feed's stored read id, or if that id and the derived one both belong to other feeds
56
+ */
57
+ read_url: string | null;
58
+ };
59
+ export type ImageUploaded = {
60
+ /**
61
+ * The stored file's name: 32 hex characters of the SHA-256 plus the extension. Pass it as a feed's `image` setting
62
+ */
63
+ file: string;
64
+ /**
65
+ * Where the image is served, absolute, under the feed's read id; public like the read link
66
+ */
67
+ url: string;
68
+ /**
69
+ * `![](url)`, to paste into a note
48
70
  */
49
- read_url: string;
71
+ markdown: string;
50
72
  };
51
73
  export type Error = {
52
74
  /**
@@ -56,16 +78,106 @@ export type Error = {
56
78
  /**
57
79
  * Stable machine-readable code; absent only on a 500
58
80
  */
59
- code?: 'invalid_feed' | 'reserved_feed' | 'auth' | 'rate_limited' | 'too_many_attempts' | 'feed_limit' | 'note_limit' | 'empty_note' | 'too_large' | 'unsupported_type' | 'invalid_body' | 'invalid_request' | 'not_found';
81
+ code?: 'invalid_feed' | 'reserved_feed' | 'auth' | 'rate_limited' | 'too_many_attempts' | 'feed_limit' | 'note_limit' | 'image_limit' | 'empty_note' | 'too_large' | 'unsupported_type' | 'invalid_body' | 'invalid_request' | 'not_found' | 'feed_exists';
60
82
  };
61
83
  export type PostJson = {
62
84
  markdown: string;
85
+ /**
86
+ * Protects the feed: 1 to 256 printable ASCII characters, with no space at the start or end. Only honored on the post that creates the feed; an existing open feed answers 409. Empty is the same as leaving it out.
87
+ */
88
+ password?: string;
89
+ /**
90
+ * Labels for the note: at most 10 tags, each 1 to 32 characters of letters, digits, `-`, `_`, `.` and `:`; case is folded to lowercase, duplicates are removed. Free labels, not verified, shown with the note (also to readers of the read link and RSS). Ignored when editing a note: an edit keeps its tags.
91
+ */
92
+ tags?: Array<string>;
63
93
  };
64
94
  export type PostForm = {
65
95
  markdown: string;
96
+ /**
97
+ * Protects the feed: 1 to 256 printable ASCII characters, with no space at the start or end. Only honored on the post that creates the feed; an existing open feed answers 409. Empty is the same as leaving it out.
98
+ */
99
+ password?: string;
100
+ /**
101
+ * Labels for the note: at most 10 tags, each 1 to 32 characters of letters, digits, `-`, `_`, `.` and `:`; case is folded to lowercase, duplicates are removed. Free labels, not verified, shown with the note (also to readers of the read link and RSS). Ignored when editing a note: an edit keeps its tags.
102
+ */
103
+ tags?: Array<string>;
104
+ };
105
+ export type PasswordJson = {
106
+ /**
107
+ * The new password: 1 to 256 printable ASCII characters, with no space at the start or end
108
+ */
109
+ password: string;
110
+ };
111
+ export type FeedSettings = {
112
+ /**
113
+ * Display title, at most 100 characters, one line; empty means none (the feed's name is shown)
114
+ */
115
+ title: string;
116
+ /**
117
+ * Description, at most 500 characters, one line; may be empty
118
+ */
119
+ description: string;
120
+ /**
121
+ * The file name of an image uploaded to this feed (see uploadImage), shown as the feed's title image; empty removes it, omitted leaves it as it is
122
+ */
123
+ image?: string;
124
+ /**
125
+ * Whether readers (RSS, the read API and pages) see who posted each note; omitted leaves it as it is, a new feed starts with true
126
+ */
127
+ show_sender?: boolean;
128
+ };
129
+ export type Feed = {
130
+ /**
131
+ * The feed's name
132
+ */
133
+ name: string;
134
+ /**
135
+ * Display title, at most 100 characters, one line; empty means none (the feed's name is shown)
136
+ */
137
+ title: string;
138
+ /**
139
+ * Description, at most 500 characters, one line; may be empty
140
+ */
141
+ description: string;
142
+ /**
143
+ * Whether the feed has its own password
144
+ */
145
+ protected: boolean;
146
+ /**
147
+ * The feed's read-only RSS link; null while the feed has no notes, or if the server can't read the feed's stored read id
148
+ */
149
+ read_url: string | null;
150
+ /**
151
+ * The feed's title image (absolute URL, served under the read id), or null
152
+ */
153
+ image_url: string | null;
154
+ /**
155
+ * Whether readers see who posted each note
156
+ */
157
+ show_sender: boolean;
158
+ };
159
+ export type ReadFeed = {
160
+ /**
161
+ * Display title, at most 100 characters, one line; empty means none (the feed's name is shown)
162
+ */
163
+ title: string;
164
+ /**
165
+ * Description, at most 500 characters, one line; may be empty
166
+ */
167
+ description: string;
168
+ /**
169
+ * The feed's title image (absolute URL, served under the read id), or null
170
+ */
171
+ image_url: string | null;
66
172
  };
67
173
  export type ListNotesData = {
68
174
  body?: never;
175
+ headers?: {
176
+ /**
177
+ * The feed's own password, when it has one: to post, list or get, and (as the current password) to change or remove it. On the `POST` that creates a feed it sets the feed's password; on a `POST` to an existing feed that has none it answers 409. An empty value is the same as no header, so a `POST` with an empty one creates an open feed.
178
+ */
179
+ 'X-Feed-Password'?: string;
180
+ };
69
181
  path: {
70
182
  /**
71
183
  * The feed's name. It is the write key: anyone who knows it can post.
@@ -81,6 +193,10 @@ export type ListNotesData = {
81
193
  * Only notes older than this id: the previous page's `next`
82
194
  */
83
195
  before?: string;
196
+ /**
197
+ * Only notes carrying this tag
198
+ */
199
+ tag?: string;
84
200
  };
85
201
  url: '/api/v1/feeds/{feed}/notes';
86
202
  };
@@ -90,7 +206,7 @@ export type ListNotesErrors = {
90
206
  */
91
207
  400: Error;
92
208
  /**
93
- * The instance has a password and it is missing or wrong
209
+ * The instance has a password, or the feed has its own, and it is missing or wrong
94
210
  */
95
211
  401: Error;
96
212
  /**
@@ -108,6 +224,16 @@ export type ListNotesResponses = {
108
224
  export type ListNotesResponse = ListNotesResponses[keyof ListNotesResponses];
109
225
  export type PostNoteData = {
110
226
  body: PostJson;
227
+ headers?: {
228
+ /**
229
+ * The feed's own password, when it has one: to post, list or get, and (as the current password) to change or remove it. On the `POST` that creates a feed it sets the feed's password; on a `POST` to an existing feed that has none it answers 409. An empty value is the same as no header, so a `POST` with an empty one creates an open feed.
230
+ */
231
+ 'X-Feed-Password'?: string;
232
+ /**
233
+ * Tags for the note, comma-separated (`ci,deploy`): at most 10 tags, each 1 to 32 characters of letters, digits, `-`, `_`, `.` and `:`; case is folded to lowercase, duplicates are removed. For a raw markdown body; a JSON or form body's own `tags` wins. Empty is none.
234
+ */
235
+ 'X-Note-Tags'?: string;
236
+ };
111
237
  path: {
112
238
  /**
113
239
  * The feed's name. It is the write key: anyone who knows it can post.
@@ -119,13 +245,17 @@ export type PostNoteData = {
119
245
  };
120
246
  export type PostNoteErrors = {
121
247
  /**
122
- * Invalid or reserved feed name; empty note; bad JSON, form or UTF-8
248
+ * Invalid or reserved feed name; empty note; bad JSON, form or UTF-8; a new password that is not printable ASCII; invalid tags
123
249
  */
124
250
  400: Error;
125
251
  /**
126
- * The instance has a password and it is missing or wrong
252
+ * The instance has a password, or the feed has its own, and it is missing or wrong
127
253
  */
128
254
  401: Error;
255
+ /**
256
+ * A password was sent for a feed that already exists without one: it can't be claimed
257
+ */
258
+ 409: Error;
129
259
  /**
130
260
  * Body over 102400 bytes
131
261
  */
@@ -151,8 +281,61 @@ export type PostNoteResponses = {
151
281
  201: Created;
152
282
  };
153
283
  export type PostNoteResponse = PostNoteResponses[keyof PostNoteResponses];
284
+ export type DeleteNoteData = {
285
+ body?: never;
286
+ headers?: {
287
+ /**
288
+ * The feed's own password, when it has one: to post, list or get, and (as the current password) to change or remove it. On the `POST` that creates a feed it sets the feed's password; on a `POST` to an existing feed that has none it answers 409. An empty value is the same as no header, so a `POST` with an empty one creates an open feed.
289
+ */
290
+ 'X-Feed-Password'?: string;
291
+ };
292
+ path: {
293
+ /**
294
+ * The feed's name. It is the write key: anyone who knows it can post.
295
+ */
296
+ feed: string;
297
+ /**
298
+ * The note's id
299
+ */
300
+ id: string;
301
+ };
302
+ query?: never;
303
+ url: '/api/v1/feeds/{feed}/notes/{id}';
304
+ };
305
+ export type DeleteNoteErrors = {
306
+ /**
307
+ * Invalid or reserved feed name
308
+ */
309
+ 400: Error;
310
+ /**
311
+ * The instance has a password, or the feed has its own, and it is missing or wrong
312
+ */
313
+ 401: Error;
314
+ /**
315
+ * No such note
316
+ */
317
+ 404: Error;
318
+ /**
319
+ * Too many posts, edits and deletes, or wrong passwords, from this client
320
+ */
321
+ 429: Error;
322
+ };
323
+ export type DeleteNoteError = DeleteNoteErrors[keyof DeleteNoteErrors];
324
+ export type DeleteNoteResponses = {
325
+ /**
326
+ * Deleted
327
+ */
328
+ 204: void;
329
+ };
330
+ export type DeleteNoteResponse = DeleteNoteResponses[keyof DeleteNoteResponses];
154
331
  export type GetNoteData = {
155
332
  body?: never;
333
+ headers?: {
334
+ /**
335
+ * The feed's own password, when it has one: to post, list or get, and (as the current password) to change or remove it. On the `POST` that creates a feed it sets the feed's password; on a `POST` to an existing feed that has none it answers 409. An empty value is the same as no header, so a `POST` with an empty one creates an open feed.
336
+ */
337
+ 'X-Feed-Password'?: string;
338
+ };
156
339
  path: {
157
340
  /**
158
341
  * The feed's name. It is the write key: anyone who knows it can post.
@@ -172,7 +355,7 @@ export type GetNoteErrors = {
172
355
  */
173
356
  400: Error;
174
357
  /**
175
- * The instance has a password and it is missing or wrong
358
+ * The instance has a password, or the feed has its own, and it is missing or wrong
176
359
  */
177
360
  401: Error;
178
361
  /**
@@ -192,11 +375,364 @@ export type GetNoteResponses = {
192
375
  200: Note;
193
376
  };
194
377
  export type GetNoteResponse = GetNoteResponses[keyof GetNoteResponses];
378
+ export type EditNoteData = {
379
+ body: PostJson;
380
+ headers?: {
381
+ /**
382
+ * The feed's own password, when it has one: to post, list or get, and (as the current password) to change or remove it. On the `POST` that creates a feed it sets the feed's password; on a `POST` to an existing feed that has none it answers 409. An empty value is the same as no header, so a `POST` with an empty one creates an open feed.
383
+ */
384
+ 'X-Feed-Password'?: string;
385
+ };
386
+ path: {
387
+ /**
388
+ * The feed's name. It is the write key: anyone who knows it can post.
389
+ */
390
+ feed: string;
391
+ /**
392
+ * The note's id
393
+ */
394
+ id: string;
395
+ };
396
+ query?: never;
397
+ url: '/api/v1/feeds/{feed}/notes/{id}';
398
+ };
399
+ export type EditNoteErrors = {
400
+ /**
401
+ * Invalid or reserved feed name; empty note; bad JSON, form or UTF-8
402
+ */
403
+ 400: Error;
404
+ /**
405
+ * The instance has a password, or the feed has its own, and it is missing or wrong
406
+ */
407
+ 401: Error;
408
+ /**
409
+ * No such note
410
+ */
411
+ 404: Error;
412
+ /**
413
+ * Body over 102400 bytes
414
+ */
415
+ 413: Error;
416
+ /**
417
+ * Unsupported content type
418
+ */
419
+ 415: Error;
420
+ /**
421
+ * Too many posts, edits and deletes, or wrong passwords, from this client
422
+ */
423
+ 429: Error;
424
+ };
425
+ export type EditNoteError = EditNoteErrors[keyof EditNoteErrors];
426
+ export type EditNoteResponses = {
427
+ /**
428
+ * The note as it is now
429
+ */
430
+ 200: Note;
431
+ };
432
+ export type EditNoteResponse = EditNoteResponses[keyof EditNoteResponses];
433
+ export type UploadImageData = {
434
+ /**
435
+ * The image's bytes: PNG, JPEG, GIF or WebP, recognized by their first bytes, not by the Content-Type
436
+ */
437
+ body: Blob | File;
438
+ headers?: {
439
+ /**
440
+ * The feed's own password, when it has one: to post, list or get, and (as the current password) to change or remove it. On the `POST` that creates a feed it sets the feed's password; on a `POST` to an existing feed that has none it answers 409. An empty value is the same as no header, so a `POST` with an empty one creates an open feed.
441
+ */
442
+ 'X-Feed-Password'?: string;
443
+ };
444
+ path: {
445
+ /**
446
+ * The feed's name. It is the write key: anyone who knows it can post.
447
+ */
448
+ feed: string;
449
+ };
450
+ query?: never;
451
+ url: '/api/v1/feeds/{feed}/images';
452
+ };
453
+ export type UploadImageErrors = {
454
+ /**
455
+ * Invalid or reserved feed name
456
+ */
457
+ 400: Error;
458
+ /**
459
+ * The instance has a password, or the feed has its own, and it is missing or wrong
460
+ */
461
+ 401: Error;
462
+ /**
463
+ * No such feed
464
+ */
465
+ 404: Error;
466
+ /**
467
+ * Body over NOTEFEED_MAX_IMAGE_BYTES
468
+ */
469
+ 413: Error;
470
+ /**
471
+ * Not a PNG, JPEG, GIF or WebP image
472
+ */
473
+ 415: Error;
474
+ /**
475
+ * Too many posts, uploads, edits and deletes, or wrong passwords, from this client
476
+ */
477
+ 429: Error;
478
+ /**
479
+ * NOTEFEED_MAX_IMAGES_PER_FEED reached
480
+ */
481
+ 507: Error;
482
+ };
483
+ export type UploadImageError = UploadImageErrors[keyof UploadImageErrors];
484
+ export type UploadImageResponses = {
485
+ /**
486
+ * Stored (or already there)
487
+ */
488
+ 201: ImageUploaded;
489
+ };
490
+ export type UploadImageResponse = UploadImageResponses[keyof UploadImageResponses];
491
+ export type DeleteFeedData = {
492
+ body?: never;
493
+ headers?: {
494
+ /**
495
+ * The feed's own password, when it has one: to post, list or get, and (as the current password) to change or remove it. On the `POST` that creates a feed it sets the feed's password; on a `POST` to an existing feed that has none it answers 409. An empty value is the same as no header, so a `POST` with an empty one creates an open feed.
496
+ */
497
+ 'X-Feed-Password'?: string;
498
+ };
499
+ path: {
500
+ /**
501
+ * The feed's name. It is the write key: anyone who knows it can post.
502
+ */
503
+ feed: string;
504
+ };
505
+ query?: never;
506
+ url: '/api/v1/feeds/{feed}';
507
+ };
508
+ export type DeleteFeedErrors = {
509
+ /**
510
+ * Invalid or reserved feed name
511
+ */
512
+ 400: Error;
513
+ /**
514
+ * The instance has a password, or the feed has its own, and it is missing or wrong
515
+ */
516
+ 401: Error;
517
+ /**
518
+ * No such feed
519
+ */
520
+ 404: Error;
521
+ /**
522
+ * Too many posts, edits and deletes, or wrong passwords, from this client
523
+ */
524
+ 429: Error;
525
+ };
526
+ export type DeleteFeedError = DeleteFeedErrors[keyof DeleteFeedErrors];
527
+ export type DeleteFeedResponses = {
528
+ /**
529
+ * Deleted
530
+ */
531
+ 204: void;
532
+ };
533
+ export type DeleteFeedResponse = DeleteFeedResponses[keyof DeleteFeedResponses];
534
+ export type GetFeedData = {
535
+ body?: never;
536
+ headers?: {
537
+ /**
538
+ * The feed's own password, when it has one: to post, list or get, and (as the current password) to change or remove it. On the `POST` that creates a feed it sets the feed's password; on a `POST` to an existing feed that has none it answers 409. An empty value is the same as no header, so a `POST` with an empty one creates an open feed.
539
+ */
540
+ 'X-Feed-Password'?: string;
541
+ };
542
+ path: {
543
+ /**
544
+ * The feed's name. It is the write key: anyone who knows it can post.
545
+ */
546
+ feed: string;
547
+ };
548
+ query?: never;
549
+ url: '/api/v1/feeds/{feed}';
550
+ };
551
+ export type GetFeedErrors = {
552
+ /**
553
+ * Invalid or reserved feed name
554
+ */
555
+ 400: Error;
556
+ /**
557
+ * The instance has a password, or the feed has its own, and it is missing or wrong
558
+ */
559
+ 401: Error;
560
+ /**
561
+ * No such feed
562
+ */
563
+ 404: Error;
564
+ /**
565
+ * Too many wrong passwords from this client
566
+ */
567
+ 429: Error;
568
+ };
569
+ export type GetFeedError = GetFeedErrors[keyof GetFeedErrors];
570
+ export type GetFeedResponses = {
571
+ /**
572
+ * The feed
573
+ */
574
+ 200: Feed;
575
+ };
576
+ export type GetFeedResponse = GetFeedResponses[keyof GetFeedResponses];
577
+ export type UpdateFeedData = {
578
+ body: FeedSettings;
579
+ headers?: {
580
+ /**
581
+ * The feed's own password, when it has one: to post, list or get, and (as the current password) to change or remove it. On the `POST` that creates a feed it sets the feed's password; on a `POST` to an existing feed that has none it answers 409. An empty value is the same as no header, so a `POST` with an empty one creates an open feed.
582
+ */
583
+ 'X-Feed-Password'?: string;
584
+ };
585
+ path: {
586
+ /**
587
+ * The feed's name. It is the write key: anyone who knows it can post.
588
+ */
589
+ feed: string;
590
+ };
591
+ query?: never;
592
+ url: '/api/v1/feeds/{feed}';
593
+ };
594
+ export type UpdateFeedErrors = {
595
+ /**
596
+ * Invalid or reserved feed name, bad JSON, or a title or description that is too long or has control characters
597
+ */
598
+ 400: Error;
599
+ /**
600
+ * The instance has a password, or the feed has its own, and it is missing or wrong
601
+ */
602
+ 401: Error;
603
+ /**
604
+ * No such feed
605
+ */
606
+ 404: Error;
607
+ /**
608
+ * Too many posts, edits and deletes, or wrong passwords, from this client
609
+ */
610
+ 429: Error;
611
+ };
612
+ export type UpdateFeedError = UpdateFeedErrors[keyof UpdateFeedErrors];
613
+ export type UpdateFeedResponses = {
614
+ /**
615
+ * The feed as it is now
616
+ */
617
+ 200: Feed;
618
+ };
619
+ export type UpdateFeedResponse = UpdateFeedResponses[keyof UpdateFeedResponses];
620
+ export type RemoveFeedPasswordData = {
621
+ body?: never;
622
+ headers?: {
623
+ /**
624
+ * The feed's current password
625
+ */
626
+ 'X-Feed-Password'?: string;
627
+ };
628
+ path: {
629
+ /**
630
+ * The feed's name. It is the write key: anyone who knows it can post.
631
+ */
632
+ feed: string;
633
+ };
634
+ query?: never;
635
+ url: '/api/v1/feeds/{feed}/password';
636
+ };
637
+ export type RemoveFeedPasswordErrors = {
638
+ /**
639
+ * Invalid or reserved feed name
640
+ */
641
+ 400: Error;
642
+ /**
643
+ * The instance has a password, or the feed has its own, and it is missing or wrong
644
+ */
645
+ 401: Error;
646
+ /**
647
+ * The feed has no password
648
+ */
649
+ 409: Error;
650
+ /**
651
+ * Too many wrong passwords from this client
652
+ */
653
+ 429: Error;
654
+ };
655
+ export type RemoveFeedPasswordError = RemoveFeedPasswordErrors[keyof RemoveFeedPasswordErrors];
656
+ export type RemoveFeedPasswordResponses = {
657
+ /**
658
+ * Removed
659
+ */
660
+ 204: void;
661
+ };
662
+ export type RemoveFeedPasswordResponse = RemoveFeedPasswordResponses[keyof RemoveFeedPasswordResponses];
663
+ export type ChangeFeedPasswordData = {
664
+ body: PasswordJson;
665
+ headers?: {
666
+ /**
667
+ * The feed's current password
668
+ */
669
+ 'X-Feed-Password'?: string;
670
+ };
671
+ path: {
672
+ /**
673
+ * The feed's name. It is the write key: anyone who knows it can post.
674
+ */
675
+ feed: string;
676
+ };
677
+ query?: never;
678
+ url: '/api/v1/feeds/{feed}/password';
679
+ };
680
+ export type ChangeFeedPasswordErrors = {
681
+ /**
682
+ * Invalid or reserved feed name, bad JSON, or a new password that is not 1 to 256 printable ASCII characters, with no space at the start or end
683
+ */
684
+ 400: Error;
685
+ /**
686
+ * The instance has a password, or the feed has its own, and it is missing or wrong
687
+ */
688
+ 401: Error;
689
+ /**
690
+ * The feed has no password
691
+ */
692
+ 409: Error;
693
+ /**
694
+ * Too many wrong passwords from this client
695
+ */
696
+ 429: Error;
697
+ };
698
+ export type ChangeFeedPasswordError = ChangeFeedPasswordErrors[keyof ChangeFeedPasswordErrors];
699
+ export type ChangeFeedPasswordResponses = {
700
+ /**
701
+ * Changed; the old password and unlock cookies stop working
702
+ */
703
+ 204: void;
704
+ };
705
+ export type ChangeFeedPasswordResponse = ChangeFeedPasswordResponses[keyof ChangeFeedPasswordResponses];
706
+ export type GetReadFeedData = {
707
+ body?: never;
708
+ path: {
709
+ /**
710
+ * The feed's read id, from its read link: 22 random characters, or the feed's own name for a reserved feed. Read-only; never reveals the name of any other feed.
711
+ */
712
+ readId: string;
713
+ };
714
+ query?: never;
715
+ url: '/api/v1/read/{readId}';
716
+ };
717
+ export type GetReadFeedErrors = {
718
+ /**
719
+ * Malformed read id
720
+ */
721
+ 404: Error;
722
+ };
723
+ export type GetReadFeedError = GetReadFeedErrors[keyof GetReadFeedErrors];
724
+ export type GetReadFeedResponses = {
725
+ /**
726
+ * The feed's public settings
727
+ */
728
+ 200: ReadFeed;
729
+ };
730
+ export type GetReadFeedResponse = GetReadFeedResponses[keyof GetReadFeedResponses];
195
731
  export type ListReadNotesData = {
196
732
  body?: never;
197
733
  path: {
198
734
  /**
199
- * The feed's read id, from its read link. Read-only; never reveals the name.
735
+ * The feed's read id, from its read link: 22 random characters, or the feed's own name for a reserved feed. Read-only; never reveals the name of any other feed.
200
736
  */
201
737
  readId: string;
202
738
  };
@@ -209,6 +745,10 @@ export type ListReadNotesData = {
209
745
  * Only notes older than this id: the previous page's `next`
210
746
  */
211
747
  before?: string;
748
+ /**
749
+ * Only notes carrying this tag
750
+ */
751
+ tag?: string;
212
752
  };
213
753
  url: '/api/v1/read/{readId}/notes';
214
754
  };
@@ -234,7 +774,7 @@ export type GetReadNoteData = {
234
774
  body?: never;
235
775
  path: {
236
776
  /**
237
- * The feed's read id, from its read link. Read-only; never reveals the name.
777
+ * The feed's read id, from its read link: 22 random characters, or the feed's own name for a reserved feed. Read-only; never reveals the name of any other feed.
238
778
  */
239
779
  readId: string;
240
780
  /**
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  export { AuthError, Client, ConfigError, InvalidRequestError, LimitReachedError, NotefeedError, NotFoundError, NoteTooLargeError, RateLimitedError, } from "./client.js";
2
- export type { ClientOptions, Created, ErrorCode, Note } from "./client.js";
2
+ export type { ClientOptions, Created, ErrorCode, Feed, FeedSettings, ImageUploaded, Note } from "./client.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "notefeed",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "Client and CLI for notefeed, a self-hosted inbox for markdown notes that you read back as RSS.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Marcel Bruckner",