Survey Resource
Survey is a resource type: the SurveyJS model lives in the content blob, the respondent page is Survey's published view, and only the genuinely survey-specific part — responses — has bespoke procedures; asset uploads come from the shared FileAssets capability.
Data model
- Content blob:
surveyResourceSchema = z.object({ model: z.string(), settings: surveySettingsSchema })— an object wrapper so future fields don't break the blob shape. The SurveyJS theme stays inside the model JSON underTHEME_KEY— no reason to split it while SurveyJS owns both. Thesettingssection is live collection state read outside the publish snapshot: the accepting-responses toggle and closed message (response controls) and the response mode (response modes) share that one object. contentVersionis server-incremented by the resource factory, never by the client.SurveyResponseEntity(Azure Table, partitionKey = survey resource id) holds respondent answers plus the opaqueparticipantTokenthat joins a response back to a program participant.- A survey belongs to no group; grouping resources at all is the deferred resource groups idea.
Capabilities
- FileAssets — asset uploads under
{id}/files/…through the shared capability procedures (resource file assets). - Publishable — publish goes through the generic
publishResourceplus the factory hooktransformPublishedContent, which clones referenced asset blobs into the attempt's own publish directory and rewrites URLs (resource file assets); and the livesettingsare merged back over every snapshot read throughResourceLiveContentMap(resource snapshots), so closing or gating never needs a re-publish. Publishing snapshots the model; the public respondent page serves that snapshot and 404s for unpublished surveys. Public reads are counted (view analytics). - DatasetProvider —
readSurveyResponsesDatasetserves responses throughdataset.readDataset, auth keyed to resource ownership.
Procedures
The survey router is createResourceProcedures(ResourceType.Survey, …) plus the type-specific procedures that are deliberately not capabilities (single consumer — see the admission rule in resources):
| Procedure | Auth | Purpose |
|---|---|---|
createSurveyResponse / updateSurveyResponse / readSurveyResponse | public, rate-limited | respondent answers → Azure Table |
readSurveyResponsesCount / deleteSurveyResponse / readSurveyResponseRecords | owner | response management tooling |
Asset uploads are not listed here: they come from the shared FileAssets capability rather than a survey-owned set (resource file assets).
Blades / routes
- Editor blade — SurveyJS creator rendered inline (
useSurveyCreator); autosave goes through the store'ssaveModel, which merges the model with the currently loadedsettingsand saves the whole{ model, settings }blob — the shape the content schema requires. SurveyJS keeps owning editor/preview state; the resource layer only sees model JSON in and out. - Overview blade — wraps the generic Overview to add the response count beside the Views row, plus the Collection card owning the
settingssection. - Responses blade — a table over
survey.readSurveyResponseRecords, the first-look surface for results, with per-row detail and delete (response management). - Respondent page —
/view/Survey/[id]viaViewComponentMap: an interactive published-view renderer (plainsurvey-coreModel + theme) that writes responses. In-progress resume stores only a per-survey response id in localStorage — answers live solely in Azure Table — and the id is removed on submit, so a later visitor on a shared device cannot reopen a submitted response. Email invite blocks link it viaRoutePath.View(ResourceType.Survey, id).
Key files
| File | Role |
|---|---|
app/components/Resource/Survey/Editor.vue | inline SurveyJS creator Editor blade |
app/components/Resource/Survey/Responses.vue | Responses blade (dataset table) |
app/components/Resource/Survey/View.vue | public respondent renderer (ViewComponentMap) |
app/composables/survey/useSurveyCreator.ts | creator setup + autosave wiring |
server/trpc/routers/survey.ts | resource factory + response procedures |
Notes
- Respondent procedures are public and rate-limited — they are the only unauthenticated write path in the platform. Every response write passes
resolveSurveyResponseWrite, the single boundary enforcing the closed toggle and the response mode, so client state can never bypass either. - Existing
surveysrows were discarded, not migrated, when the table was dropped (migration20260710120004_talented_slayback); orphaned responses can be truncated.
Previous
Storage quotas
Per-user blob-storage quotas (Free = 10 GiB) held atomically at SAS issuance, charged by Storage's own BlobCreated event for client uploads and at write time for the server's own, with a usage meter in the resource explorer's header.
Next
Published View Analytics
Best-effort view counts on public reads, surfaced on the Overview blade — making the invite → view → respond funnel visible.
