services-as-software 0.1.0 → 2.0.1

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.
Files changed (78) hide show
  1. package/.turbo/turbo-build.log +5 -0
  2. package/CHANGELOG.md +10 -0
  3. package/README.md +235 -225
  4. package/dist/client.d.ts +25 -0
  5. package/dist/client.d.ts.map +1 -0
  6. package/dist/client.js +103 -0
  7. package/dist/client.js.map +1 -0
  8. package/dist/endpoint.d.ts +102 -0
  9. package/dist/endpoint.d.ts.map +1 -0
  10. package/dist/endpoint.js +96 -0
  11. package/dist/endpoint.js.map +1 -0
  12. package/dist/entities/billing.d.ts +60 -0
  13. package/dist/entities/billing.d.ts.map +1 -0
  14. package/dist/entities/billing.js +954 -0
  15. package/dist/entities/billing.js.map +1 -0
  16. package/dist/entities/customers.d.ts +45 -0
  17. package/dist/entities/customers.d.ts.map +1 -0
  18. package/dist/entities/customers.js +679 -0
  19. package/dist/entities/customers.js.map +1 -0
  20. package/dist/entities/delivery.d.ts +59 -0
  21. package/dist/entities/delivery.d.ts.map +1 -0
  22. package/dist/entities/delivery.js +890 -0
  23. package/dist/entities/delivery.js.map +1 -0
  24. package/dist/entities/index.d.ts +114 -0
  25. package/dist/entities/index.d.ts.map +1 -0
  26. package/dist/entities/index.js +89 -0
  27. package/dist/entities/index.js.map +1 -0
  28. package/dist/entities/operations.d.ts +59 -0
  29. package/dist/entities/operations.d.ts.map +1 -0
  30. package/dist/entities/operations.js +1010 -0
  31. package/dist/entities/operations.js.map +1 -0
  32. package/dist/entities/orchestration.d.ts +52 -0
  33. package/dist/entities/orchestration.d.ts.map +1 -0
  34. package/dist/entities/orchestration.js +883 -0
  35. package/dist/entities/orchestration.js.map +1 -0
  36. package/dist/entities/services.d.ts +50 -0
  37. package/dist/entities/services.d.ts.map +1 -0
  38. package/dist/entities/services.js +805 -0
  39. package/dist/entities/services.js.map +1 -0
  40. package/dist/helpers.d.ts +362 -0
  41. package/dist/helpers.d.ts.map +1 -0
  42. package/dist/helpers.js +400 -0
  43. package/dist/helpers.js.map +1 -0
  44. package/dist/index.d.ts +17 -215
  45. package/dist/index.d.ts.map +1 -0
  46. package/dist/index.js +18 -172
  47. package/dist/index.js.map +1 -0
  48. package/dist/provider.d.ts +85 -0
  49. package/dist/provider.d.ts.map +1 -0
  50. package/dist/provider.js +158 -0
  51. package/dist/provider.js.map +1 -0
  52. package/dist/service.d.ts +43 -0
  53. package/dist/service.d.ts.map +1 -0
  54. package/dist/service.js +206 -0
  55. package/dist/service.js.map +1 -0
  56. package/dist/types.d.ts +469 -0
  57. package/dist/types.d.ts.map +1 -0
  58. package/dist/types.js +5 -0
  59. package/dist/types.js.map +1 -0
  60. package/examples/client-usage.ts +82 -0
  61. package/examples/translation-service.ts +227 -0
  62. package/package.json +24 -38
  63. package/src/client.ts +132 -0
  64. package/src/endpoint.ts +144 -0
  65. package/src/entities/billing.ts +1037 -0
  66. package/src/entities/customers.ts +740 -0
  67. package/src/entities/delivery.ts +974 -0
  68. package/src/entities/index.ts +157 -0
  69. package/src/entities/operations.ts +1099 -0
  70. package/src/entities/orchestration.ts +956 -0
  71. package/src/entities/services.ts +872 -0
  72. package/src/helpers.ts +474 -0
  73. package/src/index.ts +97 -0
  74. package/src/provider.ts +183 -0
  75. package/src/service.test.ts +195 -0
  76. package/src/service.ts +266 -0
  77. package/src/types.ts +543 -0
  78. package/tsconfig.json +9 -0
@@ -0,0 +1,183 @@
1
+ /**
2
+ * Service provider for managing multiple services
3
+ */
4
+
5
+ import type { Provider, ServiceClient, ClientConfig } from './types.js'
6
+ import { Client } from './client.js'
7
+
8
+ /**
9
+ * Provider configuration
10
+ */
11
+ export interface ProviderConfig {
12
+ /** Provider name */
13
+ name: string
14
+ /** Base URL for all services */
15
+ baseUrl: string
16
+ /** Authentication configuration */
17
+ auth?: ClientConfig['auth']
18
+ /** Available services */
19
+ services?: string[]
20
+ }
21
+
22
+ /**
23
+ * Create a service provider
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * const provider = Provider({
28
+ * name: 'AWS',
29
+ * baseUrl: 'https://api.aws.amazon.com',
30
+ * auth: {
31
+ * type: 'api-key',
32
+ * credentials: { apiKey: process.env.AWS_API_KEY },
33
+ * },
34
+ * services: ['translate', 'comprehend', 'polly'],
35
+ * })
36
+ *
37
+ * // Get a service client
38
+ * const translate = provider.service('translate')
39
+ * const result = await translate.do('translate', {
40
+ * text: 'Hello',
41
+ * to: 'es',
42
+ * })
43
+ * ```
44
+ */
45
+ export function Provider(config: ProviderConfig): Provider {
46
+ return {
47
+ name: config.name,
48
+ baseUrl: config.baseUrl,
49
+ auth: config.auth,
50
+ services: config.services || [],
51
+
52
+ service<T extends ServiceClient>(serviceName: string): T {
53
+ // Construct service URL
54
+ const serviceUrl = `${config.baseUrl}/${serviceName}`
55
+
56
+ // Create a client for this service
57
+ const client = Client({
58
+ url: serviceUrl,
59
+ auth: config.auth,
60
+ })
61
+
62
+ return client as T
63
+ },
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Common cloud providers
69
+ */
70
+ export const providers = {
71
+ /**
72
+ * AWS provider
73
+ */
74
+ aws(credentials: { accessKeyId: string; secretAccessKey: string; region?: string }): Provider {
75
+ return Provider({
76
+ name: 'AWS',
77
+ baseUrl: `https://api.${credentials.region || 'us-east-1'}.amazonaws.com`,
78
+ auth: {
79
+ type: 'api-key',
80
+ credentials: {
81
+ apiKey: credentials.accessKeyId,
82
+ secret: credentials.secretAccessKey,
83
+ },
84
+ },
85
+ services: [
86
+ 'translate',
87
+ 'comprehend',
88
+ 'polly',
89
+ 'rekognition',
90
+ 'textract',
91
+ 'transcribe',
92
+ ],
93
+ })
94
+ },
95
+
96
+ /**
97
+ * Google Cloud provider
98
+ */
99
+ gcp(credentials: { apiKey: string; projectId?: string }): Provider {
100
+ return Provider({
101
+ name: 'Google Cloud',
102
+ baseUrl: 'https://api.googleapis.com',
103
+ auth: {
104
+ type: 'api-key',
105
+ credentials: { apiKey: credentials.apiKey },
106
+ },
107
+ services: [
108
+ 'translate',
109
+ 'language',
110
+ 'speech',
111
+ 'texttospeech',
112
+ 'vision',
113
+ ],
114
+ })
115
+ },
116
+
117
+ /**
118
+ * Azure provider
119
+ */
120
+ azure(credentials: { subscriptionKey: string; region?: string }): Provider {
121
+ return Provider({
122
+ name: 'Azure',
123
+ baseUrl: `https://${credentials.region || 'eastus'}.api.cognitive.microsoft.com`,
124
+ auth: {
125
+ type: 'api-key',
126
+ credentials: { apiKey: credentials.subscriptionKey },
127
+ },
128
+ services: [
129
+ 'translator',
130
+ 'language',
131
+ 'speech',
132
+ 'vision',
133
+ 'form-recognizer',
134
+ ],
135
+ })
136
+ },
137
+
138
+ /**
139
+ * OpenAI provider
140
+ */
141
+ openai(credentials: { apiKey: string }): Provider {
142
+ return Provider({
143
+ name: 'OpenAI',
144
+ baseUrl: 'https://api.openai.com/v1',
145
+ auth: {
146
+ type: 'api-key',
147
+ credentials: { apiKey: credentials.apiKey },
148
+ },
149
+ services: [
150
+ 'chat',
151
+ 'completions',
152
+ 'embeddings',
153
+ 'images',
154
+ 'audio',
155
+ ],
156
+ })
157
+ },
158
+
159
+ /**
160
+ * Anthropic provider
161
+ */
162
+ anthropic(credentials: { apiKey: string }): Provider {
163
+ return Provider({
164
+ name: 'Anthropic',
165
+ baseUrl: 'https://api.anthropic.com/v1',
166
+ auth: {
167
+ type: 'api-key',
168
+ credentials: { apiKey: credentials.apiKey },
169
+ },
170
+ services: [
171
+ 'messages',
172
+ 'completions',
173
+ ],
174
+ })
175
+ },
176
+
177
+ /**
178
+ * Custom provider
179
+ */
180
+ custom(config: ProviderConfig): Provider {
181
+ return Provider(config)
182
+ },
183
+ }
@@ -0,0 +1,195 @@
1
+ /**
2
+ * Tests for Service implementation
3
+ */
4
+
5
+ import { describe, it, expect } from 'vitest'
6
+ import { Service, Endpoint, POST, GET } from './index.js'
7
+ import type { ServiceContext } from './types.js'
8
+
9
+ describe('Service', () => {
10
+ it('should create a service with basic configuration', () => {
11
+ const service = Service({
12
+ name: 'test-service',
13
+ version: '1.0.0',
14
+ endpoints: [],
15
+ })
16
+
17
+ expect(service).toBeDefined()
18
+ expect(service.definition.name).toBe('test-service')
19
+ expect(service.definition.version).toBe('1.0.0')
20
+ })
21
+
22
+ it('should call an endpoint and return result', async () => {
23
+ const service = Service({
24
+ name: 'test-service',
25
+ version: '1.0.0',
26
+ endpoints: [
27
+ POST({
28
+ name: 'echo',
29
+ handler: async (input: { message: string }) => {
30
+ return { echoed: input.message }
31
+ },
32
+ }),
33
+ ],
34
+ })
35
+
36
+ const result = await service.call<{ message: string }, { echoed: string }>('echo', {
37
+ message: 'hello',
38
+ })
39
+
40
+ expect(result.echoed).toBe('hello')
41
+ })
42
+
43
+ it('should provide service context to handlers', async () => {
44
+ let capturedContext: ServiceContext | undefined
45
+
46
+ const service = Service({
47
+ name: 'test-service',
48
+ version: '1.0.0',
49
+ endpoints: [
50
+ POST({
51
+ name: 'test',
52
+ handler: async (_input: unknown, context?: ServiceContext) => {
53
+ capturedContext = context
54
+ return { ok: true }
55
+ },
56
+ }),
57
+ ],
58
+ })
59
+
60
+ await service.call('test', {}, { requestId: 'test-123', entitlements: ['test'] })
61
+
62
+ expect(capturedContext).toBeDefined()
63
+ expect(capturedContext?.requestId).toBe('test-123')
64
+ expect(capturedContext?.entitlements).toContain('test')
65
+ })
66
+
67
+ it('should throw error for unknown endpoint', async () => {
68
+ const service = Service({
69
+ name: 'test-service',
70
+ version: '1.0.0',
71
+ endpoints: [],
72
+ })
73
+
74
+ await expect(service.call('unknown', {})).rejects.toThrow('Endpoint not found: unknown')
75
+ })
76
+
77
+ it('should calculate KPIs', async () => {
78
+ const service = Service({
79
+ name: 'test-service',
80
+ version: '1.0.0',
81
+ endpoints: [],
82
+ kpis: [
83
+ {
84
+ id: 'test-kpi',
85
+ name: 'Test KPI',
86
+ calculate: async () => 42,
87
+ },
88
+ ],
89
+ })
90
+
91
+ const kpis = await service.kpis()
92
+ expect(kpis['test-kpi']).toBe(42)
93
+ })
94
+
95
+ it('should calculate OKRs with key results', async () => {
96
+ const service = Service({
97
+ name: 'test-service',
98
+ version: '1.0.0',
99
+ endpoints: [],
100
+ okrs: [
101
+ {
102
+ id: 'okr-1',
103
+ objective: 'Test objective',
104
+ keyResults: [
105
+ {
106
+ description: 'Test key result',
107
+ measure: async () => 75,
108
+ target: 100,
109
+ unit: 'points',
110
+ },
111
+ ],
112
+ },
113
+ ],
114
+ })
115
+
116
+ const okrs = await service.okrs()
117
+ expect(okrs).toHaveLength(1)
118
+ expect(okrs[0]?.objective).toBe('Test objective')
119
+ expect(okrs[0]?.keyResults[0]?.current).toBe(75)
120
+ expect(okrs[0]?.keyResults[0]?.target).toBe(100)
121
+ })
122
+
123
+ it('should register event handlers', () => {
124
+ const service = Service({
125
+ name: 'test-service',
126
+ version: '1.0.0',
127
+ endpoints: [],
128
+ })
129
+
130
+ let eventFired = false
131
+ service.on('test.event', () => {
132
+ eventFired = true
133
+ })
134
+
135
+ // Event handlers are registered (actual firing would happen in event system)
136
+ expect(eventFired).toBe(false) // Not fired yet
137
+ })
138
+
139
+ it('should register scheduled tasks', () => {
140
+ const service = Service({
141
+ name: 'test-service',
142
+ version: '1.0.0',
143
+ endpoints: [],
144
+ })
145
+
146
+ let taskRan = false
147
+ service.every('0 * * * *', () => {
148
+ taskRan = true
149
+ })
150
+
151
+ // Task is registered (actual execution would happen in scheduler)
152
+ expect(taskRan).toBe(false) // Not run yet
153
+ })
154
+ })
155
+
156
+ describe('Endpoint helpers', () => {
157
+ it('should create POST endpoint', () => {
158
+ const endpoint = POST({
159
+ name: 'test',
160
+ handler: async () => ({ ok: true }),
161
+ })
162
+
163
+ expect(endpoint.method).toBe('POST')
164
+ expect(endpoint.name).toBe('test')
165
+ })
166
+
167
+ it('should create GET endpoint', () => {
168
+ const endpoint = GET({
169
+ name: 'test',
170
+ handler: async () => ({ ok: true }),
171
+ })
172
+
173
+ expect(endpoint.method).toBe('GET')
174
+ expect(endpoint.name).toBe('test')
175
+ })
176
+
177
+ it('should default to requiring auth', () => {
178
+ const endpoint = Endpoint({
179
+ name: 'test',
180
+ handler: async () => ({ ok: true }),
181
+ })
182
+
183
+ expect(endpoint.requiresAuth).toBe(true)
184
+ })
185
+
186
+ it('should allow disabling auth requirement', () => {
187
+ const endpoint = Endpoint({
188
+ name: 'test',
189
+ handler: async () => ({ ok: true }),
190
+ requiresAuth: false,
191
+ })
192
+
193
+ expect(endpoint.requiresAuth).toBe(false)
194
+ })
195
+ })
package/src/service.ts ADDED
@@ -0,0 +1,266 @@
1
+ /**
2
+ * Service implementation
3
+ */
4
+
5
+ import type {
6
+ ServiceDefinition,
7
+ Service,
8
+ ServiceContext,
9
+ EndpointDefinition,
10
+ EventHandler,
11
+ ScheduledTask,
12
+ Order,
13
+ Quote,
14
+ Subscription,
15
+ Notification,
16
+ OKRDefinition,
17
+ JSONSchema,
18
+ } from './types.js'
19
+
20
+ /**
21
+ * Create a service from a definition
22
+ *
23
+ * @example
24
+ * ```ts
25
+ * const service = Service({
26
+ * name: 'translation-service',
27
+ * version: '1.0.0',
28
+ * description: 'AI-powered translation service',
29
+ * pricing: {
30
+ * model: 'per-use',
31
+ * pricePerUnit: 0.01,
32
+ * currency: 'USD',
33
+ * },
34
+ * endpoints: [
35
+ * Endpoint({
36
+ * name: 'translate',
37
+ * method: 'POST',
38
+ * path: '/translate',
39
+ * input: {
40
+ * type: 'object',
41
+ * properties: {
42
+ * text: { type: 'string' },
43
+ * from: { type: 'string' },
44
+ * to: { type: 'string' },
45
+ * },
46
+ * required: ['text', 'to'],
47
+ * },
48
+ * handler: async (input) => {
49
+ * // Translation logic here
50
+ * return { translatedText: input.text }
51
+ * },
52
+ * }),
53
+ * ],
54
+ * })
55
+ * ```
56
+ */
57
+ export function Service(definition: ServiceDefinition): Service {
58
+ // Store endpoints by name for quick lookup
59
+ const endpointMap = new Map<string, EndpointDefinition>()
60
+ for (const endpoint of definition.endpoints) {
61
+ endpointMap.set(endpoint.name, endpoint)
62
+ }
63
+
64
+ // Store event handlers
65
+ const eventHandlers = new Map<string, EventHandler[]>()
66
+ if (definition.events) {
67
+ for (const [event, handler] of Object.entries(definition.events)) {
68
+ eventHandlers.set(event, [handler])
69
+ }
70
+ }
71
+
72
+ // Store scheduled tasks
73
+ const scheduledTasks = new Map<string, ScheduledTask>()
74
+ if (definition.scheduled) {
75
+ for (const task of definition.scheduled) {
76
+ scheduledTasks.set(task.name, task)
77
+ }
78
+ }
79
+
80
+ // Create the service instance
81
+ const service: Service = {
82
+ definition,
83
+
84
+ // Call an endpoint
85
+ async call<TInput, TOutput>(
86
+ endpoint: string,
87
+ input: TInput,
88
+ context?: ServiceContext
89
+ ): Promise<TOutput> {
90
+ const ep = endpointMap.get(endpoint)
91
+ if (!ep) {
92
+ throw new Error(`Endpoint not found: ${endpoint}`)
93
+ }
94
+
95
+ // Create context if not provided
96
+ const ctx: ServiceContext = context || {
97
+ requestId: generateRequestId(),
98
+ entitlements: [],
99
+ }
100
+
101
+ // Track usage if tracker is available
102
+ if (ctx.usage && ctx.customerId) {
103
+ await ctx.usage.track({
104
+ customerId: ctx.customerId,
105
+ resource: endpoint,
106
+ quantity: 1,
107
+ timestamp: new Date(),
108
+ })
109
+ }
110
+
111
+ // Call the handler
112
+ return ep.handler(input, ctx) as Promise<TOutput>
113
+ },
114
+
115
+ // Ask a question
116
+ async ask(question: string, context?: unknown): Promise<string> {
117
+ return service.call('ask', { question, context })
118
+ },
119
+
120
+ // Deliver results
121
+ async deliver(orderId: string, results: unknown): Promise<void> {
122
+ return service.call('deliver', { orderId, results })
123
+ },
124
+
125
+ // Execute a task
126
+ async do(action: string, input?: unknown): Promise<unknown> {
127
+ return service.call('do', { action, input })
128
+ },
129
+
130
+ // Generate content
131
+ async generate(prompt: string, options?: unknown): Promise<unknown> {
132
+ return service.call('generate', { prompt, options })
133
+ },
134
+
135
+ // Type checking/validation
136
+ async is(value: unknown, type: string | JSONSchema): Promise<boolean> {
137
+ return service.call('is', { value, type })
138
+ },
139
+
140
+ // Send notification
141
+ async notify(notification: Notification): Promise<void> {
142
+ return service.call('notify', notification)
143
+ },
144
+
145
+ // Place an order
146
+ async order<TProduct>(product: TProduct, quantity: number): Promise<Order<TProduct>> {
147
+ return service.call('order', { product, quantity })
148
+ },
149
+
150
+ // Request a quote
151
+ async quote<TProduct>(product: TProduct, quantity: number): Promise<Quote<TProduct>> {
152
+ return service.call('quote', { product, quantity })
153
+ },
154
+
155
+ // Subscribe to a plan
156
+ async subscribe(planId: string): Promise<Subscription> {
157
+ return service.call('subscribe', { planId })
158
+ },
159
+
160
+ // Get entitlements
161
+ async entitlements(): Promise<string[]> {
162
+ return service.call('entitlements', {})
163
+ },
164
+
165
+ // Get KPIs
166
+ async kpis(): Promise<Record<string, number | string>> {
167
+ const result: Record<string, number | string> = {}
168
+ if (definition.kpis) {
169
+ for (const kpi of definition.kpis) {
170
+ result[kpi.id] = await kpi.calculate()
171
+ }
172
+ }
173
+ return result
174
+ },
175
+
176
+ // Get OKRs
177
+ async okrs(): Promise<OKRDefinition[]> {
178
+ if (!definition.okrs) {
179
+ return []
180
+ }
181
+
182
+ // Calculate current values for key results
183
+ const okrs = await Promise.all(
184
+ definition.okrs.map(async (okr) => {
185
+ const keyResults = await Promise.all(
186
+ okr.keyResults.map(async (kr) => ({
187
+ ...kr,
188
+ current: await kr.measure(),
189
+ }))
190
+ )
191
+ return { ...okr, keyResults }
192
+ })
193
+ )
194
+
195
+ return okrs
196
+ },
197
+
198
+ // Register event handler
199
+ on<TPayload>(
200
+ event: string,
201
+ handler: (payload: TPayload, context?: ServiceContext) => void | Promise<void>
202
+ ): void {
203
+ const handlers = eventHandlers.get(event) || []
204
+ handlers.push({
205
+ event,
206
+ handler: handler as (payload: unknown, context?: ServiceContext) => void | Promise<void>,
207
+ })
208
+ eventHandlers.set(event, handlers)
209
+ },
210
+
211
+ // Schedule recurring task
212
+ every(
213
+ schedule: string,
214
+ handler: (context?: ServiceContext) => void | Promise<void>
215
+ ): void {
216
+ const taskName = `task-${scheduledTasks.size + 1}`
217
+ scheduledTasks.set(taskName, {
218
+ name: taskName,
219
+ schedule,
220
+ handler: (_input?: unknown, context?: ServiceContext) => handler(context),
221
+ enabled: true,
222
+ })
223
+ },
224
+
225
+ // Add queue processor
226
+ queue<TJob>(
227
+ name: string,
228
+ handler: (job: TJob, context?: ServiceContext) => void | Promise<void>
229
+ ): void {
230
+ // Queue processing would typically integrate with a queue system
231
+ // For now, we add it as an event handler
232
+ service.on(`queue:${name}`, handler)
233
+ },
234
+
235
+ // Get service as RPC target
236
+ asRPC(): unknown {
237
+ // This would integrate with the RPC system from ai-functions
238
+ // For now, return a placeholder
239
+ return {
240
+ _type: 'rpc-target',
241
+ service: definition.name,
242
+ version: definition.version,
243
+ }
244
+ },
245
+
246
+ // Get service as API routes
247
+ asAPI(): unknown {
248
+ // This would generate HTTP/REST API routes
249
+ // For now, return a placeholder with route definitions
250
+ return definition.endpoints.map((ep) => ({
251
+ method: ep.method || 'POST',
252
+ path: ep.path || `/${ep.name}`,
253
+ handler: ep.handler,
254
+ }))
255
+ },
256
+ }
257
+
258
+ return service
259
+ }
260
+
261
+ /**
262
+ * Generate a unique request ID
263
+ */
264
+ function generateRequestId(): string {
265
+ return `req_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`
266
+ }