Skip to content

Commit

Permalink
✨backport Context#expect() to v3
Browse files Browse the repository at this point in the history
In v4, context will not be an operation. Instead, you use the
`expect()` method to either get the value or throw an error if it does
not exist.

This adds that method to v3, and deprecates the old usage.
  • Loading branch information
cowboyd committed Dec 14, 2024
1 parent 43a692b commit 4d3be6c
Show file tree
Hide file tree
Showing 7 changed files with 35 additions and 16 deletions.
22 changes: 15 additions & 7 deletions lib/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { create } from "./run/create.ts";
import { useScope } from "./run/scope.ts";

export function createContext<T>(key: string, defaultValue?: T): Context<T> {

let context: Context<T> = create<Context<T>>(`Context`, { key }, {
defaultValue,
*get() {
Expand All @@ -13,16 +14,23 @@ export function createContext<T>(key: string, defaultValue?: T): Context<T> {
let scope = yield* useScope();
return scope.set(context, value);
},
*[Symbol.iterator]() {
let value = yield* context.get();
if (typeof value === "undefined") {
throw new MissingContextError(`missing required context: '${key}'`);
} else {
return value;
}
expect,
[Symbol.iterator]() {
console.warn(`⚠️ using a context (${key}) directly as an operation is deprecated. Use context.expect() instead`);
context[Symbol.iterator] = expect;
return expect();
},
});

function* expect() {
let value = yield* context.get();
if (typeof value === "undefined") {
throw new MissingContextError(`missing required context: '${key}'`);
} else {
return value;
}
}

return context;
}

Expand Down
2 changes: 1 addition & 1 deletion lib/each.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ each.next = function next() {
return {
name: "each.next()",
*[Symbol.iterator]() {
let stack = yield* EachStack;
let stack = yield* EachStack.expect();
let context = stack[stack.length - 1];
if (!context) {
let error = new Error(`cannot call next() outside of an iteration`);
Expand Down
2 changes: 1 addition & 1 deletion lib/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import process from "node:process";
* @param returns an operation that exits the program
*/
export function* exit(status: number, message?: string): Operation<void> {
let escape = yield* ExitContext;
let escape = yield* ExitContext.expect();
escape({ status, message });
}

Expand Down
2 changes: 1 addition & 1 deletion lib/signal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ export function createSignal<T, TClose = never>(): Signal<T, TClose> {
let subscribers = new Set<Queue<T, TClose>>();

let subscribe = resource<Subscription<T, TClose>>(function* (provide) {
let newQueue = yield* SignalQueueFactory;
let newQueue = yield* SignalQueueFactory.expect();
let queue = newQueue<T, TClose>();
subscribers.add(queue);

Expand Down
11 changes: 11 additions & 0 deletions lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,8 +263,19 @@ export interface Context<T> extends Operation<T> {
/**
* Get the value of the Context from the current scope. If it has not been
* set, and there is no default value, then this will return `undefined`.
*
* @return an operation provding the context value in the current scope if it is defined.
*/
get(): Operation<T | undefined>;


/**
* Get the value of the Context from the current scope. An error will be raised if the
* context has not been defined.
*
* @return an operation providing the context value in the current scope.
*/
expect(): Operation<T>;
}

/**
Expand Down
6 changes: 3 additions & 3 deletions test/context.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ describe("context", () => {

it("can be set within a given scope, but reverts after", async () => {
let values = await run(function* () {
let before = yield* numbers;
let before = yield* numbers.expect();
let within = yield* call(function* () {
yield* numbers.set(22);
return yield* numbers;
return yield* numbers.expect();
});
let after = yield* numbers;
return [before, within, after];
Expand All @@ -36,7 +36,7 @@ describe("context", () => {

it("is an error to expect() when context is missing", async () => {
await expect(run(function* () {
yield* createContext("missing");
yield* createContext("missing").expect();
})).rejects.toHaveProperty("name", "MissingContextError");
});
});
6 changes: 3 additions & 3 deletions test/scope.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ describe("Scope", () => {
let cxt = createContext<number>("number");

function* incr() {
let value = yield* cxt;
let value = yield* cxt.expect();
return yield* cxt.set(value + 1);
}

Expand All @@ -91,7 +91,7 @@ describe("Scope", () => {
let second = yield* scope.run(incr);
let third = yield* scope.run(incr);

expect(yield* cxt).toEqual(1);
expect(yield* cxt.expect()).toEqual(1);
expect(first).toEqual(2);
expect(second).toEqual(2);
expect(third).toEqual(2);
Expand All @@ -104,7 +104,7 @@ describe("Scope", () => {
expect(scope.get(context)).toEqual(void 0);
expect(scope.set(context, "Hello World!")).toEqual("Hello World!");
expect(scope.get(context)).toEqual("Hello World!");
await expect(scope.run(() => context)).resolves.toEqual("Hello World!");
await expect(scope.run(() => context.expect())).resolves.toEqual("Hello World!");
});

it("propagates uncaught errors within a scope", async () => {
Expand Down

0 comments on commit 4d3be6c

Please sign in to comment.