Spensara Spensara Knowledge Base & Documentation Repository

Repository Docs & Knowledge Base

Consolidated Spensara documentation repository (722 Markdown & architecture documents in ../repository)
Built Word Documents (.docx): Spensara Overview (.docx) Capability Assessment (.docx) Brink POS Ingestion Analysis (.docx) The Borg Ingestion Architecture (.docx)

Agent Tooling (71)

Automation workflows, orchestration references, and developer tooling guides.
agent-tooling/.agents/skills/aspireify/references/typescript-authoring.md 8,616 bytes · Updated Oct 23, 2026 10:44 AM EDT
Raw File

TypeScript AppHost Authoring

Patterns for editing apphost.ts. Never edit .aspire/modules/ — that directory is generated and any changes will be clobbered. Edit only apphost.ts.

Look up unfamiliar API: aspire docs api search <query> --language typescript
then aspire docs api get <id>.

⛔ Hard Refusal: .aspire/modules/ Is Off-Limits

If a user asks to edit any file in .aspire/modules/ (for example .aspire/modules/postgres.module.ts, .aspire/modules/redis.module.ts, .aspire/modules/api.module.ts), refuse the edit and redirect to apphost.ts. This is non-negotiable:

  • .aspire/modules/ is regenerated by the TS AppHost from apphost.ts plus the installed

Aspire packages on every build, every aspire add, every aspire start.

  • Hand edits inside .aspire/modules/ are clobbered without warning — they may survive

one run and disappear on the next.

  • The TS AppHost considers .aspire/modules/ an internal artifact, not user code.

Refusal template

When asked to edit anything inside .aspire/modules/, respond with the structure below:

I won't edit files in .aspire/modules/ — that directory is generated by the TypeScript
AppHost and any edits get clobbered on the next build / `aspire add` / `aspire start`.

The right place to make this change is apphost.ts. Here is the equivalent edit:

  // apphost.ts
  builder.addPostgres('pg', { /* the options you wanted to set */ });

If you wanted to add a new integration, run `aspire add <package>` and Aspire will
regenerate `.aspire/modules/` for you.

Apply this rule regardless of how the request is phrased — "just open it", "make a quick tweak", "change one line in .aspire/modules/", "I'll undo it later." All of these get the same refusal-plus-redirect.

❌ NEVER✅ ALWAYS
Edit .aspire/modules/postgres.module.tsEdit apphost.ts addPostgres('pg', {...})
Edit .aspire/modules/api.module.tsEdit apphost.ts addProject('api', '...')
Comment out lines in any .aspire/modules/*.ts fileRemove / guard the declaration in apphost.ts
Run codegen against .aspire/modules/ and patch the outputAdd the integration via aspire add <package>

Skeleton

ts
import { createBuilder } from '@aspire/hosting';

const builder = await createBuilder();

const pg = builder.addPostgres('pg').addDatabase('appdb');
const cache = builder.addRedis('cache');

const api = await builder.addProject('api', '../Api/Api.csproj')
    .withReference(pg)
    .withReference(cache)
    .waitFor(pg)
    .withExternalHttpEndpoints();

await builder.addNextJsApp('web', '../web')
    .withReference(api)
    .waitFor(api)
    .withBrowserLogs();

await builder.build().run();

Unified withEnvironment API

A single method handles every value kind — string, ReferenceExpression, EndpointReference, parameter builder, connection-string resource builder, or any IExpressionValue:

ts
const apiKey = builder.addParameter('apiKey', { secret: true });
const cache = builder.addRedis('cache');
const db = builder.addPostgres('pg').addDatabase('appdb');

const api = await builder.addProject('api', '../Api/Api.csproj');
await api
    .withEnvironment('SERVICE_URL', cache.primaryEndpoint)   // endpoint
    .withEnvironment('API_KEY', apiKey)                       // parameter
    .withEnvironment('DB', db);                               // connection string
❌ DO NOT USE the per-kind helpers — they are deprecated:
withEnvironmentEndpoint, withEnvironmentParameter, withEnvironmentConnectionString,
withEnvironmentExpression, withEnvironmentFromOutput, withEnvironmentFromKeyVaultSecret.

✅ Use unified withEnvironment(name, value) instead. Any agent suggesting
the per-kind helpers is wrong for current Aspire.

Endpoint Property Expressions

Endpoints expose url, host, and port properties usable inside expressions:

ts
const api = await builder.addProject('api', '../Api/Api.csproj');
const httpEndpoint = api.getEndpoint('http');

await builder.addNodeApp('worker', 'worker.js')
    .withEnvironment('API_BASE', httpEndpoint.url)
    .withEnvironment('API_HOST', httpEndpoint.host)
    .withEnvironment('API_PORT', httpEndpoint.port);

Endpoint Update Behavior

withEndpoint('name', cb) updates an existing endpoint rather than throwing. Use the excludeReferenceEndpoint flag to keep admin endpoints out of withReference():

ts
await builder.addProject('api', '../Api/Api.csproj')
    .withEndpoint('admin', e => { e.excludeReferenceEndpoint = true; });

JavaScript / TypeScript Frontends

HelperUse For
addNextJsApp(name, projectPath)Next.js (auto standalone publish; set output: 'standalone' in next.config.js)
addViteApp(name, projectPath)Vite (dev server)
addNodeApp(name, scriptPath)Plain Node service

Package managers: Bun, Yarn, pnpm are first-class alongside npm.

Publish hooks (mirror C# PublishAs*):

ts
// Vite SPA → static website with optional API proxy
await builder.addViteApp('web', '../web')
    .withReference(api)
    .publishAsStaticWebsite({ apiPath: '/api', apiTarget: api });

// Pre-bundled Node server (TanStack Start, SvelteKit)
await builder.addViteApp('web', '../web')
    .publishAsNodeServer({ entryPoint: '.output/server/index.mjs', outputPath: '.output' });

// package-script SSR (Remix, Astro, full Nitro Next.js)
await builder.addViteApp('web', '../web')
    .publishAsPackageScript({ scriptName: 'start' });

Docker Compose Hooks

ts
await builder.addContainer('netshoot', 'nicolaka/netshoot')
    .publishAsDockerComposeService((resource, service) => {
        service.privileged = true;
    });

Dockerfile Builder APIs (experimental)

ts
// Diagnostic ASPIREDOCKERFILEBUILDER001 — experimental warning
await builder.addDockerfileBuilder('myimage')
    .withDockerfileBuilder(b => b
        .from('node:20-alpine')
        .workdir('/app')
        .copy('package*.json', './')
        .run('npm ci')
        .copy('.', '.')
        .cmd(['node', 'server.js']));

YARP Routing

ts
const api = await builder.addProject('api', '../Api/Api.csproj');
const yarp = await builder.addYarp('gateway')
    .addRoute('/api/{**catch-all}', api.getEndpoint('http'))
    .addCatchAllRoute(web.getEndpoint('http'));

Compute Environments

ts
const aca = builder.addAzureContainerAppEnvironment('aca');
const aks = builder.addAzureKubernetesEnvironment('aks');

await builder.addProject('api', '../Api/Api.csproj')
    .withComputeEnvironment(aca);

ACA custom domain configuration is exposed in TS:

ts
await api.withComputeEnvironment(aca, e => {
    e.customDomains = [{ name: 'api.example.com', certificate: cert }];
});

Predefined Value Catalogs

The [AspireValue] attribute and predefined catalogs let you reference well-known values:

ts
import { FoundryModels } from '@aspire/hosting-azure';

await builder.addAzureFoundry('foundry')
    .addModel('chat', FoundryModels.OpenAI.Gpt41Mini);

Other TS Additions

APIPurpose
withAdminDeploymentScriptSubnet(...)Now exported in TS
configureEnvFile(...)Generate .env files for compute environments
Image push optionsCustom registry, tag, push behavior
Endpoint mutation callbacksModify endpoints after declaration
Builder pipelineCustom pipeline steps from TS

Browser Logs

ts
await builder.addViteApp('frontend', '../frontend')
    .withBrowserLogs();

Diagnostic IDs

DiagnosticWhat
ASPIREEXPORT013Build-time duplicate exported capability ID detection
ASPIREJAVASCRIPT001Renamed from ASPIREEXTENSION001 (13.3 breaking)
ASPIREDOCKERFILEBUILDER001Experimental warning for WithDockerfileBuilder / AddDockerfileBuilder
ASPIREDURABLETASK001Durable Task Scheduler experimental APIs

Hard Rules

RuleWhy
Never edit .aspire/modules/Generated; edits are clobbered. Edit only apphost.ts
Use unified withEnvironment(name, value)Per-kind helpers are deprecated
Use addNextJsApp / addViteApp over hand-rolled DockerfilesFirst-class lifecycle + publish helpers
Use publishAs* for JS publish — never raw Dockerfile when a helper fitsMaintained, tested, and works with aspire deploy
package.json engines.node no longer drives Node image selectionPin via publish helper options instead