@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.
@@ -0,0 +1,326 @@
1
+ import { parseVcapApplication } from './application-info.js';
2
+ import { CloudFoundryDetector } from './detector.js';
3
+ import { getDefaultRegistry, parseVcapServices, } from './spi/registry.js';
4
+ export class CloudFoundryEnvironment {
5
+ env;
6
+ registry;
7
+ localFallbackOptions;
8
+ appInfo;
9
+ services;
10
+ constructor(options = {}) {
11
+ this.env = options.env ?? process.env;
12
+ this.registry = options.registry ?? getDefaultRegistry();
13
+ if (options.localFallback === false) {
14
+ this.localFallbackOptions = null;
15
+ }
16
+ else if (typeof options.localFallback === 'object') {
17
+ this.localFallbackOptions = options.localFallback;
18
+ }
19
+ else {
20
+ // Default enabled
21
+ this.localFallbackOptions = {};
22
+ }
23
+ }
24
+ /**
25
+ * Returns true if running within a Cloud Foundry runtime.
26
+ */
27
+ isCloudFoundry() {
28
+ return CloudFoundryDetector.isCloudFoundry(this.env);
29
+ }
30
+ /**
31
+ * Returns parsed CloudFoundryApplicationInfo or null if not running in CF.
32
+ */
33
+ getApplicationInfo() {
34
+ if (this.appInfo === undefined) {
35
+ this.appInfo = parseVcapApplication(this.env);
36
+ }
37
+ return this.appInfo;
38
+ }
39
+ /**
40
+ * Retrieves all discovered Cloud Foundry services, applying optional filtering.
41
+ */
42
+ getServiceInfos(filter) {
43
+ this.ensureServicesLoaded();
44
+ const all = (this.services ?? []);
45
+ if (!filter) {
46
+ return all;
47
+ }
48
+ if (typeof filter === 'string') {
49
+ return all.filter((s) => s.name === filter || s.label === filter || s.tags.includes(filter));
50
+ }
51
+ if (filter instanceof RegExp) {
52
+ return all.filter((s) => filter.test(s.name) || filter.test(s.label));
53
+ }
54
+ return all.filter((service) => {
55
+ if (filter.name) {
56
+ if (typeof filter.name === 'string' && service.name !== filter.name)
57
+ return false;
58
+ if (filter.name instanceof RegExp && !filter.name.test(service.name))
59
+ return false;
60
+ }
61
+ if (filter.label) {
62
+ if (typeof filter.label === 'string' && service.label !== filter.label)
63
+ return false;
64
+ if (filter.label instanceof RegExp && !filter.label.test(service.label))
65
+ return false;
66
+ }
67
+ if (filter.tag) {
68
+ if (typeof filter.tag === 'string' && !service.tags.includes(filter.tag))
69
+ return false;
70
+ if (filter.tag instanceof RegExp &&
71
+ !service.tags.some((t) => filter.tag.test(t)))
72
+ return false;
73
+ }
74
+ if (filter.dialect) {
75
+ if (!('dialect' in service) ||
76
+ service.dialect !== filter.dialect) {
77
+ return false;
78
+ }
79
+ }
80
+ if (filter.predicate && !filter.predicate(service)) {
81
+ return false;
82
+ }
83
+ return true;
84
+ });
85
+ }
86
+ /**
87
+ * Retrieves a single service by name, filter, or returns the first matching service.
88
+ */
89
+ getServiceInfo(serviceNameOrFilter) {
90
+ const list = this.getServiceInfos(serviceNameOrFilter);
91
+ return list[0] ?? null;
92
+ }
93
+ /**
94
+ * Retrieves relational database service info (PostgreSQL, MySQL, MariaDB, SQLite).
95
+ */
96
+ getRelationalServiceInfo(filter) {
97
+ if (typeof filter === 'string' || filter instanceof RegExp) {
98
+ const found = this.getServiceInfo(filter);
99
+ if (found && 'dialect' in found)
100
+ return found;
101
+ }
102
+ else if (filter) {
103
+ const found = this.getServiceInfo(filter);
104
+ if (found && 'dialect' in found)
105
+ return found;
106
+ }
107
+ // Find any relational service
108
+ const all = this.getServiceInfos();
109
+ return all.find((s) => 'dialect' in s) ?? null;
110
+ }
111
+ /**
112
+ * Retrieves Redis service info.
113
+ */
114
+ getRedisServiceInfo(filter) {
115
+ if (typeof filter === 'string' || filter instanceof RegExp) {
116
+ const found = this.getServiceInfo(filter);
117
+ if (found && isRedisInfo(found))
118
+ return found;
119
+ }
120
+ else if (filter) {
121
+ const found = this.getServiceInfo(filter);
122
+ if (found && isRedisInfo(found))
123
+ return found;
124
+ }
125
+ const all = this.getServiceInfos();
126
+ return all.find((s) => isRedisInfo(s)) ?? null;
127
+ }
128
+ /**
129
+ * Retrieves AMQP / RabbitMQ service info.
130
+ */
131
+ getAmqpServiceInfo(filter) {
132
+ if (typeof filter === 'string' || filter instanceof RegExp) {
133
+ const found = this.getServiceInfo(filter);
134
+ if (found && isAmqpInfo(found))
135
+ return found;
136
+ }
137
+ else if (filter) {
138
+ const found = this.getServiceInfo(filter);
139
+ if (found && isAmqpInfo(found))
140
+ return found;
141
+ }
142
+ const all = this.getServiceInfos();
143
+ return all.find((s) => isAmqpInfo(s)) ?? null;
144
+ }
145
+ /**
146
+ * Retrieves Blob Storage / S3 service info.
147
+ */
148
+ getBlobStorageServiceInfo(filter) {
149
+ if (typeof filter === 'string' || filter instanceof RegExp) {
150
+ const found = this.getServiceInfo(filter);
151
+ if (found && isBlobStorageInfo(found))
152
+ return found;
153
+ }
154
+ else if (filter) {
155
+ const found = this.getServiceInfo(filter);
156
+ if (found && isBlobStorageInfo(found))
157
+ return found;
158
+ }
159
+ const all = this.getServiceInfos();
160
+ return all.find((s) => isBlobStorageInfo(s)) ?? null;
161
+ }
162
+ /**
163
+ * Retrieves User-Provided service info.
164
+ */
165
+ getUserProvidedServiceInfo(filter) {
166
+ if (typeof filter === 'string' || filter instanceof RegExp) {
167
+ return this.getServiceInfo(filter);
168
+ }
169
+ const all = this.getServiceInfos({
170
+ label: 'user-provided',
171
+ ...(filter || {}),
172
+ });
173
+ return all[0] ?? null;
174
+ }
175
+ /**
176
+ * Retrieves raw credentials map for a service.
177
+ */
178
+ getServiceCredentials(serviceName) {
179
+ const service = this.getServiceInfo(serviceName);
180
+ return service ? service.credentials : null;
181
+ }
182
+ ensureServicesLoaded() {
183
+ if (this.services !== undefined)
184
+ return;
185
+ let discovered = parseVcapServices(this.env, this.registry);
186
+ if (discovered.length === 0 && this.localFallbackOptions) {
187
+ discovered = this.buildLocalFallbackServices(this.localFallbackOptions);
188
+ }
189
+ this.services = discovered;
190
+ }
191
+ buildLocalFallbackServices(options) {
192
+ const list = [];
193
+ // 1. Explicit local fallback services
194
+ if (options.services) {
195
+ for (const [name, partial] of Object.entries(options.services)) {
196
+ list.push({
197
+ id: partial.id ?? name,
198
+ name: partial.name ?? name,
199
+ label: partial.label ?? 'local-fallback',
200
+ tags: partial.tags ?? ['local'],
201
+ credentials: Object.freeze({ ...(partial.credentials || {}) }),
202
+ ...partial,
203
+ });
204
+ }
205
+ }
206
+ // 2. Relational fallback from environment variables
207
+ const relationalKeys = options.relationalUriEnv ?? [
208
+ 'DATABASE_URL',
209
+ 'POSTGRES_URL',
210
+ 'POSTGRESQL_URL',
211
+ 'MYSQL_URL',
212
+ 'MARIADB_URL',
213
+ 'SQLITE_URL',
214
+ ];
215
+ for (const key of relationalKeys) {
216
+ const uriVal = this.env[key];
217
+ if (uriVal && uriVal.trim().length > 0) {
218
+ const rawEntry = {
219
+ name: 'local-db',
220
+ label: 'relational',
221
+ tags: ['local', 'relational'],
222
+ credentials: { uri: uriVal.trim() },
223
+ };
224
+ list.push(this.registry.createServiceInfo(rawEntry));
225
+ break;
226
+ }
227
+ }
228
+ // 3. Redis fallback
229
+ const redisKeys = options.redisUriEnv ?? ['REDIS_URL', 'REDIS_TLS_URL', 'REDISCLOUD_URL'];
230
+ for (const key of redisKeys) {
231
+ const uriVal = this.env[key];
232
+ if (uriVal && uriVal.trim().length > 0) {
233
+ const rawEntry = {
234
+ name: 'local-redis',
235
+ label: 'redis',
236
+ tags: ['local', 'redis', 'cache'],
237
+ credentials: { uri: uriVal.trim() },
238
+ };
239
+ list.push(this.registry.createServiceInfo(rawEntry));
240
+ break;
241
+ }
242
+ }
243
+ // 4. AMQP fallback
244
+ const amqpKeys = options.amqpUriEnv ?? [
245
+ 'AMQP_URL',
246
+ 'RABBITMQ_URL',
247
+ 'CLOUDAMQP_URL',
248
+ 'RABBITMQ_BIGWIG_URL',
249
+ ];
250
+ for (const key of amqpKeys) {
251
+ const uriVal = this.env[key];
252
+ if (uriVal && uriVal.trim().length > 0) {
253
+ const rawEntry = {
254
+ name: 'local-amqp',
255
+ label: 'rabbitmq',
256
+ tags: ['local', 'amqp', 'rabbitmq'],
257
+ credentials: { uri: uriVal.trim() },
258
+ };
259
+ list.push(this.registry.createServiceInfo(rawEntry));
260
+ break;
261
+ }
262
+ }
263
+ // 5. Blob storage fallback
264
+ const blobBucket = options.blobStorageEnv?.bucket ??
265
+ this.env.S3_BUCKET ??
266
+ this.env.AWS_S3_BUCKET ??
267
+ this.env.BLOB_STORAGE_BUCKET;
268
+ if (blobBucket) {
269
+ const rawEntry = {
270
+ name: 'local-blob-storage',
271
+ label: 's3',
272
+ tags: ['local', 's3', 'blob'],
273
+ credentials: {
274
+ bucket: blobBucket,
275
+ endpoint: options.blobStorageEnv?.endpoint ??
276
+ this.env.S3_ENDPOINT ??
277
+ this.env.AWS_ENDPOINT_URL_S3 ??
278
+ this.env.AWS_ENDPOINT_URL,
279
+ region: options.blobStorageEnv?.region ??
280
+ this.env.AWS_REGION ??
281
+ this.env.AWS_DEFAULT_REGION ??
282
+ 'us-east-1',
283
+ accessKeyId: options.blobStorageEnv?.accessKeyId ??
284
+ this.env.AWS_ACCESS_KEY_ID ??
285
+ this.env.S3_ACCESS_KEY,
286
+ secretAccessKey: options.blobStorageEnv?.secretAccessKey ??
287
+ this.env.AWS_SECRET_ACCESS_KEY ??
288
+ this.env.S3_SECRET_KEY,
289
+ },
290
+ };
291
+ list.push(this.registry.createServiceInfo(rawEntry));
292
+ }
293
+ return list;
294
+ }
295
+ }
296
+ function isRedisInfo(s) {
297
+ return ('host' in s &&
298
+ 'port' in s &&
299
+ (s.label.toLowerCase().includes('redis') ||
300
+ s.tags.includes('redis') ||
301
+ s.tags.includes('cache')));
302
+ }
303
+ function isAmqpInfo(s) {
304
+ return ('uris' in s ||
305
+ s.label.toLowerCase().includes('rabbitmq') ||
306
+ s.label.toLowerCase().includes('amqp') ||
307
+ s.tags.includes('rabbitmq') ||
308
+ s.tags.includes('amqp'));
309
+ }
310
+ function isBlobStorageInfo(s) {
311
+ return ('bucketName' in s ||
312
+ s.label.toLowerCase().includes('s3') ||
313
+ s.tags.includes('s3') ||
314
+ s.tags.includes('blob') ||
315
+ s.tags.includes('blobstore'));
316
+ }
317
+ let defaultEnvironment = null;
318
+ export function getDefaultEnvironment() {
319
+ if (!defaultEnvironment) {
320
+ defaultEnvironment = new CloudFoundryEnvironment();
321
+ }
322
+ return defaultEnvironment;
323
+ }
324
+ export function resetDefaultEnvironment() {
325
+ defaultEnvironment = null;
326
+ }
@@ -0,0 +1,13 @@
1
+ export { parseVcapApplication } from './application-info.js';
2
+ export { bindCloudFoundryConnectors, CF_AMQP_TOKEN, CF_APPLICATION_TOKEN, CF_BLOB_TOKEN, CF_ENVIRONMENT_TOKEN, CF_REDIS_TOKEN, CF_RELATIONAL_TOKEN, } from './bindings.js';
3
+ export { AmqpServiceInfoCreator } from './creators/amqp.js';
4
+ export { BlobStorageServiceInfoCreator } from './creators/blob-storage.js';
5
+ export { RedisServiceInfoCreator } from './creators/redis.js';
6
+ export { RelationalServiceInfoCreator } from './creators/relational.js';
7
+ export { UserProvidedServiceInfoCreator } from './creators/user-provided.js';
8
+ export { CloudFoundryService, EnableCloudFoundryConnectors, VcapApplication, } from './decorators.js';
9
+ export { CloudFoundryDetector, isCloudFoundry, } from './detector.js';
10
+ export { CloudFoundryEnvironment, type CloudFoundryEnvironmentOptions, getDefaultEnvironment, resetDefaultEnvironment, } from './environment.js';
11
+ export type { CloudFoundryServiceInfoCreator } from './spi/creator.js';
12
+ export { getDefaultRegistry, parseVcapServices, ServiceInfoCreatorRegistry, } from './spi/registry.js';
13
+ export type { AmqpServiceInfo, BlobStorageServiceInfo, CloudFoundryApplicationInfo, CloudFoundryServiceInfo, CloudFoundryServiceOptions, EnableCloudFoundryConnectorsOptions, LocalFallbackOptions, RawVcapServiceData, RedisServiceInfo, RelationalDialect, RelationalServiceInfo, ServiceFilter, ServicePredicate, UserProvidedServiceInfo, } from './types.js';
package/dist/index.js ADDED
@@ -0,0 +1,11 @@
1
+ export { parseVcapApplication } from './application-info.js';
2
+ export { bindCloudFoundryConnectors, CF_AMQP_TOKEN, CF_APPLICATION_TOKEN, CF_BLOB_TOKEN, CF_ENVIRONMENT_TOKEN, CF_REDIS_TOKEN, CF_RELATIONAL_TOKEN, } from './bindings.js';
3
+ export { AmqpServiceInfoCreator } from './creators/amqp.js';
4
+ export { BlobStorageServiceInfoCreator } from './creators/blob-storage.js';
5
+ export { RedisServiceInfoCreator } from './creators/redis.js';
6
+ export { RelationalServiceInfoCreator } from './creators/relational.js';
7
+ export { UserProvidedServiceInfoCreator } from './creators/user-provided.js';
8
+ export { CloudFoundryService, EnableCloudFoundryConnectors, VcapApplication, } from './decorators.js';
9
+ export { CloudFoundryDetector, isCloudFoundry, } from './detector.js';
10
+ export { CloudFoundryEnvironment, getDefaultEnvironment, resetDefaultEnvironment, } from './environment.js';
11
+ export { getDefaultRegistry, parseVcapServices, ServiceInfoCreatorRegistry, } from './spi/registry.js';
@@ -0,0 +1,15 @@
1
+ import type { CloudFoundryServiceInfo, RawVcapServiceData } from '../types.js';
2
+ /**
3
+ * Service Provider Interface (SPI) for converting raw VCAP_SERVICES entry data
4
+ * into a typed CloudFoundryServiceInfo model.
5
+ */
6
+ export interface CloudFoundryServiceInfoCreator<T extends CloudFoundryServiceInfo = CloudFoundryServiceInfo> {
7
+ /**
8
+ * Evaluates if this creator accepts and can parse the given raw service entry.
9
+ */
10
+ accept(serviceData: RawVcapServiceData): boolean;
11
+ /**
12
+ * Constructs the strongly typed ServiceInfo model from the service data.
13
+ */
14
+ createServiceInfo(serviceData: RawVcapServiceData): T;
15
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,28 @@
1
+ import type { CloudFoundryServiceInfo, RawVcapServiceData } from '../types.js';
2
+ import type { CloudFoundryServiceInfoCreator } from './creator.js';
3
+ export declare class ServiceInfoCreatorRegistry {
4
+ private creators;
5
+ constructor(registerDefaults?: boolean);
6
+ private registerDefaultCreators;
7
+ /**
8
+ * Registers a custom service info creator.
9
+ * By default, custom creators are inserted with higher priority than built-ins.
10
+ */
11
+ registerCreator(creator: CloudFoundryServiceInfoCreator, options?: {
12
+ priority?: 'high' | 'low';
13
+ }): this;
14
+ /**
15
+ * Finds the first matching creator for the given raw service entry.
16
+ */
17
+ findCreator(serviceData: RawVcapServiceData): CloudFoundryServiceInfoCreator | undefined;
18
+ /**
19
+ * Converts a single raw service data object into a typed ServiceInfo model.
20
+ */
21
+ createServiceInfo(serviceData: RawVcapServiceData): CloudFoundryServiceInfo;
22
+ /**
23
+ * Parses raw VCAP_SERVICES payload and produces typed service models.
24
+ */
25
+ parseVcapServices(input?: string | Record<string, unknown> | readonly RawVcapServiceData[]): readonly CloudFoundryServiceInfo[];
26
+ }
27
+ export declare function getDefaultRegistry(): ServiceInfoCreatorRegistry;
28
+ export declare function parseVcapServices(input?: string | Record<string, unknown> | readonly RawVcapServiceData[], registry?: ServiceInfoCreatorRegistry): readonly CloudFoundryServiceInfo[];
@@ -0,0 +1,161 @@
1
+ import { AmqpServiceInfoCreator } from '../creators/amqp.js';
2
+ import { BlobStorageServiceInfoCreator } from '../creators/blob-storage.js';
3
+ import { RedisServiceInfoCreator } from '../creators/redis.js';
4
+ import { RelationalServiceInfoCreator } from '../creators/relational.js';
5
+ import { UserProvidedServiceInfoCreator } from '../creators/user-provided.js';
6
+ import { CloudFoundryDetector } from '../detector.js';
7
+ export class ServiceInfoCreatorRegistry {
8
+ creators = [];
9
+ constructor(registerDefaults = true) {
10
+ if (registerDefaults) {
11
+ this.registerDefaultCreators();
12
+ }
13
+ }
14
+ registerDefaultCreators() {
15
+ this.creators.push(new RelationalServiceInfoCreator());
16
+ this.creators.push(new RedisServiceInfoCreator());
17
+ this.creators.push(new AmqpServiceInfoCreator());
18
+ this.creators.push(new BlobStorageServiceInfoCreator());
19
+ this.creators.push(new UserProvidedServiceInfoCreator());
20
+ }
21
+ /**
22
+ * Registers a custom service info creator.
23
+ * By default, custom creators are inserted with higher priority than built-ins.
24
+ */
25
+ registerCreator(creator, options = { priority: 'high' }) {
26
+ if (options.priority === 'low') {
27
+ this.creators.push(creator);
28
+ }
29
+ else {
30
+ this.creators.unshift(creator);
31
+ }
32
+ return this;
33
+ }
34
+ /**
35
+ * Finds the first matching creator for the given raw service entry.
36
+ */
37
+ findCreator(serviceData) {
38
+ return this.creators.find((c) => {
39
+ try {
40
+ return c.accept(serviceData);
41
+ }
42
+ catch {
43
+ return false;
44
+ }
45
+ });
46
+ }
47
+ /**
48
+ * Converts a single raw service data object into a typed ServiceInfo model.
49
+ */
50
+ createServiceInfo(serviceData) {
51
+ const creator = this.findCreator(serviceData);
52
+ if (creator) {
53
+ return creator.createServiceInfo(serviceData);
54
+ }
55
+ // Default fallback if no creator accepts
56
+ return {
57
+ id: serviceData.name,
58
+ name: serviceData.name,
59
+ label: serviceData.label || 'unknown',
60
+ ...(serviceData.plan ? { plan: serviceData.plan } : {}),
61
+ tags: (serviceData.tags || []).map(String),
62
+ credentials: Object.freeze({ ...(serviceData.credentials || {}) }),
63
+ };
64
+ }
65
+ /**
66
+ * Parses raw VCAP_SERVICES payload and produces typed service models.
67
+ */
68
+ parseVcapServices(input) {
69
+ const rawServices = extractRawServices(input);
70
+ return rawServices.map((entry) => this.createServiceInfo(entry));
71
+ }
72
+ }
73
+ function extractRawServices(input) {
74
+ if (input === null) {
75
+ return [];
76
+ }
77
+ let parsed = null;
78
+ if (input === undefined) {
79
+ const rawJson = CloudFoundryDetector.getServicesJson();
80
+ if (!rawJson)
81
+ return [];
82
+ try {
83
+ parsed = JSON.parse(rawJson);
84
+ }
85
+ catch {
86
+ return [];
87
+ }
88
+ }
89
+ else if (typeof input === 'string') {
90
+ try {
91
+ parsed = JSON.parse(input);
92
+ }
93
+ catch {
94
+ return [];
95
+ }
96
+ }
97
+ else if (Array.isArray(input)) {
98
+ return [...input];
99
+ }
100
+ else if (typeof input === 'object') {
101
+ if ('VCAP_SERVICES' in input) {
102
+ const vcapVal = input.VCAP_SERVICES;
103
+ if (typeof vcapVal === 'string' && vcapVal.trim().length > 0) {
104
+ try {
105
+ parsed = JSON.parse(vcapVal);
106
+ }
107
+ catch {
108
+ return [];
109
+ }
110
+ }
111
+ else if (typeof vcapVal === 'object' && vcapVal !== null) {
112
+ parsed = vcapVal;
113
+ }
114
+ else {
115
+ return [];
116
+ }
117
+ }
118
+ else {
119
+ parsed = input;
120
+ }
121
+ }
122
+ if (!parsed || typeof parsed !== 'object') {
123
+ return [];
124
+ }
125
+ const results = [];
126
+ // VCAP_SERVICES format is { "label-name": [ { name, label, credentials, ... } ] }
127
+ for (const [groupKey, serviceList] of Object.entries(parsed)) {
128
+ if (Array.isArray(serviceList)) {
129
+ for (const item of serviceList) {
130
+ if (typeof item === 'object' && item !== null) {
131
+ const rawItem = item;
132
+ const name = String(rawItem.name ?? groupKey);
133
+ const label = String(rawItem.label ?? groupKey);
134
+ results.push({
135
+ name,
136
+ label,
137
+ tags: Array.isArray(rawItem.tags) ? rawItem.tags.map(String) : [],
138
+ plan: typeof rawItem.plan === 'string' ? rawItem.plan : undefined,
139
+ credentials: (rawItem.credentials || {}),
140
+ volume_mounts: Array.isArray(rawItem.volume_mounts) ? rawItem.volume_mounts : undefined,
141
+ syslog_drain_url: typeof rawItem.syslog_drain_url === 'string' ? rawItem.syslog_drain_url : undefined,
142
+ provider: typeof rawItem.provider === 'string' ? rawItem.provider : undefined,
143
+ binding_name: typeof rawItem.binding_name === 'string' ? rawItem.binding_name : undefined,
144
+ instance_name: typeof rawItem.instance_name === 'string' ? rawItem.instance_name : undefined,
145
+ });
146
+ }
147
+ }
148
+ }
149
+ }
150
+ return results;
151
+ }
152
+ let defaultRegistry = null;
153
+ export function getDefaultRegistry() {
154
+ if (!defaultRegistry) {
155
+ defaultRegistry = new ServiceInfoCreatorRegistry();
156
+ }
157
+ return defaultRegistry;
158
+ }
159
+ export function parseVcapServices(input, registry = getDefaultRegistry()) {
160
+ return registry.parseVcapServices(input);
161
+ }