weifuwu 0.15.0 → 0.16.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 +180 -0
- package/dist/helmet.d.ts +18 -0
- package/dist/iii/stream.d.ts +1 -0
- package/dist/iii/types.d.ts +2 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +298 -69
- package/dist/request-id.d.ts +6 -0
- package/dist/seo.d.ts +39 -0
- package/dist/types.d.ts +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -28,6 +28,8 @@ Everything follows the same `(req, ctx) => Response` contract. The Router handle
|
|
|
28
28
|
- **Data** — Redis client, job queue with cron scheduling
|
|
29
29
|
- **Multi-tenant BaaS** — dynamic tables, auto REST + GraphQL, row-level isolation
|
|
30
30
|
- **Deploy** — self-hosted PaaS: multi-app proxy, zero-downtime updates, auto SSL
|
|
31
|
+
- **Security** — `helmet()` security headers, request ID tracing, rate limiting, CORS, auth
|
|
32
|
+
- **SEO** — `robots.txt`, `sitemap.xml`, `X-Robots-Tag` middleware, `seoTags()` for meta / OG / Twitter Card
|
|
31
33
|
- **i18n** — locale detection, JSON translations, `ctx.t()`
|
|
32
34
|
- **Email** — SMTP or custom transport
|
|
33
35
|
- **Health check** — configurable `/health` endpoint
|
|
@@ -106,6 +108,7 @@ All use the same pattern — `const m = module(options)` → `app.use('/path', m
|
|
|
106
108
|
| `graphql(handler)` | GraphQL endpoint | — |
|
|
107
109
|
| `logdb(options)` | Structured event logging | `log()`, `migrate()`, `clean()`, `close()` |
|
|
108
110
|
| `health(options?)` | Health check | — |
|
|
111
|
+
| `seo(options?)` | `robots.txt`, `sitemap.xml`, indexing control | `seoMiddleware()`, `seoTags()` |
|
|
109
112
|
| `iii(options?)` | Worker/Function/Trigger service paradigm | `migrate()`, `trigger()`, `addWorker()`, `listWorkers()`, `listFunctions()`, `listTriggers()`, `shutdown()` |
|
|
110
113
|
| `registerWorker(url)` | Pure WebSocket SDK (browser/Node) | `registerFunction()`, `registerTrigger()`, `trigger()`, `shutdown()` |
|
|
111
114
|
|
|
@@ -121,6 +124,9 @@ All use the same pattern — `const m = module(options)` → `app.use('/path', m
|
|
|
121
124
|
| `validate(schemas)` | Zod validation (body, query, params) |
|
|
122
125
|
| `upload(options?)` | Multipart file upload |
|
|
123
126
|
| `i18n(options)` | Internationalization — `ctx.t()`, locale detection |
|
|
127
|
+
| `seoMiddleware(options?)` | `X-Robots-Tag` header — string or path-based function |
|
|
128
|
+
| `helmet(options?)` | Security headers — CSP, HSTS, X-Frame-Options, etc. |
|
|
129
|
+
| `requestId(options?)` | `X-Request-ID` header + `ctx.requestId` |
|
|
124
130
|
|
|
125
131
|
## Utility functions
|
|
126
132
|
|
|
@@ -131,6 +137,10 @@ All use the same pattern — `const m = module(options)` → `app.use('/path', m
|
|
|
131
137
|
| `getCookies(req)` / `setCookie(res, ...)` / `deleteCookie(res, ...)` | Cookie helpers |
|
|
132
138
|
| `mailer(options)` | Email sender (SMTP or custom) |
|
|
133
139
|
| `createTestServer(handler)` | Start test server → `{ server, url }` |
|
|
140
|
+
| `seoTags(config)` | Generate `<title>`, `<meta>`, Open Graph, Twitter Card, canonical tags |
|
|
141
|
+
| `createSSEStream(iterable, opts?)` | SSE response from `AsyncIterable` |
|
|
142
|
+
| `formatSSE(event, data)` | Format SSE event string |
|
|
143
|
+
| `formatSSEData(data)` | Format SSE data string |
|
|
134
144
|
| `runWorkflow(options)` | DAG execution engine as AI SDK `Tool` |
|
|
135
145
|
| `pgTable(name, columns)` | Type-safe table schema builder |
|
|
136
146
|
| `pg.table(name, columns)` | Pre-bound table (no `sql` param needed) |
|
|
@@ -1485,6 +1495,176 @@ const oc = await opencode({
|
|
|
1485
1495
|
|
|
1486
1496
|
---
|
|
1487
1497
|
|
|
1498
|
+
# SEO
|
|
1499
|
+
|
|
1500
|
+
Built-in SEO module — `robots.txt`, `sitemap.xml`, indexing headers, and meta tag utilities.
|
|
1501
|
+
|
|
1502
|
+
```ts
|
|
1503
|
+
import { seo, seoMiddleware, seoTags } from 'weifuwu'
|
|
1504
|
+
|
|
1505
|
+
const app = new Router()
|
|
1506
|
+
|
|
1507
|
+
// robots.txt + sitemap.xml
|
|
1508
|
+
app.use(seo({
|
|
1509
|
+
baseUrl: 'https://example.com',
|
|
1510
|
+
robots: [
|
|
1511
|
+
{ userAgent: '*', allow: '/', disallow: ['/admin', '/api'] },
|
|
1512
|
+
],
|
|
1513
|
+
sitemap: {
|
|
1514
|
+
urls: [
|
|
1515
|
+
{ loc: '/', changefreq: 'daily', priority: 1.0 },
|
|
1516
|
+
{ loc: '/about', changefreq: 'monthly', priority: 0.8 },
|
|
1517
|
+
],
|
|
1518
|
+
// Dynamic URLs from database
|
|
1519
|
+
async resolve() {
|
|
1520
|
+
const articles = await db.query('SELECT slug, updated_at FROM articles')
|
|
1521
|
+
return articles.map(a => ({
|
|
1522
|
+
loc: `/blog/${a.slug}`,
|
|
1523
|
+
lastmod: a.updated_at,
|
|
1524
|
+
}))
|
|
1525
|
+
},
|
|
1526
|
+
cacheTTL: 3_600_000, // re-generate every hour (default)
|
|
1527
|
+
},
|
|
1528
|
+
}))
|
|
1529
|
+
```
|
|
1530
|
+
|
|
1531
|
+
### Endpoints
|
|
1532
|
+
|
|
1533
|
+
| Path | Description |
|
|
1534
|
+
|------|-------------|
|
|
1535
|
+
| `GET /robots.txt` | Generated robots.txt with optional Sitemap reference |
|
|
1536
|
+
| `GET /sitemap.xml` | Generated XML sitemap with caching |
|
|
1537
|
+
|
|
1538
|
+
### seoMiddleware — Indexing control
|
|
1539
|
+
|
|
1540
|
+
```ts
|
|
1541
|
+
// Global — block all paths
|
|
1542
|
+
app.use(seoMiddleware({ headers: { 'X-Robots-Tag': 'noindex' } }))
|
|
1543
|
+
|
|
1544
|
+
// Per-path via function
|
|
1545
|
+
app.use(seoMiddleware({
|
|
1546
|
+
headers: {
|
|
1547
|
+
'X-Robots-Tag': (path) => path.startsWith('/admin') ? 'noindex' : undefined,
|
|
1548
|
+
},
|
|
1549
|
+
}))
|
|
1550
|
+
```
|
|
1551
|
+
|
|
1552
|
+
### seoTags — Meta / OG / Twitter Card
|
|
1553
|
+
|
|
1554
|
+
Generate SEO meta tags for SSR pages:
|
|
1555
|
+
|
|
1556
|
+
```ts
|
|
1557
|
+
const tags = seoTags({
|
|
1558
|
+
title: 'My Page',
|
|
1559
|
+
description: 'A great page about things',
|
|
1560
|
+
ogImage: 'https://example.com/og.png',
|
|
1561
|
+
twitterCard: 'summary_large_image',
|
|
1562
|
+
canonical: 'https://example.com/page',
|
|
1563
|
+
})
|
|
1564
|
+
// → <title>My Page</title>
|
|
1565
|
+
// → <meta property="og:title" content="My Page">
|
|
1566
|
+
// → <meta name="twitter:card" content="summary_large_image">
|
|
1567
|
+
// → <link rel="canonical" href="https://example.com/page">
|
|
1568
|
+
// ...
|
|
1569
|
+
```
|
|
1570
|
+
|
|
1571
|
+
Use in `layout.tsx` or `page.tsx` with `tsx()`:
|
|
1572
|
+
|
|
1573
|
+
```tsx
|
|
1574
|
+
export default function RootLayout({ children }) {
|
|
1575
|
+
return (
|
|
1576
|
+
<html>
|
|
1577
|
+
<head>{seoTags({ title: 'My App' })}</head>
|
|
1578
|
+
<body>{children}</body>
|
|
1579
|
+
</html>
|
|
1580
|
+
)
|
|
1581
|
+
}
|
|
1582
|
+
```
|
|
1583
|
+
|
|
1584
|
+
# Security
|
|
1585
|
+
|
|
1586
|
+
## Helmet — Security headers
|
|
1587
|
+
|
|
1588
|
+
```ts
|
|
1589
|
+
import { helmet } from 'weifuwu'
|
|
1590
|
+
|
|
1591
|
+
// Apply all security headers with safe defaults
|
|
1592
|
+
app.use(helmet())
|
|
1593
|
+
|
|
1594
|
+
// Customize individual headers (any can be set to false to remove)
|
|
1595
|
+
app.use(helmet({
|
|
1596
|
+
contentSecurityPolicy: "default-src 'self'",
|
|
1597
|
+
xFrameOptions: 'DENY',
|
|
1598
|
+
strictTransportSecurity: 'max-age=63072000; includeSubDomains; preload',
|
|
1599
|
+
}))
|
|
1600
|
+
|
|
1601
|
+
// Middleware-order: set after helmet to override
|
|
1602
|
+
app.use(helmet({ xFrameOptions: false })) // remove a header
|
|
1603
|
+
```
|
|
1604
|
+
|
|
1605
|
+
13 security headers set by default:
|
|
1606
|
+
|
|
1607
|
+
| Header | Default |
|
|
1608
|
+
|--------|---------|
|
|
1609
|
+
| `Content-Security-Policy` | `default-src 'self'; ...` |
|
|
1610
|
+
| `X-Content-Type-Options` | `nosniff` |
|
|
1611
|
+
| `X-Frame-Options` | `SAMEORIGIN` |
|
|
1612
|
+
| `Strict-Transport-Security` | `max-age=15552000; includeSubDomains` |
|
|
1613
|
+
| `X-XSS-Protection` | `0` |
|
|
1614
|
+
| `Referrer-Policy` | `no-referrer` |
|
|
1615
|
+
| `Permissions-Policy` | `camera=(),geolocation=(),...` |
|
|
1616
|
+
| `Cross-Origin-Embedder-Policy` | `require-corp` |
|
|
1617
|
+
| `Cross-Origin-Opener-Policy` | `same-origin` |
|
|
1618
|
+
| `Cross-Origin-Resource-Policy` | `same-origin` |
|
|
1619
|
+
| `Origin-Agent-Cluster` | `?1` |
|
|
1620
|
+
| `X-DNS-Prefetch-Control` | `off` |
|
|
1621
|
+
| `X-Download-Options` | `noopen` |
|
|
1622
|
+
| `X-Permitted-Cross-Domain-Policies` | `none` |
|
|
1623
|
+
|
|
1624
|
+
Does not override response headers already set by the application — your explicit headers take precedence.
|
|
1625
|
+
|
|
1626
|
+
## Request ID
|
|
1627
|
+
|
|
1628
|
+
```ts
|
|
1629
|
+
import { requestId } from 'weifuwu'
|
|
1630
|
+
|
|
1631
|
+
// Every response gets X-Request-ID
|
|
1632
|
+
app.use(requestId())
|
|
1633
|
+
|
|
1634
|
+
// Custom header name
|
|
1635
|
+
app.use(requestId({ header: 'X-Trace-Id' }))
|
|
1636
|
+
|
|
1637
|
+
// Custom ID generator
|
|
1638
|
+
app.use(requestId({ generator: () => crypto.randomUUID() }))
|
|
1639
|
+
|
|
1640
|
+
// Access the ID in handlers via ctx.requestId
|
|
1641
|
+
app.get('/log', (req, ctx) => {
|
|
1642
|
+
console.log(`Handling request ${ctx.requestId}`)
|
|
1643
|
+
return Response.json({ id: ctx.requestId })
|
|
1644
|
+
})
|
|
1645
|
+
```
|
|
1646
|
+
|
|
1647
|
+
Preserves incoming `X-Request-ID` for distributed tracing — if the upstream service already set it, the value is reused and propagated.
|
|
1648
|
+
|
|
1649
|
+
## Server-Sent Events
|
|
1650
|
+
|
|
1651
|
+
```ts
|
|
1652
|
+
import { createSSEStream, formatSSE } from 'weifuwu'
|
|
1653
|
+
|
|
1654
|
+
async function* eventStream() {
|
|
1655
|
+
yield { type: 'ping', data: { time: Date.now() } }
|
|
1656
|
+
yield { type: 'message', data: { text: 'hello' } }
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
app.get('/events', () => createSSEStream(eventStream()))
|
|
1660
|
+
```
|
|
1661
|
+
|
|
1662
|
+
| Function | Description |
|
|
1663
|
+
|----------|-------------|
|
|
1664
|
+
| `createSSEStream(iterable, opts?)` | Returns a `Response` with `Content-Type: text/event-stream` |
|
|
1665
|
+
| `formatSSE(event, data)` | Formats an SSE event string (`event: ...\ndata: ...\n\n`) |
|
|
1666
|
+
| `formatSSEData(data)` | Formats SSE data-only string (`data: ...\n\n`) |
|
|
1667
|
+
|
|
1488
1668
|
# Health, i18n, Email & Testing
|
|
1489
1669
|
|
|
1490
1670
|
## Health check
|
package/dist/helmet.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Middleware } from './types.ts';
|
|
2
|
+
export interface HelmetOptions {
|
|
3
|
+
contentSecurityPolicy?: string | false;
|
|
4
|
+
crossOriginEmbedderPolicy?: string | false;
|
|
5
|
+
crossOriginOpenerPolicy?: string | false;
|
|
6
|
+
crossOriginResourcePolicy?: string | false;
|
|
7
|
+
originAgentCluster?: string | false;
|
|
8
|
+
referrerPolicy?: string | false;
|
|
9
|
+
strictTransportSecurity?: string | false;
|
|
10
|
+
xContentTypeOptions?: string | false;
|
|
11
|
+
xDnsPrefetchControl?: string | false;
|
|
12
|
+
xDownloadOptions?: string | false;
|
|
13
|
+
xFrameOptions?: string | false;
|
|
14
|
+
xPermittedCrossDomainPolicies?: string | false;
|
|
15
|
+
xXssProtection?: string | false;
|
|
16
|
+
permissionsPolicy?: string | false;
|
|
17
|
+
}
|
|
18
|
+
export declare function helmet(options?: HelmetOptions): Middleware;
|
package/dist/iii/stream.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type { StreamUpdateOp, StreamSubscription } from './types.ts';
|
|
|
4
4
|
export declare function createStream(opts?: {
|
|
5
5
|
pg?: PostgresClient;
|
|
6
6
|
redis?: Redis;
|
|
7
|
+
streamTTL?: number;
|
|
7
8
|
}): {
|
|
8
9
|
subscribe(ws: WebSocket, sub: StreamSubscription): void;
|
|
9
10
|
unsubscribe(ws: WebSocket): void;
|
package/dist/iii/types.d.ts
CHANGED
|
@@ -17,6 +17,8 @@ export interface TriggerInput {
|
|
|
17
17
|
export interface IIIOptions {
|
|
18
18
|
pg?: PostgresClient;
|
|
19
19
|
redis?: Redis;
|
|
20
|
+
/** TTL in seconds for Redis stream keys. Default: 3600 (1 hour). Set to 0 for no expiration. */
|
|
21
|
+
streamTTL?: number;
|
|
20
22
|
}
|
|
21
23
|
export interface TriggerRequest {
|
|
22
24
|
function_id: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -20,6 +20,12 @@ export { rateLimit } from './rate-limit.ts';
|
|
|
20
20
|
export type { RateLimitOptions } from './rate-limit.ts';
|
|
21
21
|
export { compress } from './compress.ts';
|
|
22
22
|
export type { CompressOptions } from './compress.ts';
|
|
23
|
+
export { helmet } from './helmet.ts';
|
|
24
|
+
export type { HelmetOptions } from './helmet.ts';
|
|
25
|
+
export { requestId } from './request-id.ts';
|
|
26
|
+
export type { RequestIdOptions } from './request-id.ts';
|
|
27
|
+
export { createSSEStream, formatSSE, formatSSEData } from './sse.ts';
|
|
28
|
+
export type { SSEEvent } from './sse.ts';
|
|
23
29
|
export { graphql } from './graphql.ts';
|
|
24
30
|
export type { GraphQLOptions, GraphQLHandler } from './graphql.ts';
|
|
25
31
|
export { aiStream } from './ai.ts';
|
|
@@ -47,6 +53,8 @@ export { health } from './health.ts';
|
|
|
47
53
|
export type { HealthOptions } from './health.ts';
|
|
48
54
|
export { i18n } from './i18n.ts';
|
|
49
55
|
export type { I18nOptions } from './i18n.ts';
|
|
56
|
+
export { seo, seoMiddleware, seoTags } from './seo.ts';
|
|
57
|
+
export type { SeoOptions, RobotsRule, SitemapUrl, SitemapConfig, SeoHeadersConfig, SeoTagsConfig } from './seo.ts';
|
|
50
58
|
export { mailer } from './mailer.ts';
|
|
51
59
|
export type { MailerOptions, MailOptions, Mailer } from './mailer.ts';
|
|
52
60
|
export { logdb } from './logdb/index.ts';
|
package/dist/index.js
CHANGED
|
@@ -1859,6 +1859,118 @@ function compress(options) {
|
|
|
1859
1859
|
};
|
|
1860
1860
|
}
|
|
1861
1861
|
|
|
1862
|
+
// helmet.ts
|
|
1863
|
+
var DEFAULTS = {
|
|
1864
|
+
contentSecurityPolicy: "default-src 'self';base-uri 'self';font-src 'self' https: data:;form-action 'self';frame-ancestors 'self';img-src 'self' data:;object-src 'none';script-src 'self';script-src-attr 'none';style-src 'self' https: 'unsafe-inline';upgrade-insecure-requests",
|
|
1865
|
+
crossOriginEmbedderPolicy: "require-corp",
|
|
1866
|
+
crossOriginOpenerPolicy: "same-origin",
|
|
1867
|
+
crossOriginResourcePolicy: "same-origin",
|
|
1868
|
+
originAgentCluster: "?1",
|
|
1869
|
+
referrerPolicy: "no-referrer",
|
|
1870
|
+
strictTransportSecurity: "max-age=15552000; includeSubDomains",
|
|
1871
|
+
xContentTypeOptions: "nosniff",
|
|
1872
|
+
xDnsPrefetchControl: "off",
|
|
1873
|
+
xDownloadOptions: "noopen",
|
|
1874
|
+
xFrameOptions: "SAMEORIGIN",
|
|
1875
|
+
xPermittedCrossDomainPolicies: "none",
|
|
1876
|
+
xXssProtection: "0",
|
|
1877
|
+
permissionsPolicy: "camera=(),display-capture=(),fullscreen=(),geolocation=(),microphone=()"
|
|
1878
|
+
};
|
|
1879
|
+
var HEADER_MAP = {
|
|
1880
|
+
"Content-Security-Policy": "contentSecurityPolicy",
|
|
1881
|
+
"Cross-Origin-Embedder-Policy": "crossOriginEmbedderPolicy",
|
|
1882
|
+
"Cross-Origin-Opener-Policy": "crossOriginOpenerPolicy",
|
|
1883
|
+
"Cross-Origin-Resource-Policy": "crossOriginResourcePolicy",
|
|
1884
|
+
"Origin-Agent-Cluster": "originAgentCluster",
|
|
1885
|
+
"Referrer-Policy": "referrerPolicy",
|
|
1886
|
+
"Strict-Transport-Security": "strictTransportSecurity",
|
|
1887
|
+
"X-Content-Type-Options": "xContentTypeOptions",
|
|
1888
|
+
"X-DNS-Prefetch-Control": "xDnsPrefetchControl",
|
|
1889
|
+
"X-Download-Options": "xDownloadOptions",
|
|
1890
|
+
"X-Frame-Options": "xFrameOptions",
|
|
1891
|
+
"X-Permitted-Cross-Domain-Policies": "xPermittedCrossDomainPolicies",
|
|
1892
|
+
"X-XSS-Protection": "xXssProtection",
|
|
1893
|
+
"Permissions-Policy": "permissionsPolicy"
|
|
1894
|
+
};
|
|
1895
|
+
function helmet(options) {
|
|
1896
|
+
const opts = { ...DEFAULTS, ...options };
|
|
1897
|
+
const headers = new Headers();
|
|
1898
|
+
for (const [header, key] of Object.entries(HEADER_MAP)) {
|
|
1899
|
+
const val = opts[key];
|
|
1900
|
+
if (val !== false && val !== void 0) headers.set(header, val);
|
|
1901
|
+
}
|
|
1902
|
+
return async (req, ctx, next) => {
|
|
1903
|
+
const res = await next(req, ctx);
|
|
1904
|
+
const h = new Headers(res.headers);
|
|
1905
|
+
for (const [k, v] of headers) {
|
|
1906
|
+
if (!h.has(k)) h.set(k, v);
|
|
1907
|
+
}
|
|
1908
|
+
return new Response(res.body, { status: res.status, statusText: res.statusText, headers: h });
|
|
1909
|
+
};
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1912
|
+
// request-id.ts
|
|
1913
|
+
import crypto from "node:crypto";
|
|
1914
|
+
function requestId(options) {
|
|
1915
|
+
const header = options?.header ?? "X-Request-ID";
|
|
1916
|
+
const gen = options?.generator ?? (() => crypto.randomUUID());
|
|
1917
|
+
return async (req, ctx, next) => {
|
|
1918
|
+
const existing = req.headers.get(header);
|
|
1919
|
+
const id2 = existing ?? gen();
|
|
1920
|
+
ctx.requestId = id2;
|
|
1921
|
+
const res = await next(req, ctx);
|
|
1922
|
+
if (res.headers.has(header)) return res;
|
|
1923
|
+
const h = new Headers(res.headers);
|
|
1924
|
+
h.set(header, id2);
|
|
1925
|
+
return new Response(res.body, { status: res.status, statusText: res.statusText, headers: h });
|
|
1926
|
+
};
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1929
|
+
// sse.ts
|
|
1930
|
+
var encoder = new TextEncoder();
|
|
1931
|
+
function formatSSE(event, data) {
|
|
1932
|
+
return `event: ${event}
|
|
1933
|
+
data: ${JSON.stringify(data)}
|
|
1934
|
+
|
|
1935
|
+
`;
|
|
1936
|
+
}
|
|
1937
|
+
function formatSSEData(data) {
|
|
1938
|
+
return `data: ${JSON.stringify(data)}
|
|
1939
|
+
|
|
1940
|
+
`;
|
|
1941
|
+
}
|
|
1942
|
+
function createSSEStream(iterable, opts) {
|
|
1943
|
+
return new Response(
|
|
1944
|
+
new ReadableStream({
|
|
1945
|
+
async start(controller) {
|
|
1946
|
+
try {
|
|
1947
|
+
for await (const event of iterable) {
|
|
1948
|
+
const text2 = event.type ? formatSSE(event.type, event) : formatSSEData(event);
|
|
1949
|
+
controller.enqueue(encoder.encode(text2));
|
|
1950
|
+
}
|
|
1951
|
+
} catch (e) {
|
|
1952
|
+
if (e.name !== "AbortError") {
|
|
1953
|
+
controller.enqueue(
|
|
1954
|
+
encoder.encode(formatSSE("error", { error: e.message }))
|
|
1955
|
+
);
|
|
1956
|
+
}
|
|
1957
|
+
} finally {
|
|
1958
|
+
controller.close();
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
}),
|
|
1962
|
+
{
|
|
1963
|
+
status: opts?.status ?? 200,
|
|
1964
|
+
headers: {
|
|
1965
|
+
"Content-Type": "text/event-stream",
|
|
1966
|
+
"Cache-Control": "no-cache",
|
|
1967
|
+
Connection: "keep-alive",
|
|
1968
|
+
...opts?.headers
|
|
1969
|
+
}
|
|
1970
|
+
}
|
|
1971
|
+
);
|
|
1972
|
+
}
|
|
1973
|
+
|
|
1862
1974
|
// graphql.ts
|
|
1863
1975
|
import { buildSchema, graphql as executeGraphQL } from "graphql";
|
|
1864
1976
|
import { makeExecutableSchema } from "@graphql-tools/schema";
|
|
@@ -2821,7 +2933,7 @@ import jwt2 from "jsonwebtoken";
|
|
|
2821
2933
|
import { z as z2 } from "zod";
|
|
2822
2934
|
|
|
2823
2935
|
// user/oauth2.ts
|
|
2824
|
-
import
|
|
2936
|
+
import crypto2 from "node:crypto";
|
|
2825
2937
|
import jwt from "jsonwebtoken";
|
|
2826
2938
|
function createOAuth2Server(deps) {
|
|
2827
2939
|
const { pg, users, jwtSecret, expiresIn } = deps;
|
|
@@ -2840,8 +2952,8 @@ function createOAuth2Server(deps) {
|
|
|
2840
2952
|
};
|
|
2841
2953
|
}
|
|
2842
2954
|
async function registerClient(data) {
|
|
2843
|
-
const clientId =
|
|
2844
|
-
const clientSecret =
|
|
2955
|
+
const clientId = crypto2.randomUUID();
|
|
2956
|
+
const clientSecret = crypto2.randomBytes(32).toString("hex");
|
|
2845
2957
|
const [row] = await pg.sql`
|
|
2846
2958
|
INSERT INTO "_oauth2_clients" ("name", "client_id", "client_secret", "redirect_uris")
|
|
2847
2959
|
VALUES (${data.name}, ${clientId}, ${clientSecret}, ${pg.sql.array(data.redirectUris)})
|
|
@@ -2989,7 +3101,7 @@ h2{color:#dc2626}.desc{color:#555}</style>
|
|
|
2989
3101
|
const loc2 = `${redirectUri}?error=access_denied${state ? `&state=${state}` : ""}`;
|
|
2990
3102
|
return Response.redirect(loc2, 302);
|
|
2991
3103
|
}
|
|
2992
|
-
const code =
|
|
3104
|
+
const code = crypto2.randomUUID();
|
|
2993
3105
|
const expiresAt = new Date(Date.now() + 10 * 60 * 1e3);
|
|
2994
3106
|
await pg.sql`
|
|
2995
3107
|
INSERT INTO "_oauth2_codes" ("code", "client_id", "user_id", "redirect_uri", "code_challenge", "code_challenge_method", "scope", "expires_at")
|
|
@@ -3054,7 +3166,7 @@ h2{color:#dc2626}.desc{color:#555}</style>
|
|
|
3054
3166
|
if (stored.code_challenge_method === "plain") {
|
|
3055
3167
|
expected = codeVerifier;
|
|
3056
3168
|
} else {
|
|
3057
|
-
expected =
|
|
3169
|
+
expected = crypto2.createHash("sha256").update(codeVerifier).digest().toString("base64url");
|
|
3058
3170
|
}
|
|
3059
3171
|
if (expected !== stored.code_challenge) {
|
|
3060
3172
|
return Response.json({ error: "invalid_grant", error_description: "code_verifier mismatch" }, { status: 400 });
|
|
@@ -3071,7 +3183,7 @@ h2{color:#dc2626}.desc{color:#555}</style>
|
|
|
3071
3183
|
jwtSecret,
|
|
3072
3184
|
{ expiresIn }
|
|
3073
3185
|
);
|
|
3074
|
-
const refreshToken =
|
|
3186
|
+
const refreshToken = crypto2.randomUUID();
|
|
3075
3187
|
const refreshExpires = new Date(Date.now() + 30 * 24 * 60 * 60 * 1e3);
|
|
3076
3188
|
await pg.sql`
|
|
3077
3189
|
INSERT INTO "_oauth2_tokens" ("token", "client_id", "user_id", "scope", "expires_at")
|
|
@@ -3339,7 +3451,7 @@ function redis(opts) {
|
|
|
3339
3451
|
|
|
3340
3452
|
// queue/index.ts
|
|
3341
3453
|
import { Redis as IORedis2 } from "ioredis";
|
|
3342
|
-
import
|
|
3454
|
+
import crypto3 from "node:crypto";
|
|
3343
3455
|
function cronNext(expr, from = /* @__PURE__ */ new Date()) {
|
|
3344
3456
|
const parts = expr.trim().split(/\s+/);
|
|
3345
3457
|
if (parts.length !== 5) throw new Error(`Invalid cron expression "${expr}": expected 5 fields`);
|
|
@@ -3430,7 +3542,7 @@ function queue(opts) {
|
|
|
3430
3542
|
if (job.schedule) {
|
|
3431
3543
|
try {
|
|
3432
3544
|
const nextRun = cronNext(job.schedule);
|
|
3433
|
-
const nextJob = { ...job, id:
|
|
3545
|
+
const nextJob = { ...job, id: crypto3.randomUUID(), runAt: nextRun, createdAt: Date.now() };
|
|
3434
3546
|
redis2.zadd(jobKey, nextRun, JSON.stringify(nextJob)).catch(() => {
|
|
3435
3547
|
});
|
|
3436
3548
|
} catch {
|
|
@@ -3448,7 +3560,7 @@ function queue(opts) {
|
|
|
3448
3560
|
}
|
|
3449
3561
|
}
|
|
3450
3562
|
mw.add = function add(type, payload, opts2) {
|
|
3451
|
-
const id2 =
|
|
3563
|
+
const id2 = crypto3.randomUUID();
|
|
3452
3564
|
let runAt;
|
|
3453
3565
|
if (opts2?.schedule) {
|
|
3454
3566
|
runAt = cronNext(opts2.schedule);
|
|
@@ -4495,53 +4607,6 @@ function buildRouter2(deps) {
|
|
|
4495
4607
|
// agent/run.ts
|
|
4496
4608
|
import { streamText, generateText as generateText2, embed } from "ai";
|
|
4497
4609
|
import { z as z4 } from "zod";
|
|
4498
|
-
|
|
4499
|
-
// sse.ts
|
|
4500
|
-
var encoder = new TextEncoder();
|
|
4501
|
-
function formatSSE(event, data) {
|
|
4502
|
-
return `event: ${event}
|
|
4503
|
-
data: ${JSON.stringify(data)}
|
|
4504
|
-
|
|
4505
|
-
`;
|
|
4506
|
-
}
|
|
4507
|
-
function formatSSEData(data) {
|
|
4508
|
-
return `data: ${JSON.stringify(data)}
|
|
4509
|
-
|
|
4510
|
-
`;
|
|
4511
|
-
}
|
|
4512
|
-
function createSSEStream(iterable, opts) {
|
|
4513
|
-
return new Response(
|
|
4514
|
-
new ReadableStream({
|
|
4515
|
-
async start(controller) {
|
|
4516
|
-
try {
|
|
4517
|
-
for await (const event of iterable) {
|
|
4518
|
-
const text2 = event.type ? formatSSE(event.type, event) : formatSSEData(event);
|
|
4519
|
-
controller.enqueue(encoder.encode(text2));
|
|
4520
|
-
}
|
|
4521
|
-
} catch (e) {
|
|
4522
|
-
if (e.name !== "AbortError") {
|
|
4523
|
-
controller.enqueue(
|
|
4524
|
-
encoder.encode(formatSSE("error", { error: e.message }))
|
|
4525
|
-
);
|
|
4526
|
-
}
|
|
4527
|
-
} finally {
|
|
4528
|
-
controller.close();
|
|
4529
|
-
}
|
|
4530
|
-
}
|
|
4531
|
-
}),
|
|
4532
|
-
{
|
|
4533
|
-
status: opts?.status ?? 200,
|
|
4534
|
-
headers: {
|
|
4535
|
-
"Content-Type": "text/event-stream",
|
|
4536
|
-
"Cache-Control": "no-cache",
|
|
4537
|
-
Connection: "keep-alive",
|
|
4538
|
-
...opts?.headers
|
|
4539
|
-
}
|
|
4540
|
-
}
|
|
4541
|
-
);
|
|
4542
|
-
}
|
|
4543
|
-
|
|
4544
|
-
// agent/run.ts
|
|
4545
4610
|
function hasKnowledgeDocs(sql2, agentId) {
|
|
4546
4611
|
return sql2`SELECT 1 FROM "_knowledge_documents" WHERE agent_id = ${agentId} LIMIT 1`.then((r) => r.length > 0);
|
|
4547
4612
|
}
|
|
@@ -5161,7 +5226,7 @@ function createGateway(config, getPort) {
|
|
|
5161
5226
|
}
|
|
5162
5227
|
|
|
5163
5228
|
// deploy/manager.ts
|
|
5164
|
-
import
|
|
5229
|
+
import crypto4 from "node:crypto";
|
|
5165
5230
|
function createManager(config, apps, manager) {
|
|
5166
5231
|
const router = new Router();
|
|
5167
5232
|
const auth2 = (req, ctx, next) => {
|
|
@@ -5170,7 +5235,7 @@ function createManager(config, apps, manager) {
|
|
|
5170
5235
|
const token = header.replace("Bearer ", "");
|
|
5171
5236
|
const tokenBuf = Buffer.from(token);
|
|
5172
5237
|
const secretBuf = Buffer.from(config.deployToken);
|
|
5173
|
-
if (tokenBuf.length !== secretBuf.length || !
|
|
5238
|
+
if (tokenBuf.length !== secretBuf.length || !crypto4.timingSafeEqual(tokenBuf, secretBuf)) {
|
|
5174
5239
|
return Response.json({ error: "Unauthorized" }, { status: 401 });
|
|
5175
5240
|
}
|
|
5176
5241
|
return next(req, ctx);
|
|
@@ -5269,10 +5334,10 @@ function createManager(config, apps, manager) {
|
|
|
5269
5334
|
const rawBody = await req.text();
|
|
5270
5335
|
if (config.webhookSecret) {
|
|
5271
5336
|
const sig = req.headers.get("x-hub-signature-256") ?? "";
|
|
5272
|
-
const expected = `sha256=${
|
|
5337
|
+
const expected = `sha256=${crypto4.createHmac("sha256", config.webhookSecret).update(rawBody).digest("hex")}`;
|
|
5273
5338
|
const sigBuf = Buffer.from(sig);
|
|
5274
5339
|
const expectedBuf = Buffer.from(expected);
|
|
5275
|
-
if (sigBuf.length !== expectedBuf.length || !
|
|
5340
|
+
if (sigBuf.length !== expectedBuf.length || !crypto4.timingSafeEqual(sigBuf, expectedBuf)) {
|
|
5276
5341
|
return Response.json({ error: "invalid signature" }, { status: 401 });
|
|
5277
5342
|
}
|
|
5278
5343
|
}
|
|
@@ -6588,6 +6653,155 @@ function extractCookie(req, name) {
|
|
|
6588
6653
|
return null;
|
|
6589
6654
|
}
|
|
6590
6655
|
|
|
6656
|
+
// seo.ts
|
|
6657
|
+
function escapeXml(s) {
|
|
6658
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
6659
|
+
}
|
|
6660
|
+
function buildRobotsTxt(rules, sitemapUrl) {
|
|
6661
|
+
const lines = [];
|
|
6662
|
+
for (const rule of rules) {
|
|
6663
|
+
lines.push(`User-agent: ${rule.userAgent ?? "*"}`);
|
|
6664
|
+
if (rule.allow) {
|
|
6665
|
+
for (const a of Array.isArray(rule.allow) ? rule.allow : [rule.allow]) {
|
|
6666
|
+
lines.push(`Allow: ${a}`);
|
|
6667
|
+
}
|
|
6668
|
+
}
|
|
6669
|
+
if (rule.disallow) {
|
|
6670
|
+
for (const d of Array.isArray(rule.disallow) ? rule.disallow : [rule.disallow]) {
|
|
6671
|
+
lines.push(`Disallow: ${d}`);
|
|
6672
|
+
}
|
|
6673
|
+
}
|
|
6674
|
+
}
|
|
6675
|
+
if (sitemapUrl) {
|
|
6676
|
+
lines.push(`Sitemap: ${sitemapUrl}`);
|
|
6677
|
+
}
|
|
6678
|
+
lines.push("");
|
|
6679
|
+
return lines.join("\n");
|
|
6680
|
+
}
|
|
6681
|
+
function buildSitemapXml(urls, baseUrl) {
|
|
6682
|
+
if (urls.length === 0) {
|
|
6683
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
6684
|
+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
6685
|
+
</urlset>
|
|
6686
|
+
`;
|
|
6687
|
+
}
|
|
6688
|
+
let xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
6689
|
+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
6690
|
+
`;
|
|
6691
|
+
for (const url of urls) {
|
|
6692
|
+
let loc = url.loc;
|
|
6693
|
+
if (baseUrl && loc.startsWith("/")) {
|
|
6694
|
+
loc = baseUrl.replace(/\/+$/, "") + loc;
|
|
6695
|
+
}
|
|
6696
|
+
xml += ` <url>
|
|
6697
|
+
<loc>${escapeXml(loc)}</loc>`;
|
|
6698
|
+
if (url.lastmod) {
|
|
6699
|
+
xml += `
|
|
6700
|
+
<lastmod>${escapeXml(url.lastmod)}</lastmod>`;
|
|
6701
|
+
}
|
|
6702
|
+
if (url.changefreq) {
|
|
6703
|
+
xml += `
|
|
6704
|
+
<changefreq>${escapeXml(url.changefreq)}</changefreq>`;
|
|
6705
|
+
}
|
|
6706
|
+
if (url.priority !== void 0) {
|
|
6707
|
+
xml += `
|
|
6708
|
+
<priority>${url.priority.toFixed(1)}</priority>`;
|
|
6709
|
+
}
|
|
6710
|
+
xml += `
|
|
6711
|
+
</url>
|
|
6712
|
+
`;
|
|
6713
|
+
}
|
|
6714
|
+
xml += `</urlset>
|
|
6715
|
+
`;
|
|
6716
|
+
return xml;
|
|
6717
|
+
}
|
|
6718
|
+
function getRobotsHeader(headers, path2) {
|
|
6719
|
+
if (!headers?.["X-Robots-Tag"]) return void 0;
|
|
6720
|
+
const val = headers["X-Robots-Tag"];
|
|
6721
|
+
if (typeof val === "function") return val(path2);
|
|
6722
|
+
return val;
|
|
6723
|
+
}
|
|
6724
|
+
function seoMiddleware(options) {
|
|
6725
|
+
const headers = options?.headers;
|
|
6726
|
+
return async (req, ctx, next) => {
|
|
6727
|
+
const res = await next(req, ctx);
|
|
6728
|
+
if (!headers) return res;
|
|
6729
|
+
const url = new URL(req.url);
|
|
6730
|
+
const robotTag = getRobotsHeader(headers, url.pathname);
|
|
6731
|
+
if (robotTag) {
|
|
6732
|
+
const h = new Headers(res.headers);
|
|
6733
|
+
h.set("X-Robots-Tag", robotTag);
|
|
6734
|
+
return new Response(res.body, { status: res.status, statusText: res.statusText, headers: h });
|
|
6735
|
+
}
|
|
6736
|
+
return res;
|
|
6737
|
+
};
|
|
6738
|
+
}
|
|
6739
|
+
function seo(options) {
|
|
6740
|
+
const { robots, sitemap: sitemapConfig, baseUrl } = options ?? {};
|
|
6741
|
+
const r = new Router();
|
|
6742
|
+
const robotsHandler = () => {
|
|
6743
|
+
const sitemapUrl = sitemapConfig ? `${baseUrl ?? ""}/sitemap.xml` : void 0;
|
|
6744
|
+
const body = buildRobotsTxt(robots ?? [{ userAgent: "*", allow: "/" }], sitemapUrl);
|
|
6745
|
+
return new Response(body, {
|
|
6746
|
+
headers: { "Content-Type": "text/plain; charset=utf-8" }
|
|
6747
|
+
});
|
|
6748
|
+
};
|
|
6749
|
+
let cached = null;
|
|
6750
|
+
let cacheTime = 0;
|
|
6751
|
+
const cacheTTL = sitemapConfig?.cacheTTL ?? 36e5;
|
|
6752
|
+
const sitemapHandler = async () => {
|
|
6753
|
+
if (cached && Date.now() - cacheTime < cacheTTL) {
|
|
6754
|
+
return new Response(cached, {
|
|
6755
|
+
headers: { "Content-Type": "application/xml; charset=utf-8" }
|
|
6756
|
+
});
|
|
6757
|
+
}
|
|
6758
|
+
const urls = [...sitemapConfig?.urls ?? []];
|
|
6759
|
+
if (sitemapConfig?.resolve) {
|
|
6760
|
+
const dynamic = await sitemapConfig.resolve();
|
|
6761
|
+
urls.push(...dynamic);
|
|
6762
|
+
}
|
|
6763
|
+
const xml = buildSitemapXml(urls, baseUrl);
|
|
6764
|
+
cached = xml;
|
|
6765
|
+
cacheTime = Date.now();
|
|
6766
|
+
return new Response(xml, {
|
|
6767
|
+
headers: { "Content-Type": "application/xml; charset=utf-8" }
|
|
6768
|
+
});
|
|
6769
|
+
};
|
|
6770
|
+
r.get("/robots.txt", robotsHandler);
|
|
6771
|
+
r.get("/sitemap.xml", sitemapHandler);
|
|
6772
|
+
return r;
|
|
6773
|
+
}
|
|
6774
|
+
function seoTags(config) {
|
|
6775
|
+
const tags = [];
|
|
6776
|
+
if (config.title) {
|
|
6777
|
+
tags.push(`<title>${escapeXml(config.title)}</title>`);
|
|
6778
|
+
tags.push(`<meta property="og:title" content="${escapeXml(config.title)}">`);
|
|
6779
|
+
tags.push(`<meta name="twitter:title" content="${escapeXml(config.title)}">`);
|
|
6780
|
+
}
|
|
6781
|
+
if (config.description) {
|
|
6782
|
+
tags.push(`<meta name="description" content="${escapeXml(config.description)}">`);
|
|
6783
|
+
tags.push(`<meta property="og:description" content="${escapeXml(config.description)}">`);
|
|
6784
|
+
tags.push(`<meta name="twitter:description" content="${escapeXml(config.description)}">`);
|
|
6785
|
+
}
|
|
6786
|
+
if (config.ogTitle) {
|
|
6787
|
+
tags.push(`<meta property="og:title" content="${escapeXml(config.ogTitle)}">`);
|
|
6788
|
+
}
|
|
6789
|
+
if (config.ogDescription) {
|
|
6790
|
+
tags.push(`<meta property="og:description" content="${escapeXml(config.ogDescription)}">`);
|
|
6791
|
+
}
|
|
6792
|
+
if (config.ogImage) {
|
|
6793
|
+
tags.push(`<meta property="og:image" content="${escapeXml(config.ogImage)}">`);
|
|
6794
|
+
tags.push(`<meta name="twitter:image" content="${escapeXml(config.ogImage)}">`);
|
|
6795
|
+
}
|
|
6796
|
+
if (config.twitterCard) {
|
|
6797
|
+
tags.push(`<meta name="twitter:card" content="${escapeXml(config.twitterCard)}">`);
|
|
6798
|
+
}
|
|
6799
|
+
if (config.canonical) {
|
|
6800
|
+
tags.push(`<link rel="canonical" href="${escapeXml(config.canonical)}">`);
|
|
6801
|
+
}
|
|
6802
|
+
return tags.join("\n");
|
|
6803
|
+
}
|
|
6804
|
+
|
|
6591
6805
|
// mailer.ts
|
|
6592
6806
|
import { createTransport } from "nodemailer";
|
|
6593
6807
|
function mailer(options) {
|
|
@@ -6780,7 +6994,7 @@ function logdb(options) {
|
|
|
6780
6994
|
}
|
|
6781
6995
|
|
|
6782
6996
|
// iii/client.ts
|
|
6783
|
-
import
|
|
6997
|
+
import crypto5 from "node:crypto";
|
|
6784
6998
|
|
|
6785
6999
|
// iii/stream.ts
|
|
6786
7000
|
var channels2 = /* @__PURE__ */ new Map();
|
|
@@ -6998,16 +7212,20 @@ function createPgStore(pg) {
|
|
|
6998
7212
|
}
|
|
6999
7213
|
};
|
|
7000
7214
|
}
|
|
7001
|
-
function createRedisStore(redis2) {
|
|
7215
|
+
function createRedisStore(redis2, ttl) {
|
|
7002
7216
|
function hashKey(stream, group) {
|
|
7003
7217
|
return `iii:stream:${stream}:${group}`;
|
|
7004
7218
|
}
|
|
7219
|
+
function setTTL(hk) {
|
|
7220
|
+
if (ttl) redis2.expire(hk, ttl);
|
|
7221
|
+
}
|
|
7005
7222
|
return {
|
|
7006
7223
|
async set(stream, group, item, data) {
|
|
7007
7224
|
const hk = hashKey(stream, group);
|
|
7008
7225
|
const oldRaw = await redis2.hget(hk, item);
|
|
7009
7226
|
let old = oldRaw ? JSON.parse(oldRaw) : null;
|
|
7010
7227
|
await redis2.hset(hk, item, JSON.stringify(data));
|
|
7228
|
+
setTTL(hk);
|
|
7011
7229
|
await redis2.publish(`iii:stream:${stream}`, JSON.stringify({ event: "set", group, item, data }));
|
|
7012
7230
|
notify(stream, group, item, "set", data);
|
|
7013
7231
|
return { old_value: old, new_value: deepClone(data) };
|
|
@@ -7021,6 +7239,8 @@ function createRedisStore(redis2) {
|
|
|
7021
7239
|
const oldRaw = await redis2.hget(hk, item);
|
|
7022
7240
|
const old = oldRaw ? JSON.parse(oldRaw) : null;
|
|
7023
7241
|
await redis2.hdel(hk, item);
|
|
7242
|
+
const remaining = await redis2.hlen(hk);
|
|
7243
|
+
if (remaining === 0) await redis2.del(hk);
|
|
7024
7244
|
await redis2.publish(`iii:stream:${stream}`, JSON.stringify({ event: "delete", group, item }));
|
|
7025
7245
|
notify(stream, group, item, "delete", null);
|
|
7026
7246
|
return { old_value: old };
|
|
@@ -7081,6 +7301,7 @@ function createRedisStore(redis2) {
|
|
|
7081
7301
|
const old = oldRaw ? JSON.parse(oldRaw) : null;
|
|
7082
7302
|
const newVal = applyOps(old, ops);
|
|
7083
7303
|
await redis2.hset(hk, item, JSON.stringify(newVal));
|
|
7304
|
+
setTTL(hk);
|
|
7084
7305
|
await redis2.publish(`iii:stream:${stream}`, JSON.stringify({ event: "update", group, item, data: newVal }));
|
|
7085
7306
|
notify(stream, group, item, "update", newVal);
|
|
7086
7307
|
return { old_value: old, new_value: deepClone(newVal) };
|
|
@@ -7088,7 +7309,7 @@ function createRedisStore(redis2) {
|
|
|
7088
7309
|
};
|
|
7089
7310
|
}
|
|
7090
7311
|
function createStream(opts) {
|
|
7091
|
-
const store = opts?.pg ? createPgStore(opts.pg) : opts?.redis ? createRedisStore(opts.redis) : createMemoryStore();
|
|
7312
|
+
const store = opts?.pg ? createPgStore(opts.pg) : opts?.redis ? createRedisStore(opts.redis, opts.streamTTL ?? 3600) : createMemoryStore();
|
|
7092
7313
|
let redisSub = null;
|
|
7093
7314
|
if (opts?.redis) {
|
|
7094
7315
|
redisSub = opts.redis.duplicate();
|
|
@@ -7272,7 +7493,7 @@ function buildRouter5(engine, wsHandler) {
|
|
|
7272
7493
|
|
|
7273
7494
|
// iii/client.ts
|
|
7274
7495
|
function iii(opts = {}) {
|
|
7275
|
-
const stream = createStream({ pg: opts.pg, redis: opts.redis });
|
|
7496
|
+
const stream = createStream({ pg: opts.pg, redis: opts.redis, streamTTL: opts.streamTTL });
|
|
7276
7497
|
const workers = /* @__PURE__ */ new Map();
|
|
7277
7498
|
const functions = /* @__PURE__ */ new Map();
|
|
7278
7499
|
const triggers = /* @__PURE__ */ new Map();
|
|
@@ -7295,7 +7516,7 @@ function iii(opts = {}) {
|
|
|
7295
7516
|
registerBuiltin("stream::send", (p) => stream.send(p.stream_name, p.group_id, p.type, p.data, p.id));
|
|
7296
7517
|
registerBuiltin("stream::update", (p) => stream.update(p.stream_name, p.group_id, p.item_id, p.ops));
|
|
7297
7518
|
function addLocalWorker(worker) {
|
|
7298
|
-
const workerId =
|
|
7519
|
+
const workerId = crypto5.randomUUID();
|
|
7299
7520
|
const reg = {
|
|
7300
7521
|
id: workerId,
|
|
7301
7522
|
name: worker.name,
|
|
@@ -7310,7 +7531,7 @@ function iii(opts = {}) {
|
|
|
7310
7531
|
const triggerIds = [];
|
|
7311
7532
|
for (const t of worker.getTriggers()) {
|
|
7312
7533
|
if (t.input.function_id === fn.id) {
|
|
7313
|
-
const tid =
|
|
7534
|
+
const tid = crypto5.randomUUID();
|
|
7314
7535
|
triggers.set(tid, {
|
|
7315
7536
|
id: tid,
|
|
7316
7537
|
type: t.input.type,
|
|
@@ -7339,7 +7560,7 @@ function iii(opts = {}) {
|
|
|
7339
7560
|
if (!worker) return;
|
|
7340
7561
|
const handler = async (payload) => {
|
|
7341
7562
|
if (!worker.ws) throw new Error(`Worker "${worker.name}" disconnected`);
|
|
7342
|
-
const invocationId =
|
|
7563
|
+
const invocationId = crypto5.randomUUID();
|
|
7343
7564
|
return new Promise((resolve11, reject) => {
|
|
7344
7565
|
const timer = setTimeout(() => {
|
|
7345
7566
|
pending.delete(invocationId);
|
|
@@ -7373,7 +7594,7 @@ function iii(opts = {}) {
|
|
|
7373
7594
|
}
|
|
7374
7595
|
const wsHandler = createWsHandler({
|
|
7375
7596
|
registerRemoteWorker(ws, name) {
|
|
7376
|
-
const id2 =
|
|
7597
|
+
const id2 = crypto5.randomUUID();
|
|
7377
7598
|
workers.set(id2, { id: id2, name, ws, functions: [], triggers: [] });
|
|
7378
7599
|
return id2;
|
|
7379
7600
|
},
|
|
@@ -7384,7 +7605,7 @@ function iii(opts = {}) {
|
|
|
7384
7605
|
addRemoteFunction(workerId, id2);
|
|
7385
7606
|
},
|
|
7386
7607
|
registerRemoteTrigger(workerId, input) {
|
|
7387
|
-
const tid =
|
|
7608
|
+
const tid = crypto5.randomUUID();
|
|
7388
7609
|
const reg = { id: tid, ...input, workerId };
|
|
7389
7610
|
triggers.set(tid, reg);
|
|
7390
7611
|
const worker = workers.get(workerId);
|
|
@@ -7740,13 +7961,17 @@ export {
|
|
|
7740
7961
|
auth,
|
|
7741
7962
|
compress,
|
|
7742
7963
|
cors,
|
|
7964
|
+
createSSEStream,
|
|
7743
7965
|
createTestServer,
|
|
7744
7966
|
defineConfig,
|
|
7745
7967
|
deleteCookie,
|
|
7746
7968
|
deploy,
|
|
7969
|
+
formatSSE,
|
|
7970
|
+
formatSSEData,
|
|
7747
7971
|
getCookies,
|
|
7748
7972
|
graphql,
|
|
7749
7973
|
health,
|
|
7974
|
+
helmet,
|
|
7750
7975
|
i18n,
|
|
7751
7976
|
iii,
|
|
7752
7977
|
loadEnv,
|
|
@@ -7760,7 +7985,11 @@ export {
|
|
|
7760
7985
|
rateLimit,
|
|
7761
7986
|
redis,
|
|
7762
7987
|
registerWorker,
|
|
7988
|
+
requestId,
|
|
7763
7989
|
runWorkflow,
|
|
7990
|
+
seo,
|
|
7991
|
+
seoMiddleware,
|
|
7992
|
+
seoTags,
|
|
7764
7993
|
serve,
|
|
7765
7994
|
serveStatic,
|
|
7766
7995
|
setCookie,
|
package/dist/seo.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Middleware } from './types.ts';
|
|
2
|
+
import { Router } from './router.ts';
|
|
3
|
+
export interface RobotsRule {
|
|
4
|
+
userAgent?: string;
|
|
5
|
+
allow?: string | string[];
|
|
6
|
+
disallow?: string | string[];
|
|
7
|
+
}
|
|
8
|
+
export interface SitemapUrl {
|
|
9
|
+
loc: string;
|
|
10
|
+
lastmod?: string;
|
|
11
|
+
changefreq?: 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never';
|
|
12
|
+
priority?: number;
|
|
13
|
+
}
|
|
14
|
+
export interface SitemapConfig {
|
|
15
|
+
urls?: SitemapUrl[];
|
|
16
|
+
resolve?: () => SitemapUrl[] | Promise<SitemapUrl[]>;
|
|
17
|
+
cacheTTL?: number;
|
|
18
|
+
}
|
|
19
|
+
export interface SeoHeadersConfig {
|
|
20
|
+
'X-Robots-Tag'?: string | ((path: string) => string | undefined);
|
|
21
|
+
}
|
|
22
|
+
export interface SeoOptions {
|
|
23
|
+
robots?: RobotsRule[];
|
|
24
|
+
sitemap?: SitemapConfig;
|
|
25
|
+
headers?: SeoHeadersConfig;
|
|
26
|
+
baseUrl?: string;
|
|
27
|
+
}
|
|
28
|
+
export declare function seoMiddleware(options?: SeoOptions): Middleware;
|
|
29
|
+
export declare function seo(options?: SeoOptions): Router;
|
|
30
|
+
export interface SeoTagsConfig {
|
|
31
|
+
title?: string;
|
|
32
|
+
description?: string;
|
|
33
|
+
ogImage?: string;
|
|
34
|
+
ogTitle?: string;
|
|
35
|
+
ogDescription?: string;
|
|
36
|
+
twitterCard?: 'summary' | 'summary_large_image';
|
|
37
|
+
canonical?: string;
|
|
38
|
+
}
|
|
39
|
+
export declare function seoTags(config: SeoTagsConfig): string;
|
package/dist/types.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export interface Context {
|
|
|
6
6
|
mountPath?: string;
|
|
7
7
|
locale?: string;
|
|
8
8
|
t?: (key: string, params?: Record<string, string>) => string;
|
|
9
|
+
requestId?: string;
|
|
9
10
|
}
|
|
10
11
|
export type Handler = (req: Request, ctx: Context) => Response | Promise<Response>;
|
|
11
12
|
export type Middleware = (req: Request, ctx: Context, next: Handler) => Response | Promise<Response>;
|