Welcome to Wrap! Please, take a seat and make yourself comfortable.
Originally, Vixeny developers managed functionality through Petitions
and the composer
. This method was functional but could become cumbersome specially for testing. So we decided to wrap
everything together! Mainly to have:
intuitive
, making it more accessible to developers who might be daunted by the framework’s functional nature.Currying in functional programming allows a function with multiple arguments to be decomposed into a sequence of functions, each taking a single argument. In the context of wrap
.
import { plugins, wrap } from "vixeny";
// Global options configured with plugins
const opt = plugins.globalOptions({});
// The first currying stage allows for setting initial configuration.
const handler = wrap(opt)()
.get({
path: "/",
f: () => "Morning!",
});
In the given example, wrap(opt)
is the first stage where options are provided. The second invocation ()
without arguments indicates that the initial configuration has been applied. Here’s how you might utilize the second currying stage for deeper functional composition:
import { wrap } from "vixeny";
// Define a base API wrap with a specific route prefix
const api = wrap({
wrap: {
startsWith: "/api",
},
})()
.get({
path: "/",
f: () => "Morning!",
});
// Using the second currying stage to extend with another wrap instance
const extendedHandler = wrap()(
api.unwrap(),
)
.get({
path: "/",
f: () => "Evening!",
})
// Logs all configured paths for debugging
.logPaths();
Also there is another option:
import { petitions, wrap } from "vixeny";
const custom = petitions.custom()({
path: "/custom",
f: () => new Response("Hello"),
});
const standard = petitions.add()({
path: "/custom",
f: () => "Hello",
});
wrap()(
[custom, standard],
);
The wrap
function offers several configuration options:
startWith
: This option is used to specify a base path for all the routes defined within a wrap
instance. When set, startWith
prepends the specified path to all route paths within the instance, allowing for organized and hierarchical URL structures, which is particularly useful for segmenting different areas of your application, such as admin, API, or user interfaces.import { wrap } from "vixeny";
// Define a base API wrap with a specific route prefix
const api = wrap({
wrap: {
startsWith: "/api",
},
})()
.get({
path: "/",
f: () => "Morning!",
});
// Using the second currying stage to extend with another wrap instance
const extendedHandler = wrap()(
api.unwrap(),
)
.get({
path: "/",
f: () => "Evening!",
})
// Logs all configured paths for debugging
.logPaths();
Allows the integration of different types of petitions into the current wrap instance.
Here’s how you might use addAnyPetition
to add both a custom and a standard petition to a wrap instance:
import { petitions, wrap } from "vixeny";
// Create a custom petition that responds with a custom HTTP response
const customPetition = petitions.custom()({
path: "/custom",
f: () => new Response("Custom Response Content"),
});
// Create a standard petition with a simple greeting message
const standardPetition = petitions.standard()({
path: "/greet",
f: () => "Hello, World!",
});
// Initialize a new wrap instance and add both petitions
const app = wrap()()
.addAnyPetition(customPetition)
.addAnyPetition(standardPetition);
// This setup enables the app to handle both '/custom' and '/greet' paths with their respective responses
Allows for changing the wrap options of the current instance, creating a new instance with the updated options while preserving the existing petitions.
import { plugins, wrap } from "vixeny";
// Define a plugin that determines user roles
const userIs = (name) =>
plugins.type({
name: Symbol.for("anyName"),
type: {},
// Returns the name in a nested function format
f: () => () => () => name,
});
// Create instances of the plugin for different users
const bubbles = userIs("Bubbles");
const avant = userIs("Avant");
// Initial configuration with the 'Bubbles' user plugin
const initialOptions = plugins.globalOptions({
cyclePlugin: {
user: bubbles,
},
});
// Create a wrap instance with the initial configuration
const handler = wrap(initialOptions)()
.get({
path: "/",
f: ({ user }) => `Hello, ${user}!`,
});
// Changing the configuration to use the 'Avant' user plugin
const updatedHandler = handler.changeOptions({
cyclePlugin: {
user: avant,
},
});
// Showcase
const req = new Request("http://localhost/");
console.log(
await handler.testPetitions()(req).then((r) => r.text()),
await handlerToMock.testPetitions()(req).then((r) => r.text()),
);
// The updated handler will now greet 'Avant' instead of 'Bubbles'
Consolidates all petitions within a wrap instance into a cohesive, operational unit, making the instance ready for execution.
import { wrap } from "vixeny";
const app = wrap()()
.get({
path: "/",
f: () => "Welcome to our homepage!",
})
.get({
path: "/about",
f: () => "Learn more about us on this page.",
})
.compose();
// This function can now be used to handle incoming HTTP requests
// Example of handling a request to the root path
const request = new Request("http://localhost/");
// Outputs: "Welcome to our homepage!"
console.log(await app(request).then((r) => r.text()));
customPetition
allows for defining a custom Petition where f
returns either a Response
or a Promise<Response>
. This method is suitable for scenarios where the standard response structure does not fit your needs.
Headers have to be passed manually
import { petitions, wrap } from "vixeny";
const handler = wrap({
// CORS are paseed to `headers`
cors: {
allowOrigins: "*",
},
})()
.customPetition({
path: "/fromInside",
headings: {
// Adding "content-type": "text/html"
headers: ".html",
},
f: ({ headers }) =>
new Response("hello", {
headers,
}),
})
.compose();
// "access-control-allow-origin": "*",
// "content-type": "text/html",
console.log(handler(new Request("http://localhost/fromInside")));
Provides insight into the internal state and usage of the context within the last added petition.
Context:
isUsing
: Shows the components that you are using.isAsync
: Shows if the current petition is async.Components:
$NAME
: This information depends on the component.Plugin:
$NAME
: This information depends on the plugin.import { wrap } from "vixeny";
wrap()()
.get({
path: "/one",
f: () => null,
})
/**
* Console logging:
*
* --- Context ---
* using:
* isAsync: false
*/
.debugLast()
.get({
path: "/two/:id",
f: (c) => c.param.id,
})
/**
* Console logging:
*
* --- Context ---
* using: param
* isAsync: false
* --- Components ---
* param: [id]
*/
.debugLast();
Dynamically removes one or more petitions based on their paths from an existing wrap instance.
import { wrap } from "vixeny";
// Creating a wrap with multiple routes
const handler = wrap()()
.get({
path: "/pathOne",
f: () => "one",
})
.get({
path: "/pathTwo",
f: () => "two",
})
.get({
path: "/pathThree",
f: () => "three",
});
// Excluding multiple paths dynamically
const handlerWithoutTwoThree = handler.exclude(["/pathTwo", "/pathThree"]);
// Remaining active path
handlerWithoutTwoThree.logPaths(); // Outputs: ['/pathOne']
Applies a function over each petition, wrapping each result, and then flattens all results into a single wrap.
flatMap is mostly used for the maintainers of
vixeny/core
import { wrap } from "vixeny";
// Creating a wrap
const handler = wrap()()
.get({
path: "/pathOne",
f: () => "one",
})
.get({
path: "/pathTwo",
f: () => "two",
})
.get({
path: "/pathThree",
f: () => "two",
});
// Gives granural control over the primitives inside of the `wrap`
// In this examples we are making all the petitions `Async`
const asyncHandler = handler
.flatMap(
(p) =>
handler.pure({
...p,
isAsync: true,
}),
)
// Checking the wrapped petitions
.flatMap(
(p) => void console.log(p) ?? handler.pure(p),
);
Dynamically composes a petition from the current wrap. Takes path exists among the defined petitions, it either applies provided modifications (useful for mocking or altering request handling behavior) or proceeds with the default petition handling logic.
const request = new Request("http://localhost/one");
const paths = wrap()()
.get({
path: "/one",
f: (c) => c.date.toString(),
});
// Handling the request without modifications
const handles = paths.handleRequest("/one")({});
// Handling the request with a mock date injected
const mocked = paths.handleRequest("/one")({
options: {
setDate: 1710592645075,
},
});
// Outputs the current date
console.log(await handles(request).then((r) => r.text()));
// Outputs the mocked date: "1710592645075"
console.log(await mocked(request).then((r) => r.text()));
Allows petitions to operate independently of the broader context managed by the composer, ensuring consistent behavior with a fixed setup
petitionWithoutCTX
do not depend on the context provided by the composer
. This makes them suitable for fixed-response endpoints or for integrating legacy systems where dynamic context manipulation is unnecessary or undesirable.Here’s an example illustrating the use of petitionWithoutCTX
to set up a static content delivery endpoint, which does not require any dynamic context manipulation:
import { wrap } from "vixeny";
const staticContentHandler = wrap()()
.petitionWithoutCTX({
path: "/static",
r: () =>
new Response("<p>Static Content</p>", {
headers: new Headers({ "Content-Type": "text/html" }),
}),
})
.testPetitions();
// Example of serving a request to the static path
const request = new Request("http://localhost/static");
await staticContentHandler(request)
.then((res) => res.text())
.then(console.log);
Creates a new wrap instance that is functionally equivalent to its input but disconnected from the original instance’s state.
Pure is mostly used for the maintainers of
vixeny/core
Preserve the currentoptions
import { wrap } from "vixeny";
const baseWrap = wrap()()
.get({
path: "/example",
f: () => "Original Response",
});
// Using pure to replicate the base configuration without carrying over any added state or modifications
const replicatedWrap = baseWrap.pure();
// Modifying the original wrap instance
baseWrap.get({
path: "/modified",
f: () => "Modified Response",
});
// Logging paths to show that the replicated wrap does not include modifications
replicatedWrap.logPaths(); // Output: ['/example']
baseWrap.logPaths(); // Output: ['/modified']
Defines a standard petition that processes HTTP requests and returns a response.
Here’s a straightforward example that demonstrates setting up a basic route with get
:
import { petitions, wrap } from "vixeny";
// Another way to see `get`
const std = petitions.add()({
path: "/anotherPath",
f: () => "Hello",
});
// Creating a wrap instance with a standard petition
const app = wrap()()
.get({
path: "/greet",
f: () => "Hello, World!",
})
.addAnyPetition(std);
// This setup enables the app to handle a GET request at '/greet' with a simple greeting message
const request = new Request("http://localhost/greet");
// Example of handling the request and outputting the response
app(request).then((response) => console.log(response.text())); // Outputs: "Hello, World!"
Simulates a server environment for testing the functionality of all wrapped requests, enabling comprehensive testing without an external runtime.
Wraps the
petitions
inPromise.resolve
import { wrap } from "vixeny";
const handler = wrap()()
.get({
path: "/helloWold",
f: () => "helloWold",
})
.get({
path: "/one",
f: () => "one",
})
.get({
path: "/two",
f: () => "two",
});
// Creates a handler to test
const testHandler = handler.testPetitions();
console.log(
await testHandler(new Request("http://localhost/helloWold"))
.then((response) => response.text()),
);
Combines petitions from another wrap instance with the current one, useful for modularizing and reusing petitions across different parts of the application.
import { wrap } from "vixeny";
const extension = wrap({
wrap: {
startsWith: "/extension",
},
})()
.get({
path: "/one",
f: () => "one",
})
.get({
path: "/two",
f: () => "two",
});
const handler = wrap()()
.union(extension.unwrap())
.get({
path: "/hello",
f: () => "helloWorld",
})
.logPaths(); // Outputs paths from both the current wrap and the imported `extension`.
Decomposes a wrapped instance into its constituent petitions, making them accessible for individual use or recombination.
unwrap
breaks down a wrap
instance into its underlying petitions, making them accessible for individual use or recombination.startWith
option is set, unwrap
ensures that all paths within the unwrapped petitions are prefixed accordingly, facilitating organized and hierarchical URL structures.import { wrap } from "vixeny";
// Creating two separate wrap instances
const api = wrap({
wrap: {
startsWith: "/api",
},
})()
.get({
path: "/users",
f: () => "User List",
});
const admin = wrap({
wrap: {
startsWith: "/admin",
},
})()
.get({
path: "/controls",
f: () => "Admin Controls",
});
// Combining unwrapped petitions from both instances into a single wrap instance
const combined = wrap()()
.union(api.unwrap())
.union(admin.unwrap());
// Using the combined instance to configure a server
combined.logPaths(); // Logs paths like '/api/users' and '/admin/controls'
Practical Use