Skip to content

Commit

Permalink
feat(evolver): add frost and sandbox options to allow control over be…
Browse files Browse the repository at this point in the history
…havior of input data with respect to output

changes to default behavior, which changes how output behaves

BREAKING CHANGE: by default, output is frozen and sandbox mode is copy
  • Loading branch information
Jake Lauer authored and Jake Lauer committed Aug 1, 2024
1 parent 34fa279 commit 59a2bb8
Show file tree
Hide file tree
Showing 13 changed files with 365 additions and 214 deletions.
5 changes: 3 additions & 2 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@
"request": "launch",
"name": "Run Base Test (single)",
"runtimeExecutable": "pnpm",
"runtimeArgs": [ "test:base", "--timeout", "60000", "--", "--grep", "\"should not break when dates are present\"" ],
//"runtimeArgs": [ "test", "--", "-t", "\"should return an equal object when using modify mode\"", "--testTimeout", "60000" ],
"runtimeArgs": [ "test", "--", "Evolver.test.ts", "--maxWorkers=1" ],
"internalConsoleOptions": "openOnSessionStart",
"cwd": "${workspaceFolder}",
"outputCapture": "std"
Expand All @@ -48,7 +49,7 @@
"request": "launch",
"name": "Run Sandbox Tests",
"runtimeExecutable": "pnpm",
"runtimeArgs": [ "--prefix", "./packages/sandbox", "test", "--", "--timeout", "60000", "--grep", "\"fail function\"" ],
"runtimeArgs": [ "--prefix", "./packages/sandbox", "test", "--", "--testTimeout", "60000", "-t", "\"fail function\"" ],
"internalConsoleOptions": "openOnSessionStart",
"cwd": "${workspaceFolder}",
"outputCapture": "std"
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"lint-fix": "eslint ./ --fix --resolve-plugins-relative-to=./",
"test:pkg": "pnpm -r run test",
"test:pkg:sandbox": "pnpm -r run test",
"test:watch": "vitest watch",
"test": "vitest run",
"// ==== BUILD ==== //": "",
"build:base": "tsup && resolve-tspaths",
Expand All @@ -37,7 +38,6 @@
"ex:ttt": "pnpm --prefix ./.examples/tic-tac-toe start",
"// === HELPERS === //": "",
"package": "sh -c 'pnpm --prefix ./packages/$0 run $1'",
"test:watch": "vitest watch",
"coverage": "vitest run --coverage"
},
"dependencies": {
Expand Down
23 changes: 16 additions & 7 deletions src/Theseus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import type { BroadcasterObserver } from "@Broadcast/BroadcasterObserver";
import type { DestroyCallback } from "./lib/Broadcast/Broadcaster.js";
import type { BaseParams, ITheseus } from "@Types/Theseus";
import {
cement, frost, sandbox,
defrost, frost, sandbox,
} from "theseus-sandbox";

const log = getTheseusLogger("Observation");
Expand All @@ -33,9 +33,9 @@ export class Theseus<
* @param initialData The starting data (can be null)
* @param params
*/
private constructor(data: TData, params?: BaseParams<TData, TObserverType>)
private constructor(data: TData, protected options?: BaseParams<TData, TObserverType>)
{
super(params?.broadcasterParams);
super(options?.broadcasterParams);

this.#id = uuidv4();
this.setData(data);
Expand All @@ -62,9 +62,16 @@ export class Theseus<

private setData = (data: TData) =>
{
this.internalState = sandbox(frost(data), {
mode: "copy",
});
const dataInput = this.options?.frost?.manual
? data
: frost(data);

this.internalState = sandbox(
dataInput,
this.options?.sandbox ?? {
mode: "copy",
},
);
};

/**
Expand All @@ -75,7 +82,9 @@ export class Theseus<
private async update(data: TData)
{
Object.assign(this.internalState, data);
const newState = cement(this.internalState);
const newState = this.options?.frost?.autoDefrost
? defrost(this.internalState)
: this.internalState;

this.setData(newState);

Expand Down
3 changes: 2 additions & 1 deletion src/lib/Evolvers/Evolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export class Evolver<
* Constructor for Evolver. Initializes the evolver with a name, set of mutators, and optional
* configuration.
*/
protected constructor(name: TEvolverName, mutators: TMutators, options?: EvolverOptions<TParamNoun>)
protected constructor(name: TEvolverName, mutators: TMutators, protected options?: EvolverOptions<TParamNoun>)
{
const normalizedName = this.normalizeName(name);
this.paramNoun = options?.noun ?? ("input" as TParamNoun);
Expand Down Expand Up @@ -148,6 +148,7 @@ export class Evolver<
this.paramNoun,
this.mutators,
this.#theseusId,
this.options,
);

if (!this.cachedChainableMutatorSet)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
cement, frost, getSandboxChanges, isFrost,
} from "theseus-sandbox";
import { containsSandbox } from "theseus-sandbox";
import type { SandboxableParams } from "../../Types/SandboxParams.js";
/**
* Extends MutatorSet to provide chainable mutation operations on evolver data. This class allows mutations to
* be chained together in a fluent manner, enhancing the clarity and expressiveness of state evolution logic.
Expand All @@ -36,9 +37,15 @@ export class ChainableMutatorSetBuilder<
// Created by createChainingProxy; always matches the type of the current instance
private chainingProxy: typeof this;

constructor(inputData: TData, paramNoun: TParamNoun, mutators: TMutators, theseusId?: string)
constructor(
inputData: TData,
paramNoun: TParamNoun,
mutators: TMutators,
theseusId?: string,
sandboxableOptions?: SandboxableParams,
)
{
super(inputData, paramNoun, mutators, theseusId);
super(inputData, paramNoun, mutators, theseusId, sandboxableOptions);

this.mutatorQueue = ChainableMutatorQueue.create({
paramNoun,
Expand All @@ -51,6 +58,7 @@ export class ChainableMutatorSetBuilder<
target: this,
observationId: this.__theseusId as string,
queue: this.mutatorQueue,
sandboxableOptions,
});
}

Expand Down Expand Up @@ -104,7 +112,7 @@ export class ChainableMutatorSetBuilder<
result = cement(result);
}

if (!isFrost(result))
if (!isFrost(result) && !this.sandboxableOptions?.frost?.manual)
{
result = frost(result);
}
Expand Down Expand Up @@ -145,9 +153,9 @@ export class ChainableMutatorSetBuilder<
TData extends object,
TParamNoun extends string,
TMutators extends MutatorDefs<TData, TParamNoun>,
>(data: TData, paramNoun: TParamNoun, mutators: TMutators, theseusId?: string)
>(data: TData, paramNoun: TParamNoun, mutators: TMutators, theseusId?: string, sandboxableOptions?: SandboxableParams)
{
const chain = new ChainableMutatorSetBuilder(data, paramNoun, mutators, theseusId);
const chain = new ChainableMutatorSetBuilder(data, paramNoun, mutators, theseusId, sandboxableOptions);
const proxy = chain.chainingProxy;
return this.castToChainableMutators(proxy);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Theseus } from "@/Theseus";
import { getTheseusLogger } from "theseus-logger";
import { ProxyActions, ProxyActionType } from "../proxy-actions.js";
import type { ProxyActionMapParameters } from "../proxy-action-map.js";
import { cement } from "theseus-sandbox";
import { defrost } from "theseus-sandbox";

const log = getTheseusLogger("mutator-proxy-action");

Expand All @@ -21,7 +21,10 @@ export class MutatorAction extends ProxyActions
}

private handleMutatorCall({
target, prop, proxyManager, proxy,
target,
prop,
proxyManager,
proxy,
}: ProxyActionMapParameters)
{
return (...args: any[]) =>
Expand All @@ -34,7 +37,9 @@ export class MutatorAction extends ProxyActions
{
const complete = (execResult: any) =>
{
const cementedResult = cement(execResult);
const cementedResult = proxyManager.params.sandboxableOptions?.frost?.autoDefrost
? defrost(execResult)
: execResult;

if (proxyManager.params.observationId)
{
Expand All @@ -44,7 +49,9 @@ export class MutatorAction extends ProxyActions
return proxyManager.finalizeAndReset(cementedResult);
};

return queueResult instanceof Promise ? queueResult.then(complete) : complete(queueResult);
return queueResult instanceof Promise
? queueResult.then(complete)
: complete(queueResult);
}

return proxy;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { SortaPromise } from "../../../Types/EvolverTypes.js";
import type { ChainableMutatorSetBuilder } from "../ChainableMutatorSetBuilder.js";
import type { ChainableMutatorQueue } from "../ChainableMutatorQueue.js";
import { ProxyActionMap } from "./proxy-action-map.js";
import type { SandboxableParams } from "../../../Types/SandboxParams.js";

/**
* ChainingProxy is a class that enables method chaining and queueing of operations on a proxied object. It
Expand All @@ -24,7 +25,8 @@ export class ChainingProxyManager<TTarget extends ChainableMutatorSetBuilder<any
public readonly params: {
target: TTarget;
observationId?: string;
queue: ChainableMutatorQueue<any, any>;
queue: ChainableMutatorQueue<any, any>;
sandboxableOptions?: SandboxableParams;
},
)
{
Expand Down Expand Up @@ -154,7 +156,8 @@ export class ChainingProxyManager<TTarget extends ChainableMutatorSetBuilder<any
export function createChainingProxy<TTarget extends ChainableMutatorSetBuilder<any, any, any>>(params: {
target: TTarget;
observationId?: string;
queue: ChainableMutatorQueue<any, any>;
queue: ChainableMutatorQueue<any, any>;
sandboxableOptions?: SandboxableParams;
}): TTarget
{
const chainingProxy = new ChainingProxyManager(params);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type { GenericMutator, MutatorDefs } from "../../Types/MutatorTypes.js";
import {
cement, frost, isSandbox, sandbox,
} from "theseus-sandbox";
import type { SandboxableParams } from "../../Types/SandboxParams.js";
/**
* Represents a set of mutators that can be applied to an evolver's data. It provides the infrastructure for
* adding mutator functions to the evolver and executing these functions to mutate the evolver's state.
Expand All @@ -33,6 +34,7 @@ export class MutatorSetBuilder<
protected readonly paramNoun: TParamNoun,
protected readonly mutators: TMutators,
theseusId?: string,
protected readonly sandboxableOptions?: SandboxableParams,
)
{
this.__theseusId = theseusId;
Expand All @@ -48,7 +50,9 @@ export class MutatorSetBuilder<
private setInitialData(data: TData)
{
const wrappedInput = this.inputToObject(data);
const frosted = frost(wrappedInput);
const frosted = !this.sandboxableOptions?.frost?.manual
? frost(wrappedInput)
: wrappedInput;
this._data = frosted as Record<TParamNoun, TData>;
}

Expand Down Expand Up @@ -76,7 +80,11 @@ export class MutatorSetBuilder<

protected augmentData(data: TData)
{
const sb = sandbox(this.data);
const dataInput = this.sandboxableOptions?.frost?.manual
? this.data
: frost(this.data);

const sb = sandbox(dataInput, this.sandboxableOptions?.sandbox);
sb[this.paramNoun] = data;
return sb;
}
Expand Down Expand Up @@ -145,12 +153,16 @@ export class MutatorSetBuilder<
[selfPath]: (...args: any[]) =>
{
Theseus.incrementStackDepth(this.__theseusId);

const draft =
isSandbox(this.data[this.paramNoun]) ?
this.data
: sandbox(this.data, {
mode: "copy",
});
: sandbox(
this.data,
this.sandboxableOptions?.sandbox ?? {
mode: "copy",
},
);

let funcResult: SortaPromise<TData>;
try
Expand Down Expand Up @@ -225,7 +237,7 @@ export class MutatorSetBuilder<
{
Object.assign(draft, generatedData);

draft = sandbox(draft);
draft = sandbox(draft, this.sandboxableOptions?.sandbox);

return draft;
};
Expand Down
12 changes: 2 additions & 10 deletions src/lib/Evolvers/Types/EvolverTypes.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { SandboxParams } from "theseus-sandbox";
import type { ChainableMutators } from "./ChainableTypes.js";
import type { MutatorDefs } from "./MutatorTypes.js";
import type { SandboxableParams } from "./SandboxParams.js";
export interface TypeAccess<
TData extends object,
TEvolverName extends string,
Expand Down Expand Up @@ -92,19 +92,11 @@ export type EvolveObject<
TMutators extends MutatorDefs<TData, TParamNoun>,
> = ReturnType<EvolverInstance<TData, TEvolverName, TParamNoun, TMutators>["evolve"]>;

export interface EvolverOptions<TParamNoun extends string = "input"> {
export interface EvolverOptions<TParamNoun extends string = "input"> extends SandboxableParams {
/**
* The noun used to name the data parameter in each mutator.
*/
noun?: TParamNoun;
/**
* Configuration for sandboxing the data object.
*/
sandbox?: Partial<SandboxParams>;
/**
* Whether to frost the data object before applying any mutations.
*/
frost?: boolean;
}

/**
Expand Down
21 changes: 21 additions & 0 deletions src/lib/Evolvers/Types/SandboxParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { SandboxParams } from "theseus-sandbox";

export interface SandboxableParams {
/**
* Configuration for sandboxing the data object.
*/
sandbox?: Partial<SandboxParams>;
frost?: {
/**
* Whether to disable automatic frosting of the provided data object. By default, the data object will be
* automatically frosted when passed to an Evolver and the output data will remain frosty in order to
* provide maximal immutability. Manually defrosting the resulting data object is still possible.
*
* If `true`, the data object will not be automatically frosted, and immutability will not be guaranteed.
* The returned data object's frost status will be the same as the input data object's frost status.
*
* @default false
*/
manual?: boolean;
}
}
Loading

0 comments on commit 59a2bb8

Please sign in to comment.