@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 +127 -6
- package/dist/index.d.ts +69 -28
- package/dist/index.js +16 -4
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,15 +1,136 @@
|
|
|
1
|
-
# @potonz/shortlinks-
|
|
1
|
+
# @potonz/shortlinks-manager-cf-d1
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Cloudflare D1 backend for short links manager.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Features
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
cleanUnusedLinks(maxAge: number):
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
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.
|
|
14
|
+
"version": "0.3.1",
|
|
15
15
|
"type": "module",
|
|
16
16
|
"license": "MIT",
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@potonz/shortlinks-manager": "0.
|
|
18
|
+
"@potonz/shortlinks-manager": "0.2.3"
|
|
19
19
|
},
|
|
20
20
|
"main": "./dist/index.js",
|
|
21
21
|
"module": "./dist/index.js",
|