@urbicon-ui/sveltekit-utils 6.1.4
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 +117 -0
- package/dist/cron.d.ts +18 -0
- package/dist/cron.js +34 -0
- package/dist/cron.test.d.ts +1 -0
- package/dist/cron.test.js +137 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/url.svelte.d.ts +21 -0
- package/dist/url.svelte.js +84 -0
- package/package.json +77 -0
package/README.md
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# @urbicon-ui/sveltekit-utils
|
|
2
|
+
|
|
3
|
+
Small, focused SvelteKit helpers that Urbicon apps share. Zero runtime dependencies.
|
|
4
|
+
|
|
5
|
+
Currently shipping:
|
|
6
|
+
|
|
7
|
+
- **URL-state runes** — reactive `useUrlParam` / `useUrlArrayParam` that keep component state in sync with `?query=` parameters
|
|
8
|
+
- **Cron runner** — interval-based background fetcher for scheduled server endpoints
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
This package ships inside the Urbicon UI monorepo. Install from repo root:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
bun install
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Peer dependencies: `svelte` (^5), `@sveltejs/kit`.
|
|
19
|
+
|
|
20
|
+
## URL State (`url.svelte`)
|
|
21
|
+
|
|
22
|
+
Bind a typed, reactive value to a URL search param. When the value changes, the URL is updated (and vice versa) without a full navigation.
|
|
23
|
+
|
|
24
|
+
```svelte
|
|
25
|
+
<script lang="ts">
|
|
26
|
+
import { useUrlParam, useUrlArrayParam } from '@urbicon-ui/sveltekit-utils/url.svelte';
|
|
27
|
+
|
|
28
|
+
// Single string param, typed
|
|
29
|
+
const [page, setPage] = useUrlParam<number>('page', {
|
|
30
|
+
parse: (sp) => Number(sp.get('page') ?? '1'),
|
|
31
|
+
serialize: (v) => new URLSearchParams({ page: String(v) }),
|
|
32
|
+
initial: 1
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Repeated-key array param: ?tag=a&tag=b
|
|
36
|
+
const [tags, setTags] = useUrlArrayParam('tag', { initial: [], strategy: 'repeat' });
|
|
37
|
+
|
|
38
|
+
// CSV array param: ?tag=a,b
|
|
39
|
+
const [categories, setCategories] = useUrlArrayParam('cat', { initial: [], strategy: 'csv' });
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
<button onclick={() => setPage(page() + 1)}>Next — current: {page()}</button>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Low-level escape hatch if you prefer to update multiple params at once:
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
import { updateUrlSearchParams } from '@urbicon-ui/sveltekit-utils/url.svelte';
|
|
49
|
+
|
|
50
|
+
updateUrlSearchParams({ page: '1', tag: ['a', 'b'] }, { replaceState: true });
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**Design notes**
|
|
54
|
+
|
|
55
|
+
- URL updates use `goto()` with `replaceState: true`, `noScroll: true`, `keepFocus: true` — suited for filter/pagination UIs, not full page transitions.
|
|
56
|
+
- `useUrlParam` returns getters (not Svelte stores) so consumers can read the value lazily inside `$derived`/`$effect`.
|
|
57
|
+
|
|
58
|
+
## Cron Runner (`cron`)
|
|
59
|
+
|
|
60
|
+
Fire HTTP requests against SvelteKit server endpoints on an interval. Pair with a shared-secret header so endpoints can authenticate scheduled calls.
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
// src/lib/server/cron.ts
|
|
64
|
+
import { createCronRunner } from '@urbicon-ui/sveltekit-utils/cron';
|
|
65
|
+
import { env } from '$env/dynamic/private';
|
|
66
|
+
|
|
67
|
+
export const cron = createCronRunner({
|
|
68
|
+
secret: env.CRON_SECRET,
|
|
69
|
+
baseUrl: env.BASE_URL,
|
|
70
|
+
jobs: [
|
|
71
|
+
{ path: '/api/cron/send-digest', intervalSeconds: 3600 },
|
|
72
|
+
{ path: '/api/cron/cleanup', intervalSeconds: 900, method: 'POST' }
|
|
73
|
+
],
|
|
74
|
+
onError: (job, err) => console.error(`Cron ${job.path} failed`, err)
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
cron.start();
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Receive the call and verify the secret inside your endpoint:
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
// src/routes/api/cron/send-digest/+server.ts
|
|
84
|
+
import { env } from '$env/dynamic/private';
|
|
85
|
+
|
|
86
|
+
export const POST = async ({ request }) => {
|
|
87
|
+
if (request.headers.get('x-cron-secret') !== env.CRON_SECRET) {
|
|
88
|
+
return new Response('Forbidden', { status: 403 });
|
|
89
|
+
}
|
|
90
|
+
await sendDigest();
|
|
91
|
+
return new Response('ok');
|
|
92
|
+
};
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Design notes**
|
|
96
|
+
|
|
97
|
+
- Simple `setInterval`-based scheduler. No drift compensation, no distributed locking, no exponential backoff — intended for single-process SvelteKit deployments. For scale-out scenarios use a real scheduler (e.g. BullMQ) and point it at the same HTTP endpoints.
|
|
98
|
+
- Header name defaults to `x-cron-secret`; override via `secretHeader`.
|
|
99
|
+
|
|
100
|
+
## Exports
|
|
101
|
+
|
|
102
|
+
| Subpath | Contents |
|
|
103
|
+
| -------------- | ----------------------------------------------------------------------------------- |
|
|
104
|
+
| `.` | Barrel of both modules |
|
|
105
|
+
| `./url.svelte` | `useUrlParam`, `useUrlArrayParam`, `createUrlParam`, `updateUrlSearchParams`, types |
|
|
106
|
+
| `./cron` | `createCronRunner`, `CronJob`, `CronRunnerConfig`, `CronRunner` |
|
|
107
|
+
|
|
108
|
+
## Development
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
bun --filter='@urbicon-ui/sveltekit-utils' run build # svelte-package
|
|
112
|
+
bun --filter='@urbicon-ui/sveltekit-utils' run check # svelte-check
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Scope & Roadmap
|
|
116
|
+
|
|
117
|
+
Candidate additions under consideration: form-helper runes, layout-runes, shared load-helpers.
|
package/dist/cron.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface CronJob {
|
|
2
|
+
path: string;
|
|
3
|
+
intervalSeconds: number;
|
|
4
|
+
method?: 'GET' | 'POST';
|
|
5
|
+
}
|
|
6
|
+
export interface CronRunnerConfig {
|
|
7
|
+
secret: string;
|
|
8
|
+
secretHeader?: string;
|
|
9
|
+
baseUrl?: string;
|
|
10
|
+
jobs: CronJob[];
|
|
11
|
+
onError?: (job: CronJob, error: Error) => void;
|
|
12
|
+
}
|
|
13
|
+
export interface CronRunner {
|
|
14
|
+
start(): void;
|
|
15
|
+
stop(): void;
|
|
16
|
+
isRunning(): boolean;
|
|
17
|
+
}
|
|
18
|
+
export declare function createCronRunner(config: CronRunnerConfig): CronRunner;
|
package/dist/cron.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export function createCronRunner(config) {
|
|
2
|
+
const timers = [];
|
|
3
|
+
let running = false;
|
|
4
|
+
return {
|
|
5
|
+
start() {
|
|
6
|
+
if (running)
|
|
7
|
+
return;
|
|
8
|
+
running = true;
|
|
9
|
+
for (const job of config.jobs) {
|
|
10
|
+
const timer = setInterval(async () => {
|
|
11
|
+
try {
|
|
12
|
+
const base = config.baseUrl ?? 'http://localhost:3000';
|
|
13
|
+
await fetch(`${base}${job.path}`, {
|
|
14
|
+
method: job.method ?? 'POST',
|
|
15
|
+
headers: { [config.secretHeader ?? 'x-cron-secret']: config.secret }
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
config.onError?.(job, err);
|
|
20
|
+
}
|
|
21
|
+
}, job.intervalSeconds * 1000);
|
|
22
|
+
timers.push(timer);
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
stop() {
|
|
26
|
+
running = false;
|
|
27
|
+
timers.forEach(clearInterval);
|
|
28
|
+
timers.length = 0;
|
|
29
|
+
},
|
|
30
|
+
isRunning() {
|
|
31
|
+
return running;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { createCronRunner } from './cron';
|
|
3
|
+
describe('createCronRunner', () => {
|
|
4
|
+
beforeEach(() => {
|
|
5
|
+
vi.useFakeTimers();
|
|
6
|
+
vi.stubGlobal('fetch', vi.fn().mockResolvedValue(new Response(null, { status: 200 })));
|
|
7
|
+
});
|
|
8
|
+
afterEach(() => {
|
|
9
|
+
vi.useRealTimers();
|
|
10
|
+
vi.restoreAllMocks();
|
|
11
|
+
});
|
|
12
|
+
it('should not be running initially', () => {
|
|
13
|
+
const runner = createCronRunner({
|
|
14
|
+
secret: 'test-secret',
|
|
15
|
+
jobs: [{ path: '/api/test', intervalSeconds: 60 }]
|
|
16
|
+
});
|
|
17
|
+
expect(runner.isRunning()).toBe(false);
|
|
18
|
+
});
|
|
19
|
+
it('should be running after start()', () => {
|
|
20
|
+
const runner = createCronRunner({
|
|
21
|
+
secret: 'test-secret',
|
|
22
|
+
jobs: [{ path: '/api/test', intervalSeconds: 60 }]
|
|
23
|
+
});
|
|
24
|
+
runner.start();
|
|
25
|
+
expect(runner.isRunning()).toBe(true);
|
|
26
|
+
runner.stop();
|
|
27
|
+
});
|
|
28
|
+
it('should not be running after stop()', () => {
|
|
29
|
+
const runner = createCronRunner({
|
|
30
|
+
secret: 'test-secret',
|
|
31
|
+
jobs: [{ path: '/api/test', intervalSeconds: 60 }]
|
|
32
|
+
});
|
|
33
|
+
runner.start();
|
|
34
|
+
runner.stop();
|
|
35
|
+
expect(runner.isRunning()).toBe(false);
|
|
36
|
+
});
|
|
37
|
+
it('should not start twice', () => {
|
|
38
|
+
const runner = createCronRunner({
|
|
39
|
+
secret: 'test-secret',
|
|
40
|
+
jobs: [{ path: '/api/test', intervalSeconds: 10 }]
|
|
41
|
+
});
|
|
42
|
+
runner.start();
|
|
43
|
+
runner.start();
|
|
44
|
+
vi.advanceTimersByTime(10_000);
|
|
45
|
+
expect(fetch).toHaveBeenCalledTimes(1);
|
|
46
|
+
runner.stop();
|
|
47
|
+
});
|
|
48
|
+
it('should call fetch for each job at the correct interval', async () => {
|
|
49
|
+
const runner = createCronRunner({
|
|
50
|
+
secret: 'my-secret',
|
|
51
|
+
baseUrl: 'http://localhost:5000',
|
|
52
|
+
jobs: [
|
|
53
|
+
{ path: '/api/job-a', intervalSeconds: 10 },
|
|
54
|
+
{ path: '/api/job-b', intervalSeconds: 20, method: 'GET' }
|
|
55
|
+
]
|
|
56
|
+
});
|
|
57
|
+
runner.start();
|
|
58
|
+
// At 10s: job-a fires
|
|
59
|
+
await vi.advanceTimersByTimeAsync(10_000);
|
|
60
|
+
expect(fetch).toHaveBeenCalledTimes(1);
|
|
61
|
+
expect(fetch).toHaveBeenCalledWith('http://localhost:5000/api/job-a', {
|
|
62
|
+
method: 'POST',
|
|
63
|
+
headers: { 'x-cron-secret': 'my-secret' }
|
|
64
|
+
});
|
|
65
|
+
// At 20s: job-a fires again + job-b fires for the first time
|
|
66
|
+
await vi.advanceTimersByTimeAsync(10_000);
|
|
67
|
+
expect(fetch).toHaveBeenCalledTimes(3);
|
|
68
|
+
expect(fetch).toHaveBeenCalledWith('http://localhost:5000/api/job-b', {
|
|
69
|
+
method: 'GET',
|
|
70
|
+
headers: { 'x-cron-secret': 'my-secret' }
|
|
71
|
+
});
|
|
72
|
+
runner.stop();
|
|
73
|
+
});
|
|
74
|
+
it('should use default baseUrl when not provided', async () => {
|
|
75
|
+
const runner = createCronRunner({
|
|
76
|
+
secret: 's',
|
|
77
|
+
jobs: [{ path: '/api/ping', intervalSeconds: 5 }]
|
|
78
|
+
});
|
|
79
|
+
runner.start();
|
|
80
|
+
await vi.advanceTimersByTimeAsync(5_000);
|
|
81
|
+
expect(fetch).toHaveBeenCalledWith('http://localhost:3000/api/ping', expect.any(Object));
|
|
82
|
+
runner.stop();
|
|
83
|
+
});
|
|
84
|
+
it('should use custom secretHeader', async () => {
|
|
85
|
+
const runner = createCronRunner({
|
|
86
|
+
secret: 'abc',
|
|
87
|
+
secretHeader: 'x-internal-key',
|
|
88
|
+
jobs: [{ path: '/api/test', intervalSeconds: 5 }]
|
|
89
|
+
});
|
|
90
|
+
runner.start();
|
|
91
|
+
await vi.advanceTimersByTimeAsync(5_000);
|
|
92
|
+
expect(fetch).toHaveBeenCalledWith(expect.any(String), {
|
|
93
|
+
method: 'POST',
|
|
94
|
+
headers: { 'x-internal-key': 'abc' }
|
|
95
|
+
});
|
|
96
|
+
runner.stop();
|
|
97
|
+
});
|
|
98
|
+
it('should call onError when fetch throws', async () => {
|
|
99
|
+
const error = new Error('Network error');
|
|
100
|
+
vi.mocked(fetch).mockRejectedValueOnce(error);
|
|
101
|
+
const onError = vi.fn();
|
|
102
|
+
const job = { path: '/api/fail', intervalSeconds: 5 };
|
|
103
|
+
const runner = createCronRunner({
|
|
104
|
+
secret: 's',
|
|
105
|
+
jobs: [job],
|
|
106
|
+
onError
|
|
107
|
+
});
|
|
108
|
+
runner.start();
|
|
109
|
+
await vi.advanceTimersByTimeAsync(5_000);
|
|
110
|
+
expect(onError).toHaveBeenCalledWith(job, error);
|
|
111
|
+
runner.stop();
|
|
112
|
+
});
|
|
113
|
+
it('should not call onError when fetch succeeds', async () => {
|
|
114
|
+
const onError = vi.fn();
|
|
115
|
+
const runner = createCronRunner({
|
|
116
|
+
secret: 's',
|
|
117
|
+
jobs: [{ path: '/api/ok', intervalSeconds: 5 }],
|
|
118
|
+
onError
|
|
119
|
+
});
|
|
120
|
+
runner.start();
|
|
121
|
+
await vi.advanceTimersByTimeAsync(5_000);
|
|
122
|
+
expect(onError).not.toHaveBeenCalled();
|
|
123
|
+
runner.stop();
|
|
124
|
+
});
|
|
125
|
+
it('should stop all timers and not fire after stop()', async () => {
|
|
126
|
+
const runner = createCronRunner({
|
|
127
|
+
secret: 's',
|
|
128
|
+
jobs: [{ path: '/api/test', intervalSeconds: 5 }]
|
|
129
|
+
});
|
|
130
|
+
runner.start();
|
|
131
|
+
await vi.advanceTimersByTimeAsync(5_000);
|
|
132
|
+
expect(fetch).toHaveBeenCalledTimes(1);
|
|
133
|
+
runner.stop();
|
|
134
|
+
await vi.advanceTimersByTimeAsync(15_000);
|
|
135
|
+
expect(fetch).toHaveBeenCalledTimes(1);
|
|
136
|
+
});
|
|
137
|
+
});
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type UrlArrayStrategy = 'repeat' | 'csv';
|
|
2
|
+
export type UrlParamOptions<T> = {
|
|
3
|
+
parse: (sp: URLSearchParams) => T | null | undefined;
|
|
4
|
+
serialize: (value: T) => URLSearchParams;
|
|
5
|
+
initial: T;
|
|
6
|
+
replaceState?: boolean;
|
|
7
|
+
};
|
|
8
|
+
export declare function updateUrlSearchParams(next: URLSearchParams | Record<string, string | string[]>, opts?: {
|
|
9
|
+
replaceState?: boolean;
|
|
10
|
+
keepPath?: boolean;
|
|
11
|
+
}): void;
|
|
12
|
+
export declare function createUrlParam<T>(_key: string, options: UrlParamOptions<T>): {
|
|
13
|
+
readonly get: (sp: URLSearchParams) => T;
|
|
14
|
+
readonly set: (next: T) => void;
|
|
15
|
+
};
|
|
16
|
+
export declare function useUrlParam<T>(key: string, options: UrlParamOptions<T>): readonly [() => T, (next: T) => void];
|
|
17
|
+
export declare function useUrlArrayParam(key: string, opts: {
|
|
18
|
+
initial: string[];
|
|
19
|
+
strategy?: UrlArrayStrategy;
|
|
20
|
+
delimiter?: string;
|
|
21
|
+
}): readonly [() => string[], (next: string[]) => void];
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { goto } from '$app/navigation';
|
|
2
|
+
import { page } from '$app/state';
|
|
3
|
+
// Local imperative use of URLSearchParams — not reactive state — so the
|
|
4
|
+
// SvelteURLSearchParams wrapper is unnecessary here. Likewise for `goto`:
|
|
5
|
+
// we pass constructed relative paths, not resolved route ids; callers of
|
|
6
|
+
// this helper are free to call `resolve()` at their composition point.
|
|
7
|
+
/* eslint-disable svelte/prefer-svelte-reactivity, svelte/no-navigation-without-resolve */
|
|
8
|
+
export function updateUrlSearchParams(next, opts) {
|
|
9
|
+
const base = new URLSearchParams(page.url.searchParams);
|
|
10
|
+
if (next instanceof URLSearchParams) {
|
|
11
|
+
for (const [k] of next)
|
|
12
|
+
base.delete(k);
|
|
13
|
+
for (const [k, v] of next)
|
|
14
|
+
base.append(k, v);
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
for (const [key, val] of Object.entries(next)) {
|
|
18
|
+
base.delete(key);
|
|
19
|
+
if (Array.isArray(val)) {
|
|
20
|
+
for (const v of val)
|
|
21
|
+
base.append(key, v);
|
|
22
|
+
}
|
|
23
|
+
else if (val != null) {
|
|
24
|
+
base.set(key, String(val));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const q = base.toString();
|
|
29
|
+
const path = opts?.keepPath ? page.url.pathname : '/';
|
|
30
|
+
goto(q ? `${path}?${q}` : path, {
|
|
31
|
+
replaceState: opts?.replaceState ?? true,
|
|
32
|
+
noScroll: true,
|
|
33
|
+
keepFocus: true
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
// `key` is unused here — `options.parse`/`options.serialize` already close over
|
|
37
|
+
// it (see useUrlArrayParam) — but kept for signature parity with useUrlParam.
|
|
38
|
+
export function createUrlParam(_key, options) {
|
|
39
|
+
const get = (sp) => options.parse(sp) ?? options.initial;
|
|
40
|
+
function setValue(next) {
|
|
41
|
+
const current = new URLSearchParams(page.url.searchParams);
|
|
42
|
+
const nextSp = options.serialize(next);
|
|
43
|
+
for (const [k] of nextSp)
|
|
44
|
+
current.delete(k);
|
|
45
|
+
for (const [k, v] of nextSp)
|
|
46
|
+
current.append(k, v);
|
|
47
|
+
const q = current.toString();
|
|
48
|
+
goto(q ? `?${q}` : '/', {
|
|
49
|
+
replaceState: options.replaceState ?? true,
|
|
50
|
+
noScroll: true,
|
|
51
|
+
keepFocus: true
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
return { get, set: setValue };
|
|
55
|
+
}
|
|
56
|
+
export function useUrlParam(key, options) {
|
|
57
|
+
const { get, set } = createUrlParam(key, options);
|
|
58
|
+
const getBound = () => get(page.url.searchParams);
|
|
59
|
+
return [getBound, set];
|
|
60
|
+
}
|
|
61
|
+
export function useUrlArrayParam(key, opts) {
|
|
62
|
+
const strategy = opts.strategy ?? 'repeat';
|
|
63
|
+
const delimiter = opts.delimiter ?? ',';
|
|
64
|
+
const parse = (sp) => {
|
|
65
|
+
if (strategy === 'repeat')
|
|
66
|
+
return sp.getAll(key);
|
|
67
|
+
const raw = sp.get(key);
|
|
68
|
+
return raw ? raw.split(delimiter).filter(Boolean) : [];
|
|
69
|
+
};
|
|
70
|
+
const serialize = (values) => {
|
|
71
|
+
const sp = new URLSearchParams();
|
|
72
|
+
if (strategy === 'repeat') {
|
|
73
|
+
for (const v of values)
|
|
74
|
+
sp.append(key, v);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
if (values.length)
|
|
78
|
+
sp.set(key, values.join(delimiter));
|
|
79
|
+
}
|
|
80
|
+
return sp;
|
|
81
|
+
};
|
|
82
|
+
return useUrlParam(key, { parse, serialize, initial: opts.initial });
|
|
83
|
+
}
|
|
84
|
+
/* eslint-enable svelte/prefer-svelte-reactivity, svelte/no-navigation-without-resolve */
|
package/package.json
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@urbicon-ui/sveltekit-utils",
|
|
3
|
+
"version": "6.1.4",
|
|
4
|
+
"description": "SvelteKit helper utilities — createCronRunner and URL-state runes",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://codeberg.org/urbicon/ui.git",
|
|
9
|
+
"directory": "packages/sveltekit-utils"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://ui.urbicon.de",
|
|
12
|
+
"bugs": "https://codeberg.org/urbicon/ui/issues",
|
|
13
|
+
"keywords": [
|
|
14
|
+
"svelte",
|
|
15
|
+
"sveltekit",
|
|
16
|
+
"utilities",
|
|
17
|
+
"cron",
|
|
18
|
+
"url-state"
|
|
19
|
+
],
|
|
20
|
+
"type": "module",
|
|
21
|
+
"private": false,
|
|
22
|
+
"sideEffects": false,
|
|
23
|
+
"svelte": "./dist/index.js",
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"main": "./dist/index.js",
|
|
26
|
+
"module": "./dist/index.js",
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"svelte": "./dist/index.js",
|
|
31
|
+
"import": "./dist/index.js",
|
|
32
|
+
"default": "./dist/index.js"
|
|
33
|
+
},
|
|
34
|
+
"./url.svelte": {
|
|
35
|
+
"types": "./dist/url.svelte.d.ts",
|
|
36
|
+
"svelte": "./dist/url.svelte.js",
|
|
37
|
+
"import": "./dist/url.svelte.js",
|
|
38
|
+
"default": "./dist/url.svelte.js"
|
|
39
|
+
},
|
|
40
|
+
"./cron": {
|
|
41
|
+
"types": "./dist/cron.d.ts",
|
|
42
|
+
"import": "./dist/cron.js"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"files": [
|
|
46
|
+
"dist"
|
|
47
|
+
],
|
|
48
|
+
"scripts": {
|
|
49
|
+
"dev": "svelte-package --watch",
|
|
50
|
+
"build": "svelte-kit sync && svelte-package",
|
|
51
|
+
"clean": "rm -rf dist .svelte-kit",
|
|
52
|
+
"clean-all": "bun --bun run clean && rm -rf node_modules",
|
|
53
|
+
"package": "svelte-kit sync && svelte-package",
|
|
54
|
+
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
|
55
|
+
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
|
56
|
+
"format": "biome format --write . && prettier --write \"**/*.svelte\"",
|
|
57
|
+
"lint": "biome check . && svelte-check --tsconfig ./tsconfig.json",
|
|
58
|
+
"test": "vitest run"
|
|
59
|
+
},
|
|
60
|
+
"peerDependencies": {
|
|
61
|
+
"svelte": "^5.56.3",
|
|
62
|
+
"@sveltejs/kit": "^2.65.2"
|
|
63
|
+
},
|
|
64
|
+
"devDependencies": {
|
|
65
|
+
"@sveltejs/kit": "^2.65.2",
|
|
66
|
+
"@sveltejs/package": "^2.5.8",
|
|
67
|
+
"@sveltejs/vite-plugin-svelte": "^7.0.0",
|
|
68
|
+
"prettier": "^3.8.4",
|
|
69
|
+
"prettier-plugin-svelte": "^4.1.1",
|
|
70
|
+
"prettier-plugin-tailwindcss": "^0.8.0",
|
|
71
|
+
"svelte": "^5.56.3",
|
|
72
|
+
"svelte-check": "^4.6.0",
|
|
73
|
+
"typescript": "^6.0.3",
|
|
74
|
+
"vite": "^8.0.16",
|
|
75
|
+
"vitest": "^4.1.9"
|
|
76
|
+
}
|
|
77
|
+
}
|