@stacksjs/forms 0.74.48 → 0.74.50

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/create.js CHANGED
@@ -1 +1 @@
1
- import{db}from"@stacksjs/database";import{loadFormByUuid}from"./definition";export async function createForm(siteId,input){const existing=await db.selectFrom("forms").select(["id","uuid"]).where("handle","=",input.handle).where("site_id",siteId===null?"is":"=",siteId).executeTakeFirst(),settings=JSON.stringify(input.settings??{});let formId,uuid,created=!1;if(existing){formId=Number(existing.id);uuid=String(existing.uuid);await db.updateTable("forms").set({name:input.name,status:input.status??"active",settings}).where("id","=",formId).execute()}else{uuid=crypto.randomUUID();await db.insertInto("forms").values({uuid,site_id:siteId,name:input.name,handle:input.handle,status:input.status??"active",settings}).execute();const row=await db.selectFrom("forms").select(["id"]).where("uuid","=",uuid).executeTakeFirst();formId=Number(row?.id);created=!0}await db.deleteFrom("form_fields").where("form_id","=",formId).execute();let position=0;for(const field of input.fields)await db.insertInto("form_fields").values({form_id:formId,name:field.name,label:field.label,type:field.type,required:field.required??!1,position:position++,width:field.width??"full",options:JSON.stringify(field.options??{}),conditions:field.conditions?JSON.stringify(field.conditions):null}).execute();return{id:formId,uuid,handle:input.handle,created}}export async function loadFormByHandle(siteId,handle){const row=await db.selectFrom("forms").select(["uuid"]).where("handle","=",handle).where("site_id",siteId===null?"is":"=",siteId).executeTakeFirst();if(!row)return null;return await loadFormByUuid(String(row.uuid),siteId)}export function toCreateFields(fields){return fields.map((field)=>({name:field.name,label:field.label,type:field.type,required:field.required,width:field.width,options:field.options,conditions:field.conditions}))}
1
+ import{db}from"@stacksjs/database/runtime";import{loadFormByUuid}from"./definition";export async function createForm(siteId,input){const existing=await db.selectFrom("forms").select(["id","uuid"]).where("handle","=",input.handle).where("site_id",siteId===null?"is":"=",siteId).executeTakeFirst(),settings=JSON.stringify(input.settings??{});let formId,uuid,created=!1;if(existing){formId=Number(existing.id);uuid=String(existing.uuid);await db.updateTable("forms").set({name:input.name,status:input.status??"active",settings}).where("id","=",formId).execute()}else{uuid=crypto.randomUUID();await db.insertInto("forms").values({uuid,site_id:siteId,name:input.name,handle:input.handle,status:input.status??"active",settings}).execute();const row=await db.selectFrom("forms").select(["id"]).where("uuid","=",uuid).executeTakeFirst();formId=Number(row?.id);created=!0}await db.deleteFrom("form_fields").where("form_id","=",formId).execute();let position=0;for(const field of input.fields)await db.insertInto("form_fields").values({form_id:formId,name:field.name,label:field.label,type:field.type,required:field.required??!1,position:position++,width:field.width??"full",options:JSON.stringify(field.options??{}),conditions:field.conditions?JSON.stringify(field.conditions):null}).execute();return{id:formId,uuid,handle:input.handle,created}}export async function loadFormByHandle(siteId,handle){const row=await db.selectFrom("forms").select(["uuid"]).where("handle","=",handle).where("site_id",siteId===null?"is":"=",siteId).executeTakeFirst();if(!row)return null;return await loadFormByUuid(String(row.uuid),siteId)}export function toCreateFields(fields){return fields.map((field)=>({name:field.name,label:field.label,type:field.type,required:field.required,width:field.width,options:field.options,conditions:field.conditions}))}
@@ -1 +1 @@
1
- import{db}from"@stacksjs/database";function parseJson(raw,fallback){if(raw&&typeof raw==="object")return raw;if(typeof raw==="string"&&raw)try{return JSON.parse(raw)}catch{return fallback}return fallback}function rowToField(row){return{name:row.name,label:row.label,type:row.type,required:row.required===!0||row.required===1,position:Number(row.position??0),width:row.width==="half"?"half":"full",options:parseJson(row.options,{}),conditions:parseJson(row.conditions,null)}}export async function loadFormByUuid(uuid,siteId){const form=await db.selectFrom("forms").where("uuid","=",uuid).select(["id","uuid","site_id","name","handle","status","settings"]).executeTakeFirst();if(!form)return null;if(siteId!==null&&form.site_id!==null&&Number(form.site_id)!==siteId)return null;const fields=await db.selectFrom("form_fields").where("form_id","=",form.id).select(["name","label","type","required","position","width","options","conditions"]).orderBy("position","asc").execute();return{id:Number(form.id),uuid:form.uuid,siteId:form.site_id==null?null:Number(form.site_id),name:form.name,handle:form.handle,status:["draft","active","closed"].includes(form.status)?form.status:"draft",settings:parseJson(form.settings,{}),fields:fields.map(rowToField)}}export function publicDefinition(form){return{uuid:form.uuid,name:form.name,status:form.status,submitLabel:form.settings.submitLabel??"Submit",confirmation:form.settings.confirmation?.type==="message"?form.settings.confirmation.value:null,payment:form.settings.payment?{mode:form.settings.payment.mode,amountCents:form.settings.payment.mode==="fixed"?form.settings.payment.amountCents??0:void 0,currency:form.settings.payment.currency??"USD",minAmountCents:form.settings.payment.minAmountCents}:null,fields:form.fields.sort((a,b)=>a.position-b.position).map((field)=>({name:field.name,label:field.label,type:field.type,required:field.required,width:field.width,options:{placeholder:field.options.placeholder,choices:field.options.choices,min:field.options.min,max:field.options.max,accept:field.options.accept},conditions:field.conditions}))}}
1
+ import{db}from"@stacksjs/database/runtime";function parseJson(raw,fallback){if(raw&&typeof raw==="object")return raw;if(typeof raw==="string"&&raw)try{return JSON.parse(raw)}catch{return fallback}return fallback}function rowToField(row){return{name:row.name,label:row.label,type:row.type,required:row.required===!0||row.required===1,position:Number(row.position??0),width:row.width==="half"?"half":"full",options:parseJson(row.options,{}),conditions:parseJson(row.conditions,null)}}export async function loadFormByUuid(uuid,siteId){const form=await db.selectFrom("forms").where("uuid","=",uuid).select(["id","uuid","site_id","name","handle","status","settings"]).executeTakeFirst();if(!form)return null;if(siteId!==null&&form.site_id!==null&&Number(form.site_id)!==siteId)return null;const fields=await db.selectFrom("form_fields").where("form_id","=",form.id).select(["name","label","type","required","position","width","options","conditions"]).orderBy("position","asc").execute();return{id:Number(form.id),uuid:form.uuid,siteId:form.site_id==null?null:Number(form.site_id),name:form.name,handle:form.handle,status:["draft","active","closed"].includes(form.status)?form.status:"draft",settings:parseJson(form.settings,{}),fields:fields.map(rowToField)}}export function publicDefinition(form){return{uuid:form.uuid,name:form.name,status:form.status,submitLabel:form.settings.submitLabel??"Submit",confirmation:form.settings.confirmation?.type==="message"?form.settings.confirmation.value:null,payment:form.settings.payment?{mode:form.settings.payment.mode,amountCents:form.settings.payment.mode==="fixed"?form.settings.payment.amountCents??0:void 0,currency:form.settings.payment.currency??"USD",minAmountCents:form.settings.payment.minAmountCents}:null,fields:form.fields.sort((a,b)=>a.position-b.position).map((field)=>({name:field.name,label:field.label,type:field.type,required:field.required,width:field.width,options:{placeholder:field.options.placeholder,choices:field.options.choices,min:field.options.min,max:field.options.max,accept:field.options.accept},conditions:field.conditions}))}}
@@ -1,2 +1,2 @@
1
- import{randomUUID}from"node:crypto";import{config}from"@stacksjs/config";import{db,sqlDateTime}from"@stacksjs/database";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}))}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.48",
4
+ "version": "0.74.50",
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.48",
60
- "@stacksjs/database": "0.74.48",
61
- "@stacksjs/logging": "0.74.48",
62
- "@stacksjs/notifications": "0.74.48"
59
+ "@stacksjs/config": "0.74.50",
60
+ "@stacksjs/database": "0.74.50",
61
+ "@stacksjs/logging": "0.74.50",
62
+ "@stacksjs/notifications": "0.74.50"
63
63
  },
64
64
  "devDependencies": {
65
- "@stacksjs/storage": "0.74.48",
66
- "@stacksjs/validation": "0.74.48",
65
+ "@stacksjs/storage": "0.74.50",
66
+ "@stacksjs/validation": "0.74.50",
67
67
  "better-dx": "^0.2.24"
68
68
  },
69
69
  "sideEffects": false