@potonz/shortlinks-manager-cf-d1 0.2.3 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,15 +1,136 @@
1
- # @potonz/shortlinks-manage-cf-d1
1
+ # @potonz/shortlinks-manager-cf-d1
2
2
 
3
- Short links manager backend using Cloudflare D1 database.
3
+ Cloudflare D1 backend for short links manager.
4
4
 
5
- ## Setting up
5
+ ## Features
6
6
 
7
- You have to manually set up the DB for short links.
8
- A function is provided for this, this should only be called once.
7
+ - Cloudflare D1 SQLite support for edge deployment
8
+ - Automatic table creation and indexing
9
+ - Perfect for Cloudflare Workers environment
10
+ - Built-in cache integration for high performance
11
+ - Lightweight and efficient SQLite operations
9
12
 
10
- ```ts
13
+ ## Installation
14
+
15
+ ```bash
16
+ bun add @potonz/shortlinks-manager-cf-d1
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ ```typescript
22
+ import { createManager } from "@potonz/shortlinks-manager";
23
+ import { createD1Backend } from "@potonz/shortlinks-manager-cf-d1";
11
24
  import { env } from "cloudflare:workers";
12
25
 
26
+ // Create backend with D1 binding
13
27
  const backend = createD1Backend(env.DB);
28
+
29
+ // Initialize tables (run once during setup)
14
30
  await backend.setupTables();
31
+
32
+ // Create manager
33
+ const manager = await createManager({
34
+ backend,
35
+ shortIdLength: 6,
36
+ onShortIdLengthUpdated: (newLength) => {
37
+ console.log(`Short ID length updated to ${newLength}`);
38
+ },
39
+ });
40
+
41
+ // Create short link
42
+ const shortId = await manager.createShortLink("https://example.com");
43
+ console.log(`Created short link: ${shortId}`);
44
+
45
+ // Resolve short link
46
+ const targetUrl = await manager.getTargetUrl(shortId);
47
+ console.log(`Target URL: ${targetUrl}`);
48
+ ```
49
+
50
+ ## Database Schema
51
+
52
+ The backend automatically creates the following table:
53
+
54
+ ```sql
55
+ CREATE TABLE IF NOT EXISTS sl_links_map (
56
+ short_id TEXT NOT NULL PRIMARY KEY,
57
+ target_url TEXT NOT NULL,
58
+ last_accessed_at INTEGER DEFAULT (strftime('%s', 'now')),
59
+ created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
60
+ );
61
+
62
+ CREATE INDEX IF NOT EXISTS idx_sl_links_map_last_accessed_at
63
+ ON sl_links_map(last_accessed_at);
64
+ ```
65
+
66
+ ## API
67
+
68
+ ### `createD1Backend(db: D1Database)`
69
+
70
+ Creates a Cloudflare D1 backend instance.
71
+
72
+ **Parameters:**
73
+ - `db` - Cloudflare D1 database binding (typically from `env.DB` in Workers)
74
+
75
+ **Returns:** `IShortLinksManagerBackend`
76
+
77
+ ### `backend.setupTables()`
78
+
79
+ Creates the required database tables and indexes. Should be called once during application initialization.
80
+
81
+ ### Backend Methods
82
+
83
+ Implements all `IShortLinksManagerBackend` methods:
84
+
85
+ - `getTargetUrl(shortId)` - Get target URL by short ID
86
+ - `createShortLink(shortId, targetUrl)` - Create a new short link
87
+ - `checkShortIdsExist(shortIds)` - Check which short IDs already exist
88
+ - `updateShortLinkLastAccessTime(shortId, time)` - Update last accessed timestamp
89
+ - `cleanUnusedLinks(maxAge)` - Remove links not accessed in `maxAge` days
90
+ - `removeShortLink(shortId)` - Remove a short link by ID
91
+
92
+ ## Cloudflare Workers Setup
93
+
94
+ 1. Configure D1 binding in `wrangler.jsonc`:
95
+
96
+ ```jsonc
97
+ {
98
+ "$schema": "node_modules/wrangler/config-schema.json",
99
+ "name": "your-worker-name",
100
+ "main": "src/index.ts",
101
+ "compatibility_date": "2025-12-25",
102
+ "d1_databases": [
103
+ {
104
+ "binding": "DB",
105
+ "database_id": "your-database-id",
106
+ "database_name": "shortlinks"
107
+ }
108
+ ]
109
+ }
110
+ ```
111
+
112
+ 2. Create D1 database:
113
+
114
+ ```bash
115
+ wrangler d1 create shortlinks
15
116
  ```
117
+
118
+ 3. Deploy with Wrangler:
119
+
120
+ ```bash
121
+ wrangler deploy
122
+ ```
123
+
124
+ ## Testing
125
+
126
+ Tests run against a local D1-compatible SQLite database.
127
+
128
+ To run tests locally:
129
+
130
+ ```bash
131
+ bun test packages/shortlinks-manager-cf-d1
132
+ ```
133
+
134
+ ## License
135
+
136
+ MIT
package/dist/index.d.ts CHANGED
@@ -92,6 +92,12 @@ declare abstract class D1PreparedStatement {
92
92
  columnNames?: false;
93
93
  }): Promise<T[]>;
94
94
  }
95
+ // Generated by dts-bundle-generator v9.5.1
96
+ export interface IBaseUrlRecord {
97
+ id: number;
98
+ baseUrl: string;
99
+ isActive?: boolean;
100
+ }
95
101
  export interface IShortLinksManagerBackend {
96
102
  /**
97
103
  * Initialise any logic before the manager can do its thing. E.g. setting up tables.
@@ -99,39 +105,74 @@ export interface IShortLinksManagerBackend {
99
105
  */
100
106
  init?: () => unknown;
101
107
  /**
102
- * Get target URL for the given short ID
103
- * @param {string} shortId
104
- * @returns the short ID or null if not found
105
- */
106
- getTargetUrl(shortId: string): string | null | Promise<string | null>;
108
+ * Get target URL for the given short ID
109
+ * @param {string} shortId
110
+ * @param {number} baseUrlId optional base URL ID to filter by
111
+ * @returns the target URL or null if not found
112
+ */
113
+ getTargetUrl(shortId: string, baseUrlId: number | null): string | null | Promise<string | null>;
107
114
  /**
108
- * Create a short link map with the given short ID and target URL
109
- * @param {string} shortId
110
- * @param {string} targetUrl
111
- */
112
- createShortLink(shortId: string, targetUrl: string): void | Promise<void>;
115
+ * Create a short link map with the given short ID and target URL
116
+ * @param {string} shortId
117
+ * @param {string} targetUrl
118
+ * @param {number} baseUrlId optional base URL ID
119
+ */
120
+ createShortLink(shortId: string, targetUrl: string, baseUrlId: number | null): void | Promise<void>;
113
121
  /**
114
- * Check the provided list of short IDs and return the ones that already exist.
115
- * @param {string[]} shortIds
116
- */
117
- checkShortIdsExist(shortIds: string[]): string[] | Promise<string[]>;
122
+ * Check the provided list of short IDs and return the ones that already exist.
123
+ * @param {string[]} shortIds
124
+ * @param {number} baseUrlId optional base URL ID to check within
125
+ */
126
+ checkShortIdsExist(shortIds: string[], baseUrlId: number | null): string[] | Promise<string[]>;
118
127
  /**
119
- * Update last accessed time to current timestamp
120
- * @param shortId
121
- * @param time Unix timestamp or a Date object
122
- */
123
- updateShortLinkLastAccessTime(shortId: string, time?: number | Date): void | Promise<void>;
128
+ * Update last accessed time to current timestamp
129
+ * @param shortId
130
+ * @param baseUrlId optional base URL ID to filter by
131
+ * @param time Unix timestamp or a Date object
132
+ */
133
+ updateShortLinkLastAccessTime(shortId: string, baseUrlId: number | null, time?: number | Date): void | Promise<void>;
124
134
  /**
125
- * Remove unused links that are older than the given maxAge
126
- * @param maxAge number of days the record should be kept
127
- * @returns an array of short IDs that have been cleaned
128
- */
129
- cleanUnusedLinks(maxAge: number): string[] | Promise<string[]>;
135
+ * Remove unused links that are older than the given maxAge
136
+ * @param maxAge number of days the record should be kept
137
+ * @returns an array of objects with shortId and baseUrlId that have been cleaned
138
+ */
139
+ cleanUnusedLinks(maxAge: number): Array<{
140
+ shortId: string;
141
+ baseUrlId: number | null;
142
+ }> | Promise<Array<{
143
+ shortId: string;
144
+ baseUrlId: number | null;
145
+ }>>;
130
146
  /**
131
- * Remove a short link by its ID
132
- * @param shortId the short ID to remove
133
- */
134
- removeShortLink(shortId: string): void | Promise<void>;
147
+ * Remove a short link by its ID
148
+ * @param shortId the short ID to remove
149
+ * @param baseUrlId optional base URL ID to filter by
150
+ */
151
+ removeShortLink(shortId: string, baseUrlId: number | null): void | Promise<void>;
152
+ baseUrl: {
153
+ /**
154
+ * Add a new base URL
155
+ * @param baseUrl the base URL to add
156
+ */
157
+ add(baseUrl: string): void | Promise<void>;
158
+ /**
159
+ * Remove a base URL by its ID
160
+ * @param id the ID of the base URL to remove
161
+ */
162
+ remove(id: number): void | Promise<void>;
163
+ /**
164
+ * List all base URLs
165
+ * @param includeInactive whether to include inactive base URLs (default: false)
166
+ * @returns array of base URL records
167
+ */
168
+ list(includeInactive?: boolean): IBaseUrlRecord[] | Promise<IBaseUrlRecord[]>;
169
+ /**
170
+ * Get the ID for a base URL
171
+ * @param baseUrl the base URL to get the ID for
172
+ * @returns the base URL ID or null if not found
173
+ */
174
+ getId(baseUrl: string): number | Promise<number>;
175
+ };
135
176
  }
136
177
  export interface IShortLinksManagerD1Backend extends IShortLinksManagerBackend {
137
178
  setupTables: () => Promise<void>;
package/dist/index.js CHANGED
@@ -33,15 +33,27 @@ WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
33
33
  MERCHANTABLITY OR NON-INFRINGEMENT.
34
34
  See the Apache Version 2.0 License for specific language governing permissions
35
35
  and limitations under the License.
36
- ***************************************************************************** */function j($){let p=$.getFullYear(),y=String($.getMonth()+1).padStart(2,"0"),F=String($.getDate()).padStart(2,"0"),H=String($.getHours()).padStart(2,"0"),T=String($.getMinutes()).padStart(2,"0"),Y=String($.getSeconds()).padStart(2,"0");return`${p}-${y}-${F} ${H}:${T}:${Y}`}function w($){let p=null,y=null,F=null,H=null,T=null,Y=null;return{async setupTables(){await $.prepare(`
36
+ ***************************************************************************** */function N(f){let x=f.getFullYear(),F=String(f.getMonth()+1).padStart(2,"0"),H=String(f.getDate()).padStart(2,"0"),S=String(f.getHours()).padStart(2,"0"),T=String(f.getMinutes()).padStart(2,"0"),Y=String(f.getSeconds()).padStart(2,"0");return`${x}-${F}-${H} ${S}:${T}:${Y}`}function Q(f){let x=null,F=null,H=null,S=null,T=null,Y=null,j=null,w=null,z=null,C=null,E=null,G=null,J=null;return{async setupTables(){await f.prepare(`
37
+ CREATE TABLE IF NOT EXISTS sl_base_urls (
38
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
39
+ base_url TEXT NOT NULL UNIQUE,
40
+ is_active INTEGER DEFAULT 1,
41
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
42
+ );
43
+
37
44
  CREATE TABLE IF NOT EXISTS sl_links_map (
38
- short_id VARCHAR(255) NOT NULL PRIMARY KEY,
45
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
46
+ short_id VARCHAR(255) NOT NULL,
39
47
  target_url VARCHAR(65535) NOT NULL,
48
+ base_url_id INTEGER,
40
49
  last_accessed_at DATETIME DEFAULT CURRENT_TIMESTAMP,
41
- created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
50
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
51
+ CONSTRAINT uk_short_id_base_url UNIQUE (short_id, base_url_id)
42
52
  );
43
53
 
44
54
  CREATE INDEX IF NOT EXISTS idx_sl_links_map_last_accessed_at ON sl_links_map(last_accessed_at);
55
+ CREATE INDEX IF NOT EXISTS idx_sl_links_map_base_url_id ON sl_links_map(base_url_id);
56
+ CREATE INDEX IF NOT EXISTS idx_sl_links_map_short_id_base_url ON sl_links_map(short_id, base_url_id);
45
57
 
46
58
  PRAGMA optimize;
47
- `).run()},async getTargetUrl(f){if(!p)p=$.prepare("SELECT target_url FROM sl_links_map WHERE short_id = ? LIMIT 1");return(await p.bind(f).first())?.target_url??null},async createShortLink(f,g){if(!F)F=$.prepare("INSERT INTO sl_links_map (short_id, target_url) VALUES (?, ?)");await F.bind(f,g).run()},async checkShortIdsExist(f){if(!y){let D=Array.from("?".repeat(f.length)).join(",");y=$.prepare(`SELECT short_id FROM sl_links_map WHERE short_id IN (${D})`)}let g=await y.bind(...f).all();if(!g.success)return[];return g.results.map((D)=>D.short_id)},async updateShortLinkLastAccessTime(f,g=new Date){if(!H)H=$.prepare("UPDATE sl_links_map SET last_accessed_at = ? WHERE short_id = ?");let D=g;if(typeof D==="number")D=new Date(D);await H.bind(j(D),f).run()},async cleanUnusedLinks(f){if(!T)T=$.prepare("DELETE FROM sl_links_map WHERE last_accessed_at < datetime(CURRENT_TIMESTAMP, ?) RETURNING short_id");return(await T.bind(`-${f} days`).all()).results.map((D)=>D.short_id)},async removeShortLink(f){if(!Y)Y=$.prepare("DELETE FROM sl_links_map WHERE short_id = ?");await Y.bind(f).run()}}}var C={};export{C as default,w as createD1Backend};
59
+ `).run()},async getTargetUrl($,D){if(!G)G=f.prepare("SELECT target_url FROM sl_links_map WHERE short_id = ? AND base_url_id IS NULL LIMIT 1");if(!J)J=f.prepare("SELECT target_url FROM sl_links_map WHERE short_id = ? AND base_url_id = ? LIMIT 1");return(D===null?await G.bind($).first():await J.bind($,D).first())?.target_url??null},async createShortLink($,D,g){if(!x)x=f.prepare("INSERT INTO sl_links_map (short_id, target_url, base_url_id) VALUES (?, ?, ?)");await x.bind($,D,g).run()},async checkShortIdsExist($,D){let g=Array.from("?".repeat($.length)).join(","),p;if(D===null)p=await f.prepare(`SELECT short_id FROM sl_links_map WHERE short_id IN (${g}) AND base_url_id IS NULL`).bind(...$).all();else p=await f.prepare(`SELECT short_id FROM sl_links_map WHERE short_id IN (${g}) AND base_url_id = ?`).bind(...$,D).all();return p.results.map((K)=>K.short_id)},async updateShortLinkLastAccessTime($,D,g=new Date){if(!F)F=f.prepare("UPDATE sl_links_map SET last_accessed_at = ? WHERE short_id = ? AND base_url_id IS NULL");if(!H)H=f.prepare("UPDATE sl_links_map SET last_accessed_at = ? WHERE short_id = ? AND base_url_id = ?");let p=g;if(typeof p==="number")p=new Date(p);if(D===null)await F.bind(N(p),$).run();else await H.bind(N(p),$,D).run()},async cleanUnusedLinks($){if(!S)S=f.prepare("DELETE FROM sl_links_map WHERE last_accessed_at < datetime(CURRENT_TIMESTAMP, ?) RETURNING short_id, base_url_id");return(await S.bind(`-${$} days`).all()).results.map((g)=>({shortId:g.short_id,baseUrlId:g.base_url_id}))},async removeShortLink($,D){if(!T)T=f.prepare("DELETE FROM sl_links_map WHERE short_id = ? AND base_url_id IS NULL");if(!Y)Y=f.prepare("DELETE FROM sl_links_map WHERE short_id = ? AND base_url_id = ?");if(D===null)await T.bind($).run();else await Y.bind($,D).run()},baseUrl:{async add($){if(!j)j=f.prepare("INSERT OR IGNORE INTO sl_base_urls (base_url) VALUES (?)");await j.bind($).run()},async remove($){if(!w)w=f.prepare("UPDATE sl_base_urls SET is_active = 0 WHERE id = ?");await w.bind($).run()},async list($){if(!z)z=f.prepare("SELECT id, base_url, is_active FROM sl_base_urls WHERE is_active = 1");if(!C)C=f.prepare("SELECT id, base_url, is_active FROM sl_base_urls WHERE 1=1");let D;if($)D=await C.all();else D=await z.all();return D.results.map((g)=>({id:g.id,baseUrl:g.base_url,isActive:g.is_active===1}))},async getId($){if(!E)E=f.prepare("SELECT id FROM sl_base_urls WHERE base_url = ? LIMIT 1");let D=await E.bind($).first();if(!D)throw Error(`Base URL not found: ${$}`);return D.id}}}}var Z={};export{Z as default,Q as createD1Backend};
package/package.json CHANGED
@@ -11,11 +11,11 @@
11
11
  "type": "git",
12
12
  "directory": "packages/shortlinks-manager-cf-d1"
13
13
  },
14
- "version": "0.2.3",
14
+ "version": "0.3.1",
15
15
  "type": "module",
16
16
  "license": "MIT",
17
17
  "dependencies": {
18
- "@potonz/shortlinks-manager": "0.1.1"
18
+ "@potonz/shortlinks-manager": "0.2.3"
19
19
  },
20
20
  "main": "./dist/index.js",
21
21
  "module": "./dist/index.js",