firstly 0.2.1 → 0.3.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/CHANGELOG.md +13 -0
- package/esm/bin/cmd.js +1 -156
- package/esm/changeLog/index.d.ts +1 -6
- package/esm/internals/BaseEnum.d.ts +1 -1
- package/esm/internals/FF_Entity.js +1 -17
- package/esm/internals/cellsBuildor.js +5 -4
- package/esm/internals/index.d.ts +2 -6
- package/esm/internals/storeItem.d.ts +12 -21
- package/esm/internals/storeItem.js +20 -6
- package/esm/svelte/FF_Repo.svelte.d.ts +9 -0
- package/esm/svelte/FF_Repo.svelte.js +39 -0
- package/esm/svelte/class/SP.svelte.js +14 -2
- package/esm/ui/Button.svelte +4 -52
- package/esm/ui/Button.svelte.d.ts +0 -2
- package/esm/ui/Field.svelte +10 -1
- package/esm/ui/Grid.svelte +2 -5
- package/esm/ui/Grid2.svelte +1 -4
- package/esm/ui/internals/select/MultiSelectMelt.svelte +4 -4
- package/esm/ui/internals/select/MultiSelectMelt.svelte.d.ts +1 -1
- package/esm/ui/internals/select/SelectMelt.svelte +28 -19
- package/esm/ui/internals/select/SelectMelt.svelte.d.ts +1 -1
- package/esm/ui/internals/select/SelectRadio.svelte +1 -1
- package/esm/ui/internals/select/SelectRadio.svelte.d.ts +1 -1
- package/package.json +6 -19
- package/esm/auth/AuthController.d.ts +0 -58
- package/esm/auth/AuthController.js +0 -114
- package/esm/auth/Entities.d.ts +0 -47
- package/esm/auth/Entities.js +0 -182
- package/esm/auth/README.md +0 -3
- package/esm/auth/index.d.ts +0 -5
- package/esm/auth/index.js +0 -5
- package/esm/auth/server/AuthController.server.d.ts +0 -58
- package/esm/auth/server/AuthController.server.js +0 -518
- package/esm/auth/server/handleAuth.d.ts +0 -4
- package/esm/auth/server/handleAuth.js +0 -142
- package/esm/auth/server/handleGuard.d.ts +0 -22
- package/esm/auth/server/handleGuard.js +0 -34
- package/esm/auth/server/helperDb.d.ts +0 -10
- package/esm/auth/server/helperDb.js +0 -56
- package/esm/auth/server/helperFirstly.d.ts +0 -1
- package/esm/auth/server/helperFirstly.js +0 -9
- package/esm/auth/server/helperOslo.d.ts +0 -7
- package/esm/auth/server/helperOslo.js +0 -24
- package/esm/auth/server/helperRemultServer.d.ts +0 -5
- package/esm/auth/server/helperRemultServer.js +0 -44
- package/esm/auth/server/helperRole.d.ts +0 -19
- package/esm/auth/server/helperRole.js +0 -57
- package/esm/auth/server/index.d.ts +0 -8
- package/esm/auth/server/index.js +0 -8
- package/esm/auth/server/module.d.ts +0 -300
- package/esm/auth/server/module.js +0 -230
- package/esm/auth/server/providers/github.d.ts +0 -33
- package/esm/auth/server/providers/github.js +0 -87
- package/esm/auth/server/providers/helperProvider.d.ts +0 -1
- package/esm/auth/server/providers/helperProvider.js +0 -25
- package/esm/auth/static/assets/Page-BHW08QWz.css +0 -1
- package/esm/auth/static/assets/Page-BRNWcY5Z.d.ts +0 -2
- package/esm/auth/static/assets/Page-BRNWcY5Z.js +0 -1
- package/esm/auth/static/assets/Page-CFcEsGK8.d.ts +0 -2
- package/esm/auth/static/assets/Page-CFcEsGK8.js +0 -7
- package/esm/auth/static/assets/Page-tLVs5slF.d.ts +0 -2
- package/esm/auth/static/assets/Page-tLVs5slF.js +0 -1
- package/esm/auth/static/assets/index-D38rqu4x.d.ts +0 -201
- package/esm/auth/static/assets/index-D38rqu4x.js +0 -2
- package/esm/auth/static/assets/index-DKWpA6v7.css +0 -4
- package/esm/auth/static/favicon.svg +0 -79
- package/esm/auth/static/index.html +0 -13
- package/esm/auth/types.d.ts +0 -73
- package/esm/auth/types.js +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# firstly
|
|
2
2
|
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#232](https://github.com/jycouet/firstly/pull/232) [`fec8bc0`](https://github.com/jycouet/firstly/commit/fec8bc088733d63ce6752b0a764b786f80b736cb) Thanks [@jycouet](https://github.com/jycouet)! - BREAKING: Remove deprecated lucia-style auth module (`firstly/auth`, `firstly/auth/server`).
|
|
8
|
+
|
|
9
|
+
Migrate to `better-auth` (see remult docs). Removed deps: `@oslojs/*`, `arctic`, `bcryptjs`.
|
|
10
|
+
Also removed `packages/ui` (was only used for the auth UI).
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- [#221](https://github.com/jycouet/firstly/pull/221) [`0b08040`](https://github.com/jycouet/firstly/commit/0b0804001c9a5bdff560fbcbb8b511c626d260f8) Thanks [@jycouet](https://github.com/jycouet)! - bump deps
|
|
15
|
+
|
|
3
16
|
## 0.2.1
|
|
4
17
|
|
|
5
18
|
### Patch Changes
|
package/esm/bin/cmd.js
CHANGED
|
@@ -11,7 +11,6 @@ const pkg = JSON.parse(read('./package.json') ?? '{}');
|
|
|
11
11
|
const version = pkg.devDependencies?.['firstly'] ?? pkg.dependencies?.['firstly'] ?? '???';
|
|
12
12
|
console.info('');
|
|
13
13
|
p.intro(`${green(`⚡️`)} Welcome to firstly world! ${gray(` - v${version}`)}`);
|
|
14
|
-
const keys = ['all', 'module-demo', 'dependencies'];
|
|
15
14
|
const options = [
|
|
16
15
|
{
|
|
17
16
|
value: 'all',
|
|
@@ -142,11 +141,6 @@ export default {
|
|
|
142
141
|
'.env.example': [
|
|
143
142
|
`# Enable some roles
|
|
144
143
|
# FF_ADMIN = 'JYC'
|
|
145
|
-
# FF_AUTH_ADMIN = ''
|
|
146
|
-
|
|
147
|
-
# Enable GitHub login
|
|
148
|
-
# GITHUB_CLIENT_ID = ''
|
|
149
|
-
# GITHUB_CLIENT_SECRET = ''
|
|
150
144
|
`,
|
|
151
145
|
],
|
|
152
146
|
'./tsconfig.json': [
|
|
@@ -185,7 +179,6 @@ export default defineConfig({
|
|
|
185
179
|
firstly<KIT_ROUTES>({
|
|
186
180
|
kitRoutes: {
|
|
187
181
|
LINKS: {
|
|
188
|
-
login: 'ff/auth/sign-in',
|
|
189
182
|
github: 'https://github.com/[owner]/[repo]',
|
|
190
183
|
remult_admin: 'api/admin',
|
|
191
184
|
},
|
|
@@ -198,13 +191,9 @@ export default defineConfig({
|
|
|
198
191
|
],
|
|
199
192
|
'./svelte.config.js': [
|
|
200
193
|
`import adapter from '@sveltejs/adapter-auto'
|
|
201
|
-
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
|
|
202
194
|
|
|
203
195
|
/** @type {import('@sveltejs/kit').Config} */
|
|
204
196
|
const config = {
|
|
205
|
-
// Consult https://svelte.dev/docs/kit/integrations
|
|
206
|
-
// for more information about preprocessors
|
|
207
|
-
preprocess: vitePreprocess(),
|
|
208
197
|
|
|
209
198
|
kit: {
|
|
210
199
|
// adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
|
|
@@ -251,7 +240,6 @@ export { }
|
|
|
251
240
|
'./src/server/index.ts': [
|
|
252
241
|
`import { FF_Role } from '../internals'
|
|
253
242
|
import { firstly, Module } from '../server'
|
|
254
|
-
import { auth } from '../auth/server'
|
|
255
243
|
import { mail } from '../mail/server'
|
|
256
244
|
import { changeLog } from '../changeLog/server'
|
|
257
245
|
|
|
@@ -259,44 +247,7 @@ import { log, Role } from '${libAlias}'
|
|
|
259
247
|
import { task } from '${modulesAlias}/task/server'
|
|
260
248
|
|
|
261
249
|
export const api = firstly({
|
|
262
|
-
//----------------------------------------
|
|
263
|
-
// To switch to postgres
|
|
264
|
-
// NEEDS ON TOP OF THE FILE:
|
|
265
|
-
// import { createPostgresConnection } from 'remult/postgres'
|
|
266
|
-
// import { DATABASE_URL } from '$env/static/private'
|
|
267
|
-
//----------------------------------------
|
|
268
|
-
// dataProvider: await createPostgresConnection({
|
|
269
|
-
// connectionString: DATABASE_URL,
|
|
270
|
-
// }),
|
|
271
|
-
|
|
272
250
|
modules: [
|
|
273
|
-
//----------------------------------------
|
|
274
|
-
// Core Module: auth
|
|
275
|
-
//----------------------------------------
|
|
276
|
-
auth({
|
|
277
|
-
providers: {
|
|
278
|
-
demo: [
|
|
279
|
-
{ name: 'Ermin' },
|
|
280
|
-
{ name: 'JYC', roles: [FF_Role.FF_Role_Admin] },
|
|
281
|
-
{ name: 'Noam', roles: [FF_Role.FF_Role_Admin, Role.Boss] },
|
|
282
|
-
],
|
|
283
|
-
|
|
284
|
-
// password: {},
|
|
285
|
-
|
|
286
|
-
// otp: {},
|
|
287
|
-
|
|
288
|
-
oAuths: [
|
|
289
|
-
//----------------------------------------
|
|
290
|
-
// To enable OAuth via Github
|
|
291
|
-
// Instructions by hovering the method \`github\`
|
|
292
|
-
// NEEDS ON TOP OF THE FILE:
|
|
293
|
-
// import { github } from '../auth/server'
|
|
294
|
-
//----------------------------------------
|
|
295
|
-
// github(),
|
|
296
|
-
],
|
|
297
|
-
},
|
|
298
|
-
}),
|
|
299
|
-
|
|
300
251
|
//----------------------------------------
|
|
301
252
|
// Core Module: mail
|
|
302
253
|
//----------------------------------------
|
|
@@ -331,25 +282,11 @@ export const api = firstly({
|
|
|
331
282
|
],
|
|
332
283
|
'./src/hooks.server.ts': [
|
|
333
284
|
`import { sequence } from '@sveltejs/kit/hooks'
|
|
334
|
-
import { redirect } from '@sveltejs/kit'
|
|
335
285
|
|
|
336
|
-
import { handleAuth, handleGuard } from '../auth/server'
|
|
337
|
-
import { route } from '${libAlias}/ROUTES'
|
|
338
286
|
import { api as handleRemult } from '${serverAlias}'
|
|
339
287
|
|
|
340
288
|
export const handle = sequence(
|
|
341
|
-
//
|
|
342
289
|
handleRemult,
|
|
343
|
-
handleAuth,
|
|
344
|
-
// client side guard is not here!
|
|
345
|
-
handleGuard({
|
|
346
|
-
authenticated: ['/app*'],
|
|
347
|
-
redirectToLogin: route('/'),
|
|
348
|
-
// You want to redirect to the firstly UI ? change redirectToLogin to this 👇
|
|
349
|
-
// redirectToLogin: route('login'),
|
|
350
|
-
redirectAuthenticated: route('/app'),
|
|
351
|
-
redirect,
|
|
352
|
-
})
|
|
353
290
|
)
|
|
354
291
|
`,
|
|
355
292
|
],
|
|
@@ -390,8 +327,6 @@ export const load = (async (event) => {
|
|
|
390
327
|
import { Remult, remult } from 'remult'
|
|
391
328
|
|
|
392
329
|
import { route } from '${libAlias}/ROUTES'
|
|
393
|
-
import SignIn from '${libAlias}/ui/SignIn.svelte'
|
|
394
|
-
import SignOut from '${libAlias}/ui/SignOut.svelte'
|
|
395
330
|
|
|
396
331
|
import type { LayoutData } from './$types'
|
|
397
332
|
|
|
@@ -453,26 +388,10 @@ import type { LayoutData } from './$types'
|
|
|
453
388
|
<h1>${pkg.name}</h1>
|
|
454
389
|
|
|
455
390
|
{#if remult.authenticated()}
|
|
456
|
-
<div style="float:right;">
|
|
457
|
-
<SignOut></SignOut>
|
|
458
|
-
</div>
|
|
459
391
|
<span>{remult.user?.name} ({remult.user?.roles})<br /><br /></span>
|
|
460
|
-
{:else}
|
|
461
|
-
<SignIn demo="Ermin"></SignIn>
|
|
462
|
-
<SignIn demo="JYC"></SignIn>
|
|
463
|
-
<SignIn demo="Noam"></SignIn>
|
|
464
|
-
<br />
|
|
465
|
-
<SignIn ffLink></SignIn>
|
|
466
|
-
<br />
|
|
467
|
-
<SignIn oauth="github"></SignIn>
|
|
468
392
|
{/if}
|
|
469
393
|
|
|
470
|
-
<
|
|
471
|
-
|
|
472
|
-
<a href={route('/')}>Home</a> |
|
|
473
|
-
{#if remult.authenticated()}
|
|
474
|
-
<a href={route('/app')}>App (Protected route)</a> |
|
|
475
|
-
{/if}
|
|
394
|
+
<a href={route('/')}>Home</a> |
|
|
476
395
|
<a href={route('/demo/task')}>Demo task</a>
|
|
477
396
|
|
|
478
397
|
<hr />
|
|
@@ -523,7 +442,6 @@ export const load = (async (event) => {
|
|
|
523
442
|
// Lib files
|
|
524
443
|
'./src/lib/index.ts': [
|
|
525
444
|
`import { FF_Role } from '../internals'
|
|
526
|
-
import { FF_Role_Auth } from '../auth'
|
|
527
445
|
import { Log } from '@kitql/helpers'
|
|
528
446
|
|
|
529
447
|
/**
|
|
@@ -536,81 +454,8 @@ export const log = new Log('${pkg.name}')
|
|
|
536
454
|
*/
|
|
537
455
|
export const Role = {
|
|
538
456
|
Boss: 'Boss',
|
|
539
|
-
...FF_Role_Auth,
|
|
540
457
|
...FF_Role,
|
|
541
458
|
} as const
|
|
542
|
-
`,
|
|
543
|
-
],
|
|
544
|
-
'./src/lib/ui/SignIn.svelte': [
|
|
545
|
-
`<script lang="ts">
|
|
546
|
-
import { isError } from '../internals'
|
|
547
|
-
import { AuthController } from '../auth'
|
|
548
|
-
|
|
549
|
-
import { goto, invalidateAll } from '$app/navigation'
|
|
550
|
-
|
|
551
|
-
import { route } from '${libAlias}/ROUTES'
|
|
552
|
-
|
|
553
|
-
// Examples of signin modes
|
|
554
|
-
export let demo = ''
|
|
555
|
-
export let ffLink = false
|
|
556
|
-
export let oauth: 'github' | undefined = undefined
|
|
557
|
-
|
|
558
|
-
const signinDemo = async (identif: string) => {
|
|
559
|
-
try {
|
|
560
|
-
await AuthController.signInDemo(identif)
|
|
561
|
-
invalidateAll()
|
|
562
|
-
} catch (error) {
|
|
563
|
-
if (isError(error)) {
|
|
564
|
-
// You will probably not leave this alert in production
|
|
565
|
-
alert(error.message)
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
async function signinOAuth(provider: 'github') {
|
|
571
|
-
try {
|
|
572
|
-
window.location.href = await AuthController.signInOAuthGetUrl({
|
|
573
|
-
provider,
|
|
574
|
-
redirect: window.location.pathname,
|
|
575
|
-
})
|
|
576
|
-
} catch (error) {
|
|
577
|
-
if (isError(error)) {
|
|
578
|
-
// You will probably not leave this alert in production
|
|
579
|
-
alert(error.message)
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
</script>
|
|
584
|
-
|
|
585
|
-
{#if demo}
|
|
586
|
-
<button on:click={() => signinDemo(demo)}>Login as {demo}</button>
|
|
587
|
-
{:else if ffLink}
|
|
588
|
-
<button on:click={() => goto(route('login'))}>Login with Firstly UI</button>
|
|
589
|
-
{:else if oauth}
|
|
590
|
-
<button on:click={() => signinOAuth(oauth)}>Login With {oauth}</button>
|
|
591
|
-
{/if}
|
|
592
|
-
`,
|
|
593
|
-
],
|
|
594
|
-
'./src/lib/ui/SignOut.svelte': [
|
|
595
|
-
`<script lang="ts">
|
|
596
|
-
import { isError } from '../internals'
|
|
597
|
-
import { AuthController } from '../auth'
|
|
598
|
-
|
|
599
|
-
import { invalidateAll } from '$app/navigation'
|
|
600
|
-
|
|
601
|
-
const logout = async () => {
|
|
602
|
-
try {
|
|
603
|
-
await AuthController.signOut()
|
|
604
|
-
invalidateAll()
|
|
605
|
-
} catch (error) {
|
|
606
|
-
if (isError(error)) {
|
|
607
|
-
alert(error.message)
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
</script>
|
|
612
|
-
|
|
613
|
-
<button on:click={logout}>Logout</button>
|
|
614
459
|
`,
|
|
615
460
|
],
|
|
616
461
|
// Task module
|
package/esm/changeLog/index.d.ts
CHANGED
|
@@ -45,15 +45,10 @@ export declare const withChangeLog: <entityType>(options?: EntityOptions<entityT
|
|
|
45
45
|
entityRefInit?: ((ref: import("remult").EntityRef<entityType>, row: entityType) => void) | undefined;
|
|
46
46
|
apiRequireId?: import("remult").Allowed;
|
|
47
47
|
dataProvider?: ((defaultDataProvider: import("remult").DataProvider) => import("remult").DataProvider | Promise<import("remult").DataProvider> | undefined | null) | undefined;
|
|
48
|
+
changeLog?: false | ColumnDeciderArgs<entityType> | undefined;
|
|
48
49
|
ui?: {
|
|
49
50
|
getLayout?: import("../svelte/customField").getLayout<entityType> | undefined;
|
|
50
51
|
} | undefined;
|
|
51
52
|
searchableFind?: ((str: string) => import("remult").FindOptionsBase<entityType>) | undefined;
|
|
52
53
|
displayValue?: ((item: entityType) => import("../internals").BaseItem) | undefined;
|
|
53
|
-
permissionApiCrud?: import("../internals").BaseEnum[] | import("../internals").BaseEnum;
|
|
54
|
-
permissionApiDelete?: import("../internals").BaseEnum[] | import("../internals").BaseEnum;
|
|
55
|
-
permissionApiInsert?: import("../internals").BaseEnum[] | import("../internals").BaseEnum;
|
|
56
|
-
permissionApiRead?: import("../internals").BaseEnum[] | import("../internals").BaseEnum;
|
|
57
|
-
permissionApiUpdate?: import("../internals").BaseEnum[] | import("../internals").BaseEnum;
|
|
58
|
-
changeLog?: false | ColumnDeciderArgs<entityType> | undefined;
|
|
59
54
|
};
|
|
@@ -1,21 +1,5 @@
|
|
|
1
1
|
import { Entity } from 'remult';
|
|
2
2
|
import { withChangeLog } from '../changeLog';
|
|
3
|
-
const toAllow = (permission) => {
|
|
4
|
-
if (permission) {
|
|
5
|
-
if (Array.isArray(permission)) {
|
|
6
|
-
return permission.map((p) => p.id);
|
|
7
|
-
}
|
|
8
|
-
return permission.id;
|
|
9
|
-
}
|
|
10
|
-
return undefined;
|
|
11
|
-
};
|
|
12
3
|
export function FF_Entity(key, options) {
|
|
13
|
-
return Entity(key, withChangeLog({
|
|
14
|
-
...options,
|
|
15
|
-
allowApiCrud: options?.allowApiCrud ?? toAllow(options?.permissionApiCrud),
|
|
16
|
-
allowApiDelete: options?.allowApiDelete ?? toAllow(options?.permissionApiDelete),
|
|
17
|
-
allowApiInsert: options?.allowApiInsert ?? toAllow(options?.permissionApiInsert),
|
|
18
|
-
allowApiRead: options?.allowApiRead ?? toAllow(options?.permissionApiRead),
|
|
19
|
-
allowApiUpdate: options?.allowApiUpdate ?? toAllow(options?.permissionApiUpdate),
|
|
20
|
-
}));
|
|
4
|
+
return Entity(key, withChangeLog({ ...options }));
|
|
21
5
|
}
|
|
@@ -98,9 +98,10 @@ export const buildWhere = (entity, defaultWhere, fields_filter, fields_search, o
|
|
|
98
98
|
and.push(...buildSearchWhere(entity, fields_search, obj.search));
|
|
99
99
|
}
|
|
100
100
|
for (const field of fields_filter) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
101
|
+
const rfi = getRelationFieldInfo(field);
|
|
102
|
+
// For relation fields, allow null as valid filter value (filters for NULL in DB)
|
|
103
|
+
const hasValue = rfi?.type === 'toOne' ? obj && obj[field.key] !== undefined : obj && obj[field.key];
|
|
104
|
+
if (hasValue) {
|
|
104
105
|
if (field.inputType === 'checkbox') {
|
|
105
106
|
// @ts-ignore
|
|
106
107
|
and.push({ [field.key]: obj[field.key] });
|
|
@@ -127,7 +128,7 @@ export const buildWhere = (entity, defaultWhere, fields_filter, fields_search, o
|
|
|
127
128
|
}
|
|
128
129
|
}
|
|
129
130
|
else if (rfi?.type === 'toOne') {
|
|
130
|
-
// @ts-ignore (setting the id of the relation)
|
|
131
|
+
// @ts-ignore (setting the id of the relation, null = filter for NULL)
|
|
131
132
|
and.push({ [field.key]: obj[field.key] });
|
|
132
133
|
}
|
|
133
134
|
else {
|
package/esm/internals/index.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ import { default as Link } from '../ui/link/Link.svelte';
|
|
|
18
18
|
import { default as LinkPlus } from '../ui/link/LinkPlus.svelte';
|
|
19
19
|
import { default as Loading } from '../ui/Loading.svelte';
|
|
20
20
|
import { default as Tooltip } from '../ui/Tooltip.svelte';
|
|
21
|
-
import type {
|
|
21
|
+
import type { BaseItem, BaseItemLight, FF_Icon } from './BaseEnum.js';
|
|
22
22
|
import type { CellsInput } from './cellsBuildor.js';
|
|
23
23
|
import { FF_Role } from './common.js';
|
|
24
24
|
import { storeItem, type StoreItem } from './storeItem.js';
|
|
@@ -67,16 +67,12 @@ declare module 'remult' {
|
|
|
67
67
|
};
|
|
68
68
|
default_select_if_one_item?: boolean;
|
|
69
69
|
multiSelect?: boolean;
|
|
70
|
+
filterNobodyLabel?: string;
|
|
70
71
|
skipForDefaultField?: boolean;
|
|
71
72
|
}
|
|
72
73
|
interface EntityOptions<entityType> {
|
|
73
74
|
searchableFind?: (str: string) => FindOptionsBase<entityType>;
|
|
74
75
|
displayValue?: (item: entityType) => BaseItem;
|
|
75
|
-
permissionApiCrud?: BaseEnum[] | BaseEnum;
|
|
76
|
-
permissionApiDelete?: BaseEnum[] | BaseEnum;
|
|
77
|
-
permissionApiInsert?: BaseEnum[] | BaseEnum;
|
|
78
|
-
permissionApiRead?: BaseEnum[] | BaseEnum;
|
|
79
|
-
permissionApiUpdate?: BaseEnum[] | BaseEnum;
|
|
80
76
|
changeLog?: false | ColumnDeciderArgs<entityType>;
|
|
81
77
|
}
|
|
82
78
|
}
|
|
@@ -1,28 +1,19 @@
|
|
|
1
|
-
import type { ErrorInfo, FindOptions, Repository } from 'remult';
|
|
2
|
-
export
|
|
3
|
-
|
|
4
|
-
item: T | undefined;
|
|
5
|
-
loading?: boolean;
|
|
6
|
-
errors: ErrorInfo<T> | undefined;
|
|
7
|
-
globalError?: string | undefined;
|
|
8
|
-
};
|
|
9
|
-
export declare const storeItem: <T>(repo: Repository<T>, initValues?: TheStoreItem<T>) => {
|
|
10
|
-
subscribe: (this: void, run: import("svelte/store").Subscriber<TheStoreItem<T>>, invalidate?: () => void) => import("svelte/store").Unsubscriber;
|
|
1
|
+
import type { EntityFilter, ErrorInfo, FindOptions, Repository } from 'remult';
|
|
2
|
+
export interface StoreItem<T> {
|
|
3
|
+
subscribe: (run: (value: TheStoreItem<T>) => void) => () => void;
|
|
11
4
|
create: (item: Partial<T>) => void;
|
|
12
5
|
set: (newItem: TheStoreItem<T>) => void;
|
|
13
|
-
|
|
14
|
-
* if you have keys, build the id with
|
|
15
|
-
* ```ts
|
|
16
|
-
* const id = repo.metadata.idMetadata.getId({a:1,b:2})
|
|
17
|
-
* store.fetch(id)
|
|
18
|
-
* ```
|
|
19
|
-
*/
|
|
20
|
-
fetch: (id: Parameters<Repository<T>["findId"]>[0], options?: FindOptions<T>, onNewData?: (item: T) => void) => Promise<void>;
|
|
21
|
-
/**
|
|
22
|
-
* `.save()` will `update` or `insert` the current item.
|
|
23
|
-
*/
|
|
6
|
+
fetch: (idOrWhere: string | number | EntityFilter<T>, options?: FindOptions<T>, onNewData?: (item: T | undefined) => void) => Promise<void>;
|
|
24
7
|
save: () => Promise<T | undefined>;
|
|
25
8
|
delete: () => Promise<void>;
|
|
26
9
|
onChange: (prop: keyof T, callback: (newValue: any, oldValue: any) => void) => void;
|
|
10
|
+
}
|
|
11
|
+
type TheStoreItem<T> = {
|
|
12
|
+
item: T | undefined;
|
|
13
|
+
loading?: boolean;
|
|
14
|
+
initLoading?: boolean;
|
|
15
|
+
errors: ErrorInfo<T> | undefined;
|
|
16
|
+
globalError?: string | undefined;
|
|
27
17
|
};
|
|
18
|
+
export declare const storeItem: <T>(repo: Repository<T>, initValues?: TheStoreItem<T>) => StoreItem<T>;
|
|
28
19
|
export {};
|
|
@@ -5,6 +5,7 @@ import { isError } from './helper.js';
|
|
|
5
5
|
export const storeItem = (repo, initValues = {
|
|
6
6
|
item: undefined,
|
|
7
7
|
loading: true,
|
|
8
|
+
initLoading: true,
|
|
8
9
|
errors: undefined,
|
|
9
10
|
globalError: undefined,
|
|
10
11
|
}) => {
|
|
@@ -30,38 +31,50 @@ export const storeItem = (repo, initValues = {
|
|
|
30
31
|
internalStore.set({
|
|
31
32
|
item: repo.create(item),
|
|
32
33
|
loading: false,
|
|
34
|
+
initLoading: false,
|
|
33
35
|
errors: {},
|
|
34
36
|
globalError: undefined,
|
|
35
37
|
});
|
|
36
38
|
},
|
|
37
39
|
// set: internalStore.set,
|
|
38
40
|
set: (newItem) => {
|
|
39
|
-
internalStore.update((
|
|
41
|
+
internalStore.update(() => {
|
|
40
42
|
return { ...newItem };
|
|
41
43
|
});
|
|
42
44
|
},
|
|
43
45
|
/**
|
|
44
|
-
*
|
|
46
|
+
* Fetch by ID or WHERE clause
|
|
45
47
|
* ```ts
|
|
48
|
+
* // By ID (string or number)
|
|
49
|
+
* store.fetch(123)
|
|
50
|
+
* store.fetch('abc')
|
|
51
|
+
*
|
|
52
|
+
* // By WHERE clause (object)
|
|
53
|
+
* store.fetch({ siteId: 123 })
|
|
54
|
+
*
|
|
55
|
+
* // With composite keys, build the id with
|
|
46
56
|
* const id = repo.metadata.idMetadata.getId({a:1,b:2})
|
|
47
57
|
* store.fetch(id)
|
|
48
58
|
* ```
|
|
49
59
|
*/
|
|
50
|
-
fetch: async (
|
|
60
|
+
fetch: async (idOrWhere, options, onNewData) => {
|
|
51
61
|
if (BROWSER) {
|
|
52
62
|
internalStore.update((s) => ({ ...s, loading: true }));
|
|
53
63
|
try {
|
|
54
|
-
const
|
|
55
|
-
|
|
64
|
+
const isId = typeof idOrWhere === 'string' || typeof idOrWhere === 'number';
|
|
65
|
+
const item = isId
|
|
66
|
+
? await repo.findId(idOrWhere, options)
|
|
67
|
+
: await repo.findFirst(idOrWhere, options);
|
|
56
68
|
internalStore.update((s) => ({
|
|
57
69
|
...s,
|
|
58
70
|
loading: false,
|
|
71
|
+
initLoading: false,
|
|
59
72
|
item: item ?? {},
|
|
60
73
|
errors: undefined,
|
|
61
74
|
globalError: undefined,
|
|
62
75
|
}));
|
|
63
76
|
if (onNewData) {
|
|
64
|
-
onNewData(item ??
|
|
77
|
+
onNewData(item ?? undefined);
|
|
65
78
|
}
|
|
66
79
|
}
|
|
67
80
|
catch (error) {
|
|
@@ -69,6 +82,7 @@ export const storeItem = (repo, initValues = {
|
|
|
69
82
|
internalStore.update((s) => ({
|
|
70
83
|
...s,
|
|
71
84
|
loading: false,
|
|
85
|
+
initLoading: false,
|
|
72
86
|
item: {},
|
|
73
87
|
errors: {},
|
|
74
88
|
// @ts-ignore
|
|
@@ -62,6 +62,15 @@ export declare class FF_Repo<Entity, QueryOptions extends QueryOptionsHelper<Ent
|
|
|
62
62
|
aggregates: ExtractAggregateResult<Entity, QueryOptions>;
|
|
63
63
|
hasNextPage: boolean;
|
|
64
64
|
} | undefined>;
|
|
65
|
+
/**
|
|
66
|
+
* Refresh query keeping current items count (BIG refresh)
|
|
67
|
+
* Useful after edit/delete to stay at current scroll position
|
|
68
|
+
*/
|
|
69
|
+
queryRefresh(options: Pick<QueryOptionsHelper<Entity>, 'where' | 'orderBy'>): Promise<{
|
|
70
|
+
items: Entity[];
|
|
71
|
+
aggregates: ExtractAggregateResult<Entity, QueryOptions>;
|
|
72
|
+
hasNextPage: boolean;
|
|
73
|
+
} | undefined>;
|
|
65
74
|
create(...args: Parameters<Repository<Entity>['create']>): Entity;
|
|
66
75
|
delete(...args: Parameters<Repository<Entity>['delete']>): Promise<undefined>;
|
|
67
76
|
getLayout: getLayoutStrict<Entity>;
|
|
@@ -132,6 +132,45 @@ export class FF_Repo {
|
|
|
132
132
|
hasNextPage: this.hasNextPage,
|
|
133
133
|
});
|
|
134
134
|
}
|
|
135
|
+
/**
|
|
136
|
+
* Refresh query keeping current items count (BIG refresh)
|
|
137
|
+
* Useful after edit/delete to stay at current scroll position
|
|
138
|
+
*/
|
|
139
|
+
async queryRefresh(options) {
|
|
140
|
+
const currentCount = this.items?.length ?? this.#queryOptions?.pageSize ?? 25;
|
|
141
|
+
this.loading = {
|
|
142
|
+
...this.loading,
|
|
143
|
+
fetching: true,
|
|
144
|
+
init: this.items === undefined,
|
|
145
|
+
};
|
|
146
|
+
const { data: queryResult, error: queryResultError } = tryCatchSync(() => this.#repo.query({
|
|
147
|
+
...this.#queryOptions,
|
|
148
|
+
...options,
|
|
149
|
+
pageSize: currentCount,
|
|
150
|
+
aggregate: {
|
|
151
|
+
...this.#queryOptions?.aggregate,
|
|
152
|
+
},
|
|
153
|
+
}));
|
|
154
|
+
if (queryResultError) {
|
|
155
|
+
this.globalError = queryResultError.message;
|
|
156
|
+
return this.loadingEnd();
|
|
157
|
+
}
|
|
158
|
+
const { data: paginator, error: paginatorError } = await tryCatch(queryResult.paginator());
|
|
159
|
+
if (paginatorError) {
|
|
160
|
+
this.globalError = paginatorError.message;
|
|
161
|
+
return this.loadingEnd();
|
|
162
|
+
}
|
|
163
|
+
this.#paginator = paginator;
|
|
164
|
+
this.items = this.#paginator.items;
|
|
165
|
+
// @ts-expect-error - We know the structure will match due to how we define the types
|
|
166
|
+
this.aggregates = this.#paginator.aggregates;
|
|
167
|
+
this.hasNextPage = this.#paginator.hasNextPage && this.aggregates.$count > this.items.length;
|
|
168
|
+
return this.loadingEnd({
|
|
169
|
+
items: this.items,
|
|
170
|
+
aggregates: this.aggregates,
|
|
171
|
+
hasNextPage: this.hasNextPage,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
135
174
|
create(...args) {
|
|
136
175
|
this.item = this.#repo.create(...args);
|
|
137
176
|
return this.item;
|
|
@@ -7,6 +7,7 @@ import { goto } from '$app/navigation';
|
|
|
7
7
|
import { page } from '$app/state';
|
|
8
8
|
import { debounce } from '../helpers/debounce.js';
|
|
9
9
|
const CONFIG_DELIMITER = ';';
|
|
10
|
+
const NULL_URL_VALUE = '__null__';
|
|
10
11
|
/**
|
|
11
12
|
* SearchParams class for handling URL search parameters with Svelte 5 runes
|
|
12
13
|
* Provides automatic binding and URL updates
|
|
@@ -257,6 +258,12 @@ export class SP {
|
|
|
257
258
|
const urlKey = this.keyMap[propKey]; // Get the URL parameter key
|
|
258
259
|
const paramValue = params.get(urlKey);
|
|
259
260
|
if (paramValue !== null) {
|
|
261
|
+
// Decode null sentinel from URL
|
|
262
|
+
if (paramValue === NULL_URL_VALUE) {
|
|
263
|
+
this.paramValues[propKey] = null;
|
|
264
|
+
this.debouncedValues[propKey] = null;
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
260
267
|
// If there is a decode function, always use it to get the proper object
|
|
261
268
|
if (def.decode) {
|
|
262
269
|
const decodedValue = def.decode(paramValue);
|
|
@@ -314,11 +321,16 @@ export class SP {
|
|
|
314
321
|
}
|
|
315
322
|
const params = new URLSearchParams(window.location.search);
|
|
316
323
|
for (const [propKey, value] of Object.entries(this.debouncedValues)) {
|
|
317
|
-
// Skip undefined
|
|
318
|
-
if (value === undefined
|
|
324
|
+
// Skip undefined values
|
|
325
|
+
if (value === undefined) {
|
|
319
326
|
params.delete(this.keyMap[propKey]);
|
|
320
327
|
continue;
|
|
321
328
|
}
|
|
329
|
+
// Encode null as special URL value
|
|
330
|
+
if (value === null) {
|
|
331
|
+
params.set(this.keyMap[propKey], NULL_URL_VALUE);
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
322
334
|
// Get the definition and URL key
|
|
323
335
|
const def = this.config[propKey];
|
|
324
336
|
if (!def)
|