Releases
Unreleased (integration / `cursor/process-store-cutover-a3ad`)
Minor Changes
- Process tag wire schemas and execution store (breaking).
Process.Taguses positionalsuccess/error(noProcess.resultpipe).Process.layer/serve/serveRemoteauto-append terminal runs toProcess.store(tag)and merge a default in-memoryStore.Storage;Process.makedoes not. Override withLayer.provideMerge(AppStore.layerMemory, Process.layer(...))at the app root. - Removed
ProcessExecutionStorefacet (breaking). Deleted@nikscripts/effect-pm/store/ProcessExecution,ProcessStorage.ProcessExecution, andprocess.execution.completedruntime facet. UseProcess.store(tag)→events()for execution history. Store.Storagepublic API.Store.layerDefaultMemory,Store.withDefault,Store.withStorage— replaces internalStoreScopeBridgeTag.
0.8.0-beta.28
Minor Changes
-
Queue durability is now presence-driven — the
persistconfig field is removed (breaking). A queue becomes durable when aDurableQueueStorelayer is in context (with anitemSchemaso the payload serializes) — providing the layer is the switch. An in-memory "durable" store is a contradiction, so durability is intentionally not a baked-in default; absence of the layer is the normal ephemeral in-memory queue. Removed thepersistfield onQueueResourceConfigand theQueuePersistOptionstype. Migration: deletepersist: trueand provide theDurableQueueStorelayer;persist: { maxAttempts: n }→ set the queue'sattempts(the dead-letter budget derives toattempts + 1); lease/poll use engine defaults for now; to keep one queue ephemeral while others are durable, scope the layer so that queue does not receive it. -
Add shape-first
Storecontract API with EventJournal-backed persistence. New@nikscripts/effect-pm/Storesurface:Store.contract/Store.shape(part 1 shapes + optional part 2 custom methods),Store.Service/Store.Tagaggregates withat(tag)lookup, standaloneStore.store,Resource.storetag attachment, and built-inQueueResource.store/Process.storefacet registrations. Handles are fully typed (store.<shape>.append/.read, flat aliases).layerMemoryusesEventJournal.layerMemory;layer({ filename })persists viaSqliteClient+SqlEventJournal.Store.changesstreams append events;Store.retention(maxRows)trims oldest rows per scope. The built-inQueueResource.storepersists the sameQueueEventunion the live.eventsstream carries (one event model for wire + persistence). Platform log facets remain future work.
0.8.0-beta.27
Minor Changes
-
36b4190: Flat module namespaces for the remaining Tag/service modules (breaking). Continues the object-literal → module-namespace conversion started in beta.9 (
Resource,Group,QueueResource): the module now is the namespace (import * as Name), members are flat top-level exports, and partial imports tree-shake.Now converted:
Logs,Query,LogContext,LogEntry,NodeLogs,RunResource,HttpApiResource,HttpClientRunGate,ResourceConfigure, andProcessStorage. All documented members are preserved — the flat root re-exports (And,captureLoggerLayer,LogAnnotationKeys,acceptJson,configureLayer, …) and the namespace members (Query.And,Logs.captureLoggerLayer,LogEntry.Schema,NodeLogs.layer,RunResource.Tag,HttpApiResource.Service,ResourceConfigure.tagKey, …) are the same bindings.RunResource.Tag/HttpApiResourcenow tree-shake their gate/runner and client-builder engines out of a partial import.ProcessStorage'slayer/layerRuntimeStorage, the facet aliases (ProcessStorage.Log/.QueueResource/ …), andProcessStorage.Services(now a flattype) convert likewise.Internally,
QueueResourceandCustomQueueResourcemoved their engines undersrc/internal/so the public subpaths carry only the tree-shakeable contractTag(no public-surface change — the subpaths already resolved to the namespace since beta.9).Migration. Direct subpath value imports of the converted namespace objects change form:
import { NodeLogs } from ".../NodeLogs"→import * as NodeLogs from ".../NodeLogs"(and likewiseProcessStorage,RunResource,HttpApiResource,HttpClientRunGate,ResourceConfigure,Logs,Query,LogContext,LogEntry). The barrel forms (import { NodeLogs } from "@nikscripts/effect-pm", …) are unchanged. -
29259ee:
Processis now a single Effect module namespace (export * as Process) that carries both the supervisor engine and the location-transparentResourcetoolkit — the same shape asQueueResource. Member access tree-shakes: aProcess.Tag-only consumer pulls no engine code;make/layer/servepull the engine only when referenced.New (Resource toolkit, additive):
Process.Tag— define a managed process as a toolkit resource (observation + lifecycle:statusreactive ref,start/stop/runImmediately,logs.live/logs.history). Driven locally or remotely over RPC through the sameyield* Tagsurface.Process.schedule(...)(pipeable) — attach a schedule. Inline windows (Process.schedule([Process.window(...)])) give the process its ownscheduleverb group (entries/set/add/clear); an externalProcess.Scheduleresource gates it with no added verbs.Process.result(Schema)(pipeable) — mark a process value-returning; it gains a reactiveresultholding the latest success (anOption, absent until the first run).Process.Schedule— a standalone, reusable, RPC-capable window manager (full CRUD) that can gate one or more processes.Process.window/Process.at— declarative schedule-window templates (id optional).Process.layer/Process.serve/Process.serveRemote/Process.configure— run a process resource locally, serve it (with/without the local instance), or fold a per-environment config patch.Process.scheduleLayer/Process.scheduleServedo the same for a standaloneProcess.Schedule.
Compatibility: the engine surface is unchanged and stays under the same namespace —
Process.make,Process.Service,Process.currentScheduleId,Process.scheduleControls,Process.Errorsall keep working (barrel andimport * as Processusage is unaffected). The only behavioral change is the export mechanism: a named-value import of the old namespace object (import { Process } from "@nikscripts/effect-pm/Process") must becomeimport * as Process from "@nikscripts/effect-pm/Process". -
7a1a1d4: Retire
ScheduledProcessand privatize the schedule primitive (breaking). The managed-process surface is now a single module:Processcarries both the toolkit contract (Process.Tag) and the engine (Process.make), the same shape asQueueResource.ScheduledProcessand the publicProcessScheduleprimitive are gone; everything they offered lives on theProcessnamespace.Removed
ScheduledProcess— the namespace and the@nikscripts/effect-pm/ScheduledProcesssubpath. Define a process withProcess.Tagand run it withProcess.layer/Process.serve/Process.serveRemote/Process.configure(the engineProcess.make/Process.Serviceare unchanged).- Public
ProcessSchedule— the schedule primitive is now internal. Its constructors, window builders, types, and the standalone schedule resource are re-exposed on theProcessnamespace.
Migration
| Old | New | | ------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | |
ScheduledProcess.Tag/layer/serve/serveRemote/configure|Process.Tag/Process.layer/Process.serve/Process.serveRemote/Process.configure| |import … from "@nikscripts/effect-pm/ScheduledProcess"|import * as Process from "@nikscripts/effect-pm/Process"| |ProcessSchedule.inMemory(entries?)|Process.scheduleInMemory(entries?)| |ProcessSchedule.empty|Process.scheduleInMemory()| |ProcessSchedule.alwaysArmed| (the default — omitschedule/scheduleLayer) | |ProcessSchedule.define(build)|Process.scheduleDefine(build)| |ProcessSchedule.at(...)/ProcessSchedule.window(...)|Process.at(...)/Process.window(...)| |ProcessSchedule.fromStarts(dates)|dates.map((d) => Process.at(d))| |ProcessScheduleEntry/ProcessScheduleService/ReconcileResult/ProcessScheduleControls|Process.ScheduleEntry/Process.ScheduleService/Process.ScheduleReconcileResult/Process.ScheduleControls|Prefer the pipeable combinator when a process owns its windows —
Process.Tag<T>()("id").pipe(Process.schedule([Process.window(start, stop)]))— or gate one or more processes with a standaloneProcess.Scheduleresource. Reading process status is now the reactivestatusref (status.get/status.changes), matching the queue. -
4b199c6: Replace
valuefields withref(aSubscribable). Avaluewas a plain property kept "live" by a background fiber mutating the service object in place — which Effect never does (a plain member is fixed at construction; changing state is aRefread through an effect). Withconstantalready covering the fixed-at-build case,valuewas a non-idiomatic hack between the two.- Dropped
value. Field kinds are nowconstant/ref/effect/stream/local/fleet. - New
Resource.ref(schema)→ materializes asSubscribable<A>({ get: Effect<A>; changes: Stream<A> }), uniform local and remote:yield* svc.x.getfor the current value,svc.x.changesto observe. The impl owns aSubscriptionRef, provided viaResource.subscribable(ref)(orResource.mapSubscribable(source, f)to derive one — e.g. a queue'ssizefrom itsstatus). - Removed
Resource.changes/Resource.refaccessors —reffields expose.changesnatively. - Deleted the mirror machinery (
bindValueToProp, the 30s block-for-initial and its deadlock class).
Migration:
Resource.value(S)→Resource.ref(S); the impl gives aSubscribable(subscribable(ref)ormapSubscribable) instead of a rawStream; reads becomeyield* svc.x.get(wassvc.x) andsvc.x.changes(wasResource.changes(svc, s => s.x)). Queuesize/status/isEmptyare nowrefs.Serve-family vocabulary (breaking). Modes are now protocol-neutral and uniform across
Resourceand every contract namespace (QueueResource,CustomQueueResource,Process,ApiMetrics,Telemetry):layer(tag, impl)— local only (grantsSelf | LocalCapability<Self>).serve(tag, impl)— local and served, the default. Builds the impl once and grantsSelf | LocalCapability<Self>alongside the wire handlers, so a co-located node serves its resources andyield*s them (read alocalmember, share arefcell) with no double materialization and no secondpeersLayer.serveRemote(tag, impl)— served only (a pure gateway/edge; no local grant).client(node)— remote.
Transport bundlers:
httpServer([...serve-layers], opts)exposes one or moreserve/serveRemotelayers on a single httpRpcServer(and auto-serves the reserved node-status resource, so it fully replaces the old all-in-one entry point);httpClient(node)wires a node's transport from aurl; genericconnectcovers custom protocols.Removed the transitional names:
server,serverEntry,remoteEntry,serveHttp,serveAllHttp, and theServeEntry{ tag, impl }shape. MigrateserverEntry→serve,remoteEntry→serveRemote,serveHttp(X, i, opts?)→httpServer([serve(X, i)], opts), andserveAllHttp([...])→httpServer([...serve-layers]). - Dropped
0.8.0-beta.26
Patch Changes
- Fix broken beta.25 build. The
Host→Noderename movedMultiHost.ts→MultiNode.ts, but the roottsup.config.tsentry and the./MultiHostpackage export still pointed at the deleted file, sotsupfailed withCannot find MultiHost. Repointed both toMultiNode(subpath is now@nikscripts/effect-pm/MultiNode). No source changes.
0.8.0-beta.25
Minor Changes
-
Rename
Host→Node(breaking). "Host" implied a machine; a fleet member is a per-process runtime, soNodeis accurate (and avoids clashing with Effect'sHttpApiEndpoint). Migration:Resource.Host→Resource.Node;HostKey→NodeKey,AnyHost→AnyNode,HostBoundTag→NodeBoundTag,SelfHostId→SelfNodeId,HostRef→NodeRef;hostOf→nodeOf,selfHost→selfNode.- Tag option
{ host }→{ node };Resource.multiHost([…])→Resource.distributed([…]);peersLayer(…, { hosts })→{ nodes }. - Subpaths
/HostStatus→/NodeStatus,/HostLogs→/NodeLogs; barrelHostStatus/HostLogsnamespaces likewise. ApiMetricsApiMetricsHostTag→ApiMetricsNodeTag. - Wire-visible (both ends must upgrade together): reserved
kindstrings (…/HostStatus→…/NodeStatus) and the structured-log annotation key (host→node).
-
./ScheduledProcessnow resolves to its tree-shakeable namespace (schemas re-exported);ApiMetricsis flatexport * as ApiMetrics(theApiMetricsModulealias is removed).
0.8.0-beta.24
Minor Changes
-
fa4fae6:
Resource.makefor reusable impls, and host-free multiHost resources.Resource.make(tag, impl)— anchor a hoisted implementation to its contract at the definition site. Inline impls (layer/serverEntry/serve) are already typed; extracting one to aconst(to share across the local layer + a served entry, or several serves) loses that typing and surfaces the mistake far away at the serve call.Resource.makeinfers the tag's spec, constrainsimplto itsImplOf, returns it typed. Overloaded for theEffect-form (R); runtime identity. Also exportsSpecOf<T>for theobj satisfies ImplOf<SpecOf<typeof Tag>>route.- Host-free multiHost.
Resource.peersLayer(tag, self, { hosts })takes the fleet at the use site, so a shared multiHost resource is defined host-free and exported (hosts are a deployment concern). Falls back to the tag's baked.multiHost([…])when omitted (backward-compatible).
0.8.0-beta.23
Minor Changes
-
bf1ba7a: Live
valuefields in the queues + the accessors, a de-brand, a multiHost deadlock fix, and export-naming cleanup.Resource.changes(svc, (s) => s.a.b)/Resource.ref(svc, …)— subscribe to avaluefield's live delta stream (current value first, then every update), or grab itsSubscriptionRef. A selector (nesting-friendly, full autocomplete), not a string path.valuefields are nowSubscriptionRef-backed; the plain read is unchanged.- De-brand (Effect idiom). Internal symbol brands → a string identity
TypeId+ readable_tag("constant"/"value") /fleet: true; spec types read as English, noSymbol(…)keys..annotate()now preserves the marker (value(x).annotate({…})stays a value — was a silent degrade). ImplOf<S>exported — the impl type (avalue's impl is its feedingStream, aconstant's theEffect<A>), distinct fromServiceOf.- Queues adopt
value(BREAKING service shape).status→ livevalue(plainp.statusandchanges-subscribable);statusNow/sizes/completedremoved (readp.status.*);size/isEmpty→value;metrics/logs→ nested{ live, history }. Same forCustomQueueResource(levelSizesstays aneffect). - Fix (multiHost): peer clients are lazy — a
value/streamfield no longer deadlocks the serve.peersLayerno longer eager-builds peer clients; a peer reads avalueone-shot (PeerServiceOf'svalueisEffect<A>, socombineQuery(peers, (p) => p.n, …)works like aneffect), and unreachable peers are dropped. Avalue-bearing multiHost resource now boots against a down/co-booting peer. - Export naming (BREAKING).
./QueueResource+./CustomQueueResourcesubpaths resolve to their full tree-shakeable namespaces; the confusing./QueueContract+./CustomQueueContractsubpaths are removed (import from*/Resource). No code moved; the light-Tag/heavy-engine split is unchanged. - Removed
ProcessScheduleResource(unapproved, unused). TheProcessScheduleprimitive is untouched.
0.8.0-beta.22
Minor Changes
-
d201c0e: Service-shape redesign — shape-named builders,
constant/value, single-schema payloads, and nested specs. Spec builders are named for what they resolve to in the service, not the RPC verb, the set expands beyond Effects, payloads match Effect'sRpc.make, and specs can nest.Resource.effect(→Effect<A>, wasquery) andResource.effectFn(→(In) => Effect<A>, wasmutate) — shape-named vocabulary.query/mutateare retired (renamed toolkit-wide): updateResource.query→Resource.effect,Resource.mutate→Resource.effectFn. (BREAKING)Resource.constant(S)— a value resolved once at acquire, surfaced as a plain property (p.x: A, noyield*), identical local and remote.Resource.value(S)— a plain property kept live by a background stream (impl supplies aSubscriptionRef's.changes); acquire blocks for the initial value, then reads are free.payloadaccepts a single schema everywhere (Effect-aligned).effect/streampreviously took only loose struct fields; they now also take a single schema — aSchema.Struct({…}), a bare item, or a union (item | item[]) — exactly likeeffectFnandRpc.make(Schema.Top | Schema.Struct.Fields). Both forms stay supported; prefer the schema form.- Nested specs (spec-tree). A spec can nest — groups of leaves — to any depth
(
connections: { size: effect(Number), changes: stream(Number) }); the tree flattens to path-keyed wire procedures ("connections.size") and nests back in the service, sop.connections.sizeworks identically local and remote. Leaves may be any builder.
0.8.0-beta.21
Minor Changes
- 66a6be9: New:
Telemetry— a thin, custom, dashboard-native metrics surface. Serves a host's whole EffectMetricregistry as aResource(snapshotquery +live~1s stream) for building custom in-app metrics UIs with no external infra — the counterpart to OTEL export (same source, different sink; OTEL stays doc-only via@effect/opentelemetry, no new dependency).import * as Telemetry from "@nikscripts/effect-pm/Telemetry"→Telemetry.Tag/layer/serverEntry/snapshotNow+ theMetricsSnapshotenvelope (tagged counter/gauge/histogram). Host axis is free (which host you connected to); fan out withResource.client(tag, host). Cardinality-disciplined. Seedocs/guides/telemetry.md.
0.8.0-beta.20
Patch Changes
- 537cc40: Fix:
QueueResource.servenow resolves. The beta.19 engine-servefor queues was added toQueueContractbut not re-exported through theQueueResourcenamespace, soQueueResource.serve(as written in the beta.19 changeset, example, and docs) didn't exist — onlyQueueContract.servedid, asymmetric withScheduledProcess.serve. Re-exportedservethroughQueueResourceso the two match and the docs are correct as written.
0.8.0-beta.19
Minor Changes
-
f423286: Engine-aware
serve—QueueResource.serve/ScheduledProcess.serve. The beta.18Resource.serveis query-only (mounts RPC handlers, runs no engine), so it can't serve queue/process resources with isolated per-resource dependencies. These new forms are the engine-running counterparts: they run the worker / refill /persist(queues) or tick schedule (processes) engine, mount the RPC handlers, register intoResource.servedResourcesLayer, and preserve the worker/tick requirementRso a per-resourceLayer.provideisolates it — composed underResource.httpServerexactly likeResource.serve. Same shape as the existingserveHttp.serveAllHttp+serverEntrystay the shared-dependency tool. -
ac258d3: Two ergonomic helpers.
Resource.httpServer(serves, options?)— pass theservelayers as the first argument and it bundles theprovideMerge+servedResourcesLayerboilerplate (and removes theprovideMerge-vs-providefootgun); the low-levelhttpServer(options)form is unchanged.Resource.fleetHealth(tag, pick, own)— the canned droplet-health fold:picka leaf from every peer, key it by host (Combine.byHost), and add this host'sownvalue keyed byselfHost— the recurringcombineQuery(peers, pick, Combine.byHost)+selfHostpattern in one call. A down peer is skipped (captured, never thrown).
0.8.0-beta.18
Minor Changes
-
e747e52: Per-resource dependencies at the serve —
Resource.serve/Resource.httpServer. When resources on one host need different implementations of the same dependency tag (mutually exclusive — e.g. a hook-firing handler for one, a plain one for another),serveAllHttp's single unioned provide can't tell them apart. The new primitives give each resource its ownLayer.provide, isolated, on one shared/rpc:Resource.serve(tag, impl)— a handler layer that preserves the handlers' requirementR(viaServeRequirements<Impl>), so a per-resourceLayer.providedischarges it. Self-registers for/health.Resource.httpServer(options?)— reads the served-resources registry, merges every group onto oneRpcServer(/rpc), and mounts a/healthroute.provideMergetheservelayers onto it.Resource.servedResourcesLayer/Resource.ServedResources— the registryservewrites andhttpServerreads.Resource.provide(dependency, [resources])— sugar forLayer.mergeAll(resources).pipe(Layer.provide(dependency)).
Dependencies compose via ordinary
Layer.provide(no config-embedded layer, no branding); sharing is by memoization (same value → one instance,Layer.freshto isolate). Tick/worker bodies just declare their requirement — noEffect.provide, so consumers can runstrictEffectProvide: "error".serveAllHttpis unchanged and stays the tool for the shared-dependency case. New public types:ServeMethod,ServeImplOf,ServeRequirements,ServedResource. Guide:docs/guides/per-resource-dependencies.md; dogfooded inexamples/serve-per-resource-deps.ts.
0.8.0-beta.17
Minor Changes
-
0ab917e: Durable log storage, queryable by host or by resource.
HostLogs.persistLayer(host)installs a batched capture logger that durably stores every runtime log line inLogStore— bucketed by host, with each line'sprocessId/queueIdpreserved — backed byRuntimeStorage(memory / sqlite / redis viaProcessStorage). Read it back withHostLogs.byHost(host, opts?)(every line a host logged) orHostLogs.byResource({ processId?, queueId? }, opts?)(a specific queue/process, across hosts); both return[](not an error) when empty, newest first. The logger is installed at layer-build so it captures from the start (no relay-subscription race). Removes the stranded process-group log paths:ProcessGroupLogContext/layerProcessGroupLogContextand the flatHostLogs.history/HistoryStore-bucket persistence are gone;LogAnnotationKeysgainshost(dropsgroupId), addswithHostLogAnnotations. -
1c7ebef:
Resource.client(tag, host)— read a hostless multi-host tag as a client. A hostlessmultiHosttag is N instances, so the client names which one:Resource.client(FleetDatabase, NwslHost).pipe(Layer.provide(connectHttp(NwslHost))). The transport resolves from that host, so the layer requires it (satisfied byconnectHttp) — enforced at compile time. Turns the previous runtimeService not found: RpcClient/Protocol(a hostless client wired to a host service) into a type error. Host-bound tags unchanged (Resource.client(tag)). -
28f3769:
Resource.selfHost(tag)— the host key a multi-host instance runs as, the same key itsResource.peersare keyed by. ForCombine.byHostfolds (one row per host), so a resource keys its own row without hand-threading:return { ...byHost, [self]: ownValue }. Provided bypeersLayer(now bundled) or standaloneResource.selfHostLayer(tag, self). -
cd8a472:
Resource.peersLayer(tag, self, { url })— override peer urls without freezing them into the host contract.Host.urlstays the default; the resolverurl: (host) => Effect<string | undefined>overrides per host (env ports, tunnels, EffectConfig), falling back toHost.url. Its error + requirements flow to the layer (typed) — aConfigErroris a typed layer-build failure, or returnundefinedto skip a peer. Fully back-compatible (omitoptions).
0.8.0-beta.16
Minor Changes
-
85336ae: Multi-host resources via
Resource.peers. One resource shape served as N host-local instances (one per host). Combined/fleet values are plain queries taggedResource.fleetand implemented in the layer by foldingResource.peers(the other hosts' leaf clients) + your own value — no special field kind. New surface:Resource.Host("id", { url })(the host carries its url),.pipe(Resource.multiHost([hosts]))(the fleet — hostless, every instance an equal peer),Resource.peers/peersLayer/peersFrom(the opt-in mesh), andResource.fleet/FleetField(a combined field: served + client-visible, but excluded frompeersso a fold can't fan out).Resource.layer/Resource.serverEntrygain anEffectform (build the impl effectfully — e.g. resolvepeersonce).Methodis nowPipeable. -
dfa8dd2:
@nikscripts/effect-pm/MultiHost— the isomorphic combine core (browser + node):combineQuery/combineStreamgather a field across a keyed peer map, capturing each host's outcome (HostResult), then fold;Combineshipssum/collect/byHost/successes/failures/mergeStreams/mergeByHost. A custom fold sees every outcome and owns the down-host policy.
Patch Changes
- 26c7c41: Upgrade Effect to
4.0.0-beta.92(frombeta.69) —effect(peer + dev),@effect/platform-node,@effect/sql-sqlite-node,@effect/vitestin lockstep. No source changes needed. Consumers should move toeffect@^4.0.0-beta.92.
0.8.0-beta.15
Minor Changes
-
f21e20e:
Resource.serverEntry(tag, impl)— a typedserveAllHttpentry for a raw custom resource (mirrors the contractserverEntrys). The impl is spec-checked against the tag's spec, unlike a bare{ tag, impl }literal (typedRecord<string, unknown>). Two forms: a record (R = never) or anEffectthat builds it carrying a requirementR.Resource.instance(for theserveInstancesfamily) now points here. -
1a13471:
serveAllHttpunions each entry's requirementRinstead of pinning all to one — a host serving queues/processes (workerR) next toApiMetrics(Scope) and plain resources (never) no longer needsas ServeEntry<never>per entry. Bare{ tag, impl }literals contribute nothing rather than poisoning the union. -
dfb27f2: Removed the Prisma storage backend (BREAKING).
@nikscripts/effect-pm/storage/prismaand…/prisma,PrismaRuntimeStorage, the structural client types, theeffect-pmprisma CLI helpers, and the@prisma/clientoptional peer are gone — an unused, codegen-heavy backend already covered by the sqlite and redis backends. (Recoverable from git.)
Patch Changes
- 5e6b954:
Resource.withReadinessdata-first accepts a host-bound class (atypeof Xconstructor), via inferred overloads (host preserved in the return), the wayclient/layerdo. Resource group types stay fully precise (RpcGroupOf<S>) — no erasure, no cast.
0.8.0-beta.14
Minor Changes
-
606dbbc: Full-screen health board. Tapping the host-status die opens a full-screen board over every host's
HostStatus.resources[]: a stat strip (hosts ok · resources ready · attention count), a "needs attention" list of degraded resources across all hosts with their root cause (tap → that resource), and a card per host (status · uptime · ready/total · resource count · a "ready over time" sparkline) with its resource roster. Clean overlay navigation (resource → host → board → dashboard). New/webexportHealthBoard; the die now just opens it. -
f844488: Readiness on resource detail pages. A resource's page shows an amber
degraded — <root cause>banner under its header when it isn't ready (read from its host'sHostStatus— the SSOT the board uses, via newdata.resourceHostRef). It renders nothing while ready/connecting, so it only takes space on a problem. New/webexportResourceReadinessBanner.
Patch Changes
-
4d367ea: Host metrics: each health-board host card shows a client-accumulated readiness sparkline (ready-count over time) that dips when a resource degrades — no server change.
-
871b71e: The resource-page readiness banner is hidden while ready (only appears on a degraded resource), so it doesn't push content down in the normal case.
-
bf83f20:
withReadinessaccepts host-bound tags on both overloads. The host-bound fix (aHostBoundTagisn't assignable to a bareResourceTag<any, any>— invariant[groupSym]) now covers the data-firstResource.withReadiness(tag, fn)overload too, not just data-last.pipe. (The supported way to attach readiness to a host-bound tag remains the data-last.pipeform.)
0.8.0-beta.13
Minor Changes
-
e1bf860: Hosts in the dashboard.
Resource.hostOf(tag)reads a tag's boundResource.Host, so the web dashboard derives its host list from theGrouptree (no registry). A host-status die in the header shows one pip per host (coloured by itsHostStatus, barrel-stacked, larger when fewer); tap it for a hosts panel, tap a host for its full screen (per-resource readiness + live logs), tap a resource to open its detail (back returns to the host). Header polished (screen-centered title, negative-space resource count). New/web:HostBar,HostDetail,HostDots,hostStatusBundle,useHostBundle,leafByKey; debug console gained icon + copy buttons. Theresource-webexample is now a real remote dashboard (three hosts overserveAllHttp). -
9ae6c42: ApiMetrics serves on a host (BREAKING).
ApiMetricsis now a per-instance resource with its own RPC group, so it composes intoserveAllHttplike a queue/process. NewApiMetrics.Tag<Self>()(clientId, { host?, description? })(host-bearing returnsApiMetricsHostTag) andApiMetrics.serverEntry(tag)(fed from the Metric registry); host-bound keys are host-prefixed so the sameclientIdon two hosts doesn't collide. RemovedserveInstances/clientInstances/instance;layer/layerForunchanged. -
a5a4b3c: Dependency-aware readiness.
withReadinessderivations receive the prior readiness asbaseand stack (extend a factory's check instead of replacing it);Resource.readinessOf(tag)pulls a resource's readiness by tag (compile-time-checked dependency, local or remote);Resource.allReady([...])ANDs checks. So a queue/process can report degraded when a dependency (e.g. a database resource) is down.
Patch Changes
- ad91f49: Host-bearing tags can be
exported — the host-bearing constructors return a namedResource.HostBoundTag(andApiMetricsHostTag) instead of an inline intersection, fixing TS4020 ("uses private namehostSym") when a consumer exports a host-bound tag.
0.8.0-beta.12
Minor Changes
-
fa8762c: Unified tag construction shape (BREAKING). Service tags now all follow the Effect
Context.Serviceidiom —Tag<Self>()(identity, …, options?).Resource.Tag<Self>(key)(spec)becomesResource.Tag<Self>()(key, spec, options?)(andhostmoves from a positional arg intooptions.host);ApiMetrics.Tag<Self>(clientId)()becomesApiMetrics.Tag<Self>()(clientId). The queue / scheduled-process / custom-queue / process-schedule / run factories already used this shape and are unchanged, as isResource.tagFor. No back-compat / deprecation (beta). -
765d195: Per-resource readiness →
/health503 +HostStatusboard. Every resource can report whether it's working, derived from its own status (SSOT).Resource.withReadinessis a dual combinator (withReadiness(tag, fn)ortag.pipe(withReadiness(fn))) that attaches a derivation(svc) => Effect<{ ready, detail? }>; the built-in contracts apply it (queue/custom-queue ready iff the worker pool isrunning, scheduled-process iffsupervising), a bareResource.Tagopts in the same way, and a tag without one is ready by default.serveAllHttpaggregates once: the always-onGET /healthroute returns200/503(status: "ok" | "degraded") with a body listing each resource's{ key, kind, ready, detail? }, andHostStatusfolds the same aggregate into its schema (status+resources[], plusHostStatus.resourceReadiness). Restores the readiness endpoint the removed control plane used to provide.
0.8.0-beta.11
Minor Changes
-
ace74b5: Tags now carry their contract kind. Each contract's
.Tagfactory stamps a canonicalkindid on the tag (e.g.@nikscripts/effect-pm/QueueResource), read withResource.kindOf(tag)— so consumers (notably the web/TUI dashboards) classify a tag by what it is instead of sniffing its spec members (which mis-classifiedApiMetricsas a process and broke custom-queue / process-schedule tags). Each contract exports itskind(QueueResource.kind,ScheduledProcess.kind,ApiMetrics.kind,CustomQueueResource.kind,ProcessScheduleResource.kind);makeTag/tagForaccept akindoption. -
6f7039b: Host status — every served host auto-exposes its status + logs. New
@nikscripts/effect-pm/HostStatus: a reserved, hostless resource (statusstream,statusNow,ping,logsstream,logHistory) thatResource.serveAllHttpnow serves automatically alongside your resources — the host author wires nothing. Query any host withHostStatus.clientHttp(url)(or anyRpcClient.ProtocolforResource.client(HostStatus.Tag)). Status reports{ up, startedAt, uptimeMillis, resourceCount }; logs/history come from the runtime-wideHostLogsrelay when provided. -
21418f0:
@nikscripts/effect-pm/web— API-resource dashboard widget forApiMetricstaps. A read-only per-type widget:ApiCard(a reusable iOS-stylePagedCard— throughput sparkline + error-rate health badge, and a busiest-endpoints face),ApiStats,ApiMetricChart(throughput / errors / in-flight with the shared time-window toggle), andApiEndpointTable(group / endpoint / requests / errors / avg in a sortable TanStack table). The dashboard classifiesApiMetricsleaves via the stamped kind and renders anApiDetail; exposesapiBundle/useApiBundle. Also this cycle: a fullscreen weekly schedule editor for scheduled processes, chart time-window control, group-view names from member keys, and the shippedtheme.cssnow carries.safe-area+ view-transition CSS. Uses the optional peer@tanstack/react-table(alongside the existingrecharts).
0.8.0-beta.10
Minor Changes
-
70146c5:
ApiMetricsobservability resource +HttpApiResource.Service.ApiMetrics(aResource.tagForfactory) links to anHttpApiResource.ServicebyclientIdand exposes a windowedmetricsstream and ausageNowquery (requests, errors, throughput, per-endpoint breakdown).HttpApiResource.Serviceis a class factory for a typed API client with endpoint usage metrics and registry hooks. New subpaths:@nikscripts/effect-pm/ApiMetrics,@nikscripts/effect-pm/ApiUsageSchema,@nikscripts/effect-pm/HttpApiResource. -
4954957: Rename resource/service tag identity from
idtokeyacross the toolkit. Tag factories takekeyas the first argument, tags expose Effect's.key(not a custom.id),ResourceInstanceandDuplicateResourceKeyusekey, the UI bindings (ResourceUI.key) and the web dashboard follow the same naming, and multi-instance RPC routing dispatches on the HTTPkeyheader. BREAKING: readtag.keyinstead oftag.id; passkeyas the first factory argument. -
461faa0: Pluggable queue lane take algorithm on
QueueResource— schedule how lanes are drained without changing the public enqueue API. AddslevelCountandtakeAlgorithmtoQueueResource/Service.configure("priority"default), built-in"weighted"and"strict-descending", a customCustomTakeAlgorithmhook, and a numericLaneStoreseam +buildQueueEngineextension point. The default bundle is unchanged — scheduled algorithms load via dynamic import only when configured. -
caf7e4d:
CustomQueueResource+CustomQueueContractfor N-level queues with named lanes,add(item, level?), andRecord<string, number>sizes. AddsResource.mutatePairfor two-argument wire methods. Subpaths:@nikscripts/effect-pm/CustomQueueResource,@nikscripts/effect-pm/CustomQueueContract.
Patch Changes
-
b702291:
@nikscripts/effect-pm/web: the dashboard now owns its font. It previously relied on a globalbody { font-family: var(--font-mono) }plus inheritance, so a consumer rule on an intermediate element (e.g. an#root { font-family: … }in theirindex.html, ID specificity) overrode it and the dashboard rendered in the host app's font.font-monois now declared on the dashboard's own root, so the widgets render monospace regardless of the host'sbody/#rootfont, while still honouring a consumer-defined--font-monotoken. -
0ce4ba4:
@nikscripts/effect-pm/webdashboard polish: process controls stay a horizontal row at every width (only the queue controls go vertical, to flank the metric chart); card content stays top-aligned when the grid stretches a card to the row height (instead of a bare<button>centring its content in the slack); tighter log columns (time and level); and the metric chart gains a second dropdown to pick the latency series' time unit (ms/s/min/hr). The group view now labels cards and the breadcrumb by the member key under which each parent group holds them (the routing nickname — e.g.Nwsl/Wnba), not the last segment of the tag's own key; full-screen resource pages still use the tag's own key (a resource doesn't know its group-given nickname).useGroupRoutealso returns the resolvedkeys(the member-key chain mirroring the path).
0.8.0-beta.9
Minor Changes
-
22bb124: Tag APIs are now uniform, tree-shakeable module namespaces (Effect-style) —
import * as X, no object literals.ResourceandGroupwere object exports (soimport * as/ tree-shaking couldn't work); they're now per-member namespaces. The/QueueResourcesubpath previously resolved to the internal engine (an object whoseTaghad nohostand didn't tree-shake) — it now resolves to the namespace, soimport * as QueueResource from ".../QueueResource"gives the host-fulTagandQueueResource.Tagtree-shakes (~207 KB → ~27 KB). BarequeueTag/processTagare removed.BREAKING. Migrate:
import { Resource }→import * as Resource;import { QueueResource }→import * as QueueResource;queueTag<T>()(…)→QueueResource.Tag<T>()(…);processTag<T>()(…)→ScheduledProcess.Tag<T>()(…).
Patch Changes
- 0d6d602:
@nikscripts/effect-pm/web: log timestamps use a compact 24-hour clock (HH:MM:SS, no AM/PM) with no-wrap + tabular figures, so the time column no longer wraps. Applied to the log stream and the debug console.
0.8.0-beta.8
Minor Changes
-
bcbafc3:
@nikscripts/effect-pm/webnow ships the real dashboard — the hand-crafted, per-type UI (previously trapped in the example), not the old generic introspection view.<Dashboard runtime={Atom.runtime(layer)} group={ServicesHub} />renders the responsive drill-down (queue / process / subgroup cards → styled detail with stats + edge-to-edge metric chart + icon controls + logs → routed fullscreen log viewer at/Group/Resource/logs), URL-backed navigation with view-transition animations, and locked-by-default controls with a confirm dialog on destructive actions. Compose the pieces (DashboardView+ the widgets +queueBundle/processBundle/useQueueBundle/useProcessBundle) underRegistryProvider+RuntimeProvider+ViewTransitionProvider. Runtime-injected, browser-safe (no node deps); the example dashboard now consumes/webdirectly. BREAKING: the old generic exports are removed —GroupView,ResourceView/ResourceWidget/useResourceUI,panels,primitives,binding,chart. -
b5f9383:
@nikscripts/effect-pm/web: adduseGroupRoute(root)— URL routing that mirrors theGrouptree (/Wnba/ImportSchedule, case-insensitive, History API back/forward + deep links). A selected leaf can carry a sub-view segment (/Mail/logs⇒route.view === "logs") for per-resource pages like the fullscreen log viewer.
0.8.0-beta.7
Minor Changes
-
2999895:
Resource.serveAllHttp+QueueResource.serverEntry/ScheduledProcess.serverEntry— serve a whole group of resources on one HTTP port behind oneHost(procedures group-id-prefixed). A host can now be a group on one port, not one-resource-per-port; oneResource.connectHttptransport reaches them all. -
b0a3249: Ship
@nikscripts/effect-pm/cli— build a run-and-exit CLI from your resource tags.makeResourceCli(resources, rootName)(record of tags →effect/unstable/clicommand tree; verbs + flags + help from the contract;ls),resourcesByName(tags)(shortest-unique-slash-suffix naming),render(value). -
615cbb1: Ship
@nikscripts/effect-pm/tui+ a shared UI core. The reactive React binding now lives once insrc/ui/atom-reactand is shared by web + tui (Ink is React);/tuiadds composable terminal primitives (bar,spark,compact,fmt,displayName,blankBorder,statusColor/statusIcon). -
3821c91:
@nikscripts/effect-pm/web: View Transitions helpers —ViewTransitionProvider,useViewTransition,useViewTransitionStyle— animate navigation (a card morphs to fill the screen) with conditional naming, degrading to an instant update where unsupported.
Patch Changes
-
8e2e611:
@nikscripts/effect-pm/webreactive binding:useAtomValuemounts the atom (inside the subscription) anduseAtomSetmounts its command atom, so cold stream atoms (status/metrics/logs) start on render instead of staying blank until an interaction. -
3b4c822: Fix
QueueResourcerefill dependency support: a refillloadcan require its own services, independent of the workereffect(separateRRrequirement;layer/server/serveHttp/serverEntrysurface the unionR | RR). Runtime behavior unchanged. -
0e6f5cb: Docs: add
docs/guides/setup.md— a consumer setup guide (install + peer-dep matrix, the subpath map, and wiring aserveAllHttpserver +/cli//tui//webagainst your tags).
0.8.0-beta.6
Patch Changes
-
ba9de58: Examples: a unified
pmentrypoint that drives the sameFleettags two ways — no subcommand launches the styled Ink dashboard; a subcommand runs a single command and exits (pm Mail statusNow,pm Mail pause,pm KeyRotation start,pm ls), over http via the shared data layer. Resource command names are the tag's display name, lengthened to the shortest unique slash-suffix only when two collide.Hardens the
makeResourceCliexample builder: anlssubcommand, stream/local methods filtered out by a precise contract guard (only wire query/mutate verbs become commands), and defensive flag derivation that skipsoptionalKey/date/duration fields and whole-Schemapayloads instead of throwing.
0.8.0-beta.5
Minor Changes
-
67ba214: Ship
@nikscripts/effect-pm/web— a React widget library for building resource dashboards straight from the toolkit'sTags. Queue and process cards, stat panels, metric charts, control buttons (with round-trip feedback) and live log streams, each driven by an atom bundle derived from a resource tag — no hand-rolled registry. A small Effect-atom binding (effect/unstable/reactivity) backs the same widgets in the browser and in an Ink TUI; the data layer is environment-aware, so the identical UI reads a local engine layer or a remoteResource.clientover http without changes.Also adds the worked examples that exercise it: a tag-driven web dashboard (mobile + desktop) and a matching Ink TUI dashboard over the same shared data layer, plus a
pnpm run example:sessiontmux launcher that boots the example servers and both UIs in one place.
0.8.0-beta.4
Minor Changes
-
23d41bc:
QueueResource: add a first-classrefillconfig — a self-feeding queue that loads work from a source (DB, …) on start and/or whenever it drains empty. The toolkit replacement for the legacyonStart/onDrainedlifecycle hooks:QueueResource.layer(RosterQueue, { effect, refill: { onStart: true, // bootstrap once when the worker pool starts onDrained: true, // re-poll the source each time the queue drains load: (queue) => loadFromDb(queue), }, });loadreceives the queue handle, runs in the workerR, and is best-effort.onStartis forked (a slow source load doesn't block startup);onDrainedruns after eachDrained(drives the self-feeding loop, and idles whenloadenqueues nothing). Distinct from after-the-facteventsobservation —refillis a defining queue behavior (a pull source). -
6323949: Rename the structured-logging symbols from the vestigial
ProcessManagerLog*to neutralLog*(no behavior change) — the log infra never depended on the removedProcessManager:ProcessManagerLogEntry→LogEntry,ProcessManagerLogEntrySchema→LogEntrySchema,ProcessManagerLogEntryNdjson→LogEntryNdjson,processManagerLogEntryFromLoggerOptions→logEntryFromLoggerOptionsProcessManagerLogRelay→LogRelay(+LogRelayService),ProcessManagerLogAnnotationKeys→LogAnnotationKeysProcessManagerLogQuery→LogQuery,ProcessManagerLogQueryError→LogQueryError,ProcessManagerLogSort→LogSort,ProcessManagerLogScope→LogScope
The
LogEntrynamespace (LogEntry.Schema/encode/decode) is unchanged; the renamed entry type now merges into it. Annotation key values (groupId/processId/queueId) are unchanged.
0.8.0-beta.3
Minor Changes
-
27eb8c4: Persistence — the two-plane design from
docs/handoffs/queue-persistence-design.md.Observability plane (history).
HistoryStore— a tiny backend-agnostic append-log (append/read, keyed by stream id;layerMemorytoday, SQLite/Postgres later). Each resource reads it back via*Historyqueries on the same Tag as the live stream, fully opt-in viaserviceOption(no store → empty):- Queue:
logHistory+metricsHistory(needscaptureLogsfor logs). - Process: per-process log capture (
captureLogson the process layer) feedinglogs+logHistory. - Runtime-wide:
HostLogs.persistLayer+HostLogs.history(captures all runtime logs).
Backends:
HistoryStore.layerMemory(in-process) andSQLiteHistoryStore.layer(durable across restarts, count-based retention) — same interface, swap the layer.Durability plane.
DurableQueueStore— a priority-native store of pending + in-flight work so no enqueued item is lost across a restart (at-least-once + dedup key). Inspired by Effect'sPersistedQueue(lease /attempts/ expiry-recovery blueprint) but priority-native, not FIFO: strict high/normal/low + FIFO within a lane, dedup + escalation on adedupKey, lease +recover,fail→ requeue/dead-letter,sizes. SQLite backend (SQLiteDurableQueueStorefrom@nikscripts/effect-pm/storage/sqlite) over one table; single-writer leasing in a transaction. The store port is on the core entry (no SQL dep).Engine integration.
QueueResourcegains apersistoption: when set (with aDurableQueueStorelayer +itemSchema), the store becomes the source of truth — enqueue persists, a feeder leases work into the workers, completion/failure update the store, and a restart recovers in-flight work.size/sizes/isEmpty/status/clearand shutdown-drain are store-aware, and the control verbsrelease/deadLetter/dropoperate on the durable backlog (byentryId/key). Fully gated: withpersistoff the in-memory engine is byte-for-byte unchanged.A guide for consumers:
docs/guides/history-and-persistence.md. - Queue:
-
bb6316c: Remove the pre-toolkit legacy layer (plan 17). The
Resourcetoolkit + persistence now supersede it, so the bespoke control plane and orchestration are deleted:- Control plane:
ControlService,ControlProtocol,ControlTransportRpc,ControlTransportHttp,CommandAuth,LogTransportRpc,Transport(httpEndpoint). - Orchestration:
ProcessManager,ProcessGroup(+ thestore/ProcessGroupfacet /ProcessGroupStore, removed from the composedProcessStorage). - Terminal:
Terminal,TerminalRpc(dropped; use SSH). - Legacy CLI:
cli(createCli/runCli) and theeffect-pm/effect-pm-group-childbins.
Replacements (all shipped): remote control →
Resource.client/server/serveHttp/Host; many instances →Resource.serveInstances; group organization →Group(nestable); runtime-wide logs →HostLogs; durability/history →DurableQueueStore/HistoryStore. Their subpath exports are removed. Kept log infra (LogEntry/LogContext/Logs, still namedProcessManagerLog*) is unchanged; a neutral rename is a separate follow-up. - Control plane:
-
9b3c2d3: Rename the toolkit process tag
ProcessResource→ScheduledProcess(namespace + the@nikscripts/effect-pm/ProcessContractsubpath →@nikscripts/effect-pm/ScheduledProcess). The surface is unchanged — it's a process with lifecycle (start/stop/runImmediately), observability (status/logs/logHistory), and schedule control. The name now reflects what it is: a scheduled process, distinct from a plain schedule. (Processremains the lower-level engine.) Migration:ProcessResource.Tag/layer/server/serveHttp→ScheduledProcess.*.
0.8.0-beta.2
Minor Changes
-
Tree-shakeable, browser-safe resource tags + one unified
QueueResourcenamespace + a cleanstrictEffectProvidegate.Unified
QueueResource. The toolkit queue and the engine are now a singleQueueResourcenamespace (one import) —Tag/layer/configure/server/serveHttpplus the engine helpersmake/Service/Schema/Errors.Tagis a normal Effect service driven the sameyield* Tagwhether local or remote.Tree-shakeable browser-safe tags. Queue, Process, and ProcessSchedule are restructured the Effect way — light contract modules (engine-free
Tag/spec), shared wire schemas in their own module, namespaces assembled viaexport * as, andtsupcode-splitting (splitting: true) +"sideEffects": false. Importing a tag for a browser bundle no longer pulls the engine:import * as QueueResource from "@nikscripts/effect-pm/QueueContract"; import * as ProcessResource from "@nikscripts/effect-pm/ProcessContract"; import * as ProcessScheduleResource from "@nikscripts/effect-pm/ProcessScheduleContract"; // QueueResource.Tag / ProcessResource.Tag bundle to ~kb with zero engine code (proven via esbuild).All toolkit modules are also verified to have no native/node deps (browser-safe regardless of bundler). Guaranteed barrel-namespace tree-shaking (vs the subpath form above) is a tracked follow-up.
Process default fix. A
ProcessResourceruns immediately with its layer (alwaysArmeddefault), matching the engine; passschedule: ProcessSchedule.emptyto start disarmed.strictEffectProvidecleanup. The 6 previously-held sites are fixed by building each layer once into its scope and providing the resultingContext(rate-limiter and capture-logger in the queue engine; the legacy RPC transport servers). The strict typecheck gate is now clean (0 errors).
0.8.0-beta.1
Minor Changes
-
24e2eff: Adds signed command authentication for
ControlServiceandProcessManager.Introduces the public
CommandAuthmodule, Ed25519 key records, canonical command payload signing, replay protection, strict authenticatedPOST /controlhandling, signedGetHealth, admin key generation, and ProcessManager public-key enrollment helpers. -
2a3bdcc: Add protocol request/response envelopes for control transports and expose canonical HTTP
POST /controlrouting while preserving REST route aliases. -
8573f33: Add
ControlTransportRpc, an Effect RPC adapter for dispatching existingControlProtocolenvelopes. -
850f702: Add headless React dashboard primitives and a styled ops-ui shell for adaptive controls and live logs.
This introduces browser-safe dashboard target types,
<Controls for={...} />,<Logs for={...} />,useControlPlaneLogs, andControlPlanePort.logs(...)with live-following NDJSON log streams plus bounded history parameters. It also adds the@nikscripts/effect-pm/ops-uiexport withOperatorDashboardfor a production-oriented dashboard shell, icon-only action buttons, status tables, terminal-style live logs, a styled log toolbar, a persisted resizable dashboard grid layout, shadcn-generated local UI components, bounded scrollable widgets, and persisted dashboard chrome visibility state. -
1ef3134: Reorganize
src/into Effect-style flat public modules plusinternal/storeandinternal/managerhelpers. Breaking: remove./ProcessStoreGroupLogand./QueueResourceStorepackage subpaths; facet services are exported understore/*subpaths and composed viaProcessStore.layer/store.Log/store.QueueResource. Public PM modules are nowLogContext,LogEntry, andTransport; rootindex.tsno longer re-exports internal log query/watch helpers orgroupChild. AddrelayWithCaptureLoggerLayeron@nikscripts/effect-pm/Logsfor child-runtime wiring. -
8bac9ce: Ship the package as ESM-only (
"type": "module").The dual CommonJS + ESM build is gone: there is now a single ESM build, and the
requireexport conditions are removed (eachexportsentry is{ types, default }pointing at the ESM.js).moduleResolutionmoves tobundler— transparent to consumers since tsup bundles each entry, so no relative imports escape the package. Consumers must import via ESM (import), which the only known consumer already does.Bonus: with the whole repo on ESM, the terminal UI (Ink, which is ESM-only via yoga-layout's top-level await) now runs directly under
tsxinstead of needing an esbuild→ESM bundle step. -
10afd42: Organize public exports into namespace objects while keeping short root import aliases.
Add namespaces across runtime, storage, control, and process-manager modules (
Query,ResourceConfigure,DisarmedIdleSleep,Cli,RuntimeStorage,ControlProtocol,Process/ProcessGroup/QueueResourcenestedErrors/Schema,Logs,LogEntry,LogContext, expandedProcessManager). Root exports such asAnd,configureLayer,createCli, andEndpointremain the same bindings as their namespace members (Query.And,ProcessManager.Endpoint, etc.).New subpaths:
@nikscripts/effect-pm/ResourceConfigureand@nikscripts/effect-pm/ControlProtocol.This branch is rebased on
main(configure + export namespaces only). Prisma / durable storage work lives oncursor/remove-xor-query-958b. -
eb4cd7c: Add
LogTransportRpc, an Effect RPC adapter for live process-manager log streams. -
e4a11e2: Add
pm watchandpm logsoperator commands with structured log annotations (groupId,processId,queueId). Child runtimes persist captured log lines to a SQLite-backedProcessStoreat.effect-pm/logs/<group>/logs.sqlite;pm logsqueries that history by target (group, process, or queue) with date, cursor, limit, and sort flags.Unify operator lifecycle commands:
pm start <target>andpm stop <target>dispatch by resolved identifier (group child launch/stop, process controls, or queue start). Removegroup-start,group-stop, andqueue-start. -
5777241: Ship
PrismaRuntimeStorageas a Prisma-backedRuntimeStorageadapter over normalized runtime records.The Prisma schema fragment now declares
EffectPmRuntimeRecordmapped to theeffect_pm_runtime_recordstable, with indexed columns stored as scalar fields and runtime JSON blobs serialized into string columns. The adapter expects an injected structural client with aneffectPmRuntimeRecorddelegate. Consumers continue to own Prisma generation, migrations, and client lifecycle.Add
effect-pm prisma initfor interactively adding the schema fragment to an existing Prisma project, and verify the adapter with both structural mocks and a generated Prisma SQLite client.Add typed
RuntimeStorageoperational errors for durable adapters, mapping Prisma / SQLite driver and decode failures into public storage error tags instead of defects.Breaking: static ProcessStore facet emitters now surface write failures when a storage layer is present. They still no-op when the facet layer is absent. Use the new pipeable
ProcessStore.catchErrorAndLog(...)helper for writes that should remain best-effort telemetry.Breaking: SQLite
layerProcessStorenow surfaces typed acquisition errors. UselayerProcessStoreOrDieto keep the previous defect-on-acquisition behavior at application edges. -
be2890c:
ProcessGroupDuplicateDefinitionError—makeProcessGroupfails at definition time when process names or queue tag keys are duplicated in the group config. -
b3ff52c: Add
ProcessGroup.localEnvLayerandProcessGroupServiceDefinition.localEnvLayerto compose child runtime env layers without duplicate queue merges. ExportProcessGroupServiceLayerProvidedonProcessGroup.Service.layerfor accurate requirement typing.Add
ProcessManager.groupLocalRuntimeas a one-linerLocalRuntime+ HTTP control descriptor.Fix
ControlRouter.layerFromGroupto accept groups with bundled endpoint config items. -
fe1404b: ProcessGroup
startAllnow runsQueueHandle.startfor every registered queue before starting processes (pairs withQueueResourceautoStart: false).Adds
TypedProcessGroup.startAll,TypedQueueControls.start, contract capabilitystarton queues,POST /queues/:id/starton ControlService, andRemoteQueueControls.start/POST …/starton ProcessManager. Multi-group CLI exposesqueue-start <target>(distinct fromstartfor processes). -
773eb5f: Breaking:
Process.makenow requires(id, config)or(id, effect, …); the single-object form withnamein config is removed.ProcessMakeOptionsis the public config type (nonamefield).Process.providePollingandProcess.provideScheduleare removed; pass preset polling/schedule layers positionally or on the config object. -
e4160cc: Replace module/runner endpoint shims with child-only
Endpoint.local(transport, entry), pipe child stdout/stderr into.effect-pm/logs, and addpm watchfor live structured logs andpm logsfor stored history.Group
watchstreams structured Effect log entries over the control HTTP API (/logs/stream) and replays them through the operator logger layer, replacing file tailing of child stdout/stderr. -
a3b0967: Add ProcessManager endpoint config items, endpoint label selection, status probing, module endpoint launcher support, and local group stop/run-state cleanup.
-
773eb5f: Change
Process.makedefault schedule from empty in-memory storage toProcessSchedule.alwaysArmedwhen bothscheduleandscheduleLayerare omitted. AddProcessSchedule.emptyfor apps that relied on the previous disarmed-until-mutation default — passschedule: ProcessSchedule.emptyto restore that behavior. -
f3bcbad: Breaking — rename
ProcessStoreGroupLog→LogStore.The facet that persists structured log entries for the
@nikscripts/effect-pm/Logscapture/relay pipeline never served a singleProcessGroup.Service; its bucket id (thegroupIdparameter) is an opaque partition supplied by the relay (today the PM log annotation fromLogContext). The previous "GroupLog" naming implied aProcessGroup-scoped service and conflicted with the distinctProcessGroupStorefacet, which actually does serve typed process groups.Renamed surface (no compatibility shims):
ProcessStoreGroupLog→LogStore(service tag + class)ProcessStoreGroupLogApi→LogStoreApimakeProcessStoreGroupLog→makeLogStore- Subpath
@nikscripts/effect-pm/store/GroupLog→@nikscripts/effect-pm/store/Log - Service key
@nikscripts/effect-pm/store/groupLog/ProcessStoreGroupLog→@nikscripts/effect-pm/store/log/LogStore ProcessStoreInterface.GroupLog→ProcessStoreInterface.Log(on the transitionalProcessStoremonolith)- Wire event
type: "group.log.entry"→type: "log.entry"andentityType: "group"→entityType: "log" GroupLogEntryRecordedEvent→LogEntryRecordedEventisGroupLogEntryRecorded→isLogEntryRecorded- File
src/store/groupLog.ts→src/store/log.ts
Existing SQLite rows with
type: "group.log.entry"will not decode under the new codec. Drain the durable log store or migrate rows before upgrading.The deprecated alias
makeLogStoresis removed. -
e713522: Replace the generic
ProcessStoreRuntimefacet with a per-domainRunResourceStorefacet (@nikscripts/effect-pm/store/RunResource), tailored toRunResource. Persistence is unchanged at the storage row level (facts and state changes still flow throughRuntimeStorage+ spine), but the public vocabulary is now strictly per-domain — there is no shared generic fact / ref / state-change envelope in any public API.RunResourceStoreis built viaProcessStore.Service<RunResourceStore>()(...)— one canonical class-style facet with a singlerecord+readblock. The class exposes:- Static per-type optional emitters:
RunResourceStore.recordRunStarted,.recordRunCompleted,.recordRunFailed,.recordStateChange, plus therecordFactBatch/recordStateChangeBatchsiblings. All no-op when the facet layer is absent and persist when composed. Every static emitter is wrapped by a built-incatchCause + logWarninginside the builder so observation failures never reach the caller's success/error channel. - Reads via
Effect.serviceOption(RunResourceStore)then instance methods (.facts,.stateHistory,.latestState,.runs,.byRun) — never static methods on the class. - Layer accessors:
RunResourceStore.layerRuntimeStorage(requiresRuntimeStorage) andRunResourceStore.layer(in-memory). - Type accessors via declaration merging:
RunResourceStore.Type(full service shape, for typing mocks /Layer.succeed) andRunResourceStore.EmitType(record-section emit shape).
Wire event types:
run-resource.fact.recorded,run-resource.state.changed. The previous genericruntime.fact.recorded/runtime.state.changedwire types remain insrc/internal/store/factEnvelope.tsas internal-only plumbing forQueueResourceStore.Breaking changes:
- Remove the public
ProcessStoreRuntimefacet (@nikscripts/effect-pm/store/Runtime). UseRunResourceStore(@nikscripts/effect-pm/store/RunResource) instead. Read viaEffect.serviceOption(RunResourceStore)and service instance methods. - Remove the generic
RuntimeFact,RuntimeRef,RuntimeStateBase,RuntimeStateChange,RuntimeFactQuery,RuntimeStateHistoryQuery,RuntimeFactRecordedEvent,RuntimeStateChangedEventtypes from the public API. Use the concreteRunResourceRef,RunResourceFact,RunResourceStateBase,RunResourceStateChange,RunResourceFactQuery,RunResourceStateHistoryQuery,RunResourceFactRecordedEvent,RunResourceStateChangedEventtypes exported from@nikscripts/effect-pm/store/RunResource. New domains must publish their own concrete types — seedocs/STORAGE.md. - Remove
ProcessStore.runtime,ProcessStore.runResource, andRuntimeObserver/RuntimeObserver.layerFromProcessStore/RuntimeObserver.layerListeners/RuntimeObserver.publishFact/RuntimeObserver.publishStateChange. Emissions now go through the per-type static optional emitters onRunResourceStore; in-process listeners are implemented by providing a custom service typed asRunResourceStore.TypeviaEffect.provideService/Layer.succeed. SeeRunResource's module doc andexamples/forms/resource/run-resource-runtime-observer.tsfor the fan-out pattern. - Remove
persistRuntimeObservationfrom the public API. The same failure-isolation behavior is now built into every static emitter by theProcessStore.Servicefactory; consumers no longer wire it manually. - Remove the public
ProcessStoreRuntimeApitype alias andRuntimeObservationListenerinterface. UseRunResourceStore.Typeinstead, and declare the local listener bag shape inline in the consumer that needs it. ProcessStore.layerRuntimeStorageandlayerProcessStorenow merge theRunResourceStorefacet layer in place ofProcessStoreRuntime.- The
byTimestampDeschelper in the internal spine now applies a stable event-id tiebreaker for events sharing the same millisecond timestamp, removing a long-standing flake inRunResourceprojection tests. This is observable only as more deterministic ordering on identical-timestamp rows infacts/stateHistory/eventsquery results.
- Static per-type optional emitters:
-
50ad1ac: Identifier-bound storage APIs for the four facets where it carries the most weight, plus a doc-comment polish pass across the storage surface. All additive — no breaking changes.
ProcessStore.withIdentifier(...)now decorates these facets withFacet.for(id)/Facet.withIdentifier(id)shortcuts that return an identifier-scoped read (and, where natural, write) API. The unboundyield* Facetshape is unchanged.Added —
for(id)bindingsQueueResourceStore.for(queueId)—entries(query?),entriesByKey(key, query?),lifecycle(query?),dedupeKeys(query?). All four narrow to the boundqueueId(and still respect any other filters supplied through the bound query).RunResourceStore.for(resourceId)—facts(query?),stateHistory(query?),latestState(),runs(),byRun(runId).ProcessLifecycleStore.for(processId)—lifecycle(opts?),latest()(returnsOption<ProcessLifecycleTag>),recordTransition({ tag, error?, occurredAt?, attributes? }).ProcessExecutionStore.for(processId)—executions(query?),hasPriorExecutions(),recordCompleted(input)/recordFailed(input)/recordInterrupted(input)(each takesOmit<ProcessExecutionFinishInput, "processId">).
Each facet gained a matching
IdentifierTypenamespace alias for typed mocks, and the newRunResourceScopedFactQuery/RunResourceScopedStateHistoryQuery/ProcessExecutionScopedQuery/ProcessExecutionScopedFinishInputtypes are re-exported from the package root.Tests
18 new conformance tests covering
for(...)andwithIdentifier({ id })narrowing, scope isolation, identifier-bound writes, and structuralIdentifierTypeaccessors — including a brand-newtest/process-store-process-lifecycle-facet.test.tssuite. Existing test surface (254) is unchanged; total now 272 passing.Documentation
docs/STORAGE.mdadds the identifier-bound APIs section (table of all built-inforfacets, an authoring template that delegates to shared private read helpers) and a section header listing the three builder sections (record,read,withIdentifier).Module-header polish across
RuntimeStorage,ProcessStore,ProcessStorage,ProcessStoreEvent,internal/store/spine.ts, and all six storage facets adds:- Field-by-field comments on
RuntimeRecordand per-method comments onProcessStoreSpine/RuntimeStorageService. - "At-a-glance" tables on
RunResourceStore,QueueResourceStore(wire types × indexed columns),ProcessExecutionStore,ProcessLifecycleStore,ProcessGroupStore,LogStore. @exampleblocks onProcessStorage.layer/ProcessStorage.layerRuntimeStorageand on theProcessStorebuilder.- Reworded
ProcessStoreEventmodule +AnalyticsEventBasedoc to drop the "legacy" framing — these primitives are the current shared surface, not transitional ones.
-
e0c63cd: Add optional deferred worker fork for priority queues: config
autoStartdefaults totrue(unchanged behavior). WhenautoStartisfalse,yield* queue.startforks the worker pool and lifecycle hook monitor; enqueue still succeeds and items accumulate until then.startis idempotent and becomes a no-op aftershutdown(warning logged). -
aa84825: Breaking:
QueueHandle,QueueResource.Service,QueueResource.Tag, andQueueResourceConfigreorder type parameters so worker/requirements channelRis last. Order isT,E(worker item effect failure),EEnqueue(schema enqueue failures, usuallyneverwithoutitemSchema),R(ambient services).QueueEnqueue-shaped enqueue helpers propagateR, andProcessGroupexportsProcessGroupQueueEnqueueRequirementsalongsideProcessGroupQueueEnqueueErrorso typedgroup.queue(Q).add(…)reflects enqueue-time dependencies.Bundled-queue composition for
ProcessGroup.Service.layernarrowsLayer.Layer<Self, …, Provided>and usesLayer.mergefor remerging queues so Context subtraction stays honest. -
006879a: Queue
rateLimit— EffectRateLimiteron workers (before concurrency semaphore).rateLimitconfig onQueueResource/Service.configure(window,limit,onExceededdefault"delay")onRateLimitExceededhook andqueue.ratelimit.exceededonQueueResourceStorequeueRateLimiterLayerfor in-memory limiter;record: "off"skips exceeded telemetry
-
c741f80: Remove the public
Xorruntime-record predicate from theQueryDSL.RuntimeRecordPredicatenow supports comparisons plusAnd/Orcomposition only, keeping future storage adapters aligned with common database predicate primitives. -
164baf7: Location-transparent resource toolkit: drive processes, queues, and schedules with the same
yield* Tagcode whether they run local or remote.The
Resourcefoundation is now a first-class, exported surface, with batteries-included resource kinds built on it. A resource is defined as a.Tag(aContext.Serviceclass) and its runtime is a separately-composed.layer— the same consumer code runs unchanged whether the resource is provided locally or reached over RPC; only the layer differs.New / newly-exported:
Resource(foundation) —Tag/layer/server/serveHttp/client/connect/connectHttp/Host, plusserveInstances/clientInstancesfor multi-instance hosting. Contracts are introspectable via the newly-exportedspecOf+methodMeta(kind/description/destructive/streaming) — enough to render a generic dashboard/TUI from any tag.ProcessResource(@nikscripts/effect-pm/ProcessContract) — a managed process as a toolkit resource:statusNow/status/schedule/logsreads,start/stop/runImmediatelylifecycle, andsetSchedule/addSchedule/clearSchedule. Auto-arms and runs immediately with its layer (passschedule: ProcessSchedule.emptyto start disarmed).- Toolkit
QueueResource(@nikscripts/effect-pm/QueueContract) — the priority-queue engine behind a location-transparent contract (control + observation + data-plane, remote-proven over http). The barrelQueueResourceremains the legacy engine during migration; import the toolkit queue from the subpath. ProcessScheduleResource(@nikscripts/effect-pm/ProcessScheduleContract) — a schedule store as its own resource: full CRUD (entries/get/has/set/add/upsert/remove/removeMany/clear), diff-basedreconcile, and achangesstream.Group(@nikscripts/effect-pm/Group) —Group.Tagorganizes member tags into a nestable tree (members/isGroup). Pure organization with no runtime: members can run on the same or different hosts, each resolving its own transport (no central manager).HostLogs(@nikscripts/effect-pm/HostLogs) — runtime-wide log capture + stream.
Enhancements:
.configurefor toolkit resources —QueueResource.configure(tag, patch)/ProcessResource.configure(tag, patch)return a config-patch layer (keyed by the tag id) that folds onto the layer's base config at build, for per-environment overrides (concurrency / rateLimit / …). The successor to the old.Service(...).configure(...).- Process run metrics —
ProcessSnapshot/processStatusgainrunsStarted/runsSucceeded/runsFailedandlastRunStartedAt/lastRunDurationMillis, counted at the single run boundary so they cover scheduled, polling, andrunImmediatelyruns.
All additive — no existing API is removed or changed; the legacy
Process/QueueResource/ProcessGroup/ControlServicesurfaces remain during migration. -
e6cae43: Redis
RuntimeStorageadapter (@nikscripts/effect-pm/storage/redis).RedisRuntimeStorage.layer/layerProcessStore— fullRuntimeStorageServiceover asend(command, …args)transport.makeInMemoryRedisSendfor tests without a Redis server.- Same query, readonly, and
transactionsemantics as memory and SQLite adapters.
-
be2890c:
RuntimeStorage.transaction— atomic read/write scopes on memory, SQLite, and Prisma adapters.- New
RuntimeStorageService.transaction(effect)runseffectwith a transactionalRuntimeStoragein context; commits on success, rolls back on failure. - Conformance tests cover commit and rollback semantics.
- New
-
0ff3793: Add semantic
ProcessStore.QueueResourcehelpers for queue entry, lifecycle, and dedupe-key records, and wireQueueResourceto write indexed runtime records throughProcessStorewhen it is available.Move the default in-memory
ProcessStorebacking store ontoRuntimeStorage, with analytics reads projected from normalized records.Remove
QueueResource's storage-orientedpersistandrefillcallbacks in favor ofProcessStorestorage and queue-boundonStart/onDrainedlifecycle hooks.Replace queue
handler,onEnqueue, andonCompletecallbacks with queue lifecycle envelopes such asonEnqueued,onExit,onCompleted,onFailed, and retry lifecycle hooks.Add pending-entry queue routing controls:
release,drop, anddeadLetter, plus corresponding lifecycle hooks.Add
releaseEncodedfor schema-backed remote/wire handoff while keeping local decodedreleaseavailable withoutitemSchema.Move Prisma storage onto the RuntimeStorage adapter over normalized RuntimeRecord rows.
Map
RuntimeStoragewrite failures intoProcessStoreWriteErrorso semantic ProcessStore APIs can surface duplicate and readonly write errors explicitly. -
3cfc25a: Breaking — silo per-domain storage facets onto
RuntimeStoragedirectly.Storage facets now own their wire codec end-to-end. Shared infrastructure (
internal/store/spine.ts,internal/store/helpers.ts) is type-agnostic; each facet builds and decodes its ownRuntimeRecordrows and pushes predicates intoRuntimeStorageQuerydirectly.Removed
AnalyticsEventenvelope union and the centralinternal/store/codec.tsdecoder. Facets no longer share a wire-event vocabulary.StoreEventQuery— replaced by per-facet query types (e.g.QueueEntryQuery,RunResourceFactQuery,ProcessExecutionQuery).- Shared Prisma event-row types are no longer re-exported from the package root
or from
ProcessStoreEvent. - Prisma row codec exports (
decodeEventRow,encodeEvent, decode errors) — removed from@nikscripts/effect-pm/prismaand@nikscripts/effect-pm/storage/prisma. Prisma now targets theRuntimeStorageadapter contract and no longer exposes a row codec at the package boundary. - Per-facet wire-event narrowing helpers
(
isQueueEntryRecordedEvent,isQueueLifecycleChangedEvent,isQueueDedupeKeyChangedEvent) — only the survivingisLogEntryRecordedguard remains, now exported from@nikscripts/effect-pm/store/Log. - Internal plumbing
src/internal/store/codec.tsandsrc/internal/store/factEnvelope.ts. TheFactEnvelope/FactEnvelopeStateChangeenvelope is gone — each facet writes its own payload shape. - Legacy spine shims (
append,appendBatch,events) on the internalProcessStoreSpine. Facets calls.create/s.createBatch/s.read/s.upsert/s.update/s.deletedirectly.
Reshaped queue wire types
The queue facet now exposes per-status concrete fact / change types instead of a single
runtime.fact.recordedenvelope:QueueEntryFact = QueueEntryEnqueuedFact | QueueEntryStartedFact | …(one type perqueue.entry.<status>).QueueLifecycleChange = QueueLifecycleStartedChange | …(one type perqueue.lifecycle.<tag>, including the newqueue.lifecycle.drained).QueueDedupeKeyChange = QueueDedupeKeyAddedChange | ….
Emit API: a single
recordEntry(fact)/recordLifecycle(change)/recordDedupeKey(change)(plus*Batchvariants). The previous per-status methods (entryEnqueued,entryStarted, …) and contextual binders (withQueue,withEntry) are gone.Read API:
entries(query?: QueueEntryQuery)with pushablequeueId,entryId,key,batchId,releaseId,types, andopts.entriesByKey(key, query?)for cross-queue key lookups.lifecycle(query?: QueueLifecycleQuery)anddedupeKeys(query?: QueueDedupeKeyQuery)with their own pushable predicates.
Per-facet ownership
LogEntryRecordedEventandisLogEntryRecordednow live insrc/store/log.ts(re-exported via the package root).ProcessLifecycleChangedEvent/ProcessLifecycleTagnow live insrc/store/processLifecycle.ts.ProcessExecutionCompletedEvent/ProcessExecutionStatusnow live insrc/store/processExecution.ts.RunResourceFactRecordedEvent/RunResourceStateChangedEventare removed (the run-resource facet returns concreteRunResourceFact[]/RunResourceStateChange[]already).ProcessStoreEventnow exports onlyJsonValue,QueryOpts,AnalyticsEventBase, and theProcessStoreWriteErrorchannel.
ProcessStore.record(...)DX flipProcessStore.recordnow takes an object literal of{ [methodName]: (s) => method }factories instead of a single(s) => apifactory. Emit method names are read from the object literal at module load time, which makes the static optional emitters typed without runtime introspection. The internalstubSpineis gone.Read query semantics
QueueResourceStore,ProcessGroupStore, andProcessExecutionStorenow applyopts.limitto the post- filter result whenever the storage query is a strict superset of the final projection (e.g. group queries that filter byattributes.groupId, orexecutions({ scheduleKey })). Previouslyopts.limitwas pushed to storage first, which could collapse alimit: Nquery that targeted a sparse post-filter to zero rows. Thebefore/aftertime window is still pushed down. A new internal helperwindowOptsis shared across the three facets.Queue dedupe-key emit
QueueResourcenow writesqueue.dedupe-key.addedrows when items acquire a dedupe key on enqueue or when a previously-extracted batch is restored after a failedreleaseEncoded, andqueue.dedupe-key.releasedrows on completion,release, drop, dead-letter, andclear. The previously-documented dedupe projection (.dedupeKeys) is now backed by real data instead of being unwired.Queue retry hook race fix
QueueResource.processItemnow releases the dedupe key (and emits thereleasedchange) BEFORE forking the exit hooks, instead of after. Hooks that synchronously callretryfrom insideonFailed/onExitno longer race the main fiber for theactiveKeysref, and the emitteddedupe-key.releasedalways precedes the retry's re-enqueuededupe-key.addedchange.Queue enqueue → worker race fix
QueueResource.enqueueInternalnow records itsentry.enqueuedanddedupe-key.addedchanges BEFORE waking the worker (signalWorkerWakeis now the last step). The worker thread shares the dedupe-key seq counter with the enqueue path; signalling first allowed the worker to process andreleasean item before the matchingaddedwas built, producing out-of-order analytics for the same dedupe-key cycle. The internalactiveKeysref is updated under the enqueue's gate before either record/wake step, so the runtime dedup invariant is unchanged.Worker route fix
QueueResource.dropand.deadLetternow persist the caller-suppliedreasonas a top-level field on the resultingqueue.entry.dropped/queue.entry.dead-letteredfact, instead of nesting it insideattributes. Reads through.entries({ types })therefore expose the typedreasonfield directly. -
d26e7ca: Refactor the SQLite
RuntimeStorageadapter to use Effect SQL (effect/unstable/sql’sSqlClientvia@effect/sql-sqlite-node) instead of callingbetter-sqlite3directly from package code.Breaking:
SQLiteRuntimeStorage.fromDatabaseis replaced byfromSqlClient, which installs the schema as anEffectand expects an existingSqlClient.makeRuntimeStorage/layerRuntimeStoragenow require an ambientScope(useEffect.scopedor@effect/vitestit.live) so the SQLite client lifetime matches the returned port; they useLayer.buildWithScopeinternally.SQLiteRuntimeStorageOpenErrorand directbetter-sqlite3/@effect/sql0.51 dependencies are removed from the package surface.Duplicate primary key inserts map both
UniqueViolationand SQLiteConstraintError(includingSQLITE_CONSTRAINT_PRIMARYKEY) toRuntimeStorageDuplicateRecordError.Persist every
RuntimeRecordfield in SQLite, keep query semantics aligned withRuntimeStorage.memoryvia sharedselectRuntimeRecordsevaluation, and document the adapter in the runtime storage guide alongside conformance and persistence tests. -
09be964: Breaking — storage facet read/write API and stack cleanup.
- Remove the
ProcessStoreBuilderentry module. Author facets withProcessStore.Service,ProcessStore.record, andProcessStore.read(seedocs/STORAGE.md). - Facet classes expose static emitters only for writes. No static read
methods on facet classes (
executions,load,facts, etc.). - Reads use
Effect.serviceOption(ProcessStoreX)andOption.matchwith explicitonNone/onSome: (store) => store.<read>(...). There is noProcessStore.withFacethelper and no stubmissingread API when the layer is absent. - Add
ProcessStorage(@nikscripts/effect-pm/ProcessStorage) to compose all built-in facet layers (memory andlayerProcessStore/ SQLite). - Remove NDJSON/file process store (
ProcessStore.file,src/storage/file.ts,examples/forms/process-store/process-store-events-file-layer.ts,test/process-store.test.ts). - Remove legacy monolith composite (
src/internal/store/composite.ts). - Consolidate storage documentation into
docs/STORAGE.md(removed scattered storage guide copies).
- Remove the
-
be2890c: Breaking — rename storage facet services to
*Store.| Before | After | | ------------------------------ | ----------------------- | |
ProcessStoreQueueResource|QueueResourceStore| |ProcessStoreRunResource|RunResourceStore| |ProcessStoreLog|LogStore| |ProcessStoreProcessExecution|ProcessExecutionStore| |ProcessStoreProcessLifecycle|ProcessLifecycleStore| |ProcessStoreProcessGroup|ProcessGroupStore|Context tags and
@nikscripts/effect-pm/store/*subpaths are unchanged.ProcessStorage.QueueResource(etc.) remain shorthand property aliases.ProcessStoreis still the facet builder module only.No deprecated re-exports of old names.
-
c838dd6: Adds public remote terminal session contracts and an Effect RPC group for future terminal transports.
-
f4e2c13: Add typed process group declarations, contracts, and remote management. Processes and queues can now be registered as canonical class services,
ProcessGroup.make(id, entries)builds a typed group from a single entries tuple,ProcessGroup.Serviceprovides an injectable group,ControlServiceexposes schema-validated group contracts atGET /contractplus contract-aligned process/queue REST routes,ProcessManager.connectcreates a typed remote client for supported process/queue controls, andProcessManager.Endpointprovides that remote client as an injectable Effect service.ProcessGroup.remoteLayercan now provide a group service from aProcessManager.Endpoint. Group service/control errors are widened throughProcessGroupControlError, including the newProcessGroupRemoteControlErrorandUnsupportedRemoteControlErrorexports; remote queue enqueue-style controls remain intentionally unsupported withUnsupportedRemoteControlErroruntil schema-backed queue item contracts land.ProcessManager.verifyContractnow compares the remote contract's group id, version, process ids, queue ids, and control sets against the local contract before reporting success.ControlServiceis now contract/REST-first: the legacyPOST /controlcommand endpoint and command request types were removed, and the CLI now calls the REST routes directly.ProcessManager.ConnectionRegistry.layerandProcessManager.ConnectionRegistry.layerConfigcan now provide typed group connection URLs;ProcessManager.connect(Group)and registry-backedProcessManager.Endpoint(Group)can build remote managers from that registry requirement.ProcessManager.cli([GroupA, GroupB])adds an initial multi-group CLI surface using the connection registry and normalized target resolution for globally unique process and queue ids. It supportsgroups,ls,verify,status <target>, processstart/stop/restart/now, and queuepause/resume/clear.The multi-group CLI supports
--jsonoutput forgroups,ls,verify, andstatus <target>.The multi-group CLI now checks target contract capabilities before issuing remote status/control requests, so unsupported process and queue commands fail locally before HTTP.
Adds the first runtime state/fact vocabulary for
RunResource. Originally landed as a genericRuntimeObserver+RuntimeFactmodel; it has since been re-shaped into a per-domainRunResourceStorestorage facet (@nikscripts/effect-pm/store/RunResource) — see the separateprocess-store-runtime-facetchangeset for the breaking shape.RunResourcepublishesrun-resource.run.started/run-resource.run.completed/run-resource.run.failedfacts plusRunResourceStatetransitions throughRunResourceStore.recordRunStarted/.recordRunCompleted/.recordRunFailed/.recordStateChangestatic optional emitters, which no-op when the facet layer is absent and persist asrun-resource.fact.recorded/run-resource.state.changedanalytics events when composed. The Prisma codec supports those event types.In-process listeners are implemented by providing a custom service typed as
RunResourceStore.TypeviaEffect.provideService/Layer.succeed— there is noRuntimeObserver.layerListenershelper.ProcessStore.events(query)now provides a generic storage-neutral event read across memory, file-backed, and Prisma implementations. Dedicated queue completion and lifecycle reads are also available across those stores.Per-domain projections live directly on the facet:
RunResourceStore.facts({ resourceId, runId?, types? }),.stateHistory({ resourceId }),.latestState(resourceId),.runs(resourceId)(paired started + ended history per run), and.byRun(runId)(facts for one specific run). There is noProcessStore.runtime.*/ProcessStore.runResource.*namespace on the combiner.RunResourcenow publishesRunResourceStatechanges for waiting, started, completed, failed, and interrupted runs throughRunResourceStore.recordStateChange.ProcessStore.file(filePath)andProcessStore.fileLayer(filePath)add an EffectFileSystem-backed NDJSON store for local durable analytics events.Adds dedicated package subpaths for service/resource imports (
/Process,/QueueResource,/ProcessGroup,/ProcessStore,/ProcessManager,/ControlService) and storage adapters (/storage/file,/storage/prisma). Root imports and the legacy/prismasubpath remain compatible.Also fixes
ProcessStoreexecution ordering consistency and keyed queueclear()dedup cleanup.
Patch Changes
- Clean stale branch-era docs, add a focused QueueResource example, and repair the combined post-merge type fixes.
- d6beaf3: Update the Effect v4 beta toolchain to
4.0.0-beta.69and raise the package peer range to^4.0.0-beta.69. - 6ce4729: QueueResource: Split worker wake (
takeNext) from drain-monitor wake so idle workers never unblockonDrained; enqueue only wakes workers. TheonDrainedlifecycle hook wakes after queues drain empty following item completion or afterclear. - 95cc8e1: QueueResource: Replace
config.refillandQueueHandle.refillwith queue-bound lifecycle hooks. UseonStart(queue)for bootstrap work andonDrained(queue)after queues drain empty once activity has awakened the drain monitor. Cold-start idle workers do not triggeronDrained. - 83bf98c: Document the roadmap to re-enable
anyUnknownInErrorContextin@effect/language-service(seedocs/plans/10-typescript-strict-unknown.md). Includes small refinements to ControlService, CLI, disarmed idle sleep, shared helpers, HTTP resource/run gate modules, and expandedProcessStorepublic TSDoc.
Unreleased
Minor Changes
- Add
Process.scheduleControlsso schedule controls (entries,set,add,clear) are available inside running process effects, matching the controls passed to thescheduleinitializer. - Add a new schedule-control example (
examples/schedule-control-surfaces.ts) demonstrating three control surfaces: initializer controls, in-effect controls, and external controller fibers. - Add two additional schedule-focused examples for organization and breadth:
examples/schedule-control-basics.tsandexamples/schedule-control-db-sync.ts. - Expand schedule-focused tests to cover in-effect schedule controls and change-signal behavior.
Current beta
Minor Changes
- Breaking — effect-first process runtime:
Process.makeis centered oneffect, with optionalpolling(Polling.spaced,Polling.acceleratingScoped, …) andschedule(ProcessSchedule.alwaysArmed,ProcessSchedule.cronMatch,ProcessSchedule.fromArmedRef, …) as layers. Compose atmake, viaProcess.providePolling/Process.provideSchedule, or when providingprocess.effectat fork time. Polling/ProcessSchedule: context services and preset layers;ProcessDetails/ProcessGroupstatus exposearmed,nextPollCadence, and schedule transition hints where available.- Supervisor:
start/startAllattaches schedule drivers; disarm pauses scheduled ticks while the fiber waits (hint-based or fallback idle sleep,Clock-aligned);cronMatchsampling uses the sameClock. - Resource modules:
QueueResource,RunResource,HttpClientRunGate, andHttpApiResourceuse the current class/service patterns documented indocs/RESOURCE-API.md. - Docs & examples:
docs/PROCESS-API.md,docs/RESOURCE-API.md,examples/queue-resource.ts, and the examples index describe the current beta surface.