@guidelytech/sveltekit 0.15.0 → 0.17.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.
- package/README.md +35 -0
- package/dist/index.cjs +53 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +30 -1
- package/dist/index.d.ts +30 -1
- package/dist/index.js +45 -2
- package/dist/index.js.map +1 -1
- package/dist/server.cjs +153 -6
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +91 -1
- package/dist/server.d.ts +91 -1
- package/dist/server.js +148 -5
- package/dist/server.js.map +1 -1
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -116,3 +116,38 @@ export {};
|
|
|
116
116
|
|
|
117
117
|
Use the same attributes on the server as the browser will have (`id`, traits, `account`) so both
|
|
118
118
|
agree; rollouts and experiments for anonymous visitors (no id on the server) settle in the browser.
|
|
119
|
+
|
|
120
|
+
## Error monitoring
|
|
121
|
+
|
|
122
|
+
`handleErrorWithGuidely` replaces Sentry's `handleErrorWithSentry` in both hooks. It reports the
|
|
123
|
+
error with `contexts.sveltekit` (`routeId`, `status`, `message`), then calls your `handleError` (if
|
|
124
|
+
any) and returns its result. 404s are not reported (`{ captureNotFound: true }` to report them).
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
// src/hooks.client.ts: through the browser client initGuidely() creates
|
|
128
|
+
import { handleErrorWithGuidely } from "@guidelytech/sveltekit";
|
|
129
|
+
export const handleError = handleErrorWithGuidely(({ error, message }) => ({ message: "Something went wrong" }));
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
// src/hooks.server.ts: through a server client (@guidelytech/node, secret key)
|
|
134
|
+
import { sequence } from "@sveltejs/kit/hooks";
|
|
135
|
+
import { GUIDELY_SECRET_KEY } from "$env/static/private";
|
|
136
|
+
import { Guidely } from "@guidelytech/node";
|
|
137
|
+
import { guidelyHandle, handleErrorWithGuidely, setGuidelyServerClient } from "@guidelytech/sveltekit/server";
|
|
138
|
+
|
|
139
|
+
setGuidelyServerClient(new Guidely({ secretKey: GUIDELY_SECRET_KEY, environment: "production" }));
|
|
140
|
+
export const handle = sequence(guidelyHandle(), yourHandle);
|
|
141
|
+
export const handleError = handleErrorWithGuidely();
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
| Export | Where | Description |
|
|
145
|
+
|---|---|---|
|
|
146
|
+
| `handleErrorWithGuidely(handleError?, options?)` | `@guidelytech/sveltekit` (client hook) | `mechanism: sveltekit.handleError`, tag `routeId`. Errors before `initGuidely()` has a client (a failing first `load`) are sent once it does. |
|
|
147
|
+
| `handleErrorWithGuidely(handleError?, options?)` | `/server` (server hook) | Also sends the request (cookies, `Authorization` and other credential headers removed) and names the transaction `GET /blog/[slug]`. |
|
|
148
|
+
| `guidelyHandle({ client? })` | `/server` | Runs each request in its own scope of the server client (`@guidelytech/node`: breadcrumbs, `setTag`/`setUser` per request). |
|
|
149
|
+
| `setGuidelyServerClient(client)` | `/server` | Any object with `captureException(error, context)` (plus optional `flush()`, `withScope()`). No Node built-ins here: every adapter works. |
|
|
150
|
+
|
|
151
|
+
Server reporting never throws into the request and never delays the response: the flush runs in
|
|
152
|
+
the background (at most `flushTimeoutMs`, 2000 ms) under the platform's `waitUntil` (Cloudflare's
|
|
153
|
+
`platform.context`, Vercel). On other serverless hosts pass `{ waitForFlush: true }`.
|
package/dist/index.cjs
CHANGED
|
@@ -24,14 +24,56 @@ __export(index_exports, {
|
|
|
24
24
|
createServerFlags: () => createServerFlags,
|
|
25
25
|
getFlags: () => getFlags,
|
|
26
26
|
guidelyEnv: () => guidelyEnv,
|
|
27
|
+
handleErrorWithGuidely: () => handleErrorWithGuidely,
|
|
27
28
|
initGuidely: () => initGuidely,
|
|
28
29
|
loadFlags: () => loadFlags
|
|
29
30
|
});
|
|
30
31
|
module.exports = __toCommonJS(index_exports);
|
|
31
32
|
var import_environment = require("$app/environment");
|
|
32
33
|
var import_navigation = require("$app/navigation");
|
|
34
|
+
var import_svelte2 = require("@guidelytech/svelte");
|
|
35
|
+
var import_svelte3 = require("svelte");
|
|
36
|
+
|
|
37
|
+
// src/client-errors.ts
|
|
38
|
+
var import_sdk_core = require("@guidelytech/sdk-core");
|
|
33
39
|
var import_svelte = require("@guidelytech/svelte");
|
|
34
|
-
var
|
|
40
|
+
var import_store = require("svelte/store");
|
|
41
|
+
var pending = [];
|
|
42
|
+
function drainPendingErrors(client) {
|
|
43
|
+
for (const [error, hint] of pending.splice(0)) {
|
|
44
|
+
try {
|
|
45
|
+
client.captureException(error, hint);
|
|
46
|
+
} catch {
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function liveClient() {
|
|
51
|
+
try {
|
|
52
|
+
const c = (0, import_store.get)(import_svelte.guidely) ?? import_sdk_core.GuidelyClient.get();
|
|
53
|
+
return c && !c.getState().destroyed ? c : null;
|
|
54
|
+
} catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function handleErrorWithGuidely(handleError, options = {}) {
|
|
59
|
+
return (input) => {
|
|
60
|
+
try {
|
|
61
|
+
if (input?.status !== 404 || options.captureNotFound) {
|
|
62
|
+
const routeId = input?.event?.route?.id ?? null;
|
|
63
|
+
const hint = { ...routeId ? { tags: { routeId } } : {}, contexts: { sveltekit: { routeId, status: input?.status, message: input?.message } }, mechanism: { type: "sveltekit.handleError", handled: false } };
|
|
64
|
+
const client = liveClient();
|
|
65
|
+
if (client) client.captureException(input?.error, hint);
|
|
66
|
+
else if (pending.length < 10) pending.push([input?.error, hint]);
|
|
67
|
+
}
|
|
68
|
+
} catch {
|
|
69
|
+
}
|
|
70
|
+
if (handleError) return handleError(input);
|
|
71
|
+
console.error(input?.error);
|
|
72
|
+
return void 0;
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// src/index.ts
|
|
35
77
|
__reExport(index_exports, require("@guidelytech/svelte"), module.exports);
|
|
36
78
|
|
|
37
79
|
// src/server.ts
|
|
@@ -90,25 +132,26 @@ async function loadFlags(input = {}, options = {}) {
|
|
|
90
132
|
// src/index.ts
|
|
91
133
|
function initGuidely(options) {
|
|
92
134
|
if (!import_environment.browser) {
|
|
93
|
-
(0,
|
|
135
|
+
(0, import_svelte2.setGuidelyContext)(null);
|
|
94
136
|
const flags = options.flags || void 0;
|
|
95
|
-
(0,
|
|
137
|
+
(0, import_svelte2.setFlagsContext)(flags?.payload ? (0, import_svelte2.createStaticFlags)({ payload: flags.payload, attributes: flags.attributes }) : null);
|
|
96
138
|
return null;
|
|
97
139
|
}
|
|
98
|
-
const client = (0,
|
|
99
|
-
(0,
|
|
100
|
-
(0,
|
|
140
|
+
const client = (0, import_svelte2.createGuidely)(options);
|
|
141
|
+
(0, import_svelte2.setGuidelyContext)(client);
|
|
142
|
+
(0, import_svelte2.setFlagsContext)(client);
|
|
143
|
+
drainPendingErrors(client);
|
|
101
144
|
(0, import_navigation.afterNavigate)((nav) => {
|
|
102
145
|
if (nav.type !== "enter") client.page(window.location.href);
|
|
103
146
|
});
|
|
104
|
-
(0,
|
|
105
|
-
if (guidelyStoreIs(client)) (0,
|
|
147
|
+
(0, import_svelte3.onDestroy)(() => {
|
|
148
|
+
if (guidelyStoreIs(client)) (0, import_svelte2.destroyGuidely)();
|
|
106
149
|
});
|
|
107
150
|
return client;
|
|
108
151
|
}
|
|
109
152
|
function guidelyStoreIs(client) {
|
|
110
153
|
let current = null;
|
|
111
|
-
|
|
154
|
+
import_svelte2.guidely.subscribe((c) => current = c)();
|
|
112
155
|
return current === client;
|
|
113
156
|
}
|
|
114
157
|
function guidelyEnv(env2) {
|
|
@@ -124,6 +167,7 @@ function guidelyEnv(env2) {
|
|
|
124
167
|
createServerFlags,
|
|
125
168
|
getFlags,
|
|
126
169
|
guidelyEnv,
|
|
170
|
+
handleErrorWithGuidely,
|
|
127
171
|
initGuidely,
|
|
128
172
|
loadFlags,
|
|
129
173
|
...require("@guidelytech/svelte")
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/server.ts"],"sourcesContent":["import { browser } from \"$app/environment\";\nimport { afterNavigate } from \"$app/navigation\";\nimport { createGuidely, createStaticFlags, destroyGuidely, guidely, setFlagsContext, setGuidelyContext, type GuidelyClient, type GuidelyOptions } from \"@guidelytech/svelte\";\nimport { onDestroy } from \"svelte\";\n\nexport * from \"@guidelytech/svelte\";\n\n/**\n * Call once from the root `+layout.svelte` (during component initialisation). Creates the client\n * in the browser only, calls `page()` after every SvelteKit navigation and destroys the client\n * when the layout unmounts (HMR). Returns `null` on the server. Keep the signed-in user in step\n * with `syncGuidelyUser` (re-exported from `@guidelytech/svelte`).\n *\n * Pass `flags: data.flags` (from `loadFlags()` in `+layout.server.ts`) and the flag stores\n * render the same values on the server and in the browser.\n */\nexport function initGuidely(options: GuidelyOptions): GuidelyClient | null {\n if (!browser) {\n setGuidelyContext(null);\n // Server render: flag stores read a snapshot of the bootstrap payload.\n const flags = options.flags || undefined;\n setFlagsContext(flags?.payload ? createStaticFlags({ payload: flags.payload, attributes: flags.attributes }) : null);\n return null;\n }\n const client = createGuidely(options);\n setGuidelyContext(client);\n setFlagsContext(client);\n afterNavigate((nav) => {\n if (nav.type !== \"enter\") client.page(window.location.href);\n });\n onDestroy(() => {\n if (guidelyStoreIs(client)) destroyGuidely();\n });\n return client;\n}\n\nfunction guidelyStoreIs(client: GuidelyClient): boolean {\n let current: GuidelyClient | null = null;\n guidely.subscribe((c) => (current = c))();\n return current === client;\n}\n\n/**\n * Reads the public Guidely variables into client options: `VITE_GUIDELY_*` (from `import.meta.env`)\n * or SvelteKit's `PUBLIC_GUIDELY_*` (from `$env/static/public` or `$env/dynamic/public`).\n */\nexport function guidelyEnv(env: Record<string, string | undefined>): Pick<GuidelyOptions, \"projectId\" | \"environment\" | \"apiUrl\" | \"release\"> {\n return {\n projectId: env.VITE_GUIDELY_PROJECT_ID || env.PUBLIC_GUIDELY_PROJECT_ID || \"\",\n environment: ((env.VITE_GUIDELY_ENV || env.PUBLIC_GUIDELY_ENV) as GuidelyOptions[\"environment\"]) || undefined,\n apiUrl: env.VITE_GUIDELY_API_URL || env.PUBLIC_GUIDELY_API_URL || undefined,\n release: env.VITE_GUIDELY_RELEASE || env.PUBLIC_GUIDELY_RELEASE || undefined,\n };\n}\n\nexport { createServerFlags, getFlags, loadFlags } from \"./server\";\nexport type { LoadFlagsOptions } from \"./server\";\n","/**\n * `@guidelytech/sveltekit/server` — feature flags in `hooks.server.ts`, `+layout.server.ts`,\n * `+page.server.ts` and endpoints. No `$app` imports, no Svelte.\n *\n * ```ts\n * // src/hooks.server.ts\n * const flags = createServerFlags();\n * export const handle = async ({ event, resolve }) => {\n * event.locals.flags = flags.forUser({ id: event.locals.user?.id });\n * return resolve(event);\n * };\n * ```\n */\nimport { createFlagsClient, type Attributes, type FlagsBootstrap, type FlagsClient, type FlagsClientOptions, type FlagsEvaluator } from \"@guidelytech/flags\";\n\nexport type { Attributes, FlagsBootstrap, FlagsClient, FlagsClientOptions, FlagsEvaluator, FlagDetails, JsonValue } from \"@guidelytech/flags\";\n\ninterface Registry {\n clients: Map<string, FlagsClient>;\n current: FlagsClient | null;\n warned: Set<string>;\n}\n\n/** Kept on `globalThis` so Vite's dev-server reloads reuse the client (and its timers). */\nfunction registry(): Registry {\n const g = globalThis as { __guidelySvelteKitFlags?: Registry };\n return (g.__guidelySvelteKitFlags ??= { clients: new Map(), current: null, warned: new Set() });\n}\n\nfunction env(name: string): string | undefined {\n try {\n return (globalThis as { process?: { env?: Record<string, string | undefined> } }).process?.env?.[name] || undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * The process-wide flags client (created once, reused across requests). Options default to\n * `GUIDELY_SDK_KEY` (public `pk_…` or secret `gly_sk_…`), falling back to `PUBLIC_GUIDELY_PROJECT_ID`,\n * plus `GUIDELY_API_URL` / `PUBLIC_GUIDELY_API_URL` and `GUIDELY_ENV` / `PUBLIC_GUIDELY_ENV`. Polls\n * every 60 s; on serverless / edge adapters use `{ pollingMode: \"lazy\" }`.\n */\nexport function createServerFlags(options: Partial<FlagsClientOptions> = {}): FlagsClient {\n const sdkKey = options.sdkKey ?? options.projectId ?? env(\"GUIDELY_SDK_KEY\") ?? env(\"PUBLIC_GUIDELY_PROJECT_ID\");\n if (!sdkKey) throw new Error(\"[Guidely] createServerFlags: pass `sdkKey` or set GUIDELY_SDK_KEY / PUBLIC_GUIDELY_PROJECT_ID.\");\n const apiUrl = options.apiUrl ?? env(\"GUIDELY_API_URL\") ?? env(\"PUBLIC_GUIDELY_API_URL\");\n const environment = options.environment ?? env(\"GUIDELY_ENV\") ?? env(\"PUBLIC_GUIDELY_ENV\");\n const id = `${sdkKey}|${apiUrl ?? \"\"}|${environment ?? \"\"}|${options.pollingMode ?? \"auto\"}`;\n const reg = registry();\n let client = reg.clients.get(id);\n if (!client) {\n client = createFlagsClient({ storage: false, ...options, sdkKey, apiUrl, environment });\n reg.clients.set(id, client);\n }\n reg.current = client;\n return client;\n}\n\nexport interface LoadFlagsOptions {\n /** A client from `createServerFlags(...)` (default: the last one created, or one from environment variables). */\n client?: FlagsClient;\n /** How long to wait for the first download before serving fallbacks (default 1500 ms). */\n timeoutMs?: number;\n}\n\nfunction settle(client: FlagsClient, timeoutMs: number): Promise<void> {\n if (client.isReady()) return Promise.resolve();\n return Promise.race([client.ready(), new Promise<void>((r) => setTimeout(r, timeoutMs))]);\n}\n\n/** Flag getters for one user, for this request (waits briefly for the first download). */\nexport async function getFlags(attributes: Attributes = {}, options: LoadFlagsOptions = {}): Promise<FlagsEvaluator> {\n const client = options.client ?? registry().current ?? createServerFlags();\n await settle(client, options.timeoutMs ?? 1500);\n return client.forUser(attributes);\n}\n\n/**\n * For `+layout.server.ts`: the payload and attributes to hand to the browser, so the first render\n * matches the server. Pass the request `event` (attributes come from `event.locals.flags`, set in\n * `hooks.server.ts`) or the attributes. Spread the result into `initGuidely({ …, flags: data.flags })`.\n * The payload is `null` for a `gly_sk_` client (it holds server-only flags): bootstrap from a\n * public-key client instead.\n */\nexport async function loadFlags(input: { locals?: { flags?: Pick<FlagsEvaluator, \"getAttributes\"> | null } } | Attributes = {}, options: LoadFlagsOptions = {}): Promise<{ payload: FlagsBootstrap | null; attributes: Attributes }> {\n const locals = (input as { locals?: { flags?: Pick<FlagsEvaluator, \"getAttributes\"> | null } }).locals;\n const attributes = locals && typeof locals === \"object\" ? (locals.flags?.getAttributes?.() ?? {}) : (input as Attributes);\n const client = options.client ?? registry().current ?? createServerFlags();\n if (client.sdkKey.startsWith(\"gly_sk_\")) {\n const reg = registry();\n if (!reg.warned.has(client.sdkKey)) {\n reg.warned.add(client.sdkKey);\n console.warn(\"[Guidely] loadFlags() needs a public-key client (pk_…): a gly_sk_ payload includes server-only flags. Pass { client: createServerFlags({ sdkKey: PUBLIC_GUIDELY_PROJECT_ID }) }.\");\n }\n return { payload: null, attributes };\n }\n await settle(client, options.timeoutMs ?? 1500);\n return { payload: client.getPayload(), attributes };\n}\n"],"mappings":"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","names":["import_svelte","env"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/client-errors.ts","../src/server.ts"],"sourcesContent":["import { browser } from \"$app/environment\";\nimport { afterNavigate } from \"$app/navigation\";\nimport { createGuidely, createStaticFlags, destroyGuidely, guidely, setFlagsContext, setGuidelyContext, type GuidelyClient, type GuidelyOptions } from \"@guidelytech/svelte\";\nimport { onDestroy } from \"svelte\";\nimport { drainPendingErrors } from \"./client-errors\";\n\nexport * from \"@guidelytech/svelte\";\nexport { handleErrorWithGuidely } from \"./client-errors\";\nexport type { ClientHandleErrorInput } from \"./client-errors\";\n\n/**\n * Call once from the root `+layout.svelte` (during component initialisation). Creates the client\n * in the browser only, calls `page()` after every SvelteKit navigation and destroys the client\n * when the layout unmounts (HMR). Returns `null` on the server. Keep the signed-in user in step\n * with `syncGuidelyUser` (re-exported from `@guidelytech/svelte`).\n *\n * Pass `flags: data.flags` (from `loadFlags()` in `+layout.server.ts`) and the flag stores\n * render the same values on the server and in the browser.\n */\nexport function initGuidely(options: GuidelyOptions): GuidelyClient | null {\n if (!browser) {\n setGuidelyContext(null);\n // Server render: flag stores read a snapshot of the bootstrap payload.\n const flags = options.flags || undefined;\n setFlagsContext(flags?.payload ? createStaticFlags({ payload: flags.payload, attributes: flags.attributes }) : null);\n return null;\n }\n const client = createGuidely(options);\n setGuidelyContext(client);\n setFlagsContext(client);\n // Errors `handleErrorWithGuidely` saw before the client existed (a failing first `load`).\n drainPendingErrors(client);\n afterNavigate((nav) => {\n if (nav.type !== \"enter\") client.page(window.location.href);\n });\n onDestroy(() => {\n if (guidelyStoreIs(client)) destroyGuidely();\n });\n return client;\n}\n\nfunction guidelyStoreIs(client: GuidelyClient): boolean {\n let current: GuidelyClient | null = null;\n guidely.subscribe((c) => (current = c))();\n return current === client;\n}\n\n/**\n * Reads the public Guidely variables into client options: `VITE_GUIDELY_*` (from `import.meta.env`)\n * or SvelteKit's `PUBLIC_GUIDELY_*` (from `$env/static/public` or `$env/dynamic/public`).\n */\nexport function guidelyEnv(env: Record<string, string | undefined>): Pick<GuidelyOptions, \"projectId\" | \"environment\" | \"apiUrl\" | \"release\"> {\n return {\n projectId: env.VITE_GUIDELY_PROJECT_ID || env.PUBLIC_GUIDELY_PROJECT_ID || \"\",\n environment: ((env.VITE_GUIDELY_ENV || env.PUBLIC_GUIDELY_ENV) as GuidelyOptions[\"environment\"]) || undefined,\n apiUrl: env.VITE_GUIDELY_API_URL || env.PUBLIC_GUIDELY_API_URL || undefined,\n release: env.VITE_GUIDELY_RELEASE || env.PUBLIC_GUIDELY_RELEASE || undefined,\n };\n}\n\nexport { createServerFlags, getFlags, loadFlags } from \"./server\";\nexport type { LoadFlagsOptions } from \"./server\";\n","import { GuidelyClient, type CaptureContext } from \"@guidelytech/sdk-core\";\nimport { guidely } from \"@guidelytech/svelte\";\nimport { get } from \"svelte/store\";\n\n/** `handleError`'s input in `hooks.client.ts`. */\nexport interface ClientHandleErrorInput {\n error: unknown;\n event: { route?: { id?: string | null }; url?: URL; params?: Record<string, string> };\n status: number;\n message: string;\n}\n\ntype Hint = CaptureContext & Record<string, unknown>;\n\n/**\n * Errors from before the client exists: a failing `load` on the first page runs before the root\n * layout's `initGuidely()` creates it. Sent by the next `initGuidely()` client.\n */\nconst pending: Array<[unknown, Hint]> = [];\n\n/** Sends the errors kept while no client existed (called by `initGuidely` once it has one). */\nexport function drainPendingErrors(client: GuidelyClient): void {\n for (const [error, hint] of pending.splice(0)) {\n try {\n client.captureException(error, hint);\n } catch {\n // never throw into the app\n }\n }\n}\n\nfunction liveClient(): GuidelyClient | null {\n try {\n const c = get(guidely) ?? GuidelyClient.get();\n return c && !c.getState().destroyed ? c : null;\n } catch {\n return null;\n }\n}\n\n/**\n * `handleError` for `hooks.client.ts` (Sentry's `handleErrorWithSentry`): reports the error with\n * `contexts.sveltekit` (`routeId`, `status`, `message`), then calls your `handleError` and returns\n * what it returns. 404s are not reported unless `captureNotFound`. Without a `handleError` of your\n * own the error is logged, as SvelteKit does.\n *\n * ```ts\n * // src/hooks.client.ts\n * import { handleErrorWithGuidely } from \"@guidelytech/sveltekit\";\n * export const handleError = handleErrorWithGuidely();\n * ```\n */\nexport function handleErrorWithGuidely<R = void>(handleError?: (input: ClientHandleErrorInput) => R, options: { captureNotFound?: boolean } = {}): (input: ClientHandleErrorInput) => R | undefined {\n return (input) => {\n try {\n if (input?.status !== 404 || options.captureNotFound) {\n const routeId = input?.event?.route?.id ?? null;\n const hint: Hint = { ...(routeId ? { tags: { routeId } } : {}), contexts: { sveltekit: { routeId, status: input?.status, message: input?.message } }, mechanism: { type: \"sveltekit.handleError\", handled: false } };\n const client = liveClient();\n if (client) client.captureException(input?.error, hint);\n else if (pending.length < 10) pending.push([input?.error, hint]);\n }\n } catch {\n // never break the error page\n }\n if (handleError) return handleError(input);\n console.error(input?.error);\n return undefined;\n };\n}\n","/**\n * `@guidelytech/sveltekit/server` — feature flags in `hooks.server.ts`, `+layout.server.ts`,\n * `+page.server.ts` and endpoints. No `$app` imports, no Svelte.\n *\n * ```ts\n * // src/hooks.server.ts\n * const flags = createServerFlags();\n * export const handle = async ({ event, resolve }) => {\n * event.locals.flags = flags.forUser({ id: event.locals.user?.id });\n * return resolve(event);\n * };\n * ```\n */\nimport { createFlagsClient, type Attributes, type FlagsBootstrap, type FlagsClient, type FlagsClientOptions, type FlagsEvaluator } from \"@guidelytech/flags\";\n\nexport type { Attributes, FlagsBootstrap, FlagsClient, FlagsClientOptions, FlagsEvaluator, FlagDetails, JsonValue } from \"@guidelytech/flags\";\n\ninterface Registry {\n clients: Map<string, FlagsClient>;\n current: FlagsClient | null;\n warned: Set<string>;\n}\n\n/** Kept on `globalThis` so Vite's dev-server reloads reuse the client (and its timers). */\nfunction registry(): Registry {\n const g = globalThis as { __guidelySvelteKitFlags?: Registry };\n return (g.__guidelySvelteKitFlags ??= { clients: new Map(), current: null, warned: new Set() });\n}\n\nfunction env(name: string): string | undefined {\n try {\n return (globalThis as { process?: { env?: Record<string, string | undefined> } }).process?.env?.[name] || undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * The process-wide flags client (created once, reused across requests). Options default to\n * `GUIDELY_SDK_KEY` (public `pk_…` or secret `gly_sk_…`), falling back to `PUBLIC_GUIDELY_PROJECT_ID`,\n * plus `GUIDELY_API_URL` / `PUBLIC_GUIDELY_API_URL` and `GUIDELY_ENV` / `PUBLIC_GUIDELY_ENV`. Polls\n * every 60 s; on serverless / edge adapters use `{ pollingMode: \"lazy\" }`.\n */\nexport function createServerFlags(options: Partial<FlagsClientOptions> = {}): FlagsClient {\n const sdkKey = options.sdkKey ?? options.projectId ?? env(\"GUIDELY_SDK_KEY\") ?? env(\"PUBLIC_GUIDELY_PROJECT_ID\");\n if (!sdkKey) throw new Error(\"[Guidely] createServerFlags: pass `sdkKey` or set GUIDELY_SDK_KEY / PUBLIC_GUIDELY_PROJECT_ID.\");\n const apiUrl = options.apiUrl ?? env(\"GUIDELY_API_URL\") ?? env(\"PUBLIC_GUIDELY_API_URL\");\n const environment = options.environment ?? env(\"GUIDELY_ENV\") ?? env(\"PUBLIC_GUIDELY_ENV\");\n const id = `${sdkKey}|${apiUrl ?? \"\"}|${environment ?? \"\"}|${options.pollingMode ?? \"auto\"}`;\n const reg = registry();\n let client = reg.clients.get(id);\n if (!client) {\n client = createFlagsClient({ storage: false, ...options, sdkKey, apiUrl, environment });\n reg.clients.set(id, client);\n }\n reg.current = client;\n return client;\n}\n\nexport interface LoadFlagsOptions {\n /** A client from `createServerFlags(...)` (default: the last one created, or one from environment variables). */\n client?: FlagsClient;\n /** How long to wait for the first download before serving fallbacks (default 1500 ms). */\n timeoutMs?: number;\n}\n\nfunction settle(client: FlagsClient, timeoutMs: number): Promise<void> {\n if (client.isReady()) return Promise.resolve();\n return Promise.race([client.ready(), new Promise<void>((r) => setTimeout(r, timeoutMs))]);\n}\n\n/** Flag getters for one user, for this request (waits briefly for the first download). */\nexport async function getFlags(attributes: Attributes = {}, options: LoadFlagsOptions = {}): Promise<FlagsEvaluator> {\n const client = options.client ?? registry().current ?? createServerFlags();\n await settle(client, options.timeoutMs ?? 1500);\n return client.forUser(attributes);\n}\n\n/**\n * For `+layout.server.ts`: the payload and attributes to hand to the browser, so the first render\n * matches the server. Pass the request `event` (attributes come from `event.locals.flags`, set in\n * `hooks.server.ts`) or the attributes. Spread the result into `initGuidely({ …, flags: data.flags })`.\n * The payload is `null` for a `gly_sk_` client (it holds server-only flags): bootstrap from a\n * public-key client instead.\n */\nexport async function loadFlags(input: { locals?: { flags?: Pick<FlagsEvaluator, \"getAttributes\"> | null } } | Attributes = {}, options: LoadFlagsOptions = {}): Promise<{ payload: FlagsBootstrap | null; attributes: Attributes }> {\n const locals = (input as { locals?: { flags?: Pick<FlagsEvaluator, \"getAttributes\"> | null } }).locals;\n const attributes = locals && typeof locals === \"object\" ? (locals.flags?.getAttributes?.() ?? {}) : (input as Attributes);\n const client = options.client ?? registry().current ?? createServerFlags();\n if (client.sdkKey.startsWith(\"gly_sk_\")) {\n const reg = registry();\n if (!reg.warned.has(client.sdkKey)) {\n reg.warned.add(client.sdkKey);\n console.warn(\"[Guidely] loadFlags() needs a public-key client (pk_…): a gly_sk_ payload includes server-only flags. Pass { client: createServerFlags({ sdkKey: PUBLIC_GUIDELY_PROJECT_ID }) }.\");\n }\n return { payload: null, attributes };\n }\n await settle(client, options.timeoutMs ?? 1500);\n return { payload: client.getPayload(), attributes };\n}\n\n// Error monitoring: `handleErrorWithGuidely` for hooks.server.ts and a request-scope `guidelyHandle()`.\nexport { captureServerError, guidelyHandle, handleErrorWithGuidely, setGuidelyServerClient } from \"./server-errors\";\nexport type { GuidelyServerErrorClient, RequestEventLike, ServerErrorOptions, ServerHandleErrorInput } from \"./server-errors\";\n"],"mappings":"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","names":["import_svelte","env"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -2,6 +2,35 @@ import { GuidelyOptions, GuidelyClient } from '@guidelytech/svelte';
|
|
|
2
2
|
export * from '@guidelytech/svelte';
|
|
3
3
|
import { FlagsClient, FlagsClientOptions, Attributes, FlagsEvaluator, FlagsBootstrap } from '@guidelytech/flags';
|
|
4
4
|
|
|
5
|
+
/** `handleError`'s input in `hooks.client.ts`. */
|
|
6
|
+
interface ClientHandleErrorInput {
|
|
7
|
+
error: unknown;
|
|
8
|
+
event: {
|
|
9
|
+
route?: {
|
|
10
|
+
id?: string | null;
|
|
11
|
+
};
|
|
12
|
+
url?: URL;
|
|
13
|
+
params?: Record<string, string>;
|
|
14
|
+
};
|
|
15
|
+
status: number;
|
|
16
|
+
message: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* `handleError` for `hooks.client.ts` (Sentry's `handleErrorWithSentry`): reports the error with
|
|
20
|
+
* `contexts.sveltekit` (`routeId`, `status`, `message`), then calls your `handleError` and returns
|
|
21
|
+
* what it returns. 404s are not reported unless `captureNotFound`. Without a `handleError` of your
|
|
22
|
+
* own the error is logged, as SvelteKit does.
|
|
23
|
+
*
|
|
24
|
+
* ```ts
|
|
25
|
+
* // src/hooks.client.ts
|
|
26
|
+
* import { handleErrorWithGuidely } from "@guidelytech/sveltekit";
|
|
27
|
+
* export const handleError = handleErrorWithGuidely();
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
declare function handleErrorWithGuidely<R = void>(handleError?: (input: ClientHandleErrorInput) => R, options?: {
|
|
31
|
+
captureNotFound?: boolean;
|
|
32
|
+
}): (input: ClientHandleErrorInput) => R | undefined;
|
|
33
|
+
|
|
5
34
|
/**
|
|
6
35
|
* `@guidelytech/sveltekit/server` — feature flags in `hooks.server.ts`, `+layout.server.ts`,
|
|
7
36
|
* `+page.server.ts` and endpoints. No `$app` imports, no Svelte.
|
|
@@ -63,4 +92,4 @@ declare function initGuidely(options: GuidelyOptions): GuidelyClient | null;
|
|
|
63
92
|
*/
|
|
64
93
|
declare function guidelyEnv(env: Record<string, string | undefined>): Pick<GuidelyOptions, "projectId" | "environment" | "apiUrl" | "release">;
|
|
65
94
|
|
|
66
|
-
export { type LoadFlagsOptions, createServerFlags, getFlags, guidelyEnv, initGuidely, loadFlags };
|
|
95
|
+
export { type ClientHandleErrorInput, type LoadFlagsOptions, createServerFlags, getFlags, guidelyEnv, handleErrorWithGuidely, initGuidely, loadFlags };
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,35 @@ import { GuidelyOptions, GuidelyClient } from '@guidelytech/svelte';
|
|
|
2
2
|
export * from '@guidelytech/svelte';
|
|
3
3
|
import { FlagsClient, FlagsClientOptions, Attributes, FlagsEvaluator, FlagsBootstrap } from '@guidelytech/flags';
|
|
4
4
|
|
|
5
|
+
/** `handleError`'s input in `hooks.client.ts`. */
|
|
6
|
+
interface ClientHandleErrorInput {
|
|
7
|
+
error: unknown;
|
|
8
|
+
event: {
|
|
9
|
+
route?: {
|
|
10
|
+
id?: string | null;
|
|
11
|
+
};
|
|
12
|
+
url?: URL;
|
|
13
|
+
params?: Record<string, string>;
|
|
14
|
+
};
|
|
15
|
+
status: number;
|
|
16
|
+
message: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* `handleError` for `hooks.client.ts` (Sentry's `handleErrorWithSentry`): reports the error with
|
|
20
|
+
* `contexts.sveltekit` (`routeId`, `status`, `message`), then calls your `handleError` and returns
|
|
21
|
+
* what it returns. 404s are not reported unless `captureNotFound`. Without a `handleError` of your
|
|
22
|
+
* own the error is logged, as SvelteKit does.
|
|
23
|
+
*
|
|
24
|
+
* ```ts
|
|
25
|
+
* // src/hooks.client.ts
|
|
26
|
+
* import { handleErrorWithGuidely } from "@guidelytech/sveltekit";
|
|
27
|
+
* export const handleError = handleErrorWithGuidely();
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
declare function handleErrorWithGuidely<R = void>(handleError?: (input: ClientHandleErrorInput) => R, options?: {
|
|
31
|
+
captureNotFound?: boolean;
|
|
32
|
+
}): (input: ClientHandleErrorInput) => R | undefined;
|
|
33
|
+
|
|
5
34
|
/**
|
|
6
35
|
* `@guidelytech/sveltekit/server` — feature flags in `hooks.server.ts`, `+layout.server.ts`,
|
|
7
36
|
* `+page.server.ts` and endpoints. No `$app` imports, no Svelte.
|
|
@@ -63,4 +92,4 @@ declare function initGuidely(options: GuidelyOptions): GuidelyClient | null;
|
|
|
63
92
|
*/
|
|
64
93
|
declare function guidelyEnv(env: Record<string, string | undefined>): Pick<GuidelyOptions, "projectId" | "environment" | "apiUrl" | "release">;
|
|
65
94
|
|
|
66
|
-
export { type LoadFlagsOptions, createServerFlags, getFlags, guidelyEnv, initGuidely, loadFlags };
|
|
95
|
+
export { type ClientHandleErrorInput, type LoadFlagsOptions, createServerFlags, getFlags, guidelyEnv, handleErrorWithGuidely, initGuidely, loadFlags };
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,49 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import { browser } from "$app/environment";
|
|
3
3
|
import { afterNavigate } from "$app/navigation";
|
|
4
|
-
import { createGuidely, createStaticFlags, destroyGuidely, guidely, setFlagsContext, setGuidelyContext } from "@guidelytech/svelte";
|
|
4
|
+
import { createGuidely, createStaticFlags, destroyGuidely, guidely as guidely2, setFlagsContext, setGuidelyContext } from "@guidelytech/svelte";
|
|
5
5
|
import { onDestroy } from "svelte";
|
|
6
|
+
|
|
7
|
+
// src/client-errors.ts
|
|
8
|
+
import { GuidelyClient } from "@guidelytech/sdk-core";
|
|
9
|
+
import { guidely } from "@guidelytech/svelte";
|
|
10
|
+
import { get } from "svelte/store";
|
|
11
|
+
var pending = [];
|
|
12
|
+
function drainPendingErrors(client) {
|
|
13
|
+
for (const [error, hint] of pending.splice(0)) {
|
|
14
|
+
try {
|
|
15
|
+
client.captureException(error, hint);
|
|
16
|
+
} catch {
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function liveClient() {
|
|
21
|
+
try {
|
|
22
|
+
const c = get(guidely) ?? GuidelyClient.get();
|
|
23
|
+
return c && !c.getState().destroyed ? c : null;
|
|
24
|
+
} catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function handleErrorWithGuidely(handleError, options = {}) {
|
|
29
|
+
return (input) => {
|
|
30
|
+
try {
|
|
31
|
+
if (input?.status !== 404 || options.captureNotFound) {
|
|
32
|
+
const routeId = input?.event?.route?.id ?? null;
|
|
33
|
+
const hint = { ...routeId ? { tags: { routeId } } : {}, contexts: { sveltekit: { routeId, status: input?.status, message: input?.message } }, mechanism: { type: "sveltekit.handleError", handled: false } };
|
|
34
|
+
const client = liveClient();
|
|
35
|
+
if (client) client.captureException(input?.error, hint);
|
|
36
|
+
else if (pending.length < 10) pending.push([input?.error, hint]);
|
|
37
|
+
}
|
|
38
|
+
} catch {
|
|
39
|
+
}
|
|
40
|
+
if (handleError) return handleError(input);
|
|
41
|
+
console.error(input?.error);
|
|
42
|
+
return void 0;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// src/index.ts
|
|
6
47
|
export * from "@guidelytech/svelte";
|
|
7
48
|
|
|
8
49
|
// src/server.ts
|
|
@@ -69,6 +110,7 @@ function initGuidely(options) {
|
|
|
69
110
|
const client = createGuidely(options);
|
|
70
111
|
setGuidelyContext(client);
|
|
71
112
|
setFlagsContext(client);
|
|
113
|
+
drainPendingErrors(client);
|
|
72
114
|
afterNavigate((nav) => {
|
|
73
115
|
if (nav.type !== "enter") client.page(window.location.href);
|
|
74
116
|
});
|
|
@@ -79,7 +121,7 @@ function initGuidely(options) {
|
|
|
79
121
|
}
|
|
80
122
|
function guidelyStoreIs(client) {
|
|
81
123
|
let current = null;
|
|
82
|
-
|
|
124
|
+
guidely2.subscribe((c) => current = c)();
|
|
83
125
|
return current === client;
|
|
84
126
|
}
|
|
85
127
|
function guidelyEnv(env2) {
|
|
@@ -94,6 +136,7 @@ export {
|
|
|
94
136
|
createServerFlags,
|
|
95
137
|
getFlags,
|
|
96
138
|
guidelyEnv,
|
|
139
|
+
handleErrorWithGuidely,
|
|
97
140
|
initGuidely,
|
|
98
141
|
loadFlags
|
|
99
142
|
};
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/server.ts"],"sourcesContent":["import { browser } from \"$app/environment\";\nimport { afterNavigate } from \"$app/navigation\";\nimport { createGuidely, createStaticFlags, destroyGuidely, guidely, setFlagsContext, setGuidelyContext, type GuidelyClient, type GuidelyOptions } from \"@guidelytech/svelte\";\nimport { onDestroy } from \"svelte\";\n\nexport * from \"@guidelytech/svelte\";\n\n/**\n * Call once from the root `+layout.svelte` (during component initialisation). Creates the client\n * in the browser only, calls `page()` after every SvelteKit navigation and destroys the client\n * when the layout unmounts (HMR). Returns `null` on the server. Keep the signed-in user in step\n * with `syncGuidelyUser` (re-exported from `@guidelytech/svelte`).\n *\n * Pass `flags: data.flags` (from `loadFlags()` in `+layout.server.ts`) and the flag stores\n * render the same values on the server and in the browser.\n */\nexport function initGuidely(options: GuidelyOptions): GuidelyClient | null {\n if (!browser) {\n setGuidelyContext(null);\n // Server render: flag stores read a snapshot of the bootstrap payload.\n const flags = options.flags || undefined;\n setFlagsContext(flags?.payload ? createStaticFlags({ payload: flags.payload, attributes: flags.attributes }) : null);\n return null;\n }\n const client = createGuidely(options);\n setGuidelyContext(client);\n setFlagsContext(client);\n afterNavigate((nav) => {\n if (nav.type !== \"enter\") client.page(window.location.href);\n });\n onDestroy(() => {\n if (guidelyStoreIs(client)) destroyGuidely();\n });\n return client;\n}\n\nfunction guidelyStoreIs(client: GuidelyClient): boolean {\n let current: GuidelyClient | null = null;\n guidely.subscribe((c) => (current = c))();\n return current === client;\n}\n\n/**\n * Reads the public Guidely variables into client options: `VITE_GUIDELY_*` (from `import.meta.env`)\n * or SvelteKit's `PUBLIC_GUIDELY_*` (from `$env/static/public` or `$env/dynamic/public`).\n */\nexport function guidelyEnv(env: Record<string, string | undefined>): Pick<GuidelyOptions, \"projectId\" | \"environment\" | \"apiUrl\" | \"release\"> {\n return {\n projectId: env.VITE_GUIDELY_PROJECT_ID || env.PUBLIC_GUIDELY_PROJECT_ID || \"\",\n environment: ((env.VITE_GUIDELY_ENV || env.PUBLIC_GUIDELY_ENV) as GuidelyOptions[\"environment\"]) || undefined,\n apiUrl: env.VITE_GUIDELY_API_URL || env.PUBLIC_GUIDELY_API_URL || undefined,\n release: env.VITE_GUIDELY_RELEASE || env.PUBLIC_GUIDELY_RELEASE || undefined,\n };\n}\n\nexport { createServerFlags, getFlags, loadFlags } from \"./server\";\nexport type { LoadFlagsOptions } from \"./server\";\n","/**\n * `@guidelytech/sveltekit/server` — feature flags in `hooks.server.ts`, `+layout.server.ts`,\n * `+page.server.ts` and endpoints. No `$app` imports, no Svelte.\n *\n * ```ts\n * // src/hooks.server.ts\n * const flags = createServerFlags();\n * export const handle = async ({ event, resolve }) => {\n * event.locals.flags = flags.forUser({ id: event.locals.user?.id });\n * return resolve(event);\n * };\n * ```\n */\nimport { createFlagsClient, type Attributes, type FlagsBootstrap, type FlagsClient, type FlagsClientOptions, type FlagsEvaluator } from \"@guidelytech/flags\";\n\nexport type { Attributes, FlagsBootstrap, FlagsClient, FlagsClientOptions, FlagsEvaluator, FlagDetails, JsonValue } from \"@guidelytech/flags\";\n\ninterface Registry {\n clients: Map<string, FlagsClient>;\n current: FlagsClient | null;\n warned: Set<string>;\n}\n\n/** Kept on `globalThis` so Vite's dev-server reloads reuse the client (and its timers). */\nfunction registry(): Registry {\n const g = globalThis as { __guidelySvelteKitFlags?: Registry };\n return (g.__guidelySvelteKitFlags ??= { clients: new Map(), current: null, warned: new Set() });\n}\n\nfunction env(name: string): string | undefined {\n try {\n return (globalThis as { process?: { env?: Record<string, string | undefined> } }).process?.env?.[name] || undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * The process-wide flags client (created once, reused across requests). Options default to\n * `GUIDELY_SDK_KEY` (public `pk_…` or secret `gly_sk_…`), falling back to `PUBLIC_GUIDELY_PROJECT_ID`,\n * plus `GUIDELY_API_URL` / `PUBLIC_GUIDELY_API_URL` and `GUIDELY_ENV` / `PUBLIC_GUIDELY_ENV`. Polls\n * every 60 s; on serverless / edge adapters use `{ pollingMode: \"lazy\" }`.\n */\nexport function createServerFlags(options: Partial<FlagsClientOptions> = {}): FlagsClient {\n const sdkKey = options.sdkKey ?? options.projectId ?? env(\"GUIDELY_SDK_KEY\") ?? env(\"PUBLIC_GUIDELY_PROJECT_ID\");\n if (!sdkKey) throw new Error(\"[Guidely] createServerFlags: pass `sdkKey` or set GUIDELY_SDK_KEY / PUBLIC_GUIDELY_PROJECT_ID.\");\n const apiUrl = options.apiUrl ?? env(\"GUIDELY_API_URL\") ?? env(\"PUBLIC_GUIDELY_API_URL\");\n const environment = options.environment ?? env(\"GUIDELY_ENV\") ?? env(\"PUBLIC_GUIDELY_ENV\");\n const id = `${sdkKey}|${apiUrl ?? \"\"}|${environment ?? \"\"}|${options.pollingMode ?? \"auto\"}`;\n const reg = registry();\n let client = reg.clients.get(id);\n if (!client) {\n client = createFlagsClient({ storage: false, ...options, sdkKey, apiUrl, environment });\n reg.clients.set(id, client);\n }\n reg.current = client;\n return client;\n}\n\nexport interface LoadFlagsOptions {\n /** A client from `createServerFlags(...)` (default: the last one created, or one from environment variables). */\n client?: FlagsClient;\n /** How long to wait for the first download before serving fallbacks (default 1500 ms). */\n timeoutMs?: number;\n}\n\nfunction settle(client: FlagsClient, timeoutMs: number): Promise<void> {\n if (client.isReady()) return Promise.resolve();\n return Promise.race([client.ready(), new Promise<void>((r) => setTimeout(r, timeoutMs))]);\n}\n\n/** Flag getters for one user, for this request (waits briefly for the first download). */\nexport async function getFlags(attributes: Attributes = {}, options: LoadFlagsOptions = {}): Promise<FlagsEvaluator> {\n const client = options.client ?? registry().current ?? createServerFlags();\n await settle(client, options.timeoutMs ?? 1500);\n return client.forUser(attributes);\n}\n\n/**\n * For `+layout.server.ts`: the payload and attributes to hand to the browser, so the first render\n * matches the server. Pass the request `event` (attributes come from `event.locals.flags`, set in\n * `hooks.server.ts`) or the attributes. Spread the result into `initGuidely({ …, flags: data.flags })`.\n * The payload is `null` for a `gly_sk_` client (it holds server-only flags): bootstrap from a\n * public-key client instead.\n */\nexport async function loadFlags(input: { locals?: { flags?: Pick<FlagsEvaluator, \"getAttributes\"> | null } } | Attributes = {}, options: LoadFlagsOptions = {}): Promise<{ payload: FlagsBootstrap | null; attributes: Attributes }> {\n const locals = (input as { locals?: { flags?: Pick<FlagsEvaluator, \"getAttributes\"> | null } }).locals;\n const attributes = locals && typeof locals === \"object\" ? (locals.flags?.getAttributes?.() ?? {}) : (input as Attributes);\n const client = options.client ?? registry().current ?? createServerFlags();\n if (client.sdkKey.startsWith(\"gly_sk_\")) {\n const reg = registry();\n if (!reg.warned.has(client.sdkKey)) {\n reg.warned.add(client.sdkKey);\n console.warn(\"[Guidely] loadFlags() needs a public-key client (pk_…): a gly_sk_ payload includes server-only flags. Pass { client: createServerFlags({ sdkKey: PUBLIC_GUIDELY_PROJECT_ID }) }.\");\n }\n return { payload: null, attributes };\n }\n await settle(client, options.timeoutMs ?? 1500);\n return { payload: client.getPayload(), attributes };\n}\n"],"mappings":"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","names":["env"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/client-errors.ts","../src/server.ts"],"sourcesContent":["import { browser } from \"$app/environment\";\nimport { afterNavigate } from \"$app/navigation\";\nimport { createGuidely, createStaticFlags, destroyGuidely, guidely, setFlagsContext, setGuidelyContext, type GuidelyClient, type GuidelyOptions } from \"@guidelytech/svelte\";\nimport { onDestroy } from \"svelte\";\nimport { drainPendingErrors } from \"./client-errors\";\n\nexport * from \"@guidelytech/svelte\";\nexport { handleErrorWithGuidely } from \"./client-errors\";\nexport type { ClientHandleErrorInput } from \"./client-errors\";\n\n/**\n * Call once from the root `+layout.svelte` (during component initialisation). Creates the client\n * in the browser only, calls `page()` after every SvelteKit navigation and destroys the client\n * when the layout unmounts (HMR). Returns `null` on the server. Keep the signed-in user in step\n * with `syncGuidelyUser` (re-exported from `@guidelytech/svelte`).\n *\n * Pass `flags: data.flags` (from `loadFlags()` in `+layout.server.ts`) and the flag stores\n * render the same values on the server and in the browser.\n */\nexport function initGuidely(options: GuidelyOptions): GuidelyClient | null {\n if (!browser) {\n setGuidelyContext(null);\n // Server render: flag stores read a snapshot of the bootstrap payload.\n const flags = options.flags || undefined;\n setFlagsContext(flags?.payload ? createStaticFlags({ payload: flags.payload, attributes: flags.attributes }) : null);\n return null;\n }\n const client = createGuidely(options);\n setGuidelyContext(client);\n setFlagsContext(client);\n // Errors `handleErrorWithGuidely` saw before the client existed (a failing first `load`).\n drainPendingErrors(client);\n afterNavigate((nav) => {\n if (nav.type !== \"enter\") client.page(window.location.href);\n });\n onDestroy(() => {\n if (guidelyStoreIs(client)) destroyGuidely();\n });\n return client;\n}\n\nfunction guidelyStoreIs(client: GuidelyClient): boolean {\n let current: GuidelyClient | null = null;\n guidely.subscribe((c) => (current = c))();\n return current === client;\n}\n\n/**\n * Reads the public Guidely variables into client options: `VITE_GUIDELY_*` (from `import.meta.env`)\n * or SvelteKit's `PUBLIC_GUIDELY_*` (from `$env/static/public` or `$env/dynamic/public`).\n */\nexport function guidelyEnv(env: Record<string, string | undefined>): Pick<GuidelyOptions, \"projectId\" | \"environment\" | \"apiUrl\" | \"release\"> {\n return {\n projectId: env.VITE_GUIDELY_PROJECT_ID || env.PUBLIC_GUIDELY_PROJECT_ID || \"\",\n environment: ((env.VITE_GUIDELY_ENV || env.PUBLIC_GUIDELY_ENV) as GuidelyOptions[\"environment\"]) || undefined,\n apiUrl: env.VITE_GUIDELY_API_URL || env.PUBLIC_GUIDELY_API_URL || undefined,\n release: env.VITE_GUIDELY_RELEASE || env.PUBLIC_GUIDELY_RELEASE || undefined,\n };\n}\n\nexport { createServerFlags, getFlags, loadFlags } from \"./server\";\nexport type { LoadFlagsOptions } from \"./server\";\n","import { GuidelyClient, type CaptureContext } from \"@guidelytech/sdk-core\";\nimport { guidely } from \"@guidelytech/svelte\";\nimport { get } from \"svelte/store\";\n\n/** `handleError`'s input in `hooks.client.ts`. */\nexport interface ClientHandleErrorInput {\n error: unknown;\n event: { route?: { id?: string | null }; url?: URL; params?: Record<string, string> };\n status: number;\n message: string;\n}\n\ntype Hint = CaptureContext & Record<string, unknown>;\n\n/**\n * Errors from before the client exists: a failing `load` on the first page runs before the root\n * layout's `initGuidely()` creates it. Sent by the next `initGuidely()` client.\n */\nconst pending: Array<[unknown, Hint]> = [];\n\n/** Sends the errors kept while no client existed (called by `initGuidely` once it has one). */\nexport function drainPendingErrors(client: GuidelyClient): void {\n for (const [error, hint] of pending.splice(0)) {\n try {\n client.captureException(error, hint);\n } catch {\n // never throw into the app\n }\n }\n}\n\nfunction liveClient(): GuidelyClient | null {\n try {\n const c = get(guidely) ?? GuidelyClient.get();\n return c && !c.getState().destroyed ? c : null;\n } catch {\n return null;\n }\n}\n\n/**\n * `handleError` for `hooks.client.ts` (Sentry's `handleErrorWithSentry`): reports the error with\n * `contexts.sveltekit` (`routeId`, `status`, `message`), then calls your `handleError` and returns\n * what it returns. 404s are not reported unless `captureNotFound`. Without a `handleError` of your\n * own the error is logged, as SvelteKit does.\n *\n * ```ts\n * // src/hooks.client.ts\n * import { handleErrorWithGuidely } from \"@guidelytech/sveltekit\";\n * export const handleError = handleErrorWithGuidely();\n * ```\n */\nexport function handleErrorWithGuidely<R = void>(handleError?: (input: ClientHandleErrorInput) => R, options: { captureNotFound?: boolean } = {}): (input: ClientHandleErrorInput) => R | undefined {\n return (input) => {\n try {\n if (input?.status !== 404 || options.captureNotFound) {\n const routeId = input?.event?.route?.id ?? null;\n const hint: Hint = { ...(routeId ? { tags: { routeId } } : {}), contexts: { sveltekit: { routeId, status: input?.status, message: input?.message } }, mechanism: { type: \"sveltekit.handleError\", handled: false } };\n const client = liveClient();\n if (client) client.captureException(input?.error, hint);\n else if (pending.length < 10) pending.push([input?.error, hint]);\n }\n } catch {\n // never break the error page\n }\n if (handleError) return handleError(input);\n console.error(input?.error);\n return undefined;\n };\n}\n","/**\n * `@guidelytech/sveltekit/server` — feature flags in `hooks.server.ts`, `+layout.server.ts`,\n * `+page.server.ts` and endpoints. No `$app` imports, no Svelte.\n *\n * ```ts\n * // src/hooks.server.ts\n * const flags = createServerFlags();\n * export const handle = async ({ event, resolve }) => {\n * event.locals.flags = flags.forUser({ id: event.locals.user?.id });\n * return resolve(event);\n * };\n * ```\n */\nimport { createFlagsClient, type Attributes, type FlagsBootstrap, type FlagsClient, type FlagsClientOptions, type FlagsEvaluator } from \"@guidelytech/flags\";\n\nexport type { Attributes, FlagsBootstrap, FlagsClient, FlagsClientOptions, FlagsEvaluator, FlagDetails, JsonValue } from \"@guidelytech/flags\";\n\ninterface Registry {\n clients: Map<string, FlagsClient>;\n current: FlagsClient | null;\n warned: Set<string>;\n}\n\n/** Kept on `globalThis` so Vite's dev-server reloads reuse the client (and its timers). */\nfunction registry(): Registry {\n const g = globalThis as { __guidelySvelteKitFlags?: Registry };\n return (g.__guidelySvelteKitFlags ??= { clients: new Map(), current: null, warned: new Set() });\n}\n\nfunction env(name: string): string | undefined {\n try {\n return (globalThis as { process?: { env?: Record<string, string | undefined> } }).process?.env?.[name] || undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * The process-wide flags client (created once, reused across requests). Options default to\n * `GUIDELY_SDK_KEY` (public `pk_…` or secret `gly_sk_…`), falling back to `PUBLIC_GUIDELY_PROJECT_ID`,\n * plus `GUIDELY_API_URL` / `PUBLIC_GUIDELY_API_URL` and `GUIDELY_ENV` / `PUBLIC_GUIDELY_ENV`. Polls\n * every 60 s; on serverless / edge adapters use `{ pollingMode: \"lazy\" }`.\n */\nexport function createServerFlags(options: Partial<FlagsClientOptions> = {}): FlagsClient {\n const sdkKey = options.sdkKey ?? options.projectId ?? env(\"GUIDELY_SDK_KEY\") ?? env(\"PUBLIC_GUIDELY_PROJECT_ID\");\n if (!sdkKey) throw new Error(\"[Guidely] createServerFlags: pass `sdkKey` or set GUIDELY_SDK_KEY / PUBLIC_GUIDELY_PROJECT_ID.\");\n const apiUrl = options.apiUrl ?? env(\"GUIDELY_API_URL\") ?? env(\"PUBLIC_GUIDELY_API_URL\");\n const environment = options.environment ?? env(\"GUIDELY_ENV\") ?? env(\"PUBLIC_GUIDELY_ENV\");\n const id = `${sdkKey}|${apiUrl ?? \"\"}|${environment ?? \"\"}|${options.pollingMode ?? \"auto\"}`;\n const reg = registry();\n let client = reg.clients.get(id);\n if (!client) {\n client = createFlagsClient({ storage: false, ...options, sdkKey, apiUrl, environment });\n reg.clients.set(id, client);\n }\n reg.current = client;\n return client;\n}\n\nexport interface LoadFlagsOptions {\n /** A client from `createServerFlags(...)` (default: the last one created, or one from environment variables). */\n client?: FlagsClient;\n /** How long to wait for the first download before serving fallbacks (default 1500 ms). */\n timeoutMs?: number;\n}\n\nfunction settle(client: FlagsClient, timeoutMs: number): Promise<void> {\n if (client.isReady()) return Promise.resolve();\n return Promise.race([client.ready(), new Promise<void>((r) => setTimeout(r, timeoutMs))]);\n}\n\n/** Flag getters for one user, for this request (waits briefly for the first download). */\nexport async function getFlags(attributes: Attributes = {}, options: LoadFlagsOptions = {}): Promise<FlagsEvaluator> {\n const client = options.client ?? registry().current ?? createServerFlags();\n await settle(client, options.timeoutMs ?? 1500);\n return client.forUser(attributes);\n}\n\n/**\n * For `+layout.server.ts`: the payload and attributes to hand to the browser, so the first render\n * matches the server. Pass the request `event` (attributes come from `event.locals.flags`, set in\n * `hooks.server.ts`) or the attributes. Spread the result into `initGuidely({ …, flags: data.flags })`.\n * The payload is `null` for a `gly_sk_` client (it holds server-only flags): bootstrap from a\n * public-key client instead.\n */\nexport async function loadFlags(input: { locals?: { flags?: Pick<FlagsEvaluator, \"getAttributes\"> | null } } | Attributes = {}, options: LoadFlagsOptions = {}): Promise<{ payload: FlagsBootstrap | null; attributes: Attributes }> {\n const locals = (input as { locals?: { flags?: Pick<FlagsEvaluator, \"getAttributes\"> | null } }).locals;\n const attributes = locals && typeof locals === \"object\" ? (locals.flags?.getAttributes?.() ?? {}) : (input as Attributes);\n const client = options.client ?? registry().current ?? createServerFlags();\n if (client.sdkKey.startsWith(\"gly_sk_\")) {\n const reg = registry();\n if (!reg.warned.has(client.sdkKey)) {\n reg.warned.add(client.sdkKey);\n console.warn(\"[Guidely] loadFlags() needs a public-key client (pk_…): a gly_sk_ payload includes server-only flags. Pass { client: createServerFlags({ sdkKey: PUBLIC_GUIDELY_PROJECT_ID }) }.\");\n }\n return { payload: null, attributes };\n }\n await settle(client, options.timeoutMs ?? 1500);\n return { payload: client.getPayload(), attributes };\n}\n\n// Error monitoring: `handleErrorWithGuidely` for hooks.server.ts and a request-scope `guidelyHandle()`.\nexport { captureServerError, guidelyHandle, handleErrorWithGuidely, setGuidelyServerClient } from \"./server-errors\";\nexport type { GuidelyServerErrorClient, RequestEventLike, ServerErrorOptions, ServerHandleErrorInput } from \"./server-errors\";\n"],"mappings":"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","names":["guidely","guidely","env"]}
|
package/dist/server.cjs
CHANGED
|
@@ -20,13 +20,156 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/server.ts
|
|
21
21
|
var server_exports = {};
|
|
22
22
|
__export(server_exports, {
|
|
23
|
+
captureServerError: () => captureServerError,
|
|
23
24
|
createServerFlags: () => createServerFlags,
|
|
24
25
|
getFlags: () => getFlags,
|
|
25
|
-
|
|
26
|
+
guidelyHandle: () => guidelyHandle,
|
|
27
|
+
handleErrorWithGuidely: () => handleErrorWithGuidely,
|
|
28
|
+
loadFlags: () => loadFlags,
|
|
29
|
+
setGuidelyServerClient: () => setGuidelyServerClient
|
|
26
30
|
});
|
|
27
31
|
module.exports = __toCommonJS(server_exports);
|
|
28
32
|
var import_flags = require("@guidelytech/flags");
|
|
33
|
+
|
|
34
|
+
// src/server-errors.ts
|
|
29
35
|
function registry() {
|
|
36
|
+
const g = globalThis;
|
|
37
|
+
return g.__guidelySvelteKitErrors ?? (g.__guidelySvelteKitErrors = { client: null, reported: /* @__PURE__ */ new WeakSet(), warned: false });
|
|
38
|
+
}
|
|
39
|
+
function setGuidelyServerClient(client) {
|
|
40
|
+
registry().client = client;
|
|
41
|
+
}
|
|
42
|
+
var SENSITIVE_HEADER = /^(?:cookie|set-cookie|authorization|proxy-authorization)$|token|secret|password|api[-_]?key|session|csrf/i;
|
|
43
|
+
function safeHeaders(headers) {
|
|
44
|
+
const out = {};
|
|
45
|
+
const add = (name, value) => {
|
|
46
|
+
if (Object.keys(out).length >= 50 || value === void 0 || value === null || SENSITIVE_HEADER.test(name)) return;
|
|
47
|
+
out[name.toLowerCase()] = (Array.isArray(value) ? value.join(", ") : String(value)).slice(0, 1e3);
|
|
48
|
+
};
|
|
49
|
+
try {
|
|
50
|
+
const h = headers;
|
|
51
|
+
if (h && typeof h.forEach === "function" && !Array.isArray(h)) h.forEach((value, name) => add(name, value));
|
|
52
|
+
else if (h && typeof h === "object") for (const [name, value] of Object.entries(h)) add(name, value);
|
|
53
|
+
} catch {
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
function waitUntilOf(event) {
|
|
58
|
+
try {
|
|
59
|
+
const p = event?.platform;
|
|
60
|
+
const own = p?.context?.waitUntil ?? p?.waitUntil;
|
|
61
|
+
if (typeof own === "function") return (promise) => own.call(p?.context ?? p, promise);
|
|
62
|
+
const g = globalThis;
|
|
63
|
+
const vercel = g[/* @__PURE__ */ Symbol.for("@vercel/request-context")]?.get?.()?.waitUntil;
|
|
64
|
+
if (typeof vercel === "function") return vercel;
|
|
65
|
+
} catch {
|
|
66
|
+
}
|
|
67
|
+
return void 0;
|
|
68
|
+
}
|
|
69
|
+
function flushSoon(client, options, event) {
|
|
70
|
+
const timeout = options.flushTimeoutMs ?? 2e3;
|
|
71
|
+
if (typeof client.flush !== "function" || timeout <= 0) return void 0;
|
|
72
|
+
let timer;
|
|
73
|
+
const p = Promise.race([
|
|
74
|
+
Promise.resolve().then(() => client.flush()).catch(() => {
|
|
75
|
+
}),
|
|
76
|
+
new Promise((resolve) => {
|
|
77
|
+
timer = setTimeout(resolve, timeout);
|
|
78
|
+
timer.unref?.();
|
|
79
|
+
})
|
|
80
|
+
]).finally(() => clearTimeout(timer));
|
|
81
|
+
const waitUntil = waitUntilOf(event);
|
|
82
|
+
if (waitUntil) {
|
|
83
|
+
try {
|
|
84
|
+
waitUntil(p);
|
|
85
|
+
} catch {
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return p;
|
|
89
|
+
}
|
|
90
|
+
var transactionOf = (event) => {
|
|
91
|
+
const route = event?.route?.id;
|
|
92
|
+
if (!route) return void 0;
|
|
93
|
+
const method = typeof event?.request?.method === "string" ? event.request.method.toUpperCase() : "";
|
|
94
|
+
return `${method ? `${method} ` : ""}${route}`.slice(0, 200);
|
|
95
|
+
};
|
|
96
|
+
function captureServerError(error, input, options = {}) {
|
|
97
|
+
try {
|
|
98
|
+
const reg = registry();
|
|
99
|
+
const client = options.client ?? reg.client;
|
|
100
|
+
if (!client) {
|
|
101
|
+
if (!reg.warned) {
|
|
102
|
+
reg.warned = true;
|
|
103
|
+
console.warn("[Guidely] A server error was not reported: no server client. Call setGuidelyServerClient(new Guidely({ secretKey, environment })) in hooks.server.ts.");
|
|
104
|
+
}
|
|
105
|
+
return { eventId: "" };
|
|
106
|
+
}
|
|
107
|
+
if (error && typeof error === "object") {
|
|
108
|
+
if (reg.reported.has(error)) return { eventId: "" };
|
|
109
|
+
reg.reported.add(error);
|
|
110
|
+
}
|
|
111
|
+
const req = input.event?.request;
|
|
112
|
+
const ctx = {
|
|
113
|
+
level: "error",
|
|
114
|
+
...input.tags ? { tags: input.tags } : {},
|
|
115
|
+
...input.contexts ? { contexts: input.contexts } : {},
|
|
116
|
+
...req && typeof req === "object" ? { request: { method: typeof req.method === "string" ? req.method.toUpperCase() : void 0, url: typeof req.url === "string" ? req.url : void 0, headers: safeHeaders(req.headers) } } : {},
|
|
117
|
+
mechanism: { type: input.mechanism, handled: false }
|
|
118
|
+
};
|
|
119
|
+
const transaction = transactionOf(input.event);
|
|
120
|
+
const run = () => client.captureException(error, ctx);
|
|
121
|
+
const id = transaction && typeof client.withScope === "function" ? client.withScope((scope) => {
|
|
122
|
+
scope.setTransactionName?.(transaction);
|
|
123
|
+
return run();
|
|
124
|
+
}) : run();
|
|
125
|
+
return { eventId: typeof id === "string" ? id : "", flushed: flushSoon(client, options, input.event) };
|
|
126
|
+
} catch {
|
|
127
|
+
return { eventId: "" };
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function handleErrorWithGuidely(handleError, options = {}) {
|
|
131
|
+
return async (input) => {
|
|
132
|
+
try {
|
|
133
|
+
if (input?.status !== 404 || options.captureNotFound) {
|
|
134
|
+
const routeId = input?.event?.route?.id ?? null;
|
|
135
|
+
const { flushed } = captureServerError(input?.error, { event: input?.event, tags: routeId ? { routeId } : void 0, contexts: { sveltekit: { routeId, status: input?.status, message: input?.message } }, mechanism: "sveltekit.handleError" }, options);
|
|
136
|
+
if (options.waitForFlush && flushed) await flushed;
|
|
137
|
+
}
|
|
138
|
+
} catch {
|
|
139
|
+
}
|
|
140
|
+
if (handleError) return handleError(input);
|
|
141
|
+
console.error(input?.error);
|
|
142
|
+
return void 0;
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function guidelyHandle(options = {}) {
|
|
146
|
+
return ({ event, resolve }) => {
|
|
147
|
+
let client = null;
|
|
148
|
+
try {
|
|
149
|
+
client = options.client ?? registry().client;
|
|
150
|
+
} catch {
|
|
151
|
+
client = null;
|
|
152
|
+
}
|
|
153
|
+
if (!client || typeof client.withScope !== "function") return resolve(event);
|
|
154
|
+
let entered = false;
|
|
155
|
+
try {
|
|
156
|
+
return client.withScope((scope) => {
|
|
157
|
+
entered = true;
|
|
158
|
+
try {
|
|
159
|
+
scope.setTransactionName?.(transactionOf(event));
|
|
160
|
+
} catch {
|
|
161
|
+
}
|
|
162
|
+
return resolve(event);
|
|
163
|
+
});
|
|
164
|
+
} catch (error) {
|
|
165
|
+
if (!entered) return resolve(event);
|
|
166
|
+
throw error;
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// src/server.ts
|
|
172
|
+
function registry2() {
|
|
30
173
|
const g = globalThis;
|
|
31
174
|
return g.__guidelySvelteKitFlags ?? (g.__guidelySvelteKitFlags = { clients: /* @__PURE__ */ new Map(), current: null, warned: /* @__PURE__ */ new Set() });
|
|
32
175
|
}
|
|
@@ -43,7 +186,7 @@ function createServerFlags(options = {}) {
|
|
|
43
186
|
const apiUrl = options.apiUrl ?? env("GUIDELY_API_URL") ?? env("PUBLIC_GUIDELY_API_URL");
|
|
44
187
|
const environment = options.environment ?? env("GUIDELY_ENV") ?? env("PUBLIC_GUIDELY_ENV");
|
|
45
188
|
const id = `${sdkKey}|${apiUrl ?? ""}|${environment ?? ""}|${options.pollingMode ?? "auto"}`;
|
|
46
|
-
const reg =
|
|
189
|
+
const reg = registry2();
|
|
47
190
|
let client = reg.clients.get(id);
|
|
48
191
|
if (!client) {
|
|
49
192
|
client = (0, import_flags.createFlagsClient)({ storage: false, ...options, sdkKey, apiUrl, environment });
|
|
@@ -57,16 +200,16 @@ function settle(client, timeoutMs) {
|
|
|
57
200
|
return Promise.race([client.ready(), new Promise((r) => setTimeout(r, timeoutMs))]);
|
|
58
201
|
}
|
|
59
202
|
async function getFlags(attributes = {}, options = {}) {
|
|
60
|
-
const client = options.client ??
|
|
203
|
+
const client = options.client ?? registry2().current ?? createServerFlags();
|
|
61
204
|
await settle(client, options.timeoutMs ?? 1500);
|
|
62
205
|
return client.forUser(attributes);
|
|
63
206
|
}
|
|
64
207
|
async function loadFlags(input = {}, options = {}) {
|
|
65
208
|
const locals = input.locals;
|
|
66
209
|
const attributes = locals && typeof locals === "object" ? locals.flags?.getAttributes?.() ?? {} : input;
|
|
67
|
-
const client = options.client ??
|
|
210
|
+
const client = options.client ?? registry2().current ?? createServerFlags();
|
|
68
211
|
if (client.sdkKey.startsWith("gly_sk_")) {
|
|
69
|
-
const reg =
|
|
212
|
+
const reg = registry2();
|
|
70
213
|
if (!reg.warned.has(client.sdkKey)) {
|
|
71
214
|
reg.warned.add(client.sdkKey);
|
|
72
215
|
console.warn("[Guidely] loadFlags() needs a public-key client (pk_\u2026): a gly_sk_ payload includes server-only flags. Pass { client: createServerFlags({ sdkKey: PUBLIC_GUIDELY_PROJECT_ID }) }.");
|
|
@@ -78,8 +221,12 @@ async function loadFlags(input = {}, options = {}) {
|
|
|
78
221
|
}
|
|
79
222
|
// Annotate the CommonJS export names for ESM import in node:
|
|
80
223
|
0 && (module.exports = {
|
|
224
|
+
captureServerError,
|
|
81
225
|
createServerFlags,
|
|
82
226
|
getFlags,
|
|
83
|
-
|
|
227
|
+
guidelyHandle,
|
|
228
|
+
handleErrorWithGuidely,
|
|
229
|
+
loadFlags,
|
|
230
|
+
setGuidelyServerClient
|
|
84
231
|
});
|
|
85
232
|
//# sourceMappingURL=server.cjs.map
|
package/dist/server.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server.ts"],"sourcesContent":["/**\n * `@guidelytech/sveltekit/server` — feature flags in `hooks.server.ts`, `+layout.server.ts`,\n * `+page.server.ts` and endpoints. No `$app` imports, no Svelte.\n *\n * ```ts\n * // src/hooks.server.ts\n * const flags = createServerFlags();\n * export const handle = async ({ event, resolve }) => {\n * event.locals.flags = flags.forUser({ id: event.locals.user?.id });\n * return resolve(event);\n * };\n * ```\n */\nimport { createFlagsClient, type Attributes, type FlagsBootstrap, type FlagsClient, type FlagsClientOptions, type FlagsEvaluator } from \"@guidelytech/flags\";\n\nexport type { Attributes, FlagsBootstrap, FlagsClient, FlagsClientOptions, FlagsEvaluator, FlagDetails, JsonValue } from \"@guidelytech/flags\";\n\ninterface Registry {\n clients: Map<string, FlagsClient>;\n current: FlagsClient | null;\n warned: Set<string>;\n}\n\n/** Kept on `globalThis` so Vite's dev-server reloads reuse the client (and its timers). */\nfunction registry(): Registry {\n const g = globalThis as { __guidelySvelteKitFlags?: Registry };\n return (g.__guidelySvelteKitFlags ??= { clients: new Map(), current: null, warned: new Set() });\n}\n\nfunction env(name: string): string | undefined {\n try {\n return (globalThis as { process?: { env?: Record<string, string | undefined> } }).process?.env?.[name] || undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * The process-wide flags client (created once, reused across requests). Options default to\n * `GUIDELY_SDK_KEY` (public `pk_…` or secret `gly_sk_…`), falling back to `PUBLIC_GUIDELY_PROJECT_ID`,\n * plus `GUIDELY_API_URL` / `PUBLIC_GUIDELY_API_URL` and `GUIDELY_ENV` / `PUBLIC_GUIDELY_ENV`. Polls\n * every 60 s; on serverless / edge adapters use `{ pollingMode: \"lazy\" }`.\n */\nexport function createServerFlags(options: Partial<FlagsClientOptions> = {}): FlagsClient {\n const sdkKey = options.sdkKey ?? options.projectId ?? env(\"GUIDELY_SDK_KEY\") ?? env(\"PUBLIC_GUIDELY_PROJECT_ID\");\n if (!sdkKey) throw new Error(\"[Guidely] createServerFlags: pass `sdkKey` or set GUIDELY_SDK_KEY / PUBLIC_GUIDELY_PROJECT_ID.\");\n const apiUrl = options.apiUrl ?? env(\"GUIDELY_API_URL\") ?? env(\"PUBLIC_GUIDELY_API_URL\");\n const environment = options.environment ?? env(\"GUIDELY_ENV\") ?? env(\"PUBLIC_GUIDELY_ENV\");\n const id = `${sdkKey}|${apiUrl ?? \"\"}|${environment ?? \"\"}|${options.pollingMode ?? \"auto\"}`;\n const reg = registry();\n let client = reg.clients.get(id);\n if (!client) {\n client = createFlagsClient({ storage: false, ...options, sdkKey, apiUrl, environment });\n reg.clients.set(id, client);\n }\n reg.current = client;\n return client;\n}\n\nexport interface LoadFlagsOptions {\n /** A client from `createServerFlags(...)` (default: the last one created, or one from environment variables). */\n client?: FlagsClient;\n /** How long to wait for the first download before serving fallbacks (default 1500 ms). */\n timeoutMs?: number;\n}\n\nfunction settle(client: FlagsClient, timeoutMs: number): Promise<void> {\n if (client.isReady()) return Promise.resolve();\n return Promise.race([client.ready(), new Promise<void>((r) => setTimeout(r, timeoutMs))]);\n}\n\n/** Flag getters for one user, for this request (waits briefly for the first download). */\nexport async function getFlags(attributes: Attributes = {}, options: LoadFlagsOptions = {}): Promise<FlagsEvaluator> {\n const client = options.client ?? registry().current ?? createServerFlags();\n await settle(client, options.timeoutMs ?? 1500);\n return client.forUser(attributes);\n}\n\n/**\n * For `+layout.server.ts`: the payload and attributes to hand to the browser, so the first render\n * matches the server. Pass the request `event` (attributes come from `event.locals.flags`, set in\n * `hooks.server.ts`) or the attributes. Spread the result into `initGuidely({ …, flags: data.flags })`.\n * The payload is `null` for a `gly_sk_` client (it holds server-only flags): bootstrap from a\n * public-key client instead.\n */\nexport async function loadFlags(input: { locals?: { flags?: Pick<FlagsEvaluator, \"getAttributes\"> | null } } | Attributes = {}, options: LoadFlagsOptions = {}): Promise<{ payload: FlagsBootstrap | null; attributes: Attributes }> {\n const locals = (input as { locals?: { flags?: Pick<FlagsEvaluator, \"getAttributes\"> | null } }).locals;\n const attributes = locals && typeof locals === \"object\" ? (locals.flags?.getAttributes?.() ?? {}) : (input as Attributes);\n const client = options.client ?? registry().current ?? createServerFlags();\n if (client.sdkKey.startsWith(\"gly_sk_\")) {\n const reg = registry();\n if (!reg.warned.has(client.sdkKey)) {\n reg.warned.add(client.sdkKey);\n console.warn(\"[Guidely] loadFlags() needs a public-key client (pk_…): a gly_sk_ payload includes server-only flags. Pass { client: createServerFlags({ sdkKey: PUBLIC_GUIDELY_PROJECT_ID }) }.\");\n }\n return { payload: null, attributes };\n }\n await settle(client, options.timeoutMs ?? 1500);\n return { payload: client.getPayload(), attributes };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAaA,mBAAwI;AAWxI,SAAS,WAAqB;AAC5B,QAAM,IAAI;AACV,SAAQ,EAAE,4BAAF,EAAE,0BAA4B,EAAE,SAAS,oBAAI,IAAI,GAAG,SAAS,MAAM,QAAQ,oBAAI,IAAI,EAAE;AAC/F;AAEA,SAAS,IAAI,MAAkC;AAC7C,MAAI;AACF,WAAQ,WAA0E,SAAS,MAAM,IAAI,KAAK;AAAA,EAC5G,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAQO,SAAS,kBAAkB,UAAuC,CAAC,GAAgB;AACxF,QAAM,SAAS,QAAQ,UAAU,QAAQ,aAAa,IAAI,iBAAiB,KAAK,IAAI,2BAA2B;AAC/G,MAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,gGAAgG;AAC7H,QAAM,SAAS,QAAQ,UAAU,IAAI,iBAAiB,KAAK,IAAI,wBAAwB;AACvF,QAAM,cAAc,QAAQ,eAAe,IAAI,aAAa,KAAK,IAAI,oBAAoB;AACzF,QAAM,KAAK,GAAG,MAAM,IAAI,UAAU,EAAE,IAAI,eAAe,EAAE,IAAI,QAAQ,eAAe,MAAM;AAC1F,QAAM,MAAM,SAAS;AACrB,MAAI,SAAS,IAAI,QAAQ,IAAI,EAAE;AAC/B,MAAI,CAAC,QAAQ;AACX,iBAAS,gCAAkB,EAAE,SAAS,OAAO,GAAG,SAAS,QAAQ,QAAQ,YAAY,CAAC;AACtF,QAAI,QAAQ,IAAI,IAAI,MAAM;AAAA,EAC5B;AACA,MAAI,UAAU;AACd,SAAO;AACT;AASA,SAAS,OAAO,QAAqB,WAAkC;AACrE,MAAI,OAAO,QAAQ,EAAG,QAAO,QAAQ,QAAQ;AAC7C,SAAO,QAAQ,KAAK,CAAC,OAAO,MAAM,GAAG,IAAI,QAAc,CAAC,MAAM,WAAW,GAAG,SAAS,CAAC,CAAC,CAAC;AAC1F;AAGA,eAAsB,SAAS,aAAyB,CAAC,GAAG,UAA4B,CAAC,GAA4B;AACnH,QAAM,SAAS,QAAQ,UAAU,SAAS,EAAE,WAAW,kBAAkB;AACzE,QAAM,OAAO,QAAQ,QAAQ,aAAa,IAAI;AAC9C,SAAO,OAAO,QAAQ,UAAU;AAClC;AASA,eAAsB,UAAU,QAA4F,CAAC,GAAG,UAA4B,CAAC,GAAwE;AACnO,QAAM,SAAU,MAAgF;AAChG,QAAM,aAAa,UAAU,OAAO,WAAW,WAAY,OAAO,OAAO,gBAAgB,KAAK,CAAC,IAAM;AACrG,QAAM,SAAS,QAAQ,UAAU,SAAS,EAAE,WAAW,kBAAkB;AACzE,MAAI,OAAO,OAAO,WAAW,SAAS,GAAG;AACvC,UAAM,MAAM,SAAS;AACrB,QAAI,CAAC,IAAI,OAAO,IAAI,OAAO,MAAM,GAAG;AAClC,UAAI,OAAO,IAAI,OAAO,MAAM;AAC5B,cAAQ,KAAK,uLAAkL;AAAA,IACjM;AACA,WAAO,EAAE,SAAS,MAAM,WAAW;AAAA,EACrC;AACA,QAAM,OAAO,QAAQ,QAAQ,aAAa,IAAI;AAC9C,SAAO,EAAE,SAAS,OAAO,WAAW,GAAG,WAAW;AACpD;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/server.ts","../src/server-errors.ts"],"sourcesContent":["/**\n * `@guidelytech/sveltekit/server` — feature flags in `hooks.server.ts`, `+layout.server.ts`,\n * `+page.server.ts` and endpoints. No `$app` imports, no Svelte.\n *\n * ```ts\n * // src/hooks.server.ts\n * const flags = createServerFlags();\n * export const handle = async ({ event, resolve }) => {\n * event.locals.flags = flags.forUser({ id: event.locals.user?.id });\n * return resolve(event);\n * };\n * ```\n */\nimport { createFlagsClient, type Attributes, type FlagsBootstrap, type FlagsClient, type FlagsClientOptions, type FlagsEvaluator } from \"@guidelytech/flags\";\n\nexport type { Attributes, FlagsBootstrap, FlagsClient, FlagsClientOptions, FlagsEvaluator, FlagDetails, JsonValue } from \"@guidelytech/flags\";\n\ninterface Registry {\n clients: Map<string, FlagsClient>;\n current: FlagsClient | null;\n warned: Set<string>;\n}\n\n/** Kept on `globalThis` so Vite's dev-server reloads reuse the client (and its timers). */\nfunction registry(): Registry {\n const g = globalThis as { __guidelySvelteKitFlags?: Registry };\n return (g.__guidelySvelteKitFlags ??= { clients: new Map(), current: null, warned: new Set() });\n}\n\nfunction env(name: string): string | undefined {\n try {\n return (globalThis as { process?: { env?: Record<string, string | undefined> } }).process?.env?.[name] || undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * The process-wide flags client (created once, reused across requests). Options default to\n * `GUIDELY_SDK_KEY` (public `pk_…` or secret `gly_sk_…`), falling back to `PUBLIC_GUIDELY_PROJECT_ID`,\n * plus `GUIDELY_API_URL` / `PUBLIC_GUIDELY_API_URL` and `GUIDELY_ENV` / `PUBLIC_GUIDELY_ENV`. Polls\n * every 60 s; on serverless / edge adapters use `{ pollingMode: \"lazy\" }`.\n */\nexport function createServerFlags(options: Partial<FlagsClientOptions> = {}): FlagsClient {\n const sdkKey = options.sdkKey ?? options.projectId ?? env(\"GUIDELY_SDK_KEY\") ?? env(\"PUBLIC_GUIDELY_PROJECT_ID\");\n if (!sdkKey) throw new Error(\"[Guidely] createServerFlags: pass `sdkKey` or set GUIDELY_SDK_KEY / PUBLIC_GUIDELY_PROJECT_ID.\");\n const apiUrl = options.apiUrl ?? env(\"GUIDELY_API_URL\") ?? env(\"PUBLIC_GUIDELY_API_URL\");\n const environment = options.environment ?? env(\"GUIDELY_ENV\") ?? env(\"PUBLIC_GUIDELY_ENV\");\n const id = `${sdkKey}|${apiUrl ?? \"\"}|${environment ?? \"\"}|${options.pollingMode ?? \"auto\"}`;\n const reg = registry();\n let client = reg.clients.get(id);\n if (!client) {\n client = createFlagsClient({ storage: false, ...options, sdkKey, apiUrl, environment });\n reg.clients.set(id, client);\n }\n reg.current = client;\n return client;\n}\n\nexport interface LoadFlagsOptions {\n /** A client from `createServerFlags(...)` (default: the last one created, or one from environment variables). */\n client?: FlagsClient;\n /** How long to wait for the first download before serving fallbacks (default 1500 ms). */\n timeoutMs?: number;\n}\n\nfunction settle(client: FlagsClient, timeoutMs: number): Promise<void> {\n if (client.isReady()) return Promise.resolve();\n return Promise.race([client.ready(), new Promise<void>((r) => setTimeout(r, timeoutMs))]);\n}\n\n/** Flag getters for one user, for this request (waits briefly for the first download). */\nexport async function getFlags(attributes: Attributes = {}, options: LoadFlagsOptions = {}): Promise<FlagsEvaluator> {\n const client = options.client ?? registry().current ?? createServerFlags();\n await settle(client, options.timeoutMs ?? 1500);\n return client.forUser(attributes);\n}\n\n/**\n * For `+layout.server.ts`: the payload and attributes to hand to the browser, so the first render\n * matches the server. Pass the request `event` (attributes come from `event.locals.flags`, set in\n * `hooks.server.ts`) or the attributes. Spread the result into `initGuidely({ …, flags: data.flags })`.\n * The payload is `null` for a `gly_sk_` client (it holds server-only flags): bootstrap from a\n * public-key client instead.\n */\nexport async function loadFlags(input: { locals?: { flags?: Pick<FlagsEvaluator, \"getAttributes\"> | null } } | Attributes = {}, options: LoadFlagsOptions = {}): Promise<{ payload: FlagsBootstrap | null; attributes: Attributes }> {\n const locals = (input as { locals?: { flags?: Pick<FlagsEvaluator, \"getAttributes\"> | null } }).locals;\n const attributes = locals && typeof locals === \"object\" ? (locals.flags?.getAttributes?.() ?? {}) : (input as Attributes);\n const client = options.client ?? registry().current ?? createServerFlags();\n if (client.sdkKey.startsWith(\"gly_sk_\")) {\n const reg = registry();\n if (!reg.warned.has(client.sdkKey)) {\n reg.warned.add(client.sdkKey);\n console.warn(\"[Guidely] loadFlags() needs a public-key client (pk_…): a gly_sk_ payload includes server-only flags. Pass { client: createServerFlags({ sdkKey: PUBLIC_GUIDELY_PROJECT_ID }) }.\");\n }\n return { payload: null, attributes };\n }\n await settle(client, options.timeoutMs ?? 1500);\n return { payload: client.getPayload(), attributes };\n}\n\n// Error monitoring: `handleErrorWithGuidely` for hooks.server.ts and a request-scope `guidelyHandle()`.\nexport { captureServerError, guidelyHandle, handleErrorWithGuidely, setGuidelyServerClient } from \"./server-errors\";\nexport type { GuidelyServerErrorClient, RequestEventLike, ServerErrorOptions, ServerHandleErrorInput } from \"./server-errors\";\n","/**\n * Server-side error monitoring for SvelteKit: `handleErrorWithGuidely` for `hooks.server.ts` and a\n * `guidelyHandle()` for `sequence(...)`. Errors go to a server Guidely client you register once\n * (`@guidelytech/node`'s `Guidely`, or anything with its `captureException`), typed structurally so\n * this entry has no Node built-ins and runs on every adapter (Node, Vercel, Cloudflare, Netlify).\n * Nothing here throws into the request, and nothing waits for the network on the request's path.\n */\n\n/** What capture uses from a server client; `new Guidely({ secretKey, environment })` from `@guidelytech/node` fits. */\nexport interface GuidelyServerErrorClient {\n captureException(error: unknown, context?: Record<string, unknown>): string | void;\n /** Sends what is queued; used (bounded by `flushTimeoutMs`) to get the error out before a serverless function freezes. */\n flush?(): Promise<void>;\n /** Request scopes (breadcrumbs, tags per request) and the event's transaction name (`GET /blog/[slug]`). */\n withScope?<T>(fn: (scope: { setTransactionName?(name: string | undefined): unknown }) => T): T;\n}\n\nexport interface ServerErrorOptions {\n /** The client to report to (default: the one given to `setGuidelyServerClient`). */\n client?: GuidelyServerErrorClient | null;\n /** Longest the background flush may run (default 2000 ms; 0 skips it). */\n flushTimeoutMs?: number;\n /** Wait for the flush before answering, for serverless hosts with no `waitUntil`. Default false. */\n waitForFlush?: boolean;\n /** Report 404s too (default false: a missing page is not an error, as in Sentry). */\n captureNotFound?: boolean;\n}\n\ninterface ErrorRegistry {\n client: GuidelyServerErrorClient | null;\n reported: WeakSet<object>;\n warned: boolean;\n}\n\n/** Kept on `globalThis` so Vite's dev-server reloads keep the registered client. */\nfunction registry(): ErrorRegistry {\n const g = globalThis as { __guidelySvelteKitErrors?: ErrorRegistry };\n return (g.__guidelySvelteKitErrors ??= { client: null, reported: new WeakSet(), warned: false });\n}\n\n/** Registers the server client every capture here reports to (call it once in `hooks.server.ts`). */\nexport function setGuidelyServerClient(client: GuidelyServerErrorClient | null): void {\n registry().client = client;\n}\n\n/** Credentials never leave the server: cookies, auth headers and anything named like a secret. */\nconst SENSITIVE_HEADER = /^(?:cookie|set-cookie|authorization|proxy-authorization)$|token|secret|password|api[-_]?key|session|csrf/i;\n\n/** Request headers without credentials, as a plain record (at most 50, values ≤ 1000 characters). */\nexport function safeHeaders(headers: unknown): Record<string, string> {\n const out: Record<string, string> = {};\n const add = (name: string, value: unknown) => {\n if (Object.keys(out).length >= 50 || value === undefined || value === null || SENSITIVE_HEADER.test(name)) return;\n out[name.toLowerCase()] = (Array.isArray(value) ? value.join(\", \") : String(value)).slice(0, 1000);\n };\n try {\n const h = headers as { forEach?: (cb: (value: string, name: string) => void) => void } | null;\n if (h && typeof h.forEach === \"function\" && !Array.isArray(h)) h.forEach((value, name) => add(name, value));\n else if (h && typeof h === \"object\") for (const [name, value] of Object.entries(h)) add(name, value);\n } catch {\n // unreadable headers: report without them\n }\n return out;\n}\n\ntype WaitUntil = (promise: Promise<unknown>) => void;\n\n/** The platform's `waitUntil` for this request: Cloudflare's `platform.context`, else Vercel's request context. */\nfunction waitUntilOf(event: RequestEventLike | undefined): WaitUntil | undefined {\n try {\n const p = event?.platform as { context?: { waitUntil?: unknown }; waitUntil?: unknown } | undefined;\n const own = p?.context?.waitUntil ?? p?.waitUntil;\n if (typeof own === \"function\") return (promise) => (own as WaitUntil).call(p?.context ?? p, promise);\n const g = globalThis as Record<symbol, { get?: () => { waitUntil?: WaitUntil } | undefined } | undefined>;\n const vercel = g[Symbol.for(\"@vercel/request-context\")]?.get?.()?.waitUntil;\n if (typeof vercel === \"function\") return vercel;\n } catch {\n // no request context\n }\n return undefined;\n}\n\n/** Flushes in the background, bounded by `flushTimeoutMs`; the promise never rejects. */\nfunction flushSoon(client: GuidelyServerErrorClient, options: ServerErrorOptions, event: RequestEventLike | undefined): Promise<void> | undefined {\n const timeout = options.flushTimeoutMs ?? 2000;\n if (typeof client.flush !== \"function\" || timeout <= 0) return undefined;\n let timer: ReturnType<typeof setTimeout> | undefined;\n const p = Promise.race([\n Promise.resolve()\n .then(() => client.flush!())\n .catch(() => {}),\n new Promise<void>((resolve) => {\n timer = setTimeout(resolve, timeout);\n (timer as { unref?: () => void }).unref?.();\n }),\n ]).finally(() => clearTimeout(timer));\n const waitUntil = waitUntilOf(event);\n if (waitUntil) {\n try {\n waitUntil(p);\n } catch {\n // the request already ended\n }\n }\n return p;\n}\n\n/** The parts of SvelteKit's `RequestEvent` capture reads. */\nexport interface RequestEventLike {\n request?: { method?: string; url?: string; headers?: unknown };\n route?: { id?: string | null };\n url?: URL | { href?: string; pathname?: string };\n platform?: unknown;\n}\n\n/** `handleError`'s input in `hooks.server.ts`. */\nexport interface ServerHandleErrorInput {\n error: unknown;\n event: RequestEventLike;\n status: number;\n message: string;\n}\n\nconst transactionOf = (event: RequestEventLike | undefined): string | undefined => {\n const route = event?.route?.id;\n if (!route) return undefined;\n const method = typeof event?.request?.method === \"string\" ? event.request.method.toUpperCase() : \"\";\n return `${method ? `${method} ` : \"\"}${route}`.slice(0, 200);\n};\n\n/**\n * Reports one server error (once per error object) and starts a bounded background flush.\n * Returns the event id (\"\" when nothing was reported) and the flush.\n */\nexport function captureServerError(error: unknown, input: { event?: RequestEventLike; contexts?: Record<string, Record<string, unknown>>; tags?: Record<string, string>; mechanism: string }, options: ServerErrorOptions = {}): { eventId: string; flushed?: Promise<void> } {\n try {\n const reg = registry();\n const client = options.client ?? reg.client;\n if (!client) {\n if (!reg.warned) {\n reg.warned = true;\n console.warn(\"[Guidely] A server error was not reported: no server client. Call setGuidelyServerClient(new Guidely({ secretKey, environment })) in hooks.server.ts.\");\n }\n return { eventId: \"\" };\n }\n if (error && typeof error === \"object\") {\n if (reg.reported.has(error)) return { eventId: \"\" };\n reg.reported.add(error);\n }\n const req = input.event?.request;\n const ctx: Record<string, unknown> = {\n level: \"error\",\n ...(input.tags ? { tags: input.tags } : {}),\n ...(input.contexts ? { contexts: input.contexts } : {}),\n ...(req && typeof req === \"object\" ? { request: { method: typeof req.method === \"string\" ? req.method.toUpperCase() : undefined, url: typeof req.url === \"string\" ? req.url : undefined, headers: safeHeaders(req.headers) } } : {}),\n mechanism: { type: input.mechanism, handled: false },\n };\n const transaction = transactionOf(input.event);\n const run = () => client.captureException(error, ctx);\n const id = transaction && typeof client.withScope === \"function\"\n ? client.withScope((scope) => {\n scope.setTransactionName?.(transaction);\n return run();\n })\n : run();\n return { eventId: typeof id === \"string\" ? id : \"\", flushed: flushSoon(client, options, input.event) };\n } catch {\n return { eventId: \"\" };\n }\n}\n\n/**\n * `handleError` for `hooks.server.ts` (Sentry's `handleErrorWithSentry`): reports the error with\n * the request (credentials removed) and `contexts.sveltekit` (`routeId`, `status`, `message`), then\n * calls your `handleError` and returns what it returns. 404s are not reported unless\n * `captureNotFound`. Without a `handleError` of your own the error is logged, as SvelteKit does.\n *\n * ```ts\n * // src/hooks.server.ts\n * import { Guidely } from \"@guidelytech/node\";\n * import { handleErrorWithGuidely, setGuidelyServerClient } from \"@guidelytech/sveltekit/server\";\n * setGuidelyServerClient(new Guidely({ secretKey: GUIDELY_SECRET_KEY, environment: \"production\" }));\n * export const handleError = handleErrorWithGuidely(({ error, event, status, message }) => ({ message: \"Something went wrong\" }));\n * ```\n */\nexport function handleErrorWithGuidely<R = void>(handleError?: (input: ServerHandleErrorInput) => R | Promise<R>, options: ServerErrorOptions = {}): (input: ServerHandleErrorInput) => Promise<R | undefined> {\n return async (input) => {\n try {\n if (input?.status !== 404 || options.captureNotFound) {\n const routeId = input?.event?.route?.id ?? null;\n const { flushed } = captureServerError(input?.error, { event: input?.event, tags: routeId ? { routeId } : undefined, contexts: { sveltekit: { routeId, status: input?.status, message: input?.message } }, mechanism: \"sveltekit.handleError\" }, options);\n if (options.waitForFlush && flushed) await flushed;\n }\n } catch {\n // never break the error page\n }\n if (handleError) return handleError(input);\n console.error(input?.error);\n return undefined;\n };\n}\n\n/**\n * A server `handle` for `sequence(guidelyHandle(), …)`: runs each request in its own scope of the\n * server client when it has scopes (`@guidelytech/node` does: breadcrumbs, `setTag` and `setUser`\n * calls made while handling the request stay with its errors), named `GET /blog/[slug]`.\n */\nexport function guidelyHandle(options: Pick<ServerErrorOptions, \"client\"> = {}): <E extends RequestEventLike, T>(input: { event: E; resolve: (event: E) => T }) => T {\n return ({ event, resolve }) => {\n let client: GuidelyServerErrorClient | null = null;\n try {\n client = options.client ?? registry().client;\n } catch {\n client = null;\n }\n if (!client || typeof client.withScope !== \"function\") return resolve(event);\n let entered = false;\n try {\n return client.withScope((scope) => {\n entered = true;\n try {\n scope.setTransactionName?.(transactionOf(event));\n } catch {\n // a scope without names\n }\n return resolve(event);\n });\n } catch (error) {\n // A failure inside the scope machinery must not swallow the request: resolve it plainly.\n if (!entered) return resolve(event);\n throw error;\n }\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAaA,mBAAwI;;;ACsBxI,SAAS,WAA0B;AACjC,QAAM,IAAI;AACV,SAAQ,EAAE,6BAAF,EAAE,2BAA6B,EAAE,QAAQ,MAAM,UAAU,oBAAI,QAAQ,GAAG,QAAQ,MAAM;AAChG;AAGO,SAAS,uBAAuB,QAA+C;AACpF,WAAS,EAAE,SAAS;AACtB;AAGA,IAAM,mBAAmB;AAGlB,SAAS,YAAY,SAA0C;AACpE,QAAM,MAA8B,CAAC;AACrC,QAAM,MAAM,CAAC,MAAc,UAAmB;AAC5C,QAAI,OAAO,KAAK,GAAG,EAAE,UAAU,MAAM,UAAU,UAAa,UAAU,QAAQ,iBAAiB,KAAK,IAAI,EAAG;AAC3G,QAAI,KAAK,YAAY,CAAC,KAAK,MAAM,QAAQ,KAAK,IAAI,MAAM,KAAK,IAAI,IAAI,OAAO,KAAK,GAAG,MAAM,GAAG,GAAI;AAAA,EACnG;AACA,MAAI;AACF,UAAM,IAAI;AACV,QAAI,KAAK,OAAO,EAAE,YAAY,cAAc,CAAC,MAAM,QAAQ,CAAC,EAAG,GAAE,QAAQ,CAAC,OAAO,SAAS,IAAI,MAAM,KAAK,CAAC;AAAA,aACjG,KAAK,OAAO,MAAM,SAAU,YAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,CAAC,EAAG,KAAI,MAAM,KAAK;AAAA,EACrG,QAAQ;AAAA,EAER;AACA,SAAO;AACT;AAKA,SAAS,YAAY,OAA4D;AAC/E,MAAI;AACF,UAAM,IAAI,OAAO;AACjB,UAAM,MAAM,GAAG,SAAS,aAAa,GAAG;AACxC,QAAI,OAAO,QAAQ,WAAY,QAAO,CAAC,YAAa,IAAkB,KAAK,GAAG,WAAW,GAAG,OAAO;AACnG,UAAM,IAAI;AACV,UAAM,SAAS,EAAE,uBAAO,IAAI,yBAAyB,CAAC,GAAG,MAAM,GAAG;AAClE,QAAI,OAAO,WAAW,WAAY,QAAO;AAAA,EAC3C,QAAQ;AAAA,EAER;AACA,SAAO;AACT;AAGA,SAAS,UAAU,QAAkC,SAA6B,OAAgE;AAChJ,QAAM,UAAU,QAAQ,kBAAkB;AAC1C,MAAI,OAAO,OAAO,UAAU,cAAc,WAAW,EAAG,QAAO;AAC/D,MAAI;AACJ,QAAM,IAAI,QAAQ,KAAK;AAAA,IACrB,QAAQ,QAAQ,EACb,KAAK,MAAM,OAAO,MAAO,CAAC,EAC1B,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,IACjB,IAAI,QAAc,CAAC,YAAY;AAC7B,cAAQ,WAAW,SAAS,OAAO;AACnC,MAAC,MAAiC,QAAQ;AAAA,IAC5C,CAAC;AAAA,EACH,CAAC,EAAE,QAAQ,MAAM,aAAa,KAAK,CAAC;AACpC,QAAM,YAAY,YAAY,KAAK;AACnC,MAAI,WAAW;AACb,QAAI;AACF,gBAAU,CAAC;AAAA,IACb,QAAQ;AAAA,IAER;AAAA,EACF;AACA,SAAO;AACT;AAkBA,IAAM,gBAAgB,CAAC,UAA4D;AACjF,QAAM,QAAQ,OAAO,OAAO;AAC5B,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,SAAS,OAAO,OAAO,SAAS,WAAW,WAAW,MAAM,QAAQ,OAAO,YAAY,IAAI;AACjG,SAAO,GAAG,SAAS,GAAG,MAAM,MAAM,EAAE,GAAG,KAAK,GAAG,MAAM,GAAG,GAAG;AAC7D;AAMO,SAAS,mBAAmB,OAAgB,OAA2I,UAA8B,CAAC,GAAiD;AAC5Q,MAAI;AACF,UAAM,MAAM,SAAS;AACrB,UAAM,SAAS,QAAQ,UAAU,IAAI;AACrC,QAAI,CAAC,QAAQ;AACX,UAAI,CAAC,IAAI,QAAQ;AACf,YAAI,SAAS;AACb,gBAAQ,KAAK,uJAAuJ;AAAA,MACtK;AACA,aAAO,EAAE,SAAS,GAAG;AAAA,IACvB;AACA,QAAI,SAAS,OAAO,UAAU,UAAU;AACtC,UAAI,IAAI,SAAS,IAAI,KAAK,EAAG,QAAO,EAAE,SAAS,GAAG;AAClD,UAAI,SAAS,IAAI,KAAK;AAAA,IACxB;AACA,UAAM,MAAM,MAAM,OAAO;AACzB,UAAM,MAA+B;AAAA,MACnC,OAAO;AAAA,MACP,GAAI,MAAM,OAAO,EAAE,MAAM,MAAM,KAAK,IAAI,CAAC;AAAA,MACzC,GAAI,MAAM,WAAW,EAAE,UAAU,MAAM,SAAS,IAAI,CAAC;AAAA,MACrD,GAAI,OAAO,OAAO,QAAQ,WAAW,EAAE,SAAS,EAAE,QAAQ,OAAO,IAAI,WAAW,WAAW,IAAI,OAAO,YAAY,IAAI,QAAW,KAAK,OAAO,IAAI,QAAQ,WAAW,IAAI,MAAM,QAAW,SAAS,YAAY,IAAI,OAAO,EAAE,EAAE,IAAI,CAAC;AAAA,MAClO,WAAW,EAAE,MAAM,MAAM,WAAW,SAAS,MAAM;AAAA,IACrD;AACA,UAAM,cAAc,cAAc,MAAM,KAAK;AAC7C,UAAM,MAAM,MAAM,OAAO,iBAAiB,OAAO,GAAG;AACpD,UAAM,KAAK,eAAe,OAAO,OAAO,cAAc,aAClD,OAAO,UAAU,CAAC,UAAU;AAC1B,YAAM,qBAAqB,WAAW;AACtC,aAAO,IAAI;AAAA,IACb,CAAC,IACD,IAAI;AACR,WAAO,EAAE,SAAS,OAAO,OAAO,WAAW,KAAK,IAAI,SAAS,UAAU,QAAQ,SAAS,MAAM,KAAK,EAAE;AAAA,EACvG,QAAQ;AACN,WAAO,EAAE,SAAS,GAAG;AAAA,EACvB;AACF;AAgBO,SAAS,uBAAiC,aAAiE,UAA8B,CAAC,GAA8D;AAC7M,SAAO,OAAO,UAAU;AACtB,QAAI;AACF,UAAI,OAAO,WAAW,OAAO,QAAQ,iBAAiB;AACpD,cAAM,UAAU,OAAO,OAAO,OAAO,MAAM;AAC3C,cAAM,EAAE,QAAQ,IAAI,mBAAmB,OAAO,OAAO,EAAE,OAAO,OAAO,OAAO,MAAM,UAAU,EAAE,QAAQ,IAAI,QAAW,UAAU,EAAE,WAAW,EAAE,SAAS,QAAQ,OAAO,QAAQ,SAAS,OAAO,QAAQ,EAAE,GAAG,WAAW,wBAAwB,GAAG,OAAO;AACxP,YAAI,QAAQ,gBAAgB,QAAS,OAAM;AAAA,MAC7C;AAAA,IACF,QAAQ;AAAA,IAER;AACA,QAAI,YAAa,QAAO,YAAY,KAAK;AACzC,YAAQ,MAAM,OAAO,KAAK;AAC1B,WAAO;AAAA,EACT;AACF;AAOO,SAAS,cAAc,UAA8C,CAAC,GAAwF;AACnK,SAAO,CAAC,EAAE,OAAO,QAAQ,MAAM;AAC7B,QAAI,SAA0C;AAC9C,QAAI;AACF,eAAS,QAAQ,UAAU,SAAS,EAAE;AAAA,IACxC,QAAQ;AACN,eAAS;AAAA,IACX;AACA,QAAI,CAAC,UAAU,OAAO,OAAO,cAAc,WAAY,QAAO,QAAQ,KAAK;AAC3E,QAAI,UAAU;AACd,QAAI;AACF,aAAO,OAAO,UAAU,CAAC,UAAU;AACjC,kBAAU;AACV,YAAI;AACF,gBAAM,qBAAqB,cAAc,KAAK,CAAC;AAAA,QACjD,QAAQ;AAAA,QAER;AACA,eAAO,QAAQ,KAAK;AAAA,MACtB,CAAC;AAAA,IACH,SAAS,OAAO;AAEd,UAAI,CAAC,QAAS,QAAO,QAAQ,KAAK;AAClC,YAAM;AAAA,IACR;AAAA,EACF;AACF;;;ADjNA,SAASA,YAAqB;AAC5B,QAAM,IAAI;AACV,SAAQ,EAAE,4BAAF,EAAE,0BAA4B,EAAE,SAAS,oBAAI,IAAI,GAAG,SAAS,MAAM,QAAQ,oBAAI,IAAI,EAAE;AAC/F;AAEA,SAAS,IAAI,MAAkC;AAC7C,MAAI;AACF,WAAQ,WAA0E,SAAS,MAAM,IAAI,KAAK;AAAA,EAC5G,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAQO,SAAS,kBAAkB,UAAuC,CAAC,GAAgB;AACxF,QAAM,SAAS,QAAQ,UAAU,QAAQ,aAAa,IAAI,iBAAiB,KAAK,IAAI,2BAA2B;AAC/G,MAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,gGAAgG;AAC7H,QAAM,SAAS,QAAQ,UAAU,IAAI,iBAAiB,KAAK,IAAI,wBAAwB;AACvF,QAAM,cAAc,QAAQ,eAAe,IAAI,aAAa,KAAK,IAAI,oBAAoB;AACzF,QAAM,KAAK,GAAG,MAAM,IAAI,UAAU,EAAE,IAAI,eAAe,EAAE,IAAI,QAAQ,eAAe,MAAM;AAC1F,QAAM,MAAMA,UAAS;AACrB,MAAI,SAAS,IAAI,QAAQ,IAAI,EAAE;AAC/B,MAAI,CAAC,QAAQ;AACX,iBAAS,gCAAkB,EAAE,SAAS,OAAO,GAAG,SAAS,QAAQ,QAAQ,YAAY,CAAC;AACtF,QAAI,QAAQ,IAAI,IAAI,MAAM;AAAA,EAC5B;AACA,MAAI,UAAU;AACd,SAAO;AACT;AASA,SAAS,OAAO,QAAqB,WAAkC;AACrE,MAAI,OAAO,QAAQ,EAAG,QAAO,QAAQ,QAAQ;AAC7C,SAAO,QAAQ,KAAK,CAAC,OAAO,MAAM,GAAG,IAAI,QAAc,CAAC,MAAM,WAAW,GAAG,SAAS,CAAC,CAAC,CAAC;AAC1F;AAGA,eAAsB,SAAS,aAAyB,CAAC,GAAG,UAA4B,CAAC,GAA4B;AACnH,QAAM,SAAS,QAAQ,UAAUA,UAAS,EAAE,WAAW,kBAAkB;AACzE,QAAM,OAAO,QAAQ,QAAQ,aAAa,IAAI;AAC9C,SAAO,OAAO,QAAQ,UAAU;AAClC;AASA,eAAsB,UAAU,QAA4F,CAAC,GAAG,UAA4B,CAAC,GAAwE;AACnO,QAAM,SAAU,MAAgF;AAChG,QAAM,aAAa,UAAU,OAAO,WAAW,WAAY,OAAO,OAAO,gBAAgB,KAAK,CAAC,IAAM;AACrG,QAAM,SAAS,QAAQ,UAAUA,UAAS,EAAE,WAAW,kBAAkB;AACzE,MAAI,OAAO,OAAO,WAAW,SAAS,GAAG;AACvC,UAAM,MAAMA,UAAS;AACrB,QAAI,CAAC,IAAI,OAAO,IAAI,OAAO,MAAM,GAAG;AAClC,UAAI,OAAO,IAAI,OAAO,MAAM;AAC5B,cAAQ,KAAK,uLAAkL;AAAA,IACjM;AACA,WAAO,EAAE,SAAS,MAAM,WAAW;AAAA,EACrC;AACA,QAAM,OAAO,QAAQ,QAAQ,aAAa,IAAI;AAC9C,SAAO,EAAE,SAAS,OAAO,WAAW,GAAG,WAAW;AACpD;","names":["registry"]}
|
package/dist/server.d.cts
CHANGED
|
@@ -1,6 +1,96 @@
|
|
|
1
1
|
import { FlagsClient, FlagsClientOptions, Attributes, FlagsEvaluator, FlagsBootstrap } from '@guidelytech/flags';
|
|
2
2
|
export { Attributes, FlagDetails, FlagsBootstrap, FlagsClient, FlagsClientOptions, FlagsEvaluator, JsonValue } from '@guidelytech/flags';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Server-side error monitoring for SvelteKit: `handleErrorWithGuidely` for `hooks.server.ts` and a
|
|
6
|
+
* `guidelyHandle()` for `sequence(...)`. Errors go to a server Guidely client you register once
|
|
7
|
+
* (`@guidelytech/node`'s `Guidely`, or anything with its `captureException`), typed structurally so
|
|
8
|
+
* this entry has no Node built-ins and runs on every adapter (Node, Vercel, Cloudflare, Netlify).
|
|
9
|
+
* Nothing here throws into the request, and nothing waits for the network on the request's path.
|
|
10
|
+
*/
|
|
11
|
+
/** What capture uses from a server client; `new Guidely({ secretKey, environment })` from `@guidelytech/node` fits. */
|
|
12
|
+
interface GuidelyServerErrorClient {
|
|
13
|
+
captureException(error: unknown, context?: Record<string, unknown>): string | void;
|
|
14
|
+
/** Sends what is queued; used (bounded by `flushTimeoutMs`) to get the error out before a serverless function freezes. */
|
|
15
|
+
flush?(): Promise<void>;
|
|
16
|
+
/** Request scopes (breadcrumbs, tags per request) and the event's transaction name (`GET /blog/[slug]`). */
|
|
17
|
+
withScope?<T>(fn: (scope: {
|
|
18
|
+
setTransactionName?(name: string | undefined): unknown;
|
|
19
|
+
}) => T): T;
|
|
20
|
+
}
|
|
21
|
+
interface ServerErrorOptions {
|
|
22
|
+
/** The client to report to (default: the one given to `setGuidelyServerClient`). */
|
|
23
|
+
client?: GuidelyServerErrorClient | null;
|
|
24
|
+
/** Longest the background flush may run (default 2000 ms; 0 skips it). */
|
|
25
|
+
flushTimeoutMs?: number;
|
|
26
|
+
/** Wait for the flush before answering, for serverless hosts with no `waitUntil`. Default false. */
|
|
27
|
+
waitForFlush?: boolean;
|
|
28
|
+
/** Report 404s too (default false: a missing page is not an error, as in Sentry). */
|
|
29
|
+
captureNotFound?: boolean;
|
|
30
|
+
}
|
|
31
|
+
/** Registers the server client every capture here reports to (call it once in `hooks.server.ts`). */
|
|
32
|
+
declare function setGuidelyServerClient(client: GuidelyServerErrorClient | null): void;
|
|
33
|
+
/** The parts of SvelteKit's `RequestEvent` capture reads. */
|
|
34
|
+
interface RequestEventLike {
|
|
35
|
+
request?: {
|
|
36
|
+
method?: string;
|
|
37
|
+
url?: string;
|
|
38
|
+
headers?: unknown;
|
|
39
|
+
};
|
|
40
|
+
route?: {
|
|
41
|
+
id?: string | null;
|
|
42
|
+
};
|
|
43
|
+
url?: URL | {
|
|
44
|
+
href?: string;
|
|
45
|
+
pathname?: string;
|
|
46
|
+
};
|
|
47
|
+
platform?: unknown;
|
|
48
|
+
}
|
|
49
|
+
/** `handleError`'s input in `hooks.server.ts`. */
|
|
50
|
+
interface ServerHandleErrorInput {
|
|
51
|
+
error: unknown;
|
|
52
|
+
event: RequestEventLike;
|
|
53
|
+
status: number;
|
|
54
|
+
message: string;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Reports one server error (once per error object) and starts a bounded background flush.
|
|
58
|
+
* Returns the event id ("" when nothing was reported) and the flush.
|
|
59
|
+
*/
|
|
60
|
+
declare function captureServerError(error: unknown, input: {
|
|
61
|
+
event?: RequestEventLike;
|
|
62
|
+
contexts?: Record<string, Record<string, unknown>>;
|
|
63
|
+
tags?: Record<string, string>;
|
|
64
|
+
mechanism: string;
|
|
65
|
+
}, options?: ServerErrorOptions): {
|
|
66
|
+
eventId: string;
|
|
67
|
+
flushed?: Promise<void>;
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* `handleError` for `hooks.server.ts` (Sentry's `handleErrorWithSentry`): reports the error with
|
|
71
|
+
* the request (credentials removed) and `contexts.sveltekit` (`routeId`, `status`, `message`), then
|
|
72
|
+
* calls your `handleError` and returns what it returns. 404s are not reported unless
|
|
73
|
+
* `captureNotFound`. Without a `handleError` of your own the error is logged, as SvelteKit does.
|
|
74
|
+
*
|
|
75
|
+
* ```ts
|
|
76
|
+
* // src/hooks.server.ts
|
|
77
|
+
* import { Guidely } from "@guidelytech/node";
|
|
78
|
+
* import { handleErrorWithGuidely, setGuidelyServerClient } from "@guidelytech/sveltekit/server";
|
|
79
|
+
* setGuidelyServerClient(new Guidely({ secretKey: GUIDELY_SECRET_KEY, environment: "production" }));
|
|
80
|
+
* export const handleError = handleErrorWithGuidely(({ error, event, status, message }) => ({ message: "Something went wrong" }));
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
declare function handleErrorWithGuidely<R = void>(handleError?: (input: ServerHandleErrorInput) => R | Promise<R>, options?: ServerErrorOptions): (input: ServerHandleErrorInput) => Promise<R | undefined>;
|
|
84
|
+
/**
|
|
85
|
+
* A server `handle` for `sequence(guidelyHandle(), …)`: runs each request in its own scope of the
|
|
86
|
+
* server client when it has scopes (`@guidelytech/node` does: breadcrumbs, `setTag` and `setUser`
|
|
87
|
+
* calls made while handling the request stay with its errors), named `GET /blog/[slug]`.
|
|
88
|
+
*/
|
|
89
|
+
declare function guidelyHandle(options?: Pick<ServerErrorOptions, "client">): <E extends RequestEventLike, T>(input: {
|
|
90
|
+
event: E;
|
|
91
|
+
resolve: (event: E) => T;
|
|
92
|
+
}) => T;
|
|
93
|
+
|
|
4
94
|
/**
|
|
5
95
|
* `@guidelytech/sveltekit/server` — feature flags in `hooks.server.ts`, `+layout.server.ts`,
|
|
6
96
|
* `+page.server.ts` and endpoints. No `$app` imports, no Svelte.
|
|
@@ -46,4 +136,4 @@ declare function loadFlags(input?: {
|
|
|
46
136
|
attributes: Attributes;
|
|
47
137
|
}>;
|
|
48
138
|
|
|
49
|
-
export { type LoadFlagsOptions, createServerFlags, getFlags, loadFlags };
|
|
139
|
+
export { type GuidelyServerErrorClient, type LoadFlagsOptions, type RequestEventLike, type ServerErrorOptions, type ServerHandleErrorInput, captureServerError, createServerFlags, getFlags, guidelyHandle, handleErrorWithGuidely, loadFlags, setGuidelyServerClient };
|
package/dist/server.d.ts
CHANGED
|
@@ -1,6 +1,96 @@
|
|
|
1
1
|
import { FlagsClient, FlagsClientOptions, Attributes, FlagsEvaluator, FlagsBootstrap } from '@guidelytech/flags';
|
|
2
2
|
export { Attributes, FlagDetails, FlagsBootstrap, FlagsClient, FlagsClientOptions, FlagsEvaluator, JsonValue } from '@guidelytech/flags';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Server-side error monitoring for SvelteKit: `handleErrorWithGuidely` for `hooks.server.ts` and a
|
|
6
|
+
* `guidelyHandle()` for `sequence(...)`. Errors go to a server Guidely client you register once
|
|
7
|
+
* (`@guidelytech/node`'s `Guidely`, or anything with its `captureException`), typed structurally so
|
|
8
|
+
* this entry has no Node built-ins and runs on every adapter (Node, Vercel, Cloudflare, Netlify).
|
|
9
|
+
* Nothing here throws into the request, and nothing waits for the network on the request's path.
|
|
10
|
+
*/
|
|
11
|
+
/** What capture uses from a server client; `new Guidely({ secretKey, environment })` from `@guidelytech/node` fits. */
|
|
12
|
+
interface GuidelyServerErrorClient {
|
|
13
|
+
captureException(error: unknown, context?: Record<string, unknown>): string | void;
|
|
14
|
+
/** Sends what is queued; used (bounded by `flushTimeoutMs`) to get the error out before a serverless function freezes. */
|
|
15
|
+
flush?(): Promise<void>;
|
|
16
|
+
/** Request scopes (breadcrumbs, tags per request) and the event's transaction name (`GET /blog/[slug]`). */
|
|
17
|
+
withScope?<T>(fn: (scope: {
|
|
18
|
+
setTransactionName?(name: string | undefined): unknown;
|
|
19
|
+
}) => T): T;
|
|
20
|
+
}
|
|
21
|
+
interface ServerErrorOptions {
|
|
22
|
+
/** The client to report to (default: the one given to `setGuidelyServerClient`). */
|
|
23
|
+
client?: GuidelyServerErrorClient | null;
|
|
24
|
+
/** Longest the background flush may run (default 2000 ms; 0 skips it). */
|
|
25
|
+
flushTimeoutMs?: number;
|
|
26
|
+
/** Wait for the flush before answering, for serverless hosts with no `waitUntil`. Default false. */
|
|
27
|
+
waitForFlush?: boolean;
|
|
28
|
+
/** Report 404s too (default false: a missing page is not an error, as in Sentry). */
|
|
29
|
+
captureNotFound?: boolean;
|
|
30
|
+
}
|
|
31
|
+
/** Registers the server client every capture here reports to (call it once in `hooks.server.ts`). */
|
|
32
|
+
declare function setGuidelyServerClient(client: GuidelyServerErrorClient | null): void;
|
|
33
|
+
/** The parts of SvelteKit's `RequestEvent` capture reads. */
|
|
34
|
+
interface RequestEventLike {
|
|
35
|
+
request?: {
|
|
36
|
+
method?: string;
|
|
37
|
+
url?: string;
|
|
38
|
+
headers?: unknown;
|
|
39
|
+
};
|
|
40
|
+
route?: {
|
|
41
|
+
id?: string | null;
|
|
42
|
+
};
|
|
43
|
+
url?: URL | {
|
|
44
|
+
href?: string;
|
|
45
|
+
pathname?: string;
|
|
46
|
+
};
|
|
47
|
+
platform?: unknown;
|
|
48
|
+
}
|
|
49
|
+
/** `handleError`'s input in `hooks.server.ts`. */
|
|
50
|
+
interface ServerHandleErrorInput {
|
|
51
|
+
error: unknown;
|
|
52
|
+
event: RequestEventLike;
|
|
53
|
+
status: number;
|
|
54
|
+
message: string;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Reports one server error (once per error object) and starts a bounded background flush.
|
|
58
|
+
* Returns the event id ("" when nothing was reported) and the flush.
|
|
59
|
+
*/
|
|
60
|
+
declare function captureServerError(error: unknown, input: {
|
|
61
|
+
event?: RequestEventLike;
|
|
62
|
+
contexts?: Record<string, Record<string, unknown>>;
|
|
63
|
+
tags?: Record<string, string>;
|
|
64
|
+
mechanism: string;
|
|
65
|
+
}, options?: ServerErrorOptions): {
|
|
66
|
+
eventId: string;
|
|
67
|
+
flushed?: Promise<void>;
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* `handleError` for `hooks.server.ts` (Sentry's `handleErrorWithSentry`): reports the error with
|
|
71
|
+
* the request (credentials removed) and `contexts.sveltekit` (`routeId`, `status`, `message`), then
|
|
72
|
+
* calls your `handleError` and returns what it returns. 404s are not reported unless
|
|
73
|
+
* `captureNotFound`. Without a `handleError` of your own the error is logged, as SvelteKit does.
|
|
74
|
+
*
|
|
75
|
+
* ```ts
|
|
76
|
+
* // src/hooks.server.ts
|
|
77
|
+
* import { Guidely } from "@guidelytech/node";
|
|
78
|
+
* import { handleErrorWithGuidely, setGuidelyServerClient } from "@guidelytech/sveltekit/server";
|
|
79
|
+
* setGuidelyServerClient(new Guidely({ secretKey: GUIDELY_SECRET_KEY, environment: "production" }));
|
|
80
|
+
* export const handleError = handleErrorWithGuidely(({ error, event, status, message }) => ({ message: "Something went wrong" }));
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
declare function handleErrorWithGuidely<R = void>(handleError?: (input: ServerHandleErrorInput) => R | Promise<R>, options?: ServerErrorOptions): (input: ServerHandleErrorInput) => Promise<R | undefined>;
|
|
84
|
+
/**
|
|
85
|
+
* A server `handle` for `sequence(guidelyHandle(), …)`: runs each request in its own scope of the
|
|
86
|
+
* server client when it has scopes (`@guidelytech/node` does: breadcrumbs, `setTag` and `setUser`
|
|
87
|
+
* calls made while handling the request stay with its errors), named `GET /blog/[slug]`.
|
|
88
|
+
*/
|
|
89
|
+
declare function guidelyHandle(options?: Pick<ServerErrorOptions, "client">): <E extends RequestEventLike, T>(input: {
|
|
90
|
+
event: E;
|
|
91
|
+
resolve: (event: E) => T;
|
|
92
|
+
}) => T;
|
|
93
|
+
|
|
4
94
|
/**
|
|
5
95
|
* `@guidelytech/sveltekit/server` — feature flags in `hooks.server.ts`, `+layout.server.ts`,
|
|
6
96
|
* `+page.server.ts` and endpoints. No `$app` imports, no Svelte.
|
|
@@ -46,4 +136,4 @@ declare function loadFlags(input?: {
|
|
|
46
136
|
attributes: Attributes;
|
|
47
137
|
}>;
|
|
48
138
|
|
|
49
|
-
export { type LoadFlagsOptions, createServerFlags, getFlags, loadFlags };
|
|
139
|
+
export { type GuidelyServerErrorClient, type LoadFlagsOptions, type RequestEventLike, type ServerErrorOptions, type ServerHandleErrorInput, captureServerError, createServerFlags, getFlags, guidelyHandle, handleErrorWithGuidely, loadFlags, setGuidelyServerClient };
|
package/dist/server.js
CHANGED
|
@@ -1,6 +1,145 @@
|
|
|
1
1
|
// src/server.ts
|
|
2
2
|
import { createFlagsClient } from "@guidelytech/flags";
|
|
3
|
+
|
|
4
|
+
// src/server-errors.ts
|
|
3
5
|
function registry() {
|
|
6
|
+
const g = globalThis;
|
|
7
|
+
return g.__guidelySvelteKitErrors ?? (g.__guidelySvelteKitErrors = { client: null, reported: /* @__PURE__ */ new WeakSet(), warned: false });
|
|
8
|
+
}
|
|
9
|
+
function setGuidelyServerClient(client) {
|
|
10
|
+
registry().client = client;
|
|
11
|
+
}
|
|
12
|
+
var SENSITIVE_HEADER = /^(?:cookie|set-cookie|authorization|proxy-authorization)$|token|secret|password|api[-_]?key|session|csrf/i;
|
|
13
|
+
function safeHeaders(headers) {
|
|
14
|
+
const out = {};
|
|
15
|
+
const add = (name, value) => {
|
|
16
|
+
if (Object.keys(out).length >= 50 || value === void 0 || value === null || SENSITIVE_HEADER.test(name)) return;
|
|
17
|
+
out[name.toLowerCase()] = (Array.isArray(value) ? value.join(", ") : String(value)).slice(0, 1e3);
|
|
18
|
+
};
|
|
19
|
+
try {
|
|
20
|
+
const h = headers;
|
|
21
|
+
if (h && typeof h.forEach === "function" && !Array.isArray(h)) h.forEach((value, name) => add(name, value));
|
|
22
|
+
else if (h && typeof h === "object") for (const [name, value] of Object.entries(h)) add(name, value);
|
|
23
|
+
} catch {
|
|
24
|
+
}
|
|
25
|
+
return out;
|
|
26
|
+
}
|
|
27
|
+
function waitUntilOf(event) {
|
|
28
|
+
try {
|
|
29
|
+
const p = event?.platform;
|
|
30
|
+
const own = p?.context?.waitUntil ?? p?.waitUntil;
|
|
31
|
+
if (typeof own === "function") return (promise) => own.call(p?.context ?? p, promise);
|
|
32
|
+
const g = globalThis;
|
|
33
|
+
const vercel = g[/* @__PURE__ */ Symbol.for("@vercel/request-context")]?.get?.()?.waitUntil;
|
|
34
|
+
if (typeof vercel === "function") return vercel;
|
|
35
|
+
} catch {
|
|
36
|
+
}
|
|
37
|
+
return void 0;
|
|
38
|
+
}
|
|
39
|
+
function flushSoon(client, options, event) {
|
|
40
|
+
const timeout = options.flushTimeoutMs ?? 2e3;
|
|
41
|
+
if (typeof client.flush !== "function" || timeout <= 0) return void 0;
|
|
42
|
+
let timer;
|
|
43
|
+
const p = Promise.race([
|
|
44
|
+
Promise.resolve().then(() => client.flush()).catch(() => {
|
|
45
|
+
}),
|
|
46
|
+
new Promise((resolve) => {
|
|
47
|
+
timer = setTimeout(resolve, timeout);
|
|
48
|
+
timer.unref?.();
|
|
49
|
+
})
|
|
50
|
+
]).finally(() => clearTimeout(timer));
|
|
51
|
+
const waitUntil = waitUntilOf(event);
|
|
52
|
+
if (waitUntil) {
|
|
53
|
+
try {
|
|
54
|
+
waitUntil(p);
|
|
55
|
+
} catch {
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return p;
|
|
59
|
+
}
|
|
60
|
+
var transactionOf = (event) => {
|
|
61
|
+
const route = event?.route?.id;
|
|
62
|
+
if (!route) return void 0;
|
|
63
|
+
const method = typeof event?.request?.method === "string" ? event.request.method.toUpperCase() : "";
|
|
64
|
+
return `${method ? `${method} ` : ""}${route}`.slice(0, 200);
|
|
65
|
+
};
|
|
66
|
+
function captureServerError(error, input, options = {}) {
|
|
67
|
+
try {
|
|
68
|
+
const reg = registry();
|
|
69
|
+
const client = options.client ?? reg.client;
|
|
70
|
+
if (!client) {
|
|
71
|
+
if (!reg.warned) {
|
|
72
|
+
reg.warned = true;
|
|
73
|
+
console.warn("[Guidely] A server error was not reported: no server client. Call setGuidelyServerClient(new Guidely({ secretKey, environment })) in hooks.server.ts.");
|
|
74
|
+
}
|
|
75
|
+
return { eventId: "" };
|
|
76
|
+
}
|
|
77
|
+
if (error && typeof error === "object") {
|
|
78
|
+
if (reg.reported.has(error)) return { eventId: "" };
|
|
79
|
+
reg.reported.add(error);
|
|
80
|
+
}
|
|
81
|
+
const req = input.event?.request;
|
|
82
|
+
const ctx = {
|
|
83
|
+
level: "error",
|
|
84
|
+
...input.tags ? { tags: input.tags } : {},
|
|
85
|
+
...input.contexts ? { contexts: input.contexts } : {},
|
|
86
|
+
...req && typeof req === "object" ? { request: { method: typeof req.method === "string" ? req.method.toUpperCase() : void 0, url: typeof req.url === "string" ? req.url : void 0, headers: safeHeaders(req.headers) } } : {},
|
|
87
|
+
mechanism: { type: input.mechanism, handled: false }
|
|
88
|
+
};
|
|
89
|
+
const transaction = transactionOf(input.event);
|
|
90
|
+
const run = () => client.captureException(error, ctx);
|
|
91
|
+
const id = transaction && typeof client.withScope === "function" ? client.withScope((scope) => {
|
|
92
|
+
scope.setTransactionName?.(transaction);
|
|
93
|
+
return run();
|
|
94
|
+
}) : run();
|
|
95
|
+
return { eventId: typeof id === "string" ? id : "", flushed: flushSoon(client, options, input.event) };
|
|
96
|
+
} catch {
|
|
97
|
+
return { eventId: "" };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function handleErrorWithGuidely(handleError, options = {}) {
|
|
101
|
+
return async (input) => {
|
|
102
|
+
try {
|
|
103
|
+
if (input?.status !== 404 || options.captureNotFound) {
|
|
104
|
+
const routeId = input?.event?.route?.id ?? null;
|
|
105
|
+
const { flushed } = captureServerError(input?.error, { event: input?.event, tags: routeId ? { routeId } : void 0, contexts: { sveltekit: { routeId, status: input?.status, message: input?.message } }, mechanism: "sveltekit.handleError" }, options);
|
|
106
|
+
if (options.waitForFlush && flushed) await flushed;
|
|
107
|
+
}
|
|
108
|
+
} catch {
|
|
109
|
+
}
|
|
110
|
+
if (handleError) return handleError(input);
|
|
111
|
+
console.error(input?.error);
|
|
112
|
+
return void 0;
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function guidelyHandle(options = {}) {
|
|
116
|
+
return ({ event, resolve }) => {
|
|
117
|
+
let client = null;
|
|
118
|
+
try {
|
|
119
|
+
client = options.client ?? registry().client;
|
|
120
|
+
} catch {
|
|
121
|
+
client = null;
|
|
122
|
+
}
|
|
123
|
+
if (!client || typeof client.withScope !== "function") return resolve(event);
|
|
124
|
+
let entered = false;
|
|
125
|
+
try {
|
|
126
|
+
return client.withScope((scope) => {
|
|
127
|
+
entered = true;
|
|
128
|
+
try {
|
|
129
|
+
scope.setTransactionName?.(transactionOf(event));
|
|
130
|
+
} catch {
|
|
131
|
+
}
|
|
132
|
+
return resolve(event);
|
|
133
|
+
});
|
|
134
|
+
} catch (error) {
|
|
135
|
+
if (!entered) return resolve(event);
|
|
136
|
+
throw error;
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// src/server.ts
|
|
142
|
+
function registry2() {
|
|
4
143
|
const g = globalThis;
|
|
5
144
|
return g.__guidelySvelteKitFlags ?? (g.__guidelySvelteKitFlags = { clients: /* @__PURE__ */ new Map(), current: null, warned: /* @__PURE__ */ new Set() });
|
|
6
145
|
}
|
|
@@ -17,7 +156,7 @@ function createServerFlags(options = {}) {
|
|
|
17
156
|
const apiUrl = options.apiUrl ?? env("GUIDELY_API_URL") ?? env("PUBLIC_GUIDELY_API_URL");
|
|
18
157
|
const environment = options.environment ?? env("GUIDELY_ENV") ?? env("PUBLIC_GUIDELY_ENV");
|
|
19
158
|
const id = `${sdkKey}|${apiUrl ?? ""}|${environment ?? ""}|${options.pollingMode ?? "auto"}`;
|
|
20
|
-
const reg =
|
|
159
|
+
const reg = registry2();
|
|
21
160
|
let client = reg.clients.get(id);
|
|
22
161
|
if (!client) {
|
|
23
162
|
client = createFlagsClient({ storage: false, ...options, sdkKey, apiUrl, environment });
|
|
@@ -31,16 +170,16 @@ function settle(client, timeoutMs) {
|
|
|
31
170
|
return Promise.race([client.ready(), new Promise((r) => setTimeout(r, timeoutMs))]);
|
|
32
171
|
}
|
|
33
172
|
async function getFlags(attributes = {}, options = {}) {
|
|
34
|
-
const client = options.client ??
|
|
173
|
+
const client = options.client ?? registry2().current ?? createServerFlags();
|
|
35
174
|
await settle(client, options.timeoutMs ?? 1500);
|
|
36
175
|
return client.forUser(attributes);
|
|
37
176
|
}
|
|
38
177
|
async function loadFlags(input = {}, options = {}) {
|
|
39
178
|
const locals = input.locals;
|
|
40
179
|
const attributes = locals && typeof locals === "object" ? locals.flags?.getAttributes?.() ?? {} : input;
|
|
41
|
-
const client = options.client ??
|
|
180
|
+
const client = options.client ?? registry2().current ?? createServerFlags();
|
|
42
181
|
if (client.sdkKey.startsWith("gly_sk_")) {
|
|
43
|
-
const reg =
|
|
182
|
+
const reg = registry2();
|
|
44
183
|
if (!reg.warned.has(client.sdkKey)) {
|
|
45
184
|
reg.warned.add(client.sdkKey);
|
|
46
185
|
console.warn("[Guidely] loadFlags() needs a public-key client (pk_\u2026): a gly_sk_ payload includes server-only flags. Pass { client: createServerFlags({ sdkKey: PUBLIC_GUIDELY_PROJECT_ID }) }.");
|
|
@@ -51,8 +190,12 @@ async function loadFlags(input = {}, options = {}) {
|
|
|
51
190
|
return { payload: client.getPayload(), attributes };
|
|
52
191
|
}
|
|
53
192
|
export {
|
|
193
|
+
captureServerError,
|
|
54
194
|
createServerFlags,
|
|
55
195
|
getFlags,
|
|
56
|
-
|
|
196
|
+
guidelyHandle,
|
|
197
|
+
handleErrorWithGuidely,
|
|
198
|
+
loadFlags,
|
|
199
|
+
setGuidelyServerClient
|
|
57
200
|
};
|
|
58
201
|
//# sourceMappingURL=server.js.map
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server.ts"],"sourcesContent":["/**\n * `@guidelytech/sveltekit/server` — feature flags in `hooks.server.ts`, `+layout.server.ts`,\n * `+page.server.ts` and endpoints. No `$app` imports, no Svelte.\n *\n * ```ts\n * // src/hooks.server.ts\n * const flags = createServerFlags();\n * export const handle = async ({ event, resolve }) => {\n * event.locals.flags = flags.forUser({ id: event.locals.user?.id });\n * return resolve(event);\n * };\n * ```\n */\nimport { createFlagsClient, type Attributes, type FlagsBootstrap, type FlagsClient, type FlagsClientOptions, type FlagsEvaluator } from \"@guidelytech/flags\";\n\nexport type { Attributes, FlagsBootstrap, FlagsClient, FlagsClientOptions, FlagsEvaluator, FlagDetails, JsonValue } from \"@guidelytech/flags\";\n\ninterface Registry {\n clients: Map<string, FlagsClient>;\n current: FlagsClient | null;\n warned: Set<string>;\n}\n\n/** Kept on `globalThis` so Vite's dev-server reloads reuse the client (and its timers). */\nfunction registry(): Registry {\n const g = globalThis as { __guidelySvelteKitFlags?: Registry };\n return (g.__guidelySvelteKitFlags ??= { clients: new Map(), current: null, warned: new Set() });\n}\n\nfunction env(name: string): string | undefined {\n try {\n return (globalThis as { process?: { env?: Record<string, string | undefined> } }).process?.env?.[name] || undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * The process-wide flags client (created once, reused across requests). Options default to\n * `GUIDELY_SDK_KEY` (public `pk_…` or secret `gly_sk_…`), falling back to `PUBLIC_GUIDELY_PROJECT_ID`,\n * plus `GUIDELY_API_URL` / `PUBLIC_GUIDELY_API_URL` and `GUIDELY_ENV` / `PUBLIC_GUIDELY_ENV`. Polls\n * every 60 s; on serverless / edge adapters use `{ pollingMode: \"lazy\" }`.\n */\nexport function createServerFlags(options: Partial<FlagsClientOptions> = {}): FlagsClient {\n const sdkKey = options.sdkKey ?? options.projectId ?? env(\"GUIDELY_SDK_KEY\") ?? env(\"PUBLIC_GUIDELY_PROJECT_ID\");\n if (!sdkKey) throw new Error(\"[Guidely] createServerFlags: pass `sdkKey` or set GUIDELY_SDK_KEY / PUBLIC_GUIDELY_PROJECT_ID.\");\n const apiUrl = options.apiUrl ?? env(\"GUIDELY_API_URL\") ?? env(\"PUBLIC_GUIDELY_API_URL\");\n const environment = options.environment ?? env(\"GUIDELY_ENV\") ?? env(\"PUBLIC_GUIDELY_ENV\");\n const id = `${sdkKey}|${apiUrl ?? \"\"}|${environment ?? \"\"}|${options.pollingMode ?? \"auto\"}`;\n const reg = registry();\n let client = reg.clients.get(id);\n if (!client) {\n client = createFlagsClient({ storage: false, ...options, sdkKey, apiUrl, environment });\n reg.clients.set(id, client);\n }\n reg.current = client;\n return client;\n}\n\nexport interface LoadFlagsOptions {\n /** A client from `createServerFlags(...)` (default: the last one created, or one from environment variables). */\n client?: FlagsClient;\n /** How long to wait for the first download before serving fallbacks (default 1500 ms). */\n timeoutMs?: number;\n}\n\nfunction settle(client: FlagsClient, timeoutMs: number): Promise<void> {\n if (client.isReady()) return Promise.resolve();\n return Promise.race([client.ready(), new Promise<void>((r) => setTimeout(r, timeoutMs))]);\n}\n\n/** Flag getters for one user, for this request (waits briefly for the first download). */\nexport async function getFlags(attributes: Attributes = {}, options: LoadFlagsOptions = {}): Promise<FlagsEvaluator> {\n const client = options.client ?? registry().current ?? createServerFlags();\n await settle(client, options.timeoutMs ?? 1500);\n return client.forUser(attributes);\n}\n\n/**\n * For `+layout.server.ts`: the payload and attributes to hand to the browser, so the first render\n * matches the server. Pass the request `event` (attributes come from `event.locals.flags`, set in\n * `hooks.server.ts`) or the attributes. Spread the result into `initGuidely({ …, flags: data.flags })`.\n * The payload is `null` for a `gly_sk_` client (it holds server-only flags): bootstrap from a\n * public-key client instead.\n */\nexport async function loadFlags(input: { locals?: { flags?: Pick<FlagsEvaluator, \"getAttributes\"> | null } } | Attributes = {}, options: LoadFlagsOptions = {}): Promise<{ payload: FlagsBootstrap | null; attributes: Attributes }> {\n const locals = (input as { locals?: { flags?: Pick<FlagsEvaluator, \"getAttributes\"> | null } }).locals;\n const attributes = locals && typeof locals === \"object\" ? (locals.flags?.getAttributes?.() ?? {}) : (input as Attributes);\n const client = options.client ?? registry().current ?? createServerFlags();\n if (client.sdkKey.startsWith(\"gly_sk_\")) {\n const reg = registry();\n if (!reg.warned.has(client.sdkKey)) {\n reg.warned.add(client.sdkKey);\n console.warn(\"[Guidely] loadFlags() needs a public-key client (pk_…): a gly_sk_ payload includes server-only flags. Pass { client: createServerFlags({ sdkKey: PUBLIC_GUIDELY_PROJECT_ID }) }.\");\n }\n return { payload: null, attributes };\n }\n await settle(client, options.timeoutMs ?? 1500);\n return { payload: client.getPayload(), attributes };\n}\n"],"mappings":";AAaA,SAAS,yBAA+H;AAWxI,SAAS,WAAqB;AAC5B,QAAM,IAAI;AACV,SAAQ,EAAE,4BAAF,EAAE,0BAA4B,EAAE,SAAS,oBAAI,IAAI,GAAG,SAAS,MAAM,QAAQ,oBAAI,IAAI,EAAE;AAC/F;AAEA,SAAS,IAAI,MAAkC;AAC7C,MAAI;AACF,WAAQ,WAA0E,SAAS,MAAM,IAAI,KAAK;AAAA,EAC5G,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAQO,SAAS,kBAAkB,UAAuC,CAAC,GAAgB;AACxF,QAAM,SAAS,QAAQ,UAAU,QAAQ,aAAa,IAAI,iBAAiB,KAAK,IAAI,2BAA2B;AAC/G,MAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,gGAAgG;AAC7H,QAAM,SAAS,QAAQ,UAAU,IAAI,iBAAiB,KAAK,IAAI,wBAAwB;AACvF,QAAM,cAAc,QAAQ,eAAe,IAAI,aAAa,KAAK,IAAI,oBAAoB;AACzF,QAAM,KAAK,GAAG,MAAM,IAAI,UAAU,EAAE,IAAI,eAAe,EAAE,IAAI,QAAQ,eAAe,MAAM;AAC1F,QAAM,MAAM,SAAS;AACrB,MAAI,SAAS,IAAI,QAAQ,IAAI,EAAE;AAC/B,MAAI,CAAC,QAAQ;AACX,aAAS,kBAAkB,EAAE,SAAS,OAAO,GAAG,SAAS,QAAQ,QAAQ,YAAY,CAAC;AACtF,QAAI,QAAQ,IAAI,IAAI,MAAM;AAAA,EAC5B;AACA,MAAI,UAAU;AACd,SAAO;AACT;AASA,SAAS,OAAO,QAAqB,WAAkC;AACrE,MAAI,OAAO,QAAQ,EAAG,QAAO,QAAQ,QAAQ;AAC7C,SAAO,QAAQ,KAAK,CAAC,OAAO,MAAM,GAAG,IAAI,QAAc,CAAC,MAAM,WAAW,GAAG,SAAS,CAAC,CAAC,CAAC;AAC1F;AAGA,eAAsB,SAAS,aAAyB,CAAC,GAAG,UAA4B,CAAC,GAA4B;AACnH,QAAM,SAAS,QAAQ,UAAU,SAAS,EAAE,WAAW,kBAAkB;AACzE,QAAM,OAAO,QAAQ,QAAQ,aAAa,IAAI;AAC9C,SAAO,OAAO,QAAQ,UAAU;AAClC;AASA,eAAsB,UAAU,QAA4F,CAAC,GAAG,UAA4B,CAAC,GAAwE;AACnO,QAAM,SAAU,MAAgF;AAChG,QAAM,aAAa,UAAU,OAAO,WAAW,WAAY,OAAO,OAAO,gBAAgB,KAAK,CAAC,IAAM;AACrG,QAAM,SAAS,QAAQ,UAAU,SAAS,EAAE,WAAW,kBAAkB;AACzE,MAAI,OAAO,OAAO,WAAW,SAAS,GAAG;AACvC,UAAM,MAAM,SAAS;AACrB,QAAI,CAAC,IAAI,OAAO,IAAI,OAAO,MAAM,GAAG;AAClC,UAAI,OAAO,IAAI,OAAO,MAAM;AAC5B,cAAQ,KAAK,uLAAkL;AAAA,IACjM;AACA,WAAO,EAAE,SAAS,MAAM,WAAW;AAAA,EACrC;AACA,QAAM,OAAO,QAAQ,QAAQ,aAAa,IAAI;AAC9C,SAAO,EAAE,SAAS,OAAO,WAAW,GAAG,WAAW;AACpD;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/server.ts","../src/server-errors.ts"],"sourcesContent":["/**\n * `@guidelytech/sveltekit/server` — feature flags in `hooks.server.ts`, `+layout.server.ts`,\n * `+page.server.ts` and endpoints. No `$app` imports, no Svelte.\n *\n * ```ts\n * // src/hooks.server.ts\n * const flags = createServerFlags();\n * export const handle = async ({ event, resolve }) => {\n * event.locals.flags = flags.forUser({ id: event.locals.user?.id });\n * return resolve(event);\n * };\n * ```\n */\nimport { createFlagsClient, type Attributes, type FlagsBootstrap, type FlagsClient, type FlagsClientOptions, type FlagsEvaluator } from \"@guidelytech/flags\";\n\nexport type { Attributes, FlagsBootstrap, FlagsClient, FlagsClientOptions, FlagsEvaluator, FlagDetails, JsonValue } from \"@guidelytech/flags\";\n\ninterface Registry {\n clients: Map<string, FlagsClient>;\n current: FlagsClient | null;\n warned: Set<string>;\n}\n\n/** Kept on `globalThis` so Vite's dev-server reloads reuse the client (and its timers). */\nfunction registry(): Registry {\n const g = globalThis as { __guidelySvelteKitFlags?: Registry };\n return (g.__guidelySvelteKitFlags ??= { clients: new Map(), current: null, warned: new Set() });\n}\n\nfunction env(name: string): string | undefined {\n try {\n return (globalThis as { process?: { env?: Record<string, string | undefined> } }).process?.env?.[name] || undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * The process-wide flags client (created once, reused across requests). Options default to\n * `GUIDELY_SDK_KEY` (public `pk_…` or secret `gly_sk_…`), falling back to `PUBLIC_GUIDELY_PROJECT_ID`,\n * plus `GUIDELY_API_URL` / `PUBLIC_GUIDELY_API_URL` and `GUIDELY_ENV` / `PUBLIC_GUIDELY_ENV`. Polls\n * every 60 s; on serverless / edge adapters use `{ pollingMode: \"lazy\" }`.\n */\nexport function createServerFlags(options: Partial<FlagsClientOptions> = {}): FlagsClient {\n const sdkKey = options.sdkKey ?? options.projectId ?? env(\"GUIDELY_SDK_KEY\") ?? env(\"PUBLIC_GUIDELY_PROJECT_ID\");\n if (!sdkKey) throw new Error(\"[Guidely] createServerFlags: pass `sdkKey` or set GUIDELY_SDK_KEY / PUBLIC_GUIDELY_PROJECT_ID.\");\n const apiUrl = options.apiUrl ?? env(\"GUIDELY_API_URL\") ?? env(\"PUBLIC_GUIDELY_API_URL\");\n const environment = options.environment ?? env(\"GUIDELY_ENV\") ?? env(\"PUBLIC_GUIDELY_ENV\");\n const id = `${sdkKey}|${apiUrl ?? \"\"}|${environment ?? \"\"}|${options.pollingMode ?? \"auto\"}`;\n const reg = registry();\n let client = reg.clients.get(id);\n if (!client) {\n client = createFlagsClient({ storage: false, ...options, sdkKey, apiUrl, environment });\n reg.clients.set(id, client);\n }\n reg.current = client;\n return client;\n}\n\nexport interface LoadFlagsOptions {\n /** A client from `createServerFlags(...)` (default: the last one created, or one from environment variables). */\n client?: FlagsClient;\n /** How long to wait for the first download before serving fallbacks (default 1500 ms). */\n timeoutMs?: number;\n}\n\nfunction settle(client: FlagsClient, timeoutMs: number): Promise<void> {\n if (client.isReady()) return Promise.resolve();\n return Promise.race([client.ready(), new Promise<void>((r) => setTimeout(r, timeoutMs))]);\n}\n\n/** Flag getters for one user, for this request (waits briefly for the first download). */\nexport async function getFlags(attributes: Attributes = {}, options: LoadFlagsOptions = {}): Promise<FlagsEvaluator> {\n const client = options.client ?? registry().current ?? createServerFlags();\n await settle(client, options.timeoutMs ?? 1500);\n return client.forUser(attributes);\n}\n\n/**\n * For `+layout.server.ts`: the payload and attributes to hand to the browser, so the first render\n * matches the server. Pass the request `event` (attributes come from `event.locals.flags`, set in\n * `hooks.server.ts`) or the attributes. Spread the result into `initGuidely({ …, flags: data.flags })`.\n * The payload is `null` for a `gly_sk_` client (it holds server-only flags): bootstrap from a\n * public-key client instead.\n */\nexport async function loadFlags(input: { locals?: { flags?: Pick<FlagsEvaluator, \"getAttributes\"> | null } } | Attributes = {}, options: LoadFlagsOptions = {}): Promise<{ payload: FlagsBootstrap | null; attributes: Attributes }> {\n const locals = (input as { locals?: { flags?: Pick<FlagsEvaluator, \"getAttributes\"> | null } }).locals;\n const attributes = locals && typeof locals === \"object\" ? (locals.flags?.getAttributes?.() ?? {}) : (input as Attributes);\n const client = options.client ?? registry().current ?? createServerFlags();\n if (client.sdkKey.startsWith(\"gly_sk_\")) {\n const reg = registry();\n if (!reg.warned.has(client.sdkKey)) {\n reg.warned.add(client.sdkKey);\n console.warn(\"[Guidely] loadFlags() needs a public-key client (pk_…): a gly_sk_ payload includes server-only flags. Pass { client: createServerFlags({ sdkKey: PUBLIC_GUIDELY_PROJECT_ID }) }.\");\n }\n return { payload: null, attributes };\n }\n await settle(client, options.timeoutMs ?? 1500);\n return { payload: client.getPayload(), attributes };\n}\n\n// Error monitoring: `handleErrorWithGuidely` for hooks.server.ts and a request-scope `guidelyHandle()`.\nexport { captureServerError, guidelyHandle, handleErrorWithGuidely, setGuidelyServerClient } from \"./server-errors\";\nexport type { GuidelyServerErrorClient, RequestEventLike, ServerErrorOptions, ServerHandleErrorInput } from \"./server-errors\";\n","/**\n * Server-side error monitoring for SvelteKit: `handleErrorWithGuidely` for `hooks.server.ts` and a\n * `guidelyHandle()` for `sequence(...)`. Errors go to a server Guidely client you register once\n * (`@guidelytech/node`'s `Guidely`, or anything with its `captureException`), typed structurally so\n * this entry has no Node built-ins and runs on every adapter (Node, Vercel, Cloudflare, Netlify).\n * Nothing here throws into the request, and nothing waits for the network on the request's path.\n */\n\n/** What capture uses from a server client; `new Guidely({ secretKey, environment })` from `@guidelytech/node` fits. */\nexport interface GuidelyServerErrorClient {\n captureException(error: unknown, context?: Record<string, unknown>): string | void;\n /** Sends what is queued; used (bounded by `flushTimeoutMs`) to get the error out before a serverless function freezes. */\n flush?(): Promise<void>;\n /** Request scopes (breadcrumbs, tags per request) and the event's transaction name (`GET /blog/[slug]`). */\n withScope?<T>(fn: (scope: { setTransactionName?(name: string | undefined): unknown }) => T): T;\n}\n\nexport interface ServerErrorOptions {\n /** The client to report to (default: the one given to `setGuidelyServerClient`). */\n client?: GuidelyServerErrorClient | null;\n /** Longest the background flush may run (default 2000 ms; 0 skips it). */\n flushTimeoutMs?: number;\n /** Wait for the flush before answering, for serverless hosts with no `waitUntil`. Default false. */\n waitForFlush?: boolean;\n /** Report 404s too (default false: a missing page is not an error, as in Sentry). */\n captureNotFound?: boolean;\n}\n\ninterface ErrorRegistry {\n client: GuidelyServerErrorClient | null;\n reported: WeakSet<object>;\n warned: boolean;\n}\n\n/** Kept on `globalThis` so Vite's dev-server reloads keep the registered client. */\nfunction registry(): ErrorRegistry {\n const g = globalThis as { __guidelySvelteKitErrors?: ErrorRegistry };\n return (g.__guidelySvelteKitErrors ??= { client: null, reported: new WeakSet(), warned: false });\n}\n\n/** Registers the server client every capture here reports to (call it once in `hooks.server.ts`). */\nexport function setGuidelyServerClient(client: GuidelyServerErrorClient | null): void {\n registry().client = client;\n}\n\n/** Credentials never leave the server: cookies, auth headers and anything named like a secret. */\nconst SENSITIVE_HEADER = /^(?:cookie|set-cookie|authorization|proxy-authorization)$|token|secret|password|api[-_]?key|session|csrf/i;\n\n/** Request headers without credentials, as a plain record (at most 50, values ≤ 1000 characters). */\nexport function safeHeaders(headers: unknown): Record<string, string> {\n const out: Record<string, string> = {};\n const add = (name: string, value: unknown) => {\n if (Object.keys(out).length >= 50 || value === undefined || value === null || SENSITIVE_HEADER.test(name)) return;\n out[name.toLowerCase()] = (Array.isArray(value) ? value.join(\", \") : String(value)).slice(0, 1000);\n };\n try {\n const h = headers as { forEach?: (cb: (value: string, name: string) => void) => void } | null;\n if (h && typeof h.forEach === \"function\" && !Array.isArray(h)) h.forEach((value, name) => add(name, value));\n else if (h && typeof h === \"object\") for (const [name, value] of Object.entries(h)) add(name, value);\n } catch {\n // unreadable headers: report without them\n }\n return out;\n}\n\ntype WaitUntil = (promise: Promise<unknown>) => void;\n\n/** The platform's `waitUntil` for this request: Cloudflare's `platform.context`, else Vercel's request context. */\nfunction waitUntilOf(event: RequestEventLike | undefined): WaitUntil | undefined {\n try {\n const p = event?.platform as { context?: { waitUntil?: unknown }; waitUntil?: unknown } | undefined;\n const own = p?.context?.waitUntil ?? p?.waitUntil;\n if (typeof own === \"function\") return (promise) => (own as WaitUntil).call(p?.context ?? p, promise);\n const g = globalThis as Record<symbol, { get?: () => { waitUntil?: WaitUntil } | undefined } | undefined>;\n const vercel = g[Symbol.for(\"@vercel/request-context\")]?.get?.()?.waitUntil;\n if (typeof vercel === \"function\") return vercel;\n } catch {\n // no request context\n }\n return undefined;\n}\n\n/** Flushes in the background, bounded by `flushTimeoutMs`; the promise never rejects. */\nfunction flushSoon(client: GuidelyServerErrorClient, options: ServerErrorOptions, event: RequestEventLike | undefined): Promise<void> | undefined {\n const timeout = options.flushTimeoutMs ?? 2000;\n if (typeof client.flush !== \"function\" || timeout <= 0) return undefined;\n let timer: ReturnType<typeof setTimeout> | undefined;\n const p = Promise.race([\n Promise.resolve()\n .then(() => client.flush!())\n .catch(() => {}),\n new Promise<void>((resolve) => {\n timer = setTimeout(resolve, timeout);\n (timer as { unref?: () => void }).unref?.();\n }),\n ]).finally(() => clearTimeout(timer));\n const waitUntil = waitUntilOf(event);\n if (waitUntil) {\n try {\n waitUntil(p);\n } catch {\n // the request already ended\n }\n }\n return p;\n}\n\n/** The parts of SvelteKit's `RequestEvent` capture reads. */\nexport interface RequestEventLike {\n request?: { method?: string; url?: string; headers?: unknown };\n route?: { id?: string | null };\n url?: URL | { href?: string; pathname?: string };\n platform?: unknown;\n}\n\n/** `handleError`'s input in `hooks.server.ts`. */\nexport interface ServerHandleErrorInput {\n error: unknown;\n event: RequestEventLike;\n status: number;\n message: string;\n}\n\nconst transactionOf = (event: RequestEventLike | undefined): string | undefined => {\n const route = event?.route?.id;\n if (!route) return undefined;\n const method = typeof event?.request?.method === \"string\" ? event.request.method.toUpperCase() : \"\";\n return `${method ? `${method} ` : \"\"}${route}`.slice(0, 200);\n};\n\n/**\n * Reports one server error (once per error object) and starts a bounded background flush.\n * Returns the event id (\"\" when nothing was reported) and the flush.\n */\nexport function captureServerError(error: unknown, input: { event?: RequestEventLike; contexts?: Record<string, Record<string, unknown>>; tags?: Record<string, string>; mechanism: string }, options: ServerErrorOptions = {}): { eventId: string; flushed?: Promise<void> } {\n try {\n const reg = registry();\n const client = options.client ?? reg.client;\n if (!client) {\n if (!reg.warned) {\n reg.warned = true;\n console.warn(\"[Guidely] A server error was not reported: no server client. Call setGuidelyServerClient(new Guidely({ secretKey, environment })) in hooks.server.ts.\");\n }\n return { eventId: \"\" };\n }\n if (error && typeof error === \"object\") {\n if (reg.reported.has(error)) return { eventId: \"\" };\n reg.reported.add(error);\n }\n const req = input.event?.request;\n const ctx: Record<string, unknown> = {\n level: \"error\",\n ...(input.tags ? { tags: input.tags } : {}),\n ...(input.contexts ? { contexts: input.contexts } : {}),\n ...(req && typeof req === \"object\" ? { request: { method: typeof req.method === \"string\" ? req.method.toUpperCase() : undefined, url: typeof req.url === \"string\" ? req.url : undefined, headers: safeHeaders(req.headers) } } : {}),\n mechanism: { type: input.mechanism, handled: false },\n };\n const transaction = transactionOf(input.event);\n const run = () => client.captureException(error, ctx);\n const id = transaction && typeof client.withScope === \"function\"\n ? client.withScope((scope) => {\n scope.setTransactionName?.(transaction);\n return run();\n })\n : run();\n return { eventId: typeof id === \"string\" ? id : \"\", flushed: flushSoon(client, options, input.event) };\n } catch {\n return { eventId: \"\" };\n }\n}\n\n/**\n * `handleError` for `hooks.server.ts` (Sentry's `handleErrorWithSentry`): reports the error with\n * the request (credentials removed) and `contexts.sveltekit` (`routeId`, `status`, `message`), then\n * calls your `handleError` and returns what it returns. 404s are not reported unless\n * `captureNotFound`. Without a `handleError` of your own the error is logged, as SvelteKit does.\n *\n * ```ts\n * // src/hooks.server.ts\n * import { Guidely } from \"@guidelytech/node\";\n * import { handleErrorWithGuidely, setGuidelyServerClient } from \"@guidelytech/sveltekit/server\";\n * setGuidelyServerClient(new Guidely({ secretKey: GUIDELY_SECRET_KEY, environment: \"production\" }));\n * export const handleError = handleErrorWithGuidely(({ error, event, status, message }) => ({ message: \"Something went wrong\" }));\n * ```\n */\nexport function handleErrorWithGuidely<R = void>(handleError?: (input: ServerHandleErrorInput) => R | Promise<R>, options: ServerErrorOptions = {}): (input: ServerHandleErrorInput) => Promise<R | undefined> {\n return async (input) => {\n try {\n if (input?.status !== 404 || options.captureNotFound) {\n const routeId = input?.event?.route?.id ?? null;\n const { flushed } = captureServerError(input?.error, { event: input?.event, tags: routeId ? { routeId } : undefined, contexts: { sveltekit: { routeId, status: input?.status, message: input?.message } }, mechanism: \"sveltekit.handleError\" }, options);\n if (options.waitForFlush && flushed) await flushed;\n }\n } catch {\n // never break the error page\n }\n if (handleError) return handleError(input);\n console.error(input?.error);\n return undefined;\n };\n}\n\n/**\n * A server `handle` for `sequence(guidelyHandle(), …)`: runs each request in its own scope of the\n * server client when it has scopes (`@guidelytech/node` does: breadcrumbs, `setTag` and `setUser`\n * calls made while handling the request stay with its errors), named `GET /blog/[slug]`.\n */\nexport function guidelyHandle(options: Pick<ServerErrorOptions, \"client\"> = {}): <E extends RequestEventLike, T>(input: { event: E; resolve: (event: E) => T }) => T {\n return ({ event, resolve }) => {\n let client: GuidelyServerErrorClient | null = null;\n try {\n client = options.client ?? registry().client;\n } catch {\n client = null;\n }\n if (!client || typeof client.withScope !== \"function\") return resolve(event);\n let entered = false;\n try {\n return client.withScope((scope) => {\n entered = true;\n try {\n scope.setTransactionName?.(transactionOf(event));\n } catch {\n // a scope without names\n }\n return resolve(event);\n });\n } catch (error) {\n // A failure inside the scope machinery must not swallow the request: resolve it plainly.\n if (!entered) return resolve(event);\n throw error;\n }\n };\n}\n"],"mappings":"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","names":["registry"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@guidelytech/sveltekit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"description": "SvelteKit helpers for Guidely",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -25,15 +25,15 @@
|
|
|
25
25
|
"README.md"
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@guidelytech/
|
|
29
|
-
"@guidelytech/
|
|
30
|
-
"@guidelytech/
|
|
28
|
+
"@guidelytech/sdk-core": "0.17.0",
|
|
29
|
+
"@guidelytech/flags": "0.17.0",
|
|
30
|
+
"@guidelytech/svelte": "0.17.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"tsup": "^8.5.1",
|
|
34
34
|
"typescript": "^5.9.3",
|
|
35
|
-
"vitest": "^
|
|
36
|
-
"happy-dom": "^
|
|
35
|
+
"vitest": "^4.1.12",
|
|
36
|
+
"happy-dom": "^21.0.4",
|
|
37
37
|
"eslint": "^9.39.5",
|
|
38
38
|
"@eslint/js": "^9.39.5",
|
|
39
39
|
"typescript-eslint": "^8.46.0",
|