@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 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 import_svelte2 = require("svelte");
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, import_svelte.setGuidelyContext)(null);
135
+ (0, import_svelte2.setGuidelyContext)(null);
94
136
  const flags = options.flags || void 0;
95
- (0, import_svelte.setFlagsContext)(flags?.payload ? (0, import_svelte.createStaticFlags)({ payload: flags.payload, attributes: flags.attributes }) : null);
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, import_svelte.createGuidely)(options);
99
- (0, import_svelte.setGuidelyContext)(client);
100
- (0, import_svelte.setFlagsContext)(client);
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, import_svelte2.onDestroy)(() => {
105
- if (guidelyStoreIs(client)) (0, import_svelte.destroyGuidely)();
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
- import_svelte.guidely.subscribe((c) => current = c)();
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")
@@ -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
- guidely.subscribe((c) => current = c)();
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
- loadFlags: () => loadFlags
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 = registry();
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 ?? registry().current ?? createServerFlags();
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 ?? registry().current ?? createServerFlags();
210
+ const client = options.client ?? registry2().current ?? createServerFlags();
68
211
  if (client.sdkKey.startsWith("gly_sk_")) {
69
- const reg = registry();
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
- loadFlags
227
+ guidelyHandle,
228
+ handleErrorWithGuidely,
229
+ loadFlags,
230
+ setGuidelyServerClient
84
231
  });
85
232
  //# sourceMappingURL=server.cjs.map
@@ -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":"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","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 = registry();
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 ?? registry().current ?? createServerFlags();
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 ?? registry().current ?? createServerFlags();
180
+ const client = options.client ?? registry2().current ?? createServerFlags();
42
181
  if (client.sdkKey.startsWith("gly_sk_")) {
43
- const reg = registry();
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
- loadFlags
196
+ guidelyHandle,
197
+ handleErrorWithGuidely,
198
+ loadFlags,
199
+ setGuidelyServerClient
57
200
  };
58
201
  //# sourceMappingURL=server.js.map
@@ -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.15.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/flags": "0.15.0",
29
- "@guidelytech/svelte": "0.15.0",
30
- "@guidelytech/sdk-core": "0.15.0"
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": "^3.2.7",
36
- "happy-dom": "^17.6.3",
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",