TypeScript Customization
Keep generated domain code replaceable and keep Node infrastructure out of browser packages. Runtime policy, provider resources, identity, and sinks are assembled by the server; the browser can request intent but cannot approve it.
Node context factory
type TrustedRequest = Readonly<{
tenantId: bigint;
actorId: string;
permissions: ReadonlySet<string>;
}>;
async function requestContext(
infrastructure: Infrastructure,
trusted: TrustedRequest,
): Promise<UserContext> {
const context = new UserContext();
// Register generated metadata, explicit Node provider, policy and audit sinks.
// Install only authenticated server values.
return context;
}
Use the released @teaql/teaql APIs and documented export paths. Do not expose provider construction
to route bodies and do not pass a service as a second execution argument.
Here is the concrete composition shape from Runtime Customization Assist:
interface AppAuditEvent { readonly [key: string]: unknown }
type AppAuditSink = (event: AppAuditEvent) => void | Promise<void>;
interface RequestPolicy {
readonly authorize: (operation: string, entity: string) => boolean;
}
interface RuntimeDataService extends TeaQLDataService {
setAuditSink(sink: AppAuditSink): this;
}
type RuntimeComposition = Readonly<{
dataService: RuntimeDataService;
requestPolicy: RequestPolicy;
appAuditSink: AppAuditSink;
trustedTenant: string;
}>;
function requestContext(composition: RuntimeComposition): UserContext {
if (!composition.trustedTenant.trim()) throw new Error("trusted tenant is required");
composition.dataService.setAuditSink(composition.appAuditSink);
return new UserContext()
.insertResource("dataService", composition.dataService)
.insertResource("requestPolicy", composition.requestPolicy)
.insertResource("appAuditSink", composition.appAuditSink)
.insertResource("trustedTenant", composition.trustedTenant);
}
async function readiness(context: UserContext): Promise<void> {
context.requireResource<RequestPolicy>("requestPolicy");
context.requireResource<AppAuditSink>("appAuditSink");
if (!context.requireResource<string>("trustedTenant").trim()) {
throw new Error("trusted tenant is required");
}
await context.ensureSchema();
}
The current core prepareQuery() does not automatically consume the stored
requestPolicy. The server adapter/custom data service must enforce
authorize(...) and tenant scope before provider execution; retaining the
resource alone is not authorization.
Dynamic input adapter
Parse unknown input, validate it, then map an allowlisted discriminated union to generated methods:
function applyFilter(req: CustomerOrderRequest, filter: Filter): CustomerOrderRequest {
switch (`${filter.field}:${filter.operator}`) {
case "order_number:contains":
return req.withOrderNumberContaining(requireString(filter.value));
default:
throw new InvalidQuery("Unsupported field or operator");
}
}
Reject unknown/deep paths, excessive IN/page size, reversed or wrongly typed ranges, negative offset and forbidden sort. Derive rows, count, totals, and facets from one normalized filter.
Policy, behavior, and audit
Entity-local invariants belong in generated behavior/checker contracts. Tenant scope,
permissions, approved purpose and platform limits belong in context request policy.
Every mutation uses auditAs; the immutable runtime mutation event and separately
registered masked application event are distinct evidence paths.
Provider exports
Import SQL providers only through documented Node subpaths. Keep the root/browser export free of native modules and Node built-ins. Qualify each provider with real persistence, reconnect, optimistic locking, aggregates, pagination, and relation Top-N.
Federal customization
Customize endpoint discovery, authentication transport, retry and tracing around the
generated federal protocol client without replacing the protocol with arbitrary HTTP.
The backend rebuilds trusted UserContext, validates the query allowlist, and decides
whether requested purpose is approved.
Packaging
Pin @teaql/teaql and preserve the lock file. Test Node ESM/CJS behavior and a clean browser bundle
against the documented exports. SQL providers stay behind explicit subpaths; do not replace the
published dependency with a local cache or preview tarball in production documentation.
If a change requires editing generated Q, Request, Entity, or expression source, fix KSML, generator, or runtime and regenerate.
Runtime infrastructure
TypeScript exports LocalCache and the process singleton localCache with
optional TTL. Local Lock is intentionally not applicable to the single-threaded
local runtime, and TeaQL currently claims no TypeScript Remote Cache, Remote
Lock, Nacos, or Consul adapter. Node applications may integrate external SDKs,
but that is application infrastructure rather than portable TeaQL runtime
support. See
Cache, Lock, and Cloud Runtime Infrastructure.
Worked Example: Local Cache
import { localCache } from "@teaql/teaql";
async function currencyView(
context: UserContext,
trustedTenant: string,
currencyCode: string,
): Promise<Readonly<CurrencyView>> {
const key = `${trustedTenant}:currency:${currencyCode}`;
const cached = localCache.get<Readonly<CurrencyView>>(key);
if (cached !== undefined) return cached;
const loaded = Object.freeze(
await loadAuthorizedCurrencyView(context, currencyCode),
);
localCache.put(key, loaded, 300);
return loaded;
}
function invalidateCurrency(trustedTenant: string, currencyCode: string): void {
localCache.remove(`${trustedTenant}:currency:${currencyCode}`);
}
Invalidate after the audited save succeeds. Node replicas do not share this cache. For distributed idempotency or coordination, use an application-owned database/Redis service with an owner-safe atomic contract; TeaQL TypeScript does not currently supply a Remote Lock abstraction.