Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,10 @@
"dev": "tsup --watch",
"test": "npm run build && npm run test:unit",
"test:unit": "c8 --check-coverage node scripts/run-tests.js && tsd",
"lint": "eslint 'src/**/*.{ts,tsx}'",
"lint:fix": "npm run lint -- --fix && npm run format",
"lint": "npm run format:check && eslint 'src/**/*.{ts,tsx}'",
"lint:fix": "npm run lint -- --fix",
"format": "prettier -w .",
"format:check": "prettier --check .",
"benchmark": "npm run build && node benchmarks/run.mjs",
"prepare": "npm run build",
"prepublishOnly": "npm run build && npm run test"
Expand Down
14 changes: 8 additions & 6 deletions src/container/container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,18 @@ export class Container {

async get<ProviderDepsMap extends BaseProviderDepsMap, Value>(
prov: ProviderDef<ProviderDepsMap, Value>,
ctx: ModuleContext
ctx: ModuleContext,
): Promise<Value> {
let provider = prov
let provider = prov;
if (provider.isContract) {
const bound = ctx.bindings.find(p => p.name === provider.name)
const bound = ctx.bindings.find((p) => p.name === provider.name);
if (!bound) {
throw new Error(`Contract provider "${prov.name}" has no binding in module "${ctx.name}".`)
throw new Error(
`Contract provider "${prov.name}" has no binding in module "${ctx.name}".`,
);
}

provider = bound
provider = bound;
}
let value = this.singletons.get(provider) as Promise<Value> | undefined;
if (!value) {
Expand All @@ -33,7 +35,7 @@ export class Container {

private async instantiate<ProviderDepsMap extends BaseProviderDepsMap, Value>(
provider: ProviderDef<ProviderDepsMap, Value>,
ctx: ModuleContext
ctx: ModuleContext,
): Promise<Value> {
const depsEntries = Object.entries(provider.deps) as [
string,
Expand Down
6 changes: 5 additions & 1 deletion src/hooks/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,11 @@ export function createHooks<
ctx: ModuleContext,
cache: AdapterCache,
) {
const providerMap = await resolveProviderMap(container, deps as never, ctx);
const providerMap = await resolveProviderMap(
container,
deps as never,
ctx,
);
const adapsMap = await resolveAdapterMap(fastify, adaps as never, cache);
const hookBuilder =
type === "http"
Expand Down
6 changes: 3 additions & 3 deletions src/modules/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ export async function registerModule(
const plugin = async (instance: FastifyInstance) => {
const ctx = {
name: mod.name,
bindings: mod.bindings
}
bindings: mod.bindings,
};
const adapterCache: AdapterCache = new WeakMap();
if (Array.isArray(mod.hooks)) {
for (const hookConfig of mod.hooks) {
Expand Down Expand Up @@ -88,7 +88,7 @@ export async function registerModule(
export async function resolveProviderMap(
container: Container,
map: ProvidersMap,
ctx: ModuleContext
ctx: ModuleContext,
) {
const out: Record<string, unknown> = {};
for (const [k, p] of Object.entries(map)) {
Expand Down
138 changes: 69 additions & 69 deletions src/providers/providers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -450,88 +450,88 @@ describe("contract bindings", () => {
t.assert.ok(bound);
});

test("contracts work as nested provider dependencies", async (t: TestContext) => {
t.plan(1);

let sent = "";
const MAILER_TOKEN = "mailer";
const Mailer = contract<{ send: (to: string, body: string) => void }>(
MAILER_TOKEN,
);

const smtpMailer = createProvider({
name: MAILER_TOKEN,
expose: () => ({
send: (to: string, body: string) => {
sent = `${to}:${body}`;
},
}),
});
test("contracts work as nested provider dependencies", async (t: TestContext) => {
t.plan(1);

const notificationService = createProvider({
name: "notification-service",
deps: { mailer: Mailer },
expose: ({ mailer }) => ({
notify: (email: string) => mailer.send(email, "Notification"),
}),
});
let sent = "";
const MAILER_TOKEN = "mailer";
const Mailer = contract<{ send: (to: string, body: string) => void }>(
MAILER_TOKEN,
);

const userService = createProvider({
name: "user-service",
deps: { notification: notificationService },
expose: ({ notification }) => ({
welcome: (email: string) => notification.notify(email),
}),
});
const smtpMailer = createProvider({
name: MAILER_TOKEN,
expose: () => ({
send: (to: string, body: string) => {
sent = `${to}:${body}`;
},
}),
});

const installer = createInstaller({
deps: { userService },
install: async ({ deps }) =>
deps.userService.welcome("nested@example.com"),
});
const notificationService = createProvider({
name: "notification-service",
deps: { mailer: Mailer },
expose: ({ mailer }) => ({
notify: (email: string) => mailer.send(email, "Notification"),
}),
});

const root = createModule({
name: "root",
installers: [installer],
bindings: [smtpMailer],
});
const userService = createProvider({
name: "user-service",
deps: { notification: notificationService },
expose: ({ notification }) => ({
welcome: (email: string) => notification.notify(email),
}),
});

const app = await createApp({ root });
await app.close();
const installer = createInstaller({
deps: { userService },
install: async ({ deps }) =>
deps.userService.welcome("nested@example.com"),
});

t.assert.strictEqual(sent, "nested@example.com:Notification");
const root = createModule({
name: "root",
installers: [installer],
bindings: [smtpMailer],
});

test("contracts work in installer direct dependencies", async (t: TestContext) => {
t.plan(1);
const app = await createApp({ root });
await app.close();

let tracked = "";
const TRACKER_TOKEN = "tracker";
const Tracker = contract<{ track: (msg: string) => void }>(TRACKER_TOKEN);
t.assert.strictEqual(sent, "nested@example.com:Notification");
});

const fakeTracker = createProvider({
name: TRACKER_TOKEN,
expose: () => ({
track: (msg: string) => {
tracked = msg;
},
}),
});
test("contracts work in installer direct dependencies", async (t: TestContext) => {
t.plan(1);

const installer = createInstaller({
deps: { tracker: Tracker },
install: async ({ deps }) => deps.tracker.track("installer-contract-ok"),
});
let tracked = "";
const TRACKER_TOKEN = "tracker";
const Tracker = contract<{ track: (msg: string) => void }>(TRACKER_TOKEN);

const root = createModule({
name: "root",
installers: [installer],
bindings: [fakeTracker],
});
const fakeTracker = createProvider({
name: TRACKER_TOKEN,
expose: () => ({
track: (msg: string) => {
tracked = msg;
},
}),
});

const app = await createApp({ root });
await app.close();
const installer = createInstaller({
deps: { tracker: Tracker },
install: async ({ deps }) => deps.tracker.track("installer-contract-ok"),
});

t.assert.strictEqual(tracked, "installer-contract-ok");
const root = createModule({
name: "root",
installers: [installer],
bindings: [fakeTracker],
});

const app = await createApp({ root });
await app.close();

t.assert.strictEqual(tracked, "installer-contract-ok");
});
});