firstly 0.11.0 → 0.12.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.
Files changed (43) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/esm/changeLog/index.d.ts +1 -0
  3. package/esm/changeLog/server/index.d.ts +2 -2
  4. package/esm/changeLog/server/index.js +2 -2
  5. package/esm/core/FF_Allow.d.ts +3 -3
  6. package/esm/core/FF_Allow.js +3 -3
  7. package/esm/core/FF_Filter.d.ts +3 -3
  8. package/esm/core/FF_Filter.js +3 -3
  9. package/esm/core/FF_Validators.d.ts +2 -2
  10. package/esm/core/FF_Validators.js +2 -2
  11. package/esm/core/index.d.ts +1 -0
  12. package/esm/core/index.js +1 -1
  13. package/esm/cron/server/index.d.ts +1 -1
  14. package/esm/cron/server/index.js +1 -1
  15. package/esm/mail/ui/WriteMail.svelte +1 -1
  16. package/esm/sqlAdmin/server/index.d.ts +2 -2
  17. package/esm/sqlAdmin/server/index.js +2 -2
  18. package/esm/sqlAdmin/ui/SqlAdmin.svelte +1 -2
  19. package/esm/sqlAdmin/ui/SqlDrift.svelte +41 -41
  20. package/esm/svelte/FF_DialogManager.svelte +4 -4
  21. package/esm/svelte/FF_PromptDefault.svelte +6 -6
  22. package/esm/svelte/class/SP.svelte.d.ts +1 -0
  23. package/esm/svelte/class/SP.svelte.js +14 -15
  24. package/esm/svelte/dialog.svelte.d.ts +1 -1
  25. package/esm/svelte/grid/FF_Grid.svelte +11 -8
  26. package/esm/svelte/grid/buildCells.js +6 -0
  27. package/esm/svelte/grid/cellTypes.d.ts +4 -0
  28. package/esm/svelte/handleError.d.ts +8 -2
  29. package/esm/svelte/handleError.js +24 -2
  30. package/esm/svelte/index.d.ts +1 -1
  31. package/esm/svelte/index.js +1 -1
  32. package/esm/svelte/query/index.svelte.d.ts +40 -0
  33. package/esm/svelte/query/index.svelte.js +112 -0
  34. package/esm/svelte/query/read.remote.d.ts +7 -0
  35. package/esm/svelte/query/read.remote.js +27 -0
  36. package/esm/svelte/remultApiLoad.d.ts +21 -9
  37. package/esm/svelte/remultApiLoad.js +21 -9
  38. package/esm/svelte/server/handleCaching.d.ts +2 -2
  39. package/esm/svelte/server/handleCaching.js +1 -1
  40. package/esm/svelte/ui/Icon.svelte +0 -1
  41. package/esm/vite/index.js +11 -1
  42. package/package.json +8 -3
  43. package/bin/args.d.ts +0 -13
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # firstly
2
2
 
3
+ ## 0.12.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#419](https://github.com/jycouet/firstly/pull/419) [`16cfc80`](https://github.com/jycouet/firstly/commit/16cfc808e55e7cdb275020052c6207a16b12ab66) Thanks [@jycouet](https://github.com/jycouet)! - `FF_Grid`: first sort click is `desc` for Date/Number columns (`asc` otherwise), override per cell with `firstSort`. On landing, the header arrow also reflects the entity `defaultOrderBy`.
8
+
9
+ - [#405](https://github.com/jycouet/firstly/pull/405) [`5324a64`](https://github.com/jycouet/firstly/commit/5324a6471fb20fdca6a6fb16970d2235192e0431) Thanks [@jycouet](https://github.com/jycouet)! - Add experimental `queryRepo` (`firstly/svelte/query`): `$derived(await queryRepo(E).find())` with API rules on SSR, no query on hydration, and plain `repo(E)` writes refreshing it.
10
+
11
+ - [#406](https://github.com/jycouet/firstly/pull/406) [`b2f1664`](https://github.com/jycouet/firstly/commit/b2f1664429490ebb989e4dbd8c0002b0cf6e2b37) Thanks [@jycouet](https://github.com/jycouet)! - New `reloadOnStaleChunk()` for `hooks.client`: a failed lazy import in a component reloads when a new version is deployed.
12
+
13
+ - [#406](https://github.com/jycouet/firstly/pull/406) [`dac5a18`](https://github.com/jycouet/firstly/commit/dac5a18c2164bb5bedd7bd729ba74cabee57fa26) Thanks [@jycouet](https://github.com/jycouet)! - SvelteKit 3 only (Node 22.17+), stay on `0.11.x` for Kit 2. `SP` navigates with `replace: true, reset: false`: `keepFocus` and `noScroll` in `gotoOpts` now throw, use `reset`. `firstly()` sets legacy decorators and nullifies `$app/env/private` on the client. `withStaleDeployReload` reads Kit 3's `handleError` input and skips preloads.
14
+
15
+ ### Patch Changes
16
+
17
+ - [#425](https://github.com/jycouet/firstly/pull/425) [`59d01d6`](https://github.com/jycouet/firstly/commit/59d01d6f6e862b24b50853820bdac782b2dad64d) Thanks [@jycouet](https://github.com/jycouet)! - `SP.computing` stays `true` until the navigation lands, not just until `goto` is called.
18
+
3
19
  ## 0.11.0
4
20
 
5
21
  ### Minor Changes
@@ -36,6 +36,7 @@ export declare const withChangeLog: <entityType>(options?: EntityOptions<entityT
36
36
  backendPreprocessFilter?: ((filter: import("remult").EntityFilter<entityType>, event: import("remult").PreprocessFilterEvent<entityType>) => import("remult").EntityFilter<entityType> | Promise<import("remult").EntityFilter<entityType>>) | undefined;
37
37
  backendPrefilter?: import("remult").EntityFilter<entityType> | (() => import("remult").EntityFilter<entityType> | Promise<import("remult").EntityFilter<entityType>>) | undefined;
38
38
  defaultOrderBy?: import("remult").EntityOrderBy<entityType> | undefined;
39
+ bulkInsert?: boolean;
39
40
  saving?: ((entity: entityType, event: LifecycleEvent<entityType>) => Promise<any> | any) | undefined;
40
41
  deleting?: ((entity: entityType, e: LifecycleEvent<entityType>) => Promise<any> | any) | undefined;
41
42
  validation?: ((entity: entityType, ref: LifecycleEvent<entityType>) => Promise<any> | any) | undefined;
@@ -11,7 +11,7 @@ declare module 'remult' {
11
11
  *
12
12
  * @example
13
13
  * ```ts
14
- * import { withChangeLog } from '..'
14
+ * import { withChangeLog } from 'firstly/changeLog'
15
15
  *
16
16
  * \@Entity('users', withChangeLog({
17
17
  * // ...
@@ -27,7 +27,7 @@ declare module 'remult' {
27
27
  * // APP_Entity.ts example
28
28
  * import { Entity, isBackend, type EntityOptions } from 'remult'
29
29
  *
30
- * import { withChangeLog } from '..'
30
+ * import { withChangeLog } from 'firstly/changeLog'
31
31
  *
32
32
  * export function APP_Entity<entityType>(
33
33
  * key: string,
@@ -6,7 +6,7 @@ import { ChangeLog } from '../changeLogEntities';
6
6
  *
7
7
  * @example
8
8
  * ```ts
9
- * import { withChangeLog } from '..'
9
+ * import { withChangeLog } from 'firstly/changeLog'
10
10
  *
11
11
  * \@Entity('users', withChangeLog({
12
12
  * // ...
@@ -22,7 +22,7 @@ import { ChangeLog } from '../changeLogEntities';
22
22
  * // APP_Entity.ts example
23
23
  * import { Entity, isBackend, type EntityOptions } from 'remult'
24
24
  *
25
- * import { withChangeLog } from '..'
25
+ * import { withChangeLog } from 'firstly/changeLog'
26
26
  *
27
27
  * export function APP_Entity<entityType>(
28
28
  * key: string,
@@ -16,7 +16,7 @@ export declare const FF_Allow: {
16
16
  * @example
17
17
  * Owner-only update / delete (typed):
18
18
  * ```ts
19
- * import { FF_Entity, FF_Allow } from '..'
19
+ * import { FF_Entity, FF_Allow } from 'firstly'
20
20
  *
21
21
  * \@FF_Entity<Task>('tasks', {
22
22
  * allowApiUpdate: FF_Allow.owner<Task>('userId'), // typed: 'userId' must be a key of Task
@@ -38,8 +38,8 @@ export declare const FF_Allow: {
38
38
  * @example
39
39
  * Admin OR owner (typed):
40
40
  * ```ts
41
- * import { FF_Entity, FF_Allow } from '..'
42
- * import { Roles } from '../roles'
41
+ * import { FF_Entity, FF_Allow } from 'firstly'
42
+ * import { Roles } from '../roles.js'
43
43
  *
44
44
  * \@FF_Entity<Task>('tasks', {
45
45
  * allowApiUpdate: FF_Allow.ownerOr<Task>({ roles: [Roles.Admin] }),
@@ -17,7 +17,7 @@ export const FF_Allow = {
17
17
  * @example
18
18
  * Owner-only update / delete (typed):
19
19
  * ```ts
20
- * import { FF_Entity, FF_Allow } from '..'
20
+ * import { FF_Entity, FF_Allow } from 'firstly'
21
21
  *
22
22
  * \@FF_Entity<Task>('tasks', {
23
23
  * allowApiUpdate: FF_Allow.owner<Task>('userId'), // typed: 'userId' must be a key of Task
@@ -39,8 +39,8 @@ export const FF_Allow = {
39
39
  * @example
40
40
  * Admin OR owner (typed):
41
41
  * ```ts
42
- * import { FF_Entity, FF_Allow } from '..'
43
- * import { Roles } from '../roles'
42
+ * import { FF_Entity, FF_Allow } from 'firstly'
43
+ * import { Roles } from '../roles.js'
44
44
  *
45
45
  * \@FF_Entity<Task>('tasks', {
46
46
  * allowApiUpdate: FF_Allow.ownerOr<Task>({ roles: [Roles.Admin] }),
@@ -19,7 +19,7 @@ export declare const FF_Filter: {
19
19
  * @example
20
20
  * Owner-only prefilter (typed):
21
21
  * ```ts
22
- * import { FF_Entity, FF_Filter } from '..'
22
+ * import { FF_Entity, FF_Filter } from 'firstly'
23
23
  *
24
24
  * \@FF_Entity<Task>('tasks', {
25
25
  * apiPrefilter: () => FF_Filter.owner<Task>('userId'),
@@ -41,8 +41,8 @@ export declare const FF_Filter: {
41
41
  * @example
42
42
  * Admin sees all, others only their own (typed):
43
43
  * ```ts
44
- * import { FF_Entity, FF_Filter } from '..'
45
- * import { Roles } from '../roles'
44
+ * import { FF_Entity, FF_Filter } from 'firstly'
45
+ * import { Roles } from '../roles.js'
46
46
  *
47
47
  * \@FF_Entity<Task>('tasks', {
48
48
  * apiPrefilter: () => FF_Filter.ownerOr<Task>({ roles: [Roles.Admin] }),
@@ -21,7 +21,7 @@ export const FF_Filter = {
21
21
  * @example
22
22
  * Owner-only prefilter (typed):
23
23
  * ```ts
24
- * import { FF_Entity, FF_Filter } from '..'
24
+ * import { FF_Entity, FF_Filter } from 'firstly'
25
25
  *
26
26
  * \@FF_Entity<Task>('tasks', {
27
27
  * apiPrefilter: () => FF_Filter.owner<Task>('userId'),
@@ -43,8 +43,8 @@ export const FF_Filter = {
43
43
  * @example
44
44
  * Admin sees all, others only their own (typed):
45
45
  * ```ts
46
- * import { FF_Entity, FF_Filter } from '..'
47
- * import { Roles } from '../roles'
46
+ * import { FF_Entity, FF_Filter } from 'firstly'
47
+ * import { Roles } from '../roles.js'
48
48
  *
49
49
  * \@FF_Entity<Task>('tasks', {
50
50
  * apiPrefilter: () => FF_Filter.ownerOr<Task>({ roles: [Roles.Admin] }),
@@ -30,8 +30,8 @@ export declare const resolveMessage: (m: LocalizedMessage) => string;
30
30
  * locale resolution:
31
31
  *
32
32
  * ```ts
33
- * import { createValidators } from '..'
34
- * import * as m from '../paraglide/messages'
33
+ * import { createValidators } from 'firstly'
34
+ * import * as m from '../paraglide/messages.js'
35
35
  *
36
36
  * export const App_Validators = createValidators({
37
37
  * email: {
@@ -36,8 +36,8 @@ export const resolveMessage = (m) => (typeof m === 'function' ? m() : m);
36
36
  * locale resolution:
37
37
  *
38
38
  * ```ts
39
- * import { createValidators } from '..'
40
- * import * as m from '../paraglide/messages'
39
+ * import { createValidators } from 'firstly'
40
+ * import * as m from '../paraglide/messages.js'
41
41
  *
42
42
  * export const App_Validators = createValidators({
43
43
  * email: {
@@ -0,0 +1 @@
1
+ export {};
package/esm/core/index.js CHANGED
@@ -1,4 +1,4 @@
1
- "use strict";
1
+ export {};
2
2
  // Files under lib/core/ are the pure-TS, frontend-safe primitives.
3
3
  // They are re-exported from lib/index.ts (the `firstly` root export).
4
4
  // Keep this barrel empty — import sources directly from ./BaseEnum,
@@ -63,7 +63,7 @@ export type CronJobParams = {
63
63
  * usage:
64
64
  *
65
65
  * ```ts
66
- * import { cron, cronTime } from '..'
66
+ * import { cron, cronTime } from 'firstly/cron'
67
67
  *
68
68
  * export const api = firstly({
69
69
  * modules: [
@@ -36,7 +36,7 @@ const fmtDuration = (ms) => (ms < 1000 ? `${ms}ms` : `${(ms / 1000).toFixed(1)}s
36
36
  * usage:
37
37
  *
38
38
  * ```ts
39
- * import { cron, cronTime } from '..'
39
+ * import { cron, cronTime } from 'firstly/cron'
40
40
  *
41
41
  * export const api = firstly({
42
42
  * modules: [
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
2
  import { remult } from 'remult'
3
- import { errorMessage } from '../..'
4
3
 
4
+ import { errorMessage } from '../../index.js'
5
5
  import { MailController } from '../MailController'
6
6
  import { Roles_Mail } from '../Roles_Mail'
7
7
 
@@ -65,7 +65,7 @@ export type SqlAdminOptions = {
65
65
  * @example
66
66
  * ```ts
67
67
  * import { remultApi } from 'remult/remult-sveltekit'
68
- * import { sqlAdmin } from './'
68
+ * import { sqlAdmin } from 'firstly/sqlAdmin/server'
69
69
  *
70
70
  * export const api = remultApi({
71
71
  * modules: [sqlAdmin({ tokens: { capabilities: ['read'] } })],
@@ -75,7 +75,7 @@ export type SqlAdminOptions = {
75
75
  * Then on any admin route:
76
76
  * ```svelte
77
77
  * <script>
78
- * import { SqlAdmin, SqlTokens } from '..'
78
+ * import { SqlAdmin, SqlTokens } from 'firstly/sqlAdmin'
79
79
  * </script>
80
80
  * <SqlAdmin />
81
81
  * <SqlTokens />
@@ -59,7 +59,7 @@ async function resolveReadPool(opt = 'auto') {
59
59
  * @example
60
60
  * ```ts
61
61
  * import { remultApi } from 'remult/remult-sveltekit'
62
- * import { sqlAdmin } from './'
62
+ * import { sqlAdmin } from 'firstly/sqlAdmin/server'
63
63
  *
64
64
  * export const api = remultApi({
65
65
  * modules: [sqlAdmin({ tokens: { capabilities: ['read'] } })],
@@ -69,7 +69,7 @@ async function resolveReadPool(opt = 'auto') {
69
69
  * Then on any admin route:
70
70
  * ```svelte
71
71
  * <script>
72
- * import { SqlAdmin, SqlTokens } from '..'
72
+ * import { SqlAdmin, SqlTokens } from 'firstly/sqlAdmin'
73
73
  * </script>
74
74
  * <SqlAdmin />
75
75
  * <SqlTokens />
@@ -116,8 +116,7 @@
116
116
  bind:value={sqlInput}
117
117
  class="h-52 w-full border border-input bg-background p-3 font-mono text-sm text-foreground placeholder-muted-foreground focus:border-ring focus:outline-none disabled:opacity-50"
118
118
  placeholder="Enter SQL command..."
119
- disabled={isLoading}
120
- ></textarea>
119
+ disabled={isLoading}></textarea>
121
120
  <div class="flex flex-wrap items-center gap-4">
122
121
  <button
123
122
  type="submit"
@@ -188,7 +188,7 @@
188
188
  type="button"
189
189
  {onclick}
190
190
  disabled={loading}
191
- class="bg-secondary text-secondary-foreground hover:bg-secondary/80 px-3 py-1.5 text-sm font-medium disabled:opacity-50"
191
+ class="bg-secondary px-3 py-1.5 text-sm font-medium text-secondary-foreground hover:bg-secondary/80 disabled:opacity-50"
192
192
  >
193
193
  {loading ? 'Running…' : label}
194
194
  </button>
@@ -199,19 +199,19 @@
199
199
  type="button"
200
200
  {onclick}
201
201
  {disabled}
202
- class="bg-primary text-primary-foreground hover:bg-primary/90 px-3 py-1.5 text-sm font-medium disabled:opacity-50"
202
+ class="bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50"
203
203
  >
204
204
  {label}
205
205
  </button>
206
206
  {/snippet}
207
207
 
208
208
  {#snippet resultTable(headers: string[], children: import('svelte').Snippet)}
209
- <div class="border-border overflow-auto border">
209
+ <div class="overflow-auto border border-border">
210
210
  <table class="w-full border-collapse text-sm">
211
211
  <thead class="bg-muted">
212
212
  <tr>
213
213
  {#each headers as h (h)}
214
- <th class="border-border border-b px-3 py-2 text-left font-semibold">{h}</th>
214
+ <th class="border-b border-border px-3 py-2 text-left font-semibold">{h}</th>
215
215
  {/each}
216
216
  </tr>
217
217
  </thead>
@@ -222,18 +222,18 @@
222
222
  </div>
223
223
  {/snippet}
224
224
 
225
- <div class="border-border bg-card text-card-foreground border">
225
+ <div class="border border-border bg-card text-card-foreground">
226
226
  <header class="px-5 py-4">
227
227
  <h2 class="text-lg font-semibold">Schema drift</h2>
228
- <p class="text-muted-foreground mt-1 text-sm">
228
+ <p class="mt-1 text-sm text-muted-foreground">
229
229
  Remult creates tables and adds columns, but never revisits them. Each check below is a dry run
230
230
  first; applying runs in one transaction.
231
231
  </p>
232
232
  {#if flagsError}
233
233
  <pre
234
- class="border-destructive/40 bg-destructive/10 text-destructive mt-3 overflow-auto border p-3 text-sm">{flagsError}</pre>
234
+ class="mt-3 overflow-auto border border-destructive/40 bg-destructive/10 p-3 text-sm text-destructive">{flagsError}</pre>
235
235
  {:else if flags && !Object.values(flags).some(Boolean)}
236
- <p class="text-muted-foreground mt-3 text-sm">
236
+ <p class="mt-3 text-sm text-muted-foreground">
237
237
  No check enabled. Opt in with <code class="font-mono"
238
238
  >sqlAdmin(&#123; drift: &#123; entities, relationIndexes: true, ... &#125; &#125;)</code
239
239
  >.
@@ -243,9 +243,9 @@
243
243
 
244
244
  <!-- Flow 1: primary keys -->
245
245
  {#if flags?.primaryKeys}
246
- <section class="border-border border-t p-5">
246
+ <section class="border-t border-border p-5">
247
247
  <h3 class="text-sm font-semibold">Primary key drift</h3>
248
- <p class="text-muted-foreground mt-1 mb-4 max-w-[80ch] text-sm">
248
+ <p class="mt-1 mb-4 max-w-[80ch] text-sm text-muted-foreground">
249
249
  Postgres only sets a PK on first table creation and never ALTERs it, so a later
250
250
  <code class="font-mono">id</code> change is silently ignored. Applying runs every migration atomically.
251
251
  </p>
@@ -261,12 +261,12 @@
261
261
 
262
262
  {#if pkError}
263
263
  <pre
264
- class="border-destructive/40 bg-destructive/10 text-destructive mb-4 overflow-auto border p-3 text-sm">{pkError}</pre>
264
+ class="mb-4 overflow-auto border border-destructive/40 bg-destructive/10 p-3 text-sm text-destructive">{pkError}</pre>
265
265
  {/if}
266
266
 
267
267
  {#if pkResult}
268
268
  {#if pkResult.applied}
269
- <p class="border-primary/30 bg-primary/10 text-primary mb-3 border p-2 text-sm">
269
+ <p class="mb-3 border border-primary/30 bg-primary/10 p-2 text-sm text-primary">
270
270
  Applied · {pkDrift} table{pkDrift === 1 ? '' : 's'} migrated
271
271
  </p>
272
272
  {/if}
@@ -274,15 +274,15 @@
274
274
  {#snippet pkRows()}
275
275
  {#each pkPlans as p (p.table)}
276
276
  <tr class="even:bg-muted/40" class:opacity-50={p.action === 'ok'}>
277
- <td class="border-border border-b px-3 py-2 font-mono">{p.table}</td>
278
- <td class="border-border text-muted-foreground border-b px-3 py-2 font-mono">
277
+ <td class="border-b border-border px-3 py-2 font-mono">{p.table}</td>
278
+ <td class="border-b border-border px-3 py-2 font-mono text-muted-foreground">
279
279
  {p.before.join(', ') || '-'}
280
280
  </td>
281
- <td class="border-border text-muted-foreground border-b px-3 py-2 font-mono">
281
+ <td class="border-b border-border px-3 py-2 font-mono text-muted-foreground">
282
282
  {p.after.join(', ')}
283
283
  </td>
284
284
  <td
285
- class="border-border border-b px-3 py-2 font-mono"
285
+ class="border-b border-border px-3 py-2 font-mono"
286
286
  class:text-primary={p.action === 'migrate' || p.action === 'create'}
287
287
  class:text-muted-foreground={p.action === 'missing'}
288
288
  >
@@ -298,9 +298,9 @@
298
298
 
299
299
  <!-- Flow 2: relation indexes -->
300
300
  {#if flags?.relationIndexes}
301
- <section class="border-border border-t p-5">
301
+ <section class="border-t border-border p-5">
302
302
  <h3 class="text-sm font-semibold">Relation indexes</h3>
303
- <p class="text-muted-foreground mt-1 mb-4 max-w-[80ch] text-sm">
303
+ <p class="mt-1 mb-4 max-w-[80ch] text-sm text-muted-foreground">
304
304
  Postgres doesn't auto-index foreign keys. This indexes the FK columns of
305
305
  <code class="font-mono">toOne</code> relations - unless they're already a leftmost prefix of an
306
306
  existing index (the PK counts), so <code class="font-mono">a</code> on a
@@ -320,12 +320,12 @@
320
320
 
321
321
  {#if relError}
322
322
  <pre
323
- class="border-destructive/40 bg-destructive/10 text-destructive mb-4 overflow-auto border p-3 text-sm">{relError}</pre>
323
+ class="mb-4 overflow-auto border border-destructive/40 bg-destructive/10 p-3 text-sm text-destructive">{relError}</pre>
324
324
  {/if}
325
325
 
326
326
  {#if relResult}
327
327
  {#if relResult.applied}
328
- <p class="border-primary/30 bg-primary/10 text-primary mb-3 border p-2 text-sm">
328
+ <p class="mb-3 border border-primary/30 bg-primary/10 p-2 text-sm text-primary">
329
329
  Created · {relMissing} index{relMissing === 1 ? '' : 'es'}
330
330
  </p>
331
331
  {/if}
@@ -333,12 +333,12 @@
333
333
  {#snippet relRows()}
334
334
  {#each relPlans as p (p.name)}
335
335
  <tr class="even:bg-muted/40" class:opacity-50={p.action === 'ok'}>
336
- <td class="border-border border-b px-3 py-2 font-mono">{p.name}</td>
337
- <td class="border-border text-muted-foreground border-b px-3 py-2 font-mono">
336
+ <td class="border-b border-border px-3 py-2 font-mono">{p.name}</td>
337
+ <td class="border-b border-border px-3 py-2 font-mono text-muted-foreground">
338
338
  {p.table} ({p.columns.join(', ')})
339
339
  </td>
340
340
  <td
341
- class="border-border border-b px-3 py-2 font-mono"
341
+ class="border-b border-border px-3 py-2 font-mono"
342
342
  class:text-primary={p.action === 'create'}
343
343
  >
344
344
  {p.action === 'ok'
@@ -357,9 +357,9 @@
357
357
 
358
358
  <!-- Flow 3: drop orphan columns -->
359
359
  {#if flags?.orphanColumns}
360
- <section class="border-border border-t p-5">
360
+ <section class="border-t border-border p-5">
361
361
  <h3 class="text-sm font-semibold">Orphan columns</h3>
362
- <p class="text-muted-foreground mt-1 mb-4 max-w-[80ch] text-sm">
362
+ <p class="mt-1 mb-4 max-w-[80ch] text-sm text-muted-foreground">
363
363
  Remult only ever ADDs columns, never drops them, so a field removed from an entity leaves its
364
364
  column behind. This lists live columns no entity field declares - e.g. a
365
365
  <code class="font-mono">createdAt</code> now covered by the changelog. Destructive, so nothing
@@ -377,23 +377,23 @@
377
377
 
378
378
  {#if dropError}
379
379
  <pre
380
- class="border-destructive/40 bg-destructive/10 text-destructive mb-4 overflow-auto border p-3 text-sm">{dropError}</pre>
380
+ class="mb-4 overflow-auto border border-destructive/40 bg-destructive/10 p-3 text-sm text-destructive">{dropError}</pre>
381
381
  {/if}
382
382
 
383
383
  {#if dropResult}
384
384
  {#if dropResult.applied}
385
- <p class="border-primary/30 bg-primary/10 text-primary mb-3 border p-2 text-sm">
385
+ <p class="mb-3 border border-primary/30 bg-primary/10 p-2 text-sm text-primary">
386
386
  Dropped · {dropResult.dropped.length} column{dropResult.dropped.length === 1 ? '' : 's'}
387
387
  </p>
388
388
  {/if}
389
389
 
390
390
  {#if dropPlans.length === 0}
391
- <p class="text-muted-foreground text-sm">No orphan columns - every column maps to a field.</p>
391
+ <p class="text-sm text-muted-foreground">No orphan columns - every column maps to a field.</p>
392
392
  {:else}
393
393
  {#snippet dropRows()}
394
394
  {#each dropPlans as p (keyOf(p))}
395
395
  <tr class="even:bg-muted/40">
396
- <td class="border-border border-b px-3 py-2">
396
+ <td class="border-b border-border px-3 py-2">
397
397
  <input
398
398
  type="checkbox"
399
399
  checked={selected.has(keyOf(p))}
@@ -401,8 +401,8 @@
401
401
  aria-label={`Drop ${keyOf(p)}`}
402
402
  />
403
403
  </td>
404
- <td class="border-border border-b px-3 py-2 font-mono">{p.table}</td>
405
- <td class="border-border text-primary border-b px-3 py-2 font-mono">{p.column}</td>
404
+ <td class="border-b border-border px-3 py-2 font-mono">{p.table}</td>
405
+ <td class="border-b border-border px-3 py-2 font-mono text-primary">{p.column}</td>
406
406
  </tr>
407
407
  {/each}
408
408
  {/snippet}
@@ -414,9 +414,9 @@
414
414
 
415
415
  <!-- Flow 4: nullability drift -->
416
416
  {#if flags?.nullable}
417
- <section class="border-border border-t p-5">
417
+ <section class="border-t border-border p-5">
418
418
  <h3 class="text-sm font-semibold">Nullability drift</h3>
419
- <p class="text-muted-foreground mt-1 mb-4 max-w-[80ch] text-sm">
419
+ <p class="mt-1 mb-4 max-w-[80ch] text-sm text-muted-foreground">
420
420
  A field that is not <code class="font-mono">allowNull</code> makes Remult create the column
421
421
  <code class="font-mono">default '' not null</code>, and it never relaxes that afterwards. So a
422
422
  column added before its field learned <code class="font-mono">allowNull</code> still hands every
@@ -439,25 +439,25 @@
439
439
 
440
440
  {#if nullError}
441
441
  <pre
442
- class="border-destructive/40 bg-destructive/10 text-destructive mb-4 overflow-auto border p-3 text-sm">{nullError}</pre>
442
+ class="mb-4 overflow-auto border border-destructive/40 bg-destructive/10 p-3 text-sm text-destructive">{nullError}</pre>
443
443
  {/if}
444
444
 
445
445
  {#if nullResult}
446
446
  {#if nullResult.applied}
447
- <p class="border-primary/30 bg-primary/10 text-primary mb-3 border p-2 text-sm">
447
+ <p class="mb-3 border border-primary/30 bg-primary/10 p-2 text-sm text-primary">
448
448
  Fixed · {nullResult.fixed.length} column{nullResult.fixed.length === 1 ? '' : 's'}
449
449
  </p>
450
450
  {/if}
451
451
 
452
452
  {#if nullPlans.length === 0}
453
- <p class="text-muted-foreground text-sm">
453
+ <p class="text-sm text-muted-foreground">
454
454
  No drift - every nullable field has a plain nullable column.
455
455
  </p>
456
456
  {:else}
457
457
  {#snippet nullRows()}
458
458
  {#each nullPlans as p (keyOf(p))}
459
459
  <tr class="even:bg-muted/40">
460
- <td class="border-border border-b px-3 py-2">
460
+ <td class="border-b border-border px-3 py-2">
461
461
  <input
462
462
  type="checkbox"
463
463
  checked={nullSelected.has(keyOf(p))}
@@ -465,14 +465,14 @@
465
465
  aria-label={`Fix ${keyOf(p)}`}
466
466
  />
467
467
  </td>
468
- <td class="border-border border-b px-3 py-2 font-mono">{p.table}</td>
469
- <td class="border-border text-primary border-b px-3 py-2 font-mono">{p.column}</td>
470
- <td class="border-border border-b px-3 py-2 text-sm">
468
+ <td class="border-b border-border px-3 py-2 font-mono">{p.table}</td>
469
+ <td class="border-b border-border px-3 py-2 font-mono text-primary">{p.column}</td>
470
+ <td class="border-b border-border px-3 py-2 text-sm">
471
471
  {[p.dropDefault && 'drop default', p.dropNotNull && 'drop not null']
472
472
  .filter(Boolean)
473
473
  .join(' · ')}
474
474
  </td>
475
- <td class="border-border border-b px-3 py-2 text-sm">
475
+ <td class="border-b border-border px-3 py-2 text-sm">
476
476
  {p.blankToNull ? "'' → NULL" : 'rows kept'}
477
477
  </td>
478
478
  </tr>
@@ -196,7 +196,7 @@
196
196
  role="dialog"
197
197
  aria-modal="true"
198
198
  tabindex="-1"
199
- class="bg-background text-foreground border-border relative z-[1] max-h-[90vh] w-full {widthClass[
199
+ class="relative z-[1] max-h-[90vh] w-full border-border bg-background text-foreground {widthClass[
200
200
  width
201
201
  ]} overflow-auto rounded-lg border p-5 shadow-xl"
202
202
  >
@@ -207,7 +207,7 @@
207
207
  <button
208
208
  type="button"
209
209
  aria-label="Close"
210
- class="text-muted-foreground hover:text-foreground absolute top-3 right-3 inline-flex size-7 items-center justify-center rounded-md"
210
+ class="absolute top-3 right-3 inline-flex size-7 items-center justify-center rounded-md text-muted-foreground hover:text-foreground"
211
211
  onclick={dismiss}
212
212
  >
213
213
  ✕
@@ -242,7 +242,7 @@
242
242
  aria-labelledby={title ? `ff-dlg-title-${id}` : undefined}
243
243
  aria-label={title ? undefined : message}
244
244
  aria-describedby="ff-dlg-desc-{id}"
245
- class="bg-background text-foreground border-border relative z-[1] w-full max-w-sm rounded-lg border p-5 shadow-xl"
245
+ class="relative z-[1] w-full max-w-sm rounded-lg border border-border bg-background p-5 text-foreground shadow-xl"
246
246
  >
247
247
  {#if title}
248
248
  <h2 id="ff-dlg-title-{id}" class="mb-2 text-lg font-semibold">{title}</h2>
@@ -251,7 +251,7 @@
251
251
  <div class="mt-5 flex justify-end gap-2">
252
252
  <button
253
253
  type="button"
254
- class="border-border hover:bg-muted rounded-md border px-3 py-1.5 text-sm"
254
+ class="rounded-md border border-border px-3 py-1.5 text-sm hover:bg-muted"
255
255
  onclick={cancel}
256
256
  >
257
257
  {cancelLabel}
@@ -38,7 +38,7 @@
38
38
  tabindex="-1"
39
39
  aria-labelledby={item.title ? `ff-prompt-title-${item.id}` : undefined}
40
40
  aria-label={item.title ? undefined : item.label ? resolveMessage(item.label) : undefined}
41
- class="bg-background text-foreground border-border relative z-[1] w-full max-w-sm rounded-lg border p-5 shadow-xl"
41
+ class="relative z-[1] w-full max-w-sm rounded-lg border border-border bg-background p-5 text-foreground shadow-xl"
42
42
  >
43
43
  <form
44
44
  onsubmit={(e) => {
@@ -52,7 +52,7 @@
52
52
  </h2>
53
53
  {/if}
54
54
  {#if item.label}
55
- <label class="text-muted-foreground mb-1 block text-sm" for="ff-prompt-{item.id}">
55
+ <label class="mb-1 block text-sm text-muted-foreground" for="ff-prompt-{item.id}">
56
56
  {resolveMessage(item.label)}
57
57
  </label>
58
58
  {/if}
@@ -60,22 +60,22 @@
60
60
  id="ff-prompt-{item.id}"
61
61
  bind:value
62
62
  placeholder={item.placeholder}
63
- class="border-border bg-card focus-visible:ring-ring w-full rounded-md border px-3 py-2 text-sm focus-visible:ring-2 focus-visible:outline-none"
63
+ class="w-full rounded-md border border-border bg-card px-3 py-2 text-sm focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none"
64
64
  />
65
65
  {#if item.hint}
66
- <p class="text-muted-foreground mt-1 text-xs">{item.hint(value)}</p>
66
+ <p class="mt-1 text-xs text-muted-foreground">{item.hint(value)}</p>
67
67
  {/if}
68
68
  <div class="mt-5 flex justify-end gap-2">
69
69
  <button
70
70
  type="button"
71
- class="border-border hover:bg-muted rounded-md border px-3 py-1.5 text-sm"
71
+ class="rounded-md border border-border px-3 py-1.5 text-sm hover:bg-muted"
72
72
  onclick={oncancel}
73
73
  >
74
74
  {resolveMessage(item.cancelLabel ?? cfg.messages.cancel)}
75
75
  </button>
76
76
  <button
77
77
  type="submit"
78
- class="bg-primary text-primary-foreground hover:bg-primary/90 rounded-md px-3 py-1.5 text-sm font-medium"
78
+ class="rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground hover:bg-primary/90"
79
79
  >
80
80
  {resolveMessage(item.confirmLabel ?? cfg.messages.ok)}
81
81
  </button>
@@ -22,6 +22,7 @@ export declare class SP<T extends Record<string, any>> {
22
22
  private debouncedValues;
23
23
  computing: boolean;
24
24
  private debouncedToURL;
25
+ private lastNavigation;
25
26
  private sharedDebouncePending;
26
27
  private activeDebounces;
27
28
  private longestDebounceTime;