@frigear-nu/forms 0.1.2 → 0.1.3

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 (69) hide show
  1. package/dist/module.d.mts +18 -1
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +29 -26
  4. package/dist/runtime/app/components/FormBuilderAccess.vue +255 -262
  5. package/dist/runtime/app/components/FormBuilderSettings.vue +143 -141
  6. package/dist/runtime/app/components/FormCreateSlideover.vue +2 -2
  7. package/dist/runtime/app/components/FormEmbed.vue +2 -2
  8. package/dist/runtime/app/components/FormImportSlideover.d.vue.ts +8 -0
  9. package/dist/runtime/app/components/FormImportSlideover.vue +57 -0
  10. package/dist/runtime/app/components/FormImportSlideover.vue.d.ts +8 -0
  11. package/dist/runtime/app/components/FormRenderer.vue +3 -3
  12. package/dist/runtime/app/components/FormSubmissionView.vue +2 -2
  13. package/dist/runtime/app/composables/useCaptchaSecrets.js +9 -6
  14. package/dist/runtime/app/composables/useFormActions.d.ts +14 -2
  15. package/dist/runtime/app/composables/useFormActions.js +33 -21
  16. package/dist/runtime/app/composables/useForms.d.ts +12 -5
  17. package/dist/runtime/app/composables/useForms.js +11 -5
  18. package/dist/runtime/app/pages/admin-form.vue +36 -20
  19. package/dist/runtime/app/pages/admin-submission.vue +27 -16
  20. package/dist/runtime/app/pages/admin-submissions.vue +30 -22
  21. package/dist/runtime/app/pages/admin.vue +29 -78
  22. package/dist/runtime/app/pages/form.vue +2 -1
  23. package/dist/runtime/i18n/lang/da.json +10 -2
  24. package/dist/runtime/i18n/lang/en.json +10 -2
  25. package/dist/runtime/server/mcp/tools/admin/get-submission.js +4 -4
  26. package/dist/runtime/server/mcp/tools/submit-form.js +2 -1
  27. package/dist/runtime/server/routes/admin/form-clone.post.js +6 -6
  28. package/dist/runtime/server/routes/admin/form-password.delete.js +5 -3
  29. package/dist/runtime/server/routes/admin/form-password.get.js +4 -2
  30. package/dist/runtime/server/routes/admin/form-password.put.js +4 -2
  31. package/dist/runtime/server/routes/admin/form-secret.delete.js +6 -5
  32. package/dist/runtime/server/routes/admin/form-secret.put.js +11 -0
  33. package/dist/runtime/server/routes/admin/form-secrets.get.js +4 -2
  34. package/dist/runtime/server/routes/admin/form-token.delete.js +9 -6
  35. package/dist/runtime/server/routes/admin/form-tokens.get.js +5 -3
  36. package/dist/runtime/server/routes/admin/form-tokens.post.js +5 -4
  37. package/dist/runtime/server/routes/admin/form.delete.js +6 -4
  38. package/dist/runtime/server/routes/admin/form.get.js +4 -2
  39. package/dist/runtime/server/routes/admin/form.patch.js +3 -3
  40. package/dist/runtime/server/routes/admin/forms.get.js +37 -16
  41. package/dist/runtime/server/routes/admin/forms.post.js +3 -4
  42. package/dist/runtime/server/routes/admin/import.post.js +3 -2
  43. package/dist/runtime/server/routes/admin/submission-file.get.js +8 -5
  44. package/dist/runtime/server/routes/admin/submission-redeliver.post.js +4 -3
  45. package/dist/runtime/server/routes/admin/submission.get.js +5 -17
  46. package/dist/runtime/server/routes/admin/submissions.get.js +29 -13
  47. package/dist/runtime/server/routes/public/access.get.js +3 -1
  48. package/dist/runtime/server/routes/public/form.get.js +3 -1
  49. package/dist/runtime/server/routes/public/submit.post.js +2 -1
  50. package/dist/runtime/server/utils/access.js +3 -2
  51. package/dist/runtime/server/utils/admin-schemas.d.ts +9 -0
  52. package/dist/runtime/server/utils/admin-schemas.js +4 -0
  53. package/dist/runtime/server/utils/auth.d.ts +8 -4
  54. package/dist/runtime/server/utils/auth.js +6 -7
  55. package/dist/runtime/server/utils/captcha.js +3 -2
  56. package/dist/runtime/server/utils/forms.d.ts +12 -2
  57. package/dist/runtime/server/utils/forms.js +57 -10
  58. package/dist/runtime/server/utils/mcp-access.js +2 -1
  59. package/dist/runtime/server/utils/rate-limit.js +3 -2
  60. package/dist/runtime/server/utils/submit.d.ts +3 -0
  61. package/dist/runtime/server/utils/submit.js +21 -17
  62. package/dist/runtime/utils/api.d.ts +36 -0
  63. package/dist/runtime/utils/api.js +39 -0
  64. package/dist/runtime/utils/index.d.ts +1 -0
  65. package/dist/runtime/utils/index.js +1 -0
  66. package/dist/types.d.mts +1 -1
  67. package/package.json +15 -10
  68. package/dist/runtime/server/routes/admin/form-secrets.put.js +0 -12
  69. /package/dist/runtime/server/routes/admin/{form-secrets.put.d.ts → form-secret.put.d.ts} +0 -0
@@ -1,17 +1,19 @@
1
1
  import { eq } from "drizzle-orm";
2
2
  import { db, schema } from "@nuxthub/db";
3
+ import { conflict, isoDate, notFound } from "@frigear-nu/module-kit/server";
3
4
  import { newId } from "../../utils/index.js";
5
+ import { renderAnswers } from "./render.js";
4
6
  export const toPublicForm = (row) => {
5
7
  const { delivery: _delivery, ...definition } = row.definition;
6
8
  return { id: row.id, slug: row.slug, name: row.name, version: row.version, definition };
7
9
  };
8
- export const toTokenDto = (row) => ({
10
+ export const toFormTokenDto = (row) => ({
9
11
  id: row.id,
10
12
  name: row.name,
11
13
  prefix: row.prefix,
12
- createdAt: row.createdAt.toISOString(),
13
- lastUsedAt: row.lastUsedAt?.toISOString() ?? null,
14
- revokedAt: row.revokedAt?.toISOString() ?? null
14
+ createdAt: isoDate(row.createdAt),
15
+ lastUsedAt: isoDate(row.lastUsedAt),
16
+ revokedAt: isoDate(row.revokedAt)
15
17
  });
16
18
  export const toAdminForm = (row) => ({
17
19
  id: row.id,
@@ -20,12 +22,57 @@ export const toAdminForm = (row) => ({
20
22
  status: row.status,
21
23
  version: row.version,
22
24
  definition: row.definition,
23
- createdAt: row.createdAt.toISOString(),
24
- updatedAt: row.updatedAt.toISOString()
25
+ createdAt: isoDate(row.createdAt),
26
+ updatedAt: isoDate(row.updatedAt)
25
27
  });
28
+ export const toFormSummaryDto = (row, submissionCount) => ({
29
+ id: row.id,
30
+ slug: row.slug,
31
+ name: row.name,
32
+ status: row.status,
33
+ version: row.version,
34
+ storage: row.definition.storage,
35
+ mode: row.definition.mode,
36
+ submissionCount,
37
+ updatedAt: isoDate(row.updatedAt)
38
+ });
39
+ export const toSubmissionSummaryDto = (row) => ({
40
+ id: row.id,
41
+ createdAt: isoDate(row.createdAt),
42
+ locale: row.locale,
43
+ userId: row.userId,
44
+ deliveryStatus: row.deliveryStatus,
45
+ preview: renderAnswers(row.definitionSnapshot, row.answers, row.locale).filter((answer) => answer.value).slice(0, 3).map(({ label, value }) => ({ label, value: value.slice(0, 120) }))
46
+ });
47
+ export const toSubmissionFileDto = (row) => ({
48
+ id: row.id,
49
+ fieldKey: row.fieldKey,
50
+ filename: row.filename,
51
+ contentType: row.contentType,
52
+ size: row.size
53
+ });
54
+ export const toSubmissionDto = (row, files) => ({
55
+ id: row.id,
56
+ formId: row.formId,
57
+ formSlug: row.formSlug,
58
+ formVersion: row.formVersion,
59
+ definition: row.definitionSnapshot,
60
+ answers: row.answers,
61
+ files: files.map(toSubmissionFileDto),
62
+ locale: row.locale,
63
+ userId: row.userId,
64
+ deliveryStatus: row.deliveryStatus,
65
+ deliveryError: row.deliveryError,
66
+ createdAt: isoDate(row.createdAt)
67
+ });
68
+ export async function findSubmissionById(id) {
69
+ const [row] = await db.select().from(schema.formSubmissions).where(eq(schema.formSubmissions.id, id)).limit(1);
70
+ if (!row) throw notFound("Submission not found.");
71
+ return row;
72
+ }
26
73
  export async function findFormById(id) {
27
74
  const [row] = await db.select().from(schema.forms).where(eq(schema.forms.id, id)).limit(1);
28
- if (!row) throw createError({ statusCode: 404, statusMessage: "Form not found." });
75
+ if (!row) throw notFound("Form not found.");
29
76
  return row;
30
77
  }
31
78
  export async function findFormBySlug(slug) {
@@ -34,12 +81,12 @@ export async function findFormBySlug(slug) {
34
81
  }
35
82
  export async function findPublishedForm(slug) {
36
83
  const row = await findFormBySlug(slug);
37
- if (!row || row.status !== "published") throw createError({ statusCode: 404, statusMessage: "Form not found." });
84
+ if (!row || row.status !== "published") throw notFound("Form not found.");
38
85
  return row;
39
86
  }
40
87
  export async function createForm(input) {
41
88
  if (await findFormBySlug(input.slug)) {
42
- throw createError({ statusCode: 409, statusMessage: "A form with this slug already exists." });
89
+ throw conflict("A form with this slug already exists.", { field: "slug" });
43
90
  }
44
91
  const [row] = await db.insert(schema.forms).values({ id: newId("frm"), ...input }).returning();
45
92
  return row;
@@ -47,7 +94,7 @@ export async function createForm(input) {
47
94
  export async function patchForm(id, input) {
48
95
  const current = await findFormById(id);
49
96
  if (input.slug && input.slug !== current.slug && await findFormBySlug(input.slug)) {
50
- throw createError({ statusCode: 409, statusMessage: "A form with this slug already exists." });
97
+ throw conflict("A form with this slug already exists.", { field: "slug" });
51
98
  }
52
99
  const definitionChanged = input.definition !== void 0 && JSON.stringify(input.definition) !== JSON.stringify(current.definition);
53
100
  const [row] = await db.update(schema.forms).set({
@@ -1,8 +1,9 @@
1
+ import { forbidden } from "@frigear-nu/module-kit/server";
1
2
  import { resolveAccess } from "../../utils/index.js";
2
3
  export async function assertMcpFormAccess(event, form) {
3
4
  const { auth } = resolveAccess(form.definition);
4
5
  if (auth === "token" || auth === "password") {
5
- throw createError({ statusCode: 403, statusMessage: "This form needs its own access token or password and cannot be used through MCP." });
6
+ throw forbidden("This form needs its own access token or password and cannot be used through MCP.");
6
7
  }
7
8
  await authorizeFormAccess(event, form);
8
9
  }
@@ -1,3 +1,4 @@
1
+ import { apiError } from "@frigear-nu/module-kit/server";
1
2
  import { kv } from "@nuxthub/kv";
2
3
  export async function enforceRateLimit(event, slug) {
3
4
  const { rateLimit, rateLimitWindowSeconds } = useRuntimeConfig().forms;
@@ -8,7 +9,7 @@ export async function enforceRateLimit(event, slug) {
8
9
  const used = Number(await kv.get(key) ?? 0);
9
10
  if (used >= rateLimit) {
10
11
  setResponseHeader(event, "retry-after", rateLimitWindowSeconds);
11
- throw createError({ statusCode: 429, statusMessage: "Too many submissions, try again later." });
12
+ throw apiError(429, "rate_limited", "Too many submissions, try again later.");
12
13
  }
13
14
  await kv.set(key, used + 1, { ttl: rateLimitWindowSeconds });
14
15
  }
@@ -22,7 +23,7 @@ export async function assertPasswordAttemptsLeft(event, slug) {
22
23
  if (!rateLimit) return;
23
24
  if (Number(await kv.get(attemptKey(event, slug)) ?? 0) >= rateLimit) {
24
25
  setResponseHeader(event, "retry-after", rateLimitWindowSeconds);
25
- throw createError({ statusCode: 429, statusMessage: "Too many attempts, try again later." });
26
+ throw apiError(429, "rate_limited", "Too many attempts, try again later.");
26
27
  }
27
28
  }
28
29
  export async function recordPasswordFailure(event, slug) {
@@ -1,4 +1,5 @@
1
1
  import type { H3Event } from 'h3';
2
+ import { z } from 'zod';
2
3
  import { schema } from '@nuxthub/db';
3
4
  type Upload = {
4
5
  filename: string;
@@ -12,6 +13,8 @@ export type SubmissionBody = {
12
13
  locale: string;
13
14
  uploads: Map<string, Upload[]>;
14
15
  };
16
+ /** A BCP 47-ish locale tag (`en`, `da`, `en-GB`, `zh-Hant-TW`), as every submission path (HTTP, MCP) accepts it. */
17
+ export declare const submissionLocale: z.ZodString;
15
18
  /** Validates and processes one submission for a published form. */
16
19
  export declare function processSubmission(event: H3Event, form: FormRow, userId: number | null, body?: SubmissionBody): Promise<{
17
20
  ok: true;
@@ -1,34 +1,46 @@
1
+ import { z } from "zod";
2
+ import { apiError, validationError } from "@frigear-nu/module-kit/server";
1
3
  import { eq } from "drizzle-orm";
2
4
  import { db, schema } from "@nuxthub/db";
3
5
  import { blob } from "@nuxthub/blob";
4
6
  import { buildFormSchema, newId, resolveAccess, resolveCaptcha, validatedFields } from "../../utils/index.js";
5
7
  const safeFilename = (name) => name.replace(/[^\w.-]+/g, "_").replace(/^\.+/, "").slice(0, 120) || "file";
8
+ export const submissionLocale = z.string().trim().max(20).regex(/^[a-z]{2,3}(?:-[a-z0-9]{2,8})*$/i, "Invalid locale.");
9
+ const submissionBodySchema = z.object({
10
+ answers: z.record(z.string(), z.unknown()).default({}),
11
+ locale: submissionLocale.default("en")
12
+ }).prefault({});
6
13
  async function readSubmission(event) {
7
14
  const uploads = /* @__PURE__ */ new Map();
8
15
  const contentType = getRequestHeader(event, "content-type") ?? "";
9
16
  if (contentType.includes("multipart/form-data")) {
10
17
  const parts = await readMultipartFormData(event) ?? [];
11
18
  let answers = {};
12
- let locale = "en";
19
+ let locale;
13
20
  for (const part of parts) {
14
21
  if (part.name === "answers") {
15
22
  try {
16
23
  answers = JSON.parse(part.data.toString("utf8"));
17
24
  } catch {
18
- throw createError({ statusCode: 400, statusMessage: "Invalid answers." });
25
+ throw validationError([{ path: ["answers"], message: "Invalid answers." }]);
19
26
  }
20
27
  } else if (part.name === "locale") {
21
- locale = part.data.toString("utf8").slice(0, 20);
28
+ locale = part.data.toString("utf8");
22
29
  } else if (part.name?.startsWith("file:") && part.filename) {
23
30
  const key = part.name.slice(5);
24
31
  uploads.set(key, [...uploads.get(key) ?? [], { filename: part.filename, type: part.type ?? "application/octet-stream", data: part.data }]);
25
32
  }
26
33
  }
27
- return { answers, locale, uploads };
34
+ return { ...parseSubmissionBody({ answers, locale }), uploads };
28
35
  }
29
- const body = await readBody(event);
30
- return { answers: body?.answers ?? {}, locale: String(body?.locale ?? "en").slice(0, 20), uploads };
36
+ return { ...parseSubmissionBody(await readBody(event)), uploads };
31
37
  }
38
+ function parseSubmissionBody(input) {
39
+ const parsed = submissionBodySchema.safeParse(input);
40
+ if (!parsed.success) throw validationError(toIssues(parsed.error.issues));
41
+ return parsed.data;
42
+ }
43
+ const toIssues = (issues) => issues.map((issue) => ({ path: issue.path.map((segment) => typeof segment === "symbol" ? String(segment) : segment), message: issue.message }));
32
44
  export async function processSubmission(event, form, userId, body) {
33
45
  const definition = form.definition;
34
46
  const { answers: raw, locale, uploads } = body ?? await readSubmission(event);
@@ -41,11 +53,7 @@ export async function processSubmission(event, form, userId, body) {
41
53
  const skipCaptcha = resolveAccess(definition).auth === "token";
42
54
  const parsed = buildFormSchema(definition, answers, { locale, skipCaptcha }).safeParse(answers);
43
55
  if (!parsed.success) {
44
- throw createError({
45
- statusCode: 422,
46
- statusMessage: "Invalid submission.",
47
- data: { issues: parsed.error.issues.map((issue) => ({ path: issue.path, message: issue.message })) }
48
- });
56
+ throw validationError(toIssues(parsed.error.issues));
49
57
  }
50
58
  const validated = parsed.data;
51
59
  for (const field of validatedFields(definition, raw).filter((f) => f.type === "captcha")) {
@@ -54,11 +62,7 @@ export async function processSubmission(event, form, userId, body) {
54
62
  Reflect.deleteProperty(answers, field.key);
55
63
  if (skipCaptcha) continue;
56
64
  if (!await verifyCaptcha(event, resolveCaptcha(field, useRuntimeConfig(event).public.forms?.captcha?.defaultProvider).provider, String(token), form.id)) {
57
- throw createError({
58
- statusCode: 422,
59
- statusMessage: "Invalid submission.",
60
- data: { issues: [{ path: [field.key], message: "Captcha verification failed. Please try again." }] }
61
- });
65
+ throw validationError([{ path: [field.key], message: "Captcha verification failed. Please try again." }]);
62
66
  }
63
67
  }
64
68
  const stored = definition.storage !== "none";
@@ -124,7 +128,7 @@ export async function processSubmission(event, form, userId, body) {
124
128
  deliveryError: error ?? null
125
129
  }).where(eq(schema.formSubmissions.id, submissionId));
126
130
  } else if (error) {
127
- throw createError({ statusCode: 502, statusMessage: "The submission could not be delivered." });
131
+ throw apiError(502, "delivery_failed", "The submission could not be delivered.");
128
132
  }
129
133
  if (ctx.delivered || error) {
130
134
  const result = { submissionId, formSlug: form.slug, status: error ? "failed" : "delivered", error };
@@ -0,0 +1,36 @@
1
+ export declare const formsApi: {
2
+ /** `GET` a published form's definition. */
3
+ readonly form: (slug: string) => string;
4
+ /** `GET` what the public page needs to pick between the form, a gate and "not found". */
5
+ readonly access: (slug: string) => string;
6
+ /** `POST` a submission (JSON or multipart). */
7
+ readonly submit: (slug: string) => string;
8
+ readonly admin: {
9
+ /** `GET` (paginated) / `POST` forms. */
10
+ readonly forms: "/api/admin/forms";
11
+ /** `POST` an import of form definitions. */
12
+ readonly import: "/api/admin/forms/import";
13
+ /** `GET` / `PATCH` / `DELETE` (archive) one form. */
14
+ readonly form: (id: string) => string;
15
+ /** `POST` a copy of a form as a new draft. */
16
+ readonly clone: (id: string) => string;
17
+ /** `GET` a form's submissions (paginated). */
18
+ readonly submissions: (formId: string) => string;
19
+ /** `GET` / `POST` a form's access tokens. */
20
+ readonly tokens: (formId: string) => string;
21
+ /** `DELETE` (revoke) one access token. */
22
+ readonly token: (formId: string, tokenId: string) => string;
23
+ /** `GET` / `PUT` / `DELETE` a form's shared password. */
24
+ readonly password: (formId: string) => string;
25
+ /** `GET` which of a form's secrets are set. */
26
+ readonly secrets: (formId: string) => string;
27
+ /** `PUT` / `DELETE` one secret of a form. */
28
+ readonly secret: (formId: string, key: string) => string;
29
+ /** `GET` one submission. */
30
+ readonly submission: (id: string) => string;
31
+ /** `GET` (download) one uploaded file of a submission. */
32
+ readonly submissionFile: (id: string, fileId: string) => string;
33
+ /** `POST` to run delivery of a stored submission again. */
34
+ readonly redeliver: (id: string) => string;
35
+ };
36
+ };
@@ -0,0 +1,39 @@
1
+ const enc = encodeURIComponent;
2
+ const PUBLIC_BASE = "/api/forms";
3
+ const ADMIN_BASE = "/api/admin/forms";
4
+ export const formsApi = {
5
+ /** `GET` a published form's definition. */
6
+ form: (slug) => `${PUBLIC_BASE}/${enc(slug)}`,
7
+ /** `GET` what the public page needs to pick between the form, a gate and "not found". */
8
+ access: (slug) => `${PUBLIC_BASE}/${enc(slug)}/access`,
9
+ /** `POST` a submission (JSON or multipart). */
10
+ submit: (slug) => `${PUBLIC_BASE}/${enc(slug)}/submit`,
11
+ admin: {
12
+ /** `GET` (paginated) / `POST` forms. */
13
+ forms: ADMIN_BASE,
14
+ /** `POST` an import of form definitions. */
15
+ import: `${ADMIN_BASE}/import`,
16
+ /** `GET` / `PATCH` / `DELETE` (archive) one form. */
17
+ form: (id) => `${ADMIN_BASE}/${enc(id)}`,
18
+ /** `POST` a copy of a form as a new draft. */
19
+ clone: (id) => `${ADMIN_BASE}/${enc(id)}/clone`,
20
+ /** `GET` a form's submissions (paginated). */
21
+ submissions: (formId) => `${ADMIN_BASE}/${enc(formId)}/submissions`,
22
+ /** `GET` / `POST` a form's access tokens. */
23
+ tokens: (formId) => `${ADMIN_BASE}/${enc(formId)}/tokens`,
24
+ /** `DELETE` (revoke) one access token. */
25
+ token: (formId, tokenId) => `${ADMIN_BASE}/${enc(formId)}/tokens/${enc(tokenId)}`,
26
+ /** `GET` / `PUT` / `DELETE` a form's shared password. */
27
+ password: (formId) => `${ADMIN_BASE}/${enc(formId)}/password`,
28
+ /** `GET` which of a form's secrets are set. */
29
+ secrets: (formId) => `${ADMIN_BASE}/${enc(formId)}/secrets`,
30
+ /** `PUT` / `DELETE` one secret of a form. */
31
+ secret: (formId, key) => `${ADMIN_BASE}/${enc(formId)}/secrets/${enc(key)}`,
32
+ /** `GET` one submission. */
33
+ submission: (id) => `${ADMIN_BASE}/submissions/${enc(id)}`,
34
+ /** `GET` (download) one uploaded file of a submission. */
35
+ submissionFile: (id, fileId) => `${ADMIN_BASE}/submissions/${enc(id)}/files/${enc(fileId)}`,
36
+ /** `POST` to run delivery of a stored submission again. */
37
+ redeliver: (id) => `${ADMIN_BASE}/submissions/${enc(id)}/redeliver`
38
+ }
39
+ };
@@ -24,3 +24,4 @@ export type { PhoneCountry } from './phone.js';
24
24
  export { embedMode } from './embed.js';
25
25
  export type { EmbedMode } from './embed.js';
26
26
  export { isFormOpen, nextFormOpening } from './schedule.js';
27
+ export { formsApi } from './api.js';
@@ -69,3 +69,4 @@ export {
69
69
  } from "./phone.js";
70
70
  export { embedMode } from "./embed.js";
71
71
  export { isFormOpen, nextFormOpening } from "./schedule.js";
72
+ export { formsApi } from "./api.js";
package/dist/types.d.mts CHANGED
@@ -4,4 +4,4 @@ export { type Translated } from '@frigear-nu/ui/translated'
4
4
 
5
5
  export { default } from './module.mjs'
6
6
 
7
- export { type AuthorizeContext, type CaptchaConfig, type CaptchaProvider, type CaptchaVerifyContext, type Condition, type ConditionOp, type CreatedFormAccessTokenDto, type DeliveredContext, type DeliveryStatus, type FieldOption, type FieldType, type FieldValidation, type FileDescriptor, type FormAccess, type FormAccessInfoDto, type FormAccessTokenDto, type FormAdminDto, type FormAuthMode, type FormAvailability, type FormDefinition, type FormDelivery, type FormField, type FormMode, type FormPasswordStatusDto, type FormSecretsDto, type FormStatus, type FormStep, type FormStorage, type FormSubmitSettings, type FormSummaryDto, type FormsRuntimeConfig, type ModuleOptions, type PhoneConfig, type PublicFormDto, type PublicModuleOptions, type SecretSource, type SeedForm, type SubmissionDto, type SubmissionFileDto, type SubmissionSummaryDto, type SubmitResponse, type SubmittedContext, type SubmittedFile } from './module.mjs'
7
+ export { type AuthorizeContext, type CaptchaConfig, type CaptchaProvider, type CaptchaVerifyContext, type Condition, type ConditionOp, type CreatedFormAccessTokenDto, type DeliveredContext, type DeliveryStatus, type FieldOption, type FieldType, type FieldValidation, type FileDescriptor, type FormAccess, type FormAccessInfoDto, type FormAccessTokenDto, type FormAdminDto, type FormAuthMode, type FormAvailability, type FormDefinition, type FormDelivery, type FormField, type FormImportResultDto, type FormMode, type FormPasswordStatusDto, type FormSecretsDto, type FormStatus, type FormStep, type FormStorage, type FormSubmitSettings, type FormSummaryDto, type FormsPage, type FormsRuntimeConfig, type ModuleOptions, type PhoneConfig, type PublicFormDto, type PublicModuleOptions, type SecretSource, type SeedForm, type SubmissionDeliveryDto, type SubmissionDto, type SubmissionFileDto, type SubmissionSummaryDto, type SubmitResponse, type SubmittedContext, type SubmittedFile } from './module.mjs'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frigear-nu/forms",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Dynamic, database-stored forms (single page or stepped) with submissions, file uploads and email delivery for the frigear ecosystem.",
5
5
  "homepage": "https://docs.frigear.dev/modules/forms",
6
6
  "repository": {
@@ -21,6 +21,11 @@
21
21
  "types": "./dist/runtime/utils/index.d.ts",
22
22
  "import": "./dist/runtime/utils/index.js",
23
23
  "default": "./dist/runtime/utils/index.js"
24
+ },
25
+ "./api": {
26
+ "types": "./dist/runtime/utils/api.d.ts",
27
+ "import": "./dist/runtime/utils/api.js",
28
+ "default": "./dist/runtime/utils/api.js"
24
29
  }
25
30
  },
26
31
  "files": [
@@ -30,13 +35,13 @@
30
35
  "playground"
31
36
  ],
32
37
  "dependencies": {
33
- "@frigear-nu/admin": "0.1.2",
34
- "@frigear-nu/module-kit": "1.1.2",
35
- "@frigear-nu/notify": "0.1.2",
36
- "@frigear-nu/ui": "0.1.2",
37
- "@frigear-nu/auth": "0.1.3",
38
- "@frigear-nu/mcp": "0.1.2",
39
- "@frigear-nu/users": "1.1.3",
38
+ "@frigear-nu/admin": "0.1.3",
39
+ "@frigear-nu/module-kit": "1.1.3",
40
+ "@frigear-nu/notify": "0.1.3",
41
+ "@frigear-nu/ui": "0.1.3",
42
+ "@frigear-nu/auth": "0.1.4",
43
+ "@frigear-nu/mcp": "0.1.3",
44
+ "@frigear-nu/users": "1.1.4",
40
45
  "@libsql/client": "^0.18.0",
41
46
  "@nuxt/kit": "^4.6.1",
42
47
  "drizzle-kit": "^0.31.11",
@@ -46,7 +51,7 @@
46
51
  "zod": "^4.6.5"
47
52
  },
48
53
  "peerDependencies": {
49
- "@frigear-nu/menu": "0.1.2",
54
+ "@frigear-nu/menu": "0.1.3",
50
55
  "@nuxt/ui": "^4.12.0",
51
56
  "@nuxthub/core": "^0.10.8"
52
57
  },
@@ -59,7 +64,7 @@
59
64
  }
60
65
  },
61
66
  "devDependencies": {
62
- "@frigear-nu/menu": "0.1.2",
67
+ "@frigear-nu/menu": "0.1.3",
63
68
  "@nuxt/devtools": "^3.4.2",
64
69
  "@nuxt/eslint-config": "^1.17.0",
65
70
  "@nuxt/module-builder": "^1.0.3",
@@ -1,12 +0,0 @@
1
- import { z } from "zod";
2
- const input = z.object({
3
- key: z.string().refine(isFormSecretKey, "Unknown secret key."),
4
- value: z.string().trim().min(1).max(2e3)
5
- });
6
- export default defineEventHandler(async (event) => {
7
- await requireFormsManager(event);
8
- const form = await findFormById(getRouterParam(event, "id"));
9
- const { key, value } = await readValidatedBody(event, input.parse);
10
- await setFormSecret(form.id, key, value);
11
- return describeFormSecrets(event, form.id);
12
- });