stitchkit 0.0.1 → 0.2.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.
Files changed (127) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +376 -4
  3. package/dist/browser/client.d.ts +30 -0
  4. package/dist/browser/client.d.ts.map +1 -0
  5. package/dist/browser/http.d.ts +77 -0
  6. package/dist/browser/http.d.ts.map +1 -0
  7. package/dist/browser/socket-io.d.ts +69 -0
  8. package/dist/browser/socket-io.d.ts.map +1 -0
  9. package/dist/browser/stream.d.ts +2 -0
  10. package/dist/browser/stream.d.ts.map +1 -0
  11. package/dist/contract/define.d.ts +94 -0
  12. package/dist/contract/define.d.ts.map +1 -0
  13. package/dist/contract/errors.d.ts +47 -0
  14. package/dist/contract/errors.d.ts.map +1 -0
  15. package/dist/contract/index.d.ts +4 -0
  16. package/dist/contract/index.d.ts.map +1 -0
  17. package/dist/contract/index.js +26 -0
  18. package/dist/contract/pagination.d.ts +19 -0
  19. package/dist/contract/pagination.d.ts.map +1 -0
  20. package/dist/index-5sxnvwb1.js +368 -0
  21. package/dist/index-809wc1tt.js +18 -0
  22. package/dist/index-a35v22fh.js +29 -0
  23. package/dist/index-kckky6zw.js +96 -0
  24. package/dist/index-ke4mx4ea.js +38 -0
  25. package/dist/index-n7bmdwmz.js +68 -0
  26. package/dist/index-v2z2v3mq.js +587 -0
  27. package/dist/index.d.ts +7 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +111 -0
  30. package/dist/internal/errors.d.ts +5 -0
  31. package/dist/internal/errors.d.ts.map +1 -0
  32. package/dist/internal/typed.d.ts +9 -0
  33. package/dist/internal/typed.d.ts.map +1 -0
  34. package/dist/node.d.ts +4 -0
  35. package/dist/node.d.ts.map +1 -0
  36. package/dist/node.js +26 -0
  37. package/dist/observability/audit.d.ts +32 -0
  38. package/dist/observability/audit.d.ts.map +1 -0
  39. package/dist/observability/context.d.ts +60 -0
  40. package/dist/observability/context.d.ts.map +1 -0
  41. package/dist/observability/event.d.ts +49 -0
  42. package/dist/observability/event.d.ts.map +1 -0
  43. package/dist/observability/index.d.ts +14 -0
  44. package/dist/observability/index.d.ts.map +1 -0
  45. package/dist/observability/index.js +266 -0
  46. package/dist/observability/sanitize.d.ts +42 -0
  47. package/dist/observability/sanitize.d.ts.map +1 -0
  48. package/dist/observability/trace.d.ts +42 -0
  49. package/dist/observability/trace.d.ts.map +1 -0
  50. package/dist/react/cache-bridge.d.ts +53 -0
  51. package/dist/react/cache-bridge.d.ts.map +1 -0
  52. package/dist/react/cursor-query.d.ts +37 -0
  53. package/dist/react/cursor-query.d.ts.map +1 -0
  54. package/dist/react.d.ts +3 -0
  55. package/dist/react.d.ts.map +1 -0
  56. package/dist/react.js +52 -0
  57. package/dist/server/cache.d.ts +15 -0
  58. package/dist/server/cache.d.ts.map +1 -0
  59. package/dist/server/context.d.ts +9 -0
  60. package/dist/server/context.d.ts.map +1 -0
  61. package/dist/server/create.d.ts +4 -0
  62. package/dist/server/create.d.ts.map +1 -0
  63. package/dist/server/event-bus.d.ts +24 -0
  64. package/dist/server/event-bus.d.ts.map +1 -0
  65. package/dist/server/implement.d.ts +16 -0
  66. package/dist/server/implement.d.ts.map +1 -0
  67. package/dist/server/index.d.ts +17 -0
  68. package/dist/server/index.d.ts.map +1 -0
  69. package/dist/server/index.js +399 -0
  70. package/dist/server/logger.d.ts +21 -0
  71. package/dist/server/logger.d.ts.map +1 -0
  72. package/dist/server/middleware/auth.d.ts +50 -0
  73. package/dist/server/middleware/auth.d.ts.map +1 -0
  74. package/dist/server/middleware/cookies.d.ts +34 -0
  75. package/dist/server/middleware/cookies.d.ts.map +1 -0
  76. package/dist/server/middleware/cors.d.ts +9 -0
  77. package/dist/server/middleware/cors.d.ts.map +1 -0
  78. package/dist/server/multipart.d.ts +14 -0
  79. package/dist/server/multipart.d.ts.map +1 -0
  80. package/dist/server/node.d.ts +12 -0
  81. package/dist/server/node.d.ts.map +1 -0
  82. package/dist/server/rate-limit.d.ts +17 -0
  83. package/dist/server/rate-limit.d.ts.map +1 -0
  84. package/dist/server/request.d.ts +22 -0
  85. package/dist/server/request.d.ts.map +1 -0
  86. package/dist/server/router.d.ts +42 -0
  87. package/dist/server/router.d.ts.map +1 -0
  88. package/dist/server/socket-io.d.ts +42 -0
  89. package/dist/server/socket-io.d.ts.map +1 -0
  90. package/dist/server/stream.d.ts +18 -0
  91. package/dist/server/stream.d.ts.map +1 -0
  92. package/dist/server/swept-map.d.ts +22 -0
  93. package/dist/server/swept-map.d.ts.map +1 -0
  94. package/dist/server/types.d.ts +127 -0
  95. package/dist/server/types.d.ts.map +1 -0
  96. package/dist/tools/agent.d.ts +27 -0
  97. package/dist/tools/agent.d.ts.map +1 -0
  98. package/dist/tools/coerce.d.ts +8 -0
  99. package/dist/tools/coerce.d.ts.map +1 -0
  100. package/dist/tools/execute.d.ts +45 -0
  101. package/dist/tools/execute.d.ts.map +1 -0
  102. package/dist/tools/flatten.d.ts +15 -0
  103. package/dist/tools/flatten.d.ts.map +1 -0
  104. package/dist/tools/json-schema.d.ts +18 -0
  105. package/dist/tools/json-schema.d.ts.map +1 -0
  106. package/dist/tools/manifest.d.ts +13 -0
  107. package/dist/tools/manifest.d.ts.map +1 -0
  108. package/dist/tools/mcp-handler.d.ts +21 -0
  109. package/dist/tools/mcp-handler.d.ts.map +1 -0
  110. package/dist/tools/mcp-stdio.d.ts +25 -0
  111. package/dist/tools/mcp-stdio.d.ts.map +1 -0
  112. package/dist/tools/mcp.d.ts +92 -0
  113. package/dist/tools/mcp.d.ts.map +1 -0
  114. package/dist/tools/mount.d.ts +73 -0
  115. package/dist/tools/mount.d.ts.map +1 -0
  116. package/dist/tools/names.d.ts +2 -0
  117. package/dist/tools/names.d.ts.map +1 -0
  118. package/dist/tools/remote.d.ts +22 -0
  119. package/dist/tools/remote.d.ts.map +1 -0
  120. package/dist/tools/schema.d.ts +21 -0
  121. package/dist/tools/schema.d.ts.map +1 -0
  122. package/dist/tools/view-file.d.ts +57 -0
  123. package/dist/tools/view-file.d.ts.map +1 -0
  124. package/dist/tools.d.ts +12 -0
  125. package/dist/tools.d.ts.map +1 -0
  126. package/dist/tools.js +837 -0
  127. package/package.json +147 -4
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Cursor-paginated infinite query — the family's canonical pagination hook.
3
+ *
4
+ * Built on `react-query-kit`'s `createInfiniteQuery` (the family's hook layer),
5
+ * so the result keeps the full rqk surface: `.getKey()`, `.fetcher`,
6
+ * `useSuspenseInfiniteQuery`, every option. Cursor pagination is immune to
7
+ * concurrent inserts — offset paging drops or duplicates rows when a new row
8
+ * lands on top of the list.
9
+ *
10
+ * Pass the typed contract method as `endpoint` (e.g. `apiClient.media.list`).
11
+ * The helper injects `cursor` from the page param and bakes in
12
+ * `getNextPageParam` / `initialPageParam`. An infinite hook is therefore just
13
+ * `queryKey + endpoint` — no per-hook fetcher, no pagination plumbing. Every
14
+ * other rqk option (`staleTime`, `gcTime`, `refetch*`, …) passes through.
15
+ *
16
+ * Page size is NOT a client concern: the helper never sends `limit`, so the
17
+ * server applies the contract's `limit` default (`z.coerce.number().default(N)`).
18
+ * That default is the single source of truth — the client cannot diverge.
19
+ */
20
+ import { type CompatibleError, type CreateInfiniteQueryOptions } from 'react-query-kit';
21
+ /** The cursor field the helper owns — the caller never passes it. */
22
+ interface CursorParam {
23
+ cursor?: string;
24
+ }
25
+ /** Options the helper computes itself; the caller cannot override them. */
26
+ type ControlledKeys = 'fetcher' | 'initialPageParam' | 'getNextPageParam';
27
+ export type CursorQueryConfig<TVars, TPage extends {
28
+ nextCursor: string | null;
29
+ }, TError> = Omit<CreateInfiniteQueryOptions<TPage, TVars, TError, string | null>, ControlledKeys> & {
30
+ /** Contract method, e.g. `apiClient.media.list`. */
31
+ endpoint: (args: TVars & CursorParam) => Promise<TPage>;
32
+ };
33
+ export declare function createCursorQuery<TVars, TPage extends {
34
+ nextCursor: string | null;
35
+ }, TError = CompatibleError>(config: CursorQueryConfig<TVars, TPage, TError>): import("react-query-kit").InfiniteQueryHook<TPage, TVars, TError, string | null>;
36
+ export {};
37
+ //# sourceMappingURL=cursor-query.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cursor-query.d.ts","sourceRoot":"","sources":["../../src/react/cursor-query.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,0BAA0B,EAEhC,MAAM,iBAAiB,CAAC;AAEzB,qEAAqE;AACrE,UAAU,WAAW;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,2EAA2E;AAC3E,KAAK,cAAc,GAAG,SAAS,GAAG,kBAAkB,GAAG,kBAAkB,CAAC;AAE1E,MAAM,MAAM,iBAAiB,CAC3B,KAAK,EACL,KAAK,SAAS;IAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,EAC3C,MAAM,IACJ,IAAI,CAAC,0BAA0B,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,EAAE,cAAc,CAAC,GAAG;IAC1F,oDAAoD;IACpD,QAAQ,EAAE,CAAC,IAAI,EAAE,KAAK,GAAG,WAAW,KAAK,OAAO,CAAC,KAAK,CAAC,CAAC;CACzD,CAAC;AAEF,wBAAgB,iBAAiB,CAC/B,KAAK,EACL,KAAK,SAAS;IAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,EAC3C,MAAM,GAAG,eAAe,EACxB,MAAM,EAAE,iBAAiB,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,oFAYhD"}
@@ -0,0 +1,3 @@
1
+ export { type CacheBridge, type CacheBridgeConfig, type CacheBridgeContext, type CacheBridgeHandler, type CacheBridgeHandlers, type CacheBridgeSocket, createCacheBridge, } from './react/cache-bridge';
2
+ export { type CursorQueryConfig, createCursorQuery } from './react/cursor-query';
3
+ //# sourceMappingURL=react.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,iBAAiB,GAClB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,KAAK,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC"}
package/dist/react.js ADDED
@@ -0,0 +1,52 @@
1
+ // src/react/cache-bridge.ts
2
+ function createCacheBridge(config) {
3
+ const freshWindow = config.freshWindow ?? 500;
4
+ const freshAt = new Map;
5
+ let unsubscribers = [];
6
+ const isFresh = (key) => {
7
+ const ts = freshAt.get(JSON.stringify(key));
8
+ return ts !== undefined && Date.now() - ts < freshWindow;
9
+ };
10
+ return {
11
+ connect() {
12
+ if (unsubscribers.length > 0)
13
+ return;
14
+ const queryClient = typeof config.queryClient === "function" ? config.queryClient() : config.queryClient;
15
+ const ctx = { queryClient, isFresh };
16
+ const handlers = config.handlers;
17
+ const socket = config.socket;
18
+ for (const event of Object.keys(handlers)) {
19
+ const handler = handlers[event];
20
+ if (!handler)
21
+ continue;
22
+ const off = socket.on(event, (data) => handler(data, ctx));
23
+ unsubscribers.push(off);
24
+ }
25
+ },
26
+ disconnect() {
27
+ for (const off of unsubscribers)
28
+ off();
29
+ unsubscribers = [];
30
+ },
31
+ markFresh(key) {
32
+ freshAt.set(JSON.stringify(key), Date.now());
33
+ }
34
+ };
35
+ }
36
+ // src/react/cursor-query.ts
37
+ import {
38
+ createInfiniteQuery
39
+ } from "react-query-kit";
40
+ function createCursorQuery(config) {
41
+ const { endpoint, ...rest } = config;
42
+ return createInfiniteQuery({
43
+ ...rest,
44
+ fetcher: (variables, { pageParam }) => endpoint({ ...variables, cursor: pageParam ?? undefined }),
45
+ initialPageParam: null,
46
+ getNextPageParam: (lastPage) => lastPage.nextCursor
47
+ });
48
+ }
49
+ export {
50
+ createCursorQuery,
51
+ createCacheBridge
52
+ };
@@ -0,0 +1,15 @@
1
+ /**
2
+ * In-memory key/value cache with a per-entry TTL. Expired entries are swept on
3
+ * a 30-second timer; `destroy()` stops it. For response `Cache-Control`
4
+ * headers use `cacheHeaders()` instead.
5
+ */
6
+ export declare function createCache(): {
7
+ destroy: () => void;
8
+ get(key: string): unknown | null;
9
+ set(key: string, data: unknown, maxAgeSeconds: number): void;
10
+ invalidate(prefix: string): void;
11
+ clear(): void;
12
+ };
13
+ /** Build a `Cache-Control` header capping freshness at `maxAge` seconds. */
14
+ export declare function cacheHeaders(maxAge: number, scope?: 'public' | 'private'): Record<string, string>;
15
+ //# sourceMappingURL=cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/server/cache.ts"],"names":[],"mappings":"AAOA;;;;GAIG;AACH,wBAAgB,WAAW;;aAQd,MAAM,GAAG,OAAO,GAAG,IAAI;aAUvB,MAAM,QAAQ,OAAO,iBAAiB,MAAM;uBAIlC,MAAM;;EAU5B;AAED,4EAA4E;AAC5E,wBAAgB,YAAY,CAC1B,MAAM,EAAE,MAAM,EACd,KAAK,GAAE,QAAQ,GAAG,SAAoB,GACrC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAIxB"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * `RuntimeContext` assembly — parses params / body / multipart against the
3
+ * endpoint schemas and gathers request metadata (trace id, client info).
4
+ */
5
+ import { type RuntimeContext } from '../contract';
6
+ import type { MethodDef } from './types';
7
+ export declare function buildContext(req: Request, url: URL, method: MethodDef, pathParams: Record<string, string>, traceId: string): Promise<RuntimeContext>;
8
+ export declare function buildErrorContext(req: Request, url: URL, traceId: string): RuntimeContext;
9
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/server/context.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAG9D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AA2BzC,wBAAsB,YAAY,CAChC,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,GAAG,EACR,MAAM,EAAE,SAAS,EACjB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAClC,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,cAAc,CAAC,CAyCzB;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,GAAG,cAAc,CAWzF"}
@@ -0,0 +1,4 @@
1
+ import type { BunServerConfig, HandlerConfig } from './types';
2
+ export declare function createHandler(config: HandlerConfig): (req: Request) => Promise<Response>;
3
+ export declare function createServer(config: BunServerConfig): Bun.Server<unknown>;
4
+ //# sourceMappingURL=create.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/server/create.ts"],"names":[],"mappings":"AA0BA,OAAO,KAAK,EAEV,eAAe,EACf,aAAa,EAGd,MAAM,SAAS,CAAC;AAEjB,wBAAgB,aAAa,CAAC,MAAM,EAAE,aAAa,GAAG,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAiLxF;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,eAAe,uBAsBnD"}
@@ -0,0 +1,24 @@
1
+ type EventHandler<T = unknown> = (data: T) => void | Promise<void>;
2
+ /** Default event map — untyped string-keyed events (ad-hoc buses). */
3
+ export type DefaultEventMap = Record<string, unknown>;
4
+ /**
5
+ * In-process pub/sub bus.
6
+ *
7
+ * Pass an event map for full type safety — `emit`/`on`/`once`/`off` are then
8
+ * typed per event:
9
+ *
10
+ * ```ts
11
+ * const bus = createEventBus<{ 'user:created': { id: string } }>();
12
+ * bus.emit('user:created', { id: '1' }); // typed
13
+ * ```
14
+ */
15
+ export interface EventBus<M extends Record<string, unknown> = DefaultEventMap> {
16
+ emit<K extends keyof M & string>(event: K, data: M[K]): void;
17
+ on<K extends keyof M & string>(event: K, handler: EventHandler<M[K]>): () => void;
18
+ once<K extends keyof M & string>(event: K, handler: EventHandler<M[K]>): () => void;
19
+ off<K extends keyof M & string>(event: K, handler: EventHandler<M[K]>): void;
20
+ clear(): void;
21
+ }
22
+ export declare function createEventBus<M extends Record<string, unknown> = DefaultEventMap>(): EventBus<M>;
23
+ export {};
24
+ //# sourceMappingURL=event-bus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-bus.d.ts","sourceRoot":"","sources":["../../src/server/event-bus.ts"],"names":[],"mappings":"AAAA,KAAK,YAAY,CAAC,CAAC,GAAG,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAEnE,sEAAsE;AACtE,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEtD;;;;;;;;;;GAUG;AACH,MAAM,WAAW,QAAQ,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,eAAe;IAC3E,IAAI,CAAC,CAAC,SAAS,MAAM,CAAC,GAAG,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAC7D,EAAE,CAAC,CAAC,SAAS,MAAM,CAAC,GAAG,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,CAAC;IAClF,IAAI,CAAC,CAAC,SAAS,MAAM,CAAC,GAAG,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,CAAC;IACpF,GAAG,CAAC,CAAC,SAAS,MAAM,CAAC,GAAG,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAC7E,KAAK,IAAI,IAAI,CAAC;CACf;AAYD,wBAAgB,cAAc,CAC5B,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,eAAe,KAChD,QAAQ,CAAC,CAAC,CAAC,CAuDf"}
@@ -0,0 +1,16 @@
1
+ import type { ContractDef, EndpointDef, RuntimeContext } from '../contract';
2
+ import type { Handlers, ServiceDef } from './types';
3
+ /**
4
+ * Bind a contract to its typed `handlers`, producing a `ServiceDef` to mount on
5
+ * `createServer`. Every handler is type-checked against its endpoint's schemas.
6
+ * Pass `TCtx` for a typed handler context — or use `createImplement` to fix it
7
+ * once.
8
+ */
9
+ export declare function implement<T extends Record<string, EndpointDef>, TCtx extends RuntimeContext = RuntimeContext>(contract: ContractDef<T, string>, handlers: Handlers<T, TCtx>): ServiceDef;
10
+ /**
11
+ * Fix the handler context type once — `const implement =
12
+ * createImplement<MyContext>()` — so each `implement()` call site stays free of
13
+ * the generic. The application declares its context shape in a single place.
14
+ */
15
+ export declare function createImplement<TCtx extends RuntimeContext>(): <T extends Record<string, EndpointDef>>(contract: ContractDef<T, string>, handlers: Handlers<T, TCtx>) => ServiceDef;
16
+ //# sourceMappingURL=implement.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"implement.d.ts","sourceRoot":"","sources":["../../src/server/implement.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC5E,OAAO,KAAK,EAAE,QAAQ,EAAa,UAAU,EAAE,MAAM,SAAS,CAAC;AAE/D;;;;;GAKG;AACH,wBAAgB,SAAS,CACvB,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EACrC,IAAI,SAAS,cAAc,GAAG,cAAc,EAC5C,QAAQ,EAAE,WAAW,CAAC,CAAC,EAAE,MAAM,CAAC,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,UAAU,CAoC3E;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,SAAS,cAAc,MACjD,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EAC3C,UAAU,WAAW,CAAC,CAAC,EAAE,MAAM,CAAC,EAChC,UAAU,QAAQ,CAAC,CAAC,EAAE,IAAI,CAAC,KAC1B,UAAU,CACd"}
@@ -0,0 +1,17 @@
1
+ export { AppError, appError, badRequest, conflict, forbidden, notFound, rateLimited, unauthorized, } from '../contract';
2
+ export { cacheHeaders, createCache } from './cache';
3
+ export { createHandler, createServer } from './create';
4
+ export { createEventBus, type EventBus } from './event-bus';
5
+ export { createImplement, implement } from './implement';
6
+ export { type AuthHook, type AuthHookConfig, type AuthRule, type BearerResolverConfig, createAuthHook, createBearerResolver, extractToken, type JwtPayload, verifyJwt, } from './middleware/auth';
7
+ export { type CookieDef, type CookieOptions, defineCookie, parseCookies, serializeCookie, } from './middleware/cookies';
8
+ export { type CorsConfig, corsHeaders, corsPreflightResponse, } from './middleware/cors';
9
+ export { parseMultipart } from './multipart';
10
+ export { createRateLimiter, type RateLimitConfig } from './rate-limit';
11
+ export { extractIp, generateTraceId, getClientInfo, resolveTraceId, } from './request';
12
+ export { staticRoute } from './router';
13
+ export type { SocketIOServerConfig, SocketIOServerHandle } from './socket-io';
14
+ export { createSocketIOServer } from './socket-io';
15
+ export { type ParseSSEOptions, parseSSE, streamSSE } from './stream';
16
+ export type { BunServer, BunServerConfig, HandlerConfig, Handlers, LifecycleHooks, MethodDef, RawRoute, RawRouteContext, RouteGroup, ServerPassthrough, ServiceDef, StitchLogger, } from './types';
17
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,EACR,QAAQ,EACR,UAAU,EACV,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,WAAW,EACX,YAAY,GACb,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EACL,KAAK,QAAQ,EACb,KAAK,cAAc,EACnB,KAAK,QAAQ,EACb,KAAK,oBAAoB,EACzB,cAAc,EACd,oBAAoB,EACpB,YAAY,EACZ,KAAK,UAAU,EACf,SAAS,GACV,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,KAAK,SAAS,EACd,KAAK,aAAa,EAClB,YAAY,EACZ,YAAY,EACZ,eAAe,GAChB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,KAAK,UAAU,EACf,WAAW,EACX,qBAAqB,GACtB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,iBAAiB,EAAE,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AACvE,OAAO,EACL,SAAS,EACT,eAAe,EACf,aAAa,EACb,cAAc,GACf,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,YAAY,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAC9E,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,KAAK,eAAe,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrE,YAAY,EACV,SAAS,EACT,eAAe,EACf,aAAa,EACb,QAAQ,EACR,cAAc,EACd,SAAS,EACT,QAAQ,EACR,eAAe,EACf,UAAU,EACV,iBAAiB,EACjB,UAAU,EACV,YAAY,GACb,MAAM,SAAS,CAAC"}
@@ -0,0 +1,399 @@
1
+ import {
2
+ parseSSE,
3
+ streamSSE
4
+ } from "../index-n7bmdwmz.js";
5
+ import {
6
+ corsHeaders,
7
+ corsPreflightResponse,
8
+ createHandler,
9
+ createServer,
10
+ parseMultipart,
11
+ staticRoute
12
+ } from "../index-v2z2v3mq.js";
13
+ import"../index-a35v22fh.js";
14
+ import {
15
+ AppError,
16
+ appError,
17
+ badRequest,
18
+ conflict,
19
+ forbidden,
20
+ notFound,
21
+ rateLimited,
22
+ unauthorized
23
+ } from "../index-kckky6zw.js";
24
+ import {
25
+ extractIp,
26
+ generateTraceId,
27
+ getClientInfo,
28
+ resolveTraceId
29
+ } from "../index-ke4mx4ea.js";
30
+ // src/server/swept-map.ts
31
+ function createSweptMap(options) {
32
+ const store = new Map;
33
+ const timer = setInterval(() => {
34
+ const now = Date.now();
35
+ for (const [key, value] of store) {
36
+ if (options.isExpired(value, now))
37
+ store.delete(key);
38
+ }
39
+ }, options.intervalMs);
40
+ timer.unref();
41
+ return {
42
+ store,
43
+ destroy() {
44
+ clearInterval(timer);
45
+ store.clear();
46
+ }
47
+ };
48
+ }
49
+
50
+ // src/server/cache.ts
51
+ function createCache() {
52
+ const { store, destroy } = createSweptMap({
53
+ intervalMs: 30000,
54
+ isExpired: (entry, now) => entry.expiresAt < now
55
+ });
56
+ return {
57
+ destroy,
58
+ get(key) {
59
+ const entry = store.get(key);
60
+ if (!entry)
61
+ return null;
62
+ if (entry.expiresAt < Date.now()) {
63
+ store.delete(key);
64
+ return null;
65
+ }
66
+ return entry.data;
67
+ },
68
+ set(key, data, maxAgeSeconds) {
69
+ store.set(key, { data, expiresAt: Date.now() + maxAgeSeconds * 1000 });
70
+ },
71
+ invalidate(prefix) {
72
+ for (const key of store.keys()) {
73
+ if (key.startsWith(prefix))
74
+ store.delete(key);
75
+ }
76
+ },
77
+ clear() {
78
+ store.clear();
79
+ }
80
+ };
81
+ }
82
+ function cacheHeaders(maxAge, scope = "public") {
83
+ return {
84
+ "Cache-Control": `${scope}, max-age=${maxAge}`
85
+ };
86
+ }
87
+ // src/server/event-bus.ts
88
+ function createEventBus() {
89
+ const subscriptions = new Map;
90
+ function add(event, fn, once) {
91
+ let set = subscriptions.get(event);
92
+ if (!set) {
93
+ set = new Set;
94
+ subscriptions.set(event, set);
95
+ }
96
+ const sub = { fn, once };
97
+ set.add(sub);
98
+ return () => {
99
+ set.delete(sub);
100
+ };
101
+ }
102
+ return {
103
+ emit(event, data) {
104
+ const set = subscriptions.get(event);
105
+ if (!set)
106
+ return;
107
+ for (const sub of [...set]) {
108
+ if (sub.once)
109
+ set.delete(sub);
110
+ try {
111
+ const result = sub.fn(data);
112
+ if (result instanceof Promise)
113
+ result.catch(() => {
114
+ return;
115
+ });
116
+ } catch {}
117
+ }
118
+ },
119
+ on(event, handler) {
120
+ return add(event, handler, false);
121
+ },
122
+ once(event, handler) {
123
+ return add(event, handler, true);
124
+ },
125
+ off(event, handler) {
126
+ const set = subscriptions.get(event);
127
+ if (!set)
128
+ return;
129
+ for (const sub of set) {
130
+ if (sub.fn === handler)
131
+ set.delete(sub);
132
+ }
133
+ },
134
+ clear() {
135
+ subscriptions.clear();
136
+ }
137
+ };
138
+ }
139
+ // src/server/implement.ts
140
+ function implement(contract, handlers) {
141
+ const methods = {};
142
+ const groupScope = contract.meta.scope ?? "public";
143
+ for (const [key, endpoint] of Object.entries(contract.endpoints)) {
144
+ const typedHandler = handlers[key];
145
+ methods[key] = {
146
+ method: endpoint.method,
147
+ path: endpoint.path,
148
+ desc: endpoint.desc,
149
+ toolName: "toolName" in endpoint ? endpoint.toolName : undefined,
150
+ expose: endpoint.expose,
151
+ scope: endpoint.scope ?? groupScope,
152
+ paramsSchema: endpoint.params,
153
+ inputSchema: endpoint.input,
154
+ outputSchema: endpoint.output,
155
+ multipart: endpoint.multipart,
156
+ handler: (ctx) => typedHandler(ctx)
157
+ };
158
+ }
159
+ return {
160
+ name: contract.meta.prefix,
161
+ prefix: contract.meta.prefix,
162
+ scope: groupScope,
163
+ methods
164
+ };
165
+ }
166
+ function createImplement() {
167
+ return (contract, handlers) => implement(contract, handlers);
168
+ }
169
+ // src/server/middleware/cookies.ts
170
+ function parseCookies(header) {
171
+ if (!header)
172
+ return {};
173
+ const cookies = {};
174
+ for (const pair of header.split(";")) {
175
+ const [key, ...rest] = pair.split("=");
176
+ if (!key)
177
+ continue;
178
+ const raw = rest.join("=").trim();
179
+ try {
180
+ cookies[key.trim()] = decodeURIComponent(raw);
181
+ } catch {
182
+ cookies[key.trim()] = raw;
183
+ }
184
+ }
185
+ return cookies;
186
+ }
187
+ function serializeCookie(name, value, opts = {}) {
188
+ let cookie = `${name}=${encodeURIComponent(value)}`;
189
+ if (opts.maxAge !== undefined)
190
+ cookie += `; Max-Age=${opts.maxAge}`;
191
+ if (opts.path)
192
+ cookie += `; Path=${opts.path}`;
193
+ if (opts.domain)
194
+ cookie += `; Domain=${opts.domain}`;
195
+ if (opts.secure)
196
+ cookie += "; Secure";
197
+ if (opts.httpOnly)
198
+ cookie += "; HttpOnly";
199
+ if (opts.sameSite)
200
+ cookie += `; SameSite=${opts.sameSite}`;
201
+ return cookie;
202
+ }
203
+ function defineCookie(config) {
204
+ const { name, ...options } = config;
205
+ return {
206
+ get(req) {
207
+ return parseCookies(req.headers.get("cookie"))[name];
208
+ },
209
+ set(value) {
210
+ return serializeCookie(name, value, options);
211
+ },
212
+ clear() {
213
+ return serializeCookie(name, "", { ...options, maxAge: 0 });
214
+ }
215
+ };
216
+ }
217
+
218
+ // src/server/middleware/auth.ts
219
+ function decodeBase64Url(segment) {
220
+ const b64 = segment.replace(/-/g, "+").replace(/_/g, "/");
221
+ const padded = b64.padEnd(Math.ceil(b64.length / 4) * 4, "=");
222
+ return Uint8Array.from(atob(padded), (c) => c.charCodeAt(0));
223
+ }
224
+ async function verifyJwt(token, secret) {
225
+ const [headerB64, payloadB64, signatureB64] = token.split(".");
226
+ if (!headerB64 || !payloadB64 || !signatureB64)
227
+ throw unauthorized("Invalid token format");
228
+ let header;
229
+ let payload;
230
+ let signature;
231
+ try {
232
+ const decoder = new TextDecoder;
233
+ header = JSON.parse(decoder.decode(decodeBase64Url(headerB64)));
234
+ payload = JSON.parse(decoder.decode(decodeBase64Url(payloadB64)));
235
+ signature = decodeBase64Url(signatureB64);
236
+ } catch {
237
+ throw unauthorized("Malformed token");
238
+ }
239
+ if (header.alg !== "HS256")
240
+ throw unauthorized("Unsupported token algorithm");
241
+ const encoder = new TextEncoder;
242
+ const key = await crypto.subtle.importKey("raw", encoder.encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["verify"]);
243
+ const data = encoder.encode(`${headerB64}.${payloadB64}`);
244
+ const valid = await crypto.subtle.verify("HMAC", key, signature, data);
245
+ if (!valid)
246
+ throw unauthorized("Invalid token signature");
247
+ const now = Date.now() / 1000;
248
+ if (typeof payload.exp === "number" && payload.exp < now) {
249
+ throw unauthorized("Token expired");
250
+ }
251
+ if (typeof payload.nbf === "number" && payload.nbf > now) {
252
+ throw unauthorized("Token not yet valid");
253
+ }
254
+ return payload;
255
+ }
256
+ function extractToken(req, cookieName) {
257
+ const authHeader = req.headers.get("authorization");
258
+ if (authHeader?.startsWith("Bearer "))
259
+ return authHeader.slice(7);
260
+ if (cookieName) {
261
+ const cookies = parseCookies(req.headers.get("cookie"));
262
+ return cookies[cookieName] ?? null;
263
+ }
264
+ return null;
265
+ }
266
+ function createAuthHook(config) {
267
+ const onAnonymous = config.onAnonymous ?? (() => unauthorized());
268
+ const onForbidden = config.onForbidden ?? (() => forbidden());
269
+ return async (ctx, endpoint) => {
270
+ if (!(ctx.req instanceof Request))
271
+ return;
272
+ const identity = await config.resolve(ctx);
273
+ config.inject?.(ctx, identity);
274
+ const scope = endpoint.scope ?? config.defaultScope;
275
+ if (!scope)
276
+ return;
277
+ const rule = config.rules[scope];
278
+ if (!rule || rule === "public")
279
+ return;
280
+ if (!identity) {
281
+ onAnonymous();
282
+ return;
283
+ }
284
+ if (rule === "authenticated")
285
+ return;
286
+ if (!await rule(identity, ctx))
287
+ onForbidden();
288
+ };
289
+ }
290
+ function createBearerResolver(config) {
291
+ return async (req) => {
292
+ const token = extractToken(req);
293
+ if (!token)
294
+ return null;
295
+ return config.lookup(token, req);
296
+ };
297
+ }
298
+ // src/server/rate-limit.ts
299
+ function refillBucket(entry, config, now) {
300
+ const elapsed = (now - entry.lastRefill) / 1000;
301
+ entry.tokens = Math.min(config.max, entry.tokens + elapsed / config.window * config.max);
302
+ entry.lastRefill = now;
303
+ }
304
+ function createRateLimiter() {
305
+ const { store: buckets, destroy } = createSweptMap({
306
+ intervalMs: 60000,
307
+ isExpired: (entry, now) => now - entry.lastRefill > 300000
308
+ });
309
+ return {
310
+ destroy,
311
+ check(key, config) {
312
+ const now = Date.now();
313
+ let entry = buckets.get(key);
314
+ if (!entry) {
315
+ entry = { tokens: config.max, lastRefill: now };
316
+ buckets.set(key, entry);
317
+ }
318
+ refillBucket(entry, config, now);
319
+ if (entry.tokens < 1)
320
+ return false;
321
+ entry.tokens -= 1;
322
+ return true;
323
+ },
324
+ remaining(key, config) {
325
+ const entry = buckets.get(key);
326
+ if (!entry)
327
+ return config.max;
328
+ refillBucket(entry, config, Date.now());
329
+ return Math.floor(entry.tokens);
330
+ }
331
+ };
332
+ }
333
+ // src/server/socket-io.ts
334
+ import { Server as BunEngine } from "@socket.io/bun-engine";
335
+ import { Server as SocketIOServer } from "socket.io";
336
+ function createSocketIOServer(config) {
337
+ const path = config.path ?? "/socket.io/";
338
+ const io = new SocketIOServer({
339
+ path,
340
+ cors: {
341
+ origin: config.cors.origin,
342
+ credentials: config.cors.credentials ?? true,
343
+ methods: ["GET", "POST"]
344
+ },
345
+ transports: config.transports ?? ["websocket", "polling"],
346
+ pingTimeout: config.pingTimeout ?? 20000,
347
+ pingInterval: config.pingInterval ?? 1e4
348
+ });
349
+ const engine = new BunEngine({ path });
350
+ io.bind(engine);
351
+ const { websocket } = engine.handler();
352
+ const route = {
353
+ method: "ALL",
354
+ path: `${path.replace(/\/+$/, "")}/*`,
355
+ handler: (req, ctx) => {
356
+ if (!ctx.server) {
357
+ throw new Error("[stitchkit] createSocketIOServer route needs a running Bun server — mount it via createServer.");
358
+ }
359
+ return engine.handleRequest(req, ctx.server);
360
+ }
361
+ };
362
+ return { io, websocket, route };
363
+ }
364
+ export {
365
+ verifyJwt,
366
+ unauthorized,
367
+ streamSSE,
368
+ staticRoute,
369
+ serializeCookie,
370
+ resolveTraceId,
371
+ rateLimited,
372
+ parseSSE,
373
+ parseMultipart,
374
+ parseCookies,
375
+ notFound,
376
+ implement,
377
+ getClientInfo,
378
+ generateTraceId,
379
+ forbidden,
380
+ extractToken,
381
+ extractIp,
382
+ defineCookie,
383
+ createSocketIOServer,
384
+ createServer,
385
+ createRateLimiter,
386
+ createImplement,
387
+ createHandler,
388
+ createEventBus,
389
+ createCache,
390
+ createBearerResolver,
391
+ createAuthHook,
392
+ corsPreflightResponse,
393
+ corsHeaders,
394
+ conflict,
395
+ cacheHeaders,
396
+ badRequest,
397
+ appError,
398
+ AppError
399
+ };
@@ -0,0 +1,21 @@
1
+ export declare function elapsedMs(startTime: bigint): number;
2
+ /** Map an HTTP status to a log level. */
3
+ export declare function levelForStatus(status: number): 'error' | 'warn' | 'info';
4
+ /** The structured fields shared by every completed-request log line. */
5
+ export declare function buildLogFields(method: string, path: string, status: number, durationMs: number, traceId: string): {
6
+ traceId: string;
7
+ method: string;
8
+ path: string;
9
+ status: number;
10
+ durationMs: number;
11
+ };
12
+ export interface RequestLog {
13
+ traceId: string;
14
+ startTime: bigint;
15
+ }
16
+ export declare function shouldLog(pathname: string, method: string): boolean;
17
+ /** Open the timing window for a request. Development prints a `→` line. */
18
+ export declare function logIncoming(req: Request, pathname: string, traceId: string): RequestLog;
19
+ /** Close a request. Development: `←` line. Production: one structured JSON line. */
20
+ export declare function logOutgoing(req: Request, pathname: string, status: number, log: RequestLog): void;
21
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/server/logger.ts"],"names":[],"mappings":"AA0CA,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,yCAAyC;AACzC,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,CAIxE;AAED,wEAAwE;AACxE,wBAAgB,cAAc,CAC5B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,GACd;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAEvF;AA0BD,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAID,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAGnE;AAED,2EAA2E;AAC3E,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,UAAU,CASvF;AAED,oFAAoF;AACpF,wBAAgB,WAAW,CACzB,GAAG,EAAE,OAAO,EACZ,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,UAAU,GACd,IAAI,CAoBN"}