@stacksjs/forms 0.74.62 → 0.74.64

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/index.d.ts CHANGED
@@ -22,6 +22,7 @@ export {
22
22
  completeSubmissionPayment,
23
23
  exportSubmissionsCsv,
24
24
  fetchSubmissions,
25
+ submissionIdentity,
25
26
  submitForm,
26
27
  } from './submissions';
27
28
  export {
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- export{evaluateConditions,visibleFields}from"./conditions";export{createForm,loadFormByHandle,toCreateFields}from"./create";export{loadFormByUuid,publicDefinition}from"./definition";export{dispatchSubmissionNotifications}from"./notifications";export{completeSubmissionPayment,exportSubmissionsCsv,fetchSubmissions,submitForm}from"./submissions";export{checkUpload,extensionOf,fileFieldNamed,formUploadPrefix,isOwnedUploadPath,resolveUploadLimits}from"./uploads";export{computeAmountCents,validateSubmission}from"./validate";
1
+ export{evaluateConditions,visibleFields}from"./conditions";export{createForm,loadFormByHandle,toCreateFields}from"./create";export{loadFormByUuid,publicDefinition}from"./definition";export{dispatchSubmissionNotifications}from"./notifications";export{completeSubmissionPayment,exportSubmissionsCsv,fetchSubmissions,submissionIdentity,submitForm}from"./submissions";export{checkUpload,extensionOf,fileFieldNamed,formUploadPrefix,isOwnedUploadPath,resolveUploadLimits}from"./uploads";export{computeAmountCents,validateSubmission}from"./validate";
@@ -11,6 +11,15 @@ export declare function submitForm(form: FormDefinition, payload: Record<string,
11
11
  export declare function completeSubmissionPayment(submissionUuid: string, paymentIntentId: string): Promise<boolean>;
12
12
  /** Admin list, newest first. `formId` scoping is the caller's job to have authorized. */
13
13
  export declare function fetchSubmissions(formId: number, options?: { limit?: number, offset?: number }): Promise<SubmissionListRow[]>;
14
+ /**
15
+ * Who sent a submission and what they said, for the notification mail.
16
+ *
17
+ * Reads the `data` column. The submit route used to do this itself and
18
+ * selected `values` - a SQL keyword the column was deliberately never named -
19
+ * so every submission on a form with notifications threw after its row was
20
+ * written.
21
+ */
22
+ export declare function submissionIdentity(submissionId: number): Promise<{ email: string | null, name: string | null, values: Record<string, unknown> }>;
14
23
  /** The full submission set as CSV: field columns in position order + the typed columns. */
15
24
  export declare function exportSubmissionsCsv(form: FormDefinition): Promise<string>;
16
25
  export declare interface SubmitOptions {
@@ -1,2 +1,2 @@
1
- import{randomUUID}from"node:crypto";import{config}from"@stacksjs/config";import{db,sqlDateTime}from"@stacksjs/database/runtime";import{validateSubmission}from"./validate";export async function submitForm(form,payload,options={}){if(form.status!=="active")return{ok:!1,status:409,message:"This form is not accepting responses."};const spam=config.forms?.spam??{};if((spam.honeypot??!0)&&typeof options.honeypot==="string"&&options.honeypot.trim()!=="")return{ok:!0,submissionId:0,submissionUuid:"",status:"complete",amountCents:null,confirmation:form.settings.confirmation?.type==="message"?form.settings.confirmation.value:null,redirect:null};const minSeconds=spam.minSubmitSeconds??3;if(minSeconds>0&&options.renderedAtMs!==void 0&&options.renderedAtMs<minSeconds*1000)return{ok:!1,status:429,message:"That was submitted too quickly. Please try again."};const validated=validateSubmission(form,payload);if(!validated.ok)return{ok:!1,status:422,errors:validated.errors};const status=form.settings.payment&&(validated.amountCents??0)>0?"pending_payment":"complete",uuid=randomUUID(),now=sqlDateTime(new Date);await db.insertInto("form_submissions").values({uuid,form_id:form.id,site_id:form.siteId,data:JSON.stringify(validated.values),email:validated.email,name:validated.name,status,amount_cents:validated.amountCents,ip:options.ip??null,submitted_at:now,created_at:now,updated_at:now}).execute();const row=await db.selectFrom("form_submissions").where("uuid","=",uuid).select(["id"]).executeTakeFirst(),confirmation=form.settings.confirmation;return{ok:!0,submissionId:Number(row?.id??0),submissionUuid:uuid,status,amountCents:validated.amountCents,confirmation:confirmation?.type==="message"?confirmation.value:null,redirect:confirmation?.type==="redirect"?confirmation.value:null}}export async function completeSubmissionPayment(submissionUuid,paymentIntentId){const now=sqlDateTime(new Date),updated=await db.updateTable("form_submissions").set({status:"complete",payment_intent_id:paymentIntentId,updated_at:now}).where("uuid","=",submissionUuid).where("status","=","pending_payment").execute();return(typeof updated==="number"?updated:Number(updated?.changes??0))>0}export async function fetchSubmissions(formId,options={}){return(await db.selectFrom("form_submissions").where("form_id","=",formId).where("status","!=","spam").select(["id","uuid","data","email","name","status","amount_cents","submitted_at"]).orderBy("id","desc").limit(options.limit??100).offset(options.offset??0).execute()).map((row)=>({id:Number(row.id),uuid:row.uuid,values:(()=>{try{return row.data?JSON.parse(row.data):{}}catch{return{}}})(),email:row.email,name:row.name,status:row.status,amountCents:row.amount_cents==null?null:Number(row.amount_cents),submittedAt:row.submitted_at}))}function csvEscape(value){const text=value===void 0||value===null?"":String(value),defused=/^[=+\-@]/.test(text)?`'${text}`:text;return/[",\n\r]/.test(defused)?`"${defused.replace(/"/g,'""')}"`:defused}export async function exportSubmissionsCsv(form){const fields=form.fields.filter((field)=>field.type!=="section_break").sort((a,b)=>a.position-b.position),rows=await fetchSubmissions(form.id,{limit:1e5}),lines=[[...fields.map((field)=>field.label),"Email","Name","Status","Amount","Submitted at"].map(csvEscape).join(",")];for(const row of rows)lines.push([...fields.map((field)=>csvEscape(row.values[field.name])),csvEscape(row.email),csvEscape(row.name),csvEscape(row.status),csvEscape(row.amountCents==null?"":(row.amountCents/100).toFixed(2)),csvEscape(row.submittedAt)].join(","));return lines.join(`
1
+ import{randomUUID}from"node:crypto";import{config}from"@stacksjs/config";import{db,sqlDateTime}from"@stacksjs/database/runtime";import{validateSubmission}from"./validate";export async function submitForm(form,payload,options={}){if(form.status!=="active")return{ok:!1,status:409,message:"This form is not accepting responses."};const spam=config.forms?.spam??{};if((spam.honeypot??!0)&&typeof options.honeypot==="string"&&options.honeypot.trim()!=="")return{ok:!0,submissionId:0,submissionUuid:"",status:"complete",amountCents:null,confirmation:form.settings.confirmation?.type==="message"?form.settings.confirmation.value:null,redirect:null};const minSeconds=spam.minSubmitSeconds??3;if(minSeconds>0&&options.renderedAtMs!==void 0&&options.renderedAtMs<minSeconds*1000)return{ok:!1,status:429,message:"That was submitted too quickly. Please try again."};const validated=validateSubmission(form,payload);if(!validated.ok)return{ok:!1,status:422,errors:validated.errors};const status=form.settings.payment&&(validated.amountCents??0)>0?"pending_payment":"complete",uuid=randomUUID(),now=sqlDateTime(new Date);await db.insertInto("form_submissions").values({uuid,form_id:form.id,site_id:form.siteId,data:JSON.stringify(validated.values),email:validated.email,name:validated.name,status,amount_cents:validated.amountCents,ip:options.ip??null,submitted_at:now,created_at:now,updated_at:now}).execute();const row=await db.selectFrom("form_submissions").where("uuid","=",uuid).select(["id"]).executeTakeFirst(),confirmation=form.settings.confirmation;return{ok:!0,submissionId:Number(row?.id??0),submissionUuid:uuid,status,amountCents:validated.amountCents,confirmation:confirmation?.type==="message"?confirmation.value:null,redirect:confirmation?.type==="redirect"?confirmation.value:null}}export async function completeSubmissionPayment(submissionUuid,paymentIntentId){const now=sqlDateTime(new Date),updated=await db.updateTable("form_submissions").set({status:"complete",payment_intent_id:paymentIntentId,updated_at:now}).where("uuid","=",submissionUuid).where("status","=","pending_payment").execute();return(typeof updated==="number"?updated:Number(updated?.changes??0))>0}export async function fetchSubmissions(formId,options={}){return(await db.selectFrom("form_submissions").where("form_id","=",formId).where("status","!=","spam").select(["id","uuid","data","email","name","status","amount_cents","submitted_at"]).orderBy("id","desc").limit(options.limit??100).offset(options.offset??0).execute()).map((row)=>({id:Number(row.id),uuid:row.uuid,values:(()=>{try{return row.data?JSON.parse(row.data):{}}catch{return{}}})(),email:row.email,name:row.name,status:row.status,amountCents:row.amount_cents==null?null:Number(row.amount_cents),submittedAt:row.submitted_at}))}export async function submissionIdentity(submissionId){const row=await db.selectFrom("form_submissions").where("id","=",submissionId).select(["email","name","data"]).executeTakeFirst();let values={};try{values=row?.data?JSON.parse(row.data):{}}catch{}return{email:row?.email??null,name:row?.name??null,values}}function csvEscape(value){const text=value===void 0||value===null?"":String(value),defused=/^[=+\-@]/.test(text)?`'${text}`:text;return/[",\n\r]/.test(defused)?`"${defused.replace(/"/g,'""')}"`:defused}export async function exportSubmissionsCsv(form){const fields=form.fields.filter((field)=>field.type!=="section_break").sort((a,b)=>a.position-b.position),rows=await fetchSubmissions(form.id,{limit:1e5}),lines=[[...fields.map((field)=>field.label),"Email","Name","Status","Amount","Submitted at"].map(csvEscape).join(",")];for(const row of rows)lines.push([...fields.map((field)=>csvEscape(row.values[field.name])),csvEscape(row.email),csvEscape(row.name),csvEscape(row.status),csvEscape(row.amountCents==null?"":(row.amountCents/100).toFixed(2)),csvEscape(row.submittedAt)].join(","));return lines.join(`
2
2
  `)}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stacksjs/forms",
3
3
  "type": "module",
4
- "version": "0.74.62",
4
+ "version": "0.74.64",
5
5
  "description": "The Stacks form-builder functionality.",
6
6
  "author": "Chris Breuer",
7
7
  "contributors": [
@@ -56,14 +56,14 @@
56
56
  "prepublishOnly": "bun run build"
57
57
  },
58
58
  "dependencies": {
59
- "@stacksjs/config": "0.74.62",
60
- "@stacksjs/database": "0.74.62",
61
- "@stacksjs/logging": "0.74.62",
62
- "@stacksjs/notifications": "0.74.62"
59
+ "@stacksjs/config": "0.74.64",
60
+ "@stacksjs/database": "0.74.64",
61
+ "@stacksjs/logging": "0.74.64",
62
+ "@stacksjs/notifications": "0.74.64"
63
63
  },
64
64
  "devDependencies": {
65
- "@stacksjs/storage": "0.74.62",
66
- "@stacksjs/validation": "0.74.62",
65
+ "@stacksjs/storage": "0.74.64",
66
+ "@stacksjs/validation": "0.74.64",
67
67
  "better-dx": "^0.2.24"
68
68
  },
69
69
  "sideEffects": false