silgi 0.1.0-beta.10 → 0.1.0-beta.12
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/dist/client/adapters/ofetch/index.d.mts +2 -2
- package/dist/client/adapters/ofetch/index.mjs +8 -2
- package/dist/contract.d.mts +52 -1
- package/dist/contract.mjs +90 -1
- package/dist/core/handler.mjs +4 -0
- package/dist/core/router-utils.mjs +18 -13
- package/dist/silgi.mjs +9 -1
- package/package.json +3 -2
|
@@ -36,8 +36,8 @@ interface LinkOptions<TClientContext extends ClientContext = ClientContext> {
|
|
|
36
36
|
onResponse?: FetchOptions['onResponse'];
|
|
37
37
|
onRequestError?: FetchOptions['onRequestError'];
|
|
38
38
|
onResponseError?: FetchOptions['onResponseError'];
|
|
39
|
-
/** Route metadata — pass extractRoutes(router)
|
|
40
|
-
routes?: unknown
|
|
39
|
+
/** Route metadata — pass extractRoutes(router), the full router, or a Promise that resolves to routes. Required when procedures use $route({ path }) */
|
|
40
|
+
routes?: unknown | Promise<unknown>;
|
|
41
41
|
}
|
|
42
42
|
/**
|
|
43
43
|
* Create a Silgi client link powered by ofetch.
|
|
@@ -28,9 +28,15 @@ function createLink(options) {
|
|
|
28
28
|
const defaultRetry = options.retry;
|
|
29
29
|
const defaultRetryDelay = options.retryDelay ?? 0;
|
|
30
30
|
const resolvedProtocol = options.protocol ?? (options.binary ? "messagepack" : void 0) ?? (options.devalue ? "devalue" : void 0) ?? "json";
|
|
31
|
-
|
|
31
|
+
let resolvedRoutes = void 0;
|
|
32
|
+
const routesOption = options.routes;
|
|
33
|
+
if (routesOption && typeof routesOption?.then === "function") routesOption.then((r) => {
|
|
34
|
+
resolvedRoutes = r;
|
|
35
|
+
});
|
|
36
|
+
else resolvedRoutes = routesOption;
|
|
32
37
|
return { async call(path, input, callOptions) {
|
|
33
|
-
|
|
38
|
+
if (routesOption && !resolvedRoutes && typeof routesOption?.then === "function") resolvedRoutes = await routesOption;
|
|
39
|
+
const resolved = resolvedRoutes ? resolveRoute(resolvedRoutes, path) : void 0;
|
|
34
40
|
let urlPath = resolved ? resolved.path : "/" + path.map(encodeURIComponent).join("/");
|
|
35
41
|
if (resolved) {
|
|
36
42
|
const sub = substituteParams(urlPath, input);
|
package/dist/contract.d.mts
CHANGED
|
@@ -30,7 +30,58 @@ type ImplementRouter<T extends ContractRouter> = { [K in keyof T]: T[K] extends
|
|
|
30
30
|
* Type-safe: implementation must match the contract.
|
|
31
31
|
*/
|
|
32
32
|
declare function implement<T extends ContractRouter>(contractDef: T, implementations: ImplementRouter<T>): RouterDef;
|
|
33
|
+
declare class ContractBuilder {
|
|
34
|
+
private _route?;
|
|
35
|
+
private _input?;
|
|
36
|
+
private _output?;
|
|
37
|
+
private _errors?;
|
|
38
|
+
private _type?;
|
|
39
|
+
private _description?;
|
|
40
|
+
route(r: Route): this;
|
|
41
|
+
input<T extends AnySchema>(schema: T): ContractBuilder & {
|
|
42
|
+
_input: T;
|
|
43
|
+
};
|
|
44
|
+
output<T extends AnySchema>(schema: T): ContractBuilder & {
|
|
45
|
+
_output: T;
|
|
46
|
+
};
|
|
47
|
+
errors<T extends ErrorDef>(e: T): ContractBuilder & {
|
|
48
|
+
_errors: T;
|
|
49
|
+
};
|
|
50
|
+
description(d: string): this;
|
|
51
|
+
/** Build the procedure contract */
|
|
52
|
+
build(): ProcedureContract;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Contract builder — oRPC-style chainable API for defining procedure contracts.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```ts
|
|
59
|
+
* import { oc } from 'silgi/contract'
|
|
60
|
+
*
|
|
61
|
+
* const listUsers = oc
|
|
62
|
+
* .route({ method: 'GET', path: '/api/users' })
|
|
63
|
+
* .input(z.object({ limit: z.number().optional() }))
|
|
64
|
+
* .output(z.array(UserSchema))
|
|
65
|
+
* .build()
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
declare const oc: ContractBuilder;
|
|
69
|
+
/**
|
|
70
|
+
* Strip all non-metadata from a contract or router, preserving only
|
|
71
|
+
* { route: { path, method }, type } for each procedure.
|
|
72
|
+
* Safe to serialize to JSON and ship to client bundles.
|
|
73
|
+
*
|
|
74
|
+
* oRPC equivalent: `minifyContractRouter(router)`
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```ts
|
|
78
|
+
* import { minifyContractRouter } from 'silgi/contract'
|
|
79
|
+
* const minified = minifyContractRouter(appRouter)
|
|
80
|
+
* fs.writeFileSync('routes.json', JSON.stringify(minified))
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
declare function minifyContractRouter(def: unknown): Record<string, unknown>;
|
|
33
84
|
/** Infer client type from a contract (no server code needed) */
|
|
34
85
|
type InferContractClient<T extends ContractRouter> = { [K in keyof T]: T[K] extends ProcedureContract<any, infer TInput, infer TOutput> ? TInput extends AnySchema ? (input: InferSchemaInput<TInput>) => Promise<TOutput extends AnySchema ? InferSchemaOutput<TOutput> : unknown> : () => Promise<TOutput extends AnySchema ? InferSchemaOutput<TOutput> : unknown> : T[K] extends ContractRouter ? InferContractClient<T[K]> : never };
|
|
35
86
|
//#endregion
|
|
36
|
-
export { ContractRouter, InferContractClient, ProcedureContract, contract, implement };
|
|
87
|
+
export { ContractRouter, InferContractClient, ProcedureContract, contract, implement, minifyContractRouter, oc };
|
package/dist/contract.mjs
CHANGED
|
@@ -36,5 +36,94 @@ function isProcedureContract(v) {
|
|
|
36
36
|
if (typeof v !== "object" || v === null) return false;
|
|
37
37
|
return "input" in v || "output" in v || "type" in v || "errors" in v;
|
|
38
38
|
}
|
|
39
|
+
var ContractBuilder = class {
|
|
40
|
+
_route;
|
|
41
|
+
_input;
|
|
42
|
+
_output;
|
|
43
|
+
_errors;
|
|
44
|
+
_type;
|
|
45
|
+
_description;
|
|
46
|
+
route(r) {
|
|
47
|
+
this._route = {
|
|
48
|
+
...this._route,
|
|
49
|
+
...r
|
|
50
|
+
};
|
|
51
|
+
return this;
|
|
52
|
+
}
|
|
53
|
+
input(schema) {
|
|
54
|
+
this._input = schema;
|
|
55
|
+
return this;
|
|
56
|
+
}
|
|
57
|
+
output(schema) {
|
|
58
|
+
this._output = schema;
|
|
59
|
+
return this;
|
|
60
|
+
}
|
|
61
|
+
errors(e) {
|
|
62
|
+
this._errors = e;
|
|
63
|
+
return this;
|
|
64
|
+
}
|
|
65
|
+
description(d) {
|
|
66
|
+
this._description = d;
|
|
67
|
+
return this;
|
|
68
|
+
}
|
|
69
|
+
/** Build the procedure contract */
|
|
70
|
+
build() {
|
|
71
|
+
return {
|
|
72
|
+
type: this._type,
|
|
73
|
+
input: this._input,
|
|
74
|
+
output: this._output,
|
|
75
|
+
errors: this._errors,
|
|
76
|
+
route: this._route,
|
|
77
|
+
description: this._description
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* Contract builder — oRPC-style chainable API for defining procedure contracts.
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```ts
|
|
86
|
+
* import { oc } from 'silgi/contract'
|
|
87
|
+
*
|
|
88
|
+
* const listUsers = oc
|
|
89
|
+
* .route({ method: 'GET', path: '/api/users' })
|
|
90
|
+
* .input(z.object({ limit: z.number().optional() }))
|
|
91
|
+
* .output(z.array(UserSchema))
|
|
92
|
+
* .build()
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
const oc = new ContractBuilder();
|
|
96
|
+
/**
|
|
97
|
+
* Strip all non-metadata from a contract or router, preserving only
|
|
98
|
+
* { route: { path, method }, type } for each procedure.
|
|
99
|
+
* Safe to serialize to JSON and ship to client bundles.
|
|
100
|
+
*
|
|
101
|
+
* oRPC equivalent: `minifyContractRouter(router)`
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```ts
|
|
105
|
+
* import { minifyContractRouter } from 'silgi/contract'
|
|
106
|
+
* const minified = minifyContractRouter(appRouter)
|
|
107
|
+
* fs.writeFileSync('routes.json', JSON.stringify(minified))
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
function minifyContractRouter(def) {
|
|
111
|
+
const result = {};
|
|
112
|
+
if (def == null || typeof def !== "object") return result;
|
|
113
|
+
for (const [key, value] of Object.entries(def)) {
|
|
114
|
+
if (typeof value !== "object" || value === null) continue;
|
|
115
|
+
const obj = value;
|
|
116
|
+
const route = obj.route;
|
|
117
|
+
if (route?.path) result[key] = {
|
|
118
|
+
path: route.path,
|
|
119
|
+
method: (route.method ?? "POST").toUpperCase()
|
|
120
|
+
};
|
|
121
|
+
else if ("resolve" in obj || "input" in obj || "output" in obj || "type" in obj) {} else {
|
|
122
|
+
const nested = minifyContractRouter(value);
|
|
123
|
+
if (Object.keys(nested).length > 0) result[key] = nested;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return result;
|
|
127
|
+
}
|
|
39
128
|
//#endregion
|
|
40
|
-
export { contract, implement };
|
|
129
|
+
export { contract, implement, minifyContractRouter, oc };
|
package/dist/core/handler.mjs
CHANGED
|
@@ -120,6 +120,10 @@ function createFetchHandler(routerDef, contextFactory, hooks) {
|
|
|
120
120
|
const reqTrace = analyticsTraceMap.get(request);
|
|
121
121
|
if (reqTrace) ctx.__analyticsTrace = reqTrace;
|
|
122
122
|
if (!route.passthrough) rawInput = await parseInput(request, url, qMark);
|
|
123
|
+
if (match.params && Object.keys(match.params).length > 0) rawInput = rawInput != null && typeof rawInput === "object" ? {
|
|
124
|
+
...match.params,
|
|
125
|
+
...rawInput
|
|
126
|
+
} : match.params;
|
|
123
127
|
callHook("request", {
|
|
124
128
|
path: pathname,
|
|
125
129
|
input: rawInput
|
|
@@ -31,18 +31,26 @@ function assignPaths(def, prefix = []) {
|
|
|
31
31
|
function extractRoutes(def, prefix = []) {
|
|
32
32
|
const result = {};
|
|
33
33
|
if (def == null || typeof def !== "object") return result;
|
|
34
|
-
for (const [key, value] of Object.entries(def))
|
|
35
|
-
const route = value
|
|
34
|
+
for (const [key, value] of Object.entries(def)) {
|
|
35
|
+
const route = getRouteFromEntry(value);
|
|
36
36
|
if (route?.path) result[key] = {
|
|
37
37
|
path: route.path,
|
|
38
38
|
method: (route.method ?? "POST").toUpperCase()
|
|
39
39
|
};
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
else if (typeof value === "object" && value !== null && !isProcedureDef(value)) {
|
|
41
|
+
const nested = extractRoutes(value, [...prefix, key]);
|
|
42
|
+
if (Object.keys(nested).length > 0) result[key] = nested;
|
|
43
|
+
}
|
|
43
44
|
}
|
|
44
45
|
return result;
|
|
45
46
|
}
|
|
47
|
+
/** Extract route metadata from a ProcedureDef or ProcedureContract */
|
|
48
|
+
function getRouteFromEntry(value) {
|
|
49
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
50
|
+
const obj = value;
|
|
51
|
+
if (isProcedureDef(value)) return obj.route;
|
|
52
|
+
if ("route" in obj && typeof obj.route === "object" && obj.route !== null) return obj.route;
|
|
53
|
+
}
|
|
46
54
|
/** Check if a value is an extracted route leaf (has path + method, not a nested object) */
|
|
47
55
|
function isExtractedRoute(value) {
|
|
48
56
|
return typeof value === "object" && value !== null && "path" in value && "method" in value && !("resolve" in value);
|
|
@@ -55,14 +63,11 @@ function resolveRoute(routes, path) {
|
|
|
55
63
|
current = current[segment];
|
|
56
64
|
}
|
|
57
65
|
if (isExtractedRoute(current)) return current;
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
method: (route.method ?? "POST").toUpperCase()
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
+
const route = getRouteFromEntry(current);
|
|
67
|
+
if (route?.path) return {
|
|
68
|
+
path: route.path,
|
|
69
|
+
method: (route.method ?? "POST").toUpperCase()
|
|
70
|
+
};
|
|
66
71
|
}
|
|
67
72
|
/**
|
|
68
73
|
* Substitute :param placeholders in a route path with values from input.
|
package/dist/silgi.mjs
CHANGED
|
@@ -142,12 +142,20 @@ function silgi(config) {
|
|
|
142
142
|
serve: async (routerDef, options) => {
|
|
143
143
|
const { createServeHandler } = await import("./core/serve.mjs");
|
|
144
144
|
const server = await createServeHandler(routerDef, contextFactory, hooks, options);
|
|
145
|
-
const { collectCronTasks, startCronJobs } = await import("./core/task.mjs");
|
|
145
|
+
const { collectCronTasks, startCronJobs, stopCronJobs } = await import("./core/task.mjs");
|
|
146
146
|
const cronTasks = collectCronTasks(routerDef);
|
|
147
147
|
if (cronTasks.length > 0) {
|
|
148
148
|
await startCronJobs(cronTasks);
|
|
149
149
|
console.log(` ${cronTasks.length} cron task(s) scheduled`);
|
|
150
150
|
}
|
|
151
|
+
const originalClose = server.close.bind(server);
|
|
152
|
+
server.close = async (force) => {
|
|
153
|
+
stopCronJobs();
|
|
154
|
+
return originalClose(force);
|
|
155
|
+
};
|
|
156
|
+
const onSignal = () => stopCronJobs();
|
|
157
|
+
process.once("SIGINT", onSignal);
|
|
158
|
+
process.once("SIGTERM", onSignal);
|
|
151
159
|
return server;
|
|
152
160
|
}
|
|
153
161
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "silgi",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.12",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "The fastest end-to-end type-safe RPC framework for TypeScript — compiled pipelines, single package, every runtime",
|
|
6
6
|
"keywords": [
|
|
@@ -270,6 +270,7 @@
|
|
|
270
270
|
"ocache": "^0.1.4",
|
|
271
271
|
"ofetch": "2.0.0-alpha.3",
|
|
272
272
|
"ohash": "^2.0.11",
|
|
273
|
+
"silgi": "link:",
|
|
273
274
|
"srvx": "^0.11.13",
|
|
274
275
|
"unstorage": "^2.0.0-alpha.7"
|
|
275
276
|
},
|
|
@@ -333,7 +334,7 @@
|
|
|
333
334
|
"packageManager": "pnpm@10.33.0",
|
|
334
335
|
"pnpm": {
|
|
335
336
|
"overrides": {
|
|
336
|
-
"silgi": "
|
|
337
|
+
"silgi": "link:"
|
|
337
338
|
}
|
|
338
339
|
}
|
|
339
340
|
}
|