@btst/stack 3.0.2 → 3.1.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/dist/context/index.d.cts +3 -1
- package/dist/context/index.d.mts +3 -1
- package/dist/context/index.d.ts +3 -1
- package/dist/packages/stack/src/context/auth.cjs +20 -14
- package/dist/packages/stack/src/context/auth.mjs +20 -14
- package/dist/packages/stack/src/context/provider.cjs +42 -22
- package/dist/packages/stack/src/context/provider.mjs +43 -23
- package/dist/packages/stack/src/plugins/blog/client/components/pages/home-page.cjs +32 -28
- package/dist/packages/stack/src/plugins/blog/client/components/pages/home-page.mjs +32 -29
- package/dist/packages/stack/src/plugins/blog/client/components/pages/post-page.cjs +53 -47
- package/dist/packages/stack/src/plugins/blog/client/components/pages/post-page.mjs +53 -48
- package/dist/packages/stack/src/plugins/blog/client/components/pages/tag-page.cjs +30 -24
- package/dist/packages/stack/src/plugins/blog/client/components/pages/tag-page.mjs +30 -25
- package/dist/plugins/blog/api/index.d.cts +2 -2
- package/dist/plugins/blog/api/index.d.mts +2 -2
- package/dist/plugins/blog/api/index.d.ts +2 -2
- package/dist/plugins/blog/client/components/index.d.cts +6 -10
- package/dist/plugins/blog/client/components/index.d.mts +6 -10
- package/dist/plugins/blog/client/components/index.d.ts +6 -10
- package/dist/plugins/blog/client/hooks/index.d.cts +2 -2
- package/dist/plugins/blog/client/hooks/index.d.mts +2 -2
- package/dist/plugins/blog/client/hooks/index.d.ts +2 -2
- package/dist/plugins/blog/client/index.d.cts +2 -2
- package/dist/plugins/blog/client/index.d.mts +2 -2
- package/dist/plugins/blog/client/index.d.ts +2 -2
- package/dist/plugins/blog/client/pages/post.cjs +9 -0
- package/dist/plugins/blog/client/pages/post.d.cts +8 -0
- package/dist/plugins/blog/client/pages/post.d.mts +8 -0
- package/dist/plugins/blog/client/pages/post.d.ts +8 -0
- package/dist/plugins/blog/client/pages/post.mjs +7 -0
- package/dist/plugins/blog/client/pages/posts.cjs +9 -0
- package/dist/plugins/blog/client/pages/posts.d.cts +8 -0
- package/dist/plugins/blog/client/pages/posts.d.mts +8 -0
- package/dist/plugins/blog/client/pages/posts.d.ts +8 -0
- package/dist/plugins/blog/client/pages/posts.mjs +7 -0
- package/dist/plugins/blog/client/pages/tag.cjs +9 -0
- package/dist/plugins/blog/client/pages/tag.d.cts +8 -0
- package/dist/plugins/blog/client/pages/tag.d.mts +8 -0
- package/dist/plugins/blog/client/pages/tag.d.ts +8 -0
- package/dist/plugins/blog/client/pages/tag.mjs +7 -0
- package/dist/plugins/blog/query-keys.d.cts +2 -2
- package/dist/plugins/blog/query-keys.d.mts +2 -2
- package/dist/plugins/blog/query-keys.d.ts +2 -2
- package/dist/plugins/form-builder/api/index.d.cts +1 -1
- package/dist/plugins/form-builder/api/index.d.mts +1 -1
- package/dist/plugins/form-builder/api/index.d.ts +1 -1
- package/dist/plugins/form-builder/client/hooks/index.d.cts +1 -1
- package/dist/plugins/form-builder/client/hooks/index.d.mts +1 -1
- package/dist/plugins/form-builder/client/hooks/index.d.ts +1 -1
- package/dist/plugins/form-builder/query-keys.d.cts +1 -1
- package/dist/plugins/form-builder/query-keys.d.mts +1 -1
- package/dist/plugins/form-builder/query-keys.d.ts +1 -1
- package/dist/shared/{stack.BTStHVTy.d.cts → stack.BGzgFx3R.d.cts} +5 -5
- package/dist/shared/{stack.94kN3pEK.d.mts → stack.CjCSDfWy.d.mts} +5 -5
- package/dist/shared/{stack.lPKwd_v0.d.ts → stack.Clil75lW.d.ts} +37 -37
- package/dist/shared/{stack.CtrnoQgH.d.cts → stack.DNw6Hv5o.d.cts} +37 -37
- package/dist/shared/{stack.BggTHKlo.d.mts → stack.DhZtP2JZ.d.mts} +37 -37
- package/dist/shared/{stack.DfrRYOIl.d.ts → stack.Dpg_JjIo.d.ts} +5 -5
- package/package.json +41 -2
- package/src/__tests__/provider-hydration.test.tsx +164 -0
- package/src/context/auth.tsx +21 -14
- package/src/context/provider.tsx +51 -28
- package/src/plugins/blog/__tests__/page-load-error.test.tsx +53 -0
- package/src/plugins/blog/__tests__/page-loading.test.tsx +106 -0
- package/src/plugins/blog/client/components/pages/home-page.tsx +39 -35
- package/src/plugins/blog/client/components/pages/post-page.tsx +62 -54
- package/src/plugins/blog/client/components/pages/tag-page.tsx +35 -29
- package/src/plugins/blog/client/pages/post.tsx +7 -0
- package/src/plugins/blog/client/pages/posts.tsx +7 -0
- package/src/plugins/blog/client/pages/tag.tsx +7 -0
- package/dist/shared/{stack.D20bOqfr.d.ts → stack.BuxUQqL8.d.ts} +20 -20
- package/dist/shared/{stack.AWSs6TXN.d.cts → stack.CX83YNuS.d.cts} +20 -20
- package/dist/shared/{stack.CKc_Bais.d.cts → stack.Csi-4Mgz.d.cts} +7 -7
- package/dist/shared/{stack.CKc_Bais.d.mts → stack.Csi-4Mgz.d.mts} +7 -7
- package/dist/shared/{stack.CKc_Bais.d.ts → stack.Csi-4Mgz.d.ts} +7 -7
- package/dist/shared/{stack.HB5uGLpE.d.mts → stack.Egr3RWhA.d.mts} +20 -20
|
@@ -2,7 +2,7 @@ import * as _btst_stack_plugins_client from '@btst/stack/plugins/client';
|
|
|
2
2
|
import { createApiClient } from '@btst/stack/plugins/client';
|
|
3
3
|
import * as zod from 'zod';
|
|
4
4
|
import { z } from 'zod';
|
|
5
|
-
import { c as createPostSchema, u as updatePostSchema } from './stack.
|
|
5
|
+
import { c as createPostSchema, u as updatePostSchema } from './stack.Csi-4Mgz.mjs';
|
|
6
6
|
import { P as Post, T as Tag, S as SerializedPost, a as SerializedTag } from './stack.Dsatjh0B.mjs';
|
|
7
7
|
import * as _btst_stack_plugins_api from '@btst/stack/plugins/api';
|
|
8
8
|
import { DeepReadonly, OperationContext, OperationErrorContext } from '@btst/stack/plugins/api';
|
|
@@ -89,12 +89,6 @@ declare const NextPreviousPostsQuerySchema: z.ZodObject<{
|
|
|
89
89
|
date: z.ZodPipe<z.ZodCoercedDate<unknown>, z.ZodTransform<string, Date>>;
|
|
90
90
|
}, z.core.$strip>;
|
|
91
91
|
declare const CreatePostOperationInputSchema: z.ZodObject<{
|
|
92
|
-
slug: z.ZodOptional<z.ZodString>;
|
|
93
|
-
title: z.ZodString;
|
|
94
|
-
content: z.ZodString;
|
|
95
|
-
excerpt: z.ZodString;
|
|
96
|
-
image: z.ZodOptional<z.ZodString>;
|
|
97
|
-
published: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
98
92
|
tags: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
|
|
99
93
|
name: z.ZodString;
|
|
100
94
|
}, z.core.$strip>, z.ZodObject<{
|
|
@@ -102,6 +96,12 @@ declare const CreatePostOperationInputSchema: z.ZodObject<{
|
|
|
102
96
|
name: z.ZodString;
|
|
103
97
|
slug: z.ZodString;
|
|
104
98
|
}, z.core.$strip>]>>>>;
|
|
99
|
+
slug: z.ZodOptional<z.ZodString>;
|
|
100
|
+
title: z.ZodString;
|
|
101
|
+
content: z.ZodString;
|
|
102
|
+
excerpt: z.ZodString;
|
|
103
|
+
image: z.ZodOptional<z.ZodString>;
|
|
104
|
+
published: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
105
105
|
publishedAt: z.ZodOptional<z.ZodPipe<z.ZodCoercedDate<unknown>, z.ZodTransform<string, Date>>>;
|
|
106
106
|
createdAt: z.ZodOptional<z.ZodPipe<z.ZodCoercedDate<unknown>, z.ZodTransform<string, Date>>>;
|
|
107
107
|
updatedAt: z.ZodOptional<z.ZodPipe<z.ZodCoercedDate<unknown>, z.ZodTransform<string, Date>>>;
|
|
@@ -109,12 +109,6 @@ declare const CreatePostOperationInputSchema: z.ZodObject<{
|
|
|
109
109
|
declare const UpdatePostOperationInputSchema: z.ZodObject<{
|
|
110
110
|
id: z.ZodString;
|
|
111
111
|
data: z.ZodObject<{
|
|
112
|
-
slug: z.ZodString;
|
|
113
|
-
title: z.ZodString;
|
|
114
|
-
content: z.ZodString;
|
|
115
|
-
excerpt: z.ZodString;
|
|
116
|
-
image: z.ZodOptional<z.ZodString>;
|
|
117
|
-
published: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
118
112
|
tags: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
|
|
119
113
|
name: z.ZodString;
|
|
120
114
|
}, z.core.$strip>, z.ZodObject<{
|
|
@@ -122,6 +116,12 @@ declare const UpdatePostOperationInputSchema: z.ZodObject<{
|
|
|
122
116
|
name: z.ZodString;
|
|
123
117
|
slug: z.ZodString;
|
|
124
118
|
}, z.core.$strip>]>>>>;
|
|
119
|
+
slug: z.ZodString;
|
|
120
|
+
title: z.ZodString;
|
|
121
|
+
content: z.ZodString;
|
|
122
|
+
excerpt: z.ZodString;
|
|
123
|
+
image: z.ZodOptional<z.ZodString>;
|
|
124
|
+
published: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
125
125
|
publishedAt: z.ZodOptional<z.ZodPipe<z.ZodCoercedDate<unknown>, z.ZodTransform<string, Date>>>;
|
|
126
126
|
createdAt: z.ZodOptional<z.ZodPipe<z.ZodCoercedDate<unknown>, z.ZodTransform<string, Date>>>;
|
|
127
127
|
updatedAt: z.ZodOptional<z.ZodPipe<z.ZodCoercedDate<unknown>, z.ZodTransform<string, Date>>>;
|
|
@@ -386,9 +386,6 @@ declare const blogBackendPlugin: (options?: BlogBackendOptions) => _btst_stack_p
|
|
|
386
386
|
title: string;
|
|
387
387
|
content: string;
|
|
388
388
|
excerpt: string;
|
|
389
|
-
slug?: string | undefined;
|
|
390
|
-
image?: string | undefined;
|
|
391
|
-
published?: boolean | undefined;
|
|
392
389
|
tags?: ({
|
|
393
390
|
name: string;
|
|
394
391
|
} | {
|
|
@@ -396,6 +393,9 @@ declare const blogBackendPlugin: (options?: BlogBackendOptions) => _btst_stack_p
|
|
|
396
393
|
name: string;
|
|
397
394
|
slug: string;
|
|
398
395
|
})[] | undefined;
|
|
396
|
+
slug?: string | undefined;
|
|
397
|
+
image?: string | undefined;
|
|
398
|
+
published?: boolean | undefined;
|
|
399
399
|
publishedAt?: unknown;
|
|
400
400
|
createdAt?: unknown;
|
|
401
401
|
updatedAt?: unknown;
|
|
@@ -403,9 +403,6 @@ declare const blogBackendPlugin: (options?: BlogBackendOptions) => _btst_stack_p
|
|
|
403
403
|
title: string;
|
|
404
404
|
content: string;
|
|
405
405
|
excerpt: string;
|
|
406
|
-
slug?: string | undefined;
|
|
407
|
-
image?: string | undefined;
|
|
408
|
-
published?: boolean | undefined;
|
|
409
406
|
tags?: ({
|
|
410
407
|
name: string;
|
|
411
408
|
} | {
|
|
@@ -413,6 +410,9 @@ declare const blogBackendPlugin: (options?: BlogBackendOptions) => _btst_stack_p
|
|
|
413
410
|
name: string;
|
|
414
411
|
slug: string;
|
|
415
412
|
})[] | undefined;
|
|
413
|
+
slug?: string | undefined;
|
|
414
|
+
image?: string | undefined;
|
|
415
|
+
published?: boolean | undefined;
|
|
416
416
|
publishedAt?: unknown;
|
|
417
417
|
createdAt?: unknown;
|
|
418
418
|
updatedAt?: unknown;
|
|
@@ -448,8 +448,6 @@ declare const blogBackendPlugin: (options?: BlogBackendOptions) => _btst_stack_p
|
|
|
448
448
|
title: string;
|
|
449
449
|
content: string;
|
|
450
450
|
excerpt: string;
|
|
451
|
-
image?: string | undefined;
|
|
452
|
-
published?: boolean | undefined;
|
|
453
451
|
tags?: ({
|
|
454
452
|
name: string;
|
|
455
453
|
} | {
|
|
@@ -457,6 +455,8 @@ declare const blogBackendPlugin: (options?: BlogBackendOptions) => _btst_stack_p
|
|
|
457
455
|
name: string;
|
|
458
456
|
slug: string;
|
|
459
457
|
})[] | undefined;
|
|
458
|
+
image?: string | undefined;
|
|
459
|
+
published?: boolean | undefined;
|
|
460
460
|
publishedAt?: unknown;
|
|
461
461
|
createdAt?: unknown;
|
|
462
462
|
updatedAt?: unknown;
|
|
@@ -465,8 +465,6 @@ declare const blogBackendPlugin: (options?: BlogBackendOptions) => _btst_stack_p
|
|
|
465
465
|
title: string;
|
|
466
466
|
content: string;
|
|
467
467
|
excerpt: string;
|
|
468
|
-
image?: string | undefined;
|
|
469
|
-
published?: boolean | undefined;
|
|
470
468
|
tags?: ({
|
|
471
469
|
name: string;
|
|
472
470
|
} | {
|
|
@@ -474,6 +472,8 @@ declare const blogBackendPlugin: (options?: BlogBackendOptions) => _btst_stack_p
|
|
|
474
472
|
name: string;
|
|
475
473
|
slug: string;
|
|
476
474
|
})[] | undefined;
|
|
475
|
+
image?: string | undefined;
|
|
476
|
+
published?: boolean | undefined;
|
|
477
477
|
publishedAt?: unknown;
|
|
478
478
|
createdAt?: unknown;
|
|
479
479
|
updatedAt?: unknown;
|
|
@@ -627,12 +627,6 @@ declare const blogBackendPlugin: (options?: BlogBackendOptions) => _btst_stack_p
|
|
|
627
627
|
total: number;
|
|
628
628
|
}, false>;
|
|
629
629
|
readonly createPost: _btst_stack_plugins_api.Operation<zod.ZodObject<{
|
|
630
|
-
slug: zod.ZodOptional<zod.ZodString>;
|
|
631
|
-
title: zod.ZodString;
|
|
632
|
-
content: zod.ZodString;
|
|
633
|
-
excerpt: zod.ZodString;
|
|
634
|
-
image: zod.ZodOptional<zod.ZodString>;
|
|
635
|
-
published: zod.ZodDefault<zod.ZodOptional<zod.ZodBoolean>>;
|
|
636
630
|
tags: zod.ZodDefault<zod.ZodOptional<zod.ZodArray<zod.ZodUnion<readonly [zod.ZodObject<{
|
|
637
631
|
name: zod.ZodString;
|
|
638
632
|
}, zod_v4_core.$strip>, zod.ZodObject<{
|
|
@@ -640,6 +634,12 @@ declare const blogBackendPlugin: (options?: BlogBackendOptions) => _btst_stack_p
|
|
|
640
634
|
name: zod.ZodString;
|
|
641
635
|
slug: zod.ZodString;
|
|
642
636
|
}, zod_v4_core.$strip>]>>>>;
|
|
637
|
+
slug: zod.ZodOptional<zod.ZodString>;
|
|
638
|
+
title: zod.ZodString;
|
|
639
|
+
content: zod.ZodString;
|
|
640
|
+
excerpt: zod.ZodString;
|
|
641
|
+
image: zod.ZodOptional<zod.ZodString>;
|
|
642
|
+
published: zod.ZodDefault<zod.ZodOptional<zod.ZodBoolean>>;
|
|
643
643
|
publishedAt: zod.ZodOptional<zod.ZodPipe<zod.ZodCoercedDate<unknown>, zod.ZodTransform<string, Date>>>;
|
|
644
644
|
createdAt: zod.ZodOptional<zod.ZodPipe<zod.ZodCoercedDate<unknown>, zod.ZodTransform<string, Date>>>;
|
|
645
645
|
updatedAt: zod.ZodOptional<zod.ZodPipe<zod.ZodCoercedDate<unknown>, zod.ZodTransform<string, Date>>>;
|
|
@@ -673,12 +673,6 @@ declare const blogBackendPlugin: (options?: BlogBackendOptions) => _btst_stack_p
|
|
|
673
673
|
readonly updatePost: _btst_stack_plugins_api.Operation<zod.ZodObject<{
|
|
674
674
|
id: zod.ZodString;
|
|
675
675
|
data: zod.ZodObject<{
|
|
676
|
-
slug: zod.ZodString;
|
|
677
|
-
title: zod.ZodString;
|
|
678
|
-
content: zod.ZodString;
|
|
679
|
-
excerpt: zod.ZodString;
|
|
680
|
-
image: zod.ZodOptional<zod.ZodString>;
|
|
681
|
-
published: zod.ZodDefault<zod.ZodOptional<zod.ZodBoolean>>;
|
|
682
676
|
tags: zod.ZodDefault<zod.ZodOptional<zod.ZodArray<zod.ZodUnion<readonly [zod.ZodObject<{
|
|
683
677
|
name: zod.ZodString;
|
|
684
678
|
}, zod_v4_core.$strip>, zod.ZodObject<{
|
|
@@ -686,6 +680,12 @@ declare const blogBackendPlugin: (options?: BlogBackendOptions) => _btst_stack_p
|
|
|
686
680
|
name: zod.ZodString;
|
|
687
681
|
slug: zod.ZodString;
|
|
688
682
|
}, zod_v4_core.$strip>]>>>>;
|
|
683
|
+
slug: zod.ZodString;
|
|
684
|
+
title: zod.ZodString;
|
|
685
|
+
content: zod.ZodString;
|
|
686
|
+
excerpt: zod.ZodString;
|
|
687
|
+
image: zod.ZodOptional<zod.ZodString>;
|
|
688
|
+
published: zod.ZodDefault<zod.ZodOptional<zod.ZodBoolean>>;
|
|
689
689
|
publishedAt: zod.ZodOptional<zod.ZodPipe<zod.ZodCoercedDate<unknown>, zod.ZodTransform<string, Date>>>;
|
|
690
690
|
createdAt: zod.ZodOptional<zod.ZodPipe<zod.ZodCoercedDate<unknown>, zod.ZodTransform<string, Date>>>;
|
|
691
691
|
updatedAt: zod.ZodOptional<zod.ZodPipe<zod.ZodCoercedDate<unknown>, zod.ZodTransform<string, Date>>>;
|
|
@@ -900,8 +900,8 @@ declare const blogResources: {
|
|
|
900
900
|
method: "POST";
|
|
901
901
|
input: (vars: PostCreateInput) => {
|
|
902
902
|
body: {
|
|
903
|
-
published: boolean;
|
|
904
903
|
title: string;
|
|
904
|
+
published: boolean;
|
|
905
905
|
content: string;
|
|
906
906
|
excerpt: string;
|
|
907
907
|
tags: ({
|
|
@@ -1072,8 +1072,8 @@ declare function createBlogQueryKeys(client: ReturnType<typeof createApiClient<B
|
|
|
1072
1072
|
method: "POST";
|
|
1073
1073
|
input: (vars: PostCreateInput) => {
|
|
1074
1074
|
body: {
|
|
1075
|
-
published: boolean;
|
|
1076
1075
|
title: string;
|
|
1076
|
+
published: boolean;
|
|
1077
1077
|
content: string;
|
|
1078
1078
|
excerpt: string;
|
|
1079
1079
|
tags: ({
|
|
@@ -2,7 +2,7 @@ import * as _tanstack_react_query from '@tanstack/react-query';
|
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { ResourceFormConfig, ResourceFormResult } from '@btst/stack/plugins/client/hooks';
|
|
4
4
|
import { S as SerializedPost, a as SerializedTag } from './stack.Dsatjh0B.js';
|
|
5
|
-
import { c as createPostSchema, u as updatePostSchema } from './stack.
|
|
5
|
+
import { c as createPostSchema, u as updatePostSchema } from './stack.Csi-4Mgz.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Options for the usePosts hook
|
|
@@ -136,10 +136,6 @@ declare function useSuspenseTags(): {
|
|
|
136
136
|
declare function usePostForm<TValues>(config: ResourceFormConfig<TValues, SerializedPost | null, SerializedPost | null>): ResourceFormResult<TValues, SerializedPost | null, SerializedPost | null>;
|
|
137
137
|
/** Create a new post */
|
|
138
138
|
declare function useCreatePost(): _tanstack_react_query.UseMutationResult<SerializedPost | null, Error, {
|
|
139
|
-
title: string;
|
|
140
|
-
content: string;
|
|
141
|
-
excerpt: string;
|
|
142
|
-
published: boolean;
|
|
143
139
|
tags: ({
|
|
144
140
|
name: string;
|
|
145
141
|
} | {
|
|
@@ -147,6 +143,10 @@ declare function useCreatePost(): _tanstack_react_query.UseMutationResult<Serial
|
|
|
147
143
|
name: string;
|
|
148
144
|
slug: string;
|
|
149
145
|
})[];
|
|
146
|
+
title: string;
|
|
147
|
+
content: string;
|
|
148
|
+
excerpt: string;
|
|
149
|
+
published: boolean;
|
|
150
150
|
slug?: string | undefined;
|
|
151
151
|
publishedAt?: Date | undefined;
|
|
152
152
|
createdAt?: Date | undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@btst/stack",
|
|
3
|
-
"version": "3.0
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "A composable, plugin-based library for building full-stack applications.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -799,7 +799,37 @@
|
|
|
799
799
|
},
|
|
800
800
|
"./dist/*": "./dist/*",
|
|
801
801
|
"./ui/css": "./dist/ui/components.css",
|
|
802
|
-
"./package.json": "./package.json"
|
|
802
|
+
"./package.json": "./package.json",
|
|
803
|
+
"./plugins/blog/client/pages/posts": {
|
|
804
|
+
"import": {
|
|
805
|
+
"types": "./dist/plugins/blog/client/pages/posts.d.ts",
|
|
806
|
+
"default": "./dist/plugins/blog/client/pages/posts.mjs"
|
|
807
|
+
},
|
|
808
|
+
"require": {
|
|
809
|
+
"types": "./dist/plugins/blog/client/pages/posts.d.cts",
|
|
810
|
+
"default": "./dist/plugins/blog/client/pages/posts.cjs"
|
|
811
|
+
}
|
|
812
|
+
},
|
|
813
|
+
"./plugins/blog/client/pages/post": {
|
|
814
|
+
"import": {
|
|
815
|
+
"types": "./dist/plugins/blog/client/pages/post.d.ts",
|
|
816
|
+
"default": "./dist/plugins/blog/client/pages/post.mjs"
|
|
817
|
+
},
|
|
818
|
+
"require": {
|
|
819
|
+
"types": "./dist/plugins/blog/client/pages/post.d.cts",
|
|
820
|
+
"default": "./dist/plugins/blog/client/pages/post.cjs"
|
|
821
|
+
}
|
|
822
|
+
},
|
|
823
|
+
"./plugins/blog/client/pages/tag": {
|
|
824
|
+
"import": {
|
|
825
|
+
"types": "./dist/plugins/blog/client/pages/tag.d.ts",
|
|
826
|
+
"default": "./dist/plugins/blog/client/pages/tag.mjs"
|
|
827
|
+
},
|
|
828
|
+
"require": {
|
|
829
|
+
"types": "./dist/plugins/blog/client/pages/tag.d.cts",
|
|
830
|
+
"default": "./dist/plugins/blog/client/pages/tag.cjs"
|
|
831
|
+
}
|
|
832
|
+
}
|
|
803
833
|
},
|
|
804
834
|
"typesVersions": {
|
|
805
835
|
"*": {
|
|
@@ -1024,6 +1054,15 @@
|
|
|
1024
1054
|
],
|
|
1025
1055
|
"components/empty": [
|
|
1026
1056
|
"./dist/components/empty/index.d.ts"
|
|
1057
|
+
],
|
|
1058
|
+
"plugins/blog/client/pages/posts": [
|
|
1059
|
+
"./dist/plugins/blog/client/pages/posts.d.ts"
|
|
1060
|
+
],
|
|
1061
|
+
"plugins/blog/client/pages/post": [
|
|
1062
|
+
"./dist/plugins/blog/client/pages/post.d.ts"
|
|
1063
|
+
],
|
|
1064
|
+
"plugins/blog/client/pages/tag": [
|
|
1065
|
+
"./dist/plugins/blog/client/pages/tag.d.ts"
|
|
1027
1066
|
]
|
|
1028
1067
|
}
|
|
1029
1068
|
},
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import {
|
|
3
|
+
act,
|
|
4
|
+
createContext,
|
|
5
|
+
lazy,
|
|
6
|
+
useContext,
|
|
7
|
+
useEffect,
|
|
8
|
+
useLayoutEffect,
|
|
9
|
+
useState,
|
|
10
|
+
type ReactNode,
|
|
11
|
+
type ComponentType,
|
|
12
|
+
} from "react";
|
|
13
|
+
import { createRoot, hydrateRoot } from "react-dom/client";
|
|
14
|
+
import { renderToString } from "react-dom/server";
|
|
15
|
+
import { QueryClient } from "@tanstack/react-query";
|
|
16
|
+
import { expect, it, vi } from "vitest";
|
|
17
|
+
import { z } from "zod";
|
|
18
|
+
import {
|
|
19
|
+
defineAuthorization,
|
|
20
|
+
definePermissions,
|
|
21
|
+
permission,
|
|
22
|
+
} from "../authorization";
|
|
23
|
+
import { createClientAuth } from "../authorization/client";
|
|
24
|
+
import { createClientStack } from "../client";
|
|
25
|
+
import { ComposedRoute } from "../client/components/compose";
|
|
26
|
+
import { StackProvider, useStack } from "../context";
|
|
27
|
+
|
|
28
|
+
(globalThis as any).IS_REACT_ACT_ENVIRONMENT = true;
|
|
29
|
+
|
|
30
|
+
const permissions = definePermissions("records", {
|
|
31
|
+
read: permission(z.object({ id: z.string() })),
|
|
32
|
+
});
|
|
33
|
+
const authorization = defineAuthorization({
|
|
34
|
+
identity: z.object({ id: z.string() }),
|
|
35
|
+
permissions: [permissions],
|
|
36
|
+
rules: ({ records }) => [
|
|
37
|
+
records.read.when(({ identity }) => identity?.id === "owner"),
|
|
38
|
+
],
|
|
39
|
+
});
|
|
40
|
+
const auth = createClientAuth({ authorization, getIdentity: () => null });
|
|
41
|
+
const owner = { id: "owner" };
|
|
42
|
+
const access = permissions.read({ id: "one" });
|
|
43
|
+
const navigate = () => {};
|
|
44
|
+
const useRouter = () => undefinedRouter;
|
|
45
|
+
const undefinedRouter = {};
|
|
46
|
+
const onError = () => {};
|
|
47
|
+
const Content = () => <div data-content>Prefetched content</div>;
|
|
48
|
+
const Loading = () => <div data-loading>Loading</div>;
|
|
49
|
+
const ErrorPage = () => <div data-denied>Denied</div>;
|
|
50
|
+
|
|
51
|
+
function createStack() {
|
|
52
|
+
return createClientStack({
|
|
53
|
+
api: { baseURL: "http://test.local", basePath: "/api/data" },
|
|
54
|
+
site: { baseURL: "http://test.local", basePath: "/pages" },
|
|
55
|
+
queryClient: new QueryClient(),
|
|
56
|
+
plugins: {},
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
it.each([false, true])(
|
|
61
|
+
"keeps pending page hydration safe when a provider rerenders (revoke: %s)",
|
|
62
|
+
async (revoke) => {
|
|
63
|
+
const stack = createStack();
|
|
64
|
+
function Provider({ children }: { children: ReactNode }) {
|
|
65
|
+
const [updated, setUpdated] = useState(false);
|
|
66
|
+
useEffect(() => setUpdated(true), []);
|
|
67
|
+
return (
|
|
68
|
+
<StackProvider
|
|
69
|
+
stack={stack}
|
|
70
|
+
router={{ navigate, useRouter }}
|
|
71
|
+
auth={auth}
|
|
72
|
+
initialIdentity={updated && revoke ? null : owner}
|
|
73
|
+
>
|
|
74
|
+
<output>{updated ? "updated" : "initial"}</output>
|
|
75
|
+
{children}
|
|
76
|
+
</StackProvider>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
const tree = (Page: ComponentType) => (
|
|
80
|
+
<Provider>
|
|
81
|
+
<ComposedRoute
|
|
82
|
+
path="/record/one"
|
|
83
|
+
permission={access}
|
|
84
|
+
PageComponent={Page}
|
|
85
|
+
LoadingComponent={Loading}
|
|
86
|
+
ErrorComponent={ErrorPage}
|
|
87
|
+
onError={onError}
|
|
88
|
+
/>
|
|
89
|
+
</Provider>
|
|
90
|
+
);
|
|
91
|
+
const container = document.createElement("div");
|
|
92
|
+
container.innerHTML = renderToString(tree(Content));
|
|
93
|
+
expect(container.querySelector("[data-content]")).not.toBeNull();
|
|
94
|
+
const Pending = lazy(
|
|
95
|
+
() => new Promise<{ default: typeof Content }>(() => {}),
|
|
96
|
+
);
|
|
97
|
+
const consoleError = vi
|
|
98
|
+
.spyOn(console, "error")
|
|
99
|
+
.mockImplementation(() => {});
|
|
100
|
+
const consoleWarn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
101
|
+
const onRecoverableError = vi.fn();
|
|
102
|
+
let root: ReturnType<typeof hydrateRoot> | undefined;
|
|
103
|
+
try {
|
|
104
|
+
await act(async () => {
|
|
105
|
+
root = hydrateRoot(container, tree(Pending), { onRecoverableError });
|
|
106
|
+
});
|
|
107
|
+
expect(container.querySelector("output")?.textContent).toBe("updated");
|
|
108
|
+
expect(container.querySelector("[data-content]") !== null).toBe(!revoke);
|
|
109
|
+
expect(container.querySelector("[data-denied]") !== null).toBe(revoke);
|
|
110
|
+
expect(container.querySelector("[data-loading]")).toBeNull();
|
|
111
|
+
expect(onRecoverableError).not.toHaveBeenCalled();
|
|
112
|
+
} finally {
|
|
113
|
+
await act(async () => root?.unmount());
|
|
114
|
+
stack.provider.queryClient.clear();
|
|
115
|
+
consoleError.mockRestore();
|
|
116
|
+
consoleWarn.mockRestore();
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
it("applies new router callbacks with the current host context before page effects", async () => {
|
|
122
|
+
const Workspace = createContext("A");
|
|
123
|
+
const stack = createStack();
|
|
124
|
+
const observations: string[] = [];
|
|
125
|
+
function Page() {
|
|
126
|
+
const workspace = useContext(Workspace);
|
|
127
|
+
const { router } = useStack();
|
|
128
|
+
useLayoutEffect(() => {
|
|
129
|
+
void router?.navigate?.(workspace);
|
|
130
|
+
}, [workspace]);
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
function App() {
|
|
134
|
+
const [workspace, setWorkspace] = useState("A");
|
|
135
|
+
useEffect(() => setWorkspace("B"), []);
|
|
136
|
+
return (
|
|
137
|
+
<Workspace.Provider value={workspace}>
|
|
138
|
+
<StackProvider
|
|
139
|
+
stack={stack}
|
|
140
|
+
router={{
|
|
141
|
+
navigate: (current) => {
|
|
142
|
+
observations.push(`${current}:${workspace}`);
|
|
143
|
+
},
|
|
144
|
+
}}
|
|
145
|
+
>
|
|
146
|
+
<ComposedRoute
|
|
147
|
+
path="/record"
|
|
148
|
+
PageComponent={Page}
|
|
149
|
+
LoadingComponent={Loading}
|
|
150
|
+
onError={onError}
|
|
151
|
+
/>
|
|
152
|
+
</StackProvider>
|
|
153
|
+
</Workspace.Provider>
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
const root = createRoot(document.createElement("div"));
|
|
157
|
+
try {
|
|
158
|
+
await act(async () => root.render(<App />));
|
|
159
|
+
expect(observations).toEqual(["A:A", "B:B"]);
|
|
160
|
+
} finally {
|
|
161
|
+
await act(async () => root.unmount());
|
|
162
|
+
stack.provider.queryClient.clear();
|
|
163
|
+
}
|
|
164
|
+
});
|
package/src/context/auth.tsx
CHANGED
|
@@ -193,21 +193,28 @@ export function StackAuthBoundary({
|
|
|
193
193
|
void resolveIdentity(false);
|
|
194
194
|
}, [initialIdentity, resolveIdentity]);
|
|
195
195
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
196
|
+
const value = useMemo(
|
|
197
|
+
() => ({
|
|
198
|
+
provider,
|
|
199
|
+
identity: currentState.identity,
|
|
200
|
+
isPending: currentState.isPending,
|
|
201
|
+
sourceGeneration: currentResolutionGeneration,
|
|
202
|
+
...(currentState.error ? { error: currentState.error } : {}),
|
|
203
|
+
refetch,
|
|
204
|
+
waitForResolution,
|
|
205
|
+
}),
|
|
206
|
+
[
|
|
207
|
+
provider,
|
|
208
|
+
currentState.identity,
|
|
209
|
+
currentState.isPending,
|
|
210
|
+
currentResolutionGeneration,
|
|
211
|
+
currentState.error,
|
|
212
|
+
refetch,
|
|
213
|
+
waitForResolution,
|
|
214
|
+
],
|
|
210
215
|
);
|
|
216
|
+
|
|
217
|
+
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
|
|
211
218
|
}
|
|
212
219
|
|
|
213
220
|
/** @internal Access the raw auth context (or `null` when no provider is set). */
|
package/src/context/provider.tsx
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
createContext,
|
|
4
4
|
useContext,
|
|
5
|
+
useMemo,
|
|
5
6
|
type ReactElement,
|
|
6
7
|
type ReactNode,
|
|
7
8
|
} from "react";
|
|
@@ -79,6 +80,10 @@ type StackProviderServices = {
|
|
|
79
80
|
i18n?: StackI18nProvider;
|
|
80
81
|
};
|
|
81
82
|
|
|
83
|
+
/** Plugin override configuration inferred from a resolved client stack. */
|
|
84
|
+
export type ClientStackOverrides<TStack extends ResolvedClientStack<any, any>> =
|
|
85
|
+
InferredPluginOverrides<RegisteredClientPlugins<TStack>>;
|
|
86
|
+
|
|
82
87
|
type CanonicalStackProviderOverrideProps<
|
|
83
88
|
TStack extends ResolvedClientStack<any, any>,
|
|
84
89
|
> = InferredPluginOverrides<
|
|
@@ -110,12 +115,24 @@ function stripUndefined<T extends Record<string, any>>(obj: T): Partial<T> {
|
|
|
110
115
|
return result as Partial<T>;
|
|
111
116
|
}
|
|
112
117
|
|
|
113
|
-
function
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
118
|
+
function useStaticRouter(router: StackRouterConfig | undefined) {
|
|
119
|
+
const { Link, Image, navigate, refresh, getSearchParams, setSearchParams } =
|
|
120
|
+
router ?? {};
|
|
121
|
+
const present = router !== undefined;
|
|
122
|
+
return useMemo(
|
|
123
|
+
() =>
|
|
124
|
+
present
|
|
125
|
+
? stripUndefined({
|
|
126
|
+
Link,
|
|
127
|
+
Image,
|
|
128
|
+
navigate,
|
|
129
|
+
refresh,
|
|
130
|
+
getSearchParams,
|
|
131
|
+
setSearchParams,
|
|
132
|
+
})
|
|
133
|
+
: undefined,
|
|
134
|
+
[present, Link, Image, navigate, refresh, getSearchParams, setSearchParams],
|
|
135
|
+
);
|
|
119
136
|
}
|
|
120
137
|
|
|
121
138
|
/**
|
|
@@ -135,15 +152,16 @@ function RouterBridge({
|
|
|
135
152
|
children?: ReactNode;
|
|
136
153
|
}) {
|
|
137
154
|
const hookRouter = useRouter();
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
155
|
+
const context = useMemo(
|
|
156
|
+
() => ({
|
|
157
|
+
...value,
|
|
158
|
+
router: { ...staticRouter, ...stripUndefined(hookRouter) },
|
|
159
|
+
}),
|
|
160
|
+
[value, staticRouter, hookRouter],
|
|
161
|
+
);
|
|
142
162
|
|
|
143
163
|
return (
|
|
144
|
-
<StackContext.Provider value={{
|
|
145
|
-
{children}
|
|
146
|
-
</StackContext.Provider>
|
|
164
|
+
<StackContext.Provider value={context}>{children}</StackContext.Provider>
|
|
147
165
|
);
|
|
148
166
|
}
|
|
149
167
|
|
|
@@ -205,18 +223,25 @@ export function StackProvider<
|
|
|
205
223
|
i18n,
|
|
206
224
|
}: CanonicalStackProviderProps<TStack>): ReactElement {
|
|
207
225
|
const projection = stack.provider;
|
|
208
|
-
const staticRouter =
|
|
209
|
-
const value
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
226
|
+
const staticRouter = useStaticRouter(router);
|
|
227
|
+
const value = useMemo(
|
|
228
|
+
() => ({
|
|
229
|
+
overrides: overrides ?? {},
|
|
230
|
+
basePath: projection.site.basePath,
|
|
231
|
+
api: projection.api,
|
|
232
|
+
site: projection.site,
|
|
233
|
+
plugins: projection.plugins,
|
|
234
|
+
queryClient: projection.queryClient,
|
|
235
|
+
clientStackContext: stack.context,
|
|
236
|
+
resolvedStack: stack,
|
|
237
|
+
auth,
|
|
238
|
+
}),
|
|
239
|
+
[overrides, stack, auth],
|
|
240
|
+
);
|
|
241
|
+
const context = useMemo(
|
|
242
|
+
() => ({ ...value, router: staticRouter }),
|
|
243
|
+
[value, staticRouter],
|
|
244
|
+
);
|
|
220
245
|
|
|
221
246
|
const content = auth ? (
|
|
222
247
|
<StackAuthBoundary provider={auth} initialIdentity={initialIdentity}>
|
|
@@ -235,9 +260,7 @@ export function StackProvider<
|
|
|
235
260
|
{content}
|
|
236
261
|
</RouterBridge>
|
|
237
262
|
) : (
|
|
238
|
-
<StackContext.Provider value={{
|
|
239
|
-
{content}
|
|
240
|
-
</StackContext.Provider>
|
|
263
|
+
<StackContext.Provider value={context}>{content}</StackContext.Provider>
|
|
241
264
|
);
|
|
242
265
|
|
|
243
266
|
return (
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { act, lazy } from "react";
|
|
3
|
+
import { createRoot } from "react-dom/client";
|
|
4
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
5
|
+
import { expect, it, vi } from "vitest";
|
|
6
|
+
import { createClientStack } from "@btst/stack/client";
|
|
7
|
+
import { StackProvider } from "@btst/stack/context";
|
|
8
|
+
import { blogClientPlugin } from "@btst/stack/plugins/blog/client";
|
|
9
|
+
import { createHomePage } from "../client/components/pages/home-page";
|
|
10
|
+
|
|
11
|
+
(globalThis as any).IS_REACT_ACT_ENVIRONMENT = true;
|
|
12
|
+
|
|
13
|
+
it("reports a failed page module through the existing blog error hook and UI", async () => {
|
|
14
|
+
const error = new Error("Page module unavailable");
|
|
15
|
+
const Page = createHomePage(lazy(() => Promise.reject(error)));
|
|
16
|
+
const onRouteError = vi.fn();
|
|
17
|
+
const queryClient = new QueryClient();
|
|
18
|
+
const stack = createClientStack({
|
|
19
|
+
api: { baseURL: "http://test.local", basePath: "/api/data" },
|
|
20
|
+
site: { baseURL: "http://test.local", basePath: "/pages" },
|
|
21
|
+
queryClient,
|
|
22
|
+
plugins: { blog: blogClientPlugin() },
|
|
23
|
+
});
|
|
24
|
+
const container = document.createElement("div");
|
|
25
|
+
const root = createRoot(container);
|
|
26
|
+
const consoleError = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
27
|
+
try {
|
|
28
|
+
await act(async () =>
|
|
29
|
+
root.render(
|
|
30
|
+
<QueryClientProvider client={queryClient}>
|
|
31
|
+
<StackProvider
|
|
32
|
+
stack={stack}
|
|
33
|
+
overrides={{ blog: { onRouteError, uploadImage: async () => "" } }}
|
|
34
|
+
>
|
|
35
|
+
<Page />
|
|
36
|
+
</StackProvider>
|
|
37
|
+
</QueryClientProvider>,
|
|
38
|
+
),
|
|
39
|
+
);
|
|
40
|
+
expect(
|
|
41
|
+
container.querySelector('[data-testid="error-placeholder"]'),
|
|
42
|
+
).not.toBeNull();
|
|
43
|
+
expect(onRouteError).toHaveBeenCalledExactlyOnceWith("posts", error, {
|
|
44
|
+
path: "/blog",
|
|
45
|
+
isSSR: false,
|
|
46
|
+
published: true,
|
|
47
|
+
});
|
|
48
|
+
} finally {
|
|
49
|
+
await act(async () => root.unmount());
|
|
50
|
+
queryClient.clear();
|
|
51
|
+
consoleError.mockRestore();
|
|
52
|
+
}
|
|
53
|
+
});
|