Every runtime package — element or library — implements TcePackageInterface from
@tce-v3/common:
interface TcePackageInterface {
functions: Array<string | number | symbol>; // names reachable through load()
version(): TcePackageVersion;
init(): Promise<TcePackageInterface>; // one-time boot, must stay cheap
load(name: string): ((...args: unknown[]) => unknown) | null;
contracts(): TceFunctionContract[]; // per-function semantic versions
}
TcePackage is the base class: load(name) returns the bound method only if name is listed
in functions, and null otherwise — it never throws. contracts() returns contract_list,
one entry per exposed function (Interfacing: Per-Function Contracts).
A library is one class extending TcePackage; it has no DOM.
An element's package object is a TceElementPackage(tag_name, element_class, version): its
init() registers the custom element tag, guarded with customElements.get(), and nothing
else. An element whose boot has real work (asserting library contracts, registering a file
handler) subclasses it and overrides init(), memoizing the boot promise so repeated calls are
harmless:
class TceNotepadPackage extends TceElementPackage {
private boot: Promise<TcePackageInterface> | null = null;
public async init(): Promise<TcePackageInterface> {
this.boot ??= NotepadFileSystemConnection.connect().then((file_system) => {
file_system?.register_file_handler(new TceNotepadSettings().file_handler);
return super.init();
});
return this.boot;
}
}
TcePackageMeta carries two optional declarations an element package makes about itself in
bind_self(): slots, the desktop slots it is made for (decoration, effects,
task-bar-icons), and attributes, the attributes it accepts
(Declaration: Declared Attributes). They are metadata like
width and icon: read with with_binding/resolve_binding, never by loading the package. A
desktop lists every bound element whose slots name one of its switchable slots as a component
the user can switch on; a properties dialog builds its form from attributes. An ordinary
application declares neither.
{ namespace, name } — see Naming & Identifiers. The loader keys its bindings by
namespace/name; both parts must pass the identifier pattern or bind() throws.
A TcePackageVersion is { major, minor, patch } — non-negative integers, never a string.
parse_version('1.2.3') and serialize_version(version) convert at the boundary (manifests,
registry responses). A package's version is stated once, in its entry script, and published to
the registry with every upload (Publishing: Versioning on Upload).
TceRoot.package_loader() returns the page's single TcePackageLoader, stored on
window.tobidot.tce_package_loader and created on first use. Every package script, however it
got onto the page, binds into this one loader — which is what lets several embedded elements
share one running copy of a library.
bind(identifier, version, fetch, meta) announces that a version of a package can be
resolved, with a fetch() thunk producing its instance. It validates every argument (they come
from another package's script), fires TcePackageLoaderPackageBoundEvent, and returns the
existing descriptor — with meta merged in — if that exact version is already bound.
TceRoot.bind_self(identifier, instance, meta) is the only way a package binds itself
(Lifecycle: Bootstrapping).has_binding(identifier, constraints?) answers synchronously whether a matching version
is bound.with_binding(identifier, constraints?, timeout_ms?) resolves the best matching
descriptor, waiting for a bind() from a script that is still loading. It rejects after the
timeout (15 s by default) with a message naming the identifier and asking for its <script>
tag — it never hangs forever.load(identifier, constraints?) resolves the best bound version, loads its declared
dependencies first, runs fetch() + init() exactly once (memoized on the descriptor), fires
TcePackageLoaderPackageLoadedEvent, and returns the instance.TceRoot.fast_load(identifier, version?) is with_binding + load + init: what an
activation (opening a window) calls.Use with_binding/has_binding when you only need metadata (meta.icon, meta.width, …) —
they never fetch or initialize anything (Lifecycle: Icons).
A package's meta.dependencies is a list of { identifier, version? } requirements, declared in
its entry script and in its manifest, and published with it. load() loads every dependency,
recursively, before fetching the package. A package that calls a library additionally asserts
each function it uses at boot (Interfacing);
declaring the dependency is what makes the registry and the embed pages include the library
before the package.
A constraint is a partial version { major?, minor?, patch? }. major, when given, must match
exactly; minor and patch, when given, are floors; an omitted field is unconstrained. One
implementation, is_version_matching_constraints, serves both whole-package resolution and
per-function contract checks:
is_version_matching_constraints({ major: 1, minor: 3, patch: 0 }, { major: 1, minor: 2 }) // true
is_version_matching_constraints({ major: 2, minor: 0, patch: 0 }, { major: 1 }) // false
is_version_matching_constraints({ major: 1, minor: 2, patch: 1 }, { major: 1, minor: 2, patch: 5 }) // false
Among matching versions the loader picks the newest. This is the only version-matching behavior the framework implements.
Per Naming: Namespace, every layer carries a real namespace value:
TceRoot, TcePackageLoader, resolve_asset_url) takes the namespace as part of
the identifier it is given and never constructs one.namespace as a column, groups by namespace/name, and keys version
resolution on the pair.config/tce.php, read only by the short-form routes, the
uploader's default and the generator's default.meta.dependencies and assert the functions you
call at boot.with_binding/has_binding for metadata and fast_load for activation.{} matches anything.init() so it can be called more than once.{ major, minor, patch } — minor/patch
are floors.bind() the same identifier+version twice expecting a fresh fetch() — the first
binding wins and only meta is merged.TceRoot.package_loader().