The service runtime for NetScript: turn an oRPC router into a running Hono service with health probes, OpenAPI, Scalar docs, request tracing, and graceful shutdown — in one call.
A production service is never just its handlers. It needs CORS, request logging, an OpenAPI
document, live/ready health probes an orchestrator can poll, tracing on every request, and a
shutdown path that drains in-flight work. This package materializes all of it from the oRPC router
you already have: defineService() stands up the full runtime in one call, and createService()
composes the same stages explicitly when a service needs a bespoke stack.
Authentication and authorization ship as an opt-in subpath with provider-agnostic ports, so a service that needs guarding adds it without dragging auth machinery into every service that does not.
- One-call preset —
defineService(router, options)wires CORS, logging, OpenAPI JSON, Scalar docs, RPC, service info, and health, then starts the listener and returns aRunningServicehandle withaddrand an idempotentstop(). - Fluent builder —
createService(router, config)composes the same stages step by step, thenserve()starts a listener orbuild()returns a mountable app. - Health probes —
withHealth()adds/health,/health/live, and/health/ready;healthChecks.database,.kv,.service, and.customcover common dependencies. - Graceful lifecycle —
onShutdown()registers LIFO teardown hooks;serve()drains in-flight requests, installsSIGINT/SIGTERMhandlers, and accepts an externalAbortSignal. - One app-wide budget —
createRuntimeHost()invokes existing service, worker, queue, and database drains in deterministic phase order and returns one aggregate report. - Tracing on every request — the builder registers tracing middleware as the outermost layer on every service, so each request gets a server span with W3C propagation and the service name recorded, with no per-service wiring.
- Opt-in auth —
./authships authentication and authorization ports plus static-credential, trusted-header, and scope-authorizer factories, kept off the import graph until used.
flowchart LR
R["oRPC router"] --> D["defineService()<br/>or createService()"]
D --> M["Middleware stack<br/>tracing · CORS · logging · auth"]
M --> E["Endpoints<br/>/rpc · /api · OpenAPI · Scalar docs"]
M --> H["Health<br/>/health · /health/live · /health/ready"]
D --> G["Graceful shutdown<br/>drain · LIFO hooks · signals"]
deno add jsr:@netscript/service@<version>Pin <version> to match your installed CLI; bare jsr:@netscript/* specifiers do not resolve on
the pre-release line. Generated NetScript service entrypoints already import the pinned entry.
import { defineService } from '@netscript/service';
import { router } from './router.ts';
// One call materializes the Hono + oRPC runtime and starts the listener:
// CORS, request logging, OpenAPI JSON, Scalar docs, RPC, service info, and health.
const service = await defineService(router, {
name: 'users',
version: '1.0.0',
port: 3001,
openapi: { title: 'Users API', description: 'User management service' },
});
// RunningService handle: addr + idempotent graceful stop() for tests and supervisors.
console.log(`listening on :${service.addr.port}`);
await service.stop();Compose every in-process runtime behind one bounded shutdown handle without replacing its own drain:
import { createRuntimeHost } from '@netscript/service';
const host = createRuntimeHost({
timeoutMs: 15_000,
drains: [
{ id: 'api', phase: 'service', drain: () => service.stop() },
{ id: 'jobs', phase: 'workers', drain: () => workers.stop('shutdown') },
{ id: 'messages', phase: 'queue', drain: () => queue.stop() },
{ id: 'primary-db', phase: 'database', drain: () => database.disconnect() },
],
});
const report = await host.shutdown('SIGTERM');The host drains service → workers → queue → database, preserving registration order inside each
phase. Rejected drains are reported and do not prevent later phases. If the one shared budget
expires, the active outcome is timed-out, remaining drains are skipped, and shutdown() returns
without waiting indefinitely for the slow resource.
Reach for createService() when a service needs explicit, stage-by-stage composition — and pull in
./auth to guard it:
import { createService } from '@netscript/service';
import {
createScopeAuthorizer,
createStaticCredentialAuthenticator,
} from '@netscript/service/auth';
const authenticator = createStaticCredentialAuthenticator({
credentials: {
'local-token': { subject: 'service:orders', scopes: ['orders:read'], roles: ['service'] },
},
});
const authorizer = createScopeAuthorizer({
rules: [{
match: (request) => request.path.startsWith('/api/orders'),
requireScopes: ['orders:read'],
}],
});
const running = await createService(router, { name: 'orders', version: '1.0.0' })
.withAuthn({ authenticator })
.withAuthz({ authorizer })
.withRPC()
.withHealth()
.serve({ port: 3001 });
await running.stop();The defineService() preset accepts the same ports through its auth option, so generated
entrypoints opt in without leaving the one-call surface:
import { defineService } from '@netscript/service';
import { createScopeAuthorizer, createTrustedHeaderAuthenticator } from '@netscript/service/auth';
const running = await defineService(router, {
name: 'orders',
port: 3001,
auth: {
authn: {
authenticator: createTrustedHeaderAuthenticator({
subjectHeader: 'x-authenticated-user',
scopesHeader: 'x-authenticated-scopes',
}),
},
authz: {
authorizer: createScopeAuthorizer({
rules: [{
match: (request) => request.path.startsWith('/api/orders'),
requireScopes: ['orders:read'],
}],
}),
},
},
});
await running.stop();| Entry | What it gives you |
|---|---|
. |
defineService, createService, createRuntimeHost, healthChecks, HEALTH_STATUS, handler factories (createRPCHandler, createOpenAPISpec, createScalarDocs, …) |
./auth |
createStaticCredentialAuthenticator, createTrustedHeaderAuthenticator, createScopeAuthorizer, and the authn/authz port types |
The always-current symbol list is
deno doc jsr:@netscript/service@<version>.
- Services & SDK — the pillar this package implements: rickylabs.github.io/netscript/services-sdk/
- Reference: rickylabs.github.io/netscript/reference/service/
- How-to — add a service: rickylabs.github.io/netscript/how-to/add-a-service/
- API docs on JSR: jsr.io/@netscript/service/doc
Requires Deno 2.x — the runtime listens through Deno.serve and installs Deno.addSignalListener
handlers. Services need --allow-net (listener and health probes) and --allow-env; database and
KV health checks add the permissions of the client they probe.
Apache-2.0 — see LICENSE. Published to JSR with cryptographically verified provenance.