firstly 0.8.0 → 0.9.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 +12 -0
- 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/handleCaching.d.ts +24 -0
- package/esm/svelte/server/handleCaching.js +35 -0
- package/esm/svelte/server/index.d.ts +1 -13
- package/esm/svelte/server/index.js +1 -15
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# firstly
|
|
2
2
|
|
|
3
|
+
## 0.9.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#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.
|
|
8
|
+
|
|
9
|
+
## 0.8.1
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- [#335](https://github.com/jycouet/firstly/pull/335) [`c551a73`](https://github.com/jycouet/firstly/commit/c551a730969252e89b0a1d1a59a54580a380237b) Thanks [@jycouet](https://github.com/jycouet)! - Add `handleCaching` (`firstly/svelte/server`): a `hooks.server.js` handle setting deploy-safe `Cache-Control` headers - immutable for `/_app/immutable/*` 200s, no-store for HTML/API and all non-200s (a cached 404 chunk would brick the client until "disable cache").
|
|
14
|
+
|
|
3
15
|
## 0.8.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
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
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Handle } from '@sveltejs/kit';
|
|
2
|
+
/**
|
|
3
|
+
* `hooks.server.js` handle that sets `Cache-Control` so browsers and CDNs cache
|
|
4
|
+
* correctly across deploys - the server-side twin of `withStaleDeployReload`.
|
|
5
|
+
*
|
|
6
|
+
* - `/_app/immutable/*` (200 only) → `public, max-age=31536000, immutable`.
|
|
7
|
+
* Only this subtree is content-hashed; `/_app/version.json` and `/_app/env.js`
|
|
8
|
+
* are not, and MUST stay revalidatable or clients never see a new deploy.
|
|
9
|
+
* - Everything else non-asset (HTML, API) and every non-200 → `no-cache, no-store,
|
|
10
|
+
* must-revalidate`. Never let a 404 carry a long max-age: a chunk 404 during a
|
|
11
|
+
* deploy window cached for a year bricks that client until devtools "disable cache".
|
|
12
|
+
* - Other static assets (200, by extension) are left untouched for you to tune.
|
|
13
|
+
*
|
|
14
|
+
* Replaces purge-everything-on-deploy CDN strategies.
|
|
15
|
+
*
|
|
16
|
+
* ```js
|
|
17
|
+
* // src/hooks.server.js
|
|
18
|
+
* import { sequence } from '@sveltejs/kit/hooks'
|
|
19
|
+
* import { handleCaching } from './'
|
|
20
|
+
*
|
|
21
|
+
* export const handle = sequence(handleCaching, ...rest)
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export declare const handleCaching: Handle;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Cacheable static assets by extension (used to skip the no-cache header).
|
|
2
|
+
const STATIC_ASSET_RE = /\.(js|css|ico|png|jpg|jpeg|gif|svg|webp|avif|woff2?|ttf|eot)$/;
|
|
3
|
+
/**
|
|
4
|
+
* `hooks.server.js` handle that sets `Cache-Control` so browsers and CDNs cache
|
|
5
|
+
* correctly across deploys - the server-side twin of `withStaleDeployReload`.
|
|
6
|
+
*
|
|
7
|
+
* - `/_app/immutable/*` (200 only) → `public, max-age=31536000, immutable`.
|
|
8
|
+
* Only this subtree is content-hashed; `/_app/version.json` and `/_app/env.js`
|
|
9
|
+
* are not, and MUST stay revalidatable or clients never see a new deploy.
|
|
10
|
+
* - Everything else non-asset (HTML, API) and every non-200 → `no-cache, no-store,
|
|
11
|
+
* must-revalidate`. Never let a 404 carry a long max-age: a chunk 404 during a
|
|
12
|
+
* deploy window cached for a year bricks that client until devtools "disable cache".
|
|
13
|
+
* - Other static assets (200, by extension) are left untouched for you to tune.
|
|
14
|
+
*
|
|
15
|
+
* Replaces purge-everything-on-deploy CDN strategies.
|
|
16
|
+
*
|
|
17
|
+
* ```js
|
|
18
|
+
* // src/hooks.server.js
|
|
19
|
+
* import { sequence } from '@sveltejs/kit/hooks'
|
|
20
|
+
* import { handleCaching } from './'
|
|
21
|
+
*
|
|
22
|
+
* export const handle = sequence(handleCaching, ...rest)
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export const handleCaching = async ({ event, resolve }) => {
|
|
26
|
+
const response = await resolve(event);
|
|
27
|
+
const { pathname } = event.url;
|
|
28
|
+
if (pathname.startsWith('/_app/immutable/') && response.status === 200) {
|
|
29
|
+
response.headers.set('Cache-Control', 'public, max-age=31536000, immutable');
|
|
30
|
+
}
|
|
31
|
+
else if (response.status !== 200 || !STATIC_ASSET_RE.test(pathname)) {
|
|
32
|
+
response.headers.set('Cache-Control', 'no-cache, no-store, must-revalidate');
|
|
33
|
+
}
|
|
34
|
+
return response;
|
|
35
|
+
};
|
|
@@ -1,13 +1 @@
|
|
|
1
|
-
|
|
2
|
-
/**
|
|
3
|
-
* Wrap a SvelteKit SERVER load (`+page.server.ts`) so plain global `repo()` reads
|
|
4
|
-
* apply the API gate (as the current user) instead of the privileged DB provider.
|
|
5
|
-
*
|
|
6
|
-
* Reads go through `event.fetch`: SvelteKit dispatches same-origin server fetch
|
|
7
|
-
* in-process (no network) and forwards cookies, so the app's real auth/hooks run
|
|
8
|
-
* for each read. Use it only when a server load should see exactly what the API
|
|
9
|
-
* exposes; otherwise a server load keeps the DB provider (gate rows with
|
|
10
|
-
* `backendPrefilter`). Gates direct `repo()` reads; BackendMethods keep their own
|
|
11
|
-
* `allowed` gate.
|
|
12
|
-
*/
|
|
13
|
-
export declare function remultApiServerLoad<T>(body: (event: ServerLoadEvent) => Promise<T>): (event: ServerLoadEvent) => Promise<T>;
|
|
1
|
+
export { handleCaching } from './handleCaching.js';
|
|
@@ -1,15 +1 @@
|
|
|
1
|
-
|
|
2
|
-
/**
|
|
3
|
-
* Wrap a SvelteKit SERVER load (`+page.server.ts`) so plain global `repo()` reads
|
|
4
|
-
* apply the API gate (as the current user) instead of the privileged DB provider.
|
|
5
|
-
*
|
|
6
|
-
* Reads go through `event.fetch`: SvelteKit dispatches same-origin server fetch
|
|
7
|
-
* in-process (no network) and forwards cookies, so the app's real auth/hooks run
|
|
8
|
-
* for each read. Use it only when a server load should see exactly what the API
|
|
9
|
-
* exposes; otherwise a server load keeps the DB provider (gate rows with
|
|
10
|
-
* `backendPrefilter`). Gates direct `repo()` reads; BackendMethods keep their own
|
|
11
|
-
* `allowed` gate.
|
|
12
|
-
*/
|
|
13
|
-
export function remultApiServerLoad(body) {
|
|
14
|
-
return (event) => withRemultFetch(event.fetch, () => body(event));
|
|
15
|
-
}
|
|
1
|
+
export { handleCaching } from './handleCaching.js';
|