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.
- package/.turbo/turbo-build.log +5 -0
- package/CHANGELOG.md +10 -0
- package/README.md +235 -225
- package/dist/client.d.ts +25 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +103 -0
- package/dist/client.js.map +1 -0
- package/dist/endpoint.d.ts +102 -0
- package/dist/endpoint.d.ts.map +1 -0
- package/dist/endpoint.js +96 -0
- package/dist/endpoint.js.map +1 -0
- package/dist/entities/billing.d.ts +60 -0
- package/dist/entities/billing.d.ts.map +1 -0
- package/dist/entities/billing.js +954 -0
- package/dist/entities/billing.js.map +1 -0
- package/dist/entities/customers.d.ts +45 -0
- package/dist/entities/customers.d.ts.map +1 -0
- package/dist/entities/customers.js +679 -0
- package/dist/entities/customers.js.map +1 -0
- package/dist/entities/delivery.d.ts +59 -0
- package/dist/entities/delivery.d.ts.map +1 -0
- package/dist/entities/delivery.js +890 -0
- package/dist/entities/delivery.js.map +1 -0
- package/dist/entities/index.d.ts +114 -0
- package/dist/entities/index.d.ts.map +1 -0
- package/dist/entities/index.js +89 -0
- package/dist/entities/index.js.map +1 -0
- package/dist/entities/operations.d.ts +59 -0
- package/dist/entities/operations.d.ts.map +1 -0
- package/dist/entities/operations.js +1010 -0
- package/dist/entities/operations.js.map +1 -0
- package/dist/entities/orchestration.d.ts +52 -0
- package/dist/entities/orchestration.d.ts.map +1 -0
- package/dist/entities/orchestration.js +883 -0
- package/dist/entities/orchestration.js.map +1 -0
- package/dist/entities/services.d.ts +50 -0
- package/dist/entities/services.d.ts.map +1 -0
- package/dist/entities/services.js +805 -0
- package/dist/entities/services.js.map +1 -0
- package/dist/helpers.d.ts +362 -0
- package/dist/helpers.d.ts.map +1 -0
- package/dist/helpers.js +400 -0
- package/dist/helpers.js.map +1 -0
- package/dist/index.d.ts +17 -215
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -172
- package/dist/index.js.map +1 -0
- package/dist/provider.d.ts +85 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +158 -0
- package/dist/provider.js.map +1 -0
- package/dist/service.d.ts +43 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +206 -0
- package/dist/service.js.map +1 -0
- package/dist/types.d.ts +469 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/examples/client-usage.ts +82 -0
- package/examples/translation-service.ts +227 -0
- package/package.json +24 -38
- package/src/client.ts +132 -0
- package/src/endpoint.ts +144 -0
- package/src/entities/billing.ts +1037 -0
- package/src/entities/customers.ts +740 -0
- package/src/entities/delivery.ts +974 -0
- package/src/entities/index.ts +157 -0
- package/src/entities/operations.ts +1099 -0
- package/src/entities/orchestration.ts +956 -0
- package/src/entities/services.ts +872 -0
- package/src/helpers.ts +474 -0
- package/src/index.ts +97 -0
- package/src/provider.ts +183 -0
- package/src/service.test.ts +195 -0
- package/src/service.ts +266 -0
- package/src/types.ts +543 -0
- package/tsconfig.json +9 -0
package/src/provider.ts
ADDED
|
@@ -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
|
+
}
|