notefeed 0.28.0 → 0.30.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
@@ -33,6 +33,6 @@ A posted note can be changed or removed: `await client.edit(id, content)` replac
33
33
 
34
34
  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.
35
35
 
36
- A picture is a note of its own: `await client.post(bytes, { type: "image/png", alt: "…" })` takes a `Uint8Array` or a `Blob` (PNG, JPEG, GIF or WebP; a Blob's own type is used if you leave `type` out) and returns what a markdown post returns, with the picture's `file` and `file_url` added. Write `![](file)` in a markdown note to show it; the feed is created by its first note, a picture that is too large is a `NoteTooLargeError`, and an instance that takes no pictures (`NOTEFEED_IMAGE_UPLOADS=0`) answers every picture with an `ImagesOffError`. The server checks the bytes against the declared type. `await client.update(id, { title, alt, date })` changes a note's title, a picture's alt text or a note's date, and `await client.edit(id, content)` replaces its content. `await client.updateFeed({ title, description, image: file })` makes a picture the feed's title image (`image: ""` removes it; leaving `image` out keeps it). `notefeed post --file photo.png` posts a file (the type comes from the extension, or `--type`). To post a note with its pictures in one call, `await client.post(text, { attachments: [{ name: "chart.png", content: bytes, type: "image/png" }] })` posts them with the text in one request, and the server swaps `![](chart.png)` for each file name (`null` content posts only the pictures) (`notefeed post "…" --attach chart.png` does the same); nothing is posted when one is refused. Pictures are public to anyone with the feed's read link, and metadata such as EXIF is not removed.
36
+ A picture is a note of its own: `await client.post(bytes, { type: "image/png", alt: "…" })` takes a `Uint8Array` or a `Blob` (PNG, JPEG, GIF or WebP; a Blob's own type is used if you leave `type` out) and returns what a markdown post returns, with the picture's `file` and `file_url` added. Write `![](file)` in a markdown note to show it; the feed is created by its first note, a picture that is too large is a `NoteTooLargeError`, and an instance that takes no pictures (`NOTEFEED_IMAGE_UPLOADS=0`) answers every picture with an `ImagesOffError`. The server checks the bytes against the declared type. `await client.update(id, { title, alt, tags, date })` changes a note's title, a picture's alt text, a note's tags (the whole list; `notefeed update <id> --tags a,b`) or its date, and `await client.edit(id, content)` replaces its content. `await client.updateFeed({ title, description, image: file })` makes a picture the feed's title image (`image: ""` removes it; leaving `image` out keeps it). `notefeed post --file photo.png` posts a file (the type comes from the extension, or `--type`). To post a note with its pictures in one call, `await client.post(text, { attachments: [{ name: "chart.png", content: bytes, type: "image/png" }] })` posts them with the text in one request, and the server swaps `![](chart.png)` for each file name (`null` content posts only the pictures) (`notefeed post "…" --attach chart.png` does the same); nothing is posted when one is refused. Pictures are public to anyone with the feed's read link, and metadata such as EXIF is not removed.
37
37
 
38
38
  Full documentation: https://docs.notefeed.me/integrations/clients/
package/dist/cli.js CHANGED
@@ -9,7 +9,7 @@ class UsageError extends Error {
9
9
  const USAGE = [
10
10
  "usage: notefeed post <text | - | --file PATH> [--type MEDIA_TYPE] [--title TITLE] [--tag TAG]... [--attach PATH]... [--date WHEN] [--url URL] [--feed FEED] [--password PASSWORD]",
11
11
  " notefeed edit <id> <text | - | --file PATH> [--type MEDIA_TYPE] [--url URL] [--feed FEED] [--password PASSWORD]",
12
- " notefeed update <id> [--title TITLE] [--alt ALT] [--date WHEN] [--url URL] [--feed FEED] [--password PASSWORD]",
12
+ " notefeed update <id> [--title TITLE] [--alt ALT] [--tags LIST] [--date WHEN] [--url URL] [--feed FEED] [--password PASSWORD]",
13
13
  " notefeed delete <id> [--url URL] [--feed FEED] [--password PASSWORD]",
14
14
  " notefeed notes [--limit N] [--tag TAG] [--json] [--url URL] [--feed FEED] [--password PASSWORD]",
15
15
  ].join("\n");
@@ -30,6 +30,7 @@ export async function main(argv, io = process) {
30
30
  title: { type: "string" },
31
31
  alt: { type: "string" },
32
32
  tag: { type: "string", multiple: true },
33
+ tags: { type: "string" },
33
34
  date: { type: "string" },
34
35
  attach: { type: "string", multiple: true },
35
36
  limit: { type: "string" },
@@ -65,9 +66,11 @@ export async function main(argv, io = process) {
65
66
  return 0;
66
67
  }
67
68
  if (command === "update" && text !== undefined && !rest.length) {
68
- if (values.title === undefined && values.alt === undefined && values.date === undefined)
69
- throw new UsageError("give --title, --alt and/or --date");
70
- const note = await client(values).update(text, { title: values.title, alt: values.alt, date: values.date });
69
+ if (values.title === undefined && values.alt === undefined && values.tags === undefined && values.date === undefined)
70
+ throw new UsageError("give --title, --alt, --tags and/or --date");
71
+ // --tags is the whole new list, comma-separated; "" removes them all.
72
+ const tags = values.tags?.split(",").map((t) => t.trim()).filter(Boolean);
73
+ const note = await client(values).update(text, { title: values.title, alt: values.alt, tags, date: values.date });
71
74
  io.stdout.write(`${note.url}\n`);
72
75
  return 0;
73
76
  }
package/dist/client.d.ts CHANGED
@@ -102,10 +102,11 @@ export declare class Client {
102
102
  feedPassword?: string;
103
103
  type?: string;
104
104
  }): Promise<Note>;
105
- /** Set a note's title, alt text (for pictures) and/or date (as in post(); a future one schedules the note); `""` removes a title or alt, so a markdown note's title follows its text again. Same options as post(). */
105
+ /** Set a note's title, alt text (for pictures), tags and/or date (as in post(); a future one schedules the note); `""` removes a title or alt, so a markdown note's title follows its text again; `tags` replaces the whole list (`[]` removes them). Same options as post(). */
106
106
  update(id: string, meta: {
107
107
  title?: string;
108
108
  alt?: string;
109
+ tags?: string[];
109
110
  date?: string | Date;
110
111
  }, options?: {
111
112
  feed?: string;
@@ -126,6 +127,32 @@ export declare class Client {
126
127
  feed?: string;
127
128
  feedPassword?: string;
128
129
  }): Promise<Feed>;
130
+ /** Put the license the key names on the feed, taking it off the feed it was on; a feed needs no password for it. Only on an instance that sells licenses. Same options as post(). */
131
+ attachLicense(key: string, options?: {
132
+ feed?: string;
133
+ feedPassword?: string;
134
+ }): Promise<Feed>;
135
+ /** Take the feed's license off: it is a free feed again, and the key can go on another feed. Same options as post(). */
136
+ detachLicense(options?: {
137
+ feed?: string;
138
+ feedPassword?: string;
139
+ }): Promise<Feed>;
140
+ /** Set the feed's password. On a protected feed it changes it: `feedPassword` (or the client's) is the current one. An open feed takes one without, which claims it: its name was all that protected it. Every request for the feed then needs the new password; its read link stays open. Same options as post(). */
141
+ setFeedPassword(password: string, options?: {
142
+ feed?: string;
143
+ feedPassword?: string;
144
+ }): Promise<void>;
145
+ /** Create the feed without posting a note, to reserve its name: `password` protects it from its first moment, `readId` gives it that read link. Where licenses are sold `licenseKey` is required and goes on the new feed. A feed that exists is refused (409). */
146
+ createFeed(options?: {
147
+ feed?: string;
148
+ password?: string;
149
+ licenseKey?: string;
150
+ readId?: string;
151
+ }): Promise<Feed>;
152
+ /** For a lost password, only on an instance that sells licenses: the key of the license on the feed proves ownership instead of the current password. `password` becomes the feed's password (needs an active license); left out or empty, the password is removed, which a lapsed license's key may do too. A key that is not the feed's license is refused and counts as a failed attempt. */
153
+ recoverFeedPassword(key: string, password?: string, options?: {
154
+ feed?: string;
155
+ }): Promise<void>;
129
156
  /** Delete the feed with all its notes, settings and password, for good; its name is free again. Same options as post(). */
130
157
  deleteFeed(options?: {
131
158
  feed?: string;
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 { deleteFeed, deleteNote, editNote, getFeed, getNote, getReadNote, listNotes, listReadNotes, patchNote, postNote, updateFeed } from "./generated/sdk.gen.js";
6
+ import { changeFeedPassword, createFeed, deleteFeed, deleteFeedLicense, deleteNote, editNote, getFeed, getNote, getReadNote, listNotes, listReadNotes, patchNote, postNote, putFeedLicense, recoverFeedPassword, updateFeed } 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;
@@ -189,7 +189,7 @@ export class Client {
189
189
  const headers = { ...this.opts(options.feedPassword).headers, "Content-Type": type };
190
190
  return this.call(editNote({ client: this.api, path: { feed, id }, body, headers, signal: AbortSignal.timeout(this.timeoutMs) }));
191
191
  }
192
- /** Set a note's title, alt text (for pictures) and/or date (as in post(); a future one schedules the note); `""` removes a title or alt, so a markdown note's title follows its text again. Same options as post(). */
192
+ /** Set a note's title, alt text (for pictures), tags and/or date (as in post(); a future one schedules the note); `""` removes a title or alt, so a markdown note's title follows its text again; `tags` replaces the whole list (`[]` removes them). Same options as post(). */
193
193
  async update(id, meta, options = {}) {
194
194
  const feed = this.feedFor(options.feed);
195
195
  const { date, ...rest } = meta;
@@ -212,6 +212,32 @@ export class Client {
212
212
  const body = { title: settings.title, description: settings.description, ...(settings.image === undefined ? {} : { image: settings.image }), ...(settings.read_id === undefined ? {} : { read_id: settings.read_id }) };
213
213
  return this.call(updateFeed({ client: this.api, path: { feed }, body, ...this.opts(options.feedPassword) }));
214
214
  }
215
+ /** Put the license the key names on the feed, taking it off the feed it was on; a feed needs no password for it. Only on an instance that sells licenses. Same options as post(). */
216
+ async attachLicense(key, options = {}) {
217
+ const feed = this.feedFor(options.feed);
218
+ return this.call(putFeedLicense({ client: this.api, path: { feed }, body: { key }, ...this.opts(options.feedPassword) }));
219
+ }
220
+ /** Take the feed's license off: it is a free feed again, and the key can go on another feed. Same options as post(). */
221
+ async detachLicense(options = {}) {
222
+ const feed = this.feedFor(options.feed);
223
+ return this.call(deleteFeedLicense({ client: this.api, path: { feed }, ...this.opts(options.feedPassword) }));
224
+ }
225
+ /** Set the feed's password. On a protected feed it changes it: `feedPassword` (or the client's) is the current one. An open feed takes one without, which claims it: its name was all that protected it. Every request for the feed then needs the new password; its read link stays open. Same options as post(). */
226
+ async setFeedPassword(password, options = {}) {
227
+ const feed = this.feedFor(options.feed);
228
+ await this.call(changeFeedPassword({ client: this.api, path: { feed }, body: { password }, ...this.opts(options.feedPassword) }));
229
+ }
230
+ /** Create the feed without posting a note, to reserve its name: `password` protects it from its first moment, `readId` gives it that read link. Where licenses are sold `licenseKey` is required and goes on the new feed. A feed that exists is refused (409). */
231
+ async createFeed(options = {}) {
232
+ const feed = this.feedFor(options.feed);
233
+ const body = { ...(options.password ? { password: options.password } : {}), ...(options.licenseKey ? { license_key: options.licenseKey } : {}), ...(options.readId ? { read_id: options.readId } : {}) };
234
+ return this.call(createFeed({ client: this.api, path: { feed }, body, signal: AbortSignal.timeout(this.timeoutMs) }));
235
+ }
236
+ /** For a lost password, only on an instance that sells licenses: the key of the license on the feed proves ownership instead of the current password. `password` becomes the feed's password (needs an active license); left out or empty, the password is removed, which a lapsed license's key may do too. A key that is not the feed's license is refused and counts as a failed attempt. */
237
+ async recoverFeedPassword(key, password = "", options = {}) {
238
+ const feed = this.feedFor(options.feed);
239
+ await this.call(recoverFeedPassword({ client: this.api, path: { feed }, body: { key, password }, signal: AbortSignal.timeout(this.timeoutMs) })); // the key instead of the (lost) feed password
240
+ }
215
241
  /** Delete the feed with all its notes, settings and password, for good; its name is free again. Same options as post(). */
216
242
  async deleteFeed(options = {}) {
217
243
  const feed = this.feedFor(options.feed);
@@ -4,13 +4,13 @@ export declare const createQuerySerializer: <T = unknown>({ parameters, ...args
4
4
  /**
5
5
  * Infers parseAs value from provided Content-Type header.
6
6
  */
7
- export declare const getParseAs: (contentType: string | null) => Exclude<Config["parseAs"], "auto">;
7
+ export declare const getParseAs: (contentType: string | null) => Exclude<Config['parseAs'], 'auto'>;
8
8
  export declare function setAuthParams(options: Pick<RequestOptions, 'auth' | 'query' | 'security'> & {
9
9
  headers: Headers;
10
10
  }): Promise<void>;
11
11
  export declare const buildUrl: Client['buildUrl'];
12
12
  export declare const mergeConfigs: (a: Config, b: Config) => Config;
13
- export declare const mergeHeaders: (...headers: Array<Required<Config>["headers"] | undefined>) => Headers;
13
+ export declare const mergeHeaders: (...headers: Array<Required<Config>['headers'] | undefined>) => Headers;
14
14
  type ErrInterceptor<Err, Res, Req, Options> = (error: Err,
15
15
  /** response may be undefined due to a network error where no response object is produced */
16
16
  response: Res | undefined,
@@ -19,9 +19,9 @@ type ObjectSeparatorStyle = ObjectStyle | MatrixStyle;
19
19
  interface SerializePrimitiveParam extends SerializePrimitiveOptions {
20
20
  value: string;
21
21
  }
22
- export declare const separatorArrayExplode: (style: ArraySeparatorStyle) => "." | ";" | "," | "&";
23
- export declare const separatorArrayNoExplode: (style: ArraySeparatorStyle) => "," | "|" | "%20";
24
- export declare const separatorObjectExplode: (style: ObjectSeparatorStyle) => "." | ";" | "," | "&";
22
+ export declare const separatorArrayExplode: (style: ArraySeparatorStyle) => '.' | ';' | ',' | '&';
23
+ export declare const separatorArrayNoExplode: (style: ArraySeparatorStyle) => ',' | '|' | '%20';
24
+ export declare const separatorObjectExplode: (style: ObjectSeparatorStyle) => '.' | ';' | ',' | '&';
25
25
  export declare const serializeArrayParam: ({ allowReserved, explode, name, style, value, }: SerializeOptions<ArraySeparatorStyle> & {
26
26
  value: unknown[];
27
27
  }) => string;
@@ -1,2 +1,2 @@
1
- export { changeFeedPassword, deleteFeed, deleteNote, editNote, getFeed, getNote, getOpenApi, getReadFeed, getReadNote, listNotes, listReadNotes, type Options, patchNote, postNote, removeFeedPassword, updateFeed } 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, FreeTier, GetFeedData, GetFeedError, GetFeedErrors, GetFeedResponse, GetFeedResponses, GetNoteData, GetNoteError, GetNoteErrors, GetNoteResponse, GetNoteResponses, GetOpenApiData, GetOpenApiResponse, GetOpenApiResponses, GetReadFeedData, GetReadFeedError, GetReadFeedErrors, GetReadFeedResponse, GetReadFeedResponses, GetReadNoteData, GetReadNoteError, GetReadNoteErrors, GetReadNoteResponse, GetReadNoteResponses, ListNotesData, ListNotesError, ListNotesErrors, ListNotesResponse, ListNotesResponses, ListReadNotesData, ListReadNotesError, ListReadNotesErrors, ListReadNotesResponse, ListReadNotesResponses, MultipartNote, Note, NoteEdited, NoteList, NoteMeta, PasswordJson, PatchNoteData, PatchNoteError, PatchNoteErrors, PatchNoteResponse, PatchNoteResponses, Posted, PostNoteData, PostNoteError, PostNoteErrors, PostNoteResponse, PostNoteResponses, ReadFeed, RemoveFeedPasswordData, RemoveFeedPasswordError, RemoveFeedPasswordErrors, RemoveFeedPasswordResponse, RemoveFeedPasswordResponses, UpdateFeedData, UpdateFeedError, UpdateFeedErrors, UpdateFeedResponse, UpdateFeedResponses } from './types.gen.js';
1
+ export { changeFeedPassword, createFeed, deleteFeed, deleteFeedLicense, deleteNote, editNote, getFeed, getNote, getOpenApi, getReadFeed, getReadNote, listNotes, listReadNotes, type Options, patchNote, postNote, putFeedLicense, recoverFeedPassword, removeFeedPassword, updateFeed } from './sdk.gen.js';
2
+ export type { ChangeFeedPasswordData, ChangeFeedPasswordError, ChangeFeedPasswordErrors, ChangeFeedPasswordResponse, ChangeFeedPasswordResponses, ClientOptions, Created, CreateFeedData, CreateFeedError, CreateFeedErrors, CreateFeedJson, CreateFeedResponse, CreateFeedResponses, DeleteFeedData, DeleteFeedError, DeleteFeedErrors, DeleteFeedLicenseData, DeleteFeedLicenseError, DeleteFeedLicenseErrors, DeleteFeedLicenseResponse, DeleteFeedLicenseResponses, DeleteFeedResponse, DeleteFeedResponses, DeleteNoteData, DeleteNoteError, DeleteNoteErrors, DeleteNoteResponse, DeleteNoteResponses, EditNoteData, EditNoteError, EditNoteErrors, EditNoteResponse, EditNoteResponses, Error, Feed, FeedSettings, FreeTier, GetFeedData, GetFeedError, GetFeedErrors, GetFeedResponse, GetFeedResponses, GetNoteData, GetNoteError, GetNoteErrors, GetNoteResponse, GetNoteResponses, GetOpenApiData, GetOpenApiResponse, GetOpenApiResponses, GetReadFeedData, GetReadFeedError, GetReadFeedErrors, GetReadFeedResponse, GetReadFeedResponses, GetReadNoteData, GetReadNoteError, GetReadNoteErrors, GetReadNoteResponse, GetReadNoteResponses, LicenseKey, ListNotesData, ListNotesError, ListNotesErrors, ListNotesResponse, ListNotesResponses, ListReadNotesData, ListReadNotesError, ListReadNotesErrors, ListReadNotesResponse, ListReadNotesResponses, MultipartNote, Note, NoteEdited, NoteList, NoteMeta, PasswordJson, PatchNoteData, PatchNoteError, PatchNoteErrors, PatchNoteResponse, PatchNoteResponses, Posted, PostNoteData, PostNoteError, PostNoteErrors, PostNoteResponse, PostNoteResponses, PutFeedLicenseData, PutFeedLicenseError, PutFeedLicenseErrors, PutFeedLicenseResponse, PutFeedLicenseResponses, ReadFeed, RecoverFeedPasswordData, RecoverFeedPasswordError, RecoverFeedPasswordErrors, RecoverFeedPasswordResponse, RecoverFeedPasswordResponses, RecoveryJson, RemoveFeedPasswordData, RemoveFeedPasswordError, RemoveFeedPasswordErrors, RemoveFeedPasswordResponse, RemoveFeedPasswordResponses, UpdateFeedData, UpdateFeedError, UpdateFeedErrors, UpdateFeedResponse, UpdateFeedResponses } from './types.gen.js';
@@ -1,2 +1,2 @@
1
1
  // This file is auto-generated by @hey-api/openapi-ts
2
- export { changeFeedPassword, deleteFeed, deleteNote, editNote, getFeed, getNote, getOpenApi, getReadFeed, getReadNote, listNotes, listReadNotes, patchNote, postNote, removeFeedPassword, updateFeed } from './sdk.gen.js';
2
+ export { changeFeedPassword, createFeed, deleteFeed, deleteFeedLicense, deleteNote, editNote, getFeed, getNote, getOpenApi, getReadFeed, getReadNote, listNotes, listReadNotes, patchNote, postNote, putFeedLicense, recoverFeedPassword, removeFeedPassword, updateFeed } 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 { 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, PatchNoteData, PatchNoteErrors, PatchNoteResponses, PostNoteData, PostNoteErrors, PostNoteResponses, RemoveFeedPasswordData, RemoveFeedPasswordErrors, RemoveFeedPasswordResponses, UpdateFeedData, UpdateFeedErrors, UpdateFeedResponses } from './types.gen.js';
2
+ import type { ChangeFeedPasswordData, ChangeFeedPasswordErrors, ChangeFeedPasswordResponses, CreateFeedData, CreateFeedErrors, CreateFeedResponses, DeleteFeedData, DeleteFeedErrors, DeleteFeedLicenseData, DeleteFeedLicenseErrors, DeleteFeedLicenseResponses, 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, PatchNoteData, PatchNoteErrors, PatchNoteResponses, PostNoteData, PostNoteErrors, PostNoteResponses, PutFeedLicenseData, PutFeedLicenseErrors, PutFeedLicenseResponses, RecoverFeedPasswordData, RecoverFeedPasswordErrors, RecoverFeedPasswordResponses, RemoveFeedPasswordData, RemoveFeedPasswordErrors, RemoveFeedPasswordResponses, UpdateFeedData, UpdateFeedErrors, UpdateFeedResponses } 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,7 +22,7 @@ export declare const listNotes: <ThrowOnError extends boolean = false>(options:
22
22
  /**
23
23
  * Post a note
24
24
  *
25
- * The body is the note, a file, and `Content-Type` says which kind: `text/markdown` (UTF-8, at most 102400 bytes), or an image, `image/png`, `image/jpeg`, `image/gif` or `image/webp` (at most NOTEFEED_MAX_IMAGE_BYTES, default 5 MiB). Nothing is guessed: any other type, or none, is `415`, and so is a body that is not what the type says (an image is recognized by its first bytes; SVG is refused). It is stored byte for byte, with no resizing and no metadata stripped (EXIF such as GPS position stays in an image). Creates the feed with its first note, optionally protected by its own password (`X-Feed-Password`; 1 to 256 printable ASCII characters, with no space at the start or end) and with the read id in `X-Read-Id`; both are only used by the post that creates the feed. Where licenses are sold, a feed password needs one: the post that creates the feed protected brings the license key in `X-License-Key`, or is refused with `402`. Posting to a protected feed needs its password. Also served at `POST /{feed}`, the short form curl one-liners use. The response names the note's `file` and where it is served, `file_url`, under the feed's read id (public like the read link). Metadata goes in headers: `X-Note-Title`, `X-Note-Tags` (at most 10 tags, each 1 to 32 characters of letters, digits, `-`, `_`, `.` and `:`; case is folded to lowercase, duplicates are removed), `X-Note-Alt` (images), `X-Note-Name` (the original file name), `X-Note-Date` (where it is placed in the feed: a past date backdates it, a future one schedules it). A markdown note with its pictures is one `multipart/form-data` request: a `text` part (the markdown, a field or a file part; a file part keeps its line breaks, a field's are sent as CRLF), `file` parts (up to 10 pictures, each with its file name and image `Content-Type`) and `alt.<file name>` fields. The pictures are stored first, each as its own note named by its file name, then the text with its references to them (`![](chart.png)`, `[x]: chart.png`, as written or percent-decoded) swapped for the stored files; a picture it never refers to is appended as `![](file)`. Everything is checked before the first write, and a failed write removes what the request stored: all or nothing. `X-Note-Alt` and `X-Note-Name` are 400. The answer has `attachments`, the stored pictures in the order of the `file` parts. The `text` part is optional: `X-Note-Title` goes on the text note, `X-Note-Tags` on every note, `X-Note-Date` on the text note (its pictures take the milliseconds just before it); with no `text` part only the pictures are stored, each with the title, and the answer's top level is the first picture. A refused multipart post that would have created a protected feed creates nothing.
25
+ * The body is the note, a file, and `Content-Type` says which kind: `text/markdown` (UTF-8, at most 102400 bytes), or an image, `image/png`, `image/jpeg`, `image/gif` or `image/webp` (at most NOTEFEED_MAX_IMAGE_BYTES, default 5 MiB). Nothing is guessed: any other type, or none, is `415`, and so is a body that is not what the type says (an image is recognized by its first bytes; SVG is refused). It is stored byte for byte, with no resizing and no metadata stripped (EXIF such as GPS position stays in an image). Creates the feed with its first note, optionally protected by its own password (`X-Feed-Password`; 1 to 256 printable ASCII characters, with no space at the start or end) and with the read id in `X-Read-Id`; both are only used by the post that creates the feed. Where licenses are sold, the post that creates the feed can bring a license key in `X-License-Key`, with or without a password; a feed password needs one, or the post is refused with `402`. Posting to a protected feed needs its password. Also served at `POST /{feed}`, the short form curl one-liners use. The response names the note's `file` and where it is served, `file_url`, under the feed's read id (public like the read link). Metadata goes in headers: `X-Note-Title`, `X-Note-Tags` (at most 10 tags, each 1 to 32 characters of letters, digits, `-`, `_`, `.` and `:`; case is folded to lowercase, duplicates are removed), `X-Note-Alt` (images), `X-Note-Name` (the original file name), `X-Note-Date` (where it is placed in the feed: a past date backdates it, a future one schedules it). A markdown note with its pictures is one `multipart/form-data` request: a `text` part (the markdown, a field or a file part; a file part keeps its line breaks, a field's are sent as CRLF), `file` parts (up to 10 pictures, each with its file name and image `Content-Type`) and `alt.<file name>` fields. The pictures are stored first, each as its own note named by its file name, then the text with its references to them (`![](chart.png)`, `[x]: chart.png`, as written or percent-decoded) swapped for the stored files; a picture it never refers to is appended as `![](file)`. Everything is checked before the first write, and a failed write removes what the request stored: all or nothing. `X-Note-Alt` and `X-Note-Name` are 400. The answer has `attachments`, the stored pictures in the order of the `file` parts. The `text` part is optional: `X-Note-Title` goes on the text note, `X-Note-Tags` on every note, `X-Note-Date` on the text note (its pictures take the milliseconds just before it); with no `text` part only the pictures are stored, each with the title, and the answer's top level is the first picture. A refused multipart post that would have created a protected feed creates nothing.
26
26
  */
27
27
  export declare const postNote: <ThrowOnError extends boolean = false>(options: Options<PostNoteData, ThrowOnError>) => RequestResult<PostNoteResponses, PostNoteErrors, ThrowOnError>;
28
28
  /**
@@ -36,15 +36,15 @@ export declare const deleteNote: <ThrowOnError extends boolean = false>(options:
36
36
  */
37
37
  export declare const getNote: <ThrowOnError extends boolean = false>(options: Options<GetNoteData, ThrowOnError>) => RequestResult<GetNoteResponses, GetNoteErrors, ThrowOnError>;
38
38
  /**
39
- * Change a note's title, alt text or date
39
+ * Change a note's title, alt text, tags or date
40
40
  *
41
- * Sets the note's title, alt text (only for images) and/or date. An empty title or alt removes it: a markdown note's title follows its text again. A new date moves the note in the feed, with its attachments; a date after now schedules it. Id and links stay. At least one is needed. Needs the feed's password if it has one, and counts against the post rate limit. Read links can't change notes.
41
+ * Sets the note's title, alt text (only for images), tags and/or date. An empty title or alt removes it: a markdown note's title follows its text again. `tags` replaces the whole list (an empty list removes them), with the same rules as on a post. A new date moves the note in the feed, with its attachments; a date after now schedules it. Id and links stay, and so does its RSS item's guid. At least one is needed. Needs the feed's password if it has one, and counts against the post rate limit. Read links can't change notes.
42
42
  */
43
43
  export declare const patchNote: <ThrowOnError extends boolean = false>(options: Options<PatchNoteData, ThrowOnError>) => RequestResult<PatchNoteResponses, PatchNoteErrors, ThrowOnError>;
44
44
  /**
45
45
  * Replace a note's content
46
46
  *
47
- * The body is the new file, with the same rules as posting: a `Content-Type` that is one of the accepted types, a body that is what it declares. A note keeps its type, so the type must be the note's own (`415` otherwise). Id, creation time and metadata stay: the title of a markdown note without one set follows the new text. Change the title or alt text with `PATCH`. Needs the feed's password if it has one, and counts against the post rate limit. Read links can't edit. A markdown note with its pictures is one `multipart/form-data` request: a `text` part (the markdown, a field or a file part; a file part keeps its line breaks, a field's are sent as CRLF), `file` parts (up to 10 pictures, each with its file name and image `Content-Type`) and `alt.<file name>` fields. The pictures are stored first, each as its own note named by its file name, then the text with its references to them (`![](chart.png)`, `[x]: chart.png`, as written or percent-decoded) swapped for the stored files; a picture it never refers to is appended as `![](file)`. Everything is checked before the first write, and a failed write removes what the request stored: all or nothing. `X-Note-Alt` and `X-Note-Name` are 400. The answer has `attachments`, the stored pictures in the order of the `file` parts. On a `PUT` the `text` part is required and the note must be a markdown note; the note keeps its own title and tags, `X-Note-Tags` goes on the new pictures. A raw body ignores `X-Note-Tags`. The note is looked up first: a missing note is `404` and an image note `415` whatever the body holds.
47
+ * The body is the new file, with the same rules as posting: a `Content-Type` that is one of the accepted types, a body that is what it declares. A note keeps its type, so the type must be the note's own (`415` otherwise). Id, creation time and metadata stay: the title of a markdown note without one set follows the new text. Change the title, alt text or tags with `PATCH`. Needs the feed's password if it has one, and counts against the post rate limit. Read links can't edit. A markdown note with its pictures is one `multipart/form-data` request: a `text` part (the markdown, a field or a file part; a file part keeps its line breaks, a field's are sent as CRLF), `file` parts (up to 10 pictures, each with its file name and image `Content-Type`) and `alt.<file name>` fields. The pictures are stored first, each as its own note named by its file name, then the text with its references to them (`![](chart.png)`, `[x]: chart.png`, as written or percent-decoded) swapped for the stored files; a picture it never refers to is appended as `![](file)`. Everything is checked before the first write, and a failed write removes what the request stored: all or nothing. `X-Note-Alt` and `X-Note-Name` are 400. The answer has `attachments`, the stored pictures in the order of the `file` parts. On a `PUT` the `text` part is required and the note must be a markdown note; the note keeps its own title and tags, `X-Note-Tags` goes on the new pictures. A raw body ignores `X-Note-Tags`. The note is looked up first: a missing note is `404` and an image note `415` whatever the body holds.
48
48
  */
49
49
  export declare const editNote: <ThrowOnError extends boolean = false>(options: Options<EditNoteData, ThrowOnError>) => RequestResult<EditNoteResponses, EditNoteErrors, ThrowOnError>;
50
50
  /**
@@ -59,6 +59,12 @@ export declare const deleteFeed: <ThrowOnError extends boolean = false>(options:
59
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
60
  */
61
61
  export declare const getFeed: <ThrowOnError extends boolean = false>(options: Options<GetFeedData, ThrowOnError>) => RequestResult<GetFeedResponses, GetFeedErrors, ThrowOnError>;
62
+ /**
63
+ * Create a feed without posting a note
64
+ *
65
+ * Reserves the feed name now (ADR 0047): with a password it is protected from its first moment, with a read id it gets that read link. Where licenses are sold a license key is required and goes on the new feed; free feeds start with their first note. A feed that exists is 409. Counts against the post rate limit and the instance's feed cap, like a first post. After it, change the title and description with updateFeed.
66
+ */
67
+ export declare const createFeed: <ThrowOnError extends boolean = false>(options: Options<CreateFeedData, ThrowOnError>) => RequestResult<CreateFeedResponses, CreateFeedErrors, ThrowOnError>;
62
68
  /**
63
69
  * Change a feed's settings
64
70
  *
@@ -72,11 +78,29 @@ export declare const updateFeed: <ThrowOnError extends boolean = false>(options:
72
78
  */
73
79
  export declare const removeFeedPassword: <ThrowOnError extends boolean = false>(options: Options<RemoveFeedPasswordData, ThrowOnError>) => RequestResult<RemoveFeedPasswordResponses, RemoveFeedPasswordErrors, ThrowOnError>;
74
80
  /**
75
- * Change a feed's password
81
+ * Set or change a feed's password
76
82
  *
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.
83
+ * On a protected feed: changes the password, with the current one in `X-Feed-Password`. On an open feed: sets one, with no current password, for whoever calls it. An open feed's name is all that protects it, so whoever knows the name may claim the feed this way. From then on everyone who posts to the feed, reads it by its name or changes it needs the password; the read link and RSS stay open.
78
84
  */
79
85
  export declare const changeFeedPassword: <ThrowOnError extends boolean = false>(options: Options<ChangeFeedPasswordData, ThrowOnError>) => RequestResult<ChangeFeedPasswordResponses, ChangeFeedPasswordErrors, ThrowOnError>;
86
+ /**
87
+ * Recover a feed's password with its license key
88
+ *
89
+ * Only on an instance that sells licenses (404 elsewhere). For a lost password: the key of the license on this feed proves ownership instead of the current password (ADR 0037). With a new password the feed gets it (needs an active license, like any password change); with none, or an empty one, the password is removed, which a lapsed license's key may do too. A key that is not this feed's license is 401 and counts as a failed attempt, like a wrong password; a bad new password is refused before the key is tried.
90
+ */
91
+ export declare const recoverFeedPassword: <ThrowOnError extends boolean = false>(options: Options<RecoverFeedPasswordData, ThrowOnError>) => RequestResult<RecoverFeedPasswordResponses, RecoverFeedPasswordErrors, ThrowOnError>;
92
+ /**
93
+ * Take a feed's license off
94
+ *
95
+ * Only on an instance that sells licenses (404 elsewhere). The feed is a free one again, and the key can be put on another feed; the subscription runs on. A feed without a license answers the same. Needs the feed's password if it has one, and counts against the post rate limit.
96
+ */
97
+ export declare const deleteFeedLicense: <ThrowOnError extends boolean = false>(options: Options<DeleteFeedLicenseData, ThrowOnError>) => RequestResult<DeleteFeedLicenseResponses, DeleteFeedLicenseErrors, ThrowOnError>;
98
+ /**
99
+ * Put a license on a feed
100
+ *
101
+ * Only on an instance that sells licenses (404 elsewhere). Puts the license the key names on the feed, taking it off the feed it was on; a feed that has an active license already answers 400 (take it off first). A feed needs no password for a license. A key notefeed doesn't know is 400 and counts as a failed attempt, like a wrong password. 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, which can bring the key in `X-License-Key`.
102
+ */
103
+ export declare const putFeedLicense: <ThrowOnError extends boolean = false>(options: Options<PutFeedLicenseData, ThrowOnError>) => RequestResult<PutFeedLicenseResponses, PutFeedLicenseErrors, ThrowOnError>;
80
104
  /**
81
105
  * Get a feed's title and description by its read id
82
106
  *
@@ -13,7 +13,7 @@ export const listNotes = (options) => (options.client ?? client).get({
13
13
  /**
14
14
  * Post a note
15
15
  *
16
- * The body is the note, a file, and `Content-Type` says which kind: `text/markdown` (UTF-8, at most 102400 bytes), or an image, `image/png`, `image/jpeg`, `image/gif` or `image/webp` (at most NOTEFEED_MAX_IMAGE_BYTES, default 5 MiB). Nothing is guessed: any other type, or none, is `415`, and so is a body that is not what the type says (an image is recognized by its first bytes; SVG is refused). It is stored byte for byte, with no resizing and no metadata stripped (EXIF such as GPS position stays in an image). Creates the feed with its first note, optionally protected by its own password (`X-Feed-Password`; 1 to 256 printable ASCII characters, with no space at the start or end) and with the read id in `X-Read-Id`; both are only used by the post that creates the feed. Where licenses are sold, a feed password needs one: the post that creates the feed protected brings the license key in `X-License-Key`, or is refused with `402`. Posting to a protected feed needs its password. Also served at `POST /{feed}`, the short form curl one-liners use. The response names the note's `file` and where it is served, `file_url`, under the feed's read id (public like the read link). Metadata goes in headers: `X-Note-Title`, `X-Note-Tags` (at most 10 tags, each 1 to 32 characters of letters, digits, `-`, `_`, `.` and `:`; case is folded to lowercase, duplicates are removed), `X-Note-Alt` (images), `X-Note-Name` (the original file name), `X-Note-Date` (where it is placed in the feed: a past date backdates it, a future one schedules it). A markdown note with its pictures is one `multipart/form-data` request: a `text` part (the markdown, a field or a file part; a file part keeps its line breaks, a field's are sent as CRLF), `file` parts (up to 10 pictures, each with its file name and image `Content-Type`) and `alt.<file name>` fields. The pictures are stored first, each as its own note named by its file name, then the text with its references to them (`![](chart.png)`, `[x]: chart.png`, as written or percent-decoded) swapped for the stored files; a picture it never refers to is appended as `![](file)`. Everything is checked before the first write, and a failed write removes what the request stored: all or nothing. `X-Note-Alt` and `X-Note-Name` are 400. The answer has `attachments`, the stored pictures in the order of the `file` parts. The `text` part is optional: `X-Note-Title` goes on the text note, `X-Note-Tags` on every note, `X-Note-Date` on the text note (its pictures take the milliseconds just before it); with no `text` part only the pictures are stored, each with the title, and the answer's top level is the first picture. A refused multipart post that would have created a protected feed creates nothing.
16
+ * The body is the note, a file, and `Content-Type` says which kind: `text/markdown` (UTF-8, at most 102400 bytes), or an image, `image/png`, `image/jpeg`, `image/gif` or `image/webp` (at most NOTEFEED_MAX_IMAGE_BYTES, default 5 MiB). Nothing is guessed: any other type, or none, is `415`, and so is a body that is not what the type says (an image is recognized by its first bytes; SVG is refused). It is stored byte for byte, with no resizing and no metadata stripped (EXIF such as GPS position stays in an image). Creates the feed with its first note, optionally protected by its own password (`X-Feed-Password`; 1 to 256 printable ASCII characters, with no space at the start or end) and with the read id in `X-Read-Id`; both are only used by the post that creates the feed. Where licenses are sold, the post that creates the feed can bring a license key in `X-License-Key`, with or without a password; a feed password needs one, or the post is refused with `402`. Posting to a protected feed needs its password. Also served at `POST /{feed}`, the short form curl one-liners use. The response names the note's `file` and where it is served, `file_url`, under the feed's read id (public like the read link). Metadata goes in headers: `X-Note-Title`, `X-Note-Tags` (at most 10 tags, each 1 to 32 characters of letters, digits, `-`, `_`, `.` and `:`; case is folded to lowercase, duplicates are removed), `X-Note-Alt` (images), `X-Note-Name` (the original file name), `X-Note-Date` (where it is placed in the feed: a past date backdates it, a future one schedules it). A markdown note with its pictures is one `multipart/form-data` request: a `text` part (the markdown, a field or a file part; a file part keeps its line breaks, a field's are sent as CRLF), `file` parts (up to 10 pictures, each with its file name and image `Content-Type`) and `alt.<file name>` fields. The pictures are stored first, each as its own note named by its file name, then the text with its references to them (`![](chart.png)`, `[x]: chart.png`, as written or percent-decoded) swapped for the stored files; a picture it never refers to is appended as `![](file)`. Everything is checked before the first write, and a failed write removes what the request stored: all or nothing. `X-Note-Alt` and `X-Note-Name` are 400. The answer has `attachments`, the stored pictures in the order of the `file` parts. The `text` part is optional: `X-Note-Title` goes on the text note, `X-Note-Tags` on every note, `X-Note-Date` on the text note (its pictures take the milliseconds just before it); with no `text` part only the pictures are stored, each with the title, and the answer's top level is the first picture. A refused multipart post that would have created a protected feed creates nothing.
17
17
  */
18
18
  export const postNote = (options) => (options.client ?? client).post({
19
19
  bodySerializer: null,
@@ -44,9 +44,9 @@ export const getNote = (options) => (options.client ?? client).get({
44
44
  ...options
45
45
  });
46
46
  /**
47
- * Change a note's title, alt text or date
47
+ * Change a note's title, alt text, tags or date
48
48
  *
49
- * Sets the note's title, alt text (only for images) and/or date. An empty title or alt removes it: a markdown note's title follows its text again. A new date moves the note in the feed, with its attachments; a date after now schedules it. Id and links stay. At least one is needed. Needs the feed's password if it has one, and counts against the post rate limit. Read links can't change notes.
49
+ * Sets the note's title, alt text (only for images), tags and/or date. An empty title or alt removes it: a markdown note's title follows its text again. `tags` replaces the whole list (an empty list removes them), with the same rules as on a post. A new date moves the note in the feed, with its attachments; a date after now schedules it. Id and links stay, and so does its RSS item's guid. At least one is needed. Needs the feed's password if it has one, and counts against the post rate limit. Read links can't change notes.
50
50
  */
51
51
  export const patchNote = (options) => (options.client ?? client).patch({
52
52
  security: [{ scheme: 'bearer', type: 'http' }],
@@ -60,7 +60,7 @@ export const patchNote = (options) => (options.client ?? client).patch({
60
60
  /**
61
61
  * Replace a note's content
62
62
  *
63
- * The body is the new file, with the same rules as posting: a `Content-Type` that is one of the accepted types, a body that is what it declares. A note keeps its type, so the type must be the note's own (`415` otherwise). Id, creation time and metadata stay: the title of a markdown note without one set follows the new text. Change the title or alt text with `PATCH`. Needs the feed's password if it has one, and counts against the post rate limit. Read links can't edit. A markdown note with its pictures is one `multipart/form-data` request: a `text` part (the markdown, a field or a file part; a file part keeps its line breaks, a field's are sent as CRLF), `file` parts (up to 10 pictures, each with its file name and image `Content-Type`) and `alt.<file name>` fields. The pictures are stored first, each as its own note named by its file name, then the text with its references to them (`![](chart.png)`, `[x]: chart.png`, as written or percent-decoded) swapped for the stored files; a picture it never refers to is appended as `![](file)`. Everything is checked before the first write, and a failed write removes what the request stored: all or nothing. `X-Note-Alt` and `X-Note-Name` are 400. The answer has `attachments`, the stored pictures in the order of the `file` parts. On a `PUT` the `text` part is required and the note must be a markdown note; the note keeps its own title and tags, `X-Note-Tags` goes on the new pictures. A raw body ignores `X-Note-Tags`. The note is looked up first: a missing note is `404` and an image note `415` whatever the body holds.
63
+ * The body is the new file, with the same rules as posting: a `Content-Type` that is one of the accepted types, a body that is what it declares. A note keeps its type, so the type must be the note's own (`415` otherwise). Id, creation time and metadata stay: the title of a markdown note without one set follows the new text. Change the title, alt text or tags with `PATCH`. Needs the feed's password if it has one, and counts against the post rate limit. Read links can't edit. A markdown note with its pictures is one `multipart/form-data` request: a `text` part (the markdown, a field or a file part; a file part keeps its line breaks, a field's are sent as CRLF), `file` parts (up to 10 pictures, each with its file name and image `Content-Type`) and `alt.<file name>` fields. The pictures are stored first, each as its own note named by its file name, then the text with its references to them (`![](chart.png)`, `[x]: chart.png`, as written or percent-decoded) swapped for the stored files; a picture it never refers to is appended as `![](file)`. Everything is checked before the first write, and a failed write removes what the request stored: all or nothing. `X-Note-Alt` and `X-Note-Name` are 400. The answer has `attachments`, the stored pictures in the order of the `file` parts. On a `PUT` the `text` part is required and the note must be a markdown note; the note keeps its own title and tags, `X-Note-Tags` goes on the new pictures. A raw body ignores `X-Note-Tags`. The note is looked up first: a missing note is `404` and an image note `415` whatever the body holds.
64
64
  */
65
65
  export const editNote = (options) => (options.client ?? client).put({
66
66
  bodySerializer: null,
@@ -92,6 +92,20 @@ export const getFeed = (options) => (options.client ?? client).get({
92
92
  url: '/api/v1/feeds/{feed}',
93
93
  ...options
94
94
  });
95
+ /**
96
+ * Create a feed without posting a note
97
+ *
98
+ * Reserves the feed name now (ADR 0047): with a password it is protected from its first moment, with a read id it gets that read link. Where licenses are sold a license key is required and goes on the new feed; free feeds start with their first note. A feed that exists is 409. Counts against the post rate limit and the instance's feed cap, like a first post. After it, change the title and description with updateFeed.
99
+ */
100
+ export const createFeed = (options) => (options.client ?? client).post({
101
+ security: [{ scheme: 'bearer', type: 'http' }],
102
+ url: '/api/v1/feeds/{feed}',
103
+ ...options,
104
+ headers: {
105
+ 'Content-Type': 'application/json',
106
+ ...options.headers
107
+ }
108
+ });
95
109
  /**
96
110
  * Change a feed's settings
97
111
  *
@@ -117,9 +131,9 @@ export const removeFeedPassword = (options) => (options.client ?? client).delete
117
131
  ...options
118
132
  });
119
133
  /**
120
- * Change a feed's password
134
+ * Set or change a feed's password
121
135
  *
122
- * 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.
136
+ * On a protected feed: changes the password, with the current one in `X-Feed-Password`. On an open feed: sets one, with no current password, for whoever calls it. An open feed's name is all that protects it, so whoever knows the name may claim the feed this way. From then on everyone who posts to the feed, reads it by its name or changes it needs the password; the read link and RSS stay open.
123
137
  */
124
138
  export const changeFeedPassword = (options) => (options.client ?? client).put({
125
139
  security: [{ scheme: 'bearer', type: 'http' }],
@@ -130,6 +144,44 @@ export const changeFeedPassword = (options) => (options.client ?? client).put({
130
144
  ...options.headers
131
145
  }
132
146
  });
147
+ /**
148
+ * Recover a feed's password with its license key
149
+ *
150
+ * Only on an instance that sells licenses (404 elsewhere). For a lost password: the key of the license on this feed proves ownership instead of the current password (ADR 0037). With a new password the feed gets it (needs an active license, like any password change); with none, or an empty one, the password is removed, which a lapsed license's key may do too. A key that is not this feed's license is 401 and counts as a failed attempt, like a wrong password; a bad new password is refused before the key is tried.
151
+ */
152
+ export const recoverFeedPassword = (options) => (options.client ?? client).post({
153
+ security: [{ scheme: 'bearer', type: 'http' }],
154
+ url: '/api/v1/feeds/{feed}/password/recover',
155
+ ...options,
156
+ headers: {
157
+ 'Content-Type': 'application/json',
158
+ ...options.headers
159
+ }
160
+ });
161
+ /**
162
+ * Take a feed's license off
163
+ *
164
+ * Only on an instance that sells licenses (404 elsewhere). The feed is a free one again, and the key can be put on another feed; the subscription runs on. A feed without a license answers the same. Needs the feed's password if it has one, and counts against the post rate limit.
165
+ */
166
+ export const deleteFeedLicense = (options) => (options.client ?? client).delete({
167
+ security: [{ scheme: 'bearer', type: 'http' }],
168
+ url: '/api/v1/feeds/{feed}/license',
169
+ ...options
170
+ });
171
+ /**
172
+ * Put a license on a feed
173
+ *
174
+ * Only on an instance that sells licenses (404 elsewhere). Puts the license the key names on the feed, taking it off the feed it was on; a feed that has an active license already answers 400 (take it off first). A feed needs no password for a license. A key notefeed doesn't know is 400 and counts as a failed attempt, like a wrong password. 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, which can bring the key in `X-License-Key`.
175
+ */
176
+ export const putFeedLicense = (options) => (options.client ?? client).put({
177
+ security: [{ scheme: 'bearer', type: 'http' }],
178
+ url: '/api/v1/feeds/{feed}/license',
179
+ ...options,
180
+ headers: {
181
+ 'Content-Type': 'application/json',
182
+ ...options.headers
183
+ }
184
+ });
133
185
  /**
134
186
  * Get a feed's title and description by its read id
135
187
  *
@@ -221,6 +221,10 @@ export type NoteMeta = {
221
221
  * Alternative text of an image note, at most 500 characters, one line (control and text-direction override characters are refused)
222
222
  */
223
223
  alt?: string;
224
+ /**
225
+ * The note's tags, the whole new list (`["ci","done"]`): at most 10 tags, each 1 to 32 characters of letters, digits, `-`, `_`, `.` and `:`; case is folded to lowercase, duplicates are removed. Empty removes them all; omitted leaves them as they are.
226
+ */
227
+ tags?: Array<string>;
224
228
  /**
225
229
  * An ISO 8601 date and time with its offset (`2026-10-01T09:30:00Z`, `2026-10-01T11:30:00+02:00`) or a date alone (midnight UTC). When the note is placed in the feed, from 1970 to 9999. A time after now schedules it: readers see it from then on. The note's attachments move with it.
226
230
  */
@@ -232,6 +236,36 @@ export type PasswordJson = {
232
236
  */
233
237
  password: string;
234
238
  };
239
+ export type RecoveryJson = {
240
+ /**
241
+ * The license key on this feed, as the checkout or the receipt gave it
242
+ */
243
+ key: string;
244
+ /**
245
+ * The new password: 1 to 256 printable ASCII characters, with no space at the start or end. Empty or left out removes the password
246
+ */
247
+ password?: string;
248
+ };
249
+ export type CreateFeedJson = {
250
+ /**
251
+ * A password that protects the new feed: 1 to 256 printable ASCII characters, with no space at the start or end. Empty or left out: an open feed
252
+ */
253
+ password?: string;
254
+ /**
255
+ * Where licenses are sold, required: the key of the license to put on the new feed
256
+ */
257
+ license_key?: string;
258
+ /**
259
+ * The read id to give the feed (3 to 64 characters of a-z, 0-9, - and _); left out: a random one
260
+ */
261
+ read_id?: string;
262
+ };
263
+ export type LicenseKey = {
264
+ /**
265
+ * The license key, as the checkout or the receipt gave it
266
+ */
267
+ key: string;
268
+ };
235
269
  export type FeedSettings = {
236
270
  /**
237
271
  * Display title, at most 100 characters, one line (control and text-direction override characters are refused); empty means none (the feed's name is shown)
@@ -383,7 +417,7 @@ export type PostNoteData = {
383
417
  */
384
418
  'X-Read-Id'?: string;
385
419
  /**
386
- * Only on an instance that sells licenses, with `X-Feed-Password` on the post that creates the feed: the key of the license to put on the new feed, since a feed password is for licensed feeds (402 without one). Ignored on an existing feed and without a password; 400 for a key that is none
420
+ * Only on an instance that sells licenses, on the post that creates the feed: the key of the license to put on the new feed, with or without `X-Feed-Password` (a feed password is for licensed feeds: with one and no key, 402). 400 for a key that is none, 402 for one whose license has lapsed; either way nothing is created. Ignored on an existing feed: put a license on it with `PUT /api/v1/feeds/{feed}/license`
387
421
  */
388
422
  'X-License-Key'?: string;
389
423
  /**
@@ -399,7 +433,7 @@ export type PostNoteData = {
399
433
  */
400
434
  'X-Note-Alt'?: string;
401
435
  /**
402
- * For an image body: the picture's original file name, kept with the note (at most 200 characters)
436
+ * The note's file name (at most 200 characters; `/`, `\` and control characters are dropped): for a picture, its original name. The read link serves the newest note with that name at `/r/{readId}/{name}`, a stable address for a file that changes (a name ending in a lower-case extension, such as `post-to-notefeed.md`); a note's own file name (`<id>.<ext>`) comes first
403
437
  */
404
438
  'X-Note-Name'?: string;
405
439
  /**
@@ -599,7 +633,7 @@ export type PatchNoteData = {
599
633
  };
600
634
  export type PatchNoteErrors = {
601
635
  /**
602
- * Invalid or reserved feed name; nothing to change; a bad title, alt text or date; alt for a note that has none; bad JSON
636
+ * Invalid or reserved feed name; nothing to change; a bad title, alt text, tag or date; too many tags; alt for a note that has none; bad JSON
603
637
  */
604
638
  400: Error;
605
639
  /**
@@ -807,6 +841,55 @@ export type GetFeedResponses = {
807
841
  200: Feed;
808
842
  };
809
843
  export type GetFeedResponse = GetFeedResponses[keyof GetFeedResponses];
844
+ export type CreateFeedData = {
845
+ body: CreateFeedJson;
846
+ path: {
847
+ /**
848
+ * The feed's name. It is the write key: anyone who knows it can post.
849
+ */
850
+ feed: string;
851
+ };
852
+ query?: never;
853
+ url: '/api/v1/feeds/{feed}';
854
+ };
855
+ export type CreateFeedErrors = {
856
+ /**
857
+ * Invalid or reserved feed name, bad JSON, a password that is not 1 to 256 printable ASCII characters, with no space at the start or end, a read id that is invalid, or a key that is none
858
+ */
859
+ 400: Error;
860
+ /**
861
+ * A license key is needed to create a feed without a note here, or its license has lapsed (`license_required`)
862
+ */
863
+ 402: Error;
864
+ /**
865
+ * The feed exists already (`feed_exists`), or the read id is taken (`taken`)
866
+ */
867
+ 409: Error;
868
+ /**
869
+ * The operator removed this feed for good (`removed`): its name cannot be used again
870
+ */
871
+ 410: Error;
872
+ /**
873
+ * Too many posts, or wrong keys, from this client
874
+ */
875
+ 429: Error;
876
+ /**
877
+ * The key service is unreachable: the instance is down for maintenance; try again after Retry-After
878
+ */
879
+ 503: Error;
880
+ /**
881
+ * NOTEFEED_MAX_FEEDS reached
882
+ */
883
+ 507: Error;
884
+ };
885
+ export type CreateFeedError = CreateFeedErrors[keyof CreateFeedErrors];
886
+ export type CreateFeedResponses = {
887
+ /**
888
+ * The new feed
889
+ */
890
+ 201: Feed;
891
+ };
892
+ export type CreateFeedResponse = CreateFeedResponses[keyof CreateFeedResponses];
810
893
  export type UpdateFeedData = {
811
894
  body: FeedSettings;
812
895
  headers?: {
@@ -936,9 +1019,9 @@ export type ChangeFeedPasswordErrors = {
936
1019
  */
937
1020
  402: Error;
938
1021
  /**
939
- * The feed has no password
1022
+ * No such feed
940
1023
  */
941
- 409: Error;
1024
+ 404: Error;
942
1025
  /**
943
1026
  * Too many wrong passwords from this client
944
1027
  */
@@ -951,11 +1034,154 @@ export type ChangeFeedPasswordErrors = {
951
1034
  export type ChangeFeedPasswordError = ChangeFeedPasswordErrors[keyof ChangeFeedPasswordErrors];
952
1035
  export type ChangeFeedPasswordResponses = {
953
1036
  /**
954
- * Changed; the old password and unlock cookies stop working
1037
+ * Set or changed; the old password and unlock cookies stop working
955
1038
  */
956
1039
  204: void;
957
1040
  };
958
1041
  export type ChangeFeedPasswordResponse = ChangeFeedPasswordResponses[keyof ChangeFeedPasswordResponses];
1042
+ export type RecoverFeedPasswordData = {
1043
+ body: RecoveryJson;
1044
+ path: {
1045
+ /**
1046
+ * The feed's name. It is the write key: anyone who knows it can post.
1047
+ */
1048
+ feed: string;
1049
+ };
1050
+ query?: never;
1051
+ url: '/api/v1/feeds/{feed}/password/recover';
1052
+ };
1053
+ export type RecoverFeedPasswordErrors = {
1054
+ /**
1055
+ * 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
1056
+ */
1057
+ 400: Error;
1058
+ /**
1059
+ * The key is not this feed's license, or the feed has no password
1060
+ */
1061
+ 401: Error;
1062
+ /**
1063
+ * The key's license has lapsed, and a new password needs an active one (`license_required`)
1064
+ */
1065
+ 402: Error;
1066
+ /**
1067
+ * Licenses are not sold on this instance, or the feed was deleted meanwhile
1068
+ */
1069
+ 404: Error;
1070
+ /**
1071
+ * Too many wrong passwords or keys from this client
1072
+ */
1073
+ 429: Error;
1074
+ /**
1075
+ * The key service is unreachable: the instance is down for maintenance; try again after Retry-After
1076
+ */
1077
+ 503: Error;
1078
+ };
1079
+ export type RecoverFeedPasswordError = RecoverFeedPasswordErrors[keyof RecoverFeedPasswordErrors];
1080
+ export type RecoverFeedPasswordResponses = {
1081
+ /**
1082
+ * Set or removed; the old password and unlock cookies stop working
1083
+ */
1084
+ 204: void;
1085
+ };
1086
+ export type RecoverFeedPasswordResponse = RecoverFeedPasswordResponses[keyof RecoverFeedPasswordResponses];
1087
+ export type DeleteFeedLicenseData = {
1088
+ body?: never;
1089
+ headers?: {
1090
+ /**
1091
+ * 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.
1092
+ */
1093
+ 'X-Feed-Password'?: string;
1094
+ };
1095
+ path: {
1096
+ /**
1097
+ * The feed's name. It is the write key: anyone who knows it can post.
1098
+ */
1099
+ feed: string;
1100
+ };
1101
+ query?: never;
1102
+ url: '/api/v1/feeds/{feed}/license';
1103
+ };
1104
+ export type DeleteFeedLicenseErrors = {
1105
+ /**
1106
+ * Invalid or reserved feed name
1107
+ */
1108
+ 400: Error;
1109
+ /**
1110
+ * The instance has a password, or the feed has its own, and it is missing or wrong
1111
+ */
1112
+ 401: Error;
1113
+ /**
1114
+ * No such feed, or licenses are not sold on this instance
1115
+ */
1116
+ 404: Error;
1117
+ /**
1118
+ * Too many posts, edits and deletes, or wrong passwords, from this client
1119
+ */
1120
+ 429: Error;
1121
+ /**
1122
+ * The key service is unreachable: the instance is down for maintenance; try again after Retry-After
1123
+ */
1124
+ 503: Error;
1125
+ };
1126
+ export type DeleteFeedLicenseError = DeleteFeedLicenseErrors[keyof DeleteFeedLicenseErrors];
1127
+ export type DeleteFeedLicenseResponses = {
1128
+ /**
1129
+ * The feed as it is now
1130
+ */
1131
+ 200: Feed;
1132
+ };
1133
+ export type DeleteFeedLicenseResponse = DeleteFeedLicenseResponses[keyof DeleteFeedLicenseResponses];
1134
+ export type PutFeedLicenseData = {
1135
+ body: LicenseKey;
1136
+ headers?: {
1137
+ /**
1138
+ * 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.
1139
+ */
1140
+ 'X-Feed-Password'?: string;
1141
+ };
1142
+ path: {
1143
+ /**
1144
+ * The feed's name. It is the write key: anyone who knows it can post.
1145
+ */
1146
+ feed: string;
1147
+ };
1148
+ query?: never;
1149
+ url: '/api/v1/feeds/{feed}/license';
1150
+ };
1151
+ export type PutFeedLicenseErrors = {
1152
+ /**
1153
+ * Invalid or reserved feed name, bad JSON, a key that is none, or a feed with an active license already
1154
+ */
1155
+ 400: Error;
1156
+ /**
1157
+ * The instance has a password, or the feed has its own, and it is missing or wrong
1158
+ */
1159
+ 401: Error;
1160
+ /**
1161
+ * The key's license has lapsed (`license_required`)
1162
+ */
1163
+ 402: Error;
1164
+ /**
1165
+ * No such feed, or licenses are not sold on this instance
1166
+ */
1167
+ 404: Error;
1168
+ /**
1169
+ * Too many posts, edits and deletes, or wrong passwords and keys, from this client
1170
+ */
1171
+ 429: Error;
1172
+ /**
1173
+ * The key service is unreachable: the instance is down for maintenance; try again after Retry-After
1174
+ */
1175
+ 503: Error;
1176
+ };
1177
+ export type PutFeedLicenseError = PutFeedLicenseErrors[keyof PutFeedLicenseErrors];
1178
+ export type PutFeedLicenseResponses = {
1179
+ /**
1180
+ * The feed as it is now: `free_tier` is null
1181
+ */
1182
+ 200: Feed;
1183
+ };
1184
+ export type PutFeedLicenseResponse = PutFeedLicenseResponses[keyof PutFeedLicenseResponses];
959
1185
  export type GetReadFeedData = {
960
1186
  body?: never;
961
1187
  path: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "notefeed",
3
- "version": "0.28.0",
3
+ "version": "0.30.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",
@@ -45,7 +45,7 @@
45
45
  },
46
46
  "devDependencies": {
47
47
  "@types/node": "^20",
48
- "typescript": "^5.9",
48
+ "typescript": "^7.0",
49
49
  "vitest": "^4.1"
50
50
  }
51
51
  }