@crossdelta/infrastructure 0.1.35

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Crossdelta
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,173 @@
1
+ # @crossdelta/infrastructure# @crossdelta/infrastructure
2
+
3
+
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@crossdelta/infrastructure.svg)](https://www.npmjs.com/package/@crossdelta/infrastructure)To install dependencies:
6
+
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
8
+
9
+ # Install Pulumi
10
+
11
+ > 🏗️ Infrastructure-as-Code helpers for deploying microservices to DigitalOcean App Platform with Pulumi```bash
12
+
13
+ curl -fsSL https://get.pulumi.com | sh
14
+
15
+ ## Features```
16
+
17
+
18
+
19
+ - 🚀 **Service Discovery** - Auto-discover service configs from `infra/services/*.ts````bash
20
+
21
+ - 🔧 **Type-safe Config** - Full TypeScript support with `ServiceConfig` typebun install
22
+
23
+ - 🌐 **Smart Routing** - Automatic ingress rules for public services```
24
+
25
+ - 🔒 **Internal Services** - Support for internal-only ports (no public exposure)
26
+
27
+ - 📦 **Multi-Registry** - Docker Hub, GHCR, and DOCR image supportTo run:
28
+
29
+ - 🔗 **URL Generation** - Auto-generate service URLs and port env vars
30
+
31
+ ```bash
32
+
33
+ ## Installationbun run index.ts
34
+
35
+ ```
36
+
37
+ ```bash
38
+
39
+ npm install @crossdelta/infrastructureThis project was created using `bun init` in bun v1.3.3. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
40
+
41
+ # or
42
+ bun add @crossdelta/infrastructure
43
+ ```
44
+
45
+ ## Quick Start
46
+
47
+ ### 1. Create a service config
48
+
49
+ ```typescript
50
+ // infra/services/api-gateway.ts
51
+ import type { ServiceConfig } from '@crossdelta/infrastructure'
52
+
53
+ const config: ServiceConfig = {
54
+ name: 'api-gateway',
55
+ instanceSizeSlug: 'apps-s-1vcpu-0.5gb',
56
+ httpPort: 4000,
57
+ ingressPrefix: '/api',
58
+ }
59
+
60
+ export default config
61
+ ```
62
+
63
+ ### 2. Create your Pulumi entry point
64
+
65
+ ```typescript
66
+ // infra/index.ts
67
+ import { join } from 'node:path'
68
+ import {
69
+ buildServices,
70
+ buildIngressRules,
71
+ buildServiceUrlEnvs,
72
+ buildServicePortEnvs,
73
+ discoverServices,
74
+ } from '@crossdelta/infrastructure'
75
+ import { App } from '@pulumi/digitalocean'
76
+
77
+ const serviceConfigs = discoverServices(join(__dirname, 'services'))
78
+
79
+ const app = new App('my-app', {
80
+ spec: {
81
+ name: 'my-platform',
82
+ region: 'fra',
83
+ envs: [
84
+ ...buildServiceUrlEnvs(serviceConfigs),
85
+ ...buildServicePortEnvs(serviceConfigs),
86
+ ],
87
+ services: buildServices({ serviceConfigs, registryCredentials, logtailToken }),
88
+ ingress: { rules: buildIngressRules(serviceConfigs) },
89
+ },
90
+ })
91
+ ```
92
+
93
+ ## Service Configuration
94
+
95
+ ### Public Service (with ingress)
96
+
97
+ ```typescript
98
+ const config: ServiceConfig = {
99
+ name: 'storefront',
100
+ httpPort: 3000,
101
+ ingressPrefix: '/',
102
+ instanceSizeSlug: 'apps-s-1vcpu-1gb',
103
+ }
104
+ ```
105
+
106
+ ### Internal-only Service
107
+
108
+ ```typescript
109
+ const config: ServiceConfig = {
110
+ name: 'orders',
111
+ internalPorts: [4001],
112
+ instanceSizeSlug: 'apps-s-1vcpu-0.5gb',
113
+ }
114
+ ```
115
+
116
+ ### Custom Protocol (e.g., NATS)
117
+
118
+ ```typescript
119
+ const config: ServiceConfig = {
120
+ name: 'nats',
121
+ internalPorts: [4222, 8222],
122
+ internalUrl: 'nats://nats:4222',
123
+ image: dockerHubImage('nats', '2.10-alpine'),
124
+ }
125
+ ```
126
+
127
+ ## Runtime Helpers
128
+
129
+ Read service configuration in your app at runtime:
130
+
131
+ ```typescript
132
+ import { getServicePort, getServiceUrl } from '@crossdelta/infrastructure/env'
133
+
134
+ const port = getServicePort('api-gateway', 4000)
135
+ const ordersUrl = getServiceUrl('orders')
136
+ ```
137
+
138
+ ## API Reference
139
+
140
+ ### Service Discovery
141
+
142
+ | Function | Description |
143
+ |----------|-------------|
144
+ | `discoverServices(dir)` | Auto-discover all `*.ts` service configs in a directory |
145
+
146
+ ### Builders
147
+
148
+ | Function | Description |
149
+ |----------|-------------|
150
+ | `buildServices(opts)` | Build DO App spec services array |
151
+ | `buildIngressRules(configs)` | Build ingress rules for public services |
152
+ | `buildServiceUrlEnvs(configs)` | Generate `<SERVICE>_URL` env vars |
153
+ | `buildServicePortEnvs(configs)` | Generate `<SERVICE>_PORT` env vars |
154
+ | `buildInternalUrls(configs)` | Get internal URLs for all services |
155
+ | `buildExternalUrls(configs, baseUrl)` | Get external URLs for public services |
156
+
157
+ ### Image Helpers
158
+
159
+ | Function | Description |
160
+ |----------|-------------|
161
+ | `dockerHubImage(repo, tag)` | Create Docker Hub image config |
162
+ | `ghcrImage(repo, tag)` | Create GHCR image config |
163
+
164
+ ### Runtime (import from `/env`)
165
+
166
+ | Function | Description |
167
+ |----------|-------------|
168
+ | `getServicePort(name, default?)` | Read `<SERVICE>_PORT` from env |
169
+ | `getServiceUrl(name)` | Read `<SERVICE>_URL` from env |
170
+
171
+ ## License
172
+
173
+ MIT © [Crossdelta](https://github.com/crossdelta)
package/dist/env.cjs ADDED
@@ -0,0 +1,53 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __moduleCache = /* @__PURE__ */ new WeakMap;
6
+ var __toCommonJS = (from) => {
7
+ var entry = __moduleCache.get(from), desc;
8
+ if (entry)
9
+ return entry;
10
+ entry = __defProp({}, "__esModule", { value: true });
11
+ if (from && typeof from === "object" || typeof from === "function")
12
+ __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
13
+ get: () => from[key],
14
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
15
+ }));
16
+ __moduleCache.set(from, entry);
17
+ return entry;
18
+ };
19
+ var __export = (target, all) => {
20
+ for (var name in all)
21
+ __defProp(target, name, {
22
+ get: all[name],
23
+ enumerable: true,
24
+ configurable: true,
25
+ set: (newValue) => all[name] = () => newValue
26
+ });
27
+ };
28
+
29
+ // lib/env.ts
30
+ var exports_env = {};
31
+ __export(exports_env, {
32
+ getServiceUrl: () => getServiceUrl,
33
+ getServicePort: () => getServicePort
34
+ });
35
+ module.exports = __toCommonJS(exports_env);
36
+
37
+ // lib/helpers/service-runtime.ts
38
+ var toEnvKey = (name) => name.toUpperCase().replace(/-/g, "_");
39
+ function getServicePort(serviceName, defaultPort = 8080) {
40
+ const envKey = `${toEnvKey(serviceName)}_PORT`;
41
+ const envValue = process.env[envKey];
42
+ if (envValue) {
43
+ const parsed = Number(envValue);
44
+ if (!Number.isNaN(parsed)) {
45
+ return parsed;
46
+ }
47
+ }
48
+ return defaultPort;
49
+ }
50
+ function getServiceUrl(serviceName) {
51
+ const envKey = `${toEnvKey(serviceName)}_URL`;
52
+ return process.env[envKey];
53
+ }
package/dist/env.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Environment helpers for services to read configuration.
3
+ * Use this entry point in production services (Node.js, NestJS, etc.)
4
+ *
5
+ * @example
6
+ * import { getServicePort, getServiceUrl } from '@crossdelta/infrastructure/env'
7
+ */
8
+ export * from './helpers/service-runtime';
9
+ export type { ServiceName } from './types/service-names';
package/dist/env.js ADDED
@@ -0,0 +1,21 @@
1
+ // lib/helpers/service-runtime.ts
2
+ var toEnvKey = (name) => name.toUpperCase().replace(/-/g, "_");
3
+ function getServicePort(serviceName, defaultPort = 8080) {
4
+ const envKey = `${toEnvKey(serviceName)}_PORT`;
5
+ const envValue = process.env[envKey];
6
+ if (envValue) {
7
+ const parsed = Number(envValue);
8
+ if (!Number.isNaN(parsed)) {
9
+ return parsed;
10
+ }
11
+ }
12
+ return defaultPort;
13
+ }
14
+ function getServiceUrl(serviceName) {
15
+ const envKey = `${toEnvKey(serviceName)}_URL`;
16
+ return process.env[envKey];
17
+ }
18
+ export {
19
+ getServiceUrl,
20
+ getServicePort
21
+ };
@@ -0,0 +1,29 @@
1
+ import type { Input } from '@pulumi/pulumi';
2
+ /**
3
+ * Ensures that a string ends with a dot (useful for DNS CNAME values).
4
+ */
5
+ export declare const ensureDot: (str: string) => string;
6
+ /**
7
+ * Common alert configuration for services
8
+ */
9
+ export declare const defaultAlerts: {
10
+ rule: string;
11
+ operator: string;
12
+ value: number;
13
+ window: string;
14
+ }[];
15
+ /**
16
+ * Creates log destinations config with Logtail token
17
+ */
18
+ export declare const createLogDestinations: (logtailToken: Input<string>) => {
19
+ name: string;
20
+ logtail: {
21
+ token: Input<string>;
22
+ };
23
+ }[];
24
+ /**
25
+ * Default health check configuration
26
+ */
27
+ export declare const defaultHealthCheck: {
28
+ httpPath: string;
29
+ };
@@ -0,0 +1,7 @@
1
+ import type { ServiceConfig } from '../types';
2
+ /**
3
+ * Auto-discovers all service configurations from a directory.
4
+ * Each .ts file (except index.ts) should export a ServiceConfig as default.
5
+ * @throws Error if duplicate ports are detected
6
+ */
7
+ export declare function discoverServices(servicesDir: string): ServiceConfig[];
@@ -0,0 +1,14 @@
1
+ import { type ImageConfig } from '../types';
2
+ /**
3
+ * Helper to create a Docker Hub image config.
4
+ * For official images, use 'library' as the registry.
5
+ *
6
+ * @example
7
+ * // Official image: library/nats:2.10-alpine
8
+ * dockerHubImage('nats', '2.10-alpine')
9
+ *
10
+ * @example
11
+ * // User image: bitnami/redis:7.0
12
+ * dockerHubImage('redis', '7.0', 'bitnami')
13
+ */
14
+ export declare const dockerHubImage: (repository: string, tag: string, registry?: string) => ImageConfig;
@@ -0,0 +1,13 @@
1
+ import type { Output } from '@pulumi/pulumi';
2
+ /**
3
+ * Gets the image configuration for a given repository.
4
+ * @param repository - The name of the repository.
5
+ * @returns The image configuration.
6
+ */
7
+ export declare const getImage: (repository: string, registryCredentials: Output<string>) => {
8
+ registryType: any;
9
+ registry: string;
10
+ repository: string;
11
+ registryCredentials: Output<string>;
12
+ tag: string;
13
+ };
@@ -0,0 +1,7 @@
1
+ export * from './config';
2
+ export * from './discover-services';
3
+ export * from './docker-hub-image';
4
+ export * from './image';
5
+ export * from './service-builder';
6
+ export * from './service-runtime';
7
+ export * from './service-urls';
@@ -0,0 +1,27 @@
1
+ import type { AppSpecService } from '@pulumi/digitalocean/types/input';
2
+ import type { Output } from '@pulumi/pulumi';
3
+ import type { ServiceConfig } from '../types';
4
+ export interface BuildServicesOptions {
5
+ serviceConfigs: ServiceConfig[];
6
+ registryCredentials: Output<string>;
7
+ logtailToken: Output<string>;
8
+ }
9
+ /**
10
+ * Builds the services array from service configs.
11
+ */
12
+ export declare function buildServices(options: BuildServicesOptions): AppSpecService[];
13
+ /**
14
+ * Builds the ingress rules from service configs.
15
+ * Only includes services that have both an ingressPrefix AND httpPort defined.
16
+ * Services with only internalPorts cannot have ingress rules.
17
+ */
18
+ export declare function buildIngressRules(serviceConfigs: ServiceConfig[]): {
19
+ component: {
20
+ name: (import("@pulumi/pulumi").Input<string> | undefined) & string;
21
+ };
22
+ match: {
23
+ path: {
24
+ prefix: string;
25
+ };
26
+ };
27
+ }[];
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Runtime helpers for services to access their configuration.
3
+ * These functions read from environment variables set by generate-env (local)
4
+ * or injected by DO App Platform (production).
5
+ */
6
+ import type { ServiceName } from '../types';
7
+ /**
8
+ * Get the port for a service from environment variables.
9
+ * Reads from <SERVICE_NAME>_PORT (e.g., ORDERS_PORT, API_GATEWAY_PORT)
10
+ *
11
+ * @param serviceName - The service name (e.g., 'orders', 'api-gateway')
12
+ * @param defaultPort - Fallback port if env var is not set (default: 8080)
13
+ * @returns The configured port number
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * import { getServicePort } from '@crossdelta/infrastructure/env'
18
+ *
19
+ * const port = getServicePort('orders', 3001)
20
+ * // Reads process.env.ORDERS_PORT, falls back to 3001
21
+ * ```
22
+ */
23
+ export declare function getServicePort(serviceName: ServiceName, defaultPort?: number): number;
24
+ /**
25
+ * Get the URL for a service from environment variables.
26
+ * Reads from <SERVICE_NAME>_URL (e.g., ORDERS_URL, API_GATEWAY_URL)
27
+ *
28
+ * @param serviceName - The service name (e.g., 'orders', 'api-gateway')
29
+ * @returns The service URL or undefined if not set
30
+ *
31
+ * @example
32
+ * ```typescript
33
+ * import { getServiceUrl } from '@crossdelta/infrastructure/env'
34
+ *
35
+ * const ordersUrl = getServiceUrl('orders')
36
+ * // Returns process.env.ORDERS_URL
37
+ * ```
38
+ */
39
+ export declare function getServiceUrl(serviceName: ServiceName): string | undefined;
@@ -0,0 +1,27 @@
1
+ import type { AppSpecEnv } from '@pulumi/digitalocean/types/input';
2
+ import type { ServiceConfig } from '../types';
3
+ /**
4
+ * Generate internal service URLs for service-to-service communication.
5
+ * Uses DigitalOcean App Platform's internal DNS (e.g., http://orders:3001)
6
+ * Uses internalUrl if defined, otherwise defaults to http://{name}:{port}
7
+ */
8
+ export declare function buildInternalUrls(serviceConfigs: ServiceConfig[]): Record<string, string>;
9
+ /**
10
+ * Generate external service URLs based on primary domain and ingress prefix.
11
+ */
12
+ export declare function buildExternalUrls(serviceConfigs: ServiceConfig[], baseUrl: string): Record<string, string>;
13
+ /**
14
+ * Generate local development URLs (localhost with configured ports).
15
+ */
16
+ export declare function buildLocalUrls(serviceConfigs: ServiceConfig[]): Record<string, string>;
17
+ /**
18
+ * Generate environment variables for service URLs.
19
+ * Used to inject service URLs into DO App Platform.
20
+ * Uses internalUrl if defined, otherwise defaults to http://{name}:{port}
21
+ */
22
+ export declare function buildServiceUrlEnvs(serviceConfigs: ServiceConfig[]): AppSpecEnv[];
23
+ /**
24
+ * Generate environment variables for service ports.
25
+ * Used to inject service ports into DO App Platform.
26
+ */
27
+ export declare function buildServicePortEnvs(serviceConfigs: ServiceConfig[]): AppSpecEnv[];