@dxos/edge-compute 0.0.0
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 +105 -0
- package/dist/lib/neutral/bundler/index.mjs +213 -0
- package/dist/lib/neutral/bundler/index.mjs.map +7 -0
- package/dist/lib/neutral/chunk-J5LGTIGS.mjs +10 -0
- package/dist/lib/neutral/chunk-J5LGTIGS.mjs.map +7 -0
- package/dist/lib/neutral/chunk-RN6KVGN4.mjs +63 -0
- package/dist/lib/neutral/chunk-RN6KVGN4.mjs.map +7 -0
- package/dist/lib/neutral/index.mjs +343 -0
- package/dist/lib/neutral/index.mjs.map +7 -0
- package/dist/lib/neutral/meta.json +1 -0
- package/dist/lib/neutral/native/index.mjs +186 -0
- package/dist/lib/neutral/native/index.mjs.map +7 -0
- package/dist/lib/neutral/testing/index.mjs +41 -0
- package/dist/lib/neutral/testing/index.mjs.map +7 -0
- package/dist/types/src/EdgeOperationInvoker.d.ts +21 -0
- package/dist/types/src/EdgeOperationInvoker.d.ts.map +1 -0
- package/dist/types/src/EdgeProcessManager.d.ts +14 -0
- package/dist/types/src/EdgeProcessManager.d.ts.map +1 -0
- package/dist/types/src/EdgeTriggerManager.d.ts +14 -0
- package/dist/types/src/EdgeTriggerManager.d.ts.map +1 -0
- package/dist/types/src/FunctionsServiceClient.d.ts +65 -0
- package/dist/types/src/FunctionsServiceClient.d.ts.map +1 -0
- package/dist/types/src/bundler/bundler.d.ts +38 -0
- package/dist/types/src/bundler/bundler.d.ts.map +1 -0
- package/dist/types/src/bundler/bundler.test.d.ts +2 -0
- package/dist/types/src/bundler/bundler.test.d.ts.map +1 -0
- package/dist/types/src/bundler/index.d.ts +2 -0
- package/dist/types/src/bundler/index.d.ts.map +1 -0
- package/dist/types/src/bundler/plugins/embedded-vendored-packages.d.ts +3 -0
- package/dist/types/src/bundler/plugins/embedded-vendored-packages.d.ts.map +1 -0
- package/dist/types/src/bundler/plugins/http-plugin-esbuild.d.ts +34 -0
- package/dist/types/src/bundler/plugins/http-plugin-esbuild.d.ts.map +1 -0
- package/dist/types/src/bundler/plugins/index.d.ts +4 -0
- package/dist/types/src/bundler/plugins/index.d.ts.map +1 -0
- package/dist/types/src/bundler/plugins/r2-vendored-packages.d.ts +5 -0
- package/dist/types/src/bundler/plugins/r2-vendored-packages.d.ts.map +1 -0
- package/dist/types/src/edge-client.d.ts +39 -0
- package/dist/types/src/edge-client.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +6 -0
- package/dist/types/src/index.d.ts.map +1 -0
- package/dist/types/src/native/bundler.d.ts +15 -0
- package/dist/types/src/native/bundler.d.ts.map +1 -0
- package/dist/types/src/native/index.d.ts +2 -0
- package/dist/types/src/native/index.d.ts.map +1 -0
- package/dist/types/src/testing/index.d.ts +2 -0
- package/dist/types/src/testing/index.d.ts.map +1 -0
- package/dist/types/src/testing/logger.d.ts +4 -0
- package/dist/types/src/testing/logger.d.ts.map +1 -0
- package/dist/types/src/testing/services.d.ts +53 -0
- package/dist/types/src/testing/services.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -0
- package/package.json +95 -0
- package/src/EdgeOperationInvoker.ts +65 -0
- package/src/EdgeProcessManager.ts +47 -0
- package/src/EdgeTriggerManager.ts +70 -0
- package/src/FunctionsServiceClient.ts +189 -0
- package/src/bundler/bundler.test.ts +108 -0
- package/src/bundler/bundler.ts +232 -0
- package/src/bundler/index.ts +5 -0
- package/src/bundler/plugins/embedded-vendored-packages.ts +39 -0
- package/src/bundler/plugins/http-plugin-esbuild.ts +77 -0
- package/src/bundler/plugins/index.ts +7 -0
- package/src/bundler/plugins/r2-vendored-packages.ts +35 -0
- package/src/edge-client.ts +156 -0
- package/src/index.ts +9 -0
- package/src/native/bundler.ts +231 -0
- package/src/native/index.ts +5 -0
- package/src/testing/index.ts +5 -0
- package/src/testing/logger.ts +14 -0
- package/src/testing/services.ts +95 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
/* eslint-disable no-console */
|
|
6
|
+
|
|
7
|
+
import * as Array from 'effect/Array';
|
|
8
|
+
import * as Function from 'effect/Function';
|
|
9
|
+
import * as Order from 'effect/Order';
|
|
10
|
+
import * as Record from 'effect/Record';
|
|
11
|
+
import { type Message, build } from 'esbuild';
|
|
12
|
+
import { statSync } from 'node:fs';
|
|
13
|
+
import { writeFile } from 'node:fs/promises';
|
|
14
|
+
import * as fs from 'node:fs/promises';
|
|
15
|
+
import { basename, dirname, join, relative, resolve } from 'node:path';
|
|
16
|
+
|
|
17
|
+
import { BaseError } from '@dxos/errors';
|
|
18
|
+
import { PublicKey } from '@dxos/keys';
|
|
19
|
+
import { Unit, trim } from '@dxos/util';
|
|
20
|
+
|
|
21
|
+
import { httpPlugin } from '../bundler/plugins/http-plugin-esbuild';
|
|
22
|
+
|
|
23
|
+
type BundleOptions = {
|
|
24
|
+
entryPoint: string;
|
|
25
|
+
verbose?: boolean;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type BundleResult = {
|
|
29
|
+
entryPoint: string;
|
|
30
|
+
assets: Record<string, Uint8Array>;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Bundles a function.
|
|
35
|
+
* Pulls in node_modules and wasm files.
|
|
36
|
+
*/
|
|
37
|
+
export const bundleFunction = async (options: BundleOptions): Promise<BundleResult> => {
|
|
38
|
+
const outdir = `/tmp/dxos-functions-bundle-${new Date().toISOString()}-${PublicKey.random().toHex()}`;
|
|
39
|
+
const gitDir = findGitDir(dirname(options.entryPoint));
|
|
40
|
+
|
|
41
|
+
const result = await build({
|
|
42
|
+
entryPoints: {
|
|
43
|
+
// Gets mapped to `index.js` by esbuild.
|
|
44
|
+
index: 'dxos:entrypoint',
|
|
45
|
+
},
|
|
46
|
+
bundle: true,
|
|
47
|
+
format: 'esm',
|
|
48
|
+
platform: 'browser',
|
|
49
|
+
conditions: ['workerd', 'worker', 'browser'],
|
|
50
|
+
outdir,
|
|
51
|
+
metafile: true,
|
|
52
|
+
treeShaking: true,
|
|
53
|
+
sourcemap: true,
|
|
54
|
+
splitting: true,
|
|
55
|
+
logOverride: {
|
|
56
|
+
'ignored-bare-import': 'silent',
|
|
57
|
+
},
|
|
58
|
+
loader: {
|
|
59
|
+
'.wasm': 'copy',
|
|
60
|
+
},
|
|
61
|
+
external: ['node:*', 'cloudflare:workers', 'functions-service:user-script'],
|
|
62
|
+
plugins: [
|
|
63
|
+
httpPlugin,
|
|
64
|
+
{
|
|
65
|
+
name: 'entrypoint',
|
|
66
|
+
setup: (build) => {
|
|
67
|
+
build.onResolve({ filter: /^dxos:entrypoint$/ }, () => ({
|
|
68
|
+
path: 'dxos:entrypoint',
|
|
69
|
+
namespace: 'dxos:entrypoint',
|
|
70
|
+
}));
|
|
71
|
+
build.onLoad({ filter: /^dxos:entrypoint$/, namespace: 'dxos:entrypoint' }, () => ({
|
|
72
|
+
contents: trim`
|
|
73
|
+
export default {
|
|
74
|
+
fetch: async (...args) => {
|
|
75
|
+
const { wrapFunctionHandler } = await import('@dxos/compute-runtime');
|
|
76
|
+
const { wrapHandlerForCloudflare, setupFunctionsLogger } = await import('@dxos/functions-runtime-cloudflare');
|
|
77
|
+
const { default: handler } = await import('${options.entryPoint}');
|
|
78
|
+
|
|
79
|
+
setupFunctionsLogger();
|
|
80
|
+
|
|
81
|
+
//
|
|
82
|
+
// Wrapper to make the function cloudflare-compatible.
|
|
83
|
+
//
|
|
84
|
+
return wrapHandlerForCloudflare(wrapFunctionHandler(handler))(...args);
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
`,
|
|
88
|
+
resolveDir: new URL('.', import.meta.url).pathname,
|
|
89
|
+
}));
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: 'metafile',
|
|
94
|
+
setup: (build) => {
|
|
95
|
+
build.onEnd(async (result) => {
|
|
96
|
+
await writeFile(
|
|
97
|
+
join(build.initialOptions.outdir!, 'metafile.json'),
|
|
98
|
+
JSON.stringify(result.metafile, null, 2),
|
|
99
|
+
);
|
|
100
|
+
await writeFile(
|
|
101
|
+
join(build.initialOptions.outdir!, 'manifest.json'),
|
|
102
|
+
JSON.stringify(
|
|
103
|
+
{
|
|
104
|
+
files: Object.keys(result.metafile!.outputs).map((path) => ({
|
|
105
|
+
name: relative(build.initialOptions.outdir!, path),
|
|
106
|
+
})),
|
|
107
|
+
},
|
|
108
|
+
null,
|
|
109
|
+
2,
|
|
110
|
+
),
|
|
111
|
+
);
|
|
112
|
+
});
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
if (result.errors.length > 0) {
|
|
119
|
+
throw new BundleCreationError(result.errors);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const assetPaths = Object.keys(result.metafile!.outputs).map((path) => relative(outdir, path));
|
|
123
|
+
const assets = Function.pipe(
|
|
124
|
+
await Promise.all(assetPaths.map((path) => fs.readFile(join(outdir, path)))),
|
|
125
|
+
Array.zipWith(assetPaths, (content, path) => [path, content] as const),
|
|
126
|
+
Record.fromEntries,
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
if (options.verbose) {
|
|
130
|
+
console.log('Function compiled');
|
|
131
|
+
console.log('Metafile path:', `${outdir}/metafile.json`);
|
|
132
|
+
console.log('Assets:\n');
|
|
133
|
+
console.log(
|
|
134
|
+
Object.entries(result.metafile!.outputs)
|
|
135
|
+
.sort((a, b) => b[1].bytes - a[1].bytes)
|
|
136
|
+
.map(
|
|
137
|
+
([path, desc]) =>
|
|
138
|
+
`${formatBytes(desc.bytes).padEnd(10)} - ${relative(outdir, path)} ${basename(path) === 'index.js' ? ' (entry point)' : ''}`,
|
|
139
|
+
)
|
|
140
|
+
.join('\n'),
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
const filesInOutput = Function.pipe(
|
|
144
|
+
result.metafile.outputs,
|
|
145
|
+
Record.values,
|
|
146
|
+
Array.flatMap((_) => Record.toEntries(_.inputs)),
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
const moduleInOutput = Function.pipe(
|
|
150
|
+
result.metafile.inputs,
|
|
151
|
+
Record.values,
|
|
152
|
+
Array.flatMap((_) => _.imports),
|
|
153
|
+
Array.filter((_) => !!_.original?.match(/^@[a-zA-Z]+/)),
|
|
154
|
+
Array.filter((_) => !!filesInOutput.find(([name]) => name === _.path)),
|
|
155
|
+
Array.map((_) => _.original!),
|
|
156
|
+
Array.dedupe,
|
|
157
|
+
Array.sort(Order.string),
|
|
158
|
+
);
|
|
159
|
+
console.log(`Modules in output:\n${moduleInOutput.map((_) => ` - ${_}`).join('\n')}`);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
for (const path of Object.keys(assets)) {
|
|
163
|
+
if (path.endsWith('.map')) {
|
|
164
|
+
const content = assets[path];
|
|
165
|
+
const map = JSON.parse(new TextDecoder().decode(content));
|
|
166
|
+
const processedMap = postprocessSourceMap(map, outdir, gitDir);
|
|
167
|
+
assets[path] = Buffer.from(JSON.stringify(processedMap));
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Must match esbuild entry point.
|
|
172
|
+
return { entryPoint: 'index.js', assets };
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
class BundleCreationError extends BaseError.extend('BundleCreationError', 'Bundle creation failed') {
|
|
176
|
+
constructor(errors: Message[]) {
|
|
177
|
+
super({ context: { errors } });
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const formatBytes = (bytes: number) => {
|
|
182
|
+
if (bytes < Unit.Kilobyte(1).unit.quotient) {
|
|
183
|
+
return Unit.Byte(bytes).toString();
|
|
184
|
+
}
|
|
185
|
+
if (bytes < Unit.Megabyte(1).unit.quotient) {
|
|
186
|
+
return Unit.Kilobyte(bytes).toString();
|
|
187
|
+
}
|
|
188
|
+
if (bytes < Unit.Gigabyte(1).unit.quotient) {
|
|
189
|
+
return Unit.Megabyte(bytes).toString();
|
|
190
|
+
}
|
|
191
|
+
return Unit.Gigabyte(bytes).toString();
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const findGitDir = (startPath: string): string | null => {
|
|
195
|
+
let current = startPath;
|
|
196
|
+
while (current !== '/') {
|
|
197
|
+
const gitPath = join(current, '.git');
|
|
198
|
+
try {
|
|
199
|
+
if (statSync(gitPath).isDirectory()) {
|
|
200
|
+
return gitPath;
|
|
201
|
+
}
|
|
202
|
+
} catch {
|
|
203
|
+
// ignore
|
|
204
|
+
}
|
|
205
|
+
current = dirname(current);
|
|
206
|
+
}
|
|
207
|
+
return null;
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Adjust source paths to be relative to the repo root.
|
|
212
|
+
*/
|
|
213
|
+
const postprocessSourceMap = (map: any, outDir: string, gitDir: string | null) => {
|
|
214
|
+
if (!gitDir) {
|
|
215
|
+
return map;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Update source paths to be relative to the output directory
|
|
219
|
+
if (map.sources && Array.isArray(map.sources)) {
|
|
220
|
+
map.sources = map.sources.map((source: string) => {
|
|
221
|
+
const resolved = resolve(outDir, source);
|
|
222
|
+
if (resolved.startsWith(dirname(gitDir))) {
|
|
223
|
+
const rel = relative(dirname(gitDir), resolved);
|
|
224
|
+
return rel;
|
|
225
|
+
}
|
|
226
|
+
return source;
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return map;
|
|
231
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Trace } from '@dxos/compute';
|
|
6
|
+
import { log } from '@dxos/log';
|
|
7
|
+
|
|
8
|
+
export const noopTraceWriter: Trace.TraceWriter = Trace.noopWriter;
|
|
9
|
+
|
|
10
|
+
export const consoleTraceWriter: Trace.TraceWriter = {
|
|
11
|
+
write: (event, payload) => {
|
|
12
|
+
log.info(event.key, payload as object);
|
|
13
|
+
},
|
|
14
|
+
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import type * as Context from 'effect/Context';
|
|
6
|
+
|
|
7
|
+
import type { Space } from '@dxos/client/echo';
|
|
8
|
+
import { type Credential, type Trace } from '@dxos/compute';
|
|
9
|
+
import { ConfiguredCredentialsService } from '@dxos/compute-runtime';
|
|
10
|
+
import { ServiceContainer } from '@dxos/compute-runtime';
|
|
11
|
+
import { Database } from '@dxos/echo';
|
|
12
|
+
import { type EchoDatabase } from '@dxos/echo-client';
|
|
13
|
+
import { assertArgument } from '@dxos/invariant';
|
|
14
|
+
|
|
15
|
+
import { consoleTraceWriter, noopTraceWriter } from './logger';
|
|
16
|
+
|
|
17
|
+
// TODO(burdon): Factor out.
|
|
18
|
+
export type OneOf<T> = {
|
|
19
|
+
[K in keyof T]: { [P in K]: T[P] } & { [P in Exclude<keyof T, K>]?: never };
|
|
20
|
+
}[keyof T];
|
|
21
|
+
|
|
22
|
+
export type AiServiceProvider = 'dev' | 'edge' | 'ollama' | 'lmstudio';
|
|
23
|
+
|
|
24
|
+
export type TestServiceOptions = {
|
|
25
|
+
/**
|
|
26
|
+
* AI service configuration.
|
|
27
|
+
*/
|
|
28
|
+
ai?: any;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Credentials service configuration.
|
|
32
|
+
*/
|
|
33
|
+
credentials?: OneOf<{
|
|
34
|
+
/**
|
|
35
|
+
* Predefined credentials list.
|
|
36
|
+
*/
|
|
37
|
+
services?: Credential.ServiceCredential[];
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Custom credentials service.
|
|
41
|
+
*/
|
|
42
|
+
service?: Context.Tag.Service<Credential.CredentialsService>;
|
|
43
|
+
}>;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Database configuration.
|
|
47
|
+
*/
|
|
48
|
+
db?: EchoDatabase;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Gets database service from the space.
|
|
52
|
+
* Exclusive with: `db`
|
|
53
|
+
*/
|
|
54
|
+
space?: Space;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Trace writer configuration.
|
|
58
|
+
*/
|
|
59
|
+
logging?: {
|
|
60
|
+
enabled?: boolean;
|
|
61
|
+
trace?: Context.Tag.Service<Trace.TraceService>;
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @deprecated
|
|
67
|
+
*/
|
|
68
|
+
export const createTestServices = ({
|
|
69
|
+
ai,
|
|
70
|
+
credentials,
|
|
71
|
+
db,
|
|
72
|
+
logging,
|
|
73
|
+
space,
|
|
74
|
+
}: TestServiceOptions = {}): ServiceContainer => {
|
|
75
|
+
assertArgument(!(!!space && !!db), 'space', 'space can be provided only if db is not');
|
|
76
|
+
|
|
77
|
+
return new ServiceContainer().setServices({
|
|
78
|
+
// ai: createAiService(ai),
|
|
79
|
+
credentials: createCredentialsService(credentials),
|
|
80
|
+
database: space || db ? Database.makeService(space?.db || db!) : undefined,
|
|
81
|
+
trace: logging?.trace ?? (logging?.enabled ? consoleTraceWriter : noopTraceWriter),
|
|
82
|
+
});
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const createCredentialsService = (
|
|
86
|
+
credentials: TestServiceOptions['credentials'] | undefined,
|
|
87
|
+
): Context.Tag.Service<Credential.CredentialsService> | undefined => {
|
|
88
|
+
if (credentials?.services) {
|
|
89
|
+
return new ConfiguredCredentialsService(credentials.services);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (credentials?.service) {
|
|
93
|
+
return credentials.service;
|
|
94
|
+
}
|
|
95
|
+
};
|