firstly 0.8.1 → 0.9.1
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/CHANGELOG.md +14 -0
- package/esm/carbone/CarboneController.js +4 -4
- package/esm/feedback/FeedbackController.js +7 -7
- package/esm/mail/MailController.js +1 -1
- package/esm/mail/ui/LastMails.svelte +26 -24
- package/esm/mail/ui/WriteMail.svelte +31 -25
- package/esm/sqlAdmin/SqlAdminController.js +4 -1
- package/esm/sqlAdmin/ui/SqlAdmin.svelte +9 -7
- package/esm/svelte/index.d.ts +1 -1
- package/esm/svelte/index.js +1 -1
- package/esm/svelte/remultApiLoad.d.ts +32 -16
- package/esm/svelte/remultApiLoad.js +33 -28
- package/esm/svelte/server/index.d.ts +0 -13
- package/esm/svelte/server/index.js +0 -15
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# firstly
|
|
2
2
|
|
|
3
|
+
## 0.9.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#344](https://github.com/jycouet/firstly/pull/344) [`857d7b3`](https://github.com/jycouet/firstly/commit/857d7b3b272ae6bc6b0ca7ccff7d838c4f463bc6) Thanks [@jycouet](https://github.com/jycouet)! - - BackendMethods of `mail`, `sqlAdmin`, `feedback` and `carbone` now carry an `apiPrefix` (`/api/ff/mail/sendTest`, `/api/ff/sqlAdmin/exec`, ...) so a host app method with the same name (e.g. its own `sendTest`) can no longer shadow them.
|
|
8
|
+
- `WriteMail` / `LastMails` read the semantic theme tokens (`bg-card`, `border-border`, `text-primary`, ...) like `SqlAdmin` instead of a hardcoded slate palette.
|
|
9
|
+
- `SqlAdmin`: the write toggle is a static "Allow writes" checkbox; the label no longer flips with the state.
|
|
10
|
+
|
|
11
|
+
## 0.9.0
|
|
12
|
+
|
|
13
|
+
### Minor Changes
|
|
14
|
+
|
|
15
|
+
- [#341](https://github.com/jycouet/firstly/pull/341) [`ea9b4b6`](https://github.com/jycouet/firstly/commit/ea9b4b67544dc755374b3178c22082dac986472e) Thanks [@jycouet](https://github.com/jycouet)! - svelte: added `repoFetch(fetch)` and `loadRepo(loadFn)` - bind a repo to a load's `event.fetch` so API rules apply on SSR, hydration reuses the SSR response (one query), and parallel `+layout.ts`/`+page.ts` loads can't leak into each other. Removed `remultApiUniversalLoad`, `remultApiServerLoad`, `withRemultFetch`; migrate to `loadRepo` (works for universal AND server loads) or the `repoFetch(event.fetch)` primitive.
|
|
16
|
+
|
|
3
17
|
## 0.8.1
|
|
4
18
|
|
|
5
19
|
### Patch Changes
|
|
@@ -133,24 +133,24 @@ export class CarboneController {
|
|
|
133
133
|
__decorate([
|
|
134
134
|
BackendMethod({
|
|
135
135
|
allowed: [Roles_Carbon.Carbon_Admin, Roles_Carbon.Carbon_UploadTemplate],
|
|
136
|
-
apiPrefix: 'carbone',
|
|
136
|
+
apiPrefix: 'ff/carbone',
|
|
137
137
|
})
|
|
138
138
|
], CarboneController, "uploadTemplate", null);
|
|
139
139
|
__decorate([
|
|
140
140
|
BackendMethod({
|
|
141
141
|
allowed: [Roles_Carbon.Carbon_Admin, Roles_Carbon.Carbon_DownloadTemplate],
|
|
142
|
-
apiPrefix: 'carbone',
|
|
142
|
+
apiPrefix: 'ff/carbone',
|
|
143
143
|
})
|
|
144
144
|
], CarboneController, "downloadTemplate", null);
|
|
145
145
|
__decorate([
|
|
146
146
|
BackendMethod({
|
|
147
147
|
allowed: [Roles_Carbon.Carbon_Admin, Roles_Carbon.Carbon_DeleteTemplate],
|
|
148
|
-
apiPrefix: 'carbone',
|
|
148
|
+
apiPrefix: 'ff/carbone',
|
|
149
149
|
})
|
|
150
150
|
], CarboneController, "deleteTemplate", null);
|
|
151
151
|
__decorate([
|
|
152
152
|
BackendMethod({
|
|
153
153
|
allowed: [Roles_Carbon.Carbon_Admin, Roles_Carbon.Carbon_Render],
|
|
154
|
-
apiPrefix: 'carbone',
|
|
154
|
+
apiPrefix: 'ff/carbone',
|
|
155
155
|
})
|
|
156
156
|
], CarboneController, "render", null);
|
|
@@ -370,23 +370,23 @@ repository(name: $repository, owner: $owner) {
|
|
|
370
370
|
}
|
|
371
371
|
}
|
|
372
372
|
__decorate([
|
|
373
|
-
BackendMethod({ allowed: Allow.authenticated })
|
|
373
|
+
BackendMethod({ allowed: Allow.authenticated, apiPrefix: 'ff/feedback' })
|
|
374
374
|
], FeedbackController, "getMilestones", null);
|
|
375
375
|
__decorate([
|
|
376
|
-
BackendMethod({ allowed: Allow.authenticated })
|
|
376
|
+
BackendMethod({ allowed: Allow.authenticated, apiPrefix: 'ff/feedback' })
|
|
377
377
|
], FeedbackController, "getIssues", null);
|
|
378
378
|
__decorate([
|
|
379
|
-
BackendMethod({ allowed: Allow.authenticated })
|
|
379
|
+
BackendMethod({ allowed: Allow.authenticated, apiPrefix: 'ff/feedback' })
|
|
380
380
|
], FeedbackController, "getIssue", null);
|
|
381
381
|
__decorate([
|
|
382
|
-
BackendMethod({ allowed: Allow.authenticated })
|
|
382
|
+
BackendMethod({ allowed: Allow.authenticated, apiPrefix: 'ff/feedback' })
|
|
383
383
|
], FeedbackController, "createIssue", null);
|
|
384
384
|
__decorate([
|
|
385
|
-
BackendMethod({ allowed: Allow.authenticated })
|
|
385
|
+
BackendMethod({ allowed: Allow.authenticated, apiPrefix: 'ff/feedback' })
|
|
386
386
|
], FeedbackController, "addCommentOnIssue", null);
|
|
387
387
|
__decorate([
|
|
388
|
-
BackendMethod({ allowed: Allow.authenticated })
|
|
388
|
+
BackendMethod({ allowed: Allow.authenticated, apiPrefix: 'ff/feedback' })
|
|
389
389
|
], FeedbackController, "close", null);
|
|
390
390
|
__decorate([
|
|
391
|
-
BackendMethod({ allowed: Allow.authenticated })
|
|
391
|
+
BackendMethod({ allowed: Allow.authenticated, apiPrefix: 'ff/feedback' })
|
|
392
392
|
], FeedbackController, "reOpen", null);
|
|
@@ -75,25 +75,25 @@
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
function badgeClass(status: Mail['status']): string {
|
|
78
|
-
if (status === 'sent') return 'bg-emerald-500/10 border-emerald-500/40 text-emerald-
|
|
78
|
+
if (status === 'sent') return 'bg-emerald-500/10 border-emerald-500/40 text-emerald-700'
|
|
79
79
|
if (status === 'transport_not_configured')
|
|
80
|
-
return 'bg-amber-500/10 border-amber-500/40 text-amber-
|
|
81
|
-
return 'bg-
|
|
80
|
+
return 'bg-amber-500/10 border-amber-500/40 text-amber-700'
|
|
81
|
+
return 'bg-destructive/10 border-destructive text-destructive'
|
|
82
82
|
}
|
|
83
83
|
</script>
|
|
84
84
|
|
|
85
|
-
<div class="border border-
|
|
86
|
-
<header class="flex flex-wrap items-center gap-3 border-b border-
|
|
85
|
+
<div class="border border-border bg-card text-card-foreground">
|
|
86
|
+
<header class="flex flex-wrap items-center gap-3 border-b border-border px-5 py-4">
|
|
87
87
|
<div class="flex flex-col">
|
|
88
|
-
<h2 class="text-lg font-semibold text-
|
|
89
|
-
<p class="text-sm text-
|
|
88
|
+
<h2 class="text-lg font-semibold text-foreground">Last mails</h2>
|
|
89
|
+
<p class="text-sm text-muted-foreground">Recent mails sent through this app.</p>
|
|
90
90
|
</div>
|
|
91
91
|
{#if hasAccess}
|
|
92
92
|
<button
|
|
93
93
|
type="button"
|
|
94
94
|
onclick={refresh}
|
|
95
95
|
disabled={isLoading}
|
|
96
|
-
class="ml-auto inline-flex items-center gap-2 border border-
|
|
96
|
+
class="ml-auto inline-flex items-center gap-2 border border-border bg-secondary px-3 py-1.5 text-sm font-medium text-secondary-foreground hover:bg-accent hover:text-accent-foreground disabled:opacity-50"
|
|
97
97
|
>
|
|
98
98
|
{#if isLoading}
|
|
99
99
|
<svg
|
|
@@ -111,24 +111,26 @@
|
|
|
111
111
|
{/if}
|
|
112
112
|
Refresh
|
|
113
113
|
</button>
|
|
114
|
-
<span class="text-xs text-
|
|
114
|
+
<span class="text-xs text-muted-foreground">
|
|
115
115
|
{mails.length} mail{mails.length === 1 ? '' : 's'}
|
|
116
|
-
{#if live}<span class="text-
|
|
116
|
+
{#if live}<span class="text-primary">· live</span>{/if}
|
|
117
117
|
</span>
|
|
118
118
|
{/if}
|
|
119
119
|
</header>
|
|
120
120
|
|
|
121
121
|
<div class="p-5">
|
|
122
122
|
{#if !hasAccess}
|
|
123
|
-
<div class="border border-amber-500/40 bg-amber-500/10 p-3 text-sm text-amber-
|
|
123
|
+
<div class="border border-amber-500/40 bg-amber-500/10 p-3 text-sm text-amber-700">
|
|
124
124
|
You need the
|
|
125
|
-
<code class="bg-amber-500/20 px-1 py-0.5 text-xs text-amber-
|
|
125
|
+
<code class="bg-amber-500/20 px-1 py-0.5 text-xs text-amber-800">Mail.Admin</code>
|
|
126
126
|
role to use this.
|
|
127
127
|
</div>
|
|
128
128
|
{:else if error}
|
|
129
|
-
<div class="border border-
|
|
129
|
+
<div class="border border-destructive bg-destructive/10 p-3 text-sm text-destructive">
|
|
130
|
+
{error}
|
|
131
|
+
</div>
|
|
130
132
|
{:else if mails.length === 0}
|
|
131
|
-
<div class="border border-
|
|
133
|
+
<div class="border border-border bg-muted p-3 text-sm text-muted-foreground">No mails yet.</div>
|
|
132
134
|
{:else}
|
|
133
135
|
<div class="flex flex-col gap-3">
|
|
134
136
|
{#each mails as m (m.id)}
|
|
@@ -137,24 +139,24 @@
|
|
|
137
139
|
{@const bcc = formatList(m.metadata?.bcc)}
|
|
138
140
|
{@const messageId = m.metadata?.transport?.messageId as string | undefined}
|
|
139
141
|
{@const preview = m.metadata?.transport?.preview as string | undefined}
|
|
140
|
-
<article class="flex flex-col gap-2 border border-
|
|
142
|
+
<article class="flex flex-col gap-2 border border-border bg-background p-4">
|
|
141
143
|
<div class="flex flex-wrap items-center gap-2">
|
|
142
144
|
<span class="border px-2 py-0.5 text-xs font-medium {badgeClass(m.status)}">{m.status}</span>
|
|
143
145
|
<span
|
|
144
|
-
class="border border-
|
|
146
|
+
class="border border-border bg-secondary px-2 py-0.5 text-xs font-medium text-secondary-foreground"
|
|
145
147
|
>{m.topic}</span
|
|
146
148
|
>
|
|
147
|
-
<span class="text-xs text-
|
|
148
|
-
<span class="ml-auto text-xs text-
|
|
149
|
+
<span class="text-xs text-muted-foreground">{parseTo(m.to)}</span>
|
|
150
|
+
<span class="ml-auto text-xs text-muted-foreground">{formatDate(m.createdAt)}</span>
|
|
149
151
|
</div>
|
|
150
152
|
|
|
151
|
-
<div class="text-base font-medium text-
|
|
153
|
+
<div class="text-base font-medium text-foreground">{subject || '(no subject)'}</div>
|
|
152
154
|
|
|
153
155
|
{#if cc}
|
|
154
|
-
<div class="text-xs text-
|
|
156
|
+
<div class="text-xs text-muted-foreground">cc: {cc}</div>
|
|
155
157
|
{/if}
|
|
156
158
|
{#if bcc}
|
|
157
|
-
<div class="text-xs text-
|
|
159
|
+
<div class="text-xs text-muted-foreground">bcc: {bcc}</div>
|
|
158
160
|
{/if}
|
|
159
161
|
|
|
160
162
|
{#if preview}
|
|
@@ -163,18 +165,18 @@
|
|
|
163
165
|
href={preview}
|
|
164
166
|
target="_blank"
|
|
165
167
|
rel="noopener noreferrer"
|
|
166
|
-
class="text-
|
|
168
|
+
class="text-primary underline hover:opacity-80">preview (mail not really sent)</a
|
|
167
169
|
>
|
|
168
170
|
</div>
|
|
169
171
|
{/if}
|
|
170
172
|
|
|
171
173
|
{#if messageId}
|
|
172
|
-
<div class="text-xs break-all text-
|
|
174
|
+
<div class="text-xs break-all text-muted-foreground">id: <code>{messageId}</code></div>
|
|
173
175
|
{/if}
|
|
174
176
|
|
|
175
177
|
{#if m.status === 'error' && m.errorInfo}
|
|
176
178
|
<pre
|
|
177
|
-
class="border border-
|
|
179
|
+
class="border border-destructive bg-destructive/10 p-2 text-xs whitespace-pre-wrap text-destructive">{m.errorInfo}</pre>
|
|
178
180
|
{/if}
|
|
179
181
|
</article>
|
|
180
182
|
{/each}
|
|
@@ -47,24 +47,27 @@
|
|
|
47
47
|
}
|
|
48
48
|
</script>
|
|
49
49
|
|
|
50
|
-
<div class="border border-
|
|
51
|
-
<header class="border-b border-
|
|
52
|
-
<h2 class="text-lg font-semibold text-
|
|
53
|
-
<p class="mt-1 text-sm text-
|
|
50
|
+
<div class="border border-border bg-card text-card-foreground">
|
|
51
|
+
<header class="border-b border-border px-5 py-4">
|
|
52
|
+
<h2 class="text-lg font-semibold text-foreground">Write mail</h2>
|
|
53
|
+
<p class="mt-1 text-sm text-muted-foreground">
|
|
54
|
+
Send a test mail through the configured transport.
|
|
55
|
+
</p>
|
|
54
56
|
</header>
|
|
55
57
|
|
|
56
58
|
<div class="p-5">
|
|
57
59
|
{#if !hasAccess}
|
|
58
|
-
<div class="border border-amber-500/40 bg-amber-500/10 p-3 text-sm text-amber-
|
|
60
|
+
<div class="border border-amber-500/40 bg-amber-500/10 p-3 text-sm text-amber-700">
|
|
59
61
|
You need the
|
|
60
|
-
<code class="bg-amber-500/20 px-1 py-0.5 text-xs text-amber-
|
|
62
|
+
<code class="bg-amber-500/20 px-1 py-0.5 text-xs text-amber-800">Mail.Admin</code>
|
|
61
63
|
role to use this.
|
|
62
64
|
</div>
|
|
63
65
|
{:else}
|
|
64
66
|
<form onsubmit={handleSubmit} class="flex flex-col gap-4">
|
|
65
67
|
<div class="flex flex-col gap-1">
|
|
66
|
-
<label
|
|
67
|
-
|
|
68
|
+
<label
|
|
69
|
+
for="write-mail-to"
|
|
70
|
+
class="text-xs font-medium tracking-wide text-muted-foreground uppercase">To</label
|
|
68
71
|
>
|
|
69
72
|
<input
|
|
70
73
|
id="write-mail-to"
|
|
@@ -72,14 +75,15 @@
|
|
|
72
75
|
bind:value={to}
|
|
73
76
|
disabled={isLoading}
|
|
74
77
|
placeholder="someone@example.com, other@example.com"
|
|
75
|
-
class="border border-
|
|
78
|
+
class="border border-input bg-background px-3 py-2 text-sm text-foreground placeholder-muted-foreground focus:border-ring focus:outline-none disabled:opacity-50"
|
|
76
79
|
/>
|
|
77
80
|
</div>
|
|
78
81
|
|
|
79
82
|
<div class="grid grid-cols-2 gap-4 max-md:grid-cols-1">
|
|
80
83
|
<div class="flex flex-col gap-1">
|
|
81
|
-
<label
|
|
82
|
-
|
|
84
|
+
<label
|
|
85
|
+
for="write-mail-cc"
|
|
86
|
+
class="text-xs font-medium tracking-wide text-muted-foreground uppercase">Cc</label
|
|
83
87
|
>
|
|
84
88
|
<input
|
|
85
89
|
id="write-mail-cc"
|
|
@@ -87,12 +91,13 @@
|
|
|
87
91
|
bind:value={cc}
|
|
88
92
|
disabled={isLoading}
|
|
89
93
|
placeholder="optional, comma-separated"
|
|
90
|
-
class="border border-
|
|
94
|
+
class="border border-input bg-background px-3 py-2 text-sm text-foreground placeholder-muted-foreground focus:border-ring focus:outline-none disabled:opacity-50"
|
|
91
95
|
/>
|
|
92
96
|
</div>
|
|
93
97
|
<div class="flex flex-col gap-1">
|
|
94
|
-
<label
|
|
95
|
-
|
|
98
|
+
<label
|
|
99
|
+
for="write-mail-bcc"
|
|
100
|
+
class="text-xs font-medium tracking-wide text-muted-foreground uppercase">Bcc</label
|
|
96
101
|
>
|
|
97
102
|
<input
|
|
98
103
|
id="write-mail-bcc"
|
|
@@ -100,7 +105,7 @@
|
|
|
100
105
|
bind:value={bcc}
|
|
101
106
|
disabled={isLoading}
|
|
102
107
|
placeholder="optional, comma-separated"
|
|
103
|
-
class="border border-
|
|
108
|
+
class="border border-input bg-background px-3 py-2 text-sm text-foreground placeholder-muted-foreground focus:border-ring focus:outline-none disabled:opacity-50"
|
|
104
109
|
/>
|
|
105
110
|
</div>
|
|
106
111
|
</div>
|
|
@@ -108,7 +113,7 @@
|
|
|
108
113
|
<div class="flex flex-col gap-1">
|
|
109
114
|
<label
|
|
110
115
|
for="write-mail-subject"
|
|
111
|
-
class="text-xs font-medium tracking-wide text-
|
|
116
|
+
class="text-xs font-medium tracking-wide text-muted-foreground uppercase">Subject</label
|
|
112
117
|
>
|
|
113
118
|
<input
|
|
114
119
|
id="write-mail-subject"
|
|
@@ -117,28 +122,29 @@
|
|
|
117
122
|
disabled={isLoading}
|
|
118
123
|
required
|
|
119
124
|
placeholder="Subject"
|
|
120
|
-
class="border border-
|
|
125
|
+
class="border border-input bg-background px-3 py-2 text-sm text-foreground placeholder-muted-foreground focus:border-ring focus:outline-none disabled:opacity-50"
|
|
121
126
|
/>
|
|
122
127
|
</div>
|
|
123
128
|
|
|
124
129
|
<div class="flex flex-col gap-1">
|
|
125
|
-
<label
|
|
126
|
-
|
|
130
|
+
<label
|
|
131
|
+
for="write-mail-body"
|
|
132
|
+
class="text-xs font-medium tracking-wide text-muted-foreground uppercase">Body</label
|
|
127
133
|
>
|
|
128
134
|
<textarea
|
|
129
135
|
id="write-mail-body"
|
|
130
136
|
bind:value={body}
|
|
131
137
|
disabled={isLoading}
|
|
132
138
|
placeholder="Write your message..."
|
|
133
|
-
class="h-40 w-full border border-
|
|
139
|
+
class="h-40 w-full border border-input bg-background px-3 py-2 text-sm text-foreground placeholder-muted-foreground focus:border-ring focus:outline-none disabled:opacity-50"
|
|
134
140
|
></textarea>
|
|
135
141
|
</div>
|
|
136
142
|
|
|
137
|
-
<div class="flex items-center gap-4 border-t border-
|
|
143
|
+
<div class="flex items-center gap-4 border-t border-border pt-4">
|
|
138
144
|
<button
|
|
139
145
|
type="submit"
|
|
140
146
|
disabled={isLoading || !canSend}
|
|
141
|
-
class="inline-flex items-center gap-2 bg-
|
|
147
|
+
class="inline-flex items-center gap-2 bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:opacity-90 disabled:opacity-50"
|
|
142
148
|
>
|
|
143
149
|
{#if isLoading}
|
|
144
150
|
<svg
|
|
@@ -158,12 +164,12 @@
|
|
|
158
164
|
</button>
|
|
159
165
|
|
|
160
166
|
{#if !canSend && !result && !error}
|
|
161
|
-
<span class="text-xs text-
|
|
167
|
+
<span class="text-xs text-muted-foreground"> Add a subject and at least one recipient. </span>
|
|
162
168
|
{/if}
|
|
163
169
|
|
|
164
170
|
{#if result}
|
|
165
171
|
<div
|
|
166
|
-
class="flex flex-1 items-center gap-2 border border-emerald-500/40 bg-emerald-500/10 px-3 py-1.5 text-sm text-emerald-
|
|
172
|
+
class="flex flex-1 items-center gap-2 border border-emerald-500/40 bg-emerald-500/10 px-3 py-1.5 text-sm text-emerald-700"
|
|
167
173
|
>
|
|
168
174
|
<span class="font-medium">Sent</span>
|
|
169
175
|
{#if result.messageId}
|
|
@@ -174,7 +180,7 @@
|
|
|
174
180
|
|
|
175
181
|
{#if error}
|
|
176
182
|
<pre
|
|
177
|
-
class="flex-1 overflow-auto border border-
|
|
183
|
+
class="flex-1 overflow-auto border border-destructive bg-destructive/10 px-3 py-1.5 text-xs whitespace-pre-wrap text-destructive">{error}</pre>
|
|
178
184
|
{/if}
|
|
179
185
|
</div>
|
|
180
186
|
</form>
|
|
@@ -37,5 +37,8 @@ export class SqlAdminController {
|
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
__decorate([
|
|
40
|
-
BackendMethod({
|
|
40
|
+
BackendMethod({
|
|
41
|
+
allowed: [Roles_SqlAdmin.SqlAdmin_Admin, FF_Role.FF_Role_Admin],
|
|
42
|
+
apiPrefix: 'ff/sqlAdmin',
|
|
43
|
+
})
|
|
41
44
|
], SqlAdminController, "exec", null);
|
|
@@ -21,7 +21,7 @@ LIMIT 10`
|
|
|
21
21
|
let result: { rows: any[]; rowCount: number; took: number } | undefined = $state()
|
|
22
22
|
let error = $state('')
|
|
23
23
|
let isLoading = $state(false)
|
|
24
|
-
let
|
|
24
|
+
let allowWrites = $state(false)
|
|
25
25
|
let copied = $state(false)
|
|
26
26
|
|
|
27
27
|
const queries = {
|
|
@@ -64,7 +64,7 @@ ORDER BY tablename, indexname`,
|
|
|
64
64
|
try {
|
|
65
65
|
error = ''
|
|
66
66
|
isLoading = true
|
|
67
|
-
result = await SqlAdminController.exec(sqlInput,
|
|
67
|
+
result = await SqlAdminController.exec(sqlInput, allowWrites)
|
|
68
68
|
log.info('for AI:', JSON.stringify(result.rows))
|
|
69
69
|
log.info('for humans:', result)
|
|
70
70
|
} catch (e) {
|
|
@@ -159,14 +159,16 @@ ORDER BY tablename, indexname`,
|
|
|
159
159
|
Execute SQL
|
|
160
160
|
</button>
|
|
161
161
|
|
|
162
|
+
<!-- Static label: the checkbox state is the signal, the text never
|
|
163
|
+
flips so the user always knows what ticking it does. -->
|
|
162
164
|
<label
|
|
163
165
|
class="inline-flex items-center gap-2 text-sm font-medium select-none"
|
|
164
|
-
class:text-destructive={
|
|
165
|
-
class:text-muted-foreground={!
|
|
166
|
-
title="
|
|
166
|
+
class:text-destructive={allowWrites}
|
|
167
|
+
class:text-muted-foreground={!allowWrites}
|
|
168
|
+
title="Unchecked: the query runs in a READ ONLY transaction, so the database rejects any write. Tick only when you know what you are doing."
|
|
167
169
|
>
|
|
168
|
-
<input type="checkbox" bind:checked={
|
|
169
|
-
|
|
170
|
+
<input type="checkbox" bind:checked={allowWrites} class="accent-destructive" />
|
|
171
|
+
Allow writes
|
|
170
172
|
</label>
|
|
171
173
|
|
|
172
174
|
{#if error}
|
package/esm/svelte/index.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ export { default as FF_ToastManager } from './FF_ToastManager.svelte';
|
|
|
18
18
|
export { SP } from './class/SP.svelte';
|
|
19
19
|
export type { ParamDefinition } from './class/SP.svelte';
|
|
20
20
|
export { initRemultSvelteReactivity } from './initRemultSvelteReactivity';
|
|
21
|
-
export {
|
|
21
|
+
export { loadRepo, repoFetch, type Repo } from './remultApiLoad.js';
|
|
22
22
|
export { FF_Cell, FF_CellValue, FF_Grid, GroupFields, DefaultInput, buildCells, displayCell, resolveCellComponent, getFieldMetaType, getInputType, getStyle, getCellElementConfig, defaultConfig, } from './grid/index.js';
|
|
23
23
|
export type { Cell, CellInput, CellComponent, CellUI, MetaKind, CellConfig, CellElementConfig, CellProps, CellContentProps, CellElementProps, CellMode, HubConfig, ActionConfig, } from './grid/index.js';
|
|
24
24
|
export { default as Icon } from './ui/Icon.svelte';
|
package/esm/svelte/index.js
CHANGED
|
@@ -10,7 +10,7 @@ export { toast } from './toast.js';
|
|
|
10
10
|
export { default as FF_ToastManager } from './FF_ToastManager.svelte';
|
|
11
11
|
export { SP } from './class/SP.svelte';
|
|
12
12
|
export { initRemultSvelteReactivity } from './initRemultSvelteReactivity';
|
|
13
|
-
export {
|
|
13
|
+
export { loadRepo, repoFetch } from './remultApiLoad.js';
|
|
14
14
|
// Headless cell primitives (FF_Grid / FF_Group are boutique-only — copy from src/boutique/grid).
|
|
15
15
|
export { FF_Cell, FF_CellValue, FF_Grid, GroupFields, DefaultInput, buildCells, displayCell, resolveCellComponent, getFieldMetaType, getInputType, getStyle, getCellElementConfig, defaultConfig, } from './grid/index.js';
|
|
16
16
|
export { default as Icon } from './ui/Icon.svelte';
|
|
@@ -1,25 +1,41 @@
|
|
|
1
|
-
import type { LoadEvent } from '@sveltejs/kit';
|
|
1
|
+
import type { LoadEvent, ServerLoadEvent } from '@sveltejs/kit';
|
|
2
|
+
import { repo as globalRepo } from 'remult';
|
|
3
|
+
export type Repo = typeof globalRepo;
|
|
2
4
|
/**
|
|
3
|
-
*
|
|
4
|
-
* (
|
|
5
|
-
*
|
|
5
|
+
* A `repo` whose data access goes through the API via the given fetch:
|
|
6
|
+
* API rules apply on SSR (as the current user), hydration replays the SSR
|
|
7
|
+
* responses in the browser (one single query), nothing global is mutated
|
|
8
|
+
* (safe when `+layout.ts` and `+page.ts` run in parallel), and the ambient
|
|
9
|
+
* remult (user, context) stays in charge. Inherits `remult.apiClient` config.
|
|
10
|
+
*
|
|
11
|
+
* Name the result so the intent shows at every query:
|
|
12
|
+
* ```ts
|
|
13
|
+
* const repoClient = repoFetch(event.fetch)
|
|
14
|
+
* await repoClient(Task).find()
|
|
15
|
+
* ```
|
|
6
16
|
*
|
|
7
17
|
* TODO(remult): framework-agnostic (only needs a fetch) - candidate to live in
|
|
8
18
|
* remult directly.
|
|
9
19
|
*/
|
|
10
|
-
export declare function
|
|
20
|
+
export declare function repoFetch(fetch: typeof globalThis.fetch, options?: {
|
|
21
|
+
url?: string;
|
|
22
|
+
}): Repo;
|
|
11
23
|
/**
|
|
12
|
-
* Wrap a
|
|
13
|
-
*
|
|
14
|
-
*
|
|
24
|
+
* Wrap a load: the callback receives a repo bound to `event.fetch`, then the event.
|
|
25
|
+
* Works for universal (`+page.ts`) AND server (`+page.server.ts`) loads.
|
|
26
|
+
*
|
|
27
|
+
* ```ts
|
|
28
|
+
* export const load = loadRepo(async (repoClient, { params }) => ({
|
|
29
|
+
* task: await repoClient(Task).findFirst({ id: params.id }),
|
|
30
|
+
* })) satisfies PageLoad // or PageServerLoad
|
|
31
|
+
* ```
|
|
15
32
|
*
|
|
16
|
-
* -
|
|
17
|
-
*
|
|
18
|
-
* the response.
|
|
19
|
-
* - CSR / hydration: points the client `remult` at this page's `event.fetch` so the
|
|
20
|
-
* inlined SSR response is reused, then runs the body.
|
|
33
|
+
* For server-only event members (`cookies`, `locals`, ...) annotate the event:
|
|
34
|
+
* `loadRepo(async (repoClient, event: ServerLoadEvent) => ...)`.
|
|
21
35
|
*
|
|
22
|
-
* (
|
|
23
|
-
*
|
|
36
|
+
* TODO(remult): candidate to live in remult - only the event default is
|
|
37
|
+
* SvelteKit-specific, the constraint itself is structural (`{ fetch }`).
|
|
24
38
|
*/
|
|
25
|
-
export declare function
|
|
39
|
+
export declare function loadRepo<R, Ev extends {
|
|
40
|
+
fetch: typeof globalThis.fetch;
|
|
41
|
+
} = LoadEvent | ServerLoadEvent>(loadFn: (repoClient: Repo, event: Ev) => R): (event: Ev) => R;
|
|
@@ -1,39 +1,44 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { repo as globalRepo, remult, RestDataProvider } from 'remult';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
* (
|
|
5
|
-
*
|
|
3
|
+
* A `repo` whose data access goes through the API via the given fetch:
|
|
4
|
+
* API rules apply on SSR (as the current user), hydration replays the SSR
|
|
5
|
+
* responses in the browser (one single query), nothing global is mutated
|
|
6
|
+
* (safe when `+layout.ts` and `+page.ts` run in parallel), and the ambient
|
|
7
|
+
* remult (user, context) stays in charge. Inherits `remult.apiClient` config.
|
|
8
|
+
*
|
|
9
|
+
* Name the result so the intent shows at every query:
|
|
10
|
+
* ```ts
|
|
11
|
+
* const repoClient = repoFetch(event.fetch)
|
|
12
|
+
* await repoClient(Task).find()
|
|
13
|
+
* ```
|
|
6
14
|
*
|
|
7
15
|
* TODO(remult): framework-agnostic (only needs a fetch) - candidate to live in
|
|
8
16
|
* remult directly.
|
|
9
17
|
*/
|
|
10
|
-
export function
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
18
|
+
export function repoFetch(fetch, options) {
|
|
19
|
+
const dp = new RestDataProvider(() => ({
|
|
20
|
+
...remult.apiClient,
|
|
21
|
+
httpClient: fetch,
|
|
22
|
+
...(options?.url ? { url: options.url } : {}),
|
|
23
|
+
}));
|
|
24
|
+
return (entity, dataProvider) => globalRepo(entity, dataProvider ?? dp);
|
|
16
25
|
}
|
|
17
26
|
/**
|
|
18
|
-
* Wrap a
|
|
19
|
-
*
|
|
20
|
-
*
|
|
27
|
+
* Wrap a load: the callback receives a repo bound to `event.fetch`, then the event.
|
|
28
|
+
* Works for universal (`+page.ts`) AND server (`+page.server.ts`) loads.
|
|
29
|
+
*
|
|
30
|
+
* ```ts
|
|
31
|
+
* export const load = loadRepo(async (repoClient, { params }) => ({
|
|
32
|
+
* task: await repoClient(Task).findFirst({ id: params.id }),
|
|
33
|
+
* })) satisfies PageLoad // or PageServerLoad
|
|
34
|
+
* ```
|
|
21
35
|
*
|
|
22
|
-
* -
|
|
23
|
-
*
|
|
24
|
-
* the response.
|
|
25
|
-
* - CSR / hydration: points the client `remult` at this page's `event.fetch` so the
|
|
26
|
-
* inlined SSR response is reused, then runs the body.
|
|
36
|
+
* For server-only event members (`cookies`, `locals`, ...) annotate the event:
|
|
37
|
+
* `loadRepo(async (repoClient, event: ServerLoadEvent) => ...)`.
|
|
27
38
|
*
|
|
28
|
-
* (
|
|
29
|
-
*
|
|
39
|
+
* TODO(remult): candidate to live in remult - only the event default is
|
|
40
|
+
* SvelteKit-specific, the constraint itself is structural (`{ fetch }`).
|
|
30
41
|
*/
|
|
31
|
-
export function
|
|
32
|
-
return (event) =>
|
|
33
|
-
if (!isBackend()) {
|
|
34
|
-
remult.apiClient.httpClient = event.fetch;
|
|
35
|
-
return body(event);
|
|
36
|
-
}
|
|
37
|
-
return withRemultFetch(event.fetch, () => body(event));
|
|
38
|
-
};
|
|
42
|
+
export function loadRepo(loadFn) {
|
|
43
|
+
return (event) => loadFn(repoFetch(event.fetch), event);
|
|
39
44
|
}
|
|
@@ -1,14 +1 @@
|
|
|
1
|
-
import type { ServerLoadEvent } from '@sveltejs/kit';
|
|
2
1
|
export { handleCaching } from './handleCaching.js';
|
|
3
|
-
/**
|
|
4
|
-
* Wrap a SvelteKit SERVER load (`+page.server.ts`) so plain global `repo()` reads
|
|
5
|
-
* apply the API gate (as the current user) instead of the privileged DB provider.
|
|
6
|
-
*
|
|
7
|
-
* Reads go through `event.fetch`: SvelteKit dispatches same-origin server fetch
|
|
8
|
-
* in-process (no network) and forwards cookies, so the app's real auth/hooks run
|
|
9
|
-
* for each read. Use it only when a server load should see exactly what the API
|
|
10
|
-
* exposes; otherwise a server load keeps the DB provider (gate rows with
|
|
11
|
-
* `backendPrefilter`). Gates direct `repo()` reads; BackendMethods keep their own
|
|
12
|
-
* `allowed` gate.
|
|
13
|
-
*/
|
|
14
|
-
export declare function remultApiServerLoad<T>(body: (event: ServerLoadEvent) => Promise<T>): (event: ServerLoadEvent) => Promise<T>;
|
|
@@ -1,16 +1 @@
|
|
|
1
|
-
import { withRemultFetch } from '../remultApiLoad.js';
|
|
2
1
|
export { handleCaching } from './handleCaching.js';
|
|
3
|
-
/**
|
|
4
|
-
* Wrap a SvelteKit SERVER load (`+page.server.ts`) so plain global `repo()` reads
|
|
5
|
-
* apply the API gate (as the current user) instead of the privileged DB provider.
|
|
6
|
-
*
|
|
7
|
-
* Reads go through `event.fetch`: SvelteKit dispatches same-origin server fetch
|
|
8
|
-
* in-process (no network) and forwards cookies, so the app's real auth/hooks run
|
|
9
|
-
* for each read. Use it only when a server load should see exactly what the API
|
|
10
|
-
* exposes; otherwise a server load keeps the DB provider (gate rows with
|
|
11
|
-
* `backendPrefilter`). Gates direct `repo()` reads; BackendMethods keep their own
|
|
12
|
-
* `allowed` gate.
|
|
13
|
-
*/
|
|
14
|
-
export function remultApiServerLoad(body) {
|
|
15
|
-
return (event) => withRemultFetch(event.fetch, () => body(event));
|
|
16
|
-
}
|