@urbicon-ui/sveltekit-utils 6.22.0 → 6.23.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/dist/cron.d.ts CHANGED
@@ -1,18 +1,90 @@
1
+ /** One scheduled job: an endpoint to hit and how often. */
1
2
  export interface CronJob {
3
+ /** Path (appended to {@link CronRunnerConfig.baseUrl}) to fetch on each tick. */
2
4
  path: string;
5
+ /**
6
+ * Interval between fires, in seconds. The first fire happens *after* one
7
+ * interval — there is no leading call at `start()`.
8
+ */
3
9
  intervalSeconds: number;
10
+ /**
11
+ * HTTP method for the request.
12
+ * @default 'POST'
13
+ */
4
14
  method?: 'GET' | 'POST';
5
15
  }
16
+ /** Configuration for {@link createCronRunner}. */
6
17
  export interface CronRunnerConfig {
18
+ /**
19
+ * Shared secret sent on every request in the {@link secretHeader} header, so
20
+ * the target endpoint can distinguish a scheduled call from a public one.
21
+ * Keep it in a private env var; the endpoint compares against the same value.
22
+ */
7
23
  secret: string;
24
+ /**
25
+ * Header name carrying the {@link secret}.
26
+ * @default 'x-cron-secret'
27
+ */
8
28
  secretHeader?: string;
29
+ /**
30
+ * Origin the job paths are resolved against (e.g. `https://app.example.com`).
31
+ * @default 'http://localhost:3000'
32
+ */
9
33
  baseUrl?: string;
34
+ /** Jobs to schedule; each runs on its own independent interval. */
10
35
  jobs: CronJob[];
36
+ /**
37
+ * Called when a job's `fetch` **rejects** (network error, DNS failure, abort).
38
+ * A non-2xx HTTP *response* does not reject `fetch`, so it does **not** reach
39
+ * this hook — the runner is fire-and-forget and never inspects the response.
40
+ * Have the endpoint report its own failures if you need per-run status.
41
+ */
11
42
  onError?: (job: CronJob, error: Error) => void;
12
43
  }
44
+ /** Handle returned by {@link createCronRunner}. */
13
45
  export interface CronRunner {
46
+ /**
47
+ * Arm every job's interval timer. Idempotent — calling `start()` while
48
+ * already running is a no-op (does not double-schedule).
49
+ */
14
50
  start(): void;
51
+ /**
52
+ * Clear every timer so nothing fires again, and flip {@link isRunning} to
53
+ * `false`. Call this on shutdown / HMR teardown to avoid leaked intervals.
54
+ */
15
55
  stop(): void;
56
+ /** Whether the runner is currently armed (between `start()` and `stop()`). */
16
57
  isRunning(): boolean;
17
58
  }
59
+ /**
60
+ * Create a background runner that fires HTTP requests at SvelteKit server
61
+ * endpoints on a fixed interval — a minimal in-process cron for scheduled work
62
+ * (digests, cleanup, cache warming).
63
+ *
64
+ * Deliberately simple: one `setInterval` per job, no drift compensation, no
65
+ * distributed locking, no retry/backoff. Fits a **single-process** deployment;
66
+ * for scale-out point a real scheduler (BullMQ, a platform cron) at the same
67
+ * endpoints instead. The runner starts idle — call `start()` explicitly.
68
+ *
69
+ * @param config - Secret/header, base URL, and the jobs to schedule.
70
+ * @returns A {@link CronRunner} handle (`start` / `stop` / `isRunning`).
71
+ * @example
72
+ * ```typescript
73
+ * // src/lib/server/cron.ts
74
+ * import { createCronRunner } from '@urbicon-ui/sveltekit-utils/cron';
75
+ * import { env } from '$env/dynamic/private';
76
+ *
77
+ * export const cron = createCronRunner({
78
+ * secret: env.CRON_SECRET,
79
+ * baseUrl: env.BASE_URL,
80
+ * jobs: [
81
+ * { path: '/api/cron/send-digest', intervalSeconds: 3600 },
82
+ * { path: '/api/cron/cleanup', intervalSeconds: 900 }
83
+ * ],
84
+ * onError: (job, err) => console.error(`Cron ${job.path} failed`, err)
85
+ * });
86
+ *
87
+ * cron.start();
88
+ * ```
89
+ */
18
90
  export declare function createCronRunner(config: CronRunnerConfig): CronRunner;
package/dist/cron.js CHANGED
@@ -1,3 +1,34 @@
1
+ /**
2
+ * Create a background runner that fires HTTP requests at SvelteKit server
3
+ * endpoints on a fixed interval — a minimal in-process cron for scheduled work
4
+ * (digests, cleanup, cache warming).
5
+ *
6
+ * Deliberately simple: one `setInterval` per job, no drift compensation, no
7
+ * distributed locking, no retry/backoff. Fits a **single-process** deployment;
8
+ * for scale-out point a real scheduler (BullMQ, a platform cron) at the same
9
+ * endpoints instead. The runner starts idle — call `start()` explicitly.
10
+ *
11
+ * @param config - Secret/header, base URL, and the jobs to schedule.
12
+ * @returns A {@link CronRunner} handle (`start` / `stop` / `isRunning`).
13
+ * @example
14
+ * ```typescript
15
+ * // src/lib/server/cron.ts
16
+ * import { createCronRunner } from '@urbicon-ui/sveltekit-utils/cron';
17
+ * import { env } from '$env/dynamic/private';
18
+ *
19
+ * export const cron = createCronRunner({
20
+ * secret: env.CRON_SECRET,
21
+ * baseUrl: env.BASE_URL,
22
+ * jobs: [
23
+ * { path: '/api/cron/send-digest', intervalSeconds: 3600 },
24
+ * { path: '/api/cron/cleanup', intervalSeconds: 900 }
25
+ * ],
26
+ * onError: (job, err) => console.error(`Cron ${job.path} failed`, err)
27
+ * });
28
+ *
29
+ * cron.start();
30
+ * ```
31
+ */
1
32
  export function createCronRunner(config) {
2
33
  const timers = [];
3
34
  let running = false;
@@ -1,19 +1,128 @@
1
1
  import { type TableQueryParams, type TableQueryUrlOptions } from './table-query';
2
+ /**
3
+ * How {@link useUrlArrayParam} maps an array onto the URL:
4
+ * - `repeat` — one entry per key: `?tag=a&tag=b`
5
+ * - `csv` — a single delimited value: `?tag=a,b`
6
+ */
2
7
  export type UrlArrayStrategy = 'repeat' | 'csv';
8
+ /** Codec + seed for {@link useUrlParam} / {@link createUrlParam}. */
3
9
  export type UrlParamOptions<T> = {
10
+ /**
11
+ * Read the value out of the current search params. Return `null`/`undefined`
12
+ * to signal "absent" — the getter then yields {@link initial}.
13
+ */
4
14
  parse: (sp: URLSearchParams) => T | null | undefined;
15
+ /**
16
+ * Encode the value into `URLSearchParams`. The keys it produces are the ones
17
+ * the setter manages: on write they are cleared from the current URL and
18
+ * replaced by this output, leaving every other param untouched. Emit no
19
+ * entry for a key to remove it from the URL.
20
+ */
5
21
  serialize: (value: T) => URLSearchParams;
22
+ /** Value the getter returns when {@link parse} yields `null`/`undefined`. */
6
23
  initial: T;
24
+ /**
25
+ * Replace the current history entry instead of pushing a new one, so rapid
26
+ * filter/pagination edits don't flood the back button.
27
+ * @default true
28
+ */
7
29
  replaceState?: boolean;
8
30
  };
31
+ /**
32
+ * Low-level escape hatch to update several params at once via `goto` (without a
33
+ * full navigation). Starts from the current URL, applies `next`, and keeps
34
+ * every unrelated param.
35
+ *
36
+ * Merge semantics per key in `next`: the key is first cleared, then re-applied
37
+ * — a `URLSearchParams` re-appends all of its entries (repeated keys survive),
38
+ * a record `set`s a scalar, `append`s each array element, and **removes** the
39
+ * key entirely for a `null`/`undefined` value.
40
+ *
41
+ * @param next - Params to apply, as `URLSearchParams` or a plain record. A
42
+ * record value of `null`/`undefined` deletes that key.
43
+ * @param opts - `replaceState` (default `true`) — replace vs. push history.
44
+ * @example
45
+ * ```typescript
46
+ * updateUrlSearchParams({ page: '1', tag: ['a', 'b'], filter: null });
47
+ * // ?page=1&tag=a&tag=b (any prior `filter` param is dropped)
48
+ * ```
49
+ */
9
50
  export declare function updateUrlSearchParams(next: URLSearchParams | Record<string, string | string[]>, opts?: {
10
51
  replaceState?: boolean;
11
52
  }): void;
53
+ /**
54
+ * Non-reactive core of {@link useUrlParam}: builds the `get(sp)` / `set(value)`
55
+ * pair without touching the `page` rune, so `get` can be evaluated against any
56
+ * `URLSearchParams`. Prefer {@link useUrlParam} in components — this is the
57
+ * escape hatch when you need to read against a snapshot other than the live
58
+ * page URL (tests, a server `load`).
59
+ *
60
+ * `set` rewrites only the keys that `options.serialize` produces (clear +
61
+ * re-append) and preserves the rest, then navigates with `goto`
62
+ * (`replaceState`, `noScroll`, `keepFocus`).
63
+ *
64
+ * @param _key - Ignored — `options.parse`/`options.serialize` already close
65
+ * over the key (see {@link useUrlArrayParam}); kept only for signature parity
66
+ * with {@link useUrlParam}.
67
+ * @param options - Parse/serialize codec, initial value, history behaviour.
68
+ * @returns `{ get, set }` — `get(sp)` reads a value from the given params
69
+ * (falling back to `initial`), `set(value)` writes it to the URL.
70
+ */
12
71
  export declare function createUrlParam<T>(_key: string, options: UrlParamOptions<T>): {
13
72
  readonly get: (sp: URLSearchParams) => T;
14
73
  readonly set: (next: T) => void;
15
74
  };
75
+ /**
76
+ * Bind a typed value to a URL search param, reactively. The returned getter
77
+ * reads through the `page` rune, so it re-evaluates whenever the URL changes;
78
+ * the setter writes the value back via `goto` (no full navigation).
79
+ *
80
+ * SSR-safe: the getter only reads `page.url` (populated on the server), so the
81
+ * initial render reflects the incoming URL. The setter calls the client-only
82
+ * `goto` and is meant to run from event handlers/effects — never during SSR.
83
+ *
84
+ * A **getter**, not a store, is returned on purpose: call it lazily inside
85
+ * `$derived`/`$effect` and the read is tracked there.
86
+ *
87
+ * @typeParam T - The decoded value type.
88
+ * @param key - Param key (forwarded to `createUrlParam` for signature parity;
89
+ * the actual key handling lives in `options.parse`/`options.serialize`).
90
+ * @param options - Parse/serialize codec, initial value, history behaviour.
91
+ * @returns `[get, set]` — `get()` reads the live value, `set(value)` writes it.
92
+ * @example
93
+ * ```svelte
94
+ * <script lang="ts">
95
+ * import { useUrlParam } from '@urbicon-ui/sveltekit-utils/url.svelte';
96
+ *
97
+ * const [page, setPage] = useUrlParam<number>('page', {
98
+ * parse: (sp) => Number(sp.get('page') ?? '1'),
99
+ * serialize: (v) => new URLSearchParams({ page: String(v) }),
100
+ * initial: 1
101
+ * });
102
+ * </script>
103
+ *
104
+ * <button onclick={() => setPage(page() + 1)}>Next — {page()}</button>
105
+ * ```
106
+ */
16
107
  export declare function useUrlParam<T>(key: string, options: UrlParamOptions<T>): readonly [() => T, (next: T) => void];
108
+ /**
109
+ * {@link useUrlParam} specialised for a `string[]`, with the encoding handled
110
+ * for you. Reactive read + `goto`-based write, same as {@link useUrlParam}.
111
+ *
112
+ * The `csv` strategy drops empty segments on read (`?tag=` → `[]`) and writes
113
+ * no param for an empty array, so an empty selection leaves the URL clean.
114
+ *
115
+ * @param key - The param key.
116
+ * @param opts - `initial` seed, `strategy` (default `'repeat'`), and
117
+ * `delimiter` for `csv` (default `','`). See {@link UrlArrayStrategy}.
118
+ * @returns `[get, set]` — `get()` reads the current `string[]`, `set(values)`
119
+ * writes it.
120
+ * @example
121
+ * ```typescript
122
+ * const [tags, setTags] = useUrlArrayParam('tag', { initial: [] }); // ?tag=a&tag=b
123
+ * const [cats, setCats] = useUrlArrayParam('cat', { initial: [], strategy: 'csv' }); // ?cat=a,b
124
+ * ```
125
+ */
17
126
  export declare function useUrlArrayParam(key: string, opts: {
18
127
  initial: string[];
19
128
  strategy?: UrlArrayStrategy;
@@ -1,6 +1,25 @@
1
1
  import { goto } from '$app/navigation';
2
2
  import { page } from '$app/state';
3
3
  import { applyTableQueryToSearchParams, searchParamsToTableQuery } from './table-query';
4
+ /**
5
+ * Low-level escape hatch to update several params at once via `goto` (without a
6
+ * full navigation). Starts from the current URL, applies `next`, and keeps
7
+ * every unrelated param.
8
+ *
9
+ * Merge semantics per key in `next`: the key is first cleared, then re-applied
10
+ * — a `URLSearchParams` re-appends all of its entries (repeated keys survive),
11
+ * a record `set`s a scalar, `append`s each array element, and **removes** the
12
+ * key entirely for a `null`/`undefined` value.
13
+ *
14
+ * @param next - Params to apply, as `URLSearchParams` or a plain record. A
15
+ * record value of `null`/`undefined` deletes that key.
16
+ * @param opts - `replaceState` (default `true`) — replace vs. push history.
17
+ * @example
18
+ * ```typescript
19
+ * updateUrlSearchParams({ page: '1', tag: ['a', 'b'], filter: null });
20
+ * // ?page=1&tag=a&tag=b (any prior `filter` param is dropped)
21
+ * ```
22
+ */
4
23
  // Local imperative use of URLSearchParams — not reactive state — so the
5
24
  // SvelteURLSearchParams wrapper is unnecessary here. Likewise for `goto`:
6
25
  // we pass constructed relative paths, not resolved route ids; callers of
@@ -33,6 +52,24 @@ export function updateUrlSearchParams(next, opts) {
33
52
  keepFocus: true
34
53
  });
35
54
  }
55
+ /**
56
+ * Non-reactive core of {@link useUrlParam}: builds the `get(sp)` / `set(value)`
57
+ * pair without touching the `page` rune, so `get` can be evaluated against any
58
+ * `URLSearchParams`. Prefer {@link useUrlParam} in components — this is the
59
+ * escape hatch when you need to read against a snapshot other than the live
60
+ * page URL (tests, a server `load`).
61
+ *
62
+ * `set` rewrites only the keys that `options.serialize` produces (clear +
63
+ * re-append) and preserves the rest, then navigates with `goto`
64
+ * (`replaceState`, `noScroll`, `keepFocus`).
65
+ *
66
+ * @param _key - Ignored — `options.parse`/`options.serialize` already close
67
+ * over the key (see {@link useUrlArrayParam}); kept only for signature parity
68
+ * with {@link useUrlParam}.
69
+ * @param options - Parse/serialize codec, initial value, history behaviour.
70
+ * @returns `{ get, set }` — `get(sp)` reads a value from the given params
71
+ * (falling back to `initial`), `set(value)` writes it to the URL.
72
+ */
36
73
  // `key` is unused here — `options.parse`/`options.serialize` already close over
37
74
  // it (see useUrlArrayParam) — but kept for signature parity with useUrlParam.
38
75
  export function createUrlParam(_key, options) {
@@ -53,11 +90,61 @@ export function createUrlParam(_key, options) {
53
90
  }
54
91
  return { get, set: setValue };
55
92
  }
93
+ /**
94
+ * Bind a typed value to a URL search param, reactively. The returned getter
95
+ * reads through the `page` rune, so it re-evaluates whenever the URL changes;
96
+ * the setter writes the value back via `goto` (no full navigation).
97
+ *
98
+ * SSR-safe: the getter only reads `page.url` (populated on the server), so the
99
+ * initial render reflects the incoming URL. The setter calls the client-only
100
+ * `goto` and is meant to run from event handlers/effects — never during SSR.
101
+ *
102
+ * A **getter**, not a store, is returned on purpose: call it lazily inside
103
+ * `$derived`/`$effect` and the read is tracked there.
104
+ *
105
+ * @typeParam T - The decoded value type.
106
+ * @param key - Param key (forwarded to `createUrlParam` for signature parity;
107
+ * the actual key handling lives in `options.parse`/`options.serialize`).
108
+ * @param options - Parse/serialize codec, initial value, history behaviour.
109
+ * @returns `[get, set]` — `get()` reads the live value, `set(value)` writes it.
110
+ * @example
111
+ * ```svelte
112
+ * <script lang="ts">
113
+ * import { useUrlParam } from '@urbicon-ui/sveltekit-utils/url.svelte';
114
+ *
115
+ * const [page, setPage] = useUrlParam<number>('page', {
116
+ * parse: (sp) => Number(sp.get('page') ?? '1'),
117
+ * serialize: (v) => new URLSearchParams({ page: String(v) }),
118
+ * initial: 1
119
+ * });
120
+ * </script>
121
+ *
122
+ * <button onclick={() => setPage(page() + 1)}>Next — {page()}</button>
123
+ * ```
124
+ */
56
125
  export function useUrlParam(key, options) {
57
126
  const { get, set } = createUrlParam(key, options);
58
127
  const getBound = () => get(page.url.searchParams);
59
128
  return [getBound, set];
60
129
  }
130
+ /**
131
+ * {@link useUrlParam} specialised for a `string[]`, with the encoding handled
132
+ * for you. Reactive read + `goto`-based write, same as {@link useUrlParam}.
133
+ *
134
+ * The `csv` strategy drops empty segments on read (`?tag=` → `[]`) and writes
135
+ * no param for an empty array, so an empty selection leaves the URL clean.
136
+ *
137
+ * @param key - The param key.
138
+ * @param opts - `initial` seed, `strategy` (default `'repeat'`), and
139
+ * `delimiter` for `csv` (default `','`). See {@link UrlArrayStrategy}.
140
+ * @returns `[get, set]` — `get()` reads the current `string[]`, `set(values)`
141
+ * writes it.
142
+ * @example
143
+ * ```typescript
144
+ * const [tags, setTags] = useUrlArrayParam('tag', { initial: [] }); // ?tag=a&tag=b
145
+ * const [cats, setCats] = useUrlArrayParam('cat', { initial: [], strategy: 'csv' }); // ?cat=a,b
146
+ * ```
147
+ */
61
148
  export function useUrlArrayParam(key, opts) {
62
149
  const strategy = opts.strategy ?? 'repeat';
63
150
  const delimiter = opts.delimiter ?? ',';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@urbicon-ui/sveltekit-utils",
3
- "version": "6.22.0",
3
+ "version": "6.23.0",
4
4
  "description": "SvelteKit helper utilities — createCronRunner and URL-state runes",
5
5
  "license": "MIT",
6
6
  "repository": {