@di-framework/cloudfoundry 5.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/README.md +128 -0
- package/dist/application-info.d.ts +6 -0
- package/dist/application-info.js +126 -0
- package/dist/bindings.d.ts +9 -0
- package/dist/bindings.js +61 -0
- package/dist/creators/amqp.d.ts +6 -0
- package/dist/creators/amqp.js +109 -0
- package/dist/creators/blob-storage.d.ts +6 -0
- package/dist/creators/blob-storage.js +95 -0
- package/dist/creators/redis.d.ts +6 -0
- package/dist/creators/redis.js +95 -0
- package/dist/creators/relational.d.ts +6 -0
- package/dist/creators/relational.js +179 -0
- package/dist/creators/user-provided.d.ts +6 -0
- package/dist/creators/user-provided.js +36 -0
- package/dist/decorators.d.ts +34 -0
- package/dist/decorators.js +202 -0
- package/dist/detector.d.ts +22 -0
- package/dist/detector.js +35 -0
- package/dist/environment.d.ts +62 -0
- package/dist/environment.js +326 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +11 -0
- package/dist/spi/creator.d.ts +15 -0
- package/dist/spi/creator.js +1 -0
- package/dist/spi/registry.d.ts +28 -0
- package/dist/spi/registry.js +161 -0
- package/dist/types.d.ts +145 -0
- package/dist/types.js +5 -0
- package/package.json +51 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
const POSTGRES_PATTERNS = ['postgres', 'postgresql', 'elephantsql', 'crunchy'];
|
|
2
|
+
const MYSQL_PATTERNS = ['mysql', 'cleardb', 'mariadb'];
|
|
3
|
+
const SQLITE_PATTERNS = ['sqlite', 'sqlite3'];
|
|
4
|
+
function matchesAny(str, patterns) {
|
|
5
|
+
const lower = str.toLowerCase();
|
|
6
|
+
return patterns.some((p) => lower.includes(p));
|
|
7
|
+
}
|
|
8
|
+
function detectDialectFromScheme(scheme) {
|
|
9
|
+
const s = scheme.toLowerCase().replace(':', '');
|
|
10
|
+
if (s === 'postgres' || s === 'postgresql')
|
|
11
|
+
return 'postgres';
|
|
12
|
+
if (s === 'mysql')
|
|
13
|
+
return 'mysql';
|
|
14
|
+
if (s === 'mariadb')
|
|
15
|
+
return 'mariadb';
|
|
16
|
+
if (s === 'sqlite' || s === 'sqlite3')
|
|
17
|
+
return 'sqlite';
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
export class RelationalServiceInfoCreator {
|
|
21
|
+
accept(serviceData) {
|
|
22
|
+
const label = serviceData.label || '';
|
|
23
|
+
const tags = (serviceData.tags || []).map((t) => String(t).toLowerCase());
|
|
24
|
+
const creds = (serviceData.credentials || {});
|
|
25
|
+
// 1. Label match
|
|
26
|
+
if (matchesAny(label, POSTGRES_PATTERNS) ||
|
|
27
|
+
matchesAny(label, MYSQL_PATTERNS) ||
|
|
28
|
+
matchesAny(label, SQLITE_PATTERNS)) {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
// 2. Tag match
|
|
32
|
+
if (tags.some((t) => POSTGRES_PATTERNS.some((p) => t.includes(p)) ||
|
|
33
|
+
MYSQL_PATTERNS.some((p) => t.includes(p)) ||
|
|
34
|
+
SQLITE_PATTERNS.some((p) => t.includes(p)) ||
|
|
35
|
+
t === 'relational' ||
|
|
36
|
+
t === 'rdbms')) {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
// 3. Credentials URI / JDBC / scheme inspection
|
|
40
|
+
const uriCandidate = String(creds.uri ?? creds.url ?? creds.jdbcUrl ?? '');
|
|
41
|
+
if (uriCandidate.startsWith('postgres://') || uriCandidate.startsWith('postgresql://')) {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
if (uriCandidate.startsWith('mysql://') || uriCandidate.startsWith('mariadb://')) {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
if (uriCandidate.startsWith('sqlite://') || uriCandidate.startsWith('sqlite:')) {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
if (uriCandidate.startsWith('jdbc:postgresql:') || uriCandidate.startsWith('jdbc:mysql:')) {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
createServiceInfo(serviceData) {
|
|
56
|
+
const creds = (serviceData.credentials || {});
|
|
57
|
+
const tags = (serviceData.tags || []).map(String);
|
|
58
|
+
const label = serviceData.label || '';
|
|
59
|
+
let rawUri = String(creds.uri ?? creds.url ?? '');
|
|
60
|
+
const jdbcUrlCandidate = creds.jdbcUrl ? String(creds.jdbcUrl) : undefined;
|
|
61
|
+
let dialect = 'postgres';
|
|
62
|
+
if (matchesAny(label, SQLITE_PATTERNS) || tags.some((t) => matchesAny(t, SQLITE_PATTERNS))) {
|
|
63
|
+
dialect = 'sqlite';
|
|
64
|
+
}
|
|
65
|
+
else if (matchesAny(label, MYSQL_PATTERNS) ||
|
|
66
|
+
tags.some((t) => matchesAny(t, MYSQL_PATTERNS))) {
|
|
67
|
+
dialect = matchesAny(label, ['mariadb']) || tags.includes('mariadb') ? 'mariadb' : 'mysql';
|
|
68
|
+
}
|
|
69
|
+
let host = String(creds.host ?? creds.hostname ?? 'localhost');
|
|
70
|
+
let port = typeof creds.port === 'number' ? creds.port : 5432;
|
|
71
|
+
if (typeof creds.port === 'string' && creds.port.trim().length > 0) {
|
|
72
|
+
const p = parseInt(creds.port, 10);
|
|
73
|
+
if (!Number.isNaN(p))
|
|
74
|
+
port = p;
|
|
75
|
+
}
|
|
76
|
+
let database = String(creationalDbName(creds));
|
|
77
|
+
let username = (creds.username ?? creds.user) ? String(creds.username ?? creds.user) : undefined;
|
|
78
|
+
let password = (creds.password ?? creds.pass) ? String(creds.password ?? creds.pass) : undefined;
|
|
79
|
+
let ssl = typeof creds.ssl === 'boolean' ? creds.ssl : undefined;
|
|
80
|
+
if (rawUri.startsWith('jdbc:')) {
|
|
81
|
+
// e.g. jdbc:postgresql://host:port/database
|
|
82
|
+
const normalized = rawUri.replace(/^jdbc:/, '');
|
|
83
|
+
try {
|
|
84
|
+
const parsed = new URL(normalized);
|
|
85
|
+
const schemeDialect = detectDialectFromScheme(parsed.protocol);
|
|
86
|
+
if (schemeDialect)
|
|
87
|
+
dialect = schemeDialect;
|
|
88
|
+
if (parsed.hostname)
|
|
89
|
+
host = parsed.hostname;
|
|
90
|
+
if (parsed.port)
|
|
91
|
+
port = parseInt(parsed.port, 10);
|
|
92
|
+
if (parsed.pathname && parsed.pathname.length > 1) {
|
|
93
|
+
database = decodeURIComponent(parsed.pathname.slice(1));
|
|
94
|
+
}
|
|
95
|
+
if (parsed.username)
|
|
96
|
+
username = decodeURIComponent(parsed.username);
|
|
97
|
+
if (parsed.password)
|
|
98
|
+
password = decodeURIComponent(parsed.password);
|
|
99
|
+
if (parsed.searchParams.has('ssl') || parsed.searchParams.has('sslmode')) {
|
|
100
|
+
ssl =
|
|
101
|
+
parsed.searchParams.get('ssl') === 'true' ||
|
|
102
|
+
parsed.searchParams.get('sslmode') === 'require';
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
// preserve original
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
else if (rawUri.length > 0) {
|
|
110
|
+
try {
|
|
111
|
+
const parsed = new URL(rawUri);
|
|
112
|
+
const schemeDialect = detectDialectFromScheme(parsed.protocol);
|
|
113
|
+
if (schemeDialect)
|
|
114
|
+
dialect = schemeDialect;
|
|
115
|
+
if (parsed.hostname)
|
|
116
|
+
host = parsed.hostname;
|
|
117
|
+
if (parsed.port)
|
|
118
|
+
port = parseInt(parsed.port, 10);
|
|
119
|
+
if (parsed.pathname && parsed.pathname.length > 1) {
|
|
120
|
+
database = decodeURIComponent(parsed.pathname.slice(1));
|
|
121
|
+
}
|
|
122
|
+
if (parsed.username)
|
|
123
|
+
username = decodeURIComponent(parsed.username);
|
|
124
|
+
if (parsed.password)
|
|
125
|
+
password = decodeURIComponent(parsed.password);
|
|
126
|
+
if (parsed.searchParams.has('ssl') || parsed.searchParams.has('sslmode')) {
|
|
127
|
+
ssl =
|
|
128
|
+
parsed.searchParams.get('ssl') === 'true' ||
|
|
129
|
+
parsed.searchParams.get('sslmode') === 'require';
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
// ignore url parse error
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (!rawUri || rawUri.startsWith('jdbc:')) {
|
|
137
|
+
const scheme = dialect === 'postgres' ? 'postgres' : dialect;
|
|
138
|
+
const auth = username ? (password ? `${username}:${password}@` : `${username}@`) : '';
|
|
139
|
+
rawUri = `${scheme}://${auth}${host}:${port}/${database}`;
|
|
140
|
+
}
|
|
141
|
+
const defaultPorts = {
|
|
142
|
+
postgres: 5432,
|
|
143
|
+
mysql: 3306,
|
|
144
|
+
mariadb: 3306,
|
|
145
|
+
sqlite: 0,
|
|
146
|
+
};
|
|
147
|
+
if (!port || (port === 5432 && dialect === 'mysql')) {
|
|
148
|
+
port = defaultPorts[dialect];
|
|
149
|
+
}
|
|
150
|
+
const jdbcUrl = jdbcUrlCandidate ??
|
|
151
|
+
`jdbc:${dialect === 'postgres' ? 'postgresql' : dialect}://${host}:${port}/${database}`;
|
|
152
|
+
return {
|
|
153
|
+
id: serviceData.name,
|
|
154
|
+
name: serviceData.name,
|
|
155
|
+
label: serviceData.label,
|
|
156
|
+
...(serviceData.plan ? { plan: serviceData.plan } : {}),
|
|
157
|
+
tags,
|
|
158
|
+
credentials: Object.freeze({ ...creds }),
|
|
159
|
+
dialect,
|
|
160
|
+
host,
|
|
161
|
+
port,
|
|
162
|
+
database,
|
|
163
|
+
...(username !== undefined ? { username } : {}),
|
|
164
|
+
...(password !== undefined ? { password } : {}),
|
|
165
|
+
uri: rawUri,
|
|
166
|
+
jdbcUrl,
|
|
167
|
+
...(ssl !== undefined ? { ssl } : {}),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function creationalDbName(creds) {
|
|
172
|
+
if (creds.database)
|
|
173
|
+
return String(creds.database);
|
|
174
|
+
if (creds.name)
|
|
175
|
+
return String(creds.name);
|
|
176
|
+
if (creds.db)
|
|
177
|
+
return String(creds.db);
|
|
178
|
+
return 'defaultdb';
|
|
179
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { CloudFoundryServiceInfoCreator } from '../spi/creator.js';
|
|
2
|
+
import type { RawVcapServiceData, UserProvidedServiceInfo } from '../types.js';
|
|
3
|
+
export declare class UserProvidedServiceInfoCreator implements CloudFoundryServiceInfoCreator<UserProvidedServiceInfo> {
|
|
4
|
+
accept(serviceData: RawVcapServiceData): boolean;
|
|
5
|
+
createServiceInfo(serviceData: RawVcapServiceData): UserProvidedServiceInfo;
|
|
6
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export class UserProvidedServiceInfoCreator {
|
|
2
|
+
accept(serviceData) {
|
|
3
|
+
const label = (serviceData.label || '').toLowerCase();
|
|
4
|
+
return label === 'user-provided' || label.length === 0 || !label;
|
|
5
|
+
}
|
|
6
|
+
createServiceInfo(serviceData) {
|
|
7
|
+
const creds = (serviceData.credentials || {});
|
|
8
|
+
const tags = (serviceData.tags || []).map(String);
|
|
9
|
+
const uri = (creds.uri ?? creds.url) ? String(creds.uri ?? creds.url) : undefined;
|
|
10
|
+
const host = (creds.host ?? creds.hostname) ? String(creds.host ?? creds.hostname) : undefined;
|
|
11
|
+
let port;
|
|
12
|
+
if (typeof creds.port === 'number') {
|
|
13
|
+
port = creds.port;
|
|
14
|
+
}
|
|
15
|
+
else if (typeof creds.port === 'string' && creds.port.trim().length > 0) {
|
|
16
|
+
const p = parseInt(creds.port, 10);
|
|
17
|
+
if (!Number.isNaN(p))
|
|
18
|
+
port = p;
|
|
19
|
+
}
|
|
20
|
+
const username = (creds.username ?? creds.user) ? String(creds.username ?? creds.user) : undefined;
|
|
21
|
+
const password = (creds.password ?? creds.pass) ? String(creds.password ?? creds.pass) : undefined;
|
|
22
|
+
return {
|
|
23
|
+
id: serviceData.name,
|
|
24
|
+
name: serviceData.name,
|
|
25
|
+
label: serviceData.label || 'user-provided',
|
|
26
|
+
...(serviceData.plan ? { plan: serviceData.plan } : {}),
|
|
27
|
+
tags,
|
|
28
|
+
credentials: Object.freeze({ ...creds }),
|
|
29
|
+
...(uri !== undefined ? { uri } : {}),
|
|
30
|
+
...(host !== undefined ? { host } : {}),
|
|
31
|
+
...(port !== undefined ? { port } : {}),
|
|
32
|
+
...(username !== undefined ? { username } : {}),
|
|
33
|
+
...(password !== undefined ? { password } : {}),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { CloudFoundryServiceOptions, EnableCloudFoundryConnectorsOptions, ServiceFilter } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Parameter or Property decorator that injects a bound Cloud Foundry service.
|
|
4
|
+
*
|
|
5
|
+
* @param serviceNameOrFilter Name, label, pattern, or filter object for the service.
|
|
6
|
+
* @param options Fallback environment keys, required flag, or default value.
|
|
7
|
+
*
|
|
8
|
+
* @example Property injection
|
|
9
|
+
* @Container()
|
|
10
|
+
* class DatabaseRepository {
|
|
11
|
+
* @CloudFoundryService('my-postgres')
|
|
12
|
+
* private dbService!: RelationalServiceInfo;
|
|
13
|
+
* }
|
|
14
|
+
*
|
|
15
|
+
* @example Constructor injection
|
|
16
|
+
* @Container()
|
|
17
|
+
* class CacheClient {
|
|
18
|
+
* constructor(
|
|
19
|
+
* @CloudFoundryService('my-redis') private redis: RedisServiceInfo
|
|
20
|
+
* ) {}
|
|
21
|
+
* }
|
|
22
|
+
*/
|
|
23
|
+
export declare function CloudFoundryService(serviceNameOrFilter?: string | RegExp | ServiceFilter, options?: CloudFoundryServiceOptions): (targetClass: any, propertyKey?: string | symbol, parameterIndex?: number) => void;
|
|
24
|
+
/**
|
|
25
|
+
* Injects Cloud Foundry application metadata (VCAP_APPLICATION).
|
|
26
|
+
*/
|
|
27
|
+
export declare function VcapApplication(options?: {
|
|
28
|
+
required?: boolean;
|
|
29
|
+
container?: unknown;
|
|
30
|
+
}): (targetClass: any, propertyKey?: string | symbol, parameterIndex?: number) => void;
|
|
31
|
+
/**
|
|
32
|
+
* Class decorator that enables Cloud Foundry connector auto-configuration on the DI container.
|
|
33
|
+
*/
|
|
34
|
+
export declare function EnableCloudFoundryConnectors(options?: EnableCloudFoundryConnectorsOptions): <T extends new (...args: any[]) => any>(ctor: T) => T;
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { defineMetadata, getOwnMetadata, useContainer } from '@di-framework/core/container';
|
|
2
|
+
import { bindCloudFoundryConnectors, CF_APPLICATION_TOKEN } from './bindings.js';
|
|
3
|
+
import { getDefaultEnvironment } from './environment.js';
|
|
4
|
+
const INJECT_METADATA_KEY = 'di:inject';
|
|
5
|
+
/**
|
|
6
|
+
* Parameter or Property decorator that injects a bound Cloud Foundry service.
|
|
7
|
+
*
|
|
8
|
+
* @param serviceNameOrFilter Name, label, pattern, or filter object for the service.
|
|
9
|
+
* @param options Fallback environment keys, required flag, or default value.
|
|
10
|
+
*
|
|
11
|
+
* @example Property injection
|
|
12
|
+
* @Container()
|
|
13
|
+
* class DatabaseRepository {
|
|
14
|
+
* @CloudFoundryService('my-postgres')
|
|
15
|
+
* private dbService!: RelationalServiceInfo;
|
|
16
|
+
* }
|
|
17
|
+
*
|
|
18
|
+
* @example Constructor injection
|
|
19
|
+
* @Container()
|
|
20
|
+
* class CacheClient {
|
|
21
|
+
* constructor(
|
|
22
|
+
* @CloudFoundryService('my-redis') private redis: RedisServiceInfo
|
|
23
|
+
* ) {}
|
|
24
|
+
* }
|
|
25
|
+
*/
|
|
26
|
+
export function CloudFoundryService(serviceNameOrFilter, options = {}) {
|
|
27
|
+
return (
|
|
28
|
+
// biome-ignore lint/suspicious/noExplicitAny: decorator target
|
|
29
|
+
targetClass, propertyKey, parameterIndex) => {
|
|
30
|
+
const token = deriveServiceToken(serviceNameOrFilter);
|
|
31
|
+
// Property injection
|
|
32
|
+
if (propertyKey !== undefined && parameterIndex === undefined) {
|
|
33
|
+
const metadata = getOwnMetadata(INJECT_METADATA_KEY, targetClass) || {};
|
|
34
|
+
metadata[propertyKey] = token;
|
|
35
|
+
defineMetadata(INJECT_METADATA_KEY, metadata, targetClass);
|
|
36
|
+
if (targetClass.constructor && targetClass.constructor !== Object) {
|
|
37
|
+
const ctorMetadata = getOwnMetadata(INJECT_METADATA_KEY, targetClass.constructor) || {};
|
|
38
|
+
ctorMetadata[propertyKey] = token;
|
|
39
|
+
defineMetadata(INJECT_METADATA_KEY, ctorMetadata, targetClass.constructor);
|
|
40
|
+
}
|
|
41
|
+
// Define property getter fallback in case accessed without DI resolution
|
|
42
|
+
let cachedValue;
|
|
43
|
+
Object.defineProperty(targetClass, propertyKey, {
|
|
44
|
+
configurable: true,
|
|
45
|
+
enumerable: true,
|
|
46
|
+
get() {
|
|
47
|
+
if (cachedValue !== undefined)
|
|
48
|
+
return cachedValue;
|
|
49
|
+
// Attempt container resolution first
|
|
50
|
+
const container = options.container ?? useContainer();
|
|
51
|
+
if (container && typeof container.resolve === 'function') {
|
|
52
|
+
try {
|
|
53
|
+
const res = container.resolve(token);
|
|
54
|
+
if (res !== undefined && res !== null) {
|
|
55
|
+
cachedValue = res;
|
|
56
|
+
return cachedValue;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
// fall through to environment lookup
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// Fallback to CloudFoundryEnvironment lookup
|
|
64
|
+
const env = getDefaultEnvironment();
|
|
65
|
+
let service = null;
|
|
66
|
+
if (typeof serviceNameOrFilter === 'string') {
|
|
67
|
+
service = env.getServiceInfo(serviceNameOrFilter);
|
|
68
|
+
}
|
|
69
|
+
else if (serviceNameOrFilter) {
|
|
70
|
+
service = env.getServiceInfo(serviceNameOrFilter);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
const all = env.getServiceInfos();
|
|
74
|
+
service = all[0] ?? null;
|
|
75
|
+
}
|
|
76
|
+
if (service) {
|
|
77
|
+
cachedValue = service;
|
|
78
|
+
return cachedValue;
|
|
79
|
+
}
|
|
80
|
+
if (options.fallbackEnv) {
|
|
81
|
+
const keys = Array.isArray(options.fallbackEnv)
|
|
82
|
+
? options.fallbackEnv
|
|
83
|
+
: [options.fallbackEnv];
|
|
84
|
+
for (const key of keys) {
|
|
85
|
+
const val = process.env[key];
|
|
86
|
+
if (val) {
|
|
87
|
+
cachedValue = {
|
|
88
|
+
uri: val,
|
|
89
|
+
name: key,
|
|
90
|
+
label: 'fallback',
|
|
91
|
+
tags: [],
|
|
92
|
+
credentials: { uri: val },
|
|
93
|
+
};
|
|
94
|
+
return cachedValue;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (options.defaultValue !== undefined) {
|
|
99
|
+
return options.defaultValue;
|
|
100
|
+
}
|
|
101
|
+
if (options.required !== false) {
|
|
102
|
+
throw new Error(`Cloud Foundry service '${String(serviceNameOrFilter ?? 'default')}' not found and no fallback available`);
|
|
103
|
+
}
|
|
104
|
+
return undefined;
|
|
105
|
+
},
|
|
106
|
+
set(value) {
|
|
107
|
+
cachedValue = value;
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
// Constructor parameter injection
|
|
112
|
+
else if (parameterIndex !== undefined) {
|
|
113
|
+
const metadata = getOwnMetadata(INJECT_METADATA_KEY, targetClass) || {};
|
|
114
|
+
metadata[`param_${parameterIndex}`] = token;
|
|
115
|
+
defineMetadata(INJECT_METADATA_KEY, metadata, targetClass);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Injects Cloud Foundry application metadata (VCAP_APPLICATION).
|
|
121
|
+
*/
|
|
122
|
+
export function VcapApplication(options = {}) {
|
|
123
|
+
return (
|
|
124
|
+
// biome-ignore lint/suspicious/noExplicitAny: decorator target
|
|
125
|
+
targetClass, propertyKey, parameterIndex) => {
|
|
126
|
+
const token = CF_APPLICATION_TOKEN;
|
|
127
|
+
if (propertyKey !== undefined && parameterIndex === undefined) {
|
|
128
|
+
const metadata = getOwnMetadata(INJECT_METADATA_KEY, targetClass) || {};
|
|
129
|
+
metadata[propertyKey] = token;
|
|
130
|
+
defineMetadata(INJECT_METADATA_KEY, metadata, targetClass);
|
|
131
|
+
if (targetClass.constructor && targetClass.constructor !== Object) {
|
|
132
|
+
const ctorMetadata = getOwnMetadata(INJECT_METADATA_KEY, targetClass.constructor) || {};
|
|
133
|
+
ctorMetadata[propertyKey] = token;
|
|
134
|
+
defineMetadata(INJECT_METADATA_KEY, ctorMetadata, targetClass.constructor);
|
|
135
|
+
}
|
|
136
|
+
let cachedAppInfo;
|
|
137
|
+
Object.defineProperty(targetClass, propertyKey, {
|
|
138
|
+
configurable: true,
|
|
139
|
+
enumerable: true,
|
|
140
|
+
get() {
|
|
141
|
+
if (cachedAppInfo !== undefined)
|
|
142
|
+
return cachedAppInfo;
|
|
143
|
+
const container = options.container ?? useContainer();
|
|
144
|
+
if (container && typeof container.resolve === 'function') {
|
|
145
|
+
try {
|
|
146
|
+
const res = container.resolve(token);
|
|
147
|
+
if (res !== undefined && res !== null) {
|
|
148
|
+
cachedAppInfo = res;
|
|
149
|
+
return cachedAppInfo;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
// fallback
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
const info = getDefaultEnvironment().getApplicationInfo();
|
|
157
|
+
if (!info && options.required === true) {
|
|
158
|
+
throw new Error('Cloud Foundry application info (VCAP_APPLICATION) is required but not present');
|
|
159
|
+
}
|
|
160
|
+
cachedAppInfo = info;
|
|
161
|
+
return cachedAppInfo;
|
|
162
|
+
},
|
|
163
|
+
set(value) {
|
|
164
|
+
cachedAppInfo = value;
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
else if (parameterIndex !== undefined) {
|
|
169
|
+
const metadata = getOwnMetadata(INJECT_METADATA_KEY, targetClass) || {};
|
|
170
|
+
metadata[`param_${parameterIndex}`] = token;
|
|
171
|
+
defineMetadata(INJECT_METADATA_KEY, metadata, targetClass);
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Class decorator that enables Cloud Foundry connector auto-configuration on the DI container.
|
|
177
|
+
*/
|
|
178
|
+
export function EnableCloudFoundryConnectors(options = {}) {
|
|
179
|
+
// biome-ignore lint/suspicious/noExplicitAny: class constructor
|
|
180
|
+
return (ctor) => {
|
|
181
|
+
bindCloudFoundryConnectors(options.container, options);
|
|
182
|
+
return ctor;
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
function deriveServiceToken(serviceNameOrFilter) {
|
|
186
|
+
if (typeof serviceNameOrFilter === 'string') {
|
|
187
|
+
return `cf:service:${serviceNameOrFilter}`;
|
|
188
|
+
}
|
|
189
|
+
if (serviceNameOrFilter &&
|
|
190
|
+
typeof serviceNameOrFilter === 'object' &&
|
|
191
|
+
'name' in serviceNameOrFilter &&
|
|
192
|
+
typeof serviceNameOrFilter.name === 'string') {
|
|
193
|
+
return `cf:service:${serviceNameOrFilter.name}`;
|
|
194
|
+
}
|
|
195
|
+
if (serviceNameOrFilter &&
|
|
196
|
+
typeof serviceNameOrFilter === 'object' &&
|
|
197
|
+
'label' in serviceNameOrFilter &&
|
|
198
|
+
typeof serviceNameOrFilter.label === 'string') {
|
|
199
|
+
return `cf:service:${serviceNameOrFilter.label}`;
|
|
200
|
+
}
|
|
201
|
+
return 'cf:service:default';
|
|
202
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility class for detecting Cloud Foundry runtime environments.
|
|
3
|
+
*/
|
|
4
|
+
export declare class CloudFoundryDetector {
|
|
5
|
+
/**
|
|
6
|
+
* Returns true if running within a Cloud Foundry runtime environment
|
|
7
|
+
* (determined by presence of VCAP_APPLICATION or VCAP_SERVICES).
|
|
8
|
+
*/
|
|
9
|
+
static isCloudFoundry(env?: Record<string, string | undefined>): boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Retrieves raw VCAP_APPLICATION JSON string from environment.
|
|
12
|
+
*/
|
|
13
|
+
static getApplicationJson(env?: Record<string, string | undefined>): string | undefined;
|
|
14
|
+
/**
|
|
15
|
+
* Retrieves raw VCAP_SERVICES JSON string from environment.
|
|
16
|
+
*/
|
|
17
|
+
static getServicesJson(env?: Record<string, string | undefined>): string | undefined;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Convenient standalone check for Cloud Foundry runtime.
|
|
21
|
+
*/
|
|
22
|
+
export declare function isCloudFoundry(env?: Record<string, string | undefined>): boolean;
|
package/dist/detector.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility class for detecting Cloud Foundry runtime environments.
|
|
3
|
+
*/
|
|
4
|
+
export class CloudFoundryDetector {
|
|
5
|
+
/**
|
|
6
|
+
* Returns true if running within a Cloud Foundry runtime environment
|
|
7
|
+
* (determined by presence of VCAP_APPLICATION or VCAP_SERVICES).
|
|
8
|
+
*/
|
|
9
|
+
static isCloudFoundry(env = process.env) {
|
|
10
|
+
const vcapApp = env.VCAP_APPLICATION;
|
|
11
|
+
const vcapServices = env.VCAP_SERVICES;
|
|
12
|
+
return Boolean((typeof vcapApp === 'string' && vcapApp.trim().length > 0) ||
|
|
13
|
+
(typeof vcapServices === 'string' && vcapServices.trim().length > 0));
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Retrieves raw VCAP_APPLICATION JSON string from environment.
|
|
17
|
+
*/
|
|
18
|
+
static getApplicationJson(env = process.env) {
|
|
19
|
+
const val = env.VCAP_APPLICATION;
|
|
20
|
+
return typeof val === 'string' && val.trim().length > 0 ? val.trim() : undefined;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Retrieves raw VCAP_SERVICES JSON string from environment.
|
|
24
|
+
*/
|
|
25
|
+
static getServicesJson(env = process.env) {
|
|
26
|
+
const val = env.VCAP_SERVICES;
|
|
27
|
+
return typeof val === 'string' && val.trim().length > 0 ? val.trim() : undefined;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Convenient standalone check for Cloud Foundry runtime.
|
|
32
|
+
*/
|
|
33
|
+
export function isCloudFoundry(env = process.env) {
|
|
34
|
+
return CloudFoundryDetector.isCloudFoundry(env);
|
|
35
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { type ServiceInfoCreatorRegistry } from './spi/registry.js';
|
|
2
|
+
import type { AmqpServiceInfo, BlobStorageServiceInfo, CloudFoundryApplicationInfo, CloudFoundryServiceInfo, LocalFallbackOptions, RedisServiceInfo, RelationalServiceInfo, ServiceFilter, UserProvidedServiceInfo } from './types.js';
|
|
3
|
+
export interface CloudFoundryEnvironmentOptions {
|
|
4
|
+
/** Custom environment dictionary (defaults to process.env) */
|
|
5
|
+
env?: Record<string, string | undefined>;
|
|
6
|
+
/** Custom service info creator registry */
|
|
7
|
+
registry?: ServiceInfoCreatorRegistry;
|
|
8
|
+
/** Options or boolean for local fallback */
|
|
9
|
+
localFallback?: boolean | LocalFallbackOptions;
|
|
10
|
+
}
|
|
11
|
+
export declare class CloudFoundryEnvironment {
|
|
12
|
+
private readonly env;
|
|
13
|
+
private readonly registry;
|
|
14
|
+
private readonly localFallbackOptions;
|
|
15
|
+
private appInfo;
|
|
16
|
+
private services;
|
|
17
|
+
constructor(options?: CloudFoundryEnvironmentOptions);
|
|
18
|
+
/**
|
|
19
|
+
* Returns true if running within a Cloud Foundry runtime.
|
|
20
|
+
*/
|
|
21
|
+
isCloudFoundry(): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Returns parsed CloudFoundryApplicationInfo or null if not running in CF.
|
|
24
|
+
*/
|
|
25
|
+
getApplicationInfo(): CloudFoundryApplicationInfo | null;
|
|
26
|
+
/**
|
|
27
|
+
* Retrieves all discovered Cloud Foundry services, applying optional filtering.
|
|
28
|
+
*/
|
|
29
|
+
getServiceInfos<T extends CloudFoundryServiceInfo = CloudFoundryServiceInfo>(filter?: ServiceFilter | string | RegExp): readonly T[];
|
|
30
|
+
/**
|
|
31
|
+
* Retrieves a single service by name, filter, or returns the first matching service.
|
|
32
|
+
*/
|
|
33
|
+
getServiceInfo<T extends CloudFoundryServiceInfo = CloudFoundryServiceInfo>(serviceNameOrFilter?: string | RegExp | ServiceFilter): T | null;
|
|
34
|
+
/**
|
|
35
|
+
* Retrieves relational database service info (PostgreSQL, MySQL, MariaDB, SQLite).
|
|
36
|
+
*/
|
|
37
|
+
getRelationalServiceInfo(filter?: string | RegExp | ServiceFilter): RelationalServiceInfo | null;
|
|
38
|
+
/**
|
|
39
|
+
* Retrieves Redis service info.
|
|
40
|
+
*/
|
|
41
|
+
getRedisServiceInfo(filter?: string | RegExp | ServiceFilter): RedisServiceInfo | null;
|
|
42
|
+
/**
|
|
43
|
+
* Retrieves AMQP / RabbitMQ service info.
|
|
44
|
+
*/
|
|
45
|
+
getAmqpServiceInfo(filter?: string | RegExp | ServiceFilter): AmqpServiceInfo | null;
|
|
46
|
+
/**
|
|
47
|
+
* Retrieves Blob Storage / S3 service info.
|
|
48
|
+
*/
|
|
49
|
+
getBlobStorageServiceInfo(filter?: string | RegExp | ServiceFilter): BlobStorageServiceInfo | null;
|
|
50
|
+
/**
|
|
51
|
+
* Retrieves User-Provided service info.
|
|
52
|
+
*/
|
|
53
|
+
getUserProvidedServiceInfo(filter?: string | RegExp | ServiceFilter): UserProvidedServiceInfo | null;
|
|
54
|
+
/**
|
|
55
|
+
* Retrieves raw credentials map for a service.
|
|
56
|
+
*/
|
|
57
|
+
getServiceCredentials<T = Record<string, unknown>>(serviceName: string): T | null;
|
|
58
|
+
private ensureServicesLoaded;
|
|
59
|
+
private buildLocalFallbackServices;
|
|
60
|
+
}
|
|
61
|
+
export declare function getDefaultEnvironment(): CloudFoundryEnvironment;
|
|
62
|
+
export declare function resetDefaultEnvironment(): void;
|