@jimhoyd/urlcode 0.3.0 → 0.4.0-alpha.2
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/.claude/skills/urlcode-authoring/SKILL.md +122 -0
- package/.claude/skills/urlcode-operations/SKILL.md +108 -0
- package/.claude-plugin/marketplace.json +18 -0
- package/CONTRIBUTING.md +30 -2
- package/README.md +195 -255
- package/ROADMAP.md +143 -15
- package/SECURITY.md +31 -9
- package/dist/BUILD-MANIFEST.json +72 -47
- package/dist/adapters.js +4 -23
- package/dist/agent-lists.js +1 -1
- package/dist/agents-guide.js +113 -0
- package/dist/authoring-files.js +60 -0
- package/dist/authoring.js +11 -1
- package/dist/aws.js +4 -3
- package/dist/build-cloudflare.js +11 -24
- package/dist/build-static.js +134 -0
- package/dist/bulk.js +37 -0
- package/dist/capabilities.js +262 -0
- package/dist/capability-query.js +71 -0
- package/dist/catalog.js +105 -0
- package/dist/cli.js +165 -34
- package/dist/client-address.js +1 -1
- package/dist/compliance-rules/baseline.js +9 -17
- package/dist/compliance-rules/privacy.js +7 -18
- package/dist/compliance-rules/shared.js +0 -2
- package/dist/compliance-rules/strict.js +5 -5
- package/dist/compliance.js +6 -8
- package/dist/conditions.js +88 -0
- package/dist/config.js +69 -6
- package/dist/context.js +155 -0
- package/dist/ecosystem-cli.js +88 -0
- package/dist/egress.js +98 -0
- package/dist/examples.js +92 -0
- package/dist/explain-cli.js +64 -0
- package/dist/explain.js +131 -0
- package/dist/extensions.js +231 -0
- package/dist/function-sources.js +49 -5
- package/dist/function-worker.js +3 -1
- package/dist/functions.js +84 -13
- package/dist/guest-api.js +29 -3
- package/dist/index.js +40 -6
- package/dist/init-with.js +165 -0
- package/dist/interchange-cli.js +42 -0
- package/dist/interchange.js +189 -0
- package/dist/manifest.js +109 -0
- package/dist/match.js +2 -2
- package/dist/mcp-authoring.js +147 -0
- package/dist/mcp.js +97 -0
- package/dist/observability.js +7 -21
- package/dist/operator-host.js +29 -0
- package/dist/plugins.js +12 -0
- package/dist/policies/agents.js +2 -2
- package/dist/policies/cache.js +8 -3
- package/dist/policies/compression.js +2 -1
- package/dist/policies/security.js +0 -0
- package/dist/policies.js +1 -1
- package/dist/policy.js +56 -15
- package/dist/prerender.js +100 -41
- package/dist/project-tests.js +3 -3
- package/dist/provider-verification.js +92 -0
- package/dist/proxy.js +44 -0
- package/dist/readiness.js +34 -11
- package/dist/recipes.js +41 -0
- package/dist/route-diff.js +106 -0
- package/dist/router.js +45 -7
- package/dist/runtime.js +164 -64
- package/dist/sandbox.js +48 -0
- package/dist/scaffold.js +0 -0
- package/dist/schema-query.js +62 -0
- package/dist/scripts/operational-drills.js +12 -54
- package/dist/server.js +3 -29
- package/dist/signals.js +24 -0
- package/dist/site.js +0 -0
- package/dist/tooling.js +96 -0
- package/dist/trusted-functions.js +210 -0
- package/dist/types/adapters.d.ts +7 -4
- package/dist/types/agent-lists.d.ts +0 -1
- package/dist/types/agents-guide.d.ts +17 -0
- package/dist/types/authoring-files.d.ts +10 -0
- package/dist/types/aws.d.ts +3 -1
- package/dist/types/build-cloudflare.d.ts +1 -0
- package/dist/types/build-static.d.ts +43 -0
- package/dist/types/bulk.d.ts +27 -0
- package/dist/types/capabilities.d.ts +64 -0
- package/dist/types/capability-query.d.ts +24 -0
- package/dist/types/catalog.d.ts +65 -0
- package/dist/types/client-address.d.ts +0 -1
- package/dist/types/compliance-rules/baseline.d.ts +1 -9
- package/dist/types/compliance-rules/privacy.d.ts +1 -4
- package/dist/types/compliance-rules/shared.d.ts +0 -2
- package/dist/types/compliance-rules/strict.d.ts +0 -5
- package/dist/types/compliance.d.ts +0 -3
- package/dist/types/conditions.d.ts +19 -0
- package/dist/types/config.d.ts +21 -2
- package/dist/types/context.d.ts +66 -0
- package/dist/types/ecosystem-cli.d.ts +17 -0
- package/dist/types/egress.d.ts +46 -0
- package/dist/types/examples.d.ts +50 -0
- package/dist/types/explain-cli.d.ts +11 -0
- package/dist/types/explain.d.ts +95 -0
- package/dist/types/extensions.d.ts +177 -0
- package/dist/types/function-sources.d.ts +9 -0
- package/dist/types/functions.d.ts +48 -5
- package/dist/types/guest-api.d.ts +1 -0
- package/dist/types/index.d.ts +36 -6
- package/dist/types/init-with.d.ts +30 -0
- package/dist/types/interchange-cli.d.ts +16 -0
- package/dist/types/interchange.d.ts +42 -0
- package/dist/types/manifest.d.ts +79 -0
- package/dist/types/match.d.ts +1 -0
- package/dist/types/mcp-authoring.d.ts +92 -0
- package/dist/types/mcp.d.ts +12 -0
- package/dist/types/observability.d.ts +3 -14
- package/dist/types/operator-host.d.ts +8 -0
- package/dist/types/plugins.d.ts +2 -0
- package/dist/types/policies/agents.d.ts +0 -2
- package/dist/types/policies/compression.d.ts +2 -0
- package/dist/types/policies/security.d.ts +0 -1
- package/dist/types/policy.d.ts +15 -4
- package/dist/types/project-tests.d.ts +3 -2
- package/dist/types/provider-verification.d.ts +53 -0
- package/dist/types/proxy.d.ts +21 -0
- package/dist/types/readiness.d.ts +10 -3
- package/dist/types/recipes.d.ts +30 -0
- package/dist/types/route-diff.d.ts +27 -0
- package/dist/types/router.d.ts +2 -1
- package/dist/types/runtime.d.ts +11 -27
- package/dist/types/sandbox.d.ts +12 -0
- package/dist/types/scaffold.d.ts +0 -2
- package/dist/types/schema-query.d.ts +12 -0
- package/dist/types/server.d.ts +1 -4
- package/dist/types/signals.d.ts +25 -0
- package/dist/types/site.d.ts +0 -1
- package/dist/types/tooling.d.ts +115 -0
- package/dist/types/trusted-functions.d.ts +29 -0
- package/dist/types/types.d.ts +71 -7
- package/dist/types/typescript-authoring.d.ts +12 -0
- package/dist/types/vercel.d.ts +3 -1
- package/dist/types/verify-deployment.d.ts +47 -0
- package/dist/types.js +37 -5
- package/dist/typescript-authoring.js +142 -0
- package/dist/vercel.js +4 -3
- package/dist/verify-deployment.js +270 -0
- package/docs/AI-AUTHORING.md +232 -15
- package/docs/AWS.md +4 -4
- package/docs/BEST-PRACTICES.md +3 -2
- package/docs/BULK.md +79 -0
- package/docs/CAPABILITIES.md +192 -0
- package/docs/CAPACITY.md +129 -32
- package/docs/CI.md +142 -0
- package/docs/CLOUDFLARE.md +1 -2
- package/docs/COMPLIANCE.md +6 -9
- package/docs/CONDITIONS.md +74 -0
- package/docs/DEPLOYMENT-CHECKS.md +108 -0
- package/docs/EGRESS.md +125 -0
- package/docs/EXTENSIONS.md +398 -0
- package/docs/FRAMEWORK.md +198 -0
- package/docs/FUNCTION-SECURITY.md +129 -32
- package/docs/INSTALL.md +45 -12
- package/docs/INTERCHANGE.md +134 -0
- package/docs/LOAD-TESTING.md +4 -4
- package/docs/MIDDLEWARE-EXAMPLES.md +75 -0
- package/docs/MIDDLEWARE.md +29 -16
- package/docs/MONITORING.md +2 -19
- package/docs/NEXT-PHASE-PLAN.md +98 -0
- package/docs/NEXT-STEPS.md +634 -0
- package/docs/OBSERVABILITY.md +11 -18
- package/docs/OPEN-DECISIONS.md +212 -0
- package/docs/OPERATIONAL-PROOF.md +30 -31
- package/docs/OPERATIONS.md +29 -35
- package/docs/PLUGINS.md +37 -0
- package/docs/POLICIES.md +23 -309
- package/docs/PRERENDER.md +41 -1
- package/docs/PROJECT-DIRECTION.md +75 -8
- package/docs/PROVIDER-VERIFICATION.md +84 -0
- package/docs/READINESS.md +21 -1
- package/docs/README.md +87 -34
- package/docs/RECIPES.md +99 -0
- package/docs/RELEASE-READINESS.md +57 -35
- package/docs/RELEASE-SECURITY.md +116 -7
- package/docs/RESILIENCE.md +16 -15
- package/docs/ROUTING.md +8 -10
- package/docs/SANDBOX-REVIEW.md +19 -6
- package/docs/SCAFFOLDING.md +0 -2
- package/docs/SECURITY-AUDIT.md +41 -1
- package/docs/SPECIFICATION.md +150 -29
- package/docs/SPIKE-AI-FRAMEWORK-BENCHMARK.md +287 -0
- package/docs/SPIKE-BUSINESS-SUITE.md +1021 -0
- package/docs/SPIKE-CORE-LAYERING.md +337 -0
- package/docs/SPIKE-DEFAULT-TRUST-MODEL.md +209 -0
- package/docs/SPIKE-EXTENSION-MODEL.md +419 -0
- package/docs/SPIKE-EXTENSIONS.md +6 -0
- package/docs/SPIKE-LAMBDA-COMPILE.md +201 -0
- package/docs/SPIKE-MONOREPO.md +322 -0
- package/docs/STANDARDS.md +150 -142
- package/docs/STARTERS.md +21 -1
- package/docs/STATIC.md +94 -0
- package/docs/TOOLING.md +295 -0
- package/docs/TUNNELS.md +0 -3
- package/docs/TYPESCRIPT-AUTHORING.md +82 -0
- package/docs/TYPESCRIPT.md +25 -4
- package/docs/USABILITY-REVIEW.md +129 -0
- package/docs/VERCEL.md +4 -5
- package/docs/VERSION-ALIGNMENT.md +205 -0
- package/docs/YAML-GUIDE.md +15 -479
- package/docs/YAML-REFERENCE.md +143 -22
- package/docs/policies/agents.md +1 -1
- package/docs/policies/cache.md +13 -0
- package/docs/policies/contract.md +52 -0
- package/docs/policies/hardened.md +56 -0
- package/docs/policies/interoperability.md +169 -0
- package/docs/policies/operations.md +45 -0
- package/docs/yaml/assets.md +36 -0
- package/docs/yaml/conditions.md +20 -0
- package/docs/yaml/functions.md +160 -0
- package/docs/yaml/middleware.md +29 -0
- package/docs/yaml/organization.md +74 -0
- package/docs/yaml/policies.md +37 -0
- package/docs/yaml/redirects.md +64 -0
- package/docs/yaml/responses.md +57 -0
- package/docs/yaml/site.md +24 -0
- package/examples/assets/example.yaml +17 -0
- package/examples/aws/example.yaml +20 -0
- package/examples/cloudflare/example.yaml +19 -0
- package/examples/compliance/example.yaml +11 -0
- package/examples/conditions/README.md +12 -0
- package/examples/conditions/example.yaml +19 -0
- package/examples/conditions/tests/requests.json +13 -0
- package/examples/conditions/urlcode.yaml +24 -0
- package/examples/cookbook/README.md +8 -4
- package/examples/cookbook/example.yaml +17 -0
- package/examples/cookbook/functions/catalog.mjs +3 -0
- package/examples/cookbook/functions/fail.mjs +4 -0
- package/examples/cookbook/functions/items.mjs +3 -0
- package/examples/cookbook/functions/profile.mjs +3 -0
- package/examples/cookbook/functions/resource.mjs +3 -0
- package/examples/cookbook/functions/status.mjs +3 -0
- package/examples/cookbook/middleware/auth.mjs +48 -0
- package/examples/cookbook/middleware/body.mjs +15 -0
- package/examples/cookbook/middleware/bucket.mjs +29 -0
- package/examples/cookbook/middleware/cors.mjs +21 -0
- package/examples/cookbook/middleware/debug.mjs +13 -0
- package/examples/cookbook/middleware/envelope.mjs +11 -0
- package/examples/cookbook/middleware/errors.mjs +11 -0
- package/examples/cookbook/middleware/etag.mjs +18 -0
- package/examples/cookbook/middleware/locale.mjs +20 -0
- package/examples/cookbook/middleware/maintenance.mjs +10 -0
- package/examples/cookbook/middleware/methods.mjs +15 -0
- package/examples/cookbook/middleware/negotiate.mjs +20 -0
- package/examples/cookbook/middleware/referer.mjs +12 -0
- package/examples/cookbook/middleware/request-id.mjs +16 -0
- package/examples/cookbook/route-index.json +676 -0
- package/examples/cookbook/routes/middleware.yaml +126 -0
- package/examples/cookbook/tests/requests.json +526 -0
- package/examples/cookbook/urlcode.yaml +1 -0
- package/examples/egress/README.md +22 -0
- package/examples/egress/example.yaml +19 -0
- package/examples/egress/urlcode.yaml +19 -0
- package/examples/extensions/README.md +7 -0
- package/examples/extensions/example.yaml +21 -0
- package/examples/extensions/urlcode.yaml +25 -0
- package/examples/monitoring/example.yaml +8 -0
- package/examples/prerender/README.md +2 -2
- package/examples/prerender/example.yaml +16 -0
- package/examples/provider-conformance/README.md +12 -0
- package/examples/provider-conformance/example.yaml +14 -0
- package/examples/provider-conformance/urlcode.yaml +34 -0
- package/examples/tunnel/example.yaml +8 -0
- package/examples/vercel/example.yaml +19 -0
- package/llms-full.txt +3084 -0
- package/llms.txt +61 -21
- package/package.json +36 -7
- package/packaging/claude-plugin/.claude-plugin/plugin.json +19 -0
- package/packaging/claude-plugin/skills/urlcode-authoring/SKILL.md +122 -0
- package/packaging/claude-plugin/skills/urlcode-operations/SKILL.md +108 -0
- package/recipes/authenticated-json-api/README.md +51 -0
- package/recipes/authenticated-json-api/functions/profile.mjs +5 -0
- package/recipes/authenticated-json-api/recipe.yaml +34 -0
- package/recipes/authenticated-json-api/tests/requests.json +39 -0
- package/recipes/authenticated-json-api/urlcode.yaml +12 -0
- package/recipes/contact-form/README.md +25 -0
- package/recipes/contact-form/functions/contact.mjs +17 -0
- package/recipes/contact-form/recipe.yaml +33 -0
- package/recipes/contact-form/tests/requests.json +47 -0
- package/recipes/contact-form/urlcode.yaml +18 -0
- package/recipes/cors-api/README.md +16 -0
- package/recipes/cors-api/functions/items.mjs +3 -0
- package/recipes/cors-api/middleware/cors.mjs +21 -0
- package/recipes/cors-api/recipe.yaml +26 -0
- package/recipes/cors-api/tests/requests.json +65 -0
- package/recipes/cors-api/urlcode.yaml +12 -0
- package/recipes/health-page/README.md +13 -0
- package/recipes/health-page/recipe.yaml +23 -0
- package/recipes/health-page/tests/requests.json +36 -0
- package/recipes/health-page/urlcode.yaml +19 -0
- package/recipes/json-api/README.md +6 -0
- package/recipes/json-api/functions/echo.mjs +3 -0
- package/recipes/json-api/recipe.yaml +25 -0
- package/recipes/json-api/tests/requests.json +34 -0
- package/recipes/json-api/urlcode.yaml +12 -0
- package/recipes/middleware/README.md +34 -0
- package/recipes/middleware/functions/catalog.mjs +3 -0
- package/recipes/middleware/functions/fail.mjs +4 -0
- package/recipes/middleware/functions/items.mjs +3 -0
- package/recipes/middleware/functions/profile.mjs +3 -0
- package/recipes/middleware/functions/resource.mjs +3 -0
- package/recipes/middleware/functions/status.mjs +3 -0
- package/recipes/middleware/middleware/auth.mjs +48 -0
- package/recipes/middleware/middleware/body.mjs +15 -0
- package/recipes/middleware/middleware/bucket.mjs +29 -0
- package/recipes/middleware/middleware/cors.mjs +21 -0
- package/recipes/middleware/middleware/debug.mjs +13 -0
- package/recipes/middleware/middleware/envelope.mjs +11 -0
- package/recipes/middleware/middleware/errors.mjs +11 -0
- package/recipes/middleware/middleware/etag.mjs +18 -0
- package/recipes/middleware/middleware/locale.mjs +20 -0
- package/recipes/middleware/middleware/maintenance.mjs +10 -0
- package/recipes/middleware/middleware/methods.mjs +15 -0
- package/recipes/middleware/middleware/negotiate.mjs +20 -0
- package/recipes/middleware/middleware/referer.mjs +12 -0
- package/recipes/middleware/middleware/request-id.mjs +16 -0
- package/recipes/middleware/public/guide.txt +1 -0
- package/recipes/middleware/recipe.yaml +50 -0
- package/recipes/middleware/tests/requests.json +528 -0
- package/recipes/middleware/urlcode.yaml +127 -0
- package/recipes/protected-download/README.md +22 -0
- package/recipes/protected-download/files/report.txt +1 -0
- package/recipes/protected-download/recipe.yaml +31 -0
- package/recipes/protected-download/tests/requests.json +32 -0
- package/recipes/protected-download/urlcode.yaml +15 -0
- package/recipes/redirect/README.md +7 -0
- package/recipes/redirect/recipe.yaml +25 -0
- package/recipes/redirect/tests/requests.json +19 -0
- package/recipes/redirect/urlcode.yaml +9 -0
- package/recipes/static-plus-api/README.md +15 -0
- package/recipes/static-plus-api/functions/info.mjs +3 -0
- package/recipes/static-plus-api/public/assets/index.html +3 -0
- package/recipes/static-plus-api/public/assets/site.css +1 -0
- package/recipes/static-plus-api/public/index.html +8 -0
- package/recipes/static-plus-api/recipe.yaml +29 -0
- package/recipes/static-plus-api/tests/requests.json +56 -0
- package/recipes/static-plus-api/urlcode.yaml +23 -0
- package/recipes/typescript/README.md +8 -0
- package/recipes/typescript/functions/hello.ts +5 -0
- package/recipes/typescript/recipe.yaml +23 -0
- package/recipes/typescript/tests/requests.json +18 -0
- package/recipes/typescript/urlcode.yaml +5 -0
- package/recipes/webhook-receiver/README.md +20 -0
- package/recipes/webhook-receiver/functions/receive.mjs +16 -0
- package/recipes/webhook-receiver/recipe.yaml +27 -0
- package/recipes/webhook-receiver/tests/requests.json +59 -0
- package/recipes/webhook-receiver/urlcode.yaml +23 -0
- package/schemas/recipe.schema.json +139 -0
- package/schemas/urlcode.schema.json +659 -110
- package/skills/urlcode/SKILL.md +119 -0
- package/starters/default/.github/workflows/urlcode.yml +23 -0
- package/starters/default/.mcp.json +12 -0
- package/starters/default/AGENTS.md +79 -0
- package/starters/default/urlcode.yaml +0 -1
- package/dist/link-api.js +0 -136
- package/dist/link-cli.js +0 -141
- package/dist/link-events.js +0 -76
- package/dist/link-records.js +0 -31
- package/dist/link-store-worker.js +0 -150
- package/dist/link-store.js +0 -250
- package/dist/management-policy.js +0 -41
- package/dist/sqlite-version.js +0 -6
- package/dist/types/link-api.d.ts +0 -30
- package/dist/types/link-cli.d.ts +0 -36
- package/dist/types/link-events.d.ts +0 -27
- package/dist/types/link-records.d.ts +0 -11
- package/dist/types/link-store-worker.d.ts +0 -1
- package/dist/types/link-store.d.ts +0 -130
- package/dist/types/management-policy.d.ts +0 -9
- package/dist/types/sqlite-version.d.ts +0 -1
- package/docs/DYNAMIC-LINKS.md +0 -561
- package/docs/MANAGEMENT-SECURITY.md +0 -82
- package/examples/live-links/README.md +0 -11
- package/examples/live-links/tests/requests.json +0 -6
- package/examples/live-links/urlcode.yaml +0 -16
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# Usability review: URLCode against the tools people reach for instead
|
|
2
|
+
|
|
3
|
+
Status: an honest assessment written 2026-09-18 against the source in the four
|
|
4
|
+
repositories at that date. It is opinion grounded in the code and docs, not a
|
|
5
|
+
benchmark. Its purpose is to name the friction a first-time user or an AI
|
|
6
|
+
agent meets, compare it with what competing tools do, and rank the changes
|
|
7
|
+
that would matter most. It does not change any contract by itself.
|
|
8
|
+
|
|
9
|
+
> **Update:** written the day before `0.4.0-alpha.2` reversed the default
|
|
10
|
+
> execution mode. Where the table below credits functions and middleware with
|
|
11
|
+
> isolation, that now describes a route declaring `sandbox: true`; the default
|
|
12
|
+
> is trusted, in-process execution with full Node, npm and filesystem access.
|
|
13
|
+
> See [SPIKE-DEFAULT-TRUST-MODEL.md](SPIKE-DEFAULT-TRUST-MODEL.md).
|
|
14
|
+
|
|
15
|
+
## 1. Who the competitors are, by rung
|
|
16
|
+
|
|
17
|
+
URLCode is not one product; it competes with a different tool on each rung
|
|
18
|
+
of [the ladder](FRAMEWORK.md#the-ladder).
|
|
19
|
+
|
|
20
|
+
| Rung | What people use today | Where URLCode is better | Where it is worse today |
|
|
21
|
+
|---|---|---|---|
|
|
22
|
+
| Redirects, responses | Netlify `_redirects`, Cloudflare `_redirects`/Rules, Vercel `vercel.json`, nginx | One portable file that validates before deploy, tests with fixtures, counts routes, converts from those formats and refuses lossy conversions | Those files are two columns; `urlcode.yaml` needs `version`, a route key and a handler object. Nobody needs a validator for ten redirects |
|
|
23
|
+
| Pages, files | Any static host, Astro, Hugo | Same file, no build step, native ranges/ETags, `site` conventions | No content pipeline, no templating for pages outside the extension kit |
|
|
24
|
+
| Functions, middleware | Cloudflare Workers, Vercel Functions, Hono, Express | Real isolation with a fresh heap per call; typed args from YAML; secrets only by revision-pinned grant; runs the same on a laptop | No `fetch`, no timers, no streaming, no npm packages in guests. Every framework above lets you call an API from a function; here you declare a `proxy` or stop |
|
|
25
|
+
| Live links | Bitly, Dub, Short.io, a Postgres table | Records without reloads, versioned writes, private management API, no account system to run | Single host SQLite that needs a patched Node build; no dashboard until you install admin |
|
|
26
|
+
| Accounts | Clerk, Auth0, Auth.js, Better Auth, Lucia, Supabase Auth, Devise | Everything is operator-owned and reviewable: no vendor, no callback URLs on someone else's dashboard, no per-MAU bill; passkeys, OIDC, TOTP, recovery and an account page in one package | Install is a reviewed-tarball ritual, a host file, a SHA-256 pin and a JSON-on-stdin bootstrap. Clerk is `npm i` and one env var. Better Auth is a config object and a CLI migration |
|
|
27
|
+
| Administration | Django admin, Rails Administrate, Retool, Forest Admin, Supabase Studio | Built on the same service with two-person cases and audited reveal, which none of the generic admins give you | Only manages auth's entities; there is no way to expose the project's own data. Django admin is one line per model |
|
|
28
|
+
| Full app in one place | Rails, Django, Laravel, Next.js + Clerk + Prisma, Supabase | The whole thing is declarative and portable; an agent can generate and validate it without a build; the trust boundary between generated code and the operator is explicit | Everything that is not a route, a function or an account is out of scope: no data model, no ORM, no forms for your own tables, no client bundle, no email templates for your app |
|
|
29
|
+
|
|
30
|
+
The honest summary: URLCode wins on portability, validation, isolation and
|
|
31
|
+
operator ownership, and loses on the first fifteen minutes and on anything
|
|
32
|
+
that needs application data.
|
|
33
|
+
|
|
34
|
+
## 2. The first fifteen minutes
|
|
35
|
+
|
|
36
|
+
What a new person does today, compared with the fastest competitor at each
|
|
37
|
+
step. Times are what the docs imply, not measurements.
|
|
38
|
+
|
|
39
|
+
| Step | URLCode today | Fastest competitor | Gap |
|
|
40
|
+
|---|---|---|---|
|
|
41
|
+
| Install | `npm i -g @jimhoyd/urlcode` or brew or a checksum script; three channels documented at equal weight | `npx create-next-app` | Too many equal choices on the first screen. Pick one, put the rest in the install guide |
|
|
42
|
+
| First project | `urlcode init` writes a function route, a redirect and fixtures | `wrangler init` | Equal |
|
|
43
|
+
| First redirect | 5 lines of YAML for one redirect | 1 line in `_redirects` | Acceptable once, heavy for 200. `bulk-import` exists but a person starting small never learns it |
|
|
44
|
+
| First function | Must know: `parameters` with `schema`, `args` with `from: path`, the Request/Response subset, that `fetch` is absent | Hono: `app.get('/x', c => c.json(...))` | The typed-args design is right but needs a two-line minimal form. Today the smallest function route is 10 lines |
|
|
45
|
+
| Add accounts | Clone two private repos, build tarballs with `pack-sources.mjs --core --auth --ui --admin --core-revision SHA`, install four tarballs, `urlcode-auth init`, compute `inspectExtensionRevision`, paste the SHA into an env var, `bootstrap` with JSON on stdin, `serve --host-file --origin` | Clerk: `npm i @clerk/nextjs`, one env var, wrap the app | This is the largest gap in the framework. Most of it is a consequence of the packages being unpublished and of the revision pin, and it is the step the README leads with |
|
|
46
|
+
| Add admin | `urlcode-admin init` writes everything | Django `admin.site.register(Model)` | Comparable once installed |
|
|
47
|
+
| Deploy | Container, Node process, or an adapter that refuses functions, links and extensions | `vercel deploy` | The refusals are correct but the person learns them at deploy time. `urlcode capabilities` exists and is not in the quick start |
|
|
48
|
+
|
|
49
|
+
## 3. What an AI agent meets
|
|
50
|
+
|
|
51
|
+
The framework's claim is that an agent should build from a handful of
|
|
52
|
+
redirects to a full application without rebuilding the core. Measured
|
|
53
|
+
against that claim:
|
|
54
|
+
|
|
55
|
+
- **Strong:** one JSON Schema, a generated field reference, fixtures the agent
|
|
56
|
+
must write, `validate`, `test` and `audit` that give exact failures with the
|
|
57
|
+
route named, an MCP server for read-only inspection, and a capability
|
|
58
|
+
matrix that lists what does not exist. Few frameworks tell an agent what it
|
|
59
|
+
cannot do. This is the right foundation.
|
|
60
|
+
- **Weak:** the documentation was organized by feature history rather than by
|
|
61
|
+
task. Before this review, `llms.txt` listed 40 documents at equal weight,
|
|
62
|
+
three of them status logs, and the README opened with release history and
|
|
63
|
+
"unreleased source" caveats. An agent reading it spent its context on
|
|
64
|
+
provenance rather than on the shape of a project. The framework page and the
|
|
65
|
+
reorganized index in this change address that; the remaining cost is the
|
|
66
|
+
size of the reference documents themselves (the YAML guide, policies and
|
|
67
|
+
dynamic links are each over 400 lines).
|
|
68
|
+
- **Missing:** an agent cannot yet discover the extension packages' YAML from
|
|
69
|
+
the core schema. `extensions.auth.config` is validated by auth's schema at
|
|
70
|
+
activation, but there is no way to ask the installed runtime "what config
|
|
71
|
+
does `auth` accept" without the host file. A `urlcode extensions --schema`
|
|
72
|
+
command that reads the host file and prints each extension's configuration
|
|
73
|
+
and policy schemas would close this, and would let `urlcode mcp` serve them.
|
|
74
|
+
- **Missing:** no single command creates the whole layered project. Today it
|
|
75
|
+
is `urlcode init`, then `urlcode-auth init`, then `urlcode-admin init`, each
|
|
76
|
+
with its own directory conventions. One `urlcode init --with auth,admin`
|
|
77
|
+
that delegates to the installed extension packages' scaffolds would make
|
|
78
|
+
the ladder real for an agent.
|
|
79
|
+
|
|
80
|
+
## 4. Ranked recommendations
|
|
81
|
+
|
|
82
|
+
Ordered by how much each would change the experience per unit of work, and
|
|
83
|
+
whether it touches a contract.
|
|
84
|
+
|
|
85
|
+
1. **Publish the three extension packages** (even as `0.1.0-alpha` with the
|
|
86
|
+
caveats their status files carry). Every install step in section 2's
|
|
87
|
+
"add accounts" row except the revision pin existed because they were
|
|
88
|
+
unpublished. This was a decision, not code; the repositories already had
|
|
89
|
+
the release checks. No contract change. Done 2026-09-18: all three are on
|
|
90
|
+
npm as `0.1.0-alpha.x` (review still pending, issue 58).
|
|
91
|
+
2. **`urlcode init --with auth,admin,ui`.** Delegate to each installed
|
|
92
|
+
package's existing scaffold; write one host file and one README. No contract
|
|
93
|
+
change; a CLI addition in core that calls into optional peers.
|
|
94
|
+
3. **Print extension schemas.** `urlcode extensions --host-file … --json`
|
|
95
|
+
listing each registered extension's name, version, configuration schema and
|
|
96
|
+
policy schema, and expose it through `urlcode mcp`. No contract change.
|
|
97
|
+
4. **A short form for the common function route.** Allow `function:
|
|
98
|
+
functions/hello.mjs` as a string with path parameters inferred as required
|
|
99
|
+
strings of bounded length, expanding to today's long form. This is a schema
|
|
100
|
+
addition (`version: "1"` stays valid) and the single largest cut in YAML
|
|
101
|
+
for first-time users and agents. Needs the usual generated-reference and
|
|
102
|
+
cookbook updates.
|
|
103
|
+
5. **Lead the README with the ladder, not the release history.** Done in this
|
|
104
|
+
change; keep it that way. Move status caveats to the readiness register.
|
|
105
|
+
6. **One install channel on the first screen.** npm first; brew, script and
|
|
106
|
+
container in the install guide.
|
|
107
|
+
7. **Fold the `presentation`/`ui` split.** Auth and admin still render through
|
|
108
|
+
the primitives while the kit is the documented way to restyle. Finishing
|
|
109
|
+
kit adoption (already listed in the ui status file) removes the one place
|
|
110
|
+
where the framework's story and its code differ.
|
|
111
|
+
8. **Later, and a real contract question:** application data. The thing every
|
|
112
|
+
competitor on the last rung has and URLCode does not is a place for the
|
|
113
|
+
project's own records with an admin view. The runtime already has one
|
|
114
|
+
bounded store (links) and one admin surface (auth's entities). Whether a
|
|
115
|
+
declared `collection` handler with operator-owned SQLite and an admin
|
|
116
|
+
registration belongs in the framework is the next spike worth writing.
|
|
117
|
+
Without it, "full-fledged application" means "site with accounts".
|
|
118
|
+
|
|
119
|
+
## 5. What is fine and should stay
|
|
120
|
+
|
|
121
|
+
- The operator host file. It is the reason the project can be untrusted and
|
|
122
|
+
portable at the same time; Clerk's convenience is bought with a vendor in
|
|
123
|
+
the loop. Keep the boundary, make the file generated.
|
|
124
|
+
- The revision pin. It is unusual and it is what makes "an agent changed the
|
|
125
|
+
YAML" a reviewable event rather than a silent grant. Keep it, print it
|
|
126
|
+
loudly, and make `init` write it.
|
|
127
|
+
- Refusing instead of degrading on every target. The failure names the route.
|
|
128
|
+
- The capability matrix in the AI guide. Extend it to the extension packages
|
|
129
|
+
rather than softening it.
|
package/docs/VERCEL.md
CHANGED
|
@@ -5,8 +5,8 @@ The Vercel adapter runs a URLCode project as a Node function. The same
|
|
|
5
5
|
that is the point of the project format.
|
|
6
6
|
|
|
7
7
|
**This adapter serves native handlers only:** redirects, validated responses,
|
|
8
|
-
pages, static assets and downloads.
|
|
9
|
-
|
|
8
|
+
pages, static assets and downloads. `function` and `middleware` routes are
|
|
9
|
+
refused at activation, trusted or sandboxed alike, not per request, so a deployment cannot
|
|
10
10
|
half-work. See [what is not supported](#what-this-adapter-does-not-do).
|
|
11
11
|
|
|
12
12
|
A working project is in [`examples/vercel/`](../examples/vercel/).
|
|
@@ -71,9 +71,8 @@ and want it in generated URLs.
|
|
|
71
71
|
|
|
72
72
|
| Not supported | Why |
|
|
73
73
|
|---|---|
|
|
74
|
-
|
|
|
75
|
-
| Middleware | Runs in the same
|
|
76
|
-
| Stored live links | SQLite needs a durable writable file. A serverless filesystem is ephemeral and per-instance, so records would silently diverge between instances. |
|
|
74
|
+
| `function` routes | They need the self-hosted Node lifecycle; a `sandbox: true` route would additionally spawn worker threads and load the WASM engine on every cold start. Correctness is not the issue; predictable latency is, and it is unmeasured. |
|
|
75
|
+
| Middleware | Runs in the same execution mode as the route's function, and is refused with it. |
|
|
77
76
|
| `urlcode serve` operational endpoints | `/_urlcode/health` and `/_urlcode/ready` describe a long-lived process. Use Vercel's own observability. |
|
|
78
77
|
|
|
79
78
|
Each refusal happens at activation with a message naming the route, so you find
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# Core version alignment across repositories
|
|
2
|
+
|
|
3
|
+
Which core version each downstream package supports, how it says so, and the
|
|
4
|
+
order in which a core behavior change reaches downstream repositories. This
|
|
5
|
+
page is a mechanism and an invariant, not a schedule: it states no release
|
|
6
|
+
cadence, no LTS line and no support window. Those are not decided.
|
|
7
|
+
|
|
8
|
+
It exists because "the current core" had come to mean five different things at
|
|
9
|
+
once, and one of them did not resolve. The `0.4.0-alpha.2` release closes that;
|
|
10
|
+
the table below is where every repository lands, and it is the register to
|
|
11
|
+
change whenever a version changes anywhere.
|
|
12
|
+
|
|
13
|
+
| Repository | How it names core | Value (read from its own `package.json`/`peers.json`) |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| `urlcode` | source version | `0.4.0-alpha.2` |
|
|
16
|
+
| `urlcode-auth`, `urlcode-admin` | peer range plus a reviewed SHA | `>=0.4.0-alpha.1 <0.5.0`; `peers.json` `urlcode` = `d5e86017e93b96ec24bfdbf840692b95fc323151` in both |
|
|
17
|
+
| `urlcode-dynamic-link` | peer range | `>=0.4.0-alpha.1 <0.5.0` |
|
|
18
|
+
| `urlcode-middleware` | peer range | `>=0.4.0-alpha.2 <0.5.0` |
|
|
19
|
+
| `urlcode-short`, `urlcode-template`, `urlcode-docs` | exact dependency pin | `0.4.0-alpha.2` |
|
|
20
|
+
|
|
21
|
+
Only `urlcode-middleware` requires `0.4.0-alpha.2` specifically: it uses the
|
|
22
|
+
`middleware()` extension hook, `ExtensionActivation.root` and
|
|
23
|
+
`RuntimeExtension.cacheSensitive`, none of which exist in `0.4.0-alpha.1`. The
|
|
24
|
+
other extension packages work against either alpha and keep the wider floor,
|
|
25
|
+
which is what the supported-floor definition below requires of them.
|
|
26
|
+
|
|
27
|
+
The npm dist-tags for `@jimhoyd/urlcode` are `latest` = `0.3.0` and `alpha` =
|
|
28
|
+
`0.4.0-alpha.2`. `latest` deliberately stays on the `0.3.0` Apache-2.0
|
|
29
|
+
self-hosted baseline: the `0.4.0` line is a prerelease and must not become the
|
|
30
|
+
default install. Every release workflow derives its dist-tag from the version
|
|
31
|
+
rather than defaulting, so a prerelease can only publish under `alpha`.
|
|
32
|
+
|
|
33
|
+
The sibling packages are `@jimhoyd/urlcode-ui` `0.1.0-alpha.5`,
|
|
34
|
+
`@jimhoyd/urlcode-auth` and `@jimhoyd/urlcode-admin` `0.1.0-alpha.3`, and
|
|
35
|
+
`@jimhoyd/urlcode-dynamic-link`, `@jimhoyd/urlcode-middleware` and
|
|
36
|
+
`@jimhoyd/urlcode-short` `0.1.0-alpha.2`. For the extension line, `latest` and
|
|
37
|
+
`alpha` point at the same version — see the second invariant below.
|
|
38
|
+
|
|
39
|
+
Every one of those is a new version in this release. Each package's previous
|
|
40
|
+
release sat at the same version number as a source tree that had moved well
|
|
41
|
+
past it — 43 merged commits in `urlcode-auth`, 40 in `urlcode-ui`, 28 in
|
|
42
|
+
`urlcode-admin` — so the published version number identified nothing. A
|
|
43
|
+
version number that does not change when the source does is the same class of
|
|
44
|
+
defect as a peer range that cannot resolve, and the rule is the same: change
|
|
45
|
+
the version in the pull request that changes the source.
|
|
46
|
+
|
|
47
|
+
## The supported floor
|
|
48
|
+
|
|
49
|
+
The supported core floor for an extension package is the **lowest core version
|
|
50
|
+
published to npm that contains every core API the extension calls**. It is a
|
|
51
|
+
property of the code, not of a calendar: raise it when the extension starts
|
|
52
|
+
using a core API that older published cores do not have, and not otherwise.
|
|
53
|
+
|
|
54
|
+
Everything below follows from that one definition.
|
|
55
|
+
|
|
56
|
+
## How a package expresses its core requirement
|
|
57
|
+
|
|
58
|
+
Three forms are in use. They are not interchangeable.
|
|
59
|
+
|
|
60
|
+
**Peer range (`peerDependencies`), for an extension package.** An extension is
|
|
61
|
+
installed alongside core by the operator, so it must not carry its own copy;
|
|
62
|
+
`peerDependencies` is the correct field. Write it as a floor plus the next
|
|
63
|
+
breaking bound — `">=<floor> <0.5.0"` — where the floor is the supported floor
|
|
64
|
+
above. `urlcode-auth` and `urlcode-admin` use this form.
|
|
65
|
+
|
|
66
|
+
**Exact pin (`dependencies`), for an application or a starter.** A project that
|
|
67
|
+
is deployed or cloned rather than composed — `urlcode-short`, `urlcode-template`,
|
|
68
|
+
`urlcode-docs` — depends on one core version and pins it exactly. This is the
|
|
69
|
+
right form when the repository's tests, generated files and documentation were
|
|
70
|
+
all produced against one runtime and are only claimed to hold for that runtime.
|
|
71
|
+
|
|
72
|
+
**Reviewed SHA (`peers.json`), for source CI on top of a range.** A reviewed
|
|
73
|
+
commit is not a substitute for the peer range; it is an addition to it, used
|
|
74
|
+
where the repository must build and test against an exact reviewed core
|
|
75
|
+
checkout rather than whatever the registry resolves. `urlcode-auth` and
|
|
76
|
+
`urlcode-admin` each keep one `peers.json` naming the reviewed core commit;
|
|
77
|
+
each file states in its own `$comment` that published releases do not use those
|
|
78
|
+
SHAs and resolve peers from the registry by the `package.json` range instead.
|
|
79
|
+
Use a reviewed SHA when source CI needs reproducibility; do not use it to
|
|
80
|
+
express what an installing operator will get.
|
|
81
|
+
|
|
82
|
+
## The invariant: a published peer range must be satisfiable
|
|
83
|
+
|
|
84
|
+
**A package published to npm must never declare a peer range that no published
|
|
85
|
+
core version satisfies.** At publish time, at least one version on the registry
|
|
86
|
+
must fall inside the range. A range that points at an unpublished core is not a
|
|
87
|
+
forward-looking declaration — it is an install failure for everyone who takes
|
|
88
|
+
the package from the registry.
|
|
89
|
+
|
|
90
|
+
The worked example came from this project. `@jimhoyd/urlcode-middleware`
|
|
91
|
+
`0.1.0-alpha.1` was published declaring `peerDependencies`
|
|
92
|
+
`{"@jimhoyd/urlcode": ">=0.4.0-alpha.2"}` at a time when the registry held only
|
|
93
|
+
`0.3.0` and `0.4.0-alpha.1`, so nothing satisfied it. The range was correct —
|
|
94
|
+
the package genuinely needs APIs that first appear in `0.4.0-alpha.2` — so the
|
|
95
|
+
fix was never to widen the range. The publication order was wrong: the package
|
|
96
|
+
was published before the core it requires, and it could be installed only from
|
|
97
|
+
source against a vendored core tarball carried for exactly that reason.
|
|
98
|
+
|
|
99
|
+
Publishing core `0.4.0-alpha.2` resolves it without any change to the already
|
|
100
|
+
published package: the range becomes satisfiable the moment core is on the
|
|
101
|
+
registry. The vendored tarball and the source-only install path go away with
|
|
102
|
+
it.
|
|
103
|
+
|
|
104
|
+
That is what the invariant prevents, and it is the only ordering rule that
|
|
105
|
+
cannot be relaxed.
|
|
106
|
+
|
|
107
|
+
## The second invariant: `latest` must not fall below a sibling's floor
|
|
108
|
+
|
|
109
|
+
**Where a package line publishes under a prerelease dist-tag, `latest` must
|
|
110
|
+
still resolve to a version that satisfies every peer floor its siblings
|
|
111
|
+
declare.** `latest` is what a plain `npm install <package>` resolves, so a
|
|
112
|
+
`latest` left behind hands the installing operator a build that another
|
|
113
|
+
package in the same install refuses.
|
|
114
|
+
|
|
115
|
+
The worked example, again live in this project: `@jimhoyd/urlcode-auth`
|
|
116
|
+
published `alpha` = `0.1.0-alpha.2` while leaving `latest` = `0.1.0-alpha.1`.
|
|
117
|
+
`@jimhoyd/urlcode-admin` declares a peer floor of `>=0.1.0-alpha.2` on auth, so
|
|
118
|
+
`npm install @jimhoyd/urlcode-auth` resolved a build below the floor admin
|
|
119
|
+
requires. Nothing in the source is wrong; the dist-tag is.
|
|
120
|
+
|
|
121
|
+
The fix is a registry operation rather than a source change:
|
|
122
|
+
|
|
123
|
+
```sh
|
|
124
|
+
npm dist-tag add @jimhoyd/urlcode-auth@0.1.0-alpha.2 latest
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Core is the deliberate exception. Its `latest` stays on `0.3.0` because no
|
|
128
|
+
sibling declares a floor above it — the extension packages name core through
|
|
129
|
+
`peerDependencies`, which resolve by range and never by dist-tag.
|
|
130
|
+
|
|
131
|
+
## A deliberate older pin is a position, not drift
|
|
132
|
+
|
|
133
|
+
`urlcode-template`, `urlcode-docs` and `urlcode-short` now all pin
|
|
134
|
+
`0.4.0-alpha.2`, so no downstream repository is currently behind. An older pin
|
|
135
|
+
remains a legitimate position, and the rule for it does not change: it is
|
|
136
|
+
recorded where a reader will meet it. The repository's README says which core
|
|
137
|
+
version it pins, and every statement about runtime behavior in that repository
|
|
138
|
+
is read against that version. Where a statement is only true for the pinned version, it
|
|
139
|
+
says so and names the version, rather than being silently corrected to match
|
|
140
|
+
core's unreleased `main`.
|
|
141
|
+
|
|
142
|
+
This matters most for the trust model. Core `0.4.0-alpha.2` runs `function` and
|
|
143
|
+
`middleware` routes trusted and unsandboxed by default, with `sandbox: true` as
|
|
144
|
+
a per-route opt-in ([decision record](SPIKE-DEFAULT-TRUST-MODEL.md)).
|
|
145
|
+
`0.4.0-alpha.1` and earlier sandbox all such code unconditionally and have no
|
|
146
|
+
`sandbox` field in the schema. This is why moving a pin to `0.4.0-alpha.2` is
|
|
147
|
+
a behavior change even when no YAML changes: every `function`/`middleware`
|
|
148
|
+
route that does not declare `sandbox` becomes trusted on upgrade. Review those
|
|
149
|
+
routes before raising a pin, and add `sandbox: true` to the ones that handle
|
|
150
|
+
input or code you would not trust with full Node/filesystem/network access.
|
|
151
|
+
A repository still pinned to `0.4.0-alpha.1` or `0.3.0` that documents
|
|
152
|
+
sandbox-by-default is **describing its pin correctly**. Its
|
|
153
|
+
generated files — field references, schemas, scaffolding output, vendored agent
|
|
154
|
+
skills — are likewise correct for that pin. Divergence from core's current
|
|
155
|
+
`main` is not by itself staleness, and it is not a defect to be "fixed" by
|
|
156
|
+
importing behavior the pinned runtime does not have. The same point is made
|
|
157
|
+
about the advisory `npm run check:downstream-skills` report in
|
|
158
|
+
[release readiness](RELEASE-READINESS.md).
|
|
159
|
+
|
|
160
|
+
What is a defect: a repository that does not say which core version it pins, or
|
|
161
|
+
that states pin-dependent behavior as an unconditional property of URLCode.
|
|
162
|
+
|
|
163
|
+
## Order of operations when core changes behavior
|
|
164
|
+
|
|
165
|
+
A core behavior change reaches downstream repositories in this order. Each step
|
|
166
|
+
depends on the one before it; skipping ahead is what produced the
|
|
167
|
+
`urlcode-middleware` breakage above.
|
|
168
|
+
|
|
169
|
+
1. **Publish core.** The version carrying the change goes to npm first. Until it
|
|
170
|
+
is on the registry, no downstream peer range may reference it (the invariant
|
|
171
|
+
above).
|
|
172
|
+
2. **Raise downstream floors.** Extension packages move their peer range floor
|
|
173
|
+
to the published version; applications and starters move their exact pin.
|
|
174
|
+
A repository that chooses to stay on the older core does nothing here — that
|
|
175
|
+
is a valid outcome, and step 3 does not apply to it.
|
|
176
|
+
3. **Regenerate downstream generated files.** Field references, schemas,
|
|
177
|
+
scaffolding output and vendored agent skills are regenerated *after* the
|
|
178
|
+
floor moves, against the version now pinned — never before, or they describe
|
|
179
|
+
a runtime the repository does not depend on.
|
|
180
|
+
|
|
181
|
+
Reviewed-SHA repositories take the same three steps, and additionally update
|
|
182
|
+
`peers.json` to the reviewed commit for the new core when source CI is expected
|
|
183
|
+
to build against it. The SHA and the published floor answer different questions
|
|
184
|
+
and are updated independently.
|
|
185
|
+
|
|
186
|
+
## Open: two publishing conventions
|
|
187
|
+
|
|
188
|
+
The repositories publish under two different conventions, and the maintainer has
|
|
189
|
+
not settled which one the project uses. Both are recorded here neutrally; this
|
|
190
|
+
page does not pick one.
|
|
191
|
+
|
|
192
|
+
- **Private until release.** `urlcode-dynamic-link` and `urlcode-middleware`
|
|
193
|
+
keep `"private": true` in their `package.json` on `main` and drop it in the
|
|
194
|
+
release commit. Publication is an explicit, visible act in the release diff,
|
|
195
|
+
and an accidental `npm publish` from `main` fails closed. Both packages are
|
|
196
|
+
nonetheless published on npm, so the convention has been exercised.
|
|
197
|
+
- **Publishable on main.** `urlcode-auth`, `urlcode-admin` and `urlcode-ui`
|
|
198
|
+
carry no `private` field and declare `publishConfig.access = "public"`
|
|
199
|
+
instead. The committed manifest is
|
|
200
|
+
the manifest that publishes, so what is on the registry can be diffed against
|
|
201
|
+
`main` without accounting for a release-only edit.
|
|
202
|
+
|
|
203
|
+
The split is currently by repository, not by package kind, and nothing records
|
|
204
|
+
why. Settling it is a maintainer decision; no `package.json` is changed on the
|
|
205
|
+
strength of this page.
|