@questpie/admin 3.2.7 → 3.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.
Files changed (79) hide show
  1. package/dist/client/components/blocks/block-editor-provider.mjs +13 -0
  2. package/dist/client/components/fields/array-field.mjs +105 -122
  3. package/dist/client/components/fields/asset-preview-field.mjs +1 -1
  4. package/dist/client/components/fields/blocks-field/blocks-field.mjs +1 -1
  5. package/dist/client/components/fields/boolean-field.mjs +1 -1
  6. package/dist/client/components/fields/date-field.mjs +1 -1
  7. package/dist/client/components/fields/datetime-field.mjs +1 -1
  8. package/dist/client/components/fields/email-field.mjs +1 -1
  9. package/dist/client/components/fields/field-wrapper.mjs +44 -15
  10. package/dist/client/components/fields/number-field.mjs +1 -1
  11. package/dist/client/components/fields/object-array-field.mjs +179 -149
  12. package/dist/client/components/fields/object-field.mjs +96 -87
  13. package/dist/client/components/fields/relation-picker.mjs +1 -1
  14. package/dist/client/components/fields/relation-select.mjs +1 -1
  15. package/dist/client/components/fields/rich-text-editor/index.mjs +1 -1
  16. package/dist/client/components/fields/select-field.mjs +1 -1
  17. package/dist/client/components/fields/text-field.mjs +1 -1
  18. package/dist/client/components/fields/textarea-field.mjs +1 -1
  19. package/dist/client/components/fields/time-field.mjs +1 -1
  20. package/dist/client/components/fields/upload-field.mjs +1 -1
  21. package/dist/client/components/history-sidebar.mjs +10 -4
  22. package/dist/client/components/structured-diff.mjs +367 -0
  23. package/dist/client/components/ui/sidebar.mjs +1 -1
  24. package/dist/client/hooks/use-field-options.mjs +34 -15
  25. package/dist/client/hooks/use-transition-stage.mjs +2 -2
  26. package/dist/client/utils/auto-expand-fields.mjs +1 -1
  27. package/dist/client/views/collection/auto-form-fields.mjs +23 -19
  28. package/dist/client/views/collection/cells/complex-cells.mjs +1 -1
  29. package/dist/client/views/collection/columns/build-columns.mjs +1 -1
  30. package/dist/client/views/collection/columns/column-defaults.mjs +17 -4
  31. package/dist/client/views/collection/field-renderer.mjs +19 -7
  32. package/dist/client/views/collection/form-view.mjs +10 -6
  33. package/dist/client/views/collection/table-view.mjs +19 -13
  34. package/dist/client/views/globals/global-form-view.mjs +47 -27
  35. package/dist/client/views/layout/admin-sidebar.mjs +2 -2
  36. package/dist/client.mjs +1 -1
  37. package/dist/index.mjs +1 -1
  38. package/dist/server/i18n/messages/cs.mjs +8 -0
  39. package/dist/server/i18n/messages/de.mjs +8 -0
  40. package/dist/server/i18n/messages/en.mjs +8 -0
  41. package/dist/server/i18n/messages/es.mjs +8 -0
  42. package/dist/server/i18n/messages/fr.mjs +8 -0
  43. package/dist/server/i18n/messages/pl.mjs +8 -0
  44. package/dist/server/i18n/messages/pt.mjs +8 -0
  45. package/dist/server/i18n/messages/sk.mjs +8 -0
  46. package/dist/server/modules/admin/collections/account.d.mts +50 -50
  47. package/dist/server/modules/admin/collections/admin-locks.d.mts +54 -54
  48. package/dist/server/modules/admin/collections/admin-preferences.d.mts +39 -39
  49. package/dist/server/modules/admin/collections/admin-saved-views.d.mts +47 -47
  50. package/dist/server/modules/admin/collections/apikey.d.mts +68 -68
  51. package/dist/server/modules/admin/collections/assets.d.mts +39 -39
  52. package/dist/server/modules/admin/collections/session.d.mts +42 -42
  53. package/dist/server/modules/admin/collections/user.d.mts +63 -63
  54. package/dist/server/modules/admin/collections/verification.d.mts +36 -36
  55. package/dist/server/modules/admin/routes/admin-config.d.mts +2 -2
  56. package/dist/server/modules/admin/routes/execute-action.d.mts +9 -9
  57. package/dist/server/modules/admin/routes/locales.d.mts +2 -2
  58. package/dist/server/modules/admin/routes/preview.d.mts +24 -19
  59. package/dist/server/modules/admin/routes/preview.mjs +83 -62
  60. package/dist/server/modules/admin/routes/reactive.d.mts +9 -9
  61. package/dist/server/modules/admin/routes/setup.d.mts +7 -7
  62. package/dist/server/modules/admin/routes/translations.d.mts +4 -4
  63. package/dist/server/modules/admin/routes/widget-data.d.mts +5 -5
  64. package/dist/server/modules/admin-preferences/collections/saved-views.d.mts +41 -41
  65. package/dist/server/modules/audit/.generated/module.d.mts +6 -6
  66. package/dist/server/modules/audit/collections/audit-log.d.mts +80 -78
  67. package/dist/server/modules/audit/collections/audit-log.mjs +7 -2
  68. package/dist/server/modules/audit/config/localize-title.mjs +67 -0
  69. package/dist/server/modules/audit/index.d.mts +2 -1
  70. package/dist/server/modules/audit/jobs/audit-cleanup.d.mts +2 -2
  71. package/dist/server/modules/audit/log-audit-entry.d.mts +85 -0
  72. package/dist/server/modules/audit/log-audit-entry.mjs +125 -0
  73. package/dist/server/plugin.mjs +4 -4
  74. package/dist/server.d.mts +2 -1
  75. package/dist/server.mjs +2 -1
  76. package/dist/shared/preview-utils.d.mts +4 -4
  77. package/dist/shared/preview-utils.mjs +5 -7
  78. package/package.json +3 -3
  79. package/dist/client/hooks/use-audit-history.mjs +0 -38
@@ -1,57 +1,57 @@
1
- import * as questpie_shared55 from "questpie/shared";
2
- import * as questpie369 from "questpie";
3
- import * as questpie_src_server_modules_core_fields_email_js7 from "questpie/src/server/modules/core/fields/email.js";
4
- import * as questpie_src_server_modules_core_fields_json_js7 from "questpie/src/server/modules/core/fields/json.js";
5
- import * as drizzle_orm_pg_core122 from "drizzle-orm/pg-core";
6
- import * as drizzle_orm61 from "drizzle-orm";
1
+ import * as questpie_shared67 from "questpie/shared";
2
+ import * as questpie428 from "questpie";
3
+ import * as questpie_src_server_modules_core_fields_email_js9 from "questpie/src/server/modules/core/fields/email.js";
4
+ import * as questpie_src_server_modules_core_fields_json_js9 from "questpie/src/server/modules/core/fields/json.js";
5
+ import * as drizzle_orm_pg_core141 from "drizzle-orm/pg-core";
6
+ import * as drizzle_orm79 from "drizzle-orm";
7
7
 
8
8
  //#region src/server/modules/admin/collections/verification.d.ts
9
- declare const _default: questpie369.CollectionBuilder<questpie_shared55.Override<questpie369.EmptyCollectionState<"verification", undefined, {
10
- readonly text: typeof questpie369.text;
11
- readonly textarea: typeof questpie369.textarea;
12
- readonly email: typeof questpie_src_server_modules_core_fields_email_js7.email;
13
- readonly url: typeof questpie369.url;
14
- readonly number: typeof questpie369.number;
15
- readonly boolean: typeof questpie369.boolean;
16
- readonly date: typeof questpie369.date;
17
- readonly datetime: typeof questpie369.datetime;
18
- readonly time: typeof questpie369.time;
19
- readonly select: typeof questpie369.select;
20
- readonly upload: typeof questpie369.upload;
21
- readonly relation: typeof questpie369.relation;
22
- readonly object: typeof questpie369.object;
23
- readonly json: typeof questpie_src_server_modules_core_fields_json_js7.json;
24
- readonly from: typeof questpie369.from;
9
+ declare const _default: questpie428.CollectionBuilder<questpie_shared67.Override<questpie428.EmptyCollectionState<"verification", undefined, {
10
+ readonly text: typeof questpie428.text;
11
+ readonly textarea: typeof questpie428.textarea;
12
+ readonly email: typeof questpie_src_server_modules_core_fields_email_js9.email;
13
+ readonly url: typeof questpie428.url;
14
+ readonly number: typeof questpie428.number;
15
+ readonly boolean: typeof questpie428.boolean;
16
+ readonly date: typeof questpie428.date;
17
+ readonly datetime: typeof questpie428.datetime;
18
+ readonly time: typeof questpie428.time;
19
+ readonly select: typeof questpie428.select;
20
+ readonly upload: typeof questpie428.upload;
21
+ readonly relation: typeof questpie428.relation;
22
+ readonly object: typeof questpie428.object;
23
+ readonly json: typeof questpie_src_server_modules_core_fields_json_js9.json;
24
+ readonly from: typeof questpie428.from;
25
25
  }>, {
26
26
  name: "verification";
27
27
  fields: Record<string, any> & {
28
- readonly identifier: drizzle_orm61.NotNull<drizzle_orm_pg_core122.PgVarcharBuilder<[string, ...string[]]>>;
29
- readonly value: drizzle_orm61.NotNull<drizzle_orm_pg_core122.PgVarcharBuilder<[string, ...string[]]>>;
30
- readonly expiresAt: drizzle_orm61.NotNull<drizzle_orm_pg_core122.PgTimestampBuilder>;
28
+ readonly identifier: drizzle_orm79.NotNull<drizzle_orm_pg_core141.PgVarcharBuilder<[string, ...string[]]>>;
29
+ readonly value: drizzle_orm79.NotNull<drizzle_orm_pg_core141.PgVarcharBuilder<[string, ...string[]]>>;
30
+ readonly expiresAt: drizzle_orm79.NotNull<drizzle_orm_pg_core141.PgTimestampBuilder>;
31
31
  };
32
32
  virtuals: undefined;
33
- relations: Record<string, questpie369.RelationConfig>;
33
+ relations: Record<string, questpie428.RelationConfig>;
34
34
  indexes: Record<string, any>;
35
35
  title: "identifier";
36
- options: questpie369.CollectionOptions & {
36
+ options: questpie428.CollectionOptions & {
37
37
  timestamps: true;
38
38
  };
39
39
  hooks: Record<string, any>;
40
40
  access: Record<string, any>;
41
41
  searchable: undefined;
42
42
  fieldDefinitions: {
43
- readonly identifier: questpie369.FieldWithMethods<Omit<questpie369.TextFieldState, "notNull" | "column"> & {
43
+ readonly identifier: questpie428.FieldWithMethods<Omit<questpie428.TextFieldState, "notNull" | "column"> & {
44
44
  notNull: true;
45
- column: drizzle_orm61.NotNull<drizzle_orm_pg_core122.PgVarcharBuilder<[string, ...string[]]>>;
46
- }, questpie369.TextFieldMethods>;
47
- readonly value: questpie369.FieldWithMethods<Omit<questpie369.TextFieldState, "notNull" | "column"> & {
45
+ column: drizzle_orm79.NotNull<drizzle_orm_pg_core141.PgVarcharBuilder<[string, ...string[]]>>;
46
+ }, questpie428.TextFieldMethods>;
47
+ readonly value: questpie428.FieldWithMethods<Omit<questpie428.TextFieldState, "notNull" | "column"> & {
48
48
  notNull: true;
49
- column: drizzle_orm61.NotNull<drizzle_orm_pg_core122.PgVarcharBuilder<[string, ...string[]]>>;
50
- }, questpie369.TextFieldMethods>;
51
- readonly expiresAt: questpie369.FieldWithMethods<Omit<questpie369.DatetimeFieldState, "notNull" | "column"> & {
49
+ column: drizzle_orm79.NotNull<drizzle_orm_pg_core141.PgVarcharBuilder<[string, ...string[]]>>;
50
+ }, questpie428.TextFieldMethods>;
51
+ readonly expiresAt: questpie428.FieldWithMethods<Omit<questpie428.DatetimeFieldState, "notNull" | "column"> & {
52
52
  notNull: true;
53
- column: drizzle_orm61.NotNull<drizzle_orm_pg_core122.PgTimestampBuilder>;
54
- }, questpie369.DatetimeFieldMethods>;
53
+ column: drizzle_orm79.NotNull<drizzle_orm_pg_core141.PgTimestampBuilder>;
54
+ }, questpie428.DatetimeFieldMethods>;
55
55
  };
56
56
  upload: undefined;
57
57
  output: {};
@@ -1,4 +1,4 @@
1
- import * as questpie367 from "questpie";
1
+ import * as questpie454 from "questpie";
2
2
 
3
3
  //#region src/server/modules/admin/routes/admin-config.d.ts
4
4
 
@@ -22,7 +22,7 @@ import * as questpie367 from "questpie";
22
22
  * Registered via module routes and exposed through the fetch handler.
23
23
  */
24
24
  declare const adminConfigFunctions: {
25
- readonly getAdminConfig: questpie367.JsonRouteDefinition<Record<string, never> | undefined, any, questpie367.JsonRouteParams>;
25
+ readonly getAdminConfig: questpie454.JsonRouteDefinition<Record<string, never> | undefined, any, questpie454.JsonRouteParams>;
26
26
  };
27
27
  //#endregion
28
28
  export { adminConfigFunctions };
@@ -1,7 +1,7 @@
1
1
  import { ServerActionDefinition, ServerActionResult } from "../../../augmentation/actions.mjs";
2
2
  import "../../../augmentation.mjs";
3
3
  import { App } from "./route-helpers.mjs";
4
- import * as questpie64 from "questpie";
4
+ import * as questpie121 from "questpie";
5
5
 
6
6
  //#region src/server/modules/admin/routes/execute-action.d.ts
7
7
 
@@ -56,37 +56,37 @@ declare function executeAction(app: App, request: ExecuteActionRequest, session?
56
56
  * });
57
57
  * ```
58
58
  */
59
- declare const executeActionFn: questpie64.JsonRouteDefinition<{
59
+ declare const executeActionFn: questpie121.JsonRouteDefinition<{
60
60
  collection: string;
61
61
  actionId: string;
62
62
  itemId?: string | undefined;
63
63
  itemIds?: string[] | undefined;
64
64
  data?: Record<string, unknown> | undefined;
65
65
  locale?: string | undefined;
66
- }, any, questpie64.JsonRouteParams>;
66
+ }, any, questpie121.JsonRouteParams>;
67
67
  /**
68
68
  * Get actions configuration for a collection.
69
69
  * Returns action definitions without handlers for client rendering.
70
70
  */
71
- declare const getActionsConfigFn: questpie64.JsonRouteDefinition<{
71
+ declare const getActionsConfigFn: questpie121.JsonRouteDefinition<{
72
72
  collection: string;
73
- }, any, questpie64.JsonRouteParams>;
73
+ }, any, questpie121.JsonRouteParams>;
74
74
  /**
75
75
  * QUESTPIE functions for action execution.
76
76
  * These are registered on the `adminModule`.
77
77
  */
78
78
  declare const actionFunctions: {
79
- executeAction: questpie64.JsonRouteDefinition<{
79
+ executeAction: questpie121.JsonRouteDefinition<{
80
80
  collection: string;
81
81
  actionId: string;
82
82
  itemId?: string | undefined;
83
83
  itemIds?: string[] | undefined;
84
84
  data?: Record<string, unknown> | undefined;
85
85
  locale?: string | undefined;
86
- }, any, questpie64.JsonRouteParams>;
87
- getActionsConfig: questpie64.JsonRouteDefinition<{
86
+ }, any, questpie121.JsonRouteParams>;
87
+ getActionsConfig: questpie121.JsonRouteDefinition<{
88
88
  collection: string;
89
- }, any, questpie64.JsonRouteParams>;
89
+ }, any, questpie121.JsonRouteParams>;
90
90
  };
91
91
  //#endregion
92
92
  export { ExecuteActionRequest, ExecuteActionResponse, actionFunctions, executeAction, executeActionFn, getActionsConfig, getActionsConfigFn };
@@ -1,4 +1,4 @@
1
- import * as questpie395 from "questpie";
1
+ import * as questpie456 from "questpie";
2
2
 
3
3
  //#region src/server/modules/admin/routes/locales.d.ts
4
4
 
@@ -12,7 +12,7 @@ import * as questpie395 from "questpie";
12
12
  * Bundle of locale-related functions.
13
13
  */
14
14
  declare const localeFunctions: {
15
- readonly getContentLocales: questpie395.JsonRouteDefinition<Record<string, never> | undefined, any, questpie395.JsonRouteParams>;
15
+ readonly getContentLocales: questpie456.JsonRouteDefinition<Record<string, never> | undefined, any, questpie456.JsonRouteParams>;
16
16
  };
17
17
  //#endregion
18
18
  export { localeFunctions };
@@ -1,4 +1,4 @@
1
- import * as questpie88 from "questpie";
1
+ import * as questpie141 from "questpie";
2
2
 
3
3
  //#region src/server/modules/admin/routes/preview.d.ts
4
4
 
@@ -10,6 +10,9 @@ import * as questpie88 from "questpie";
10
10
  *
11
11
  * Browser-safe utilities (isDraftMode, createDraftModeCookie, etc.) are in @questpie/admin/shared
12
12
  */
13
+ type PreviewSecretResolverContext = Record<string, any>;
14
+ type PreviewSecretResolver = (ctx: PreviewSecretResolverContext) => string | Promise<string>;
15
+ type PreviewSecretSource = string | PreviewSecretResolver;
13
16
  interface PreviewTokenPayload {
14
17
  path: string;
15
18
  exp: number;
@@ -17,17 +20,18 @@ interface PreviewTokenPayload {
17
20
  /**
18
21
  * Create preview-related RPC functions.
19
22
  *
20
- * @param secret - Secret key for signing tokens
23
+ * @param secret - Secret key or resolver for signing tokens. Defaults to
24
+ * `app.config.secret` from the current route context.
21
25
  * @returns Object with preview functions
22
26
  */
23
- declare function createPreviewFunctions(secret: string): {
24
- mintPreviewToken: questpie88.JsonRouteDefinition<{
27
+ declare function createPreviewFunctions(secret?: PreviewSecretSource): {
28
+ mintPreviewToken: questpie141.JsonRouteDefinition<{
25
29
  path: string;
26
30
  ttlMs?: number | undefined;
27
- }, any, questpie88.JsonRouteParams>;
28
- verifyPreviewToken: questpie88.JsonRouteDefinition<{
31
+ }, any, questpie141.JsonRouteParams>;
32
+ verifyPreviewToken: questpie141.JsonRouteDefinition<{
29
33
  token: string;
30
- }, any, questpie88.JsonRouteParams>;
34
+ }, any, questpie141.JsonRouteParams>;
31
35
  };
32
36
  /**
33
37
  * Verify a preview token without RPC.
@@ -37,44 +41,45 @@ declare function createPreviewFunctions(secret: string): {
37
41
  * @param secret - The secret used to sign the token
38
42
  * @returns The payload if valid, null otherwise
39
43
  */
40
- declare function verifyPreviewTokenDirect(token: string, secret: string): PreviewTokenPayload | null;
44
+ declare function verifyPreviewTokenDirect(token: string, secret: string): Promise<PreviewTokenPayload | null>;
41
45
  /**
42
46
  * Create a preview token verifier with bound secret.
43
47
  * Use this in route handlers to avoid passing secret repeatedly.
44
48
  *
45
- * @param secret - The secret used to sign tokens (optional, uses env if not provided)
49
+ * @param secret - The secret used to sign tokens.
46
50
  * @returns A verify function that only needs the token
47
51
  *
48
52
  * @example
49
53
  * ```ts
50
54
  * // Create once at module level
51
- * const verifyPreviewToken = createPreviewTokenVerifier();
55
+ * const verifyPreviewToken = createPreviewTokenVerifier(secret);
52
56
  *
53
57
  * // Use in route handler
54
- * const payload = verifyPreviewToken(token);
58
+ * const payload = await verifyPreviewToken(token);
55
59
  * if (!payload) {
56
60
  * return new Response("Invalid token", { status: 401 });
57
61
  * }
58
62
  * ```
59
63
  */
60
- declare function createPreviewTokenVerifier(secret?: string): (token: string) => PreviewTokenPayload | null;
64
+ declare function createPreviewTokenVerifier(secret: string): (token: string) => Promise<PreviewTokenPayload | null>;
61
65
  /**
62
- * Default preview functions bundle with env-based secret.
66
+ * Default preview functions bundle. The route handlers resolve the token
67
+ * secret from `app.config.secret` at request time.
63
68
  * Used by the `adminModule` to register preview RPC functions.
64
69
  */
65
70
  declare const previewFunctions: {
66
- getPreviewUrl: questpie88.JsonRouteDefinition<{
71
+ getPreviewUrl: questpie141.JsonRouteDefinition<{
67
72
  collection: string;
68
73
  record: Record<string, unknown>;
69
74
  locale?: string | undefined;
70
- }, any, questpie88.JsonRouteParams>;
71
- mintPreviewToken: questpie88.JsonRouteDefinition<{
75
+ }, any, questpie141.JsonRouteParams>;
76
+ mintPreviewToken: questpie141.JsonRouteDefinition<{
72
77
  path: string;
73
78
  ttlMs?: number | undefined;
74
- }, any, questpie88.JsonRouteParams>;
75
- verifyPreviewToken: questpie88.JsonRouteDefinition<{
79
+ }, any, questpie141.JsonRouteParams>;
80
+ verifyPreviewToken: questpie141.JsonRouteDefinition<{
76
81
  token: string;
77
- }, any, questpie88.JsonRouteParams>;
82
+ }, any, questpie141.JsonRouteParams>;
78
83
  };
79
84
  //#endregion
80
85
  export { PreviewTokenPayload, createPreviewFunctions, createPreviewTokenVerifier, previewFunctions, verifyPreviewTokenDirect };
@@ -1,9 +1,7 @@
1
- import { getPreviewSecret } from "../../../../shared/preview-utils.mjs";
2
1
  import { getApp, getCollectionState, getLocale, getSession } from "./route-helpers.mjs";
3
2
  import { translateAdminMessage } from "./i18n-helpers.mjs";
4
3
  import { z } from "zod";
5
4
  import { ApiError, route } from "questpie";
6
- import { createHmac, timingSafeEqual } from "node:crypto";
7
5
 
8
6
  //#region src/server/modules/admin/routes/preview.ts
9
7
  /**
@@ -14,14 +12,72 @@ import { createHmac, timingSafeEqual } from "node:crypto";
14
12
  *
15
13
  * Browser-safe utilities (isDraftMode, createDraftModeCookie, etc.) are in @questpie/admin/shared
16
14
  */
15
+ const textEncoder = new TextEncoder();
16
+ const textDecoder = new TextDecoder();
17
+ function bytesFromBase64Input(input) {
18
+ if (typeof input === "string") return textEncoder.encode(input);
19
+ if (input instanceof Uint8Array) return input;
20
+ return new Uint8Array(input);
21
+ }
22
+ function bytesToBase64(bytes) {
23
+ let binary = "";
24
+ const chunkSize = 32768;
25
+ for (let i = 0; i < bytes.length; i += chunkSize) binary += String.fromCharCode(...bytes.subarray(i, i + chunkSize));
26
+ return btoa(binary);
27
+ }
28
+ function base64ToBytes(input) {
29
+ const binary = atob(input);
30
+ const bytes = new Uint8Array(binary.length);
31
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
32
+ return bytes;
33
+ }
17
34
  function base64UrlEncode(input) {
18
- return (Buffer.isBuffer(input) ? input : Buffer.from(input)).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
35
+ return bytesToBase64(bytesFromBase64Input(input)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
19
36
  }
20
37
  function base64UrlDecode(input) {
21
38
  let base64 = input.replace(/-/g, "+").replace(/_/g, "/");
22
39
  const padding = base64.length % 4;
23
40
  if (padding) base64 += "=".repeat(4 - padding);
24
- return Buffer.from(base64, "base64").toString("utf8");
41
+ return textDecoder.decode(base64ToBytes(base64));
42
+ }
43
+ async function signPayload(payload, secret) {
44
+ const subtle = globalThis.crypto?.subtle;
45
+ if (!subtle) throw new Error("[preview] Web Crypto API is required to sign preview tokens.");
46
+ const key = await subtle.importKey("raw", textEncoder.encode(secret), {
47
+ name: "HMAC",
48
+ hash: "SHA-256"
49
+ }, false, ["sign"]);
50
+ return base64UrlEncode(await subtle.sign("HMAC", key, textEncoder.encode(payload)));
51
+ }
52
+ function timingSafeEqualAscii(a, b) {
53
+ const aBytes = textEncoder.encode(a);
54
+ const bBytes = textEncoder.encode(b);
55
+ let diff = aBytes.length ^ bBytes.length;
56
+ const maxLength = Math.max(aBytes.length, bBytes.length);
57
+ for (let i = 0; i < maxLength; i++) diff |= (aBytes[i] ?? 0) ^ (bBytes[i] ?? 0);
58
+ return diff === 0;
59
+ }
60
+ function defaultPreviewSecret(ctx) {
61
+ return getApp(ctx).config.secret ?? "dev-preview-secret";
62
+ }
63
+ async function resolvePreviewSecret(source, ctx) {
64
+ const secret = typeof source === "function" ? await source(ctx) : source;
65
+ if (!secret) throw new Error("[preview] Preview token secret must not be empty.");
66
+ return secret;
67
+ }
68
+ function parsePreviewTokenPayload(encodedPayload) {
69
+ try {
70
+ const payload = JSON.parse(base64UrlDecode(encodedPayload));
71
+ if (!payload?.exp || typeof payload.exp !== "number") return { error: "invalidPayload" };
72
+ if (payload.exp < Date.now()) return { error: "tokenExpired" };
73
+ if (!payload.path || typeof payload.path !== "string") return { error: "invalidPath" };
74
+ return { payload };
75
+ } catch {
76
+ return { error: "invalidPayload" };
77
+ }
78
+ }
79
+ async function verifyPreviewSignature(encodedPayload, signature, secret) {
80
+ return timingSafeEqualAscii(signature, await signPayload(encodedPayload, secret));
25
81
  }
26
82
  const mintPreviewTokenSchema = z.object({
27
83
  path: z.string().min(1),
@@ -41,13 +97,11 @@ const DEFAULT_TTL_MS = 3600 * 1e3;
41
97
  /**
42
98
  * Create preview-related RPC functions.
43
99
  *
44
- * @param secret - Secret key for signing tokens
100
+ * @param secret - Secret key or resolver for signing tokens. Defaults to
101
+ * `app.config.secret` from the current route context.
45
102
  * @returns Object with preview functions
46
103
  */
47
- function createPreviewFunctions(secret) {
48
- const signPayload = (payload) => {
49
- return base64UrlEncode(createHmac("sha256", secret).update(payload).digest());
50
- };
104
+ function createPreviewFunctions(secret = defaultPreviewSecret) {
51
105
  return {
52
106
  mintPreviewToken: route().post().schema(mintPreviewTokenSchema).outputSchema(mintPreviewTokenOutputSchema).handler(async (ctx) => {
53
107
  const { input } = ctx;
@@ -62,7 +116,7 @@ function createPreviewFunctions(secret) {
62
116
  };
63
117
  const encodedPayload = base64UrlEncode(JSON.stringify(payload));
64
118
  return {
65
- token: `${encodedPayload}.${signPayload(encodedPayload)}`,
119
+ token: `${encodedPayload}.${await signPayload(encodedPayload, await resolvePreviewSecret(secret, ctx))}`,
66
120
  expiresAt
67
121
  };
68
122
  }),
@@ -76,41 +130,19 @@ function createPreviewFunctions(secret) {
76
130
  valid: false,
77
131
  error: t("preview.invalidTokenFormat")
78
132
  };
79
- const expectedSignature = signPayload(encodedPayload);
80
- const signatureBuffer = Uint8Array.from(Buffer.from(signature));
81
- const expectedBuffer = Uint8Array.from(Buffer.from(expectedSignature));
82
- if (signatureBuffer.length !== expectedBuffer.length) return {
133
+ if (!await verifyPreviewSignature(encodedPayload, signature, await resolvePreviewSecret(secret, ctx))) return {
83
134
  valid: false,
84
135
  error: t("preview.invalidSignature")
85
136
  };
86
- if (!timingSafeEqual(signatureBuffer, expectedBuffer)) return {
137
+ const parsed = parsePreviewTokenPayload(encodedPayload);
138
+ if ("error" in parsed) return {
87
139
  valid: false,
88
- error: t("preview.invalidSignature")
140
+ error: t(`preview.${parsed.error}`)
141
+ };
142
+ return {
143
+ valid: true,
144
+ path: parsed.payload.path
89
145
  };
90
- try {
91
- const payload = JSON.parse(base64UrlDecode(encodedPayload));
92
- if (!payload?.exp || typeof payload.exp !== "number") return {
93
- valid: false,
94
- error: t("preview.invalidPayload")
95
- };
96
- if (payload.exp < Date.now()) return {
97
- valid: false,
98
- error: t("preview.tokenExpired")
99
- };
100
- if (!payload.path || typeof payload.path !== "string") return {
101
- valid: false,
102
- error: t("preview.invalidPath")
103
- };
104
- return {
105
- valid: true,
106
- path: payload.path
107
- };
108
- } catch {
109
- return {
110
- valid: false,
111
- error: t("preview.invalidPayload")
112
- };
113
- }
114
146
  })
115
147
  };
116
148
  }
@@ -122,47 +154,35 @@ function createPreviewFunctions(secret) {
122
154
  * @param secret - The secret used to sign the token
123
155
  * @returns The payload if valid, null otherwise
124
156
  */
125
- function verifyPreviewTokenDirect(token, secret) {
157
+ async function verifyPreviewTokenDirect(token, secret) {
126
158
  const [encodedPayload, signature] = token.split(".");
127
159
  if (!encodedPayload || !signature) return null;
128
- const expectedSignature = base64UrlEncode(createHmac("sha256", secret).update(encodedPayload).digest());
129
- const signatureBuffer = Uint8Array.from(Buffer.from(signature));
130
- const expectedBuffer = Uint8Array.from(Buffer.from(expectedSignature));
131
- if (signatureBuffer.length !== expectedBuffer.length) return null;
132
- if (!timingSafeEqual(signatureBuffer, expectedBuffer)) return null;
133
- try {
134
- const payload = JSON.parse(base64UrlDecode(encodedPayload));
135
- if (!payload?.exp || typeof payload.exp !== "number") return null;
136
- if (payload.exp < Date.now()) return null;
137
- if (!payload.path || typeof payload.path !== "string") return null;
138
- return payload;
139
- } catch {
140
- return null;
141
- }
160
+ if (!await verifyPreviewSignature(encodedPayload, signature, secret)) return null;
161
+ const parsed = parsePreviewTokenPayload(encodedPayload);
162
+ return "payload" in parsed ? parsed.payload : null;
142
163
  }
143
164
  /**
144
165
  * Create a preview token verifier with bound secret.
145
166
  * Use this in route handlers to avoid passing secret repeatedly.
146
167
  *
147
- * @param secret - The secret used to sign tokens (optional, uses env if not provided)
168
+ * @param secret - The secret used to sign tokens.
148
169
  * @returns A verify function that only needs the token
149
170
  *
150
171
  * @example
151
172
  * ```ts
152
173
  * // Create once at module level
153
- * const verifyPreviewToken = createPreviewTokenVerifier();
174
+ * const verifyPreviewToken = createPreviewTokenVerifier(secret);
154
175
  *
155
176
  * // Use in route handler
156
- * const payload = verifyPreviewToken(token);
177
+ * const payload = await verifyPreviewToken(token);
157
178
  * if (!payload) {
158
179
  * return new Response("Invalid token", { status: 401 });
159
180
  * }
160
181
  * ```
161
182
  */
162
183
  function createPreviewTokenVerifier(secret) {
163
- const resolvedSecret = secret ?? getPreviewSecret();
164
184
  return (token) => {
165
- return verifyPreviewTokenDirect(token, resolvedSecret);
185
+ return verifyPreviewTokenDirect(token, secret);
166
186
  };
167
187
  }
168
188
  const getPreviewUrlSchema = z.object({
@@ -226,11 +246,12 @@ const getPreviewUrl = route().post().schema(getPreviewUrlSchema).outputSchema(ge
226
246
  }
227
247
  });
228
248
  /**
229
- * Default preview functions bundle with env-based secret.
249
+ * Default preview functions bundle. The route handlers resolve the token
250
+ * secret from `app.config.secret` at request time.
230
251
  * Used by the `adminModule` to register preview RPC functions.
231
252
  */
232
253
  const previewFunctions = {
233
- ...createPreviewFunctions(getPreviewSecret()),
254
+ ...createPreviewFunctions(),
234
255
  getPreviewUrl
235
256
  };
236
257
 
@@ -1,4 +1,4 @@
1
- import * as questpie80 from "questpie";
1
+ import * as questpie113 from "questpie";
2
2
 
3
3
  //#region src/server/modules/admin/routes/reactive.d.ts
4
4
 
@@ -13,7 +13,7 @@ import * as questpie80 from "questpie";
13
13
  * Batch reactive endpoint.
14
14
  * Executes multiple reactive handlers in a single request.
15
15
  */
16
- declare const batchReactive: questpie80.JsonRouteDefinition<{
16
+ declare const batchReactive: questpie113.JsonRouteDefinition<{
17
17
  collection: string;
18
18
  type: "collection" | "global";
19
19
  requests: {
@@ -27,12 +27,12 @@ declare const batchReactive: questpie80.JsonRouteDefinition<{
27
27
  }[];
28
28
  formData?: Record<string, unknown> | undefined;
29
29
  prevData?: Record<string, unknown> | null | undefined;
30
- }, any, questpie80.JsonRouteParams>;
30
+ }, any, questpie113.JsonRouteParams>;
31
31
  /**
32
32
  * Dynamic options endpoint.
33
33
  * Fetches options for select/relation fields with search and pagination.
34
34
  */
35
- declare const fieldOptions: questpie80.JsonRouteDefinition<{
35
+ declare const fieldOptions: questpie113.JsonRouteDefinition<{
36
36
  collection: string;
37
37
  type: "collection" | "global";
38
38
  field: string;
@@ -41,12 +41,12 @@ declare const fieldOptions: questpie80.JsonRouteDefinition<{
41
41
  page: number;
42
42
  limit: number;
43
43
  siblingData?: Record<string, unknown> | null | undefined;
44
- }, any, questpie80.JsonRouteParams>;
44
+ }, any, questpie113.JsonRouteParams>;
45
45
  /**
46
46
  * Reactive functions bundle.
47
47
  */
48
48
  declare const reactiveFunctions: {
49
- readonly batchReactive: questpie80.JsonRouteDefinition<{
49
+ readonly batchReactive: questpie113.JsonRouteDefinition<{
50
50
  collection: string;
51
51
  type: "collection" | "global";
52
52
  requests: {
@@ -60,8 +60,8 @@ declare const reactiveFunctions: {
60
60
  }[];
61
61
  formData?: Record<string, unknown> | undefined;
62
62
  prevData?: Record<string, unknown> | null | undefined;
63
- }, any, questpie80.JsonRouteParams>;
64
- readonly fieldOptions: questpie80.JsonRouteDefinition<{
63
+ }, any, questpie113.JsonRouteParams>;
64
+ readonly fieldOptions: questpie113.JsonRouteDefinition<{
65
65
  collection: string;
66
66
  type: "collection" | "global";
67
67
  field: string;
@@ -70,7 +70,7 @@ declare const reactiveFunctions: {
70
70
  page: number;
71
71
  limit: number;
72
72
  siblingData?: Record<string, unknown> | null | undefined;
73
- }, any, questpie80.JsonRouteParams>;
73
+ }, any, questpie113.JsonRouteParams>;
74
74
  };
75
75
  //#endregion
76
76
  export { batchReactive, fieldOptions, reactiveFunctions };
@@ -1,4 +1,4 @@
1
- import * as questpie72 from "questpie";
1
+ import * as questpie129 from "questpie";
2
2
 
3
3
  //#region src/server/modules/admin/routes/setup.d.ts
4
4
 
@@ -20,7 +20,7 @@ import * as questpie72 from "questpie";
20
20
  * }
21
21
  * ```
22
22
  */
23
- declare const isSetupRequired: questpie72.JsonRouteDefinition<Record<string, never>, any, questpie72.JsonRouteParams>;
23
+ declare const isSetupRequired: questpie129.JsonRouteDefinition<Record<string, never>, any, questpie129.JsonRouteParams>;
24
24
  /**
25
25
  * Create the first admin user in the system.
26
26
  * This function only works when no admin users exist (setup mode).
@@ -43,21 +43,21 @@ declare const isSetupRequired: questpie72.JsonRouteDefinition<Record<string, nev
43
43
  * }
44
44
  * ```
45
45
  */
46
- declare const createFirstAdmin: questpie72.JsonRouteDefinition<{
46
+ declare const createFirstAdmin: questpie129.JsonRouteDefinition<{
47
47
  email: string;
48
48
  password: string;
49
49
  name: string;
50
- }, any, questpie72.JsonRouteParams>;
50
+ }, any, questpie129.JsonRouteParams>;
51
51
  /**
52
52
  * Bundle of setup-related functions.
53
53
  */
54
54
  declare const setupFunctions: {
55
- readonly isSetupRequired: questpie72.JsonRouteDefinition<Record<string, never>, any, questpie72.JsonRouteParams>;
56
- readonly createFirstAdmin: questpie72.JsonRouteDefinition<{
55
+ readonly isSetupRequired: questpie129.JsonRouteDefinition<Record<string, never>, any, questpie129.JsonRouteParams>;
56
+ readonly createFirstAdmin: questpie129.JsonRouteDefinition<{
57
57
  email: string;
58
58
  password: string;
59
59
  name: string;
60
- }, any, questpie72.JsonRouteParams>;
60
+ }, any, questpie129.JsonRouteParams>;
61
61
  };
62
62
  //#endregion
63
63
  export { createFirstAdmin, isSetupRequired, setupFunctions };
@@ -1,4 +1,4 @@
1
- import * as questpie397 from "questpie";
1
+ import * as questpie458 from "questpie";
2
2
 
3
3
  //#region src/server/modules/admin/routes/translations.d.ts
4
4
 
@@ -17,10 +17,10 @@ import * as questpie397 from "questpie";
17
17
  * Bundle of translation-related functions.
18
18
  */
19
19
  declare const translationFunctions: {
20
- readonly getAdminTranslations: questpie397.JsonRouteDefinition<{
20
+ readonly getAdminTranslations: questpie458.JsonRouteDefinition<{
21
21
  locale: string;
22
- }, any, questpie397.JsonRouteParams>;
23
- readonly getAdminLocales: questpie397.JsonRouteDefinition<Record<string, never> | undefined, any, questpie397.JsonRouteParams>;
22
+ }, any, questpie458.JsonRouteParams>;
23
+ readonly getAdminLocales: questpie458.JsonRouteDefinition<Record<string, never> | undefined, any, questpie458.JsonRouteParams>;
24
24
  };
25
25
  //#endregion
26
26
  export { translationFunctions };
@@ -1,4 +1,4 @@
1
- import * as questpie16 from "questpie";
1
+ import * as questpie137 from "questpie";
2
2
 
3
3
  //#region src/server/modules/admin/routes/widget-data.d.ts
4
4
 
@@ -20,13 +20,13 @@ import * as questpie16 from "questpie";
20
20
  * const data = await client.routes.fetchWidgetData({ widgetId: "my-widget" });
21
21
  * ```
22
22
  */
23
- declare const fetchWidgetData: questpie16.JsonRouteDefinition<{
23
+ declare const fetchWidgetData: questpie137.JsonRouteDefinition<{
24
24
  widgetId: string;
25
- }, any, questpie16.JsonRouteParams>;
25
+ }, any, questpie137.JsonRouteParams>;
26
26
  declare const widgetDataFunctions: {
27
- readonly fetchWidgetData: questpie16.JsonRouteDefinition<{
27
+ readonly fetchWidgetData: questpie137.JsonRouteDefinition<{
28
28
  widgetId: string;
29
- }, any, questpie16.JsonRouteParams>;
29
+ }, any, questpie137.JsonRouteParams>;
30
30
  };
31
31
  //#endregion
32
32
  export { fetchWidgetData, widgetDataFunctions };