@200stack/static-forms-core
v0.2.4
Published
Shared Static Forms build contract schemas and canonical digest utilities.
Readme
@200stack/static-forms-core
Shared Static Forms build contract primitives for later Dashboard, framework adapter, and build-runtime slices.
This package currently includes:
- strict Zod schemas and inferred TypeScript types for adapter probe results, materialization refs, framework adapter metadata, slot render targets, and build manifests
- strict Zod schemas and inferred TypeScript types for
StaticFormDefinitionV1,StaticFormConditionAstV1, client-safeStaticFormRenderTreeV1,StaticFormBuildSnapshotV1, andStaticFormAdapterProofV1 - shared validation for text, email, textarea, select, radio, single-consent checkbox, multiple-choice checkboxes, tel, url, date, and number fields
- deterministic Definition to RenderTree compilation
- a deterministic reference HTML renderer for semantic fragments
- a minimal client state machine and submission validator
- Draft 2020-12 structural JSON Schema exports generated from the implemented Zod schemas with Zod 4 native
z.toJSONSchema() - a versioned, limited canonical JSON serializer for JSON-compatible values
- a Node build-time SHA-256 digest helper for canonical JSON bytes, exported only from
@200stack/static-forms-core/node
checkboxes(複数選択)は選んだ値を配列で送ります。0.2.3 以上の adapter と build protocol v2 が要ります。
tel・url・date・number は 0.2.4 以上の adapter と build protocol v2 が要ります。
This package intentionally does not change Dashboard, build-runtime, forms-worker, DB, API, UI, Worker, or Cloudflare behavior.
The Definition contract is intentionally limited to the Phase 1a public/client-safe slice. Mail templates, security policy, and integrations are exported only as id/version/digest references; provider configuration, recipients, secrets, template bodies, attachment processing, D1/R2/Queue behavior, Dashboard APIs, and framework adapters remain outside this package.
Consumers should treat this package as the shared wire contract source of truth. Dashboard can validate publish-time definitions and manifests, adapters can validate their metadata and slot targets, build-runtime can accept only materialization refs/manifests/snapshots that parse through these schemas, and Workers can reuse the same field validator.
Client/server split
The root export and @200stack/static-forms-core/schema, @200stack/static-forms-core/json-schema, @200stack/static-forms-core/canonical-json, and @200stack/static-forms-core/core are browser and Cloudflare Worker safe. They do not import node:crypto.
Node-only SHA-256 helpers live under @200stack/static-forms-core/node. Build-time callers can combine that subpath with canonicalJsonStringify() to compute digests. Worker/browser callers should provide their own runtime-appropriate digest implementation if they need hashing.
0.2.4 の変更
フィールドの種類に、電話番号 tel・URL url・日付 date・数値 number を足しました。値はどれも文字列です。
tel: 半角の数字と+-()空白だけ、数字は 6〜15 桁、32 文字まで。url:http:かhttps:でホストがあるもの、空白・制御文字なし、2048 文字まで。date: 実在するYYYY-MM-DD(1900〜2100 年)。validation.min/maxで範囲を指定できます(範囲外はtoo_early/too_late)。number: 10 進の数(整数部と小数部を合わせて 15 桁、小数は 6 桁まで)。validation.min/max/integerを指定できます(範囲外はtoo_small/too_large)。入力欄はtype="text"とinputmodeです。- 形が合わないときのエラーコードは
invalid_formatです。 - controller は電話番号・数値の全角の数字と記号を半角に直して確認・送信します。日付を途中まで入れた欄は
invalid_formatで止めます。 - build protocol v2 だけで使えます(v1 ではビルドを止めます)。0.2.3 以前の adapter・core はこの 4 つを含むフォームを読めないため、使うときは adapter を 0.2.4 に上げてください(adapter 0.2.4 は core 0.2.4 を使います。core 0.2.3 は公開していません)。今の種類の動きは変わりません。
0.2.2 の変更
フィールドの種類に、複数選択のチェックボックス checkboxes を足しました。選択肢を並べ、選んだ値を配列で送ります。必須(1 つ以上)と、選べる数の下限・上限(validation.minSelected / maxSelected)を指定できます。確認画面とメールでは、選んだ選択肢の名前を「、」でつないで出します。build protocol v2 だけで使えます(v1 ではビルドを止めます)。0.2.2 以前の adapter は checkboxes を含むフォームを読めないため、使うときは adapter を 0.2.3 に上げてください(adapter 0.2.3 は core 0.2.2 を使います)。今の種類の動きは変わりません。
0.2.1 の変更
v2 と開発表示のプライバシーの案内のリンク(flow.privacy)は、別のタブで開きます(target="_blank" rel="noopener noreferrer")。入力の途中でポリシーを開いても、書きかけは消えません。別のタブで開くことは、リンクの中の読み上げだけの添え書き(data-200stack-static-form-new-tab。見た目には出さない)で伝えます。ビルド入力・schema・controller は 0.2.0 と同じです。
0.2.0 の変更
完了後の移動先はサイト内のルートからのパスだけです。@200stack/static-forms-core/browser の isSafeRedirect() は、2,048 字以内で / から始まるパスを検証します。//・絶対 URL・バックスラッシュ・空白や制御文字・#・パスの .. 区間・エンコードされた . / / / バックスラッシュは拒否します。クエリは使えます。controller は移動先の origin も確認します。
受付を確認すると、フォームのルートから STATIC_FORM_ACCEPTED_EVENT (200stack-static-form:accepted) を通知します。bubbles: true・cancelable: false の CustomEvent で、StaticFormAcceptedEventDetail は凍結した { slotId?: string, publicFormId: string, instanceId: string } です。メッセージを表示した後、または移動を呼ぶ前に通知し、入力値や受付 ID は含めません。同じ controller の同じ送信で最大 1 回通知します。定数と型は root・/core・/browser から export し、ブラウザのコードでは /browser を使ってください。
フォームの定義と render tree は lang: 'ja' | 'en' を持てます。resolveStaticFormLang(tree, optionLang?) は定義の言語を使い、定義に無ければ adapter の指定、両方無ければ日本語を使います。定義と異なる指定は StaticFormLangMismatchError になります。staticFormLanguages は対応する言語、staticFormLanguageNames は表示名です。これらは root と /core から export します。v2 と開発表示は解決した言語で固定文と検証エラーを表示します。項目名など保存済みの文言は自動翻訳しません。v1 は従来の英語固定です。
StaticFormDeliveryPlanV1.destinations は、運営者への ses_owner_email 1 件、またはその後に送信者への ses_submitter_auto_reply 1 件を加えた tuple の union です。自動返信は recipientFieldName で返信先の項目を指定し、固定テンプレートに入力値の変数を含めません。運営者のテンプレートは {{display.<fieldName>}} を使えます。definition を渡すと、select・radio は選択肢のラベル、チェック済み checkbox は同意文、その他は入力値になります。非表示・未入力・存在しない項目は空文字です。fields.* は従来の値を使い、HTML の置換値はエスケープします。renderStaticFormDeliveryPlan() は運営者の 1 件だけを返します。自動返信の描画や配送をこの関数に任せないでください。
v2 の renderStaticFormReferenceHtml() は slotId を指定するとルートに data-200stack-static-form-slot 属性を出し、受付イベントの detail.slotId に使います。未指定や旧い HTML ではイベントのキーを省略します。v1 と開発表示にはこの属性を出しません。
0.1.0 は 0.2.0 のビルド入力を読めません。新しい入力を使うサイトは core・SDK・adapter を対応する版に更新し、サイトを再ビルドしてください。
Determinism and reference rendering
compileStaticFormRenderTreeV1() is a pure compiler from a parsed Definition plus caller-provided digests/assets to a client-safe render tree. It does not read the clock, generate random values, or include absolute paths. Snapshot objects do not self-embed snapshotSha256 or generatedAt.
StaticFormDefinitionV1 uses the Phase 1a wire contract from the ExecPlan: renderer.accentToken, flow.confirmation as none | generated | configured(title, description?, submitLabel?), flow.completion as message or redirect, scrollBehavior as none | form | message, and runtime callback names such as beforeConfirm / beforeSubmit. publicFormId is intentionally not part of Definition or RenderTree; it belongs to snapshot slots and render options.
renderStaticFormReferenceHtml() renders a deterministic semantic <section><form>... fragment from a render tree and caller-provided publicFormId/instanceId. Message completion renders a hidden completion subtree with escaped title/body. Redirect completion is not converted into an arbitrary message. The renderer exists for conformance fixtures, previews, and simple static targets such as Eleventy. It is not a post-build DOM mutation tool and is not a replacement for framework adapter templates.
Adapter proof semanticDomDigest values are SHA-256 digests of this core reference HTML fragment for the occurrence. They are not digests of the framework-native output DOM. Framework adapters must still prove native output parity separately; semanticDomVerified: false remains the build-runtime posture until a native DOM verification step exists.
Definitions and render trees do not accept raw HTML, arbitrary CSS/class/style, JavaScript, event handlers, selector maps, or user-supplied tag/attribute maps. Text is plain text and the reference renderer escapes text and attributes before output.
Validation boundary
Exports whose names end in StructuralJsonSchema reject unknown object properties and enforce the constraints that Draft 2020-12 can represent. They are not a replacement for the corresponding Zod parser. superRefine invariants such as default-target membership, unique target/slot IDs, and slot target allowlists are semantic checks and require Zod parsing (or an equivalent implementation in another language) before wire input is accepted. Safe R2 object-key syntax is encoded in both Zod and the structural JSON Schema pattern.
StaticFormPublicId has an opaque-token-compatible wire shape of 22 to 128 base64url characters. The future generator remains responsible for using a cryptographically secure source with at least 128 bits of randomness; parsing a string cannot prove its entropy.
Canonical JSON v1
canonicalJsonStringify() is the algorithm identified by STATIC_FORM_CANONICAL_JSON_VERSION = 1; it does not claim RFC 8785 compatibility. It accepts only data properties containing JSON-compatible values, sorts object keys with JavaScript's default UTF-16 code-unit ordering, preserves array order, serializes finite numbers and strings with JSON.stringify, and performs no Unicode normalization. The Node helper hashes the resulting UTF-8 bytes with SHA-256. Non-JavaScript adapters must match the package's golden vectors before they can emit compatible digests.
見た目を指定する(v2)
StaticFormStyle はクラス名の文字列だけを受け取ります。項目の指定は field → 種類 (text / email / textarea / select / radio / checkbox / checkboxes / tel / url / date / number) → byName[name] の順に連結します。この順は CSS の優先順位を保証しません。未知の byName は警告し、種類に合わない部品は警告して使いません。
const style = {
section: { title: 'text-xl font-bold' },
field: { root: 'mb-5', label: 'font-medium', error: 'text-rose-600' },
email: { control: 'rounded border aria-[invalid=true]:border-rose-600' },
radio: { group: 'grid gap-2', option: 'flex gap-2', input: 'size-4' },
byName: { email: { label: 'text-indigo-800' } },
} satisfies StaticFormStyle| style のキー | part の正確な値 | 本番の DOM | 開発の表示 |
| --- | --- | --- | --- |
| root | root | section[data-200stack-static-form] | 外側の div[role=form] (root form) |
| form | form | form | 外側の div[role=form] |
| section.root / .title / .description | section / section-title / section-description | section / h2 / p | 同じ |
| field.root | field | 項目の外枠 div | 同じ |
| *.label / .description / .error | label / description / error | label または legend / p / p | 同じ |
| text・email・textarea・select・tel・url・date・number .control | control | input / textarea / select | 同じ |
| radio・checkbox・checkboxes .group / .option / .input | group / option / input | fieldset / 選択肢 label / input | 同じ |
| formErrors / status | form-errors / status | 全体エラー / 状態の div | 出ない |
| privacy.root / .link | privacy / privacy-link | p / a | 同じ |
| submit | submit | 送信 button | 無効の button |
| confirmation.* | confirmation、confirmation-title、confirmation-description、confirmation-list、confirmation-item、confirmation-label、confirmation-value、confirmation-back、confirmation-send | div / h2 / p / dl / div / dt / dd / buttons | 出ない |
| completion.* | completion、completion-title、completion-body | div / h2 / p | 出ない |
条件で DOM が出ない部品には効果がありません。特に、render tree で consentText が無い checkbox には group と label がありません。theme.css と公開の目印は v2 と開発表示だけで、v1 の出力には効きません。制御文字、JSON 全体 32 KiB、合成済み class 属性 2,000 バイトを超えるとエラーです。Tailwind は完全なクラス名をソースに書き、v3 は content、v4 は @source に共有 style ファイルを含めます。動的なクラスは検出されず、エラー用には aria-[invalid=true]:border-rose-600 のように書きます。
CSS の目印と theme.css
v2 本番と開発表示で公開する目印は、各部品の data-200stack-static-form-part、項目外枠の data-200stack-static-form-type・-name・-required・-invalid です。複数の役割は空白区切りなので [data-200stack-static-form-part~="form"] のように ~= を使います。name は .・: を含み得るため引用符で囲み、name を変更すると name を狙う CSS と byName は黙って外れます。controller 実行時の要素と開発表示の説明文は公開契約外です。HTML は項目ごとに 100〜200 バイト、選択肢ごとに約 70 バイト増えます。
adapter の theme は Next で import '@200stack/static-forms-next/theme.css'、Astro で import '@200stack/static-forms-astro/theme.css'、Eleventy で theme: true と {% staticFormsTheme %} を使います。--200stack-sf-accent、--200stack-sf-accent-hover、--200stack-sf-text、--200stack-sf-muted、--200stack-sf-border、--200stack-sf-frame-border、--200stack-sf-error、--200stack-sf-error-text、--200stack-sf-radius、--200stack-sf-surface、--200stack-sf-error-surface、--200stack-sf-on-accent、--200stack-sf-completion-surface、--200stack-sf-completion-text を上書きできます。
@layer static-forms;
[data-200stack-static-form-part~="root"] { --200stack-sf-accent: #0f766e; --200stack-sf-surface: #06251f; --200stack-sf-text: #ecfdf5; }一般のサイトは CSS の先頭に @layer static-forms; を置きます。レイヤー外の CSS と後ろのレイヤーの CSS が勝ちます。Tailwind v4 は @layer theme, base, static-forms, components, utilities; を @import "tailwindcss"; より前に置き、theme.css は layer() を付けずに読み込みます。Astro は既定で小さな CSS を inline にするため、厳しい CSP では build.inlineStylesheets: 'never' にしてください。
theme.css だけの本番は style-src 'self'(または配信元)で足りますが、v2 は固定の <style> を常に出します。本番は 'sha256-LE3mSYpIJxrT67iqOfLXzAcqron3YCrRA+rogsSVPYA='、開発表示は 'sha256-PooA9aoFhckRsEL2CK2Uxp0tODRwW2JydE6jQ+WgQ+M=' を style-src(指定時は style-src-elem にも)へ追加してください。開発表示の重複診断 style は出力ごとの hash が必要です。
0.1 からの移行
| 0.1 | 0.2 |
| --- | --- |
| 平たい label / description / error | field.label / .description / .error |
| 平たい input / textarea / select | text 系の .control |
| fields.* / types.* の input | text 系は .control、radio・checkbox は .input |
| 文字列の field / section / confirmation / completion | それぞれの .root |
| choiceGroup / choiceLabel / radio / checkbox | choice の .group / .option / .input |
| sectionTitle / sectionDescription | section.title / .description |
| confirmationList など | confirmation.list など |
| types / fields | 種類キー / byName |
| first / last | section 内の表示位置は CSS、特定項目は byName |
StaticFormElementClasses・StaticFormFieldClasses は削除済みで、項目の型は StaticFormFieldStyle です。旧キーは読み替えず、Static Forms style uses 0.1 key at fields.email; see README: Migrating from 0.1 のようにエラーになります。
