@jimhoyd/urlcode 0.4.0-alpha.1 → 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 +24 -8
- package/.claude/skills/urlcode-operations/SKILL.md +10 -16
- package/.claude-plugin/marketplace.json +1 -1
- package/README.md +75 -62
- package/ROADMAP.md +61 -16
- package/SECURITY.md +23 -8
- package/dist/BUILD-MANIFEST.json +38 -43
- package/dist/agents-guide.js +18 -18
- package/dist/build-static.js +134 -0
- package/dist/capabilities.js +85 -31
- package/dist/capability-query.js +0 -1
- package/dist/cli.js +23 -31
- package/dist/compliance-rules/baseline.js +2 -10
- package/dist/compliance-rules/privacy.js +5 -16
- package/dist/compliance-rules/shared.js +0 -2
- package/dist/compliance.js +6 -8
- package/dist/config.js +15 -8
- package/dist/context.js +9 -10
- package/dist/examples.js +2 -2
- package/dist/explain-cli.js +3 -3
- package/dist/explain.js +5 -6
- package/dist/extensions.js +61 -2
- package/dist/function-sources.js +34 -2
- package/dist/function-worker.js +3 -1
- package/dist/functions.js +84 -13
- package/dist/guest-api.js +29 -3
- package/dist/index.js +3 -6
- package/dist/manifest.js +5 -7
- package/dist/mcp.js +1 -1
- package/dist/observability.js +1 -21
- package/dist/policies/cache.js +8 -3
- package/dist/policy.js +28 -9
- package/dist/project-tests.js +3 -3
- package/dist/readiness.js +32 -9
- package/dist/router.js +5 -7
- package/dist/runtime.js +77 -58
- package/dist/sandbox.js +48 -0
- package/dist/scaffold.js +0 -0
- package/dist/scripts/operational-drills.js +12 -54
- package/dist/server.js +3 -29
- package/dist/tooling.js +1 -1
- package/dist/trusted-functions.js +210 -0
- package/dist/types/build-static.d.ts +43 -0
- package/dist/types/capabilities.d.ts +14 -5
- package/dist/types/compliance-rules/shared.d.ts +0 -2
- package/dist/types/compliance.d.ts +0 -3
- package/dist/types/config.d.ts +2 -1
- package/dist/types/context.d.ts +2 -1
- package/dist/types/examples.d.ts +1 -1
- package/dist/types/extensions.d.ts +55 -0
- package/dist/types/function-sources.d.ts +4 -0
- package/dist/types/functions.d.ts +48 -5
- package/dist/types/guest-api.d.ts +1 -0
- package/dist/types/index.d.ts +3 -6
- package/dist/types/manifest.d.ts +1 -3
- package/dist/types/observability.d.ts +1 -14
- package/dist/types/project-tests.d.ts +1 -2
- package/dist/types/readiness.d.ts +10 -3
- package/dist/types/router.d.ts +2 -1
- package/dist/types/runtime.d.ts +0 -27
- package/dist/types/sandbox.d.ts +12 -0
- package/dist/types/scaffold.d.ts +0 -2
- package/dist/types/server.d.ts +1 -4
- package/dist/types/tooling.d.ts +3 -3
- package/dist/types/trusted-functions.d.ts +29 -0
- package/dist/types/types.d.ts +14 -7
- package/dist/types/verify-deployment.d.ts +2 -2
- package/dist/types.js +17 -4
- package/dist/typescript-authoring.js +55 -17
- package/dist/verify-deployment.js +3 -3
- package/docs/AI-AUTHORING.md +104 -9
- package/docs/AWS.md +4 -4
- package/docs/BEST-PRACTICES.md +3 -2
- package/docs/CAPABILITIES.md +29 -16
- package/docs/CAPACITY.md +128 -31
- package/docs/CLOUDFLARE.md +1 -2
- package/docs/COMPLIANCE.md +6 -9
- package/docs/DEPLOYMENT-CHECKS.md +1 -1
- package/docs/EXTENSIONS.md +183 -11
- package/docs/FRAMEWORK.md +35 -19
- package/docs/FUNCTION-SECURITY.md +129 -32
- package/docs/INSTALL.md +0 -5
- package/docs/LOAD-TESTING.md +4 -4
- package/docs/MIDDLEWARE.md +27 -16
- package/docs/MONITORING.md +2 -19
- package/docs/NEXT-PHASE-PLAN.md +12 -4
- package/docs/NEXT-STEPS.md +259 -40
- package/docs/OBSERVABILITY.md +7 -16
- package/docs/OPEN-DECISIONS.md +212 -0
- package/docs/OPERATIONAL-PROOF.md +26 -30
- package/docs/OPERATIONS.md +23 -32
- package/docs/POLICIES.md +15 -4
- package/docs/PRERENDER.md +2 -2
- package/docs/PROJECT-DIRECTION.md +34 -9
- package/docs/README.md +13 -11
- package/docs/RELEASE-READINESS.md +49 -29
- package/docs/RELEASE-SECURITY.md +89 -3
- 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 +40 -0
- package/docs/SPECIFICATION.md +60 -26
- package/docs/SPIKE-AI-FRAMEWORK-BENCHMARK.md +287 -0
- package/docs/SPIKE-BUSINESS-SUITE.md +8 -0
- package/docs/SPIKE-CORE-LAYERING.md +337 -0
- package/docs/SPIKE-DEFAULT-TRUST-MODEL.md +209 -0
- package/docs/SPIKE-EXTENSION-MODEL.md +2 -2
- package/docs/SPIKE-EXTENSIONS.md +5 -0
- package/docs/SPIKE-LAMBDA-COMPILE.md +4 -2
- package/docs/SPIKE-MONOREPO.md +322 -0
- package/docs/STARTERS.md +1 -1
- package/docs/STATIC.md +94 -0
- package/docs/TOOLING.md +13 -9
- package/docs/TUNNELS.md +0 -3
- package/docs/TYPESCRIPT-AUTHORING.md +27 -12
- package/docs/TYPESCRIPT.md +25 -4
- package/docs/USABILITY-REVIEW.md +7 -1
- package/docs/VERCEL.md +4 -5
- package/docs/VERSION-ALIGNMENT.md +205 -0
- package/docs/YAML-GUIDE.md +0 -3
- package/docs/YAML-REFERENCE.md +16 -6
- package/docs/policies/cache.md +13 -0
- package/examples/assets/example.yaml +1 -1
- package/examples/aws/example.yaml +1 -1
- package/examples/cloudflare/example.yaml +1 -1
- package/examples/conditions/example.yaml +1 -1
- package/examples/cookbook/example.yaml +1 -1
- package/examples/cookbook/middleware/bucket.mjs +12 -2
- package/examples/cookbook/middleware/locale.mjs +7 -3
- package/examples/egress/example.yaml +1 -1
- package/examples/extensions/example.yaml +1 -1
- package/examples/prerender/README.md +2 -2
- package/examples/prerender/example.yaml +1 -1
- package/examples/provider-conformance/example.yaml +1 -1
- package/examples/vercel/example.yaml +1 -1
- package/llms-full.txt +594 -219
- package/llms.txt +23 -12
- package/package.json +10 -3
- package/packaging/claude-plugin/.claude-plugin/plugin.json +1 -1
- package/packaging/claude-plugin/skills/urlcode-authoring/SKILL.md +24 -8
- package/packaging/claude-plugin/skills/urlcode-operations/SKILL.md +10 -16
- package/recipes/authenticated-json-api/recipe.yaml +1 -1
- package/recipes/contact-form/recipe.yaml +1 -1
- package/recipes/cors-api/recipe.yaml +1 -1
- package/recipes/health-page/recipe.yaml +1 -1
- package/recipes/json-api/recipe.yaml +1 -1
- package/recipes/middleware/middleware/bucket.mjs +12 -2
- package/recipes/middleware/middleware/locale.mjs +7 -3
- package/recipes/middleware/recipe.yaml +1 -1
- package/recipes/protected-download/recipe.yaml +1 -1
- package/recipes/redirect/recipe.yaml +1 -1
- package/recipes/static-plus-api/recipe.yaml +1 -1
- package/recipes/typescript/README.md +2 -1
- package/recipes/typescript/recipe.yaml +1 -1
- package/recipes/webhook-receiver/README.md +5 -1
- package/recipes/webhook-receiver/recipe.yaml +2 -1
- package/recipes/webhook-receiver/urlcode.yaml +7 -0
- package/schemas/recipe.schema.json +4 -3
- package/schemas/urlcode.schema.json +14 -41
- package/skills/urlcode/SKILL.md +29 -8
- package/starters/default/AGENTS.md +18 -18
- 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 -40
- package/dist/sqlite-version.js +0 -6
- package/dist/types/link-api.d.ts +0 -30
- package/dist/types/link-cli.d.ts +0 -37
- 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 -8
- package/dist/types/sqlite-version.d.ts +0 -1
- package/docs/DYNAMIC-LINKS.md +0 -61
- package/docs/MANAGEMENT-SECURITY.md +0 -82
- package/docs/links/cli.md +0 -110
- package/docs/links/limits.md +0 -175
- package/docs/links/management-api.md +0 -80
- package/docs/links/pools.md +0 -75
- package/docs/links/setup.md +0 -135
- package/docs/yaml/links.md +0 -30
- package/examples/live-links/README.md +0 -11
- package/examples/live-links/example.yaml +0 -21
- package/examples/live-links/tests/requests.json +0 -6
- package/examples/live-links/urlcode.yaml +0 -16
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
# Spike: consolidating core, auth, admin, ui (and the two pending extractions) into one repo
|
|
2
|
+
|
|
3
|
+
Status: proposal, nothing implemented, no repo touched. Drafted at the requester's
|
|
4
|
+
explicit direction to produce a plan document only — see "What this is not"
|
|
5
|
+
below. Treat this the same way as the other `SPIKE-*.md` documents in this
|
|
6
|
+
directory: a recorded decision trail for the maintainer to accept, amend or
|
|
7
|
+
reject, not committed scope.
|
|
8
|
+
|
|
9
|
+
## What this is not
|
|
10
|
+
|
|
11
|
+
This is not a recommendation to touch any of `urlcode`, `urlcode-auth`,
|
|
12
|
+
`urlcode-admin` or `urlcode-ui` tonight. No git history has been merged, no
|
|
13
|
+
package has been moved, no CI has been reconfigured. Everything below is a
|
|
14
|
+
sequenced plan to review, not a changelog of what happened.
|
|
15
|
+
|
|
16
|
+
## The problem this is answering
|
|
17
|
+
|
|
18
|
+
Four repos (`urlcode`, `urlcode-auth`, `urlcode-admin`, `urlcode-ui`) already
|
|
19
|
+
coordinate tightly — `auth`/`admin`/`ui` each pin an exact core revision in
|
|
20
|
+
their own `peers.json`, and `docs/FRAMEWORK.md` describes them as one
|
|
21
|
+
composed product, not four independent ones. Concretely observed cost of that
|
|
22
|
+
coordination happening across four repos, from an evening spent reading all
|
|
23
|
+
four:
|
|
24
|
+
|
|
25
|
+
- **Observed and since fixed, which is the point rather than a counterpoint.**
|
|
26
|
+
When core landed trusted-by-default execution (`b3bde4e`), `urlcode-auth`
|
|
27
|
+
and `urlcode-admin` were both still pinning core at `50790d3a`
|
|
28
|
+
(`0.4.0-alpha.1`), predating it, and `urlcode-auth/SECURITY.md` still
|
|
29
|
+
carried a sentence ("sandboxed guest code") that assumed the old model.
|
|
30
|
+
Both have since been corrected — both repos now pin `d5e86017`, and that
|
|
31
|
+
sentence is gone. Nothing was ever broken in production by either.
|
|
32
|
+
The cost this plan is describing is not "drift goes unnoticed forever"; it
|
|
33
|
+
is that catching and fixing it took a manual pass across three separate
|
|
34
|
+
repositories, with nothing structural to catch it automatically — no
|
|
35
|
+
mechanism flags a downstream repo's prose or pin as stale when an upstream
|
|
36
|
+
contract changes underneath it. That pass has to be repeated by hand on
|
|
37
|
+
every future contract change, for every downstream repo, indefinitely.
|
|
38
|
+
Consolidation removes the class of work, not just this instance of it.
|
|
39
|
+
- Two more repos, planned in `docs/SPIKE-CORE-LAYERING.md` and originally
|
|
40
|
+
drafted here as "not yet created," turned out to already exist by the time
|
|
41
|
+
this doc was reviewed: `urlcode-dynamic-link` (7 commits, Phase 2 already
|
|
42
|
+
implemented, `v0.1.0-alpha.1` released) and `urlcode-middleware` (5 commits,
|
|
43
|
+
implemented, `v0.1.0-alpha.1` released), each with its own real commit
|
|
44
|
+
history, release workflow and open issues. That raises the
|
|
45
|
+
actively-coordinated repo count from four to six today, not hypothetically
|
|
46
|
+
— before this plan even accounts for `urlcode-template`, `urlcode-short`,
|
|
47
|
+
`urlcode-docs`, `urlcode-cloud` and `homebrew-urlcode`. It also means
|
|
48
|
+
"create them directly in the monorepo" (this doc's original framing) is no
|
|
49
|
+
longer available for these two — they now need the same history-preserving
|
|
50
|
+
migration as `auth`/`admin`/`ui`, covered in "Migration mechanics" below.
|
|
51
|
+
|
|
52
|
+
None of this is a defect in any one repo. It's the accumulating tax of
|
|
53
|
+
coordinating tightly-coupled, independently-versioned packages across
|
|
54
|
+
separate git histories, issue trackers and CI pipelines by hand.
|
|
55
|
+
|
|
56
|
+
## Scope: what moves, what doesn't
|
|
57
|
+
|
|
58
|
+
Decided (see conversation this spike is drafted from):
|
|
59
|
+
|
|
60
|
+
**In scope — six existing repos, all with real history, folded into one
|
|
61
|
+
repo as workspace packages:**
|
|
62
|
+
|
|
63
|
+
| Repo today | Becomes |
|
|
64
|
+
|---|---|
|
|
65
|
+
| `urlcode` (core) | `packages/core` (or repo root stays core-shaped, TBD in "Layout options" below) |
|
|
66
|
+
| `urlcode-auth` | `packages/auth` |
|
|
67
|
+
| `urlcode-admin` | `packages/admin` |
|
|
68
|
+
| `urlcode-ui` | `packages/ui` |
|
|
69
|
+
| `urlcode-dynamic-link` (real repo, `v0.1.0-alpha.1` released) | `packages/dynamic-link` |
|
|
70
|
+
| `urlcode-middleware` (real repo, `v0.1.0-alpha.1` released) | `packages/middleware` |
|
|
71
|
+
|
|
72
|
+
**Explicitly out of scope, each for a distinct, real reason — not just "left
|
|
73
|
+
for later":**
|
|
74
|
+
|
|
75
|
+
- **`homebrew-urlcode`** — cannot move. Homebrew tap conventions require a
|
|
76
|
+
repo literally named `homebrew-<name>`; this is an external platform
|
|
77
|
+
constraint, not a project choice.
|
|
78
|
+
- **`urlcode-docs`** — `AGENTS.md` is explicit that public documentation is
|
|
79
|
+
"authored there directly," deliberately separate from code, "no longer
|
|
80
|
+
generated from this repository." Folding it in would reverse a stated,
|
|
81
|
+
recent decision, not follow one.
|
|
82
|
+
- **`urlcode-cloud`** — a separately-lifecycled hosted product (private
|
|
83
|
+
repo); its release cadence and access model have no reason to match a
|
|
84
|
+
library monorepo's.
|
|
85
|
+
- **`urlcode-template` / `urlcode-short`** — these are example/starter
|
|
86
|
+
projects, not library packages. Mixing "things you `npm install`" with
|
|
87
|
+
"things you `git clone` as a starting point" in one workspace is a
|
|
88
|
+
different kind of repo than what this spike is solving for.
|
|
89
|
+
|
|
90
|
+
## Why six, and not four
|
|
91
|
+
|
|
92
|
+
`link` and `middleware` were extracted *out* of core specifically so core
|
|
93
|
+
stays "the smallest thing that is still a complete product on its own"
|
|
94
|
+
(`docs/SPIKE-CORE-LAYERING.md`). Both are now real, shipped repos: they
|
|
95
|
+
already paid the coordination cost this spike is trying to remove —
|
|
96
|
+
`urlcode-dynamic-link`'s and `urlcode-middleware`'s own `peers.json`-style
|
|
97
|
+
pins against core, their own CI, their own docs that can drift the same way
|
|
98
|
+
`urlcode-auth/SECURITY.md` already did. Folding them into this consolidation
|
|
99
|
+
alongside `auth`/`admin`/`ui` stops that from compounding further, rather
|
|
100
|
+
than leaving two more repos outside the fix.
|
|
101
|
+
|
|
102
|
+
## Layout: decided — option A
|
|
103
|
+
|
|
104
|
+
**A. Root repo is core, extensions live under `packages/`.**
|
|
105
|
+
```
|
|
106
|
+
urlcode/
|
|
107
|
+
src/ # core, unchanged in place
|
|
108
|
+
packages/
|
|
109
|
+
auth/
|
|
110
|
+
admin/
|
|
111
|
+
ui/
|
|
112
|
+
dynamic-link/
|
|
113
|
+
middleware/
|
|
114
|
+
```
|
|
115
|
+
Lowest-friction for core's own history (nothing moves), but makes "core" and
|
|
116
|
+
"the monorepo" the same name, which may read as core absorbing the
|
|
117
|
+
extensions rather than the extensions and core coexisting as peers — worth a
|
|
118
|
+
naming discussion given `AGENTS.md`'s "Core never imports them" independence
|
|
119
|
+
framing.
|
|
120
|
+
|
|
121
|
+
**B. Everything moves under `packages/`, including core — considered, not
|
|
122
|
+
chosen.** Would have been symmetric and avoided the naming overlap noted
|
|
123
|
+
above, at real cost: core's own history would need to move too, and every
|
|
124
|
+
external reference to `urlcode`'s current repo path (`docs/`, READMEs
|
|
125
|
+
elsewhere, the `@jimhoyd/urlcode` package's repository field, CI badges,
|
|
126
|
+
this evening's own `peer-camera`/`peer-eyes` citations) would need updating.
|
|
127
|
+
Decided against for exactly that reason.
|
|
128
|
+
|
|
129
|
+
**Decided: (A).** Core's repo and history stay exactly where they are; the
|
|
130
|
+
six packages move to it (five extensions plus core itself now living in the
|
|
131
|
+
same repo as a `packages/*` sibling). The one open item this still leaves,
|
|
132
|
+
worth a short naming discussion rather than blocking anything: "core" and
|
|
133
|
+
"the consolidated repo" now share a name, which could read as core absorbing
|
|
134
|
+
the extensions rather than the two coexisting as independent packages
|
|
135
|
+
(`AGENTS.md`'s "Core never imports them" framing still holds in code either
|
|
136
|
+
way — this is a naming-perception question, not a contract question).
|
|
137
|
+
|
|
138
|
+
## Migration mechanics, per repo
|
|
139
|
+
|
|
140
|
+
For each of `urlcode-auth`, `urlcode-admin`, `urlcode-ui`,
|
|
141
|
+
`urlcode-dynamic-link` and `urlcode-middleware` — all six now real repos
|
|
142
|
+
with real history:
|
|
143
|
+
|
|
144
|
+
0. **Drain open pull requests first — a hard precondition, not a courtesy.**
|
|
145
|
+
Before a repo is migrated, it must have zero open PRs (and no unmerged
|
|
146
|
+
release branch). A PR open against the source repo at the moment its code
|
|
147
|
+
moves is stranded: its branch targets a `main` that no longer receives
|
|
148
|
+
code, its diff is written against paths (`src/…`) that no longer exist at
|
|
149
|
+
that location, and re-creating it against the consolidated repo means
|
|
150
|
+
rebasing onto a different repository and a new path prefix
|
|
151
|
+
(`packages/<name>/src/…`) by hand. GitHub cannot retarget a PR across
|
|
152
|
+
repositories. So for each repo, in order: stop merging new work, merge or
|
|
153
|
+
close what is open, confirm `gh pr list`/the API reports none, then
|
|
154
|
+
migrate. Any PR that cannot be merged in time should be closed with its
|
|
155
|
+
branch preserved and re-opened against the consolidated repo afterwards —
|
|
156
|
+
a deliberate choice recorded on the PR, not an accident discovered later.
|
|
157
|
+
This is also the real reason to pick a quiet window for the migration
|
|
158
|
+
rather than a busy one: the cost of this step scales with how much is
|
|
159
|
+
in flight.
|
|
160
|
+
1. **Preserve history with `git subtree add` or `git filter-repo` +
|
|
161
|
+
merge**, not a fresh copy — so `git log`/`git blame` on
|
|
162
|
+
`packages/auth/src/auth.ts` still resolves to the real authorship history
|
|
163
|
+
from `urlcode-auth`, and so a future "actually, let's give this its own
|
|
164
|
+
repo back" is a clean `git filter-repo` extraction, not archaeology.
|
|
165
|
+
`git subtree` is the lower-risk default (reversible, no force-push
|
|
166
|
+
required on the source repos); `git filter-repo` gives cleaner resulting
|
|
167
|
+
history at the cost of being a one-way rewrite of the joining repo's
|
|
168
|
+
local copy (the original `urlcode-auth` GitHub repo is untouched either
|
|
169
|
+
way — this only rewrites what gets pulled in).
|
|
170
|
+
2. **npm workspace restructuring**: `package.json` at the monorepo root gets
|
|
171
|
+
`"workspaces": ["packages/*"]` (the same shape `peer-camera` already
|
|
172
|
+
uses); each `packages/<name>/package.json` keeps its own name/version,
|
|
173
|
+
independently publishable — this is what preserves "independently
|
|
174
|
+
versioned packages" as a property, not something this migration gives up.
|
|
175
|
+
**Decided: [Changesets](https://github.com/changesets/changesets) for the
|
|
176
|
+
release flow, not Nx or Turborepo.** A changeset is a small, bounded,
|
|
177
|
+
git-diffable markdown file (package name + semver bump + description) —
|
|
178
|
+
cheap and low-risk for an agent or a human to generate correctly, easy
|
|
179
|
+
for CI to verify mechanically ("does every touched package have one"),
|
|
180
|
+
and it's the deliberate checkpoint that stops local workspace-linked
|
|
181
|
+
development (testing against a sibling package's unreleased state, which
|
|
182
|
+
is now the default once auth/admin/ui/dynamic-link/middleware sit next to
|
|
183
|
+
core) from silently becoming a real release. Nx/Turborepo were considered
|
|
184
|
+
and set aside: both add a much larger, more inference-heavy configuration
|
|
185
|
+
surface (task graphs, remote caching semantics) that's a bigger, more
|
|
186
|
+
opaque thing to get wrong than this repo's six packages currently need —
|
|
187
|
+
plain `npm test -w packages/auth`-style workspace scoping already covers
|
|
188
|
+
what this size of repo actually requires. Revisit only if the package
|
|
189
|
+
count grows enough that rebuild/retest time becomes a real problem.
|
|
190
|
+
3. **`peers.json` becomes unnecessary for the six that moved** — a
|
|
191
|
+
workspace package can depend on a sibling workspace package directly
|
|
192
|
+
(`"@jimhoyd/urlcode": "workspace:*"` or npm's equivalent), which is
|
|
193
|
+
inherently always in sync, no separate pin file, no drift possible by
|
|
194
|
+
construction. `peers.json`-the-mechanism might still matter if any
|
|
195
|
+
*external* consumer needs a reviewed-revision pin story — worth deciding
|
|
196
|
+
explicitly rather than silently dropping the safeguard.
|
|
197
|
+
4. **CI consolidation**: one `verify.yml` (or similar) with
|
|
198
|
+
path-filtered jobs per package, replacing four separate workflow files.
|
|
199
|
+
`CODEOWNERS` can still express per-package ownership within one repo
|
|
200
|
+
(path-scoped rules), so "who reviews auth changes" doesn't have to
|
|
201
|
+
become "everyone reviews everything."
|
|
202
|
+
5. **Docs cross-references**: every `[EXTENSIONS.md](../urlcode/docs/...)`-
|
|
203
|
+
style cross-repo link in `auth`/`admin`/`ui`'s current docs becomes a
|
|
204
|
+
same-repo relative link once consolidated — this is a real cleanup
|
|
205
|
+
opportunity, not just migration overhead, since it directly targets the
|
|
206
|
+
"docs silently drifted apart" problem this spike opened with.
|
|
207
|
+
6. **Re-register npm Trusted Publishing per package.** All six repos'
|
|
208
|
+
release workflows publish via OIDC trusted publishing, no long-lived npm
|
|
209
|
+
token (`docs/SPIKE-CORE-LAYERING.md`'s governance section, confirmed by
|
|
210
|
+
`urlcode-dynamic-link`'s and `urlcode-middleware`'s own "Add
|
|
211
|
+
trusted-publishing release workflow" commits). That trust is registered
|
|
212
|
+
on npmjs.com per package, pinned to an exact GitHub repo + workflow
|
|
213
|
+
filename (+ optional environment) — it does not follow the code when the
|
|
214
|
+
repo path changes. Each of `@jimhoyd/urlcode-auth`, `-admin`, `-ui`,
|
|
215
|
+
`-dynamic-link`, `-middleware` needs its npmjs.com trusted-publisher entry
|
|
216
|
+
updated to the new repo and new workflow path *before* that package's
|
|
217
|
+
first release from the consolidated location, or the publish step fails
|
|
218
|
+
closed (correctly — not a security gap, just an ordering dependency this
|
|
219
|
+
plan needs to carry explicitly rather than discover at release time).
|
|
220
|
+
7. **Issue migration — decided: recreate open issues in the consolidated
|
|
221
|
+
repo, not leave-and-link.** GitHub doesn't move issues across repos
|
|
222
|
+
natively, so this means bulk-recreating each open issue at the new
|
|
223
|
+
location with a back-link to the original (closed with a pointer) rather
|
|
224
|
+
than leaving it where it is. Concrete scope as of this doc: `auth`,
|
|
225
|
+
`admin` and `ui`'s own open-issue counts weren't re-audited here, but
|
|
226
|
+
`urlcode-dynamic-link` and `urlcode-middleware` were, since they're the
|
|
227
|
+
two repos whose "does this even apply" status changed mid-conversation:
|
|
228
|
+
- `urlcode-dynamic-link`: 0 open issues — nothing to migrate.
|
|
229
|
+
- `urlcode-middleware`: 2 open issues to recreate —
|
|
230
|
+
[`#1`](https://github.com/jimhoyd-com/urlcode-middleware/issues/1)
|
|
231
|
+
("`sandbox: true` is not supported — needs its own QuickJS/WASM worker
|
|
232
|
+
pool") and
|
|
233
|
+
[`#3`](https://github.com/jimhoyd-com/urlcode-middleware/issues/3)
|
|
234
|
+
("Remove vendored core tarball once `@jimhoyd/urlcode` 0.4.0-alpha.2+ is
|
|
235
|
+
published to npm"). Both should move to the consolidated repo's tracker
|
|
236
|
+
when the merge actually happens, each closed in its original location
|
|
237
|
+
with a link to the new issue.
|
|
238
|
+
|
|
239
|
+
## What this preserves, unchanged
|
|
240
|
+
|
|
241
|
+
- **The trust/extension model itself.** `packages/auth` published from the
|
|
242
|
+
monorepo is exactly as separate a package, with exactly the same
|
|
243
|
+
`RuntimeExtension` contract, revision-pinning and operator-registration
|
|
244
|
+
requirements, as `urlcode-auth` published from its own repo today. This
|
|
245
|
+
spike changes where the source lives, not what the extension mechanism
|
|
246
|
+
guarantees.
|
|
247
|
+
- **Independent versioning and release cadence per package** — a monorepo
|
|
248
|
+
with workspaces is not "one version number for everything."
|
|
249
|
+
|
|
250
|
+
## What this gives up, honestly
|
|
251
|
+
|
|
252
|
+
- **Per-repo maturity gating.** `docs/SPIKE-CORE-LAYERING.md` records that
|
|
253
|
+
`auth`/`admin`/`ui` used a "`private: true` until reviewed" pattern before
|
|
254
|
+
their first public release, and that the two new repos are deliberately
|
|
255
|
+
*not* following that pattern ("published public from the start"). A
|
|
256
|
+
monorepo can't easily make one folder private and another public — the
|
|
257
|
+
repo-level visibility setting is all-or-nothing on GitHub. Once
|
|
258
|
+
consolidated, "private until reviewed" stops being available as a pattern
|
|
259
|
+
for whatever the next extension after `middleware`/`dynamic-link` turns
|
|
260
|
+
out to be, unless it's built in yet another separate private repo first
|
|
261
|
+
and merged in later — which reintroduces a version of the coordination
|
|
262
|
+
cost this spike is trying to remove, just for pre-release work instead of
|
|
263
|
+
ongoing maintenance.
|
|
264
|
+
- **"Fork just one piece" stops being a plain `git clone` — but scoped to a
|
|
265
|
+
narrow audience, not every auth user.** `SPIKE-AUTH.md` names forkability
|
|
266
|
+
as a deliberate design goal specifically for `auth`. It's important not to
|
|
267
|
+
overstate who this actually affects: a developer customizing auth's look
|
|
268
|
+
or copy (theme, relabeling, `extra.css`, a shadowed template) works
|
|
269
|
+
entirely inside *their own* project repo via the `ui` extension's layering
|
|
270
|
+
system (`ui/copy`, `ui/extra.css`, `ui/templates`) — they never clone or
|
|
271
|
+
fork `urlcode-auth` at all, install it from npm like any dependency, and
|
|
272
|
+
this migration changes nothing for them. The friction increase applies
|
|
273
|
+
only to the much narrower case of someone changing auth's actual *logic*
|
|
274
|
+
(a new sign-in method, different session semantics) — something the
|
|
275
|
+
layering system can't express because it's behavior, not presentation.
|
|
276
|
+
For that persona, forking just the auth package post-consolidation means a
|
|
277
|
+
`git filter-repo`-style history extraction instead of `git clone
|
|
278
|
+
jimhoyd-com/urlcode-auth` — solvable, but a real step up in friction, for
|
|
279
|
+
a small population, not the common path.
|
|
280
|
+
- **Blast radius of a bad CI run.** One consolidated CI means a
|
|
281
|
+
misconfigured job can, in principle, block merges across all six
|
|
282
|
+
packages at once, where today a broken `urlcode-ui` pipeline can't stop an
|
|
283
|
+
unrelated `urlcode-auth` merge. Path-filtered jobs mitigate this but don't
|
|
284
|
+
eliminate it the way full repo separation does.
|
|
285
|
+
|
|
286
|
+
## Sequencing, if this is accepted
|
|
287
|
+
|
|
288
|
+
1. Decide layout (A vs. B above) and confirm the out-of-scope list.
|
|
289
|
+
2. **Check open pull requests across all six repos before starting, and again
|
|
290
|
+
per repo immediately before its own migration** (mechanics #0). A repo with
|
|
291
|
+
anything open is not ready to move. Doing this as a survey first also sizes
|
|
292
|
+
the whole migration honestly: the number of in-flight PRs is the real
|
|
293
|
+
scheduling constraint, not the git mechanics.
|
|
294
|
+
3. Migrate `urlcode-ui` first (fewest inbound dependents — `auth`/`admin`
|
|
295
|
+
both depend on it, nothing depends on them), proving the subtree +
|
|
296
|
+
workspace mechanics on the lowest-risk package. Re-register its npm
|
|
297
|
+
trusted publisher (mechanics #6) before cutting its first release from
|
|
298
|
+
the new location — treat this as part of "done," not a follow-up.
|
|
299
|
+
4. Migrate `urlcode-auth`, then `urlcode-admin` — same re-registration step
|
|
300
|
+
each time.
|
|
301
|
+
5. Migrate `urlcode-dynamic-link`, then `urlcode-middleware` — same
|
|
302
|
+
subtree/filter-repo mechanics and trusted-publisher re-registration as
|
|
303
|
+
the other three, now that both are real repos with real history rather
|
|
304
|
+
than something created fresh in place. Recreate their open issues (see
|
|
305
|
+
"Migration mechanics" #7 above: 0 from `dynamic-link`, `#1` and `#3` from
|
|
306
|
+
`middleware`) in the consolidated tracker as part of each repo's
|
|
307
|
+
migration step, not as a separate pass.
|
|
308
|
+
6. Retire (archive, don't delete — GitHub redirects an archived repo's clone
|
|
309
|
+
URL) all six now-empty source repos, with their READMEs pointing at the
|
|
310
|
+
new location.
|
|
311
|
+
|
|
312
|
+
## Open questions for the maintainer, not answered here
|
|
313
|
+
|
|
314
|
+
- Does `peers.json`'s reviewed-pin discipline need an equivalent for any
|
|
315
|
+
external (non-workspace) consumer, or does workspace-linking fully replace
|
|
316
|
+
its purpose?
|
|
317
|
+
- `git subtree` vs. `git filter-repo` for history preservation — a real
|
|
318
|
+
tradeoff between migration safety and final history cleanliness, worth a
|
|
319
|
+
deliberate call rather than defaulting.
|
|
320
|
+
- The naming-perception question from "Layout: decided — option A" above
|
|
321
|
+
(core's repo and the consolidated repo sharing a name) — worth a short
|
|
322
|
+
discussion, not blocking.
|
package/docs/STARTERS.md
CHANGED
|
@@ -44,7 +44,7 @@ See [readiness](READINESS.md) and [security](FUNCTION-SECURITY.md).
|
|
|
44
44
|
|
|
45
45
|
To start an extended site instead, install the extension packages from npm
|
|
46
46
|
(`npm install @jimhoyd/urlcode-auth @jimhoyd/urlcode-admin`, published as
|
|
47
|
-
`0.1.0-alpha.
|
|
47
|
+
`0.1.0-alpha.x` prereleases) in the directory you run from and pass their names: `urlcode init ../my-site --with
|
|
48
48
|
auth,admin` writes the same starter under `my-site/app/`, merges each package's
|
|
49
49
|
routes and declarations into it, and generates one `host.mjs` and README beside
|
|
50
50
|
it. The contract each package fulfils is in [extensions](EXTENSIONS.md#scaffolding-with-init---with).
|
package/docs/STATIC.md
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Deploying to static hosting (S3 + CloudFront)
|
|
2
|
+
|
|
3
|
+
The `static` target is the bottom rung of the [capability
|
|
4
|
+
ladder](SPIKE-CORE-LAYERING.md#the-full-ladder-one-contract-one-vocabulary-per-level):
|
|
5
|
+
the same `urlcode.yaml` a self-hosted server or a serverless adapter runs, with
|
|
6
|
+
no server process at all. `urlcode build --target static` compiles a project
|
|
7
|
+
ahead of time into plain files and two small JSON manifests meant for an S3
|
|
8
|
+
bucket served through CloudFront — nothing here executes a request.
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
urlcode build --target static --project . --out dist --origin https://links.example
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Declarative routes only, and less than that
|
|
15
|
+
|
|
16
|
+
This target serves **redirects**, **declared responses** (`respond:`) and
|
|
17
|
+
static files (`page`, `static`, `download`) — no path parameters, no query
|
|
18
|
+
passthrough or mapping, GET/HEAD only, and no `enabled: false` or `expires`
|
|
19
|
+
(there is no server to answer a disabled or expired route with 404/410, so the
|
|
20
|
+
build refuses one instead of silently serving it forever). Everything else is
|
|
21
|
+
refused **at build time**, with the route pattern and the reason named:
|
|
22
|
+
|
|
23
|
+
| Handler / feature | Why it is refused |
|
|
24
|
+
| --- | --- |
|
|
25
|
+
| `function`, `middleware` | no server, so no dynamic or sandboxed execution |
|
|
26
|
+
| `extension`, `policies.extensions` | no server, so no operator extension registry |
|
|
27
|
+
| `proxy`, `signals` | no server, so no bounded or fire-and-forget egress |
|
|
28
|
+
| `conditional`, `match` (`conditions`) | no server, so no request-time condition matching |
|
|
29
|
+
| `parameters`, `request.body` | no server, so no request-time validation |
|
|
30
|
+
| `response.headers` | no server, so no per-request headers; set them as S3 object metadata or a CloudFront response headers policy instead |
|
|
31
|
+
| `env`, `secrets` (`bindings`) | no server, so no per-request binding resolution |
|
|
32
|
+
| every `policies.*` | no server, so no runtime policy enforcement |
|
|
33
|
+
| a redirect with a `{parameter}` in its path | S3's per-object redirect is keyed to one exact object, not a pattern |
|
|
34
|
+
| a redirect with `query.pass`/`query.map` | S3's per-object redirect cannot compute a target per request |
|
|
35
|
+
| a redirect with a `status` other than 301 | S3's per-object website redirect always answers 301 |
|
|
36
|
+
| a route declaring methods other than GET/HEAD | static hosting only ever answers GET/HEAD |
|
|
37
|
+
| a route with `enabled: false` or `expires` | no server to answer a disabled/expired route; remove the route instead |
|
|
38
|
+
|
|
39
|
+
Run `urlcode capabilities --target static` for the full catalog.
|
|
40
|
+
|
|
41
|
+
## What the build emits
|
|
42
|
+
|
|
43
|
+
`--out` (default `dist/static`) receives:
|
|
44
|
+
|
|
45
|
+
- `objects/<key>` — the actual files to upload, one per served route. A `page`
|
|
46
|
+
or `download` route becomes one file; a `static` mount becomes one file per
|
|
47
|
+
entry in its directory. Object keys match the S3 convention: the route
|
|
48
|
+
pattern with its leading `/` stripped, and `/` itself becomes `index.html`
|
|
49
|
+
(matching a bucket's own "Index document" setting). A redirect route also
|
|
50
|
+
gets a zero-byte placeholder object at its key, so it exists to carry
|
|
51
|
+
metadata.
|
|
52
|
+
- `objects.json` — `{ format, objects: [{ key, contentType, cacheControl?,
|
|
53
|
+
contentDisposition? }] }`. Plain file upload loses this metadata (S3 does not
|
|
54
|
+
reliably infer content type or cache headers from an extension-less key), so
|
|
55
|
+
a deploy step reads this file and sets each object's own `Content-Type`,
|
|
56
|
+
`Cache-Control` and `Content-Disposition` from it, for example with
|
|
57
|
+
`aws s3 cp --content-type ... --cache-control ...` per entry, or an
|
|
58
|
+
equivalent `aws s3 sync` post-processing step.
|
|
59
|
+
- `redirects.json` — `{ format, redirects: [{ key, location, status: 301 }] }`.
|
|
60
|
+
Each entry is applied as the S3 object's
|
|
61
|
+
[website redirect location](https://docs.aws.amazon.com/AmazonS3/latest/userguide/how-to-page-redirect.html)
|
|
62
|
+
metadata (`x-amz-website-redirect-location`), for example
|
|
63
|
+
`aws s3api put-object --website-redirect-location <location> --bucket ... --key <key>`.
|
|
64
|
+
S3 always answers a request for that key with a 301 to `location`
|
|
65
|
+
regardless of the object's own content — this is why a declared `status`
|
|
66
|
+
other than 301 is refused rather than silently downgraded.
|
|
67
|
+
- `manifest.json` — the same project-level semantic manifest every target
|
|
68
|
+
writes (see [tooling](TOOLING.md)).
|
|
69
|
+
|
|
70
|
+
None of these are edited by hand, and `dist/` belongs in `.gitignore`. This
|
|
71
|
+
target ships no runtime import (there is nothing to run), so there is no
|
|
72
|
+
`@jimhoyd/urlcode/static` entry point — the build output is the deployment.
|
|
73
|
+
|
|
74
|
+
## What this is not: GitHub Pages
|
|
75
|
+
|
|
76
|
+
**This target's claimed support is S3 + CloudFront, not GitHub Pages.**
|
|
77
|
+
GitHub Pages has no per-object redirect metadata and no server-side rewrite
|
|
78
|
+
layer, so a `redirect` route compiled the way this target compiles it (S3
|
|
79
|
+
object metadata) has no equivalent there — the only options are a
|
|
80
|
+
meta-refresh/JavaScript page (visibly a redirect, not an HTTP one: search
|
|
81
|
+
engines, curl and anything that does not run JavaScript sees the source page,
|
|
82
|
+
not a 3xx) or a static 404-page trick, both lower fidelity than what every
|
|
83
|
+
other target in this project does for the same route. Rather than claim a
|
|
84
|
+
portability promise this platform cannot keep, GitHub Pages is out of scope
|
|
85
|
+
for this target. If you need Pages, treat it as a distinct target with its own
|
|
86
|
+
explicit fidelity caveat, not a rename of this one.
|
|
87
|
+
|
|
88
|
+
## Verification status
|
|
89
|
+
|
|
90
|
+
This target has local build tests only (`test/static.test.ts`): the compiled
|
|
91
|
+
object layout, the redirect manifest, and every refusal above. **It has not
|
|
92
|
+
been deployed to S3 or fronted by CloudFront.** Bucket policy, CloudFront
|
|
93
|
+
caching behavior, TLS/domain setup and the exact `aws s3` invocations above are
|
|
94
|
+
unverified until a real deployment exercises them.
|
package/docs/TOOLING.md
CHANGED
|
@@ -76,7 +76,7 @@ network. Keys always appear in this order:
|
|
|
76
76
|
- `urlcode` (package version) and `schema` (`"1"`).
|
|
77
77
|
- `project`: entry file, route count, handlers used with counts, extensions
|
|
78
78
|
declared, policies in effect at the top level and the number of routes each
|
|
79
|
-
policy applies to, requested env and secret binding names,
|
|
79
|
+
policy applies to, requested env and secret binding names,
|
|
80
80
|
`site` keys, and `files` (include, function and middleware paths). With
|
|
81
81
|
`--host-file`, `host` counts the operator module's extensions and plugins
|
|
82
82
|
without activating them.
|
|
@@ -113,13 +113,17 @@ checks. Build output remains an explicit separate build API/CLI operation.
|
|
|
113
113
|
|
|
114
114
|
The package root also exports existing operator-invoked workflow APIs:
|
|
115
115
|
`buildCloudflare(project, options)` compiles and writes a Cloudflare artifact;
|
|
116
|
-
`
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
`
|
|
122
|
-
|
|
116
|
+
`buildStatic(project, options)` compiles redirects and static files into plain
|
|
117
|
+
objects and redirect metadata for S3 + CloudFront (see [static
|
|
118
|
+
hosting](STATIC.md)); `runProjectTests(project, options)` starts the local
|
|
119
|
+
runtime, executes request fixtures and closes it; `scaffoldProject(project,
|
|
120
|
+
{dryRun})` creates missing placeholders while preserving existing files;
|
|
121
|
+
`initProject(destination)` creates the standard starter; and
|
|
122
|
+
`addRedirect(project, destination, alias?)` updates project YAML under the
|
|
123
|
+
authoring lock. `CloudflareBuildOptions`, `CloudflareBuildReport`,
|
|
124
|
+
`StaticBuildOptions`, `StaticBuildReport`, `ProjectTestOptions`,
|
|
125
|
+
`ProjectTestResult`, `ScaffoldReport` and `ScaffoldUnresolved` describe these
|
|
126
|
+
existing operations.
|
|
123
127
|
|
|
124
128
|
These SDK functions have explicit write or execution effects and are available
|
|
125
129
|
to trusted callers only. Project tests use normal runtime activation, grants and
|
|
@@ -150,7 +154,7 @@ per-target support), the union of capabilities used, extension declarations
|
|
|
150
154
|
(version, configuration keys, mounts and protected routes), recipe provenance
|
|
151
155
|
(from a `recipe.yaml` beside the entry file when one exists), external
|
|
152
156
|
requirements (environment and secret names, proxy and signal origins,
|
|
153
|
-
extensions
|
|
157
|
+
extensions), the function and middleware
|
|
154
158
|
modules with the routes that use them, and per-target compatibility. Without
|
|
155
159
|
`--json` a short summary prints. The manifest is deterministic: the same
|
|
156
160
|
project produces the same bytes. `urlcode build` writes the same document as
|
package/docs/TUNNELS.md
CHANGED
|
@@ -65,9 +65,6 @@ scanners that find it within minutes.
|
|
|
65
65
|
- The runtime has **no authentication and no rate limiting**. Put access control
|
|
66
66
|
in the tunnel: ngrok's OAuth, OIDC or basic auth; Cloudflare Access; a
|
|
67
67
|
Tailscale ACL.
|
|
68
|
-
- **Never tunnel the link-management API.** It is a separate listener bound to
|
|
69
|
-
loopback on purpose, holding credentials that can rewrite live links. Tunnel
|
|
70
|
-
the application port only.
|
|
71
68
|
- Prefer `serve` with a fixed snapshot, a project containing no real secrets,
|
|
72
69
|
and a tunnel you shut down when finished.
|
|
73
70
|
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
# Build-time TypeScript guests
|
|
2
2
|
|
|
3
3
|
TypeScript guest authoring is a separate build step. Serving still accepts only
|
|
4
|
-
JavaScript ES modules
|
|
5
|
-
|
|
4
|
+
JavaScript ES modules. The build never imports application modules into Node
|
|
5
|
+
or runs application code, and it is `sandbox`-aware per route
|
|
6
|
+
(docs/SPIKE-DEFAULT-TRUST-MODEL.md): a route that declares `sandbox: true` is
|
|
7
|
+
transpiled under the sandbox's own module rules (relative imports only, no
|
|
8
|
+
dynamic import/bare specifiers, the module/size limits below), exactly as
|
|
9
|
+
before; a trusted (non-`sandbox: true`) route is transpiled without those
|
|
10
|
+
import/size restrictions, since it will run with full Node access — bare/npm
|
|
11
|
+
imports, dynamic `import()`, `import.meta` — once served. Both modes still
|
|
12
|
+
diagnose only syntax, not semantics, and neither imports application code into
|
|
13
|
+
the build process in a way that executes it.
|
|
6
14
|
|
|
7
15
|
```sh
|
|
8
16
|
urlcode recipes add typescript --out ./hello-source
|
|
@@ -29,17 +37,24 @@ The trusted pinned TypeScript compiler transpiles ES2022/ES modules with fixed
|
|
|
29
37
|
settings. It does not read `tsconfig.json`, package scripts, plugins, compiler
|
|
30
38
|
transformers, dependency packages, Node declarations or ambient environment
|
|
31
39
|
files. It diagnoses syntax errors, but does **not** perform semantic type
|
|
32
|
-
checking
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
outside the project are refused. No import extension inference occurs.
|
|
40
|
+
checking, in either mode. CommonJS import/export syntax and imports outside
|
|
41
|
+
the project are always refused. Type-only relative imports are erased without
|
|
42
|
+
reading their targets.
|
|
36
43
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
44
|
+
For a `sandbox: true` route, bare/npm imports (including static type-only
|
|
45
|
+
imports), dynamic runtime imports, `import.meta` and import attributes are
|
|
46
|
+
refused, and the source graph is limited to 128 modules, 1 MiB per source and
|
|
47
|
+
4 MiB aggregate; the emitted graph must also pass the runtime's own sandboxed
|
|
48
|
+
source parser and byte limits before publication. For a trusted route, none of
|
|
49
|
+
that applies: bare/npm specifiers, dynamic `import()`, `import.meta` and
|
|
50
|
+
import attributes pass through unchanged (resolved by Node at serve time, not
|
|
51
|
+
by this build), and there is no module-count or size ceiling. In both modes,
|
|
52
|
+
no import extension inference occurs — relative imports of project modules
|
|
53
|
+
still need an explicit `.ts`/`.js`/`.mjs` extension to be rewritten and
|
|
54
|
+
followed. This does not execute the modules or replace normal route, policy,
|
|
55
|
+
binding or sandbox validation at activation. Unsupported host/browser APIs
|
|
56
|
+
remain unavailable in QuickJS, even if TypeScript accepts their names. Run
|
|
57
|
+
`urlcode validate --local` and project tests on the output.
|
|
43
58
|
|
|
44
59
|
Includes are flattened into a duplicate-checked entry document. Only referenced
|
|
45
60
|
modules, page/download assets, static trees, site favicon/llms files and the
|
package/docs/TYPESCRIPT.md
CHANGED
|
@@ -16,7 +16,7 @@ beside them, and `dist/BUILD-MANIFEST.json` with a SHA-256 per emitted file.
|
|
|
16
16
|
| Import | Runtime | Declarations |
|
|
17
17
|
|---|---|---|
|
|
18
18
|
| `urlcode` | `dist/index.js` | `dist/types/index.d.ts` |
|
|
19
|
-
| `@jimhoyd/urlcode/plugins`, `@jimhoyd/urlcode/policies`, `@jimhoyd/urlcode/observability`, `@jimhoyd/urlcode/compliance`, `@jimhoyd/urlcode/prerender`, `@jimhoyd/urlcode/extensions` | `dist/<name>.js` | `dist/types/<name>.d.ts` |
|
|
19
|
+
| `@jimhoyd/urlcode/plugins`, `@jimhoyd/urlcode/policies`, `@jimhoyd/urlcode/observability`, `@jimhoyd/urlcode/compliance`, `@jimhoyd/urlcode/prerender`, `@jimhoyd/urlcode/extensions`, `@jimhoyd/urlcode/sandbox` | `dist/<name>.js` | `dist/types/<name>.d.ts` |
|
|
20
20
|
| `@jimhoyd/urlcode/aws`, `@jimhoyd/urlcode/vercel`, `@jimhoyd/urlcode/cloudflare` | `dist/<name>.js` | `dist/types/<name>.d.ts` |
|
|
21
21
|
| `@jimhoyd/urlcode/schema` | `schemas/urlcode.schema.json` | — |
|
|
22
22
|
|
|
@@ -36,9 +36,7 @@ release cannot ship a declaration that does not resolve.
|
|
|
36
36
|
## The main exported types
|
|
37
37
|
|
|
38
38
|
- `urlcode`: `Runtime`, `RuntimeOptions`, `RuntimeRequest`, `RequestTrace`,
|
|
39
|
-
`Server`, `ServerOptions`, `HostPlugin`, `Observer`, `TestPlan
|
|
40
|
-
`LinkRow`, `LinkStoreOptions`, `LinkReader`, `LinkStoreBinding`, `LinkApi`,
|
|
41
|
-
`LinkApiOptions`, `LinkEvent`, `LinkObserverOptions`.
|
|
39
|
+
`Server`, `ServerOptions`, `HostPlugin`, `Observer`, `TestPlan`.
|
|
42
40
|
- `@jimhoyd/urlcode/plugins`: `Plugin`, `PluginRuntime`, `PolicyRequest`,
|
|
43
41
|
`HandlerResult`, `HeaderPair`, `TargetName`. See [plugins](PLUGINS.md).
|
|
44
42
|
- `@jimhoyd/urlcode/policies`: `PolicyModule`, `PolicyRegistry`, `PolicyRequest`,
|
|
@@ -54,6 +52,29 @@ release cannot ship a declaration that does not resolve.
|
|
|
54
52
|
- `@jimhoyd/urlcode/aws`, `@jimhoyd/urlcode/vercel`, `@jimhoyd/urlcode/cloudflare`: `LambdaEvent`,
|
|
55
53
|
`LambdaHandler`, `LambdaHandlerOptions`; `VercelHandler`,
|
|
56
54
|
`VercelHandlerOptions`; `Artifact`, `WorkerRoute`, `Validators`.
|
|
55
|
+
- `@jimhoyd/urlcode/sandbox`: `SandboxPool`, `SandboxEntry`, `SandboxTarget`,
|
|
56
|
+
`SandboxInvocation`, `SandboxPoolOptions`, `functionFile`. The public
|
|
57
|
+
sandboxed-execution primitive: the same QuickJS/worker-thread engine that
|
|
58
|
+
already backs a `sandbox: true` `function`/`middleware` route
|
|
59
|
+
(`FunctionPool`, internally), generalized to an explicit list of
|
|
60
|
+
`{source, export}` entries instead of anything route/YAML-shaped, for an
|
|
61
|
+
extension package that needs to run a project-supplied hook through real
|
|
62
|
+
isolation when the project's own config declares `sandbox: true` on it
|
|
63
|
+
(see [EXTENSIONS.md](EXTENSIONS.md#project-level-lifecycle-hooks) and
|
|
64
|
+
[FUNCTION-SECURITY.md](FUNCTION-SECURITY.md)). There is no trusted-mode
|
|
65
|
+
export here: a hook that does not declare `sandbox: true` is ordinary
|
|
66
|
+
project code the extension `import()`s directly via
|
|
67
|
+
`ExtensionActivation.root`, no primitive required.
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
import { SandboxPool, functionFile } from '@jimhoyd/urlcode/sandbox';
|
|
71
|
+
|
|
72
|
+
const source = await functionFile(root, hookConfig.source); // root: ExtensionActivation.root
|
|
73
|
+
const entries = [{ source, export: hookConfig.export ?? 'default' }];
|
|
74
|
+
const pool = await new SandboxPool(entries, { root, workers: 1 }).start();
|
|
75
|
+
const result = await pool.execute({ entry: entries[0] }, request, context, undefined);
|
|
76
|
+
await pool.close();
|
|
77
|
+
```
|
|
57
78
|
|
|
58
79
|
```ts
|
|
59
80
|
import { startServer, type ServerOptions, type Observer } from '@jimhoyd/urlcode';
|
package/docs/USABILITY-REVIEW.md
CHANGED
|
@@ -6,6 +6,12 @@ benchmark. Its purpose is to name the friction a first-time user or an AI
|
|
|
6
6
|
agent meets, compare it with what competing tools do, and rank the changes
|
|
7
7
|
that would matter most. It does not change any contract by itself.
|
|
8
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
|
+
|
|
9
15
|
## 1. Who the competitors are, by rung
|
|
10
16
|
|
|
11
17
|
URLCode is not one product; it competes with a different tool on each rung
|
|
@@ -81,7 +87,7 @@ whether it touches a contract.
|
|
|
81
87
|
"add accounts" row except the revision pin existed because they were
|
|
82
88
|
unpublished. This was a decision, not code; the repositories already had
|
|
83
89
|
the release checks. No contract change. Done 2026-09-18: all three are on
|
|
84
|
-
npm as `0.1.0-alpha.
|
|
90
|
+
npm as `0.1.0-alpha.x` (review still pending, issue 58).
|
|
85
91
|
2. **`urlcode init --with auth,admin,ui`.** Delegate to each installed
|
|
86
92
|
package's existing scaffold; write one host file and one README. No contract
|
|
87
93
|
change; a CLI addition in core that calls into optional peers.
|
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
|