silgi 0.52.2 → 0.53.1
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/adapters/aws-lambda.mjs +1 -1
- package/dist/broker/redis.mjs +1 -2
- package/dist/builder.mjs +35 -7
- package/dist/caller.mjs +67 -50
- package/dist/client/plugins/retry.mjs +9 -4
- package/dist/compile.d.mts +17 -10
- package/dist/compile.mjs +161 -144
- package/dist/core/ctx-symbols.mjs +18 -1
- package/dist/core/handler.d.mts +3 -3
- package/dist/core/handler.mjs +94 -83
- package/dist/core/input.mjs +116 -37
- package/dist/core/schema-converter.d.mts +68 -63
- package/dist/core/schema-converter.mjs +85 -56
- package/dist/core/serve.d.mts +18 -17
- package/dist/core/serve.mjs +154 -64
- package/dist/core/sse.d.mts +5 -6
- package/dist/core/sse.mjs +86 -46
- package/dist/core/task.d.mts +36 -8
- package/dist/core/task.mjs +210 -90
- package/dist/core/url.mjs +19 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/integrations/better-auth/index.mjs +11 -2
- package/dist/lazy.mjs +3 -0
- package/dist/map-input.d.mts +8 -6
- package/dist/map-input.mjs +8 -6
- package/dist/plugins/analytics/routes.mjs +25 -13
- package/dist/plugins/cache.d.mts +62 -126
- package/dist/plugins/cache.mjs +146 -134
- package/dist/plugins/coerce.d.mts +3 -2
- package/dist/plugins/coerce.mjs +25 -8
- package/dist/scalar.d.mts +24 -13
- package/dist/scalar.mjs +292 -201
- package/dist/silgi.d.mts +35 -0
- package/dist/silgi.mjs +177 -103
- package/dist/ws.d.mts +26 -27
- package/dist/ws.mjs +128 -89
- package/package.json +2 -4
package/dist/plugins/cache.mjs
CHANGED
|
@@ -4,86 +4,128 @@ import { defineCachedFunction, setStorage, useStorage } from "ocache";
|
|
|
4
4
|
import { hash } from "ohash";
|
|
5
5
|
//#region src/plugins/cache.ts
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* -
|
|
18
|
-
*
|
|
7
|
+
* Response cache plugin
|
|
8
|
+
* -----------------------
|
|
9
|
+
*
|
|
10
|
+
* Caches resolver output using `ocache` underneath. Drop the wrap on
|
|
11
|
+
* a query procedure and its return value is memoized, deduplicated,
|
|
12
|
+
* and (by default) served stale-while-revalidate.
|
|
13
|
+
*
|
|
14
|
+
* The storage backend is pluggable. If the parent silgi instance has
|
|
15
|
+
* a `'cache'` mount configured via `silgi({ storage })`, we wire ocache
|
|
16
|
+
* to that mount the first time `cacheQuery()` runs. Otherwise ocache
|
|
17
|
+
* falls back to its built-in in-memory map. Users who want a different
|
|
18
|
+
* backend without the silgi storage mount can call `setCacheStorage()`
|
|
19
|
+
* directly, or wrap an unstorage instance via `createUnstorageAdapter()`.
|
|
20
|
+
*
|
|
21
|
+
* Every ocache feature is exposed through the options:
|
|
22
|
+
*
|
|
23
|
+
* TTL + stale-while-revalidate — `maxAge`, `swr`, `staleMaxAge`
|
|
24
|
+
* Request deduplication — built-in, concurrent calls share
|
|
25
|
+
* one in-flight promise
|
|
26
|
+
* Integrity — redeploy invalidates stale cache
|
|
27
|
+
* when the resolver's hash changes
|
|
28
|
+
* Per-request bypass / invalidate — `shouldBypassCache`,
|
|
29
|
+
* `shouldInvalidateCache`
|
|
30
|
+
* Entry validation / transform — `validate`, `transform`
|
|
31
|
+
* Multi-tier storage — read cascade, write to all
|
|
32
|
+
* Manual invalidation — `invalidateQueryCache(name)`
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* import { cacheQuery, setCacheStorage, createUnstorageAdapter } from 'silgi/cache'
|
|
36
|
+
* import { createStorage } from 'silgi/unstorage'
|
|
37
|
+
* import redisDriver from 'unstorage/drivers/redis'
|
|
38
|
+
*
|
|
39
|
+
* const listUsers = k
|
|
40
|
+
* .$use(cacheQuery({ maxAge: 60 }))
|
|
41
|
+
* .$resolve(({ ctx }) => ctx.db.users.findMany())
|
|
42
|
+
*
|
|
43
|
+
* const storage = createStorage({ driver: redisDriver({ url: 'redis://localhost' }) })
|
|
44
|
+
* setCacheStorage(createUnstorageAdapter(storage))
|
|
45
|
+
*/
|
|
46
|
+
/**
|
|
47
|
+
* Build an ocache `StorageInterface` from anything that quacks like
|
|
48
|
+
* an unstorage store. The plugin uses it twice — once internally to
|
|
49
|
+
* bridge silgi's configured storage mount, and once as a public helper
|
|
50
|
+
* for users who bring their own unstorage instance.
|
|
51
|
+
*
|
|
52
|
+
* Setting `value` to `null` / `undefined` intentionally triggers a
|
|
53
|
+
* delete so ocache's "mark as stale" signalling survives every
|
|
54
|
+
* backend uniformly.
|
|
55
|
+
*/
|
|
56
|
+
function adaptUnstorage(storage) {
|
|
57
|
+
return {
|
|
58
|
+
get: (key) => storage.getItem(key),
|
|
59
|
+
set: (key, value, opts) => {
|
|
60
|
+
if (value === null || value === void 0) return storage.removeItem(key);
|
|
61
|
+
return storage.setItem(key, value, opts);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Public helper for users who already have an unstorage instance.
|
|
19
67
|
*
|
|
20
68
|
* @example
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* // Basic: cache for 60 seconds with SWR
|
|
25
|
-
* const listUsers = k
|
|
26
|
-
* .$use(cacheQuery({ maxAge: 60 }))
|
|
27
|
-
* .$resolve(({ ctx }) => ctx.db.users.findMany())
|
|
28
|
-
*
|
|
29
|
-
* // With unstorage backend (Redis)
|
|
30
|
-
* import { createUnstorageAdapter } from 'silgi/cache'
|
|
31
|
-
* import { createStorage } from 'silgi/unstorage'
|
|
32
|
-
* import redisDriver from 'unstorage/drivers/redis'
|
|
33
|
-
*
|
|
34
|
-
* const storage = createStorage({ driver: redisDriver({ url: 'redis://localhost' }) })
|
|
35
|
-
* setCacheStorage(createUnstorageAdapter(storage))
|
|
36
|
-
* ```
|
|
69
|
+
* const storage = createStorage({ driver: redisDriver({ ... }) })
|
|
70
|
+
* setCacheStorage(createUnstorageAdapter(storage))
|
|
37
71
|
*/
|
|
72
|
+
function createUnstorageAdapter(storage) {
|
|
73
|
+
return adaptUnstorage(storage);
|
|
74
|
+
}
|
|
38
75
|
/**
|
|
39
|
-
*
|
|
40
|
-
*
|
|
76
|
+
* Connect ocache to the silgi instance's `'cache'` storage mount, if
|
|
77
|
+
* one is configured. Idempotent — the first `cacheQuery()` call wins
|
|
78
|
+
* and every subsequent one is a cheap boolean check.
|
|
79
|
+
*
|
|
80
|
+
* When silgi has no storage mount the call is a silent no-op: ocache
|
|
81
|
+
* keeps its built-in in-memory backend, which is still correct (just
|
|
82
|
+
* not shared across processes).
|
|
41
83
|
*/
|
|
42
|
-
let
|
|
84
|
+
let storageConnected = false;
|
|
43
85
|
function ensureStorageConnected() {
|
|
44
|
-
if (
|
|
45
|
-
|
|
86
|
+
if (storageConnected) return;
|
|
87
|
+
storageConnected = true;
|
|
46
88
|
try {
|
|
47
|
-
|
|
48
|
-
setStorage({
|
|
49
|
-
get: (key) => storage.getItem(key),
|
|
50
|
-
set: (key, value, opts) => {
|
|
51
|
-
if (value === null || value === void 0) {
|
|
52
|
-
storage.removeItem(key);
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
storage.setItem(key, value, opts?.ttl ? { ttl: opts.ttl } : void 0);
|
|
56
|
-
}
|
|
57
|
-
});
|
|
89
|
+
setStorage(adaptUnstorage(useStorage$1("cache")));
|
|
58
90
|
} catch {}
|
|
59
91
|
}
|
|
60
|
-
/**
|
|
92
|
+
/**
|
|
93
|
+
* Per-procedure set of cache keys, keyed by the procedure's cache name.
|
|
94
|
+
* `invalidateQueryCache(name)` uses this to wipe every key that a given
|
|
95
|
+
* procedure has written to backing storage.
|
|
96
|
+
*
|
|
97
|
+
* Module-global by design: different silgi instances in the same
|
|
98
|
+
* process calling the same procedure name end up sharing the same
|
|
99
|
+
* ocache backend anyway, so a unified registry is correct.
|
|
100
|
+
*/
|
|
61
101
|
const cacheKeyRegistry = /* @__PURE__ */ new Map();
|
|
62
102
|
/**
|
|
63
|
-
*
|
|
103
|
+
* Build the cache-key generator from user options. When the user
|
|
104
|
+
* passes `getKey`, we use it verbatim. Otherwise we hash the input
|
|
105
|
+
* with ohash, falling back to an empty string when there is no input
|
|
106
|
+
* (shared-cache-for-parameterless-queries is almost always what
|
|
107
|
+
* people want).
|
|
108
|
+
*/
|
|
109
|
+
function buildKeyFn(custom) {
|
|
110
|
+
if (custom) return custom;
|
|
111
|
+
return (input) => input !== void 0 && input !== null ? hash(input) : "";
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Wrap middleware that caches a query procedure's output.
|
|
64
115
|
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
116
|
+
* Defaults: 60-second TTL, SWR on. Every other knob comes from
|
|
117
|
+
* {@link CacheQueryOptions}.
|
|
67
118
|
*
|
|
68
119
|
* @example
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
* swr: true,
|
|
79
|
-
* staleMaxAge: 600,
|
|
80
|
-
* shouldBypassCache: (input) => (input as any)?.noCache,
|
|
81
|
-
* shouldInvalidateCache: (input) => (input as any)?.refresh,
|
|
82
|
-
* validate: (entry) => Array.isArray(entry.value),
|
|
83
|
-
* onError: (err) => console.error('[cache]', err),
|
|
84
|
-
* }))
|
|
85
|
-
* .$resolve(({ ctx }) => ctx.db.posts.findMany())
|
|
86
|
-
* ```
|
|
120
|
+
* const listPosts = k
|
|
121
|
+
* .$use(cacheQuery({
|
|
122
|
+
* maxAge: 300,
|
|
123
|
+
* staleMaxAge: 600,
|
|
124
|
+
* shouldBypassCache: (input) => (input as any)?.noCache,
|
|
125
|
+
* validate: (entry) => Array.isArray(entry.value),
|
|
126
|
+
* onError: (err) => console.error('[cache]', err),
|
|
127
|
+
* }))
|
|
128
|
+
* .$resolve(({ ctx }) => ctx.db.posts.findMany())
|
|
87
129
|
*/
|
|
88
130
|
function cacheQuery(options = {}) {
|
|
89
131
|
const maxAge = options.maxAge ?? 60;
|
|
@@ -91,29 +133,32 @@ function cacheQuery(options = {}) {
|
|
|
91
133
|
const staleMaxAge = options.staleMaxAge ?? maxAge;
|
|
92
134
|
const customGetKey = options.getKey;
|
|
93
135
|
let cacheName = options.name;
|
|
94
|
-
const pendingNextMap = /* @__PURE__ */ new Map();
|
|
95
|
-
let requestCounter = 0;
|
|
96
136
|
let cachedFn = null;
|
|
97
|
-
let keyFn;
|
|
137
|
+
let keyFn = buildKeyFn(customGetKey);
|
|
138
|
+
/**
|
|
139
|
+
* Two concurrent requests can race through the same cache entry:
|
|
140
|
+
* ocache calls our inner `_resolve` for each one, and they must not
|
|
141
|
+
* share a closure-scoped `next`. We keep a per-request map keyed
|
|
142
|
+
* by a monotonic counter so each call resolves its *own* `next()`.
|
|
143
|
+
*/
|
|
144
|
+
const pendingNext = /* @__PURE__ */ new Map();
|
|
145
|
+
let requestCounter = 0;
|
|
98
146
|
return {
|
|
99
147
|
kind: "wrap",
|
|
100
148
|
fn: async (ctx, next) => {
|
|
101
149
|
ensureStorageConnected();
|
|
102
150
|
if (!cachedFn) {
|
|
103
|
-
|
|
151
|
+
cacheName ??= ctx.__procedurePath ?? `proc_${hash(next.toString()).slice(0, 8)}`;
|
|
104
152
|
const resolvedName = cacheName;
|
|
153
|
+
const resolvedBase = options.base ?? "/cache";
|
|
105
154
|
const keySet = /* @__PURE__ */ new Set();
|
|
106
155
|
cacheKeyRegistry.set(resolvedName, keySet);
|
|
107
|
-
keyFn = customGetKey ? (input) => customGetKey(input) : (input) => input !== void 0 && input !== null ? hash(input) : "";
|
|
108
|
-
const resolvedBase = options.base ?? "/cache";
|
|
109
156
|
cachedFn = defineCachedFunction(async (_input, requestId) => {
|
|
110
|
-
const
|
|
111
|
-
const
|
|
112
|
-
if (
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
throw new Error("[silgi/cache] Missing next() for cache resolve");
|
|
157
|
+
const key = requestId ?? keyFn(_input);
|
|
158
|
+
const fn = pendingNext.get(key);
|
|
159
|
+
if (!fn) throw new Error("[silgi/cache] Missing next() for cache resolve");
|
|
160
|
+
pendingNext.delete(key);
|
|
161
|
+
return fn();
|
|
117
162
|
}, {
|
|
118
163
|
name: resolvedName,
|
|
119
164
|
group: "silgi",
|
|
@@ -125,8 +170,8 @@ function cacheQuery(options = {}) {
|
|
|
125
170
|
onError: options.onError,
|
|
126
171
|
validate: options.validate,
|
|
127
172
|
transform: options.transform,
|
|
128
|
-
shouldBypassCache: options.shouldBypassCache
|
|
129
|
-
shouldInvalidateCache: options.shouldInvalidateCache
|
|
173
|
+
shouldBypassCache: options.shouldBypassCache,
|
|
174
|
+
shouldInvalidateCache: options.shouldInvalidateCache,
|
|
130
175
|
getKey: (input) => {
|
|
131
176
|
const key = keyFn(input);
|
|
132
177
|
keySet.add(`${resolvedBase}:silgi:${resolvedName}:${key}.json`);
|
|
@@ -136,77 +181,44 @@ function cacheQuery(options = {}) {
|
|
|
136
181
|
}
|
|
137
182
|
const input = ctx[RAW_INPUT];
|
|
138
183
|
const requestId = `__req_${++requestCounter}`;
|
|
139
|
-
|
|
184
|
+
pendingNext.set(requestId, next);
|
|
140
185
|
return cachedFn(input, requestId);
|
|
141
186
|
}
|
|
142
187
|
};
|
|
143
188
|
}
|
|
144
189
|
/**
|
|
145
|
-
*
|
|
190
|
+
* Wipe every cached entry for a procedure by its cache name.
|
|
146
191
|
*
|
|
147
|
-
*
|
|
192
|
+
* Typically called after a mutation that makes related queries stale.
|
|
193
|
+
* Names default to the procedure's auto-generated path, or whatever
|
|
194
|
+
* was passed via `cacheQuery({ name })`.
|
|
148
195
|
*
|
|
149
196
|
* @example
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
* })
|
|
156
|
-
* ```
|
|
197
|
+
* const createUser = k.$resolve(async ({ input, ctx }) => {
|
|
198
|
+
* const user = await ctx.db.users.create(input)
|
|
199
|
+
* await invalidateQueryCache('users_list')
|
|
200
|
+
* return user
|
|
201
|
+
* })
|
|
157
202
|
*/
|
|
158
203
|
async function invalidateQueryCache(name) {
|
|
159
204
|
const keys = cacheKeyRegistry.get(name);
|
|
160
|
-
if (keys)
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
205
|
+
if (!keys) return;
|
|
206
|
+
const storage = useStorage();
|
|
207
|
+
await Promise.all([...keys].map((key) => storage.set(key, null)));
|
|
208
|
+
keys.clear();
|
|
165
209
|
}
|
|
166
210
|
/**
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
* For production, use `createUnstorageAdapter()` with Redis, Cloudflare KV, etc.
|
|
211
|
+
* Replace ocache's storage backend. Default is an in-memory map with
|
|
212
|
+
* TTL; production deployments usually want Redis or Cloudflare KV via
|
|
213
|
+
* `createUnstorageAdapter()`.
|
|
171
214
|
*
|
|
172
215
|
* @example
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
* import redisDriver from 'unstorage/drivers/redis'
|
|
177
|
-
*
|
|
178
|
-
* setCacheStorage(createUnstorageAdapter(
|
|
179
|
-
* createStorage({ driver: redisDriver({ url: 'redis://localhost' }) })
|
|
180
|
-
* ))
|
|
181
|
-
* ```
|
|
216
|
+
* setCacheStorage(createUnstorageAdapter(
|
|
217
|
+
* createStorage({ driver: redisDriver({ url: 'redis://localhost' }) })
|
|
218
|
+
* ))
|
|
182
219
|
*/
|
|
183
220
|
function setCacheStorage(storage) {
|
|
184
221
|
setStorage(storage);
|
|
185
222
|
}
|
|
186
|
-
/**
|
|
187
|
-
* Create an ocache-compatible storage adapter from an unstorage instance.
|
|
188
|
-
*
|
|
189
|
-
* @example
|
|
190
|
-
* ```ts
|
|
191
|
-
* import { createStorage } from 'silgi/unstorage'
|
|
192
|
-
* import redisDriver from 'unstorage/drivers/redis'
|
|
193
|
-
*
|
|
194
|
-
* const storage = createStorage({ driver: redisDriver({ url: 'redis://localhost' }) })
|
|
195
|
-
* const adapter = createUnstorageAdapter(storage)
|
|
196
|
-
* setCacheStorage(adapter)
|
|
197
|
-
* ```
|
|
198
|
-
*/
|
|
199
|
-
function createUnstorageAdapter(storage) {
|
|
200
|
-
return {
|
|
201
|
-
get: (key) => storage.getItem(key),
|
|
202
|
-
set: (key, value, opts) => {
|
|
203
|
-
if (value === null || value === void 0) {
|
|
204
|
-
storage.removeItem(key);
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
207
|
-
storage.setItem(key, value, opts);
|
|
208
|
-
}
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
223
|
//#endregion
|
|
212
224
|
export { cacheQuery, createUnstorageAdapter, invalidateQueryCache, setCacheStorage };
|
|
@@ -13,8 +13,9 @@ import { WrapDef } from "../types.mjs";
|
|
|
13
13
|
* - "" → undefined (empty strings become undefined)
|
|
14
14
|
* - Everything else → kept as-is
|
|
15
15
|
*
|
|
16
|
-
* Implemented as a wrap
|
|
17
|
-
*
|
|
16
|
+
* Implemented as a wrap so it runs after the pipeline has populated the
|
|
17
|
+
* `RAW_INPUT` slot on ctx — see the caveat in the top-level docstring
|
|
18
|
+
* about ordering vs. input schema validation.
|
|
18
19
|
*/
|
|
19
20
|
declare const coerceGuard: WrapDef<Record<string, unknown>>;
|
|
20
21
|
declare function coerceValue(value: unknown): unknown;
|
package/dist/plugins/coerce.mjs
CHANGED
|
@@ -7,17 +7,33 @@ import { RAW_INPUT } from "../core/ctx-symbols.mjs";
|
|
|
7
7
|
* query parameters. This wrap coerces common types automatically:
|
|
8
8
|
* "123" → 123, "true" → true, "null" → null, etc.
|
|
9
9
|
*
|
|
10
|
+
* @remarks
|
|
11
|
+
* **Ordering caveat.** The pipeline validates `$input` schemas *before*
|
|
12
|
+
* running the wrap onion. That means a plain `z.number()` rejects "123"
|
|
13
|
+
* before this wrap can see it. You have three options:
|
|
14
|
+
*
|
|
15
|
+
* 1. **Use Zod's own coercion** — `z.coerce.number()`, `z.coerce.boolean()`.
|
|
16
|
+
* Zero extra wrap, works for simple cases.
|
|
17
|
+
* 2. **Skip the input schema and validate inside the resolver** — pairs
|
|
18
|
+
* naturally with `coerceGuard` because the wrap always runs first.
|
|
19
|
+
* 3. **Use a string-accepting schema and coerce with `.transform()`** —
|
|
20
|
+
* e.g. `z.object({ id: z.string().transform(Number) })`. Again no
|
|
21
|
+
* wrap needed.
|
|
22
|
+
*
|
|
23
|
+
* `coerceGuard` itself is useful when you have NO input schema but still
|
|
24
|
+
* want `"42"` / `"true"` / `"null"` normalised before your resolver runs.
|
|
25
|
+
*
|
|
10
26
|
* @example
|
|
11
27
|
* ```ts
|
|
12
|
-
*
|
|
13
|
-
*
|
|
28
|
+
* // Works: no schema → wrap can reshape freely.
|
|
14
29
|
* const getUser = k
|
|
15
30
|
* .$use(coerceGuard)
|
|
16
|
-
* .$
|
|
17
|
-
* .$resolve(({ input }) => db.users.find(input.id))
|
|
31
|
+
* .$resolve(({ input }) => db.users.find((input as any).id))
|
|
18
32
|
*
|
|
19
|
-
* //
|
|
20
|
-
*
|
|
33
|
+
* // Works: schema uses z.coerce.
|
|
34
|
+
* const getUser2 = k
|
|
35
|
+
* .$input(z.object({ id: z.coerce.number() }))
|
|
36
|
+
* .$resolve(({ input }) => db.users.find(input.id))
|
|
21
37
|
* ```
|
|
22
38
|
*/
|
|
23
39
|
/**
|
|
@@ -32,8 +48,9 @@ import { RAW_INPUT } from "../core/ctx-symbols.mjs";
|
|
|
32
48
|
* - "" → undefined (empty strings become undefined)
|
|
33
49
|
* - Everything else → kept as-is
|
|
34
50
|
*
|
|
35
|
-
* Implemented as a wrap
|
|
36
|
-
*
|
|
51
|
+
* Implemented as a wrap so it runs after the pipeline has populated the
|
|
52
|
+
* `RAW_INPUT` slot on ctx — see the caveat in the top-level docstring
|
|
53
|
+
* about ordering vs. input schema validation.
|
|
37
54
|
*/
|
|
38
55
|
const coerceGuard = {
|
|
39
56
|
kind: "wrap",
|
package/dist/scalar.d.mts
CHANGED
|
@@ -9,42 +9,53 @@ interface ScalarOptions {
|
|
|
9
9
|
url: string;
|
|
10
10
|
description?: string;
|
|
11
11
|
}[];
|
|
12
|
-
/** Security scheme (e.g. Bearer token) */
|
|
12
|
+
/** Security scheme (e.g. Bearer token, API key header). */
|
|
13
13
|
security?: {
|
|
14
|
-
type: 'http' | 'apiKey';
|
|
15
|
-
scheme?: string;
|
|
16
|
-
bearerFormat?: string;
|
|
17
|
-
in?: 'header' | 'query';
|
|
14
|
+
type: 'http' | 'apiKey'; /** For `type: 'http'`, e.g. `'bearer'`. */
|
|
15
|
+
scheme?: string; /** For `type: 'http'` + bearer, e.g. `'JWT'`. */
|
|
16
|
+
bearerFormat?: string; /** For `type: 'apiKey'`, which side of the request carries the key. */
|
|
17
|
+
in?: 'header' | 'query'; /** For `type: 'apiKey'`, the header / query-param name. */
|
|
18
18
|
name?: string;
|
|
19
19
|
description?: string;
|
|
20
20
|
};
|
|
21
|
-
/** Contact info */
|
|
22
21
|
contact?: {
|
|
23
22
|
name?: string;
|
|
24
23
|
url?: string;
|
|
25
24
|
email?: string;
|
|
26
25
|
};
|
|
27
|
-
/** License */
|
|
28
26
|
license?: {
|
|
29
27
|
name: string;
|
|
30
28
|
url?: string;
|
|
31
29
|
};
|
|
32
|
-
/** External docs */
|
|
33
30
|
externalDocs?: {
|
|
34
31
|
url: string;
|
|
35
32
|
description?: string;
|
|
36
33
|
};
|
|
37
34
|
/**
|
|
38
|
-
* Scalar UI script
|
|
35
|
+
* Where to load the Scalar UI script from.
|
|
39
36
|
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
37
|
+
* `'cdn'` — `cdn.jsdelivr.net` (default).
|
|
38
|
+
* `'unpkg'` — `unpkg.com`.
|
|
39
|
+
* `'local'` — serve from `node_modules` (offline; requires the
|
|
40
|
+
* `@scalar/api-reference` package installed).
|
|
41
|
+
* string — any custom URL, e.g. a self-hosted asset path.
|
|
44
42
|
*/
|
|
45
43
|
cdn?: 'cdn' | 'unpkg' | 'local' | (string & {});
|
|
46
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* Generate an OpenAPI 3.1.0 document for a `RouterDef`.
|
|
47
|
+
*
|
|
48
|
+
* The document is a plain object and can be re-serialized, cached,
|
|
49
|
+
* piped into any OpenAPI consumer (codegen, docs site, validators). We
|
|
50
|
+
* do not return a typed `OpenAPIV3_1.Document` because the typings in
|
|
51
|
+
* the ecosystem are noisy; downstream consumers usually only care
|
|
52
|
+
* about a handful of keys anyway.
|
|
53
|
+
*/
|
|
47
54
|
declare function generateOpenAPI(router: RouterDef, options?: ScalarOptions, basePath?: string, registry?: SchemaRegistry): Record<string, unknown>;
|
|
55
|
+
/**
|
|
56
|
+
* Render the minimal HTML shell the Scalar UI needs. The UI itself is
|
|
57
|
+
* a single script that reads the `data-url` attribute to pull the spec.
|
|
58
|
+
*/
|
|
48
59
|
declare function scalarHTML(specUrl: string, options?: ScalarOptions): string;
|
|
49
60
|
//#endregion
|
|
50
61
|
export { ScalarOptions, generateOpenAPI, scalarHTML };
|