@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 +72 -0
- package/dist/cron.js +31 -0
- package/dist/url.svelte.d.ts +109 -0
- package/dist/url.svelte.js +87 -0
- package/package.json +1 -1
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;
|
package/dist/url.svelte.d.ts
CHANGED
|
@@ -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;
|
package/dist/url.svelte.js
CHANGED
|
@@ -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 ?? ',';
|