Skip to content

Commit fc6e1c1

Browse files
committed
pick: tegg service worker
1 parent bc1a0f7 commit fc6e1c1

63 files changed

Lines changed: 2333 additions & 1 deletion

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

pnpm-workspace.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ catalog:
209209
type-is: ^2.0.0
210210
typebox: ^1.0.65
211211
typescript: ^5.9.3
212+
undici: ^7.10.0
212213
unplugin-unused: ^0.5.4
213214
urijs: ^1.19.11
214215
urllib: ^4.8.2
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { type EggProtoImplClass, ImplDecorator, SingletonProtoParams } from '@eggjs/tegg-types';
2+
import { QualifierImplDecoratorUtil, SingletonProto } from '@eggjs/tegg';
3+
4+
export abstract class AbstractEventHandler<E = any, R = any> {
5+
abstract handleEvent(event: E): Promise<R>;
6+
}
7+
8+
export const EVENT_HANDLER_ATTRIBUTE = Symbol('EVENT_HANDLER_ATTRIBUTE');
9+
10+
export type EventType = Record<string, string>;
11+
12+
export const EventHandler: ImplDecorator<AbstractEventHandler, EventType> =
13+
QualifierImplDecoratorUtil.generatorDecorator(AbstractEventHandler, EVENT_HANDLER_ATTRIBUTE);
14+
15+
export const EventHandlerProto = (type: EventType[keyof EventType], params?: SingletonProtoParams) => {
16+
return (clazz: EggProtoImplClass<AbstractEventHandler>) => {
17+
EventHandler(type)(clazz);
18+
SingletonProto(params)(clazz);
19+
};
20+
};
21+
22+
export interface FetchEventLike {
23+
type: string;
24+
request: Request;
25+
respondWith(response: Response | Promise<Response>): void;
26+
waitUntil?(promise: Promise<unknown>): void;
27+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from './typing.ts';
22
export * from './util/index.ts';
33
export * from './decorator/index.ts';
4+
export * from './event/EventHandler.ts';

tegg/core/test-util/package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,13 @@
2929
"types": "./dist/index.d.ts",
3030
"exports": {
3131
".": "./src/index.ts",
32+
"./StandaloneTestUtil": "./src/StandaloneTestUtil.ts",
3233
"./package.json": "./package.json"
3334
},
3435
"publishConfig": {
3536
"exports": {
3637
".": "./dist/index.js",
38+
"./StandaloneTestUtil": "./dist/StandaloneTestUtil.js",
3739
"./package.json": "./package.json"
3840
}
3941
},
@@ -47,7 +49,9 @@
4749
"@eggjs/tegg-common-util": "workspace:*",
4850
"@eggjs/tegg-loader": "workspace:*",
4951
"@eggjs/tegg-runtime": "workspace:*",
50-
"globby": "catalog:"
52+
"@eggjs/tegg-types": "workspace:*",
53+
"globby": "catalog:",
54+
"undici": "catalog:"
5155
},
5256
"devDependencies": {
5357
"@types/node": "catalog:",
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { createServer, IncomingMessage, OutgoingHttpHeaders, Server, ServerOptions, ServerResponse } from 'node:http';
2+
import { pipeline } from 'node:stream';
3+
import { Headers, BodyInit, Request, Response } from 'undici';
4+
import { FetchEvent } from '@eggjs/tegg-types/standalone';
5+
6+
export type FetchEventListener = (event: FetchEvent) => Promise<Response>;
7+
8+
export interface StartHTTPServerOptions extends ServerOptions {
9+
listener: FetchEventListener;
10+
}
11+
12+
export class StandaloneTestUtil {
13+
static skipOnNode(minVersion = 18) {
14+
const version = parseInt(process.versions.node.split('.')[0], 10);
15+
return version < minVersion;
16+
}
17+
18+
static #buildRequest(req: IncomingMessage): Request {
19+
const origin = `http://${req.headers.host ?? 'localhost'}`;
20+
const url = new URL(req.url ?? '', origin);
21+
22+
const body: BodyInit | null = req.method === 'GET' || req.method === 'HEAD' ? null : req;
23+
24+
req.headers.host = url.host;
25+
26+
const headers = new Headers();
27+
for (const [ name, values ] of Object.entries(req.headers)) {
28+
if (Array.isArray(values)) {
29+
for (const value of values) {
30+
headers.append(name, value);
31+
}
32+
} else if (values !== undefined) {
33+
headers.append(name, values);
34+
}
35+
}
36+
37+
return new Request(url, {
38+
method: req.method,
39+
headers,
40+
body,
41+
duplex: body ? 'half' : undefined,
42+
});
43+
}
44+
45+
static #createHTTPServerListener(listener: FetchEventListener) {
46+
return async (req: IncomingMessage, res: ServerResponse) => {
47+
const request = StandaloneTestUtil.#buildRequest(req);
48+
// TODO currently fake FetchEvent
49+
const event: any = new Event('fetch');
50+
event.request = request;
51+
const response = await listener(event);
52+
53+
const headers: OutgoingHttpHeaders = {};
54+
for (const [ key, value ] of response.headers) {
55+
headers[key.toLowerCase()] = value;
56+
}
57+
58+
res.writeHead(response.status, headers);
59+
60+
if (!response.body) {
61+
res.end();
62+
return;
63+
}
64+
pipeline(response.body, res, e => {
65+
if (e) {
66+
console.error(`pipeline writing response error for url ${response.url}`, e);
67+
res.end();
68+
}
69+
});
70+
};
71+
}
72+
73+
static startHTTPServer(host: string, port: number, { listener, ...options }: StartHTTPServerOptions) {
74+
const serverListener = StandaloneTestUtil.#createHTTPServerListener(listener);
75+
const server = createServer(options ?? {}, serverListener);
76+
77+
return new Promise<Server>(resolve => {
78+
server.listen(port, host, () => resolve(server));
79+
});
80+
}
81+
}

tegg/core/types/package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,9 @@
124124
"./runtime/model/EggObject": "./src/runtime/model/EggObject.ts",
125125
"./runtime/model/LoadUnitInstance": "./src/runtime/model/LoadUnitInstance.ts",
126126
"./schedule": "./src/schedule.ts",
127+
"./standalone": "./standalone/index.ts",
128+
"./standalone/fetch": "./standalone/fetch.ts",
129+
"./standalone/ServiceWorkerContext": "./standalone/ServiceWorkerContext.ts",
127130
"./transaction": "./src/transaction.ts",
128131
"./package.json": "./package.json"
129132
},
@@ -227,6 +230,9 @@
227230
"./runtime/model/EggObject": "./dist/runtime/model/EggObject.js",
228231
"./runtime/model/LoadUnitInstance": "./dist/runtime/model/LoadUnitInstance.js",
229232
"./schedule": "./dist/schedule.js",
233+
"./standalone": "./dist/standalone/index.js",
234+
"./standalone/fetch": "./dist/standalone/fetch.js",
235+
"./standalone/ServiceWorkerContext": "./dist/standalone/ServiceWorkerContext.js",
230236
"./transaction": "./dist/transaction.js",
231237
"./package.json": "./package.json"
232238
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { FetchEvent } from './fetch.ts';
2+
3+
export interface ServiceWorkerContextInit<T> {
4+
event: T;
5+
}
6+
7+
export interface ServiceWorkerContext<Event, Response> {
8+
event: Event;
9+
get response(): Response | undefined;
10+
set response(response: Response);
11+
12+
get body(): any | undefined;
13+
set body(body: any);
14+
}
15+
16+
export type ServiceWorkerFetchContext = ServiceWorkerContext<FetchEvent, Response>;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export interface FetchEvent extends Event {
2+
request: Request;
3+
waitUntil(f: Promise<any>): void;
4+
respondWith(r: Response | PromiseLike<Response>): void;
5+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './fetch.ts';
2+
export * from './ServiceWorkerContext.ts';
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
{
2+
"name": "@eggjs/tegg-service-worker",
3+
"version": "4.0.0-beta.29",
4+
"private": true,
5+
"description": "tegg service worker",
6+
"keywords": [
7+
"egg",
8+
"typescript",
9+
"tegg",
10+
"standalone",
11+
"service worker"
12+
],
13+
"homepage": "https://github.com/eggjs/egg/tree/next/tegg/standalone/service-worker",
14+
"bugs": {
15+
"url": "https://github.com/eggjs/egg/issues"
16+
},
17+
"license": "MIT",
18+
"author": "killagu <killa123@126.com>",
19+
"repository": {
20+
"type": "git",
21+
"url": "git+https://github.com/eggjs/egg.git",
22+
"directory": "tegg/standalone/service-worker"
23+
},
24+
"files": [
25+
"dist"
26+
],
27+
"type": "module",
28+
"main": "./dist/index.js",
29+
"module": "./dist/index.js",
30+
"types": "./dist/index.d.ts",
31+
"exports": {
32+
".": "./src/index.ts",
33+
"./package.json": "./package.json"
34+
},
35+
"publishConfig": {
36+
"exports": {
37+
".": "./dist/index.js",
38+
"./package.json": "./package.json"
39+
}
40+
},
41+
"scripts": {
42+
"typecheck": "tsgo --noEmit"
43+
},
44+
"dependencies": {
45+
"@eggjs/router": "workspace:*",
46+
"@eggjs/ajv-plugin": "workspace:*",
47+
"@eggjs/tegg-aop-runtime": "workspace:*",
48+
"@eggjs/tegg-dal-plugin": "workspace:*",
49+
"@eggjs/tegg-dynamic-inject-runtime": "workspace:*",
50+
"@eggjs/tegg-lifecycle": "workspace:*",
51+
"@eggjs/tegg-metadata": "workspace:*",
52+
"@eggjs/tegg-standalone": "workspace:*",
53+
"@eggjs/tegg-types": "workspace:*",
54+
"@modelcontextprotocol/sdk": "1.24.3",
55+
"egg-errors": "catalog:",
56+
"path-to-regexp": "catalog:path-to-regexp1",
57+
"type-is": "catalog:",
58+
"urllib": "catalog:"
59+
},
60+
"peerDependencies": {
61+
"@eggjs/tegg": "workspace:*"
62+
},
63+
"devDependencies": {
64+
"@eggjs/module-test-util": "workspace:*",
65+
"@eggjs/tegg": "workspace:*",
66+
"@types/node": "catalog:",
67+
"@types/type-is": "catalog:",
68+
"typescript": "catalog:",
69+
"undici": "catalog:"
70+
},
71+
"engines": {
72+
"node": ">=22.18.0"
73+
}
74+
}

0 commit comments

Comments
 (0)