@xandreed/runtime 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Xand Reed
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # @xandreed/runtime
2
+
3
+ Efferent composable agent framework, 0.3.0. Bun on Linux.
4
+
5
+ Documentation: https://github.com/xandreeddev/efferent
@@ -0,0 +1,88 @@
1
+ import { Effect } from "effect";
2
+ import { HarnessConfig, HarnessError } from "@xandreed/core";
3
+ import type { Plugin } from "@xandreed/core";
4
+ export interface ConfigSource {
5
+ readonly path: string;
6
+ readonly config: HarnessConfig;
7
+ readonly plugins?: ReadonlyArray<Plugin>;
8
+ }
9
+ export declare const mergeConfig: (base: HarnessConfig, overlay: HarnessConfig) => HarnessConfig;
10
+ export declare const decodeConfig: (input: unknown, source: string) => Effect.Effect<HarnessConfig, HarnessError>;
11
+ export declare const loadConfig: (options: {
12
+ readonly workspace: string;
13
+ readonly home: string;
14
+ readonly preset: HarnessConfig;
15
+ readonly profile?: string;
16
+ readonly invocation?: HarnessConfig;
17
+ }) => Effect.Effect<{
18
+ config: {
19
+ profile: string;
20
+ plugins?: readonly {
21
+ readonly id: string;
22
+ readonly options?: {
23
+ readonly [x: string]: unknown;
24
+ } | undefined;
25
+ readonly use: string;
26
+ readonly enabled?: boolean | undefined;
27
+ }[] | undefined;
28
+ bindings?: {
29
+ readonly [x: string]: string;
30
+ } | undefined;
31
+ system?: string | undefined;
32
+ version: 1;
33
+ profiles?: {
34
+ readonly [x: string]: {
35
+ readonly plugins?: readonly {
36
+ readonly id: string;
37
+ readonly options?: {
38
+ readonly [x: string]: unknown;
39
+ } | undefined;
40
+ readonly use: string;
41
+ readonly enabled?: boolean | undefined;
42
+ }[] | undefined;
43
+ readonly bindings?: {
44
+ readonly [x: string]: string;
45
+ } | undefined;
46
+ readonly system?: string | undefined;
47
+ };
48
+ } | undefined;
49
+ };
50
+ sources: {
51
+ path: string;
52
+ config: {
53
+ readonly plugins?: readonly {
54
+ readonly id: string;
55
+ readonly options?: {
56
+ readonly [x: string]: unknown;
57
+ } | undefined;
58
+ readonly use: string;
59
+ readonly enabled?: boolean | undefined;
60
+ }[] | undefined;
61
+ readonly bindings?: {
62
+ readonly [x: string]: string;
63
+ } | undefined;
64
+ readonly system?: string | undefined;
65
+ readonly version: 1;
66
+ readonly profile?: string | undefined;
67
+ readonly profiles?: {
68
+ readonly [x: string]: {
69
+ readonly plugins?: readonly {
70
+ readonly id: string;
71
+ readonly options?: {
72
+ readonly [x: string]: unknown;
73
+ } | undefined;
74
+ readonly use: string;
75
+ readonly enabled?: boolean | undefined;
76
+ }[] | undefined;
77
+ readonly bindings?: {
78
+ readonly [x: string]: string;
79
+ } | undefined;
80
+ readonly system?: string | undefined;
81
+ };
82
+ } | undefined;
83
+ };
84
+ }[];
85
+ plugins: Plugin[];
86
+ }, HarnessError, never>;
87
+ export declare const writeConfig: (path: string, config: HarnessConfig) => Effect.Effect<void, HarnessError, never>;
88
+ export declare const redact: (value: unknown) => unknown;
package/dist/config.js ADDED
@@ -0,0 +1,50 @@
1
+ import { access, mkdir, readFile, rename, writeFile } from "node:fs/promises";
2
+ import { dirname, join } from "node:path";
3
+ import { pathToFileURL } from "node:url";
4
+ import { Effect, Option, Schema } from "effect";
5
+ import { HarnessConfig, HarnessError } from "@xandreed/core";
6
+ const mergeEntries = (left, right) => right.reduce((entries, incoming) => {
7
+ const old = entries.find((entry) => entry.id === incoming.id), next = old === void 0 || old.use !== incoming.use ? incoming : {
8
+ ...old,
9
+ ...incoming,
10
+ options: { ...old.options, ...incoming.options }
11
+ };
12
+ return old === void 0 ? [...entries, next] : entries.map((entry) => entry.id === incoming.id ? next : entry);
13
+ }, left);
14
+ export const mergeConfig = (base, overlay) => ({
15
+ ...base,
16
+ ...overlay,
17
+ plugins: mergeEntries(base.plugins ?? [], overlay.plugins ?? []),
18
+ bindings: { ...base.bindings, ...overlay.bindings },
19
+ profiles: { ...base.profiles, ...overlay.profiles }
20
+ }), decodeConfig = (input, source) => Schema.decodeUnknown(HarnessConfig)(input, { onExcessProperty: "error" }).pipe(Effect.mapError((error) => new HarnessError({ code: "config.invalid", message: `${source}: ${String(error)}` })));
21
+ const io = (action, label) => Effect.tryPromise({
22
+ try: action,
23
+ catch: (error) => new HarnessError({ code: "config.io", message: `${label}: ${String(error)}` })
24
+ }), exists = (path) => Effect.tryPromise({ try: () => access(path), catch: () => !1 }).pipe(Effect.as(!0), Effect.orElseSucceed(() => !1)), readJson = (path) => io(() => readFile(path, "utf8"), path).pipe(Effect.flatMap((text) => Schema.decodeUnknown(Schema.parseJson(HarnessConfig))(text, { onExcessProperty: "error" })), Effect.mapError((error) => new HarnessError({ code: "config.invalid", message: `${path}: ${String(error)}` }))), readBase = (directory) => Effect.gen(function* () {
25
+ const json = join(directory, "efferent.config.json"), ts = join(directory, "efferent.config.ts"), found = yield* Effect.filter([json, ts], exists);
26
+ if (found.length > 1)
27
+ return yield* Effect.fail(new HarnessError({ code: "config.ambiguous", message: `Choose one base configuration in ${directory}: JSON or TypeScript` }));
28
+ const path = found[0];
29
+ if (path === void 0)
30
+ return Option.none();
31
+ if (!path.endsWith(".ts"))
32
+ return Option.some({ path, config: yield* readJson(path) });
33
+ const module = yield* io(() => import(`${pathToFileURL(path).href}?revision=${Date.now()}`), path), config = yield* decodeConfig(module.default, path), plugins = module.plugins ?? [];
34
+ if (!Array.isArray(plugins) || plugins.some((value) => typeof value !== "object" || value === null || !("build" in value) || typeof value.build !== "function" || !("id" in value) || typeof value.id !== "string"))
35
+ return yield* Effect.fail(new HarnessError({ code: "config.plugins", message: `${path}: plugins must be an array of definePlugin definitions` }));
36
+ return Option.some({ path, config, plugins });
37
+ });
38
+ export const loadConfig = (options) => Effect.gen(function* () {
39
+ const sources = (yield* Effect.forEach([join(options.home, ".efferent"), options.workspace], readBase)).flatMap(Option.toArray), combined = sources.reduce((config, source) => mergeConfig(config, source.config), options.preset), profileName = options.profile ?? combined.profile ?? "smith", profile = combined.profiles?.[profileName];
40
+ if (profile === void 0 && profileName !== "smith")
41
+ return yield* Effect.fail(new HarnessError({ code: "config.profile", message: `Unknown profile: ${profileName}` }));
42
+ const selected = profile === void 0 ? combined : mergeConfig(combined, { version: 1, ...profile }), overridesPath = join(options.workspace, ".efferent", "overrides.json"), override = (yield* exists(overridesPath)) ? yield* readJson(overridesPath) : { version: 1 }, allSources = [...sources, { path: `profile:${profileName}`, config: { version: 1, ...profile } }, { path: overridesPath, config: override }];
43
+ return { config: { ...mergeConfig(mergeConfig(selected, override), options.invocation ?? { version: 1 }), profile: profileName }, sources: allSources.map(({ path, config }) => ({ path, config })), plugins: sources.flatMap((source) => source.plugins ?? []) };
44
+ }), writeConfig = (path, config) => decodeConfig(config, path).pipe(Effect.flatMap((validated) => io(async () => {
45
+ await mkdir(dirname(path), { recursive: !0 });
46
+ const temporary = `${path}.${crypto.randomUUID()}.tmp`;
47
+ await writeFile(temporary, `${JSON.stringify(validated, null, 2)}
48
+ `, { mode: 384 });
49
+ await rename(temporary, path);
50
+ }, path))), redact = (value) => Array.isArray(value) ? value.map(redact) : value !== null && typeof value === "object" ? Object.fromEntries(Object.entries(value).map(([key, item]) => [key, /secret|password|token|api.?key|credential/i.test(key) ? "[redacted]" : redact(item)])) : value;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,17 @@
1
+ import { Context, Effect, Scope } from "effect";
2
+ import { HarnessError } from "@xandreed/core";
3
+ import type { HarnessConfig, Plugin, PluginEntry } from "@xandreed/core";
4
+ export interface PluginNode {
5
+ readonly entry: PluginEntry;
6
+ readonly plugin: Plugin;
7
+ readonly options: Readonly<Record<string, unknown>>;
8
+ }
9
+ export interface PluginGraph {
10
+ readonly nodes: ReadonlyArray<PluginNode>;
11
+ readonly providers: Readonly<Record<string, string>>;
12
+ readonly config: HarnessConfig;
13
+ }
14
+ export declare const resolveGraph: (config: HarnessConfig, plugins: ReadonlyArray<Plugin>, external?: ReadonlyArray<string>) => Effect.Effect<PluginGraph, HarnessError>;
15
+ /** Every activation is scoped. A failed staging scope is disposed by its caller. */
16
+ export declare const activateGraph: (graph: PluginGraph, lifetime: "runtime" | "session", services: Context.Context<never>, scope: Scope.Scope) => Effect.Effect<Context.Context<never>, HarnessError>;
17
+ export declare const graphFingerprint: (graph: PluginGraph, scope?: "runtime" | "session") => string;
package/dist/graph.js ADDED
@@ -0,0 +1,62 @@
1
+ import { Context, Effect, Schema, Scope } from "effect";
2
+ import { createHash } from "node:crypto";
3
+ import { HarnessError, PLUGIN_API_VERSION } from "@xandreed/core";
4
+ const invalid = (message) => Effect.fail(new HarnessError({ code: "config.graph", message }));
5
+ export const resolveGraph = (config, plugins, external = []) => Effect.gen(function* () {
6
+ const duplicatePlugin = plugins.find((plugin, index) => plugins.findIndex((other) => other.id === plugin.id) !== index);
7
+ if (duplicatePlugin !== void 0)
8
+ return yield* invalid(`duplicate plugin definition: ${duplicatePlugin.id}`);
9
+ const entries = (config.plugins ?? []).filter((entry) => entry.enabled !== !1), duplicate = entries.find((entry, index) => entries.findIndex((other) => other.id === entry.id) !== index);
10
+ if (duplicate !== void 0)
11
+ return yield* invalid(`duplicate plugin instance: ${duplicate.id}`);
12
+ const nodes = yield* Effect.forEach(entries, (entry) => Effect.gen(function* () {
13
+ const plugin = plugins.find((candidate) => candidate.id === entry.use);
14
+ if (plugin === void 0)
15
+ return yield* invalid(`plugin ${entry.use} is not installed (instance ${entry.id})`);
16
+ if (plugin.apiVersion !== PLUGIN_API_VERSION)
17
+ return yield* invalid(`${entry.id}: plugin API ${plugin.apiVersion} is incompatible with ${PLUGIN_API_VERSION}`);
18
+ const options = { ...plugin.defaults, ...entry.options };
19
+ yield* Schema.decodeUnknown(plugin.schema)(options, { onExcessProperty: "error" }).pipe(Effect.mapError((error) => new HarnessError({ code: "config.options", plugin: entry.id, message: String(error) })));
20
+ return { entry, plugin, options };
21
+ })), keys = [...new Set(nodes.flatMap((node) => node.plugin.provides))], providers = Object.fromEntries(yield* Effect.forEach(keys, (key) => Effect.gen(function* () {
22
+ const candidates = nodes.filter((node) => node.plugin.provides.includes(key)), binding = config.bindings?.[key];
23
+ if (binding !== void 0 && !candidates.some((node) => node.entry.id === binding))
24
+ return yield* invalid(`${key}: binding ${binding} does not provide this service`);
25
+ if (candidates.length > 1 && binding === void 0)
26
+ return yield* invalid(`${key}: choose a binding from ${candidates.map((node) => node.entry.id).join(", ")}`);
27
+ return [key, binding ?? candidates[0].entry.id];
28
+ })));
29
+ yield* Effect.forEach(Object.keys(config.bindings ?? {}), (key) => keys.includes(key) ? Effect.void : invalid(`binding references an unavailable service: ${key}`));
30
+ yield* Effect.forEach(nodes, (node) => Effect.forEach(node.plugin.requires, (key) => {
31
+ const provider = nodes.find((candidate) => candidate.entry.id === providers[key]);
32
+ if (provider === void 0 && !external.includes(key))
33
+ return invalid(`${node.entry.id} requires missing service ${key}`);
34
+ if (node.plugin.scope === "runtime" && provider?.plugin.scope === "session")
35
+ return invalid(`${node.entry.id}: runtime plugins cannot depend on session service ${key}`);
36
+ return Effect.void;
37
+ }));
38
+ const order = (remaining, sorted) => {
39
+ if (remaining.length === 0)
40
+ return Effect.succeed(sorted);
41
+ const ready = remaining.filter((node) => node.plugin.requires.every((key) => external.includes(key) || sorted.some((candidate) => candidate.entry.id === providers[key])));
42
+ if (ready.length === 0)
43
+ return invalid(`dependency cycle: ${remaining.map((node) => node.entry.id).join(" \u2192 ")}`);
44
+ return Effect.suspend(() => order(remaining.filter((node) => !ready.includes(node)), [...sorted, ...ready]));
45
+ };
46
+ return { nodes: yield* order(nodes, []), providers, config };
47
+ }), activateGraph = (graph, lifetime, services, scope) => Effect.reduce(graph.nodes.filter((node) => node.plugin.scope === lifetime), services, (context, node) => Effect.gen(function* () {
48
+ const dependencies = Context.unsafeMake(new Map(node.plugin.requires.flatMap((key) => context.unsafeMap.has(key) ? [[key, context.unsafeMap.get(key)]] : []))), built = yield* Scope.extend(node.plugin.build(node.options, dependencies), scope), missing = node.plugin.provides.filter((key) => !built.unsafeMap.has(key));
49
+ if (missing.length > 0)
50
+ return yield* invalid(`${node.entry.id} did not provide declared services: ${missing.join(", ")}`);
51
+ const selected = new Map(node.plugin.provides.flatMap((key) => graph.providers[key] === node.entry.id ? [[key, built.unsafeMap.get(key)]] : []));
52
+ return Context.merge(context, Context.unsafeMake(selected));
53
+ })), graphFingerprint = (graph, scope) => createHash("sha256").update(JSON.stringify({
54
+ nodes: graph.nodes.filter((node) => scope === void 0 || node.plugin.scope === scope).map((node) => ({
55
+ id: node.entry.id,
56
+ use: node.plugin.id,
57
+ version: node.plugin.version,
58
+ options: node.options
59
+ })),
60
+ bindings: Object.fromEntries(Object.entries(graph.providers).filter(([, id]) => scope === void 0 || graph.nodes.some((node) => node.entry.id === id && node.plugin.scope === scope))),
61
+ ...scope === "runtime" ? {} : { system: graph.config.system }
62
+ })).digest("hex");
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,4 @@
1
+ export * from "./graph.js";
2
+ export * from "./config.js";
3
+ export * from "./plugins.js";
4
+ export * from "./live-layer.adapter.js";
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export * from "./graph.js";
2
+ export * from "./config.js";
3
+ export * from "./plugins.js";
4
+ export * from "./live-layer.adapter.js";
@@ -0,0 +1,11 @@
1
+ import { Effect, Scope } from "effect";
2
+ import type { Layer } from "effect";
3
+ import { HarnessError } from "@xandreed/core";
4
+ /** Scoped generations for replaceable modules. Each use pins one successful
5
+ * generation; retiring a generation does not close its resources until its
6
+ * last user completes. Failed staging never replaces the working services. */
7
+ export declare const makeLiveLayer: <A, E, R>(initial: Layer.Layer<A, E, R>) => Effect.Effect<{
8
+ use: <B, E2, R2>(effect: Effect.Effect<B, E2, R2>) => Effect.Effect<B, HarnessError | E2, Exclude<R2, A>>;
9
+ generation: Effect.Effect<number, never, never>;
10
+ replace: (layer: Layer.Layer<A, E, R>) => Effect.Effect<number, HarnessError | E, Exclude<R, R>>;
11
+ }, E, Scope.Scope | R>;
@@ -0,0 +1,48 @@
1
+ import { Context, Effect, Exit, Ref, Scope } from "effect";
2
+ import * as Layers from "effect/Layer";
3
+ import { HarnessError } from "@xandreed/core";
4
+ export const makeLiveLayer = (initial) => Effect.gen(function* () {
5
+ const dependencies = yield* Effect.context(), gate = yield* Effect.makeSemaphore(1), generations = yield* Ref.make(new Map), current = yield* Ref.make(0), closed = yield* Ref.make(!1), unavailable = () => new HarnessError({ code: "module.closed", message: "Module registry is closed" }), stage = (layer, id) => Effect.uninterruptibleMask((restore) => Effect.gen(function* () {
6
+ const scope = yield* Scope.make(), services = yield* restore(Layers.buildWithScope(layer, scope).pipe(Effect.provide(dependencies))).pipe(Effect.onExit((exit) => Exit.isFailure(exit) ? Scope.close(scope, exit) : Effect.void));
7
+ return { id, services, scope, leases: 0, retired: !1 };
8
+ })), first = yield* stage(initial, 0);
9
+ yield* Ref.set(generations, new Map([[0, first]]));
10
+ yield* Effect.addFinalizer(() => gate.withPermits(1)(Effect.gen(function* () {
11
+ yield* Ref.set(closed, !0);
12
+ const remaining = yield* Ref.getAndSet(generations, new Map);
13
+ yield* Effect.forEach(remaining.values(), (generation) => Scope.close(generation.scope, Exit.void));
14
+ })));
15
+ const acquire = gate.withPermits(1)(Effect.gen(function* () {
16
+ if (yield* Ref.get(closed))
17
+ return yield* Effect.fail(unavailable());
18
+ const id = yield* Ref.get(current), all = yield* Ref.get(generations), generation = all.get(id);
19
+ if (!generation)
20
+ return yield* Effect.fail(unavailable());
21
+ yield* Ref.set(generations, new Map([...all, [id, { ...generation, leases: generation.leases + 1 }]]));
22
+ return generation;
23
+ })), release = (lease) => gate.withPermits(1)(Effect.gen(function* () {
24
+ const all = yield* Ref.get(generations), generation = all.get(lease.id);
25
+ if (!generation)
26
+ return;
27
+ const next = { ...generation, leases: generation.leases - 1 };
28
+ if (next.retired && next.leases === 0) {
29
+ yield* Ref.set(generations, new Map([...all].filter(([id]) => id !== next.id)));
30
+ yield* Scope.close(next.scope, Exit.void);
31
+ } else
32
+ yield* Ref.set(generations, new Map([...all, [next.id, next]]));
33
+ }));
34
+ return {
35
+ use: (effect) => Effect.acquireUseRelease(acquire, (generation) => effect.pipe(Effect.provide(generation.services)), release),
36
+ generation: Ref.get(current),
37
+ replace: (layer) => gate.withPermits(1)(Effect.uninterruptibleMask((restore) => Effect.gen(function* () {
38
+ if (yield* Ref.get(closed))
39
+ return yield* Effect.fail(unavailable());
40
+ const id = yield* Ref.get(current), next = yield* restore(stage(layer, id + 1)), all = yield* Ref.get(generations), previous = all.get(id), retained = [...all].filter(([key]) => key !== id);
41
+ yield* Ref.set(generations, new Map([...retained, ...previous && previous.leases > 0 ? [[id, { ...previous, retired: !0 }]] : [], [next.id, next]]));
42
+ yield* Ref.set(current, next.id);
43
+ if (previous && previous.leases === 0)
44
+ yield* Scope.close(previous.scope, Exit.void);
45
+ return next.id;
46
+ })))
47
+ };
48
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,5 @@
1
+ import { Effect, JSONSchema } from "effect";
2
+ import { HarnessError } from "@xandreed/core";
3
+ import type { HarnessConfig, Plugin } from "@xandreed/core";
4
+ export declare const loadPlugins: (config: HarnessConfig, builtins: ReadonlyArray<Plugin>, workspace: string, home: string) => Effect.Effect<ReadonlyArray<Plugin>, HarnessError>;
5
+ export declare const pluginSchema: (plugin: Plugin) => JSONSchema.JsonSchema7Root;
@@ -0,0 +1,15 @@
1
+ import { createRequire } from "node:module";
2
+ import { join, resolve } from "node:path";
3
+ import { pathToFileURL } from "node:url";
4
+ import { Effect, JSONSchema } from "effect";
5
+ import { HarnessError } from "@xandreed/core";
6
+ export const loadPlugins = (config, builtins, workspace, home) => Effect.reduce([...new Set((config.plugins ?? []).filter((entry) => entry.enabled !== !1).map((entry) => entry.use))], builtins, (loaded, use) => {
7
+ if (loaded.some((plugin) => plugin.id === use))
8
+ return Effect.succeed(loaded);
9
+ return (use.startsWith(".") || use.startsWith("/") ? Effect.succeed(resolve(workspace, use)) : Effect.try(() => createRequire(join(workspace, "package.json")).resolve(use)).pipe(Effect.orElse(() => Effect.try(() => createRequire(join(home, ".efferent/plugins/package.json")).resolve(use))))).pipe(Effect.flatMap((path) => Effect.tryPromise(() => import(pathToFileURL(path).href))), Effect.map((module) => {
10
+ const value = module.default;
11
+ if (value === null || typeof value !== "object" || typeof value.build !== "function" || !Array.isArray(value.requires) || !Array.isArray(value.provides) || typeof value.version !== "string")
12
+ return { valid: !1 };
13
+ return { valid: !0, plugin: { ...value, id: use } };
14
+ }), Effect.mapError((error) => new HarnessError({ code: "plugin.load", plugin: use, message: `Cannot load installed plugin ${use}: ${String(error)}` })), Effect.flatMap((result) => result.valid ? Effect.succeed([...loaded, result.plugin]) : Effect.fail(new HarnessError({ code: "plugin.manifest", plugin: use, message: "The module must default-export a definePlugin definition" }))));
15
+ }), pluginSchema = (plugin) => JSONSchema.make(plugin.schema);
@@ -0,0 +1,7 @@
1
+ import { Context } from "effect";
2
+ declare const Version_base: Context.TagClass<Version, "test/Version", {
3
+ readonly value: number;
4
+ }>;
5
+ export declare class Version extends Version_base {
6
+ }
7
+ export {};
@@ -0,0 +1,4 @@
1
+ import { Context } from "effect";
2
+
3
+ export class Version extends Context.Tag("test/Version")() {
4
+ }
@@ -0,0 +1,8 @@
1
+ import { Context } from "effect";
2
+ declare const Value_base: Context.TagClass<Value, "test/Value", number>;
3
+ export declare class Value extends Value_base {
4
+ }
5
+ declare const Consumer_base: Context.TagClass<Consumer, "test/Consumer", number>;
6
+ export declare class Consumer extends Consumer_base {
7
+ }
8
+ export {};
@@ -0,0 +1,7 @@
1
+ import { Context } from "effect";
2
+
3
+ export class Value extends Context.Tag("test/Value")() {
4
+ }
5
+
6
+ export class Consumer extends Context.Tag("test/Consumer")() {
7
+ }
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@xandreed/runtime",
3
+ "version": "0.3.0",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "exports": {
7
+ ".": {
8
+ "types": "./dist/index.d.ts",
9
+ "default": "./dist/index.js"
10
+ }
11
+ },
12
+ "dependencies": {
13
+ "@xandreed/core": "0.3.0",
14
+ "effect": "3.21.4"
15
+ },
16
+ "private": false,
17
+ "types": "./dist/index.d.ts",
18
+ "files": [
19
+ "dist",
20
+ "skills",
21
+ "profiles",
22
+ "README.md",
23
+ "LICENSE"
24
+ ],
25
+ "engines": {
26
+ "bun": ">=1.3.0"
27
+ },
28
+ "license": "MIT"
29
+ }