@mccaskie-labs/workbench-config 0.1.26 → 0.1.27
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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @mccaskie-labs/workbench-config
|
|
2
2
|
|
|
3
|
+
## 0.1.27
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- The forms manifest (`capabilities/forms.json`): five rules for `workbench-forms` (the endpoint decides, one verdict on the page and at the endpoint, codes turned into words by the strings catalogue, bounds from the limits register, no members on a form) and two no-cost probes, `forms.validate` and `forms.browser`. `forms` joins the component list.
|
|
8
|
+
|
|
3
9
|
## 0.1.26
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "./schema.json",
|
|
3
|
+
"format": 1,
|
|
4
|
+
"component": "forms",
|
|
5
|
+
"title": "Form validation",
|
|
6
|
+
"summary": "workbench-forms checks what people type into a form with one validator that runs the same on the page and at the endpoint. It returns every problem at once as permanent codes, the tidied value to show back in each field and the value to store, with limits from the app's own register and country shapes from workbench-country. It runs in the app itself, with no Gateway call and no cost.",
|
|
7
|
+
"status": "built",
|
|
8
|
+
"surfaces": {
|
|
9
|
+
"package": [
|
|
10
|
+
"defineForm",
|
|
11
|
+
"text",
|
|
12
|
+
"longText",
|
|
13
|
+
"email",
|
|
14
|
+
"phone",
|
|
15
|
+
"money",
|
|
16
|
+
"whole",
|
|
17
|
+
"date",
|
|
18
|
+
"choice",
|
|
19
|
+
"validate",
|
|
20
|
+
"readForm",
|
|
21
|
+
"embedConfig",
|
|
22
|
+
"inputAttributes",
|
|
23
|
+
"messageKeys",
|
|
24
|
+
"tidyLine",
|
|
25
|
+
"tidyText",
|
|
26
|
+
"CODES",
|
|
27
|
+
"FormsSetupError"
|
|
28
|
+
]
|
|
29
|
+
},
|
|
30
|
+
"rules": [
|
|
31
|
+
{
|
|
32
|
+
"id": "forms.endpoint-decides",
|
|
33
|
+
"kind": "must",
|
|
34
|
+
"text": "Validate every form at the endpoint with validate() and store only result.values; the page's copy of the validator (FORMS_SCRIPT) only helps the person, it never decides.",
|
|
35
|
+
"enforced_by": "review",
|
|
36
|
+
"probe": "forms.validate"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"id": "forms.same-verdict",
|
|
40
|
+
"kind": "must",
|
|
41
|
+
"text": "Put the form on the page with embedConfig and FORMS_SCRIPT, never a second set of rules, so the page and the endpoint reach the same verdict.",
|
|
42
|
+
"enforced_by": "review",
|
|
43
|
+
"probe": "forms.browser"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"id": "forms.codes-not-sentences",
|
|
47
|
+
"kind": "must",
|
|
48
|
+
"text": "Turn problem codes into words through the strings catalogue (messageKeys: validation.<form>.<field>.<code>, then validation.<code>), never words written in code.",
|
|
49
|
+
"enforced_by": "review",
|
|
50
|
+
"probe": "forms.validate"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"id": "forms.limits-register",
|
|
54
|
+
"kind": "must",
|
|
55
|
+
"text": "Take every length, amount and date bound from the app's limits register (defineLimits, boundsFor), and keep it matched to the database with workbench-supabase check:limits.",
|
|
56
|
+
"enforced_by": "types",
|
|
57
|
+
"probe": "forms.validate"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"id": "forms.no-members-on-a-form",
|
|
61
|
+
"kind": "must_not",
|
|
62
|
+
"text": "List a choice field's members on the form: the vocabulary owns them, and the app passes them in context.choices.",
|
|
63
|
+
"enforced_by": "types",
|
|
64
|
+
"probe": "forms.validate"
|
|
65
|
+
}
|
|
66
|
+
],
|
|
67
|
+
"examples": [
|
|
68
|
+
{
|
|
69
|
+
"id": "forms.example.endpoint",
|
|
70
|
+
"title": "Check a posted form at the endpoint",
|
|
71
|
+
"lang": "ts",
|
|
72
|
+
"probe": "forms.validate",
|
|
73
|
+
"code": "import { defineForm, email, money, readForm, text, validate } from '@mccaskie-labs/workbench-forms'\nimport { boundsFor } from '@mccaskie-labs/workbench-forms/limits'\nimport { COUNTRY_PACKS } from '@mccaskie-labs/workbench-country'\n\nconst lead = defineForm('lead', { name: text({ limit: 'lead.name', required: true }), email: email({ required: true }), budget: money({ limit: 'lead.budget' }) })\nconst result = validate(lead, readForm(lead, await request.formData()), { bounds: boundsFor(LIMITS, lead), country: COUNTRY_PACKS.PH! })\nif (!result.ok) return show(result.problems, result.shown)\nawait db.from('leads').insert(result.values)"
|
|
74
|
+
}
|
|
75
|
+
],
|
|
76
|
+
"probes": [
|
|
77
|
+
{
|
|
78
|
+
"id": "forms.validate",
|
|
79
|
+
"half": "runtime",
|
|
80
|
+
"expect": "pass",
|
|
81
|
+
"smoke": true,
|
|
82
|
+
"cost_hint_usd": 0,
|
|
83
|
+
"note": "In the Worker itself: validates a fixed form (text, email, phone, money, date, choice) for PH with one good and one bad input, and compares the stored values (E.164 phone, decimal-string amount, ISO date) and the problem codes with the expected ones"
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"id": "forms.browser",
|
|
87
|
+
"half": "runtime",
|
|
88
|
+
"expect": "pass",
|
|
89
|
+
"cost_hint_usd": 0,
|
|
90
|
+
"note": "The probe page loads FORMS_SCRIPT and the form's embedConfig block, runs the same inputs in the browser and checks the verdict matches the Worker's; no Gateway call"
|
|
91
|
+
}
|
|
92
|
+
]
|
|
93
|
+
}
|
package/capabilities/schema.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const MANIFEST_VERSION: "
|
|
1
|
+
export declare const MANIFEST_VERSION: "c83a7c612319";
|
|
2
2
|
export declare const capabilities: {
|
|
3
3
|
readonly ai: {
|
|
4
4
|
readonly $schema: "./schema.json";
|
|
@@ -1084,6 +1084,69 @@ export declare const capabilities: {
|
|
|
1084
1084
|
readonly note: "Another project's namespace";
|
|
1085
1085
|
}];
|
|
1086
1086
|
};
|
|
1087
|
+
readonly forms: {
|
|
1088
|
+
readonly $schema: "./schema.json";
|
|
1089
|
+
readonly format: 1;
|
|
1090
|
+
readonly component: "forms";
|
|
1091
|
+
readonly title: "Form validation";
|
|
1092
|
+
readonly summary: "workbench-forms checks what people type into a form with one validator that runs the same on the page and at the endpoint. It returns every problem at once as permanent codes, the tidied value to show back in each field and the value to store, with limits from the app's own register and country shapes from workbench-country. It runs in the app itself, with no Gateway call and no cost.";
|
|
1093
|
+
readonly status: "built";
|
|
1094
|
+
readonly surfaces: {
|
|
1095
|
+
readonly package: readonly ["defineForm", "text", "longText", "email", "phone", "money", "whole", "date", "choice", "validate", "readForm", "embedConfig", "inputAttributes", "messageKeys", "tidyLine", "tidyText", "CODES", "FormsSetupError"];
|
|
1096
|
+
};
|
|
1097
|
+
readonly rules: readonly [{
|
|
1098
|
+
readonly id: "forms.endpoint-decides";
|
|
1099
|
+
readonly kind: "must";
|
|
1100
|
+
readonly text: "Validate every form at the endpoint with validate() and store only result.values; the page's copy of the validator (FORMS_SCRIPT) only helps the person, it never decides.";
|
|
1101
|
+
readonly enforced_by: "review";
|
|
1102
|
+
readonly probe: "forms.validate";
|
|
1103
|
+
}, {
|
|
1104
|
+
readonly id: "forms.same-verdict";
|
|
1105
|
+
readonly kind: "must";
|
|
1106
|
+
readonly text: "Put the form on the page with embedConfig and FORMS_SCRIPT, never a second set of rules, so the page and the endpoint reach the same verdict.";
|
|
1107
|
+
readonly enforced_by: "review";
|
|
1108
|
+
readonly probe: "forms.browser";
|
|
1109
|
+
}, {
|
|
1110
|
+
readonly id: "forms.codes-not-sentences";
|
|
1111
|
+
readonly kind: "must";
|
|
1112
|
+
readonly text: "Turn problem codes into words through the strings catalogue (messageKeys: validation.<form>.<field>.<code>, then validation.<code>), never words written in code.";
|
|
1113
|
+
readonly enforced_by: "review";
|
|
1114
|
+
readonly probe: "forms.validate";
|
|
1115
|
+
}, {
|
|
1116
|
+
readonly id: "forms.limits-register";
|
|
1117
|
+
readonly kind: "must";
|
|
1118
|
+
readonly text: "Take every length, amount and date bound from the app's limits register (defineLimits, boundsFor), and keep it matched to the database with workbench-supabase check:limits.";
|
|
1119
|
+
readonly enforced_by: "types";
|
|
1120
|
+
readonly probe: "forms.validate";
|
|
1121
|
+
}, {
|
|
1122
|
+
readonly id: "forms.no-members-on-a-form";
|
|
1123
|
+
readonly kind: "must_not";
|
|
1124
|
+
readonly text: "List a choice field's members on the form: the vocabulary owns them, and the app passes them in context.choices.";
|
|
1125
|
+
readonly enforced_by: "types";
|
|
1126
|
+
readonly probe: "forms.validate";
|
|
1127
|
+
}];
|
|
1128
|
+
readonly examples: readonly [{
|
|
1129
|
+
readonly id: "forms.example.endpoint";
|
|
1130
|
+
readonly title: "Check a posted form at the endpoint";
|
|
1131
|
+
readonly lang: "ts";
|
|
1132
|
+
readonly code: "import { defineForm, email, money, readForm, text, validate } from '@mccaskie-labs/workbench-forms'\nimport { boundsFor } from '@mccaskie-labs/workbench-forms/limits'\nimport { COUNTRY_PACKS } from '@mccaskie-labs/workbench-country'\n\nconst lead = defineForm('lead', { name: text({ limit: 'lead.name', required: true }), email: email({ required: true }), budget: money({ limit: 'lead.budget' }) })\nconst result = validate(lead, readForm(lead, await request.formData()), { bounds: boundsFor(LIMITS, lead), country: COUNTRY_PACKS.PH! })\nif (!result.ok) return show(result.problems, result.shown)\nawait db.from('leads').insert(result.values)";
|
|
1133
|
+
readonly probe: "forms.validate";
|
|
1134
|
+
}];
|
|
1135
|
+
readonly probes: readonly [{
|
|
1136
|
+
readonly id: "forms.validate";
|
|
1137
|
+
readonly half: "runtime";
|
|
1138
|
+
readonly expect: "pass";
|
|
1139
|
+
readonly smoke: true;
|
|
1140
|
+
readonly cost_hint_usd: 0;
|
|
1141
|
+
readonly note: "In the Worker itself: validates a fixed form (text, email, phone, money, date, choice) for PH with one good and one bad input, and compares the stored values (E.164 phone, decimal-string amount, ISO date) and the problem codes with the expected ones";
|
|
1142
|
+
}, {
|
|
1143
|
+
readonly id: "forms.browser";
|
|
1144
|
+
readonly half: "runtime";
|
|
1145
|
+
readonly expect: "pass";
|
|
1146
|
+
readonly cost_hint_usd: 0;
|
|
1147
|
+
readonly note: "The probe page loads FORMS_SCRIPT and the form's embedConfig block, runs the same inputs in the browser and checks the verdict matches the Worker's; no Gateway call";
|
|
1148
|
+
}];
|
|
1149
|
+
};
|
|
1087
1150
|
readonly gateway: {
|
|
1088
1151
|
readonly $schema: "./schema.json";
|
|
1089
1152
|
readonly format: 1;
|
package/dist/capabilities.gen.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Generated by `npm run capabilities:build` from capabilities/*.json. Do not edit.
|
|
2
2
|
// check:capabilities fails in Checks when this file is stale.
|
|
3
|
-
export const MANIFEST_VERSION = "
|
|
3
|
+
export const MANIFEST_VERSION = "c83a7c612319";
|
|
4
4
|
export const capabilities = {
|
|
5
5
|
"ai": {
|
|
6
6
|
"$schema": "./schema.json",
|
|
@@ -1475,6 +1475,99 @@ export const capabilities = {
|
|
|
1475
1475
|
}
|
|
1476
1476
|
]
|
|
1477
1477
|
},
|
|
1478
|
+
"forms": {
|
|
1479
|
+
"$schema": "./schema.json",
|
|
1480
|
+
"format": 1,
|
|
1481
|
+
"component": "forms",
|
|
1482
|
+
"title": "Form validation",
|
|
1483
|
+
"summary": "workbench-forms checks what people type into a form with one validator that runs the same on the page and at the endpoint. It returns every problem at once as permanent codes, the tidied value to show back in each field and the value to store, with limits from the app's own register and country shapes from workbench-country. It runs in the app itself, with no Gateway call and no cost.",
|
|
1484
|
+
"status": "built",
|
|
1485
|
+
"surfaces": {
|
|
1486
|
+
"package": [
|
|
1487
|
+
"defineForm",
|
|
1488
|
+
"text",
|
|
1489
|
+
"longText",
|
|
1490
|
+
"email",
|
|
1491
|
+
"phone",
|
|
1492
|
+
"money",
|
|
1493
|
+
"whole",
|
|
1494
|
+
"date",
|
|
1495
|
+
"choice",
|
|
1496
|
+
"validate",
|
|
1497
|
+
"readForm",
|
|
1498
|
+
"embedConfig",
|
|
1499
|
+
"inputAttributes",
|
|
1500
|
+
"messageKeys",
|
|
1501
|
+
"tidyLine",
|
|
1502
|
+
"tidyText",
|
|
1503
|
+
"CODES",
|
|
1504
|
+
"FormsSetupError"
|
|
1505
|
+
]
|
|
1506
|
+
},
|
|
1507
|
+
"rules": [
|
|
1508
|
+
{
|
|
1509
|
+
"id": "forms.endpoint-decides",
|
|
1510
|
+
"kind": "must",
|
|
1511
|
+
"text": "Validate every form at the endpoint with validate() and store only result.values; the page's copy of the validator (FORMS_SCRIPT) only helps the person, it never decides.",
|
|
1512
|
+
"enforced_by": "review",
|
|
1513
|
+
"probe": "forms.validate"
|
|
1514
|
+
},
|
|
1515
|
+
{
|
|
1516
|
+
"id": "forms.same-verdict",
|
|
1517
|
+
"kind": "must",
|
|
1518
|
+
"text": "Put the form on the page with embedConfig and FORMS_SCRIPT, never a second set of rules, so the page and the endpoint reach the same verdict.",
|
|
1519
|
+
"enforced_by": "review",
|
|
1520
|
+
"probe": "forms.browser"
|
|
1521
|
+
},
|
|
1522
|
+
{
|
|
1523
|
+
"id": "forms.codes-not-sentences",
|
|
1524
|
+
"kind": "must",
|
|
1525
|
+
"text": "Turn problem codes into words through the strings catalogue (messageKeys: validation.<form>.<field>.<code>, then validation.<code>), never words written in code.",
|
|
1526
|
+
"enforced_by": "review",
|
|
1527
|
+
"probe": "forms.validate"
|
|
1528
|
+
},
|
|
1529
|
+
{
|
|
1530
|
+
"id": "forms.limits-register",
|
|
1531
|
+
"kind": "must",
|
|
1532
|
+
"text": "Take every length, amount and date bound from the app's limits register (defineLimits, boundsFor), and keep it matched to the database with workbench-supabase check:limits.",
|
|
1533
|
+
"enforced_by": "types",
|
|
1534
|
+
"probe": "forms.validate"
|
|
1535
|
+
},
|
|
1536
|
+
{
|
|
1537
|
+
"id": "forms.no-members-on-a-form",
|
|
1538
|
+
"kind": "must_not",
|
|
1539
|
+
"text": "List a choice field's members on the form: the vocabulary owns them, and the app passes them in context.choices.",
|
|
1540
|
+
"enforced_by": "types",
|
|
1541
|
+
"probe": "forms.validate"
|
|
1542
|
+
}
|
|
1543
|
+
],
|
|
1544
|
+
"examples": [
|
|
1545
|
+
{
|
|
1546
|
+
"id": "forms.example.endpoint",
|
|
1547
|
+
"title": "Check a posted form at the endpoint",
|
|
1548
|
+
"lang": "ts",
|
|
1549
|
+
"code": "import { defineForm, email, money, readForm, text, validate } from '@mccaskie-labs/workbench-forms'\nimport { boundsFor } from '@mccaskie-labs/workbench-forms/limits'\nimport { COUNTRY_PACKS } from '@mccaskie-labs/workbench-country'\n\nconst lead = defineForm('lead', { name: text({ limit: 'lead.name', required: true }), email: email({ required: true }), budget: money({ limit: 'lead.budget' }) })\nconst result = validate(lead, readForm(lead, await request.formData()), { bounds: boundsFor(LIMITS, lead), country: COUNTRY_PACKS.PH! })\nif (!result.ok) return show(result.problems, result.shown)\nawait db.from('leads').insert(result.values)",
|
|
1550
|
+
"probe": "forms.validate"
|
|
1551
|
+
}
|
|
1552
|
+
],
|
|
1553
|
+
"probes": [
|
|
1554
|
+
{
|
|
1555
|
+
"id": "forms.validate",
|
|
1556
|
+
"half": "runtime",
|
|
1557
|
+
"expect": "pass",
|
|
1558
|
+
"smoke": true,
|
|
1559
|
+
"cost_hint_usd": 0,
|
|
1560
|
+
"note": "In the Worker itself: validates a fixed form (text, email, phone, money, date, choice) for PH with one good and one bad input, and compares the stored values (E.164 phone, decimal-string amount, ISO date) and the problem codes with the expected ones"
|
|
1561
|
+
},
|
|
1562
|
+
{
|
|
1563
|
+
"id": "forms.browser",
|
|
1564
|
+
"half": "runtime",
|
|
1565
|
+
"expect": "pass",
|
|
1566
|
+
"cost_hint_usd": 0,
|
|
1567
|
+
"note": "The probe page loads FORMS_SCRIPT and the form's embedConfig block, runs the same inputs in the browser and checks the verdict matches the Worker's; no Gateway call"
|
|
1568
|
+
}
|
|
1569
|
+
]
|
|
1570
|
+
},
|
|
1478
1571
|
"gateway": {
|
|
1479
1572
|
"$schema": "./schema.json",
|
|
1480
1573
|
"format": 1,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
export declare const COMPONENTS: readonly ["gateway", "ai", "images", "image-search", "config", "email", "email-inbound", "email-sending", "database", "workers", "domains", "dns", "analytics", "country", "pdf", "access", "r2", "kv", "github", "strings", "ui"];
|
|
2
|
+
export declare const COMPONENTS: readonly ["gateway", "ai", "images", "image-search", "config", "email", "email-inbound", "email-sending", "database", "workers", "domains", "dns", "analytics", "country", "pdf", "access", "r2", "kv", "github", "strings", "ui", "forms"];
|
|
3
3
|
export type Component = (typeof COMPONENTS)[number];
|
|
4
4
|
export declare const allowedSchemas: {
|
|
5
5
|
gateway: z.ZodObject<{
|
|
@@ -103,6 +103,7 @@ export declare const capabilitySchema: z.ZodObject<{
|
|
|
103
103
|
email: "email";
|
|
104
104
|
"email-inbound": "email-inbound";
|
|
105
105
|
"email-sending": "email-sending";
|
|
106
|
+
forms: "forms";
|
|
106
107
|
gateway: "gateway";
|
|
107
108
|
github: "github";
|
|
108
109
|
"image-search": "image-search";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mccaskie-labs/workbench-config",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.27",
|
|
4
4
|
"description": "Workbench app configuration: WORKBENCH_ENV, typed variables that fail by name, secrets that are never printed, and the capabilities manifest.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|