folderblog 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,88 @@
1
+ # folderblog
2
+
3
+ TypeScript SDK for **[folder.blog](https://folder.blog)** — git-native blogging.
4
+
5
+ Zero dependencies. Works in Node.js, Deno, Bun, and browsers.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install folderblog
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```typescript
16
+ import { folderBlog } from 'folderblog';
17
+
18
+ const blog = folderBlog('yourname.folder.blog');
19
+
20
+ const posts = await blog.posts.list();
21
+ const post = await blog.posts.get('hello-world');
22
+ const site = await blog.site.get();
23
+ ```
24
+
25
+ ## API
26
+
27
+ ### `blog.posts.list()`
28
+
29
+ ```typescript
30
+ const posts = await blog.posts.list();
31
+
32
+ posts[0].slug // "hello-world"
33
+ posts[0].title // "Hello World"
34
+ posts[0].date // "2024-01-15"
35
+ posts[0].tags // ["intro"]
36
+ posts[0].excerpt // "My first post..."
37
+ posts[0].url // "/api/posts/hello-world"
38
+ ```
39
+
40
+ ### `blog.posts.get(slug)`
41
+
42
+ ```typescript
43
+ const post = await blog.posts.get('hello-world');
44
+
45
+ post.content // "<p>Full HTML here...</p>"
46
+ post.raw // "# Hello World\n\nFull markdown..."
47
+ post.images // ["/posts/images/photo.jpg"]
48
+ ```
49
+
50
+ ### `blog.site.get()`
51
+
52
+ ```typescript
53
+ const site = await blog.site.get();
54
+
55
+ site.site // "My Blog"
56
+ site.variant // "light" | "dark" | "serif" | "mono"
57
+ site.postsCount // 12
58
+ site.url // "https://yourname.folder.blog"
59
+ ```
60
+
61
+ ## Error Handling
62
+
63
+ ```typescript
64
+ import { folderBlog, NotFoundError } from 'folderblog';
65
+
66
+ try {
67
+ await blog.posts.get('missing');
68
+ } catch (error) {
69
+ if (error instanceof NotFoundError) {
70
+ // 404
71
+ }
72
+ }
73
+ ```
74
+
75
+ ## Types
76
+
77
+ ```typescript
78
+ import type { Post, PostSummary, Site } from 'folderblog';
79
+ ```
80
+
81
+ ## Links
82
+
83
+ - **Website:** [folder.blog](https://folder.blog)
84
+ - **Docs:** [folder.blog/docs](https://folder.blog/docs)
85
+
86
+ ## License
87
+
88
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ "use strict";var d=Object.defineProperty;var g=Object.getOwnPropertyDescriptor;var f=Object.getOwnPropertyNames;var w=Object.prototype.hasOwnProperty;var y=(r,t)=>{for(var s in t)d(r,s,{get:t[s],enumerable:!0})},P=(r,t,s,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of f(t))!w.call(r,o)&&o!==s&&d(r,o,{get:()=>t[o],enumerable:!(i=g(t,o))||i.enumerable});return r};var x=r=>P(d({},"__esModule",{value:!0}),r);var F={};y(F,{ApiError:()=>c,FolderBlogError:()=>n,NetworkError:()=>p,NotFoundError:()=>a,folderBlog:()=>h});module.exports=x(F);var n=class extends Error{status;url;constructor(t,s){super(t),this.name="FolderBlogError",this.status=s?.status,this.url=s?.url}},a=class extends n{constructor(t,s,i){super(`${t} not found: ${s}`,{status:404,url:i}),this.name="NotFoundError"}},c=class extends n{constructor(t,s,i){super(t,{status:s,url:i}),this.name="ApiError"}},p=class extends n{cause;constructor(t,s){super(t),this.name="NetworkError",this.cause=s}};function E(r){let t=r.trim();return t.endsWith("/")&&(t=t.slice(0,-1)),!t.startsWith("http://")&&!t.startsWith("https://")&&(t=`https://${t}`),t}function h(r,t={}){let s=E(r),i=t.fetch??globalThis.fetch;async function o(l){let e=`${s}${l}`,u;try{u=await i(e,{method:"GET",headers:{Accept:"application/json"}})}catch(m){throw new p(`Failed to fetch ${e}`,m instanceof Error?m:void 0)}if(!u.ok)throw u.status===404?new a("Resource",l,e):new c(`API request failed: ${u.statusText}`,u.status,e);try{return await u.json()}catch{throw new c("Failed to parse JSON response",u.status,e)}}return{posts:{async list(){return(await o("/api/posts")).posts},async get(l){if(!l)throw new n("Post slug is required");try{return await o(`/api/posts/${encodeURIComponent(l)}`)}catch(e){throw e instanceof a?new a("Post",l,e.url):e}}},site:{async get(){return o("/api")}}}}0&&(module.exports={ApiError,FolderBlogError,NetworkError,NotFoundError,folderBlog});
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Base post fields shared between list and detail responses
3
+ */
4
+ interface PostBase {
5
+ slug: string;
6
+ title: string;
7
+ date: string;
8
+ tags: string[];
9
+ excerpt: string;
10
+ }
11
+ /**
12
+ * Post summary in list responses
13
+ */
14
+ interface PostSummary extends PostBase {
15
+ url: string;
16
+ }
17
+ /**
18
+ * Full post from detail endpoint
19
+ */
20
+ interface Post extends PostBase {
21
+ content: string;
22
+ raw: string;
23
+ images: string[];
24
+ }
25
+ /**
26
+ * Site information
27
+ */
28
+ interface Site {
29
+ site: string;
30
+ variant: "light" | "dark" | "serif" | "mono";
31
+ postsCount: number;
32
+ url: string;
33
+ }
34
+ /**
35
+ * Client options
36
+ */
37
+ interface FolderBlogOptions {
38
+ fetch?: typeof fetch;
39
+ }
40
+ /**
41
+ * The FolderBlog client
42
+ */
43
+ interface FolderBlogClient {
44
+ posts: {
45
+ list(): Promise<PostSummary[]>;
46
+ get(slug: string): Promise<Post>;
47
+ };
48
+ site: {
49
+ get(): Promise<Site>;
50
+ };
51
+ }
52
+
53
+ /**
54
+ * Create a FolderBlog client
55
+ *
56
+ * @example
57
+ * ```ts
58
+ * const blog = folderBlog('yourname.folder.blog');
59
+ * const posts = await blog.posts.list();
60
+ * const post = await blog.posts.get('hello-world');
61
+ * const site = await blog.site.get();
62
+ * ```
63
+ */
64
+ declare function folderBlog(domain: string, options?: FolderBlogOptions): FolderBlogClient;
65
+
66
+ /**
67
+ * Base error class for folder-blog SDK errors
68
+ */
69
+ declare class FolderBlogError extends Error {
70
+ /** HTTP status code if applicable */
71
+ readonly status?: number;
72
+ /** The URL that was requested */
73
+ readonly url?: string;
74
+ constructor(message: string, options?: {
75
+ status?: number;
76
+ url?: string;
77
+ });
78
+ }
79
+ /**
80
+ * Error thrown when a resource is not found (404)
81
+ */
82
+ declare class NotFoundError extends FolderBlogError {
83
+ constructor(resource: string, identifier: string, url?: string);
84
+ }
85
+ /**
86
+ * Error thrown when the API request fails
87
+ */
88
+ declare class ApiError extends FolderBlogError {
89
+ constructor(message: string, status: number, url?: string);
90
+ }
91
+ /**
92
+ * Error thrown when network/fetch fails
93
+ */
94
+ declare class NetworkError extends FolderBlogError {
95
+ readonly cause?: Error;
96
+ constructor(message: string, cause?: Error);
97
+ }
98
+
99
+ export { ApiError, type FolderBlogClient, FolderBlogError, type FolderBlogOptions, NetworkError, NotFoundError, type Post, type PostSummary, type Site, folderBlog };
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Base post fields shared between list and detail responses
3
+ */
4
+ interface PostBase {
5
+ slug: string;
6
+ title: string;
7
+ date: string;
8
+ tags: string[];
9
+ excerpt: string;
10
+ }
11
+ /**
12
+ * Post summary in list responses
13
+ */
14
+ interface PostSummary extends PostBase {
15
+ url: string;
16
+ }
17
+ /**
18
+ * Full post from detail endpoint
19
+ */
20
+ interface Post extends PostBase {
21
+ content: string;
22
+ raw: string;
23
+ images: string[];
24
+ }
25
+ /**
26
+ * Site information
27
+ */
28
+ interface Site {
29
+ site: string;
30
+ variant: "light" | "dark" | "serif" | "mono";
31
+ postsCount: number;
32
+ url: string;
33
+ }
34
+ /**
35
+ * Client options
36
+ */
37
+ interface FolderBlogOptions {
38
+ fetch?: typeof fetch;
39
+ }
40
+ /**
41
+ * The FolderBlog client
42
+ */
43
+ interface FolderBlogClient {
44
+ posts: {
45
+ list(): Promise<PostSummary[]>;
46
+ get(slug: string): Promise<Post>;
47
+ };
48
+ site: {
49
+ get(): Promise<Site>;
50
+ };
51
+ }
52
+
53
+ /**
54
+ * Create a FolderBlog client
55
+ *
56
+ * @example
57
+ * ```ts
58
+ * const blog = folderBlog('yourname.folder.blog');
59
+ * const posts = await blog.posts.list();
60
+ * const post = await blog.posts.get('hello-world');
61
+ * const site = await blog.site.get();
62
+ * ```
63
+ */
64
+ declare function folderBlog(domain: string, options?: FolderBlogOptions): FolderBlogClient;
65
+
66
+ /**
67
+ * Base error class for folder-blog SDK errors
68
+ */
69
+ declare class FolderBlogError extends Error {
70
+ /** HTTP status code if applicable */
71
+ readonly status?: number;
72
+ /** The URL that was requested */
73
+ readonly url?: string;
74
+ constructor(message: string, options?: {
75
+ status?: number;
76
+ url?: string;
77
+ });
78
+ }
79
+ /**
80
+ * Error thrown when a resource is not found (404)
81
+ */
82
+ declare class NotFoundError extends FolderBlogError {
83
+ constructor(resource: string, identifier: string, url?: string);
84
+ }
85
+ /**
86
+ * Error thrown when the API request fails
87
+ */
88
+ declare class ApiError extends FolderBlogError {
89
+ constructor(message: string, status: number, url?: string);
90
+ }
91
+ /**
92
+ * Error thrown when network/fetch fails
93
+ */
94
+ declare class NetworkError extends FolderBlogError {
95
+ readonly cause?: Error;
96
+ constructor(message: string, cause?: Error);
97
+ }
98
+
99
+ export { ApiError, type FolderBlogClient, FolderBlogError, type FolderBlogOptions, NetworkError, NotFoundError, type Post, type PostSummary, type Site, folderBlog };
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ var o=class extends Error{status;url;constructor(t,r){super(t),this.name="FolderBlogError",this.status=r?.status,this.url=r?.url}},a=class extends o{constructor(t,r,u){super(`${t} not found: ${r}`,{status:404,url:u}),this.name="NotFoundError"}},l=class extends o{constructor(t,r,u){super(t,{status:r,url:u}),this.name="ApiError"}},c=class extends o{cause;constructor(t,r){super(t),this.name="NetworkError",this.cause=r}};function m(e){let t=e.trim();return t.endsWith("/")&&(t=t.slice(0,-1)),!t.startsWith("http://")&&!t.startsWith("https://")&&(t=`https://${t}`),t}function h(e,t={}){let r=m(e),u=t.fetch??globalThis.fetch;async function p(n){let s=`${r}${n}`,i;try{i=await u(s,{method:"GET",headers:{Accept:"application/json"}})}catch(d){throw new c(`Failed to fetch ${s}`,d instanceof Error?d:void 0)}if(!i.ok)throw i.status===404?new a("Resource",n,s):new l(`API request failed: ${i.statusText}`,i.status,s);try{return await i.json()}catch{throw new l("Failed to parse JSON response",i.status,s)}}return{posts:{async list(){return(await p("/api/posts")).posts},async get(n){if(!n)throw new o("Post slug is required");try{return await p(`/api/posts/${encodeURIComponent(n)}`)}catch(s){throw s instanceof a?new a("Post",n,s.url):s}}},site:{async get(){return p("/api")}}}}export{l as ApiError,o as FolderBlogError,c as NetworkError,a as NotFoundError,h as folderBlog};
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "folderblog",
3
+ "version": "0.0.1",
4
+ "description": "SDK for consuming folder.blog content APIs",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsup",
21
+ "dev": "tsup --watch",
22
+ "test": "vitest run",
23
+ "test:watch": "vitest",
24
+ "test:coverage": "vitest run --coverage",
25
+ "typecheck": "tsc --noEmit",
26
+ "prepublishOnly": "pnpm build"
27
+ },
28
+ "keywords": [
29
+ "folderblog",
30
+ "blog",
31
+ "cms",
32
+ "headless",
33
+ "api",
34
+ "sdk",
35
+ "markdown",
36
+ "static-site"
37
+ ],
38
+ "author": "",
39
+ "license": "MIT",
40
+ "homepage": "https://folder.blog",
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "https://github.com/anthropics/folderblog"
44
+ },
45
+ "sideEffects": false,
46
+ "devDependencies": {
47
+ "tsup": "^8.0.0",
48
+ "typescript": "^5.7.2",
49
+ "vitest": "^2.0.0"
50
+ },
51
+ "engines": {
52
+ "node": ">=18.0.0"
53
+ }
54
+ }