@gadicc/loom
v1.16.1
Published
Deno-first shared helpers for extracting common Next.js app code.
Readme
loom
Deno-first shared helpers for extracting common code from multiple Next.js apps.
This repo currently still lives in the gadicc directory, but the package name
is @gadicc/loom and the intended CLI name is loom.
Project status and support
Loom is primarily developed for the maintainer's own commercial and open source applications. Others are welcome to use it, but it is provided without warranty and without any commitment to support, compatibility, maintenance periods, or a particular roadmap. Releases follow semantic versioning so independently pinned consumer apps can upgrade deliberately.
Security issues should be reported privately as described in
SECURITY.md, not through a public issue.
Current feature set:
- shared DB stack scaffolding for Neon/Drizzle/Valibot/PGlite consumers
- shared jobs framework primitives for app-local
src/jobsimplementations - shared Next.js App Router jobs adapters for list pages, run pages, and route handlers
- auditable, scope-agnostic API-key authorization with an app-owned Better Auth adapter, managed audit schema, explicit jobs integration, optional MUI UI, Vercel country/IP snapshots, and bounded retention
- a Loom-managed env runner for non-Next commands that need the same
.env*semantics as Next - Deno tests with BDD-style assertions
- npm package generation via
dnt
License
Copyright © 2026 Gadi Cohen. Licensed under the Apache License 2.0.
The license includes warranty disclaimers and a limitation of liability; consumers remain responsible for determining whether Loom is appropriate for their applications.
Loom does not redistribute third-party agent skills. Its metadata-only catalog records reviewed upstream revisions and license evidence; consumer projects choose and cache the corresponding content directly from upstream.
Public npm package
Loom's public distribution is @gadicc/loom on npm:
pnpm add @gadicc/loom
# or: npm install @gadicc/loomConsumer projects do not need registry mappings or package-read tokens.
Maintainer bootstrap, trusted-publisher configuration, release verification, and
consumer migration are documented in
docs/npm-publishing.md.
CLI
loom now includes a small bootstrap CLI and feature-scaffolding entrypoint.
loom initadds@gadicc/loomtopackage.json, creates or updatesloom.json, refreshes the managed Loom bootstrap block inAGENTS.md, and ensures the managed.agents/skills/*links point at the installed@gadicc/loompackage, activates skills exposed by installed npm packages, and configures version-matched Next.js guidance when supported by the installed Next version.loom addlists the shared features that the CLI can activate and shows a short description for each one.loom add <feature>activates one shared feature inloom.jsonand applies that feature's scaffolding without overwriting app-local wrappers.loom add jobsactivates the shared jobs feature inloom.jsonand scaffolds any missing local jobs/admin route files without overwriting existing app-local wrappers, while also enabling theloom-jobsskill link.loom add dbactivates the shared DB feature inloom.json, adds the managed DB dependencies and scripts topackage.json, rewrites the standardneon.ts/neonFull.ts/drizzle.config.ts/ PGlite test harness files into Loom-managed thin wrappers, scaffolds the app-local fixture script entrypoint, and enables theloom-dbskill link.loom add filesactivates the shared files schema contract after the DB feature is active. It materializes the Loom-managed Drizzle schema blueprint into an app-local committed schema file, records the blueprint version and hash inloom.json, scaffolds app-local repository/storage/access/route files, and leaves migration generation to the app's normaldb:generatereview flow.loom files r2 setupcreates or reuses private Cloudflare R2 buckets for Production and Preview, records the explicit Cloudflare account and storage policy inloom.json, replaces only the untouched generated storage stub, configures canonicalFILES_S3_*values in Vercel, and configures local MinIO in.env.localby default. Use--local-provider r2for deliberately shared development storage or--local-provider noneto leave local storage unwired. Existing custom storage modules and credentials are preserved.loom files r2 checkverifies the selected Cloudflare account, bucket existence and private access, exact non-secret Vercel values in Production and Preview, the absence of Files credentials in Vercel Development, and the configured local MinIO endpoint/bucket. The same checks run throughloom check --network.loom add api-keysrequires active auth and DB features, materializes the versioned audit schema, scaffolds fail-safe policy/verifier/repository/action boundaries and user/admin pages, and links theloom-api-keysskill. It does not authorize existing routes, change jobs, or generate migrations.@gadicc/loom/apiKeysprovides typed scope registries, exact-match policy dispatch, credential extraction, fingerprints, authorization/audit contracts, country normalization, and bounded audit retention.@gadicc/loom/next/apiKeysadds App Router authorization, session-or-key composition, completion auditing, and explicit Vercel request context.@gadicc/loom/mui/apiKeysprovides optional lifecycle and audit UI.loom api-keys integrate jobs --resource <name> --action <name>records an explicit additive jobs authorization integration after the app has protected its HTTP jobs route with fresh session/admin policy. Direct local runners are unchanged and remain API-key-free.loom api-keys retention enablerecords the default 180-day/5,000-request retention policy. The app must transactionally implement the retention repository and schedule the shared runner; Loom does not silently create or modify jobs. Useretention disablefor an app-owned legal-hold workflow.@gadicc/loom/filesprovides the shared files runtime primitives: SHA-256 hashing, digest verification, metadata detection, repository/storage adapter contracts, in-memory test adapters, and headless upload helpers.@gadicc/loom/files/s3adapts an app-provided AWS SDK v3 S3 client to the shared storage contract without making AWS SDK packages Loom dependencies.@gadicc/loom/next/filesprovides App Router route-handler factories for uploads, metadata lookup, and downloads over app-provided persistence and storage adapters.loom add pwaactivates the shared Serwist PWA feature, adds the Serwist dependencies and build script, scaffolds configurator-modeserwist.config.mjsplus worker/provider files, records whether the update prompt should use the MUI adapter or browser fallback, adds a 60-second update check interval for MUI installs, and ignores generated service-worker output.loom add skillsadopts matching reviewed external skills into the gitignored.loom/skillscache, records exact revisions and content hashes inloom-skills.lock.json, and activates package-provided skills.loom statussummarizes the installed Loom package,loom.jsonstate, active features, local-dev sync state, and managed/recommended skill links.loom env -- <command> [...args]loads the consumer project.env*files with Next-compatible@next/envsemantics, preserves existing shell env precedence, then runs the command from the consumer project root. Use it for non-Next commands such as direct job runners, Drizzle, ad hoc DB inspection, and app scripts that need env. Next commands such asnext devandnext buildalready load.env*themselves.loom skills checkvalidates the project lock, external cache, and activation links.loom skills updateupdates adopted external skills to Loom's current reviewed immutable revisions.loom skills disable <name>opts out of automatic activation;enableclears the opt-out andsyncreactivates the skill.loom db fixturesruns the Loom-managed DB fixture dump entrypoint for the active DB feature, using the configuredscripts/dump-test-tables.tsand scripts tsconfig.loom db schema-genruns the Loom-managedvs_tocodeworkflow for the active DB feature and then refreshes the Valibot barrel file. Pass extra generator flags after--, for exampleloom db schema-gen -- --watch.loom db schemas syncre-materializes active Loom-managed schema blueprints into app-local schema files only when the file still matches the last recorded Loom hash. It refuses to overwrite local edits, updates simple schema barrel exports, and never generates Drizzle migrations.loom db neon setupprovisions a Vercel-managed Neon database for the active DB feature, connects it to the linked Vercel project for Production and Preview only, skips local env pulling, and records the provider policy inloom.json.loom db neon checkruns only the DB/Neon network checks: Vercel CLI availability, project linking, Neon resource connection, Production/Preview DB env vars, and the absence of DB env vars in Development.loom db release setup-githubcreates/updates the GitHubproductionenvironment for staged Vercel releases: it storesVERCEL_ORG_IDandVERCEL_PROJECT_ID, creates a Vercel token scoped to the linked Vercel team, and saves that token as the GitHubVERCEL_TOKENenvironment secret.loom dev local /path/to/loomenables local unpublished Loom development in a pnpm app by adding a managedfile:marker override for@gadicc/loomtopnpm-workspace.yaml, then running the local-dev sync flow when needed.loom dev syncrebuilds the local Loom package when source files are newer than the generated npm output, then refreshes the consuming app withpnpm update --no-save @gadicc/loom@file:/...andpnpm installwhen the installed file dependency is stale. Other Loom commands also run this sync automatically when the managed local-dev override is active; pass--no-syncto skip it. Each sync records the Loom source commit in the managedpnpm-workspace.yamlblock and gitignored.loom/dev-state.jsonwhen the local package path points at a Loom git checkout.loom dev publishedremoves that managed local-dev override so the app can return to the published package version. When a prior sync recorded a Loom source commit, this command verifies that the commit is reachable fromorigin/mainand either included in a semver release tag or package-equivalent to the latest semver release because only non-package files changed after that tag. It then switches the app to the exact published@gadicc/loomversion that covers the sync. If no recorded commit exists, the command preserves the older compatibility behavior and switches back to the existing published dependency with an explicit warning. The command also notices stale lockfiles that still resolve Loom to a local package even after the workspace override has been removed.loom checkverifiespackage.json,loom.json, the managedAGENTS.mdblock and.agents/skills/*links, and then runs feature-specific checks for active entries inloom.json, including jobs-wrapper validation plus DB runtime, script, Valibot-barrel wiring, and the production migration release flow for active DB installs, managed local schema validation for active files installs, and Serwist package, worker, provider, update-check interval, MUI-confirm, and build-script wiring for active PWA installs. The DB release-flow check expects Vercel automatic production Git deployments to be disabled invercel.jsonand a GitHub Actions workflow to pull Vercel production settings, build, upload a staged prebuilt production deployment, rundb:migratethroughvercel env run -e production, then promote the staged deployment.loom check --networkalso verifies the required GitHub release secret/variables when that workflow exists. When.github/workflows/*.yml|yamlstill contains Loom's obsolete GitHub Packages registry/auth setup,loom checkrecommends removing it. The command also activates newly discovered npm package skills with a notice, warns when the managed local dev override is active, reports inlinedotenv/@next/envimports or DB/jobs scripts that bypassloom env --, and warns whenpnpm-lock.yamlstill resolves Loom to local source. If.loom/dev-state.jsonrecords a synced Loom commit,loom checkalso explains whether that commit is unpublished or published in a newer exact Loom version than the app is pinned to. Pass--strict-skillsto turn missing skill recommendations into check failures. Pass--productionto make local Loom dev overrides, local lockfile resolutions, unresolved synced Loom commits, and DB migration release-flow gaps release-blocking failures. Pass--networkto also run registered feature network checks after the local project checks.
The current CLI scope is project bootstrap, validation, and feature-specific
scaffolding. Extracted feature hooks are added behind the same init, add,
feature subcommand, and check flows.
Consumer app release workflow
Consumer apps may stay in local Loom development mode during normal feature work:
loom dev local /home/dragon/www/npm/loom
loom dev sync
loom checkBefore pushing or releasing a consumer app, switch back to the published Loom package and run the production gate:
loom dev published
pnpm update --no-save @gadicc/loom@<version-from-loom-output>
pnpm install
loom check --productionPublishing Loom is only required when the app was synced against
package-relevant Loom changes that are not already covered by a published semver
tag. Publishing a new Loom version does not move other consumer apps; each app
remains on its committed package.json and lockfile until that app deliberately
upgrades.
New app DB bootstrap
For a new Vercel app that needs the shared DB stack:
loom init
loom add db
vercel link
loom db neon setup
loom check --networkloom db neon setup runs the Vercel Marketplace install through the Vercel CLI
using Production and Preview environments only. It intentionally avoids
Development and uses --no-env-pull so local .env* files are not populated by
the integration. Pass --name <resource-name> to override the default
<project>-db resource name, --region <region> to select a supported Neon
Marketplace region such as sin1, or --plan <plan-id> if a new Marketplace
installation requires a specific plan id. When Neon is already installed for the
team, Loom omits --plan and preserves that installation's current plan. Neon
Auth is explicitly disabled by default because Loom's auth feature uses Better
Auth; pass --neon-auth only when the app intentionally wants Neon Auth too.
Network checks are line-based for now so they remain readable in CI logs:
[1/6] Checking Vercel CLI is available ......................... ok ✓
Using vercel.
[2/6] Checking Project is linked to Vercel ..................... ok ✓
Linked to Vercel project prj_...When stdout is an interactive terminal, Loom uses an updating progress line with
a progress bar and prints only problem checks at the end. This is disabled
automatically in CI and can be disabled explicitly with --no-progress. Use
--no-color to disable colored status output.
See docs/network-checks.md for the hook design and
future expansion notes.
AI skills
The package now publishes Codex skill folders under skills/.
skills/loomprovides global loom guidance for consumer apps, centered onloom.json, feature activation, shared-vs-local ownership boundaries, and a small set of shared consumer-repo policies such as verification, documentation/comments, and commit-message expectations. Consumer repos keepAGENTS.mdsmall and use a managed Loom block there to route work into this shared skill.skills/upgrade-depsprovides a staged Deno and JavaScript/TypeScript dependency-upgrade workflow covering compatibility groups, rollback safety, failure isolation, major migrations, reviewable commits, and exact-tree final verification.skills/loom-dbprovides DB-feature guidance for Neon/Drizzle wiring, managed local schema blueprints, Valibot generation, and the shared PGlite harness.skills/loom-jobsprovides jobs-feature guidance for app-local catalogs, admin route wrappers, and shared helper wiring.skills/loom-api-keysprovides API-key adoption, authorization, audit, Vercel geolocation, UI mapping, jobs integration, and retention guidance.
Projects do not need to copy these files manually. loom init, loom add db,
loom add jobs, and loom check manage a small project-local AGENTS.md Loom
block plus .agents/skills/* symlinks that point to the installed
node_modules/@gadicc/loom/skills/* folders. The global loom and
upgrade-deps skills are linked for every initialized project; feature skills
are linked when their matching feature is active.
Third-party skills are not packaged with Loom. Loom carries a metadata-only
catalog containing source URLs, immutable commits, content hashes, and license
evidence. loom add skills installs applicable reviewed skills directly into a
consumer project's gitignored .loom/skills cache and records the selection in
the committed loom-skills.lock.json. Missing or unclear licenses require the
explicit --accept-unknown-license <name> flag. loom skills migrate replaces
legacy symlinks from older Loom releases, while preserving custom local skill
directories.
Loom also scans installed npm packages for a root SKILL.md, skills/*, or
.agents/skills/*. New package-provided skills are activated automatically by
loom init, loom skills sync, and ordinary loom check, with a notice that
includes loom skills disable <name> as the opt-out path.
For local Loom package development, prefer loom dev local and the managed
file: marker flow over pnpm link. loom dev sync owns the rebuild and pnpm
refresh step for that local package. When it refreshes the installed package at
the start of another Loom command, rerun that original command so the current
process uses the refreshed CLI code. This keeps the committed dependency on the
published version while still resolving peer dependencies correctly during local
development. Treat this as a workaround for current pnpm behavior rather than a
permanent preference; if pnpm's local-link behavior changes, this flow can be
simplified or removed later.
The managed local-dev block also records the Loom source commit that was synced
when the local package path points at a Loom git checkout, and the same state is
persisted in gitignored .loom/dev-state.json so Loom can still diagnose stale
local lockfiles after the workspace override has been removed.
loom dev published uses that record as a release gate: clean commits that have
reached origin/main and are either included in a semver release tag or have no
package-relevant changes after the latest semver tag can be switched to the
exact published version, while dirty or unreleased package changes block the
switch. Existing apps with older local-dev blocks that lack this metadata can
still switch back to their current published dependency, but new syncs should be
used before relying on the release gate.
Loom's external catalog currently recommends these defaults:
ai-sdkfind-skillsfrontend-designvercel-composition-patternsvercel-react-best-practices
It adds these dependency-matched recommendations:
better-auth-best-practicesfor projects that depend on Better Auth packagespnpmfor projects that use pnpmvitestfor projects that use Vitest
Next.js is handled separately because current Next releases ship documentation
matching the installed framework version under node_modules/next/dist/docs.
Loom adds a managed AGENTS.md pointer to those docs. Older Next releases can
use the official npx @next/codemod@latest agents-md fallback; migration
removes the retired next-best-practices symlink.
See docs/project-skills.md for lock/cache semantics,
package discovery, opt-outs, collision behavior, and migration details.
Jobs framework
The shared jobs code is designed so each app keeps its own jobs in
src/jobs/*.ts, while the shared package owns the common controller, catalog,
execution, formatting, and direct-run helpers. Keep metadata in a
dependency-free module and use createLazyJob in the executable catalog so
constructing an HTTP or direct-run catalog does not initialize every job's
transitive dependencies.
Example app-local job:
import type { Job } from "@gadicc/loom/jobs";
import { testMeta as meta } from "./indexWithMetaOnly";
export { meta };
export const handler: Job["handler"] = async (job) => {
job.log("hello");
};Example dependency-free metadata and lazy catalog entry:
// indexWithMetaOnly.ts
import type { Job } from "@gadicc/loom/jobs";
export const testMeta: Job["meta"] = {
id: "test",
desc: "Example job",
};
// index.ts
import { createLazyJob } from "@gadicc/loom/jobs";
import { testMeta } from "./indexWithMetaOnly";
export const test = createLazyJob(testMeta, () => import("./test"));For the strongest Next.js deployment boundary, mount jobs in static route files
with createJobRouteHandler({ job }). A dynamic [id] route is one serverless
function and therefore still traces every implementation reachable from its
catalog, even when createLazyJob prevents unrelated modules from evaluating at
runtime.
Loom can manage those file-based entrypoints from the Jobs config in
loom.json:
{
"staticRoutes": {
"handlerFactoryExport": "createHttpJobRouteHandler",
"jobs": {
"example": "@/jobs/example",
"short-example": {
"module": "@/jobs/short-example",
"maxDuration": 300
}
},
"maxDuration": 1800,
"routeDirectory": "src/app/admin/jobs"
}
}Run loom add jobs after changing the map. Loom creates, updates, and removes
only wrappers bearing its managed marker; an existing unmanaged route is
preserved and reported. loom check verifies the generated set and its direct
literal maxDuration exports. The default is Vercel's current 30-minute maximum
of 1800, while staticRoutes.maxDuration and each job's optional
maxDuration override keep shorter limits explicit and checkable.
