spooder 6.1.4 → 6.1.6
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 +46 -30
- package/bun.lock +4 -7
- package/package.json +1 -1
- package/src/api.ts +23 -5
package/README.md
CHANGED
|
@@ -601,6 +601,9 @@ log_list(input: any[], delimiter = ', ');
|
|
|
601
601
|
http_serve(port: number, hostname?: string): Server;
|
|
602
602
|
server.stop(immediate: boolean): Promise<void>;
|
|
603
603
|
|
|
604
|
+
// cookies
|
|
605
|
+
cookies_get(req: Request): Bun.CookieMap
|
|
606
|
+
|
|
604
607
|
// routing
|
|
605
608
|
server.route(path: string, handler: RequestHandler, method?: HTTP_METHODS);
|
|
606
609
|
server.json(path: string, handler: JSONRequestHandler, method?: HTTP_METHODS);
|
|
@@ -680,6 +683,7 @@ git_get_hashes_sync(length: number): Record<string, string>;
|
|
|
680
683
|
// database utilities
|
|
681
684
|
db_set_cast<T extends string>(set: string | null): Set<T>;
|
|
682
685
|
db_set_serialize<T extends string>(set: Iterable<T> | null): string;
|
|
686
|
+
db_exists(db: SQL, table_name: string, value: string|number, column_name = 'id'): Promise<boolean>;
|
|
683
687
|
|
|
684
688
|
// database schema
|
|
685
689
|
type SchemaOptions = {
|
|
@@ -1417,35 +1421,6 @@ server.websocket('/path/to/websocket', {
|
|
|
1417
1421
|
> [!IMPORTANT]
|
|
1418
1422
|
> While it is possible to register multiple routes for websockets, the only handler which is unique per route is `accept()`. The last handlers provided to any route (with the exception of `accept()`) will apply to ALL websocket routes. This is a limitation in Bun.
|
|
1419
1423
|
|
|
1420
|
-
<a id="api-http-cookies"></a>
|
|
1421
|
-
## API > HTTP > Cookies
|
|
1422
|
-
|
|
1423
|
-
### 🔧 `cookies_get(req: Request): Bun.CookieMap`
|
|
1424
|
-
|
|
1425
|
-
When called on a request, the `cookies_get` function will return a `Bun.CookieMap` contains all of the cookies parsed from the `Cookie` header on the request.
|
|
1426
|
-
|
|
1427
|
-
```ts
|
|
1428
|
-
server.route('/', (req, url) => {
|
|
1429
|
-
const cookies = cookies_get(req);
|
|
1430
|
-
return `Hello ${cookies.get('person') ?? 'unknown'}`;
|
|
1431
|
-
});
|
|
1432
|
-
```
|
|
1433
|
-
|
|
1434
|
-
The return `Bun.CookieMap` is an iterable map with a custom API for reading/setting cookies. The full API [can be seen here](https://bun.com/docs/runtime/cookies).
|
|
1435
|
-
|
|
1436
|
-
Any changes made to the cookie map (adding, deletion, editing, etc) will be sent as `Set-Cookie` headers on the response automatically. Unchanged cookies are not sent.
|
|
1437
|
-
|
|
1438
|
-
```ts
|
|
1439
|
-
server.route('/', (req, url) => {
|
|
1440
|
-
const cookies = cookies_get(req);
|
|
1441
|
-
cookies.set('test', 'foobar');
|
|
1442
|
-
return 'Hello, world!';
|
|
1443
|
-
|
|
1444
|
-
// the response automatically gets:
|
|
1445
|
-
// Set-Cookie test=foobar; Path=/; SameSite=Lax
|
|
1446
|
-
});
|
|
1447
|
-
```
|
|
1448
|
-
|
|
1449
1424
|
<a id="api-http-bootstrap"></a>
|
|
1450
1425
|
## API > HTTP > Bootstrap
|
|
1451
1426
|
|
|
@@ -1778,6 +1753,35 @@ Functions in `global_subs` and route-specific `subs` are called during template
|
|
|
1778
1753
|
3. Route-specific `subs` and `global_subs` are applied
|
|
1779
1754
|
4. Hash substitutions (if enabled) are applied
|
|
1780
1755
|
|
|
1756
|
+
<a id="api-http-cookies"></a>
|
|
1757
|
+
## API > HTTP > Cookies
|
|
1758
|
+
|
|
1759
|
+
### 🔧 `cookies_get(req: Request): Bun.CookieMap`
|
|
1760
|
+
|
|
1761
|
+
When called on a request, the `cookies_get` function will return a `Bun.CookieMap` contains all of the cookies parsed from the `Cookie` header on the request.
|
|
1762
|
+
|
|
1763
|
+
```ts
|
|
1764
|
+
server.route('/', (req, url) => {
|
|
1765
|
+
const cookies = cookies_get(req);
|
|
1766
|
+
return `Hello ${cookies.get('person') ?? 'unknown'}`;
|
|
1767
|
+
});
|
|
1768
|
+
```
|
|
1769
|
+
|
|
1770
|
+
The return `Bun.CookieMap` is an iterable map with a custom API for reading/setting cookies. The full API [can be seen here](https://bun.com/docs/runtime/cookies).
|
|
1771
|
+
|
|
1772
|
+
Any changes made to the cookie map (adding, deletion, editing, etc) will be sent as `Set-Cookie` headers on the response automatically. Unchanged cookies are not sent.
|
|
1773
|
+
|
|
1774
|
+
```ts
|
|
1775
|
+
server.route('/', (req, url) => {
|
|
1776
|
+
const cookies = cookies_get(req);
|
|
1777
|
+
cookies.set('test', 'foobar');
|
|
1778
|
+
return 'Hello, world!';
|
|
1779
|
+
|
|
1780
|
+
// the response automatically gets:
|
|
1781
|
+
// Set-Cookie test=foobar; Path=/; SameSite=Lax
|
|
1782
|
+
});
|
|
1783
|
+
```
|
|
1784
|
+
|
|
1781
1785
|
<a id="api-error-handling"></a>
|
|
1782
1786
|
## API > Error Handling
|
|
1783
1787
|
|
|
@@ -2589,7 +2593,7 @@ if (set.has(Fruits.Apple)) {
|
|
|
2589
2593
|
}
|
|
2590
2594
|
```
|
|
2591
2595
|
|
|
2592
|
-
###
|
|
2596
|
+
### ``db_set_serialize<T extends string>(set: Iterable<T> | null): string``
|
|
2593
2597
|
|
|
2594
2598
|
Takes an `Iterable<T>` and returns a database SET string. If the set is empty or `null`, it returns an empty string.
|
|
2595
2599
|
|
|
@@ -2613,6 +2617,18 @@ await sql`UPDATE some_table SET fruits = ${sql(db_set_serialize(fruits))} WHERE
|
|
|
2613
2617
|
await sql`UPDATE some_table SET fruits = ${sql(db_set_serialize([Fruits.Apple, Fruits.Lemon]))}`;
|
|
2614
2618
|
```
|
|
2615
2619
|
|
|
2620
|
+
### 🔧 ``db_exists(db: SQL, table_name: string, value: string|number, column_name = 'id'): Promise<boolean>``
|
|
2621
|
+
|
|
2622
|
+
Returns true if a database row exists in the table.
|
|
2623
|
+
|
|
2624
|
+
```ts
|
|
2625
|
+
// checks if row exists with id 1 in 'table'
|
|
2626
|
+
const exists = await db_exists(db, 'table', 1);
|
|
2627
|
+
|
|
2628
|
+
// checks if row exists with column 'foo' = 'bar' in 'table'
|
|
2629
|
+
const exists = await db_exists(db, 'table', 'bar', 'foo');
|
|
2630
|
+
```
|
|
2631
|
+
|
|
2616
2632
|
<a id="api-database-schema"></a>
|
|
2617
2633
|
## API > Database > Schema
|
|
2618
2634
|
|
package/bun.lock
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"lockfileVersion": 1,
|
|
3
|
+
"configVersion": 1,
|
|
3
4
|
"workspaces": {
|
|
4
5
|
"": {
|
|
5
6
|
"name": "spooder",
|
|
@@ -9,15 +10,11 @@
|
|
|
9
10
|
},
|
|
10
11
|
},
|
|
11
12
|
"packages": {
|
|
12
|
-
"@types/bun": ["@types/bun@1.3.
|
|
13
|
+
"@types/bun": ["@types/bun@1.3.3", "", { "dependencies": { "bun-types": "1.3.3" } }, "sha512-ogrKbJ2X5N0kWLLFKeytG0eHDleBYtngtlbu9cyBKFtNL3cnpDZkNdQj8flVf6WTZUX5ulI9AY1oa7ljhSrp+g=="],
|
|
13
14
|
|
|
14
|
-
"@types/node": ["@types/node@24.
|
|
15
|
+
"@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="],
|
|
15
16
|
|
|
16
|
-
"
|
|
17
|
-
|
|
18
|
-
"bun-types": ["bun-types@1.3.1", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-NMrcy7smratanWJ2mMXdpatalovtxVggkj11bScuWuiOoXTiKIu2eVS1/7qbyI/4yHedtsn175n4Sm4JcdHLXw=="],
|
|
19
|
-
|
|
20
|
-
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
|
|
17
|
+
"bun-types": ["bun-types@1.3.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-z3Xwlg7j2l9JY27x5Qn3Wlyos8YAp0kKRlrePAOjgjMGS5IG6E7Jnlx736vH9UVI4wUICwwhC9anYL++XeOgTQ=="],
|
|
21
18
|
|
|
22
19
|
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
|
|
23
20
|
}
|
package/package.json
CHANGED
package/src/api.ts
CHANGED
|
@@ -1841,24 +1841,37 @@ export function http_serve(port: number, hostname?: string) {
|
|
|
1841
1841
|
/** Register a JSON endpoint with automatic content validation. */
|
|
1842
1842
|
json: (path: string, handler: JSONRequestHandler, method: HTTP_METHODS = 'POST'): void => {
|
|
1843
1843
|
const json_wrapper: RequestHandler = async (req: Request, url: URL) => {
|
|
1844
|
+
// handle CORS preflight
|
|
1845
|
+
if (req.method === 'OPTIONS') {
|
|
1846
|
+
return new Response(null, {
|
|
1847
|
+
status: 204,
|
|
1848
|
+
headers: {
|
|
1849
|
+
'Access-Control-Allow-Origin': '*',
|
|
1850
|
+
'Access-Control-Allow-Methods': `${Array.isArray(method) ? method.join(', ') : method}, OPTIONS`,
|
|
1851
|
+
'Access-Control-Allow-Headers': 'Content-Type, User-Agent'
|
|
1852
|
+
}
|
|
1853
|
+
});
|
|
1854
|
+
}
|
|
1855
|
+
|
|
1844
1856
|
try {
|
|
1845
1857
|
if (req.headers.get('Content-Type') !== 'application/json')
|
|
1846
1858
|
return 400; // Bad Request
|
|
1847
|
-
|
|
1859
|
+
|
|
1848
1860
|
const json = await req.json();
|
|
1849
1861
|
if (json === null || typeof json !== 'object' || Array.isArray(json))
|
|
1850
1862
|
return 400; // Bad Request
|
|
1851
|
-
|
|
1863
|
+
|
|
1852
1864
|
return handler(req, url, json as JsonObject);
|
|
1853
1865
|
} catch (e) {
|
|
1854
1866
|
return 400; // Bad Request
|
|
1855
1867
|
}
|
|
1856
1868
|
};
|
|
1857
|
-
|
|
1869
|
+
|
|
1858
1870
|
if (path.length > 1 && path.endsWith('/'))
|
|
1859
1871
|
path = path.slice(0, -1);
|
|
1860
|
-
|
|
1861
|
-
|
|
1872
|
+
|
|
1873
|
+
const methods: HTTP_METHODS = Array.isArray(method) ? [...method, 'OPTIONS'] : [method, 'OPTIONS'];
|
|
1874
|
+
routes.push([path.split('/'), json_wrapper, methods]);
|
|
1862
1875
|
},
|
|
1863
1876
|
|
|
1864
1877
|
/** Unregister a specific route */
|
|
@@ -2182,6 +2195,11 @@ export function db_set_serialize<T extends string>(set: Iterable<T> | null): str
|
|
|
2182
2195
|
return set ? Array.from(set).join(',') : '';
|
|
2183
2196
|
}
|
|
2184
2197
|
|
|
2198
|
+
export async function db_exists(db: SQL, table_name: string, value: string|number, column_name = 'id'): Promise<boolean> {
|
|
2199
|
+
const rows = await db`SELECT 1 FROM ${db(table_name)} WHERE ${db(column_name)} = ${value} LIMIT 1`;
|
|
2200
|
+
return rows.length > 0;
|
|
2201
|
+
}
|
|
2202
|
+
|
|
2185
2203
|
export async function db_get_schema_revision(db: SQL): Promise<number|null> {
|
|
2186
2204
|
try {
|
|
2187
2205
|
const [result] = await db`SELECT MAX(revision_number) as latest_revision FROM db_schema`;
|