tce_packagestce_packagesEvery published version is one row of tce_packages (App\Models\TcePackage): namespace,
name, kind (element | library | sdk), description, major/minor/patch,
width/height, extra (free-form JSON; extra.tags holds the tags). Its code zip and icon are attachments (relation: code /
relation: icon), reusing the site's polymorphic attachment mechanism unchanged. Declared
dependencies are rows of tce_package_dependencies, pointing at the dependency's row, with
optional required_major/minor/patch constraints.
| Kind | Zip contents | Produced by |
|---|---|---|
element, library |
dist/index.js, dist/index.global.js, copied public/ files |
npm run pack (release/<name>-<version>.zip), or npm run publish, which zips dist/ itself |
sdk |
package.json + src/**/*.ts |
npm run publish |
One top-level folder in the zip is flattened on extraction, so zipping dist/ as a folder or
zipping its contents both work.
| Path | Who | Declares dependencies? | One request? |
|---|---|---|---|
POST /api/v1/tce-packages |
script / CI | yes | yes |
MCP tool upload_tce_package |
an agent, tce:import-v3-packages |
yes | yes |
| Nova admin | a human | by hand | no — row, then two attachments |
All three hand their input to one service (TcePackageService::upload), so the rules below hold
for every path.
POST /api/v1/tce-packages (auth:sanctum, multipart/form-data)
| Field | Required | Notes |
|---|---|---|
namespace |
yes | The publisher (Naming). |
name |
yes | The package name; elements and libraries must match the tag-name pattern. |
zip |
yes, or zip_upload_id |
See What Gets Uploaded. |
zip_upload_id |
instead of zip |
A zip sent in chunks, by its upload id. |
version |
no | major.minor.patch. Omitted: the previous patch + 1. |
kind |
no | element | library | sdk. Omitted: the previous kind, else element. |
description |
no | Omitted: carried over. |
icon |
no | SVG or PNG. Omitted: carried over. |
width, height |
no | Default window size. Omitted: carried over. |
dependencies[i][namespace], dependencies[i][name] |
no | Declared dependencies. Omitted: carried over; an empty list clears. |
tags[i] |
no | Tags. Omitted: carried over; an empty list clears. |
A zip above the server's request limit (PHP post_max_size) goes up in chunks first:
POST /api/v1/tce-packages/chunks (auth:sanctum, multipart/form-data)
upload_id client-chosen, [A-Za-z0-9_-], at most 100 characters
chunk_index 0 … total_chunks - 1
total_chunks at most 50
chunk the bytes, at most 10 MB
Each chunk answers {"status": "chunk_uploaded"}; the one that completes the set assembles the
zip and answers {"status": "completed", "size": …}. POST /api/v1/tce-packages then names it
by zip_upload_id instead of sending zip; the session is deleted once the upload ran. Chunks
are stored through the same chunk assembly the Nova upload uses, scoped to the token's user — an
upload id never reaches another user's chunks.
The response is the published version as the registry lists it. A README generated by the
generator contains the exact curl call for its project.
Versions are removed by hand only. The newest version of a package always stays when older ones
are cleared, and so does a version that a dependency requires or a page embeds as name@x.y.z.
A dependency on a removed version moves to the newest remaining version of that package.
The site's MCP server exposes upload_tce_package with the same fields (zip_base64,
icon_base64 + icon_file_name instead of files) and list_tce_packages to inspect the
registry. php artisan tce:import-v3-packages uploads through the same service, reading
everything from each package's manifest in a staging folder inside the app
container.
The Nova TcePackage resource creates the row by hand (namespace, name, version, kind,
description, size, dependencies), then the Upload Attachment action attaches the code zip
(relation: code) and the icon (relation: icon) in two steps. Nothing auto-increments the
version here.
Version resolution is keyed on (namespace, name):
version given, no prior upload for the pair → 0.0.1.version given, prior upload exists → same major.minor, patch + 1.version given → must be strictly greater than the pair's latest; the same or an older number
is rejected. A bad release is fixed by a new patch, never by re-uploading its number.Dependencies are declared by identifier and resolved to the dependency's latest uploaded version at upload time. A dependency that was never uploaded rejects the whole upload, naming every missing identifier — so packages are published dependencies first, and a published version can never be missing part of its dependency list. The declared list is what the embed pages and the package manager use to include libraries before the packages that need them.
A package's tags are topic words the package manager offers as filters (game, tool,
effect, …): lower-case words joined by hyphens, de-duplicated and lower-cased on upload; any
other tag rejects the upload. Publishing tooling takes them from the manifest's
informal.categories, minus the categories every package carries (custom-element, tce and
the kind). They are stored in extra.tags and listed as tags in the registry.
GET /api/public/tce-packages (no auth)
Every element and library version that has a code attachment, grouped by namespace/name
(two publishers using the same name never share a group). SDK packages are build-time only and
are not listed.
{
"version": 1,
"packages": {
"tobidot/tce-notepad": [
{
"namespace": "tobidot", "name": "tce-notepad", "kind": "element",
"description": "A simple text editor for .txt and .md files.",
"major": 0, "minor": 2, "patch": 0,
"root": "https://…/storage/tce-packages/<uuid>",
"content": "https://…/storage/tce-packages/<uuid>/index.js",
"icon": "https://…/storage/media/<uuid>/icon.svg",
"width": 480, "height": 360, "tags": ["tool", "files"],
"extra": { "tags": ["tool", "files"] }, "created_at": "2026-10-17T18:45:35.000000Z",
"dependencies": [{ "identifier": { "namespace": "tobidot", "name": "dll-tce-file-system" } }]
}
]
}
}
A version entry is TcePackageResource; the client-side schema is registry_response_schema
in @tce-v3/common, and every consumer reads the endpoint through
fetch_json(url, registry_response_schema). The two change together.
| Route | Serves |
|---|---|
GET /tce-elements/{namespace}/{name} |
the latest version's IIFE, prefixed with its content root (Integration) |
GET /tce-elements/{namespace}/{name}@{x}.{y}.{z} |
exactly that version, immutable |
GET /tce-elements/{name} |
the same, in the site's default namespace |
GET /tce/{name} |
fullscreen page of the latest element of that name, default namespace |
GET /tce-packages/{id}/view |
fullscreen page of one specific upload |
GET /, GET /v3 |
the desktop (with / without the site header) |
GET /api/public/tce-packages |
the registry response above |
POST /api/v1/tce-packages, POST /api/v1/tce-packages/chunks |
publishing (REST Upload, Large Uploads) |
GET /documentation/{path} |
this documentation as raw markdown, for tce-documentation |
The embed route answers 404 for a version without an IIFE build and for SDK packages.
npm run publish, or npm run pack + REST), which
reads the manifest; never zip by hand what the tooling zips./tce/{name}, not an {id}, unless you want to pin a version.TcePackageResource and registry_response_schema together.