silgi 0.1.0-beta.7 → 0.1.0-beta.9
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/builder.d.mts +9 -5
- package/dist/builder.mjs +1 -1
- package/dist/client/client.d.mts +2 -7
- package/dist/client/client.mjs +1 -6
- package/dist/core/handler.mjs +3 -0
- package/dist/core/serve.mjs +2 -2
- package/dist/core/storage.d.mts +5 -6
- package/dist/core/storage.mjs +30 -32
- package/dist/index.mjs +1 -1
- package/dist/integrations/pinia-colada/index.d.mts +2 -2
- package/dist/integrations/pinia-colada/types.d.mts +3 -4
- package/dist/plugins/analytics.d.mts +6 -3
- package/dist/plugins/analytics.mjs +117 -14
- package/dist/plugins/index.mjs +1 -1
- package/dist/silgi.mjs +5 -1
- package/dist/types.d.mts +3 -1
- package/package.json +17 -15
package/dist/builder.d.mts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { AnySchema, InferSchemaInput, InferSchemaOutput } from "./core/schema.mjs";
|
|
2
|
-
import { ErrorDef,
|
|
2
|
+
import { ErrorDef, GuardDef, Meta, ProcedureDef, ProcedureType, ResolveContext, Route, WrapDef } from "./types.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/builder.d.ts
|
|
5
5
|
/** Initial builder — no input, no output, no errors yet */
|
|
6
6
|
interface ProcedureBuilder<TType extends ProcedureType, TBaseCtx extends Record<string, unknown>, TCtx extends Record<string, unknown> = TBaseCtx, TInput = undefined, TErrors extends ErrorDef = {}> {
|
|
7
|
-
/** Add
|
|
8
|
-
$use<
|
|
7
|
+
/** Add a guard — enriches context with guard return type */
|
|
8
|
+
$use<TReturn extends Record<string, unknown> | void, TGErrors extends ErrorDef = {}>(guard: GuardDef<TCtx, TReturn, TGErrors>): ProcedureBuilder<TType, TBaseCtx, TReturn extends Record<string, unknown> ? TCtx & TReturn : TCtx, TInput, TGErrors & TErrors>;
|
|
9
|
+
/** Add a wrap middleware — does not change context type */
|
|
10
|
+
$use(wrap: WrapDef<TCtx>): ProcedureBuilder<TType, TBaseCtx, TCtx, TInput, TErrors>;
|
|
9
11
|
/** Set input schema */
|
|
10
12
|
$input<TSchema extends AnySchema>(schema: TSchema): ProcedureBuilder<TType, TBaseCtx, TCtx, InferSchemaOutput<TSchema>, TErrors>;
|
|
11
13
|
/** Set output schema — enables return type autocomplete */
|
|
@@ -21,8 +23,10 @@ interface ProcedureBuilder<TType extends ProcedureType, TBaseCtx extends Record<
|
|
|
21
23
|
}
|
|
22
24
|
/** Builder after .$output() — return type is constrained for autocomplete */
|
|
23
25
|
interface ProcedureBuilderWithOutput<TType extends ProcedureType, TBaseCtx extends Record<string, unknown>, TCtx extends Record<string, unknown>, TInput, TOutputResolved, TErrors extends ErrorDef> {
|
|
24
|
-
/** Add
|
|
25
|
-
$use(
|
|
26
|
+
/** Add a guard — enriches context with guard return type */
|
|
27
|
+
$use<TReturn extends Record<string, unknown> | void, TGErrors extends ErrorDef = {}>(guard: GuardDef<TCtx, TReturn, TGErrors>): ProcedureBuilderWithOutput<TType, TBaseCtx, TReturn extends Record<string, unknown> ? TCtx & TReturn : TCtx, TInput, TOutputResolved, TGErrors & TErrors>;
|
|
28
|
+
/** Add a wrap middleware — does not change context type */
|
|
29
|
+
$use(wrap: WrapDef<TCtx>): ProcedureBuilderWithOutput<TType, TBaseCtx, TCtx, TInput, TOutputResolved, TErrors>;
|
|
26
30
|
/** Set typed errors */
|
|
27
31
|
$errors<TNewErrors extends ErrorDef>(errors: TNewErrors): ProcedureBuilderWithOutput<TType, TBaseCtx, TCtx, TInput, TOutputResolved, TNewErrors & TErrors>;
|
|
28
32
|
/** Set route metadata */
|
package/dist/builder.mjs
CHANGED
package/dist/client/client.d.mts
CHANGED
|
@@ -5,16 +5,11 @@ import { ClientContext, ClientLink } from "./types.mjs";
|
|
|
5
5
|
/**
|
|
6
6
|
* Create a type-safe client from a link.
|
|
7
7
|
*
|
|
8
|
-
* Accepts either a router type (auto-inferred) or a pre-inferred client type:
|
|
9
8
|
* ```ts
|
|
10
|
-
*
|
|
11
|
-
* const client = createClient<AppRouter>(link)
|
|
12
|
-
*
|
|
13
|
-
* // Also works — explicit InferClient
|
|
14
|
-
* const client = createClient<InferClient<AppRouter>>(link)
|
|
9
|
+
* const client = createClient<typeof appRouter>(link)
|
|
15
10
|
* ```
|
|
16
11
|
*/
|
|
17
|
-
declare function createClient<T, TClientContext extends ClientContext = Record<never, never>>(link: ClientLink<TClientContext>): InferClient<T
|
|
12
|
+
declare function createClient<T, TClientContext extends ClientContext = Record<never, never>>(link: ClientLink<TClientContext>): InferClient<T>;
|
|
18
13
|
/**
|
|
19
14
|
* Safe client wrapper — returns [error, data] tuples instead of throwing.
|
|
20
15
|
*/
|
package/dist/client/client.mjs
CHANGED
|
@@ -2,13 +2,8 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Create a type-safe client from a link.
|
|
4
4
|
*
|
|
5
|
-
* Accepts either a router type (auto-inferred) or a pre-inferred client type:
|
|
6
5
|
* ```ts
|
|
7
|
-
*
|
|
8
|
-
* const client = createClient<AppRouter>(link)
|
|
9
|
-
*
|
|
10
|
-
* // Also works — explicit InferClient
|
|
11
|
-
* const client = createClient<InferClient<AppRouter>>(link)
|
|
6
|
+
* const client = createClient<typeof appRouter>(link)
|
|
12
7
|
* ```
|
|
13
8
|
*/
|
|
14
9
|
function createClient(link) {
|
package/dist/core/handler.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { routerCache } from "./router-utils.mjs";
|
|
2
2
|
import { compileRouter, createContext, releaseContext } from "../compile.mjs";
|
|
3
3
|
import { applyContext } from "./dispatch.mjs";
|
|
4
|
+
import { analyticsTraceMap } from "../plugins/analytics.mjs";
|
|
4
5
|
import { detectResponseFormat, encodeResponse, makeErrorResponse } from "./codec.mjs";
|
|
5
6
|
import { parseInput } from "./input.mjs";
|
|
6
7
|
import { iteratorToEventStream } from "./sse.mjs";
|
|
@@ -116,6 +117,8 @@ function createFetchHandler(routerDef, contextFactory, hooks) {
|
|
|
116
117
|
const baseCtxResult = contextFactory(request);
|
|
117
118
|
applyContext(ctx, baseCtxResult instanceof Promise ? await baseCtxResult : baseCtxResult);
|
|
118
119
|
if (match.params) ctx.params = match.params;
|
|
120
|
+
const reqTrace = analyticsTraceMap.get(request);
|
|
121
|
+
if (reqTrace) ctx.__analyticsTrace = reqTrace;
|
|
119
122
|
if (!route.passthrough) rawInput = await parseInput(request, url, qMark);
|
|
120
123
|
callHook("request", {
|
|
121
124
|
path: pathname,
|
package/dist/core/serve.mjs
CHANGED
|
@@ -44,8 +44,8 @@ async function createServeHandler(routerDef, contextFactory, hooks, options) {
|
|
|
44
44
|
const url = rawUrl.endsWith("/") ? rawUrl.slice(0, -1) : rawUrl;
|
|
45
45
|
if (options?.ws && server.node?.server) {
|
|
46
46
|
const { attachWebSocket } = await import("../ws.mjs");
|
|
47
|
-
const
|
|
48
|
-
await attachWebSocket(
|
|
47
|
+
const wsOpts = typeof options.ws === "object" ? options.ws : void 0;
|
|
48
|
+
await attachWebSocket(server.node.server, routerDef, wsOpts);
|
|
49
49
|
}
|
|
50
50
|
console.log(`\nSilgi server running at ${url}`);
|
|
51
51
|
if (options?.http2) console.log(` HTTP/2 enabled (with HTTP/1.1 fallback)`);
|
package/dist/core/storage.d.mts
CHANGED
|
@@ -3,16 +3,15 @@ import { Driver, Storage, StorageValue } from "unstorage";
|
|
|
3
3
|
//#region src/core/storage.d.ts
|
|
4
4
|
/** Storage config — map of mount path → driver instance, or a pre-built Storage */
|
|
5
5
|
type StorageConfig = Storage | Record<string, Driver>;
|
|
6
|
-
/**
|
|
7
|
-
* Initialize storage from config — called once at startup.
|
|
8
|
-
* Accepts either a pre-built Storage or a map of mount paths → drivers.
|
|
9
|
-
*/
|
|
10
|
-
declare function initStorage(config?: StorageConfig): Storage;
|
|
11
6
|
/**
|
|
12
7
|
* Get the storage instance with optional prefix.
|
|
13
|
-
* Creates default
|
|
8
|
+
* Creates default storage with `data` and `cache` mounts on first call.
|
|
14
9
|
*/
|
|
15
10
|
declare function useStorage<T extends StorageValue = StorageValue>(base?: string): Storage<T>;
|
|
11
|
+
/**
|
|
12
|
+
* Initialize storage from config — call once at startup.
|
|
13
|
+
*/
|
|
14
|
+
declare function initStorage(config?: StorageConfig): Storage;
|
|
16
15
|
/**
|
|
17
16
|
* Reset storage — for testing.
|
|
18
17
|
*/
|
package/dist/core/storage.mjs
CHANGED
|
@@ -1,63 +1,61 @@
|
|
|
1
1
|
import { createStorage, prefixStorage } from "unstorage";
|
|
2
|
+
import memoryDriver from "unstorage/drivers/memory";
|
|
2
3
|
//#region src/core/storage.ts
|
|
3
4
|
/**
|
|
4
|
-
* Storage
|
|
5
|
+
* Storage — Nitro-style global storage with unstorage.
|
|
5
6
|
*
|
|
6
|
-
* Two
|
|
7
|
+
* Two default mounts are created automatically:
|
|
8
|
+
* - `data` — persistent data (analytics, sessions, etc.)
|
|
9
|
+
* - `cache` — ephemeral cache (query results, SWR, etc.)
|
|
10
|
+
*
|
|
11
|
+
* Both use in-memory drivers by default. Override with custom drivers:
|
|
7
12
|
*
|
|
8
|
-
* 1. Declarative config (type-safe driver options):
|
|
9
13
|
* ```ts
|
|
10
14
|
* import redisDriver from 'unstorage/drivers/redis'
|
|
11
|
-
* import
|
|
15
|
+
* import fsDriver from 'unstorage/drivers/fs'
|
|
12
16
|
*
|
|
13
17
|
* const s = silgi({
|
|
14
18
|
* context: () => ({}),
|
|
15
19
|
* storage: {
|
|
20
|
+
* data: fsDriver({ base: '.data' }),
|
|
16
21
|
* cache: redisDriver({ url: 'redis://localhost' }),
|
|
17
|
-
* sessions: memoryDriver(),
|
|
18
22
|
* },
|
|
19
23
|
* })
|
|
20
|
-
* ```
|
|
21
|
-
*
|
|
22
|
-
* 2. Bring your own storage instance:
|
|
23
|
-
* ```ts
|
|
24
|
-
* const storage = createStorage({})
|
|
25
|
-
* storage.mount('cache', redisDriver({ ... }))
|
|
26
24
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* })
|
|
25
|
+
* // In procedures:
|
|
26
|
+
* const data = useStorage('data')
|
|
27
|
+
* const cache = useStorage('cache')
|
|
31
28
|
* ```
|
|
32
29
|
*/
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
if (config && "getItem" in config) {
|
|
41
|
-
_storage = config;
|
|
42
|
-
return _storage;
|
|
43
|
-
}
|
|
44
|
-
_storage = createStorage({});
|
|
45
|
-
if (config) for (const [path, driver] of Object.entries(config)) _storage.mount(path, driver);
|
|
46
|
-
return _storage;
|
|
30
|
+
function _initStorage(config) {
|
|
31
|
+
if (config && "getItem" in config) return config;
|
|
32
|
+
const storage = createStorage({});
|
|
33
|
+
storage.mount("data", memoryDriver());
|
|
34
|
+
storage.mount("cache", memoryDriver());
|
|
35
|
+
if (config) for (const [path, driver] of Object.entries(config)) storage.mount(path, driver);
|
|
36
|
+
return storage;
|
|
47
37
|
}
|
|
48
38
|
/**
|
|
49
39
|
* Get the storage instance with optional prefix.
|
|
50
|
-
* Creates default
|
|
40
|
+
* Creates default storage with `data` and `cache` mounts on first call.
|
|
51
41
|
*/
|
|
52
42
|
function useStorage(base = "") {
|
|
53
|
-
const storage = _storage
|
|
43
|
+
const storage = useStorage._storage ??= _initStorage();
|
|
54
44
|
return base ? prefixStorage(storage, base) : storage;
|
|
55
45
|
}
|
|
56
46
|
/**
|
|
47
|
+
* Initialize storage from config — call once at startup.
|
|
48
|
+
*/
|
|
49
|
+
function initStorage(config) {
|
|
50
|
+
const storage = _initStorage(config);
|
|
51
|
+
useStorage._storage = storage;
|
|
52
|
+
return storage;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
57
55
|
* Reset storage — for testing.
|
|
58
56
|
*/
|
|
59
57
|
function resetStorage() {
|
|
60
|
-
_storage = void 0;
|
|
58
|
+
useStorage._storage = void 0;
|
|
61
59
|
}
|
|
62
60
|
//#endregion
|
|
63
61
|
export { initStorage, resetStorage, useStorage };
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { SilgiError, isDefinedError, toSilgiError } from "./core/error.mjs";
|
|
2
2
|
import { ValidationError, type, validateSchema } from "./core/schema.mjs";
|
|
3
3
|
import { compileProcedure, compileRouter, createContext } from "./compile.mjs";
|
|
4
|
+
import { initStorage, resetStorage, useStorage } from "./core/storage.mjs";
|
|
4
5
|
import { AsyncIteratorClass, mapAsyncIterator } from "./core/iterator.mjs";
|
|
5
6
|
import { getEventMeta, withEventMeta } from "./core/sse.mjs";
|
|
6
7
|
import { silgi } from "./silgi.mjs";
|
|
@@ -8,6 +9,5 @@ import { callable } from "./callable.mjs";
|
|
|
8
9
|
import { lifecycleWrap } from "./lifecycle.mjs";
|
|
9
10
|
import { mapInput } from "./map-input.mjs";
|
|
10
11
|
import { isLazy, lazy, resolveLazy } from "./lazy.mjs";
|
|
11
|
-
import { initStorage, resetStorage, useStorage } from "./core/storage.mjs";
|
|
12
12
|
import { generateOpenAPI, scalarHTML } from "./scalar.mjs";
|
|
13
13
|
export { AsyncIteratorClass, SilgiError, ValidationError, callable, compileProcedure, compileRouter, createContext, generateOpenAPI, getEventMeta, initStorage, isDefinedError, isLazy, lazy, lifecycleWrap, mapAsyncIterator, mapInput, resetStorage, resolveLazy, scalarHTML, silgi, toSilgiError, type, useStorage, validateSchema, withEventMeta };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { BuildKeyOptions, PartialDeep, buildKey } from "./key.mjs";
|
|
2
2
|
import { GeneralUtils, createGeneralUtils } from "./general-utils.mjs";
|
|
3
|
-
import { MaybeOptionalOptions, MutationOptions, MutationOptionsIn, QueryOptions, QueryOptionsIn,
|
|
3
|
+
import { MaybeOptionalOptions, MutationOptions, MutationOptionsIn, QueryOptions, QueryOptionsIn, UseQueryFnContext } from "./types.mjs";
|
|
4
4
|
import { CreateProcedureUtilsOptions, ProcedureUtils, createProcedureUtils } from "./procedure-utils.mjs";
|
|
5
5
|
import { CreateRouterUtilsOptions, RouterUtils, createRouterUtils } from "./router-utils.mjs";
|
|
6
|
-
export { BuildKeyOptions, CreateProcedureUtilsOptions, CreateRouterUtilsOptions, GeneralUtils, MaybeOptionalOptions, MutationOptions, MutationOptionsIn, PartialDeep, ProcedureUtils, QueryOptions, QueryOptionsIn, RouterUtils,
|
|
6
|
+
export { BuildKeyOptions, CreateProcedureUtilsOptions, CreateRouterUtilsOptions, GeneralUtils, MaybeOptionalOptions, MutationOptions, MutationOptionsIn, PartialDeep, ProcedureUtils, QueryOptions, QueryOptionsIn, RouterUtils, UseQueryFnContext, buildKey, createRouterUtils as createColadaUtils, createGeneralUtils, createProcedureUtils, createRouterUtils };
|
|
@@ -3,7 +3,6 @@ import { MaybeRefOrGetter } from "vue";
|
|
|
3
3
|
import { UseMutationOptions, UseQueryOptions } from "@pinia/colada";
|
|
4
4
|
|
|
5
5
|
//#region src/integrations/pinia-colada/types.d.ts
|
|
6
|
-
type SetOptional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
|
|
7
6
|
type MaybeOptionalOptions<T> = Partial<T> extends T ? [options?: T] : [options: T];
|
|
8
7
|
type UseQueryFnContext = Parameters<UseQueryOptions<any>['query']>[0];
|
|
9
8
|
type QueryOptionsIn<TClientContext extends ClientContext, TInput, TOutput, TError, TInitialData extends TOutput | undefined> = (undefined extends TInput ? {
|
|
@@ -14,13 +13,13 @@ type QueryOptionsIn<TClientContext extends ClientContext, TInput, TOutput, TErro
|
|
|
14
13
|
context?: MaybeRefOrGetter<TClientContext>;
|
|
15
14
|
} : {
|
|
16
15
|
context: MaybeRefOrGetter<TClientContext>;
|
|
17
|
-
}) &
|
|
16
|
+
}) & Partial<QueryOptions<TOutput, TError, TInitialData>>;
|
|
18
17
|
type QueryOptions<TOutput, TError, TInitialData extends TOutput | undefined> = UseQueryOptions<TOutput, TError, TInitialData>;
|
|
19
18
|
type MutationOptionsIn<TClientContext extends ClientContext, TInput, TOutput, TError, TMutationContext extends Record<any, any>> = (Record<never, never> extends TClientContext ? {
|
|
20
19
|
context?: MaybeRefOrGetter<TClientContext>;
|
|
21
20
|
} : {
|
|
22
21
|
context: MaybeRefOrGetter<TClientContext>;
|
|
23
|
-
}) &
|
|
22
|
+
}) & Partial<MutationOptions<TInput, TOutput, TError, TMutationContext>>;
|
|
24
23
|
type MutationOptions<TInput, TOutput, TError, TMutationContext extends Record<any, any>> = UseMutationOptions<TOutput, TInput, TError, TMutationContext>;
|
|
25
24
|
//#endregion
|
|
26
|
-
export { MaybeOptionalOptions, MutationOptions, MutationOptionsIn, QueryOptions, QueryOptionsIn,
|
|
25
|
+
export { MaybeOptionalOptions, MutationOptions, MutationOptionsIn, QueryOptions, QueryOptionsIn, UseQueryFnContext };
|
|
@@ -91,6 +91,8 @@ interface AnalyticsOptions {
|
|
|
91
91
|
* - `undefined` — no auth (open access, NOT recommended in production)
|
|
92
92
|
*/
|
|
93
93
|
auth?: string | ((req: Request) => boolean | Promise<boolean>);
|
|
94
|
+
/** Interval in ms between storage flushes (default: 5000) */
|
|
95
|
+
flushInterval?: number;
|
|
94
96
|
}
|
|
95
97
|
interface ProcedureSnapshot {
|
|
96
98
|
count: number;
|
|
@@ -165,8 +167,9 @@ declare class AnalyticsCollector {
|
|
|
165
167
|
recordError(path: string, durationMs: number, errorMsg: string): void;
|
|
166
168
|
recordDetailedError(entry: Omit<ErrorEntry, 'id'>): void;
|
|
167
169
|
recordDetailedRequest(entry: Omit<RequestEntry, 'id'>): void;
|
|
168
|
-
getErrors(): ErrorEntry[]
|
|
169
|
-
getRequests(): RequestEntry[]
|
|
170
|
+
getErrors(): Promise<ErrorEntry[]>;
|
|
171
|
+
getRequests(): Promise<RequestEntry[]>;
|
|
172
|
+
dispose(): Promise<void>;
|
|
170
173
|
toJSON(): AnalyticsSnapshot;
|
|
171
174
|
}
|
|
172
175
|
declare class RequestAccumulator {
|
|
@@ -193,7 +196,7 @@ declare function sanitizeHeaders(headers: Headers): Record<string, string>;
|
|
|
193
196
|
/** Return auth-failure response for analytics routes. */
|
|
194
197
|
declare function analyticsAuthResponse(pathname: string): Response;
|
|
195
198
|
/** Serve analytics dashboard and API routes. */
|
|
196
|
-
declare function serveAnalyticsRoute(pathname: string, collector: AnalyticsCollector, dashboardHtml: string | undefined): Response
|
|
199
|
+
declare function serveAnalyticsRoute(pathname: string, collector: AnalyticsCollector, dashboardHtml: string | undefined): Promise<Response>;
|
|
197
200
|
/**
|
|
198
201
|
* Wrap a fetch handler with analytics collection.
|
|
199
202
|
* Intercepts analytics dashboard routes and instruments every request.
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { SilgiError, toSilgiError } from "../core/error.mjs";
|
|
2
2
|
import { ValidationError } from "../core/schema.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { useStorage } from "../core/storage.mjs";
|
|
4
4
|
import { readFileSync } from "node:fs";
|
|
5
5
|
import { dirname, resolve } from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { parse } from "cookie-es";
|
|
7
8
|
//#region src/plugins/analytics.ts
|
|
8
9
|
/**
|
|
9
10
|
* Built-in analytics plugin — zero-dependency monitoring with deep error tracing.
|
|
@@ -142,6 +143,86 @@ async function trace(ctx, name, fn, opts) {
|
|
|
142
143
|
if (reqTrace) return reqTrace.trace(name, fn, opts);
|
|
143
144
|
return fn();
|
|
144
145
|
}
|
|
146
|
+
var AnalyticsStore = class {
|
|
147
|
+
#storage;
|
|
148
|
+
#pendingRequests = [];
|
|
149
|
+
#pendingErrors = [];
|
|
150
|
+
#maxRequests;
|
|
151
|
+
#maxErrors;
|
|
152
|
+
#timer = null;
|
|
153
|
+
#flushing = false;
|
|
154
|
+
constructor(maxRequests, maxErrors, flushInterval) {
|
|
155
|
+
this.#storage = useStorage("data");
|
|
156
|
+
this.#maxRequests = maxRequests;
|
|
157
|
+
this.#maxErrors = maxErrors;
|
|
158
|
+
this.#timer = setInterval(() => this.flush(), flushInterval);
|
|
159
|
+
if (typeof this.#timer === "object" && "unref" in this.#timer) this.#timer.unref();
|
|
160
|
+
}
|
|
161
|
+
enqueueRequest(entry) {
|
|
162
|
+
this.#pendingRequests.push(entry);
|
|
163
|
+
}
|
|
164
|
+
enqueueError(entry) {
|
|
165
|
+
this.#pendingErrors.push(entry);
|
|
166
|
+
}
|
|
167
|
+
async flush() {
|
|
168
|
+
if (this.#flushing) return;
|
|
169
|
+
const requests = this.#pendingRequests.splice(0);
|
|
170
|
+
const errors = this.#pendingErrors.splice(0);
|
|
171
|
+
if (requests.length === 0 && errors.length === 0) return;
|
|
172
|
+
this.#flushing = true;
|
|
173
|
+
try {
|
|
174
|
+
if (requests.length > 0) {
|
|
175
|
+
const merged = [...await this.#storage.getItem("analytics:requests") ?? [], ...requests].slice(-this.#maxRequests);
|
|
176
|
+
await this.#storage.setItem("analytics:requests", merged);
|
|
177
|
+
}
|
|
178
|
+
if (errors.length > 0) {
|
|
179
|
+
const merged = [...await this.#storage.getItem("analytics:errors") ?? [], ...errors].slice(-this.#maxErrors);
|
|
180
|
+
await this.#storage.setItem("analytics:errors", merged);
|
|
181
|
+
}
|
|
182
|
+
} catch {
|
|
183
|
+
this.#pendingRequests.unshift(...requests);
|
|
184
|
+
this.#pendingErrors.unshift(...errors);
|
|
185
|
+
} finally {
|
|
186
|
+
this.#flushing = false;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
async getRequests() {
|
|
190
|
+
const stored = await this.#storage.getItem("analytics:requests") ?? [];
|
|
191
|
+
if (this.#pendingRequests.length === 0) return stored;
|
|
192
|
+
return [...stored, ...this.#pendingRequests].slice(-this.#maxRequests);
|
|
193
|
+
}
|
|
194
|
+
async getErrors() {
|
|
195
|
+
const stored = await this.#storage.getItem("analytics:errors") ?? [];
|
|
196
|
+
if (this.#pendingErrors.length === 0) return stored;
|
|
197
|
+
return [...stored, ...this.#pendingErrors].slice(-this.#maxErrors);
|
|
198
|
+
}
|
|
199
|
+
async hydrate() {
|
|
200
|
+
try {
|
|
201
|
+
return await this.#storage.getItem("analytics:counters") ?? {
|
|
202
|
+
totalRequests: 0,
|
|
203
|
+
totalErrors: 0
|
|
204
|
+
};
|
|
205
|
+
} catch {
|
|
206
|
+
return {
|
|
207
|
+
totalRequests: 0,
|
|
208
|
+
totalErrors: 0
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
async saveCounters(totalRequests, totalErrors) {
|
|
213
|
+
try {
|
|
214
|
+
await this.#storage.setItem("analytics:counters", {
|
|
215
|
+
totalRequests,
|
|
216
|
+
totalErrors
|
|
217
|
+
});
|
|
218
|
+
} catch {}
|
|
219
|
+
}
|
|
220
|
+
async dispose() {
|
|
221
|
+
if (this.#timer) clearInterval(this.#timer);
|
|
222
|
+
this.#timer = null;
|
|
223
|
+
await this.flush();
|
|
224
|
+
}
|
|
225
|
+
};
|
|
145
226
|
var AnalyticsCollector = class {
|
|
146
227
|
#procedures = /* @__PURE__ */ new Map();
|
|
147
228
|
#startTime = Date.now();
|
|
@@ -157,6 +238,8 @@ var AnalyticsCollector = class {
|
|
|
157
238
|
#nextErrorId = 1;
|
|
158
239
|
#requests = [];
|
|
159
240
|
#nextRequestId = 1;
|
|
241
|
+
#store;
|
|
242
|
+
#counterFlushCounter = 0;
|
|
160
243
|
constructor(options = {}) {
|
|
161
244
|
this.#bufferSize = options.bufferSize ?? 1024;
|
|
162
245
|
this.#historySeconds = options.historySeconds ?? 120;
|
|
@@ -167,6 +250,11 @@ var AnalyticsCollector = class {
|
|
|
167
250
|
count: 0,
|
|
168
251
|
errors: 0
|
|
169
252
|
};
|
|
253
|
+
this.#store = new AnalyticsStore(this.#maxRequests, this.#maxErrors, options.flushInterval ?? 5e3);
|
|
254
|
+
this.#store.hydrate().then((c) => {
|
|
255
|
+
this.#totalRequests += c.totalRequests;
|
|
256
|
+
this.#totalErrors += c.totalErrors;
|
|
257
|
+
});
|
|
170
258
|
}
|
|
171
259
|
record(path, durationMs) {
|
|
172
260
|
this.#totalRequests++;
|
|
@@ -187,18 +275,23 @@ var AnalyticsCollector = class {
|
|
|
187
275
|
this.#tick(true);
|
|
188
276
|
}
|
|
189
277
|
recordDetailedError(entry) {
|
|
190
|
-
|
|
278
|
+
const full = {
|
|
191
279
|
...entry,
|
|
192
280
|
id: this.#nextErrorId++
|
|
193
|
-
}
|
|
281
|
+
};
|
|
282
|
+
this.#errors.push(full);
|
|
194
283
|
if (this.#errors.length > this.#maxErrors) this.#errors.shift();
|
|
284
|
+
this.#store.enqueueError(full);
|
|
195
285
|
}
|
|
196
286
|
recordDetailedRequest(entry) {
|
|
197
|
-
|
|
287
|
+
const full = {
|
|
198
288
|
...entry,
|
|
199
289
|
id: this.#nextRequestId++
|
|
200
|
-
}
|
|
290
|
+
};
|
|
291
|
+
this.#requests.push(full);
|
|
201
292
|
if (this.#requests.length > this.#maxRequests) this.#requests.shift();
|
|
293
|
+
this.#store.enqueueRequest(full);
|
|
294
|
+
this.#flushCountersIfNeeded();
|
|
202
295
|
}
|
|
203
296
|
#getOrCreate(path) {
|
|
204
297
|
let entry = this.#procedures.get(path);
|
|
@@ -231,10 +324,17 @@ var AnalyticsCollector = class {
|
|
|
231
324
|
if (isError) this.#currentWindow.errors++;
|
|
232
325
|
}
|
|
233
326
|
getErrors() {
|
|
234
|
-
return this.#
|
|
327
|
+
return this.#store.getErrors();
|
|
235
328
|
}
|
|
236
329
|
getRequests() {
|
|
237
|
-
return this.#
|
|
330
|
+
return this.#store.getRequests();
|
|
331
|
+
}
|
|
332
|
+
#flushCountersIfNeeded() {
|
|
333
|
+
if (++this.#counterFlushCounter % 50 === 0) this.#store.saveCounters(this.#totalRequests, this.#totalErrors);
|
|
334
|
+
}
|
|
335
|
+
async dispose() {
|
|
336
|
+
await this.#store.saveCounters(this.#totalRequests, this.#totalErrors);
|
|
337
|
+
await this.#store.dispose();
|
|
238
338
|
}
|
|
239
339
|
toJSON() {
|
|
240
340
|
const uptimeSeconds = (Date.now() - this.#startTime) / 1e3;
|
|
@@ -538,8 +638,11 @@ function analyticsAuthResponse(pathname) {
|
|
|
538
638
|
headers: { "content-type": "text/html" }
|
|
539
639
|
});
|
|
540
640
|
}
|
|
641
|
+
function jsonResponse(data, headers) {
|
|
642
|
+
return new Response(JSON.stringify(data), { headers });
|
|
643
|
+
}
|
|
541
644
|
/** Serve analytics dashboard and API routes. */
|
|
542
|
-
function serveAnalyticsRoute(pathname, collector, dashboardHtml) {
|
|
645
|
+
async function serveAnalyticsRoute(pathname, collector, dashboardHtml) {
|
|
543
646
|
const jsonCacheHeaders = {
|
|
544
647
|
"content-type": "application/json",
|
|
545
648
|
"cache-control": "no-cache"
|
|
@@ -548,23 +651,23 @@ function serveAnalyticsRoute(pathname, collector, dashboardHtml) {
|
|
|
548
651
|
"content-type": "text/markdown; charset=utf-8",
|
|
549
652
|
"cache-control": "no-cache"
|
|
550
653
|
};
|
|
551
|
-
if (pathname === "analytics/_api/stats") return
|
|
552
|
-
if (pathname === "analytics/_api/errors") return
|
|
553
|
-
if (pathname === "analytics/_api/requests") return
|
|
654
|
+
if (pathname === "analytics/_api/stats") return jsonResponse(collector.toJSON(), jsonCacheHeaders);
|
|
655
|
+
if (pathname === "analytics/_api/errors") return jsonResponse(await collector.getErrors(), jsonCacheHeaders);
|
|
656
|
+
if (pathname === "analytics/_api/requests") return jsonResponse(await collector.getRequests(), jsonCacheHeaders);
|
|
554
657
|
if (pathname.startsWith("analytics/_api/requests/") && pathname.endsWith("/md")) {
|
|
555
658
|
const id = Number(pathname.slice(24, -3));
|
|
556
|
-
const entry = collector.getRequests().find((r) => r.id === id);
|
|
659
|
+
const entry = (await collector.getRequests()).find((r) => r.id === id);
|
|
557
660
|
if (entry) return new Response(requestToMarkdown(entry), { headers: mdHeaders });
|
|
558
661
|
return new Response("not found", { status: 404 });
|
|
559
662
|
}
|
|
560
663
|
if (pathname.startsWith("analytics/_api/errors/") && pathname.endsWith("/md")) {
|
|
561
664
|
const id = Number(pathname.slice(22, -3));
|
|
562
|
-
const entry = collector.getErrors().find((e) => e.id === id);
|
|
665
|
+
const entry = (await collector.getErrors()).find((e) => e.id === id);
|
|
563
666
|
if (entry) return new Response(errorToMarkdown(entry), { headers: mdHeaders });
|
|
564
667
|
return new Response("not found", { status: 404 });
|
|
565
668
|
}
|
|
566
669
|
if (pathname === "analytics/_api/errors/md") {
|
|
567
|
-
const errors = collector.getErrors();
|
|
670
|
+
const errors = await collector.getErrors();
|
|
568
671
|
const md = errors.length === 0 ? "No errors.\n" : `# Errors (${errors.length})\n\n` + errors.map((e) => errorToMarkdown(e)).join("\n\n---\n\n");
|
|
569
672
|
return new Response(md, { headers: mdHeaders });
|
|
570
673
|
}
|
package/dist/plugins/index.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { AnalyticsCollector, RequestTrace, analyticsHTML, errorToMarkdown, trace } from "./analytics.mjs";
|
|
1
2
|
import { cors, corsHeaders } from "./cors.mjs";
|
|
2
3
|
import { otelWrap } from "./otel.mjs";
|
|
3
4
|
import { loggingHooks } from "./pino.mjs";
|
|
@@ -10,5 +11,4 @@ import { coerceGuard, coerceObject, coerceValue } from "./coerce.mjs";
|
|
|
10
11
|
import { createBatchHandler } from "./batch-server.mjs";
|
|
11
12
|
import { MemoryPubSub, createPublisher } from "./pubsub.mjs";
|
|
12
13
|
import { fileGuard, parseMultipart } from "./file-upload.mjs";
|
|
13
|
-
import { AnalyticsCollector, RequestTrace, analyticsHTML, errorToMarkdown, trace } from "./analytics.mjs";
|
|
14
14
|
export { AnalyticsCollector, MemoryPubSub, MemoryRateLimiter, RequestTrace, analyticsHTML, bodyLimitGuard, coerceGuard, coerceObject, coerceValue, cors, corsHeaders, createBatchHandler, createPublisher, decrypt, deleteCookie, encrypt, errorToMarkdown, fileGuard, getCookie, loggingHooks, otelWrap, parseCookies, parseMultipart, rateLimitGuard, setCookie, sign, strictGetGuard, trace, unsign };
|
package/dist/silgi.mjs
CHANGED
|
@@ -87,7 +87,11 @@ function silgi(config) {
|
|
|
87
87
|
}),
|
|
88
88
|
$resolve: ((fn) => createProcedure("query", fn)),
|
|
89
89
|
$input: ((schema) => createProcedureBuilder("query").$input(schema)),
|
|
90
|
-
$use: ((...middleware) =>
|
|
90
|
+
$use: ((...middleware) => {
|
|
91
|
+
const b = createProcedureBuilder("query");
|
|
92
|
+
for (const m of middleware) b.$use(m);
|
|
93
|
+
return b;
|
|
94
|
+
}),
|
|
91
95
|
$output: ((schema) => createProcedureBuilder("query").$output(schema)),
|
|
92
96
|
$errors: ((errors) => createProcedureBuilder("query").$errors(errors)),
|
|
93
97
|
$route: ((route) => createProcedureBuilder("query").$route(route)),
|
package/dist/types.d.mts
CHANGED
|
@@ -99,7 +99,9 @@ interface ResolveContext<TCtx, TInput, TErrors extends ErrorDef> {
|
|
|
99
99
|
type RouterDef = {
|
|
100
100
|
[key: string]: ProcedureDef<any, any, any, any> | RouterDef;
|
|
101
101
|
};
|
|
102
|
+
/** Return type wrapper — subscription yields, query/mutation returns Promise */
|
|
103
|
+
type ProcedureResult<TType extends ProcedureType, TOutput> = TType extends 'subscription' ? AsyncIterableIterator<TOutput> : Promise<TOutput>;
|
|
102
104
|
/** Infer client type from router */
|
|
103
|
-
type InferClient<T> = T extends ProcedureDef<infer TType, infer TInput, infer TOutput> ?
|
|
105
|
+
type InferClient<T> = T extends ProcedureDef<infer TType, infer TInput, infer TOutput> ? undefined extends TInput ? () => ProcedureResult<TType, TOutput> : (input: TInput) => ProcedureResult<TType, TOutput> : T extends Record<string, unknown> ? { [K in keyof T]: InferClient<T[K]> } : never;
|
|
104
106
|
//#endregion
|
|
105
107
|
export { ErrorDef, ErrorDefItem, FailFn, GuardDef, GuardFn, InferClient, InferContextFromUse, InferGuardOutput, Meta, MiddlewareDef, ProcedureDef, ProcedureType, ResolveContext, Route, RouterDef, WrapDef, WrapFn };
|
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.9",
|
|
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": [
|
|
@@ -236,6 +236,7 @@
|
|
|
236
236
|
"bench:http": "node --experimental-strip-types bench/http.ts",
|
|
237
237
|
"bench:http:bun": "bun bench/http-bun.ts",
|
|
238
238
|
"bench:memory": "node --expose-gc --experimental-strip-types bench/memory.ts",
|
|
239
|
+
"bench:types": "tsx bench/types.ts",
|
|
239
240
|
"bench:bun": "bun test/bun-compat.ts",
|
|
240
241
|
"play": "pnpm build && pnpm --filter silgi-playground dev",
|
|
241
242
|
"lint": "oxlint . && oxfmt --check --ignore-path .oxfmtignore .",
|
|
@@ -250,7 +251,7 @@
|
|
|
250
251
|
},
|
|
251
252
|
"dependencies": {
|
|
252
253
|
"@standard-schema/spec": "^1.1.0",
|
|
253
|
-
"cookie-es": "^3.
|
|
254
|
+
"cookie-es": "^3.1.1",
|
|
254
255
|
"crossws": "^0.4.4",
|
|
255
256
|
"defu": "^6.1.4",
|
|
256
257
|
"devalue": "^5.6.4",
|
|
@@ -260,23 +261,24 @@
|
|
|
260
261
|
"ocache": "^0.1.4",
|
|
261
262
|
"ofetch": "2.0.0-alpha.3",
|
|
262
263
|
"ohash": "^2.0.11",
|
|
263
|
-
"srvx": "^0.11.
|
|
264
|
+
"srvx": "^0.11.13",
|
|
264
265
|
"unstorage": "^2.0.0-alpha.7"
|
|
265
266
|
},
|
|
266
267
|
"devDependencies": {
|
|
268
|
+
"@ark/attest": "^0.56.0",
|
|
267
269
|
"@hono/node-server": "^1.19.11",
|
|
268
270
|
"@nuxt/test-utils": "^4.0.0",
|
|
269
|
-
"@orpc/client": "^1.13.
|
|
270
|
-
"@orpc/contract": "^1.13.
|
|
271
|
-
"@orpc/server": "^1.13.
|
|
271
|
+
"@orpc/client": "^1.13.13",
|
|
272
|
+
"@orpc/contract": "^1.13.13",
|
|
273
|
+
"@orpc/server": "^1.13.13",
|
|
272
274
|
"@pinia/colada": "^1.0.0",
|
|
273
|
-
"@trpc/client": "^11.
|
|
274
|
-
"@trpc/server": "^11.
|
|
275
|
+
"@trpc/client": "^11.16.0",
|
|
276
|
+
"@trpc/server": "^11.16.0",
|
|
275
277
|
"@types/express": "^5.0.6",
|
|
276
278
|
"@types/node": "^25.5.0",
|
|
277
279
|
"@types/ws": "^8.18.1",
|
|
278
280
|
"@typescript/native-preview": "7.0.0-dev.20260316.1",
|
|
279
|
-
"ai": "^6.0.
|
|
281
|
+
"ai": "^6.0.141",
|
|
280
282
|
"elysia": "^1.4.28",
|
|
281
283
|
"express": "^5.2.1",
|
|
282
284
|
"fastify": "^5.8.4",
|
|
@@ -288,15 +290,15 @@
|
|
|
288
290
|
"nitro": "3.0.260311-beta",
|
|
289
291
|
"nuxt-nightly": "5.0.0-29563820.579db312",
|
|
290
292
|
"obuild": "^0.4.32",
|
|
291
|
-
"oxfmt": "^0.
|
|
292
|
-
"oxlint": "^1.
|
|
293
|
+
"oxfmt": "^0.42.0",
|
|
294
|
+
"oxlint": "^1.57.0",
|
|
293
295
|
"pinia": "^3.0.4",
|
|
294
296
|
"rou3": "^0.8.1",
|
|
295
|
-
"tsdown": "^0.21.
|
|
297
|
+
"tsdown": "^0.21.7",
|
|
296
298
|
"typescript": "^6.0.2",
|
|
297
|
-
"vitest": "^4.1.
|
|
299
|
+
"vitest": "^4.1.2",
|
|
298
300
|
"vue": "^3.5.31",
|
|
299
|
-
"ws": "^8.
|
|
301
|
+
"ws": "^8.20.0",
|
|
300
302
|
"zod": "^4.3.6"
|
|
301
303
|
},
|
|
302
304
|
"peerDependencies": {
|
|
@@ -319,7 +321,7 @@
|
|
|
319
321
|
"node": ">=24",
|
|
320
322
|
"pnpm": ">=10"
|
|
321
323
|
},
|
|
322
|
-
"packageManager": "pnpm@10.
|
|
324
|
+
"packageManager": "pnpm@10.33.0",
|
|
323
325
|
"pnpm": {
|
|
324
326
|
"overrides": {
|
|
325
327
|
"silgi": "workspace:*"
|