creation
There are very few way creating
import { provide, derive } from "@pumped-fn/core-next";
provide
const value = provide(() => "string");
derive
const derived = derive(value, (value) => {
/* */
});
const derivedUsingArray = derive([value, otherValue], ([value, otherValue]) => {
/* */
});
const derivedUsingObject = derive(
{ value, otherValue },
({ value, otherValue }) => {
/* */
}
);
accessing controller
const value = provide((ctl) => "string");
const otherValue = provide((ctl) => 20);
const derived = derive(value, (value, ctl) => {
/* */
});
const derivedUsingArray = derive(
[value, otherValue],
([value, otherValue], ctl) => {
/* */
}
);
const derivedUsingObject = derive(
{ value, otherValue },
({ value, otherValue }, ctl) => {
ctl.
/* */
}
);
type Controller = Core.Controller
accessing variations
import { provide, derive } from "@pumped-fn/core-next";
const value = provide(() => 0);
const derivedValue = derive(
[value.lazy, value.static, value.reactive],
([accessor, value, anotherAccessor]) => {}
);
presetting
An executor can be "assumed" to be a specific value, on scope resolving that particular executor, if the scope recognized there's an assumed value, it'll resolved with the "assumed" value instead of triggering the original factory
import { provide, derive, createScope, preset } from "@pumped-fn/core-next";
const value = provide(() => 0);
const assumedValue = preset(value, 1);
const scope = createScope(assumedValue);
const resolvedValue = await scope.resolve(value); // will be 1
Preset is the technique built-in pumped-fn to use in testing and building middleware
scope
Scope is the unit that in charge of resolving the graph of dependencies, bring value to life (an escape hatch)
import { createScope } from "@pumped-fn/core-next";
createScope
const scope = createScope();
scope.resolve
const resolvedValue = await scope.resolve(value);
scope.update
let resolvedValue = await scope.resolve(value);
// 0
await scope.update(value, 1);
await scope.update(value, (current) => 1); // react setState style
resolvedValue = await scope.resolve(value);
// 1
- Updating requires the executor to be resolved upfront, via direct resolve or as a part of the graph
On update, the following mechanism happen
- cleanups got called
- The .reactive dependencies got triggered
- factory function is called
scope.release
Release a reference and its value (and also all of dependencies relying on the reference)
const derivedValue = derive(value, (value) => value + "1");
let resolvedValue = await scope.resolve(derivedValue);
await scope.release(value);
// will also release derivedValue
scope.accessor
Retrieve the singleton of an executor respresentative in a scope
const valueAccessor = scope.accessor(value);
const getValue = valueAccessor.get(); // retrieve value. Will throw error if executor is yet resolved
const maybeValue = valueAccessor.lookup();
const resolvedValue = await valueAccessor.resolve();
typeof valueAccessor['
scope.dispose
import { provide, createScope, type Core } from "@pumped-fn/core-next";
const value = provide((ctl) => {
// acquire connection
ctl.cleanup(() => {
/** cleanup logic */
});
return 0;
});
const scope = createScope();
await scope.dispose();
Dispose will cleanup all resources resolved in the scope, also mark the scope as disposed
. Disposed scope will not be able to do anything afteward
middleware
Middleware provides cross-cutting concerns without modifying core logic. It can intercept resolution, updates, and releases.
scope.use
Register middleware to intercept scope operations:
import { createScope, middleware } from "@pumped-fn/core-next";
const scope = createScope();
const cleanup = scope.use(middleware({
init: (scope) => {
// Called when middleware is registered
},
dispose: async (scope) => {
// Called when scope is disposed
}
}));
scope.onChange
Intercept resolution and update events:
import { createScope, preset, provide } from "@pumped-fn/core-next";
const scope = createScope();
const value = provide(() => "original");
scope.onChange((event, executor, value, scope) => {
if (event === "resolve") {
console.log("Resolved:", value);
}
if (event === "update") {
console.log("Updated:", value);
}
// Return preset() to transform the value
if (value === "transform-me") {
return preset(executor, "transformed");
}
});
scope.onRelease
Handle executor cleanup:
import { createScope, provide } from "@pumped-fn/core-next";
const scope = createScope();
const connection = provide(() => ({ id: "db-1" }));
scope.onRelease(async (event, executor, scope) => {
// Cleanup when executor is released
console.log("Releasing executor");
});
practical middleware examples
import { createScope, middleware, provide, derive, preset } from "@pumped-fn/core-next";
// Analytics middleware
const analytics = middleware({
init: (scope) => {
scope.onChange((event, executor, value) => {
if (event === "resolve") {
// Track resolution metrics
}
});
}
});
// Value sanitizer
const sanitizer = middleware({
init: (scope) => {
scope.onChange((event, executor, value, scope) => {
if (typeof value === "string" && value.includes("unsafe")) {
return preset(executor, value.replace("unsafe", "safe"));
}
});
}
});
const scope = createScope();
scope.use(analytics);
scope.use(sanitizer);
meta
Meta provides type-safe decorative information attached to executors. It uses StandardSchema for validation and integrates seamlessly with middleware for runtime inspection.
creating meta functions
import { meta, custom } from "@pumped-fn/core-next";
// Create a meta function with a schema
const name = meta("service-name", custom<string>());
const port = meta("port", custom<number>());
const config = meta("config", custom<{ url: string; timeout: number }>());
attaching meta to executors
import { provide, derive, meta, custom } from "@pumped-fn/core-next";
const name = meta("name", custom<string>());
const priority = meta("priority", custom<number>());
// Attach meta during creation
const database = provide(
() => ({ connection: "postgres://..." }),
name("database"),
priority(1)
);
// Multiple metas
const cache = provide(
() => new Map(),
name("cache"),
priority(2)
);
accessing meta values
import { provide, meta, custom } from "@pumped-fn/core-next";
const name = meta("name", custom<string>());
const service = provide(() => {}, name("auth"));
// Get meta value from executor
const serviceName = name.get(service); // "auth"
// Find first matching meta
const maybeName = name.find(service); // "auth" | undefined
// Get all matching metas (executors can have multiple of same type)
const allNames = name.some(service); // string[]
meta with middleware
import { createScope, middleware, provide, meta, custom } from "@pumped-fn/core-next";
const name = meta("name", custom<string>());
const metrics = meta("metrics", custom<boolean>());
// Middleware that uses meta for conditional logic
const metricsMiddleware = middleware({
init: (scope) => {
scope.onChange((event, executor, value) => {
// Check if executor has metrics enabled
if (metrics.find(executor)) {
const serviceName = name.get(executor) ?? "unknown";
console.log(`[${serviceName}] ${event}:`, value);
}
});
}
});
const api = provide(
() => ({ endpoint: "/api" }),
name("api-service"),
metrics(true) // Enable metrics for this executor
);
const internal = provide(
() => ({ data: "internal" }),
name("internal-service")
// No metrics meta - won't be tracked
);
meta accessor integration
import { createScope, provide, meta, custom } from "@pumped-fn/core-next";
const description = meta("description", custom<string>());
const service = provide(() => "service", description("Main service"));
const scope = createScope();
const accessor = scope.accessor(service);
// Accessor includes metas
const desc = description.find(accessor); // "Main service"
practical meta patterns
import { provide, derive, meta, custom, createScope, middleware } from "@pumped-fn/core-next";
// Version tracking
const version = meta("version", custom<string>());
// Deprecation warnings
const deprecated = meta("deprecated", custom<{ since: string; alternative?: string }>());
// Service classification
const tier = meta("tier", custom<"critical" | "standard" | "low">());
// Deprecation middleware
const deprecationWarning = middleware({
init: (scope) => {
scope.onChange((event, executor) => {
const deprecation = deprecated.find(executor);
if (deprecation && event === "resolve") {
console.warn(
`Deprecated since ${deprecation.since}`,
deprecation.alternative ? `Use ${deprecation.alternative} instead` : ""
);
}
});
}
});
// Usage
const oldApi = provide(
() => ({ v1: true }),
version("1.0.0"),
deprecated({ since: "2.0.0", alternative: "newApi" }),
tier("low")
);
const newApi = provide(
() => ({ v2: true }),
version("2.0.0"),
tier("critical")
);