@nebutra/provider-factory 0.2.2 → 0.2.3
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/package.json +14 -4
- package/.turbo/turbo-build.log +0 -17
- package/.turbo/turbo-test.log +0 -14
- package/.turbo/turbo-typecheck.log +0 -4
- package/src/__tests__/provider-factory.test.ts +0 -110
- package/src/index.ts +0 -86
- package/tsconfig.json +0 -10
- package/tsup.config.ts +0 -10
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nebutra/provider-factory",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "Neutral provider-resolution primitive: the identical 'explicit ?? env ?? detect-chain ?? fallback' selection + prod-guard shared by every provider-agnostic Nebutra package. No instantiation logic.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"license": "MIT",
|
|
@@ -10,10 +10,14 @@
|
|
|
10
10
|
"category": "platform",
|
|
11
11
|
"summary": "Shared provider-type resolution + prod safety guard"
|
|
12
12
|
},
|
|
13
|
-
"main": "./
|
|
14
|
-
"types": "./
|
|
13
|
+
"main": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
15
|
"exports": {
|
|
16
|
-
".":
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"import": "./dist/index.js",
|
|
19
|
+
"default": "./dist/index.js"
|
|
20
|
+
}
|
|
17
21
|
},
|
|
18
22
|
"dependencies": {},
|
|
19
23
|
"devDependencies": {
|
|
@@ -34,6 +38,12 @@
|
|
|
34
38
|
"publishConfig": {
|
|
35
39
|
"access": "public"
|
|
36
40
|
},
|
|
41
|
+
"files": [
|
|
42
|
+
"dist",
|
|
43
|
+
"README.md",
|
|
44
|
+
"LICENSE",
|
|
45
|
+
"CHANGELOG.md"
|
|
46
|
+
],
|
|
37
47
|
"scripts": {
|
|
38
48
|
"build": "tsup",
|
|
39
49
|
"test": "vitest run",
|
package/.turbo/turbo-build.log
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
> @nebutra/provider-factory@0.2.2 build /Users/tseka_luk/Documents/Nebutra-SaaS-Lab/Nebutra-Sailor/packages/platform/provider-factory
|
|
3
|
-
> tsup
|
|
4
|
-
|
|
5
|
-
CLI Building entry: src/index.ts
|
|
6
|
-
CLI Using tsconfig: tsconfig.json
|
|
7
|
-
CLI tsup v8.5.1
|
|
8
|
-
CLI Using tsup config: /Users/tseka_luk/Documents/Nebutra-SaaS-Lab/Nebutra-Sailor/packages/platform/provider-factory/tsup.config.ts
|
|
9
|
-
CLI Target: es2022
|
|
10
|
-
CLI Cleaning output folder
|
|
11
|
-
ESM Build start
|
|
12
|
-
ESM dist/index.js 997.00 B
|
|
13
|
-
ESM dist/index.js.map 3.97 KB
|
|
14
|
-
ESM ⚡️ Build success in 19ms
|
|
15
|
-
DTS Build start
|
|
16
|
-
DTS ⚡️ Build success in 2777ms
|
|
17
|
-
DTS dist/index.d.ts 2.53 KB
|
package/.turbo/turbo-test.log
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
> @nebutra/provider-factory@0.2.1 test /Users/tseka_luk/Documents/Nebutra-SaaS-Lab/Nebutra-Sailor/packages/platform/provider-factory
|
|
3
|
-
> vitest run
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
RUN v4.1.4 /Users/tseka_luk/Documents/Nebutra-SaaS-Lab/Nebutra-Sailor/packages/platform/provider-factory
|
|
7
|
-
|
|
8
|
-
✓ src/__tests__/provider-factory.test.ts (9 tests) 3ms
|
|
9
|
-
|
|
10
|
-
Test Files 1 passed (1)
|
|
11
|
-
Tests 9 passed (9)
|
|
12
|
-
Start at 09:38:26
|
|
13
|
-
Duration 204ms (transform 26ms, setup 0ms, import 37ms, tests 3ms, environment 0ms)
|
|
14
|
-
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
import { afterEach, describe, expect, it } from "vitest";
|
|
2
|
-
import { assertProviderAllowed, envPresent, resolveProviderType } from "../index";
|
|
3
|
-
|
|
4
|
-
const SAVED = { ...process.env };
|
|
5
|
-
afterEach(() => {
|
|
6
|
-
process.env = { ...SAVED };
|
|
7
|
-
});
|
|
8
|
-
|
|
9
|
-
type P = "qstash" | "bullmq" | "memory";
|
|
10
|
-
|
|
11
|
-
const detectors = [
|
|
12
|
-
{ provider: "qstash" as P, when: envPresent("QSTASH_TOKEN") },
|
|
13
|
-
{ provider: "bullmq" as P, when: envPresent("REDIS_URL") },
|
|
14
|
-
];
|
|
15
|
-
|
|
16
|
-
describe("resolveProviderType precedence", () => {
|
|
17
|
-
it("1. explicit config wins over everything", () => {
|
|
18
|
-
process.env.QUEUE_PROVIDER = "bullmq";
|
|
19
|
-
process.env.QSTASH_TOKEN = "x";
|
|
20
|
-
expect(
|
|
21
|
-
resolveProviderType<P>({
|
|
22
|
-
explicit: "memory",
|
|
23
|
-
envVarName: "QUEUE_PROVIDER",
|
|
24
|
-
detectors,
|
|
25
|
-
fallback: "memory",
|
|
26
|
-
}),
|
|
27
|
-
).toBe("memory");
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it("2. env var beats detection", () => {
|
|
31
|
-
process.env.QSTASH_TOKEN = "x"; // would detect qstash
|
|
32
|
-
process.env.QUEUE_PROVIDER = "bullmq";
|
|
33
|
-
expect(
|
|
34
|
-
resolveProviderType<P>({ envVarName: "QUEUE_PROVIDER", detectors, fallback: "memory" }),
|
|
35
|
-
).toBe("bullmq");
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it("3. detection chain order is honoured", () => {
|
|
39
|
-
process.env.QUEUE_PROVIDER = undefined;
|
|
40
|
-
process.env.REDIS_URL = "redis://x";
|
|
41
|
-
expect(
|
|
42
|
-
resolveProviderType<P>({ envVarName: "QUEUE_PROVIDER", detectors, fallback: "memory" }),
|
|
43
|
-
).toBe("bullmq");
|
|
44
|
-
process.env.QSTASH_TOKEN = "x";
|
|
45
|
-
expect(
|
|
46
|
-
resolveProviderType<P>({ envVarName: "QUEUE_PROVIDER", detectors, fallback: "memory" }),
|
|
47
|
-
).toBe("qstash"); // earlier detector wins
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it("4. fallback when nothing matches", () => {
|
|
51
|
-
expect(
|
|
52
|
-
resolveProviderType<P>({ envVarName: "QUEUE_PROVIDER", detectors, fallback: "memory" }),
|
|
53
|
-
).toBe("memory");
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it("trims/ignores an empty env var", () => {
|
|
57
|
-
process.env.QUEUE_PROVIDER = " ";
|
|
58
|
-
expect(
|
|
59
|
-
resolveProviderType<P>({ envVarName: "QUEUE_PROVIDER", detectors, fallback: "memory" }),
|
|
60
|
-
).toBe("memory");
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
describe("envPresent", () => {
|
|
65
|
-
it("is true only for a non-empty value", () => {
|
|
66
|
-
process.env.X = "";
|
|
67
|
-
expect(envPresent("X")()).toBe(false);
|
|
68
|
-
process.env.X = "v";
|
|
69
|
-
expect(envPresent("X")()).toBe(true);
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
describe("assertProviderAllowed", () => {
|
|
74
|
-
it("throws for a disallowed provider in production unless overridden", () => {
|
|
75
|
-
process.env.NODE_ENV = "production";
|
|
76
|
-
expect(() =>
|
|
77
|
-
assertProviderAllowed("memory", {
|
|
78
|
-
disallowedInProd: ["memory"],
|
|
79
|
-
overrideEnv: "ALLOW_MEMORY",
|
|
80
|
-
hint: "configure REDIS_URL",
|
|
81
|
-
}),
|
|
82
|
-
).toThrow(/production/);
|
|
83
|
-
process.env.ALLOW_MEMORY = "true";
|
|
84
|
-
expect(() =>
|
|
85
|
-
assertProviderAllowed("memory", {
|
|
86
|
-
disallowedInProd: ["memory"],
|
|
87
|
-
overrideEnv: "ALLOW_MEMORY",
|
|
88
|
-
hint: "configure REDIS_URL",
|
|
89
|
-
}),
|
|
90
|
-
).not.toThrow();
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it("throws the caller's exact message when provided", () => {
|
|
94
|
-
process.env.NODE_ENV = "production";
|
|
95
|
-
expect(() =>
|
|
96
|
-
assertProviderAllowed("memory", {
|
|
97
|
-
disallowedInProd: ["memory"],
|
|
98
|
-
overrideEnv: "X",
|
|
99
|
-
message: "Refusing to use the in-memory queue provider in production",
|
|
100
|
-
}),
|
|
101
|
-
).toThrow("Refusing to use the in-memory queue provider in production");
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
it("never throws outside production", () => {
|
|
105
|
-
process.env.NODE_ENV = "test";
|
|
106
|
-
expect(() =>
|
|
107
|
-
assertProviderAllowed("memory", { disallowedInProd: ["memory"], overrideEnv: "X" }),
|
|
108
|
-
).not.toThrow();
|
|
109
|
-
});
|
|
110
|
-
});
|
package/src/index.ts
DELETED
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @nebutra/provider-factory — the neutral provider-resolution primitive.
|
|
3
|
-
*
|
|
4
|
-
* Every provider-agnostic Nebutra package (queue, search, notifications,
|
|
5
|
-
* vault, uploads, …) repeated the exact same selection:
|
|
6
|
-
*
|
|
7
|
-
* providerType = explicit ?? env[`${X}_PROVIDER`] ?? detect-chain ?? fallback
|
|
8
|
-
*
|
|
9
|
-
* plus a "refuse the in-memory provider in production" guard. That selection
|
|
10
|
-
* is identical and domain-free; only the subsequent *instantiation* switch
|
|
11
|
-
* (dynamic-importing the concrete provider) is domain-specific and stays in
|
|
12
|
-
* each package. This module owns the selection, nothing else.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
/** One auto-detection rule: pick `provider` when `when()` is true. */
|
|
16
|
-
export interface ProviderDetector<T extends string> {
|
|
17
|
-
readonly provider: T;
|
|
18
|
-
readonly when: () => boolean;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/** `true` when the named env var is set to a non-empty (trimmed) value. */
|
|
22
|
-
export function envPresent(name: string): () => boolean {
|
|
23
|
-
return () => {
|
|
24
|
-
const v = process.env[name];
|
|
25
|
-
return typeof v === "string" && v.trim().length > 0;
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export interface ResolveProviderInput<T extends string> {
|
|
30
|
-
/** Explicit caller config — highest precedence. */
|
|
31
|
-
readonly explicit?: T | undefined;
|
|
32
|
-
/** Env var holding an explicit provider name (e.g. `QUEUE_PROVIDER`). */
|
|
33
|
-
readonly envVarName: string;
|
|
34
|
-
/** Ordered auto-detection rules; first match wins. */
|
|
35
|
-
readonly detectors: ReadonlyArray<ProviderDetector<T>>;
|
|
36
|
-
/** Used when nothing else resolves. */
|
|
37
|
-
readonly fallback: T;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Resolve the provider type. Precedence: explicit → env var → detector chain
|
|
42
|
-
* (in order) → fallback. An empty/whitespace env var is ignored.
|
|
43
|
-
*/
|
|
44
|
-
export function resolveProviderType<T extends string>(input: ResolveProviderInput<T>): T {
|
|
45
|
-
if (input.explicit) return input.explicit;
|
|
46
|
-
const fromEnv = process.env[input.envVarName]?.trim();
|
|
47
|
-
if (fromEnv) return fromEnv as T;
|
|
48
|
-
for (const d of input.detectors) {
|
|
49
|
-
if (d.when()) return d.provider;
|
|
50
|
-
}
|
|
51
|
-
return input.fallback;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export interface ProdGuardOptions<T extends string> {
|
|
55
|
-
/** Providers that must not run in production. */
|
|
56
|
-
readonly disallowedInProd: readonly T[];
|
|
57
|
-
/** Env var that, when "true", permits the disallowed provider anyway. */
|
|
58
|
-
readonly overrideEnv: string;
|
|
59
|
-
/** Optional remediation hint appended to the default error. */
|
|
60
|
-
readonly hint?: string;
|
|
61
|
-
/**
|
|
62
|
-
* Exact error message to throw, overriding the default. Use this to keep a
|
|
63
|
-
* package's existing wording (error strings are domain copy, not shared
|
|
64
|
-
* logic — only the prod-detection is shared).
|
|
65
|
-
*/
|
|
66
|
-
readonly message?: string;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Refuse a disallowed provider in production unless the override env is set.
|
|
71
|
-
* No-op outside `NODE_ENV==="production"`.
|
|
72
|
-
*/
|
|
73
|
-
export function assertProviderAllowed<T extends string>(
|
|
74
|
-
provider: T,
|
|
75
|
-
opts: ProdGuardOptions<T>,
|
|
76
|
-
): void {
|
|
77
|
-
if (process.env.NODE_ENV !== "production") return;
|
|
78
|
-
if (!opts.disallowedInProd.includes(provider)) return;
|
|
79
|
-
if (process.env[opts.overrideEnv] === "true") return;
|
|
80
|
-
if (opts.message) throw new Error(opts.message);
|
|
81
|
-
throw new Error(
|
|
82
|
-
`Refusing to use the "${provider}" provider in production. ` +
|
|
83
|
-
`${opts.hint ? `${opts.hint}. ` : ""}` +
|
|
84
|
-
`Set ${opts.overrideEnv}=true for an explicit temporary override.`,
|
|
85
|
-
);
|
|
86
|
-
}
|
package/tsconfig.json
DELETED