@roj-ai/standalone-server 0.1.11 → 0.1.12
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/dist/files-upload-route.d.ts +36 -0
- package/dist/files-upload-route.d.ts.map +1 -0
- package/dist/files-upload-route.js +50 -0
- package/dist/files-upload-route.js.map +1 -0
- package/dist/instance.d.ts +6 -0
- package/dist/instance.d.ts.map +1 -1
- package/dist/instance.js +5 -0
- package/dist/instance.js.map +1 -1
- package/dist/local-registry.d.ts +109 -0
- package/dist/local-registry.d.ts.map +1 -0
- package/dist/local-registry.js +252 -0
- package/dist/local-registry.js.map +1 -0
- package/dist/main.js +7 -1
- package/dist/main.js.map +1 -1
- package/dist/platform-api.d.ts +8 -1
- package/dist/platform-api.d.ts.map +1 -1
- package/dist/platform-api.js +164 -13
- package/dist/platform-api.js.map +1 -1
- package/dist/server.d.ts +7 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +27 -1
- package/dist/server.js.map +1 -1
- package/dist/session-file-route.d.ts +24 -0
- package/dist/session-file-route.d.ts.map +1 -0
- package/dist/session-file-route.js +72 -0
- package/dist/session-file-route.js.map +1 -0
- package/dist/signed-token.d.ts +25 -0
- package/dist/signed-token.d.ts.map +1 -0
- package/dist/signed-token.js +50 -0
- package/dist/signed-token.js.map +1 -0
- package/dist/user-config-loader.d.ts.map +1 -1
- package/dist/user-config-loader.js +37 -1
- package/dist/user-config-loader.js.map +1 -1
- package/package.json +5 -5
- package/src/files-upload-route.ts +64 -0
- package/src/instance.ts +12 -1
- package/src/local-registry.ts +334 -0
- package/src/main.ts +7 -1
- package/src/platform-api.ts +234 -13
- package/src/server.ts +42 -2
- package/src/session-file-route.ts +99 -0
- package/src/signed-token.ts +68 -0
- package/src/user-config-loader.ts +41 -2
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* POST /api/v1/files/upload — multipart endpoint matching the `@roj-ai/client/platform.files.upload`
|
|
3
|
+
* two-phase contract:
|
|
4
|
+
*
|
|
5
|
+
* 1. Preflight with `contentHash`, `filename`, `mimeType` only. If we already have
|
|
6
|
+
* the bytes (sha-256 match), respond `{ ok, fileId, ..., deduped: true }`.
|
|
7
|
+
* Otherwise respond 409 `{ error: 'file-required' }`.
|
|
8
|
+
* 2. Retry with the same fields plus a `file` blob. We persist the bytes,
|
|
9
|
+
* record the file, and respond `{ ok, fileId, ..., deduped: false }`.
|
|
10
|
+
*
|
|
11
|
+
* No bearer auth (standalone is open) — but we accept and ignore an
|
|
12
|
+
* `Authorization: Bearer …` header so client code copy-pasted from the platform
|
|
13
|
+
* keeps working.
|
|
14
|
+
*/
|
|
15
|
+
import type { Context } from 'hono';
|
|
16
|
+
import type { LocalRegistry } from './local-registry.js';
|
|
17
|
+
interface Deps {
|
|
18
|
+
registry: LocalRegistry;
|
|
19
|
+
}
|
|
20
|
+
export declare function createFilesUploadRoute(deps: Deps): (c: Context) => Promise<(Response & import("hono").TypedResponse<{
|
|
21
|
+
ok: false;
|
|
22
|
+
error: string;
|
|
23
|
+
}, 400, "json">) | (Response & import("hono").TypedResponse<{
|
|
24
|
+
ok: false;
|
|
25
|
+
error: "file-required";
|
|
26
|
+
}, 409, "json">) | (Response & import("hono").TypedResponse<{
|
|
27
|
+
ok: true;
|
|
28
|
+
fileId: string;
|
|
29
|
+
filename: string;
|
|
30
|
+
mimeType: string;
|
|
31
|
+
size: number;
|
|
32
|
+
r2Key: string;
|
|
33
|
+
deduped: boolean;
|
|
34
|
+
}, import("hono/utils/http-status").ContentfulStatusCode, "json">)>;
|
|
35
|
+
export {};
|
|
36
|
+
//# sourceMappingURL=files-upload-route.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"files-upload-route.d.ts","sourceRoot":"","sources":["../src/files-upload-route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AACnC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAExD,UAAU,IAAI;IACb,QAAQ,EAAE,aAAa,CAAA;CACvB;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,IAAI,IAClC,GAAG,OAAO;;;;;;;;;;;;;;oEAwCxB"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* POST /api/v1/files/upload — multipart endpoint matching the `@roj-ai/client/platform.files.upload`
|
|
3
|
+
* two-phase contract:
|
|
4
|
+
*
|
|
5
|
+
* 1. Preflight with `contentHash`, `filename`, `mimeType` only. If we already have
|
|
6
|
+
* the bytes (sha-256 match), respond `{ ok, fileId, ..., deduped: true }`.
|
|
7
|
+
* Otherwise respond 409 `{ error: 'file-required' }`.
|
|
8
|
+
* 2. Retry with the same fields plus a `file` blob. We persist the bytes,
|
|
9
|
+
* record the file, and respond `{ ok, fileId, ..., deduped: false }`.
|
|
10
|
+
*
|
|
11
|
+
* No bearer auth (standalone is open) — but we accept and ignore an
|
|
12
|
+
* `Authorization: Bearer …` header so client code copy-pasted from the platform
|
|
13
|
+
* keeps working.
|
|
14
|
+
*/
|
|
15
|
+
export function createFilesUploadRoute(deps) {
|
|
16
|
+
return async (c) => {
|
|
17
|
+
let form;
|
|
18
|
+
try {
|
|
19
|
+
form = await c.req.formData();
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return c.json({ ok: false, error: 'invalid_multipart' }, 400);
|
|
23
|
+
}
|
|
24
|
+
const contentHash = form.get('contentHash');
|
|
25
|
+
const filename = form.get('filename');
|
|
26
|
+
const mimeType = form.get('mimeType');
|
|
27
|
+
if (typeof contentHash !== 'string' || typeof filename !== 'string' || typeof mimeType !== 'string') {
|
|
28
|
+
return c.json({ ok: false, error: 'invalid_form' }, 400);
|
|
29
|
+
}
|
|
30
|
+
const file = form.get('file');
|
|
31
|
+
const buffer = file && typeof file !== 'string' && 'arrayBuffer' in file
|
|
32
|
+
? Buffer.from(await file.arrayBuffer())
|
|
33
|
+
: undefined;
|
|
34
|
+
const result = await deps.registry.uploadFile({ buffer, filename, mimeType, contentHash });
|
|
35
|
+
if ('error' in result) {
|
|
36
|
+
// Preflight miss — client retries with body.
|
|
37
|
+
return c.json({ ok: false, error: result.error }, 409);
|
|
38
|
+
}
|
|
39
|
+
return c.json({
|
|
40
|
+
ok: true,
|
|
41
|
+
fileId: result.fileId,
|
|
42
|
+
filename: result.filename,
|
|
43
|
+
mimeType: result.mimeType,
|
|
44
|
+
size: result.size,
|
|
45
|
+
r2Key: '',
|
|
46
|
+
deduped: result.deduped,
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=files-upload-route.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"files-upload-route.js","sourceRoot":"","sources":["../src/files-upload-route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AASH,MAAM,UAAU,sBAAsB,CAAC,IAAU;IAChD,OAAO,KAAK,EAAE,CAAU,EAAE,EAAE;QAC3B,IAAI,IAAc,CAAA;QAClB,IAAI,CAAC;YACJ,IAAI,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAA;QAC9B,CAAC;QAAC,MAAM,CAAC;YACR,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,mBAAmB,EAAE,EAAE,GAAG,CAAC,CAAA;QAC9D,CAAC;QAED,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,CAAA;QAC3C,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,CAAA;QACrC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,CAAA;QACrC,IAAI,OAAO,WAAW,KAAK,QAAQ,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;YACrG,OAAO,CAAC,CAAC,IAAI,CACZ,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,cAAc,EAAE,EACpC,GAAG,CACH,CAAA;QACF,CAAC;QAED,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;QAC7B,MAAM,MAAM,GAAG,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,aAAa,IAAI,IAAI;YACvE,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC;YACvC,CAAC,CAAC,SAAS,CAAA;QAEZ,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC,CAAA;QAE1F,IAAI,OAAO,IAAI,MAAM,EAAE,CAAC;YACvB,6CAA6C;YAC7C,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,EAAE,GAAG,CAAC,CAAA;QACvD,CAAC;QAED,OAAO,CAAC,CAAC,IAAI,CAAC;YACb,EAAE,EAAE,IAAI;YACR,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,KAAK,EAAE,EAAE;YACT,OAAO,EAAE,MAAM,CAAC,OAAO;SACvB,CAAC,CAAA;IACH,CAAC,CAAA;AACF,CAAC"}
|
package/dist/instance.d.ts
CHANGED
|
@@ -4,16 +4,22 @@
|
|
|
4
4
|
* A standalone server hosts exactly one "instance" — a workspace containing
|
|
5
5
|
* sessions. The instance id is either supplied via config or auto-generated
|
|
6
6
|
* on startup and remains stable for the process lifetime.
|
|
7
|
+
*
|
|
8
|
+
* `metadata` is mutated by `instances.create` calls (last write wins). It is
|
|
9
|
+
* a single-tenant local-dev convenience; consumers that need per-instance
|
|
10
|
+
* isolation must run separate processes.
|
|
7
11
|
*/
|
|
8
12
|
export interface InstanceState {
|
|
9
13
|
id: string;
|
|
10
14
|
name: string;
|
|
11
15
|
createdAt: string;
|
|
12
16
|
presetIds: string[];
|
|
17
|
+
metadata: Record<string, unknown> | null;
|
|
13
18
|
}
|
|
14
19
|
export declare function createInstance(options: {
|
|
15
20
|
id?: string;
|
|
16
21
|
name?: string;
|
|
17
22
|
presetIds: string[];
|
|
23
|
+
metadata?: Record<string, unknown> | null;
|
|
18
24
|
}): InstanceState;
|
|
19
25
|
//# sourceMappingURL=instance.d.ts.map
|
package/dist/instance.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instance.d.ts","sourceRoot":"","sources":["../src/instance.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"instance.d.ts","sourceRoot":"","sources":["../src/instance.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,WAAW,aAAa;IAC7B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,EAAE,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;CACxC;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE;IACvC,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,EAAE,CAAA;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;CACzC,GAAG,aAAa,CAQhB"}
|
package/dist/instance.js
CHANGED
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
* A standalone server hosts exactly one "instance" — a workspace containing
|
|
5
5
|
* sessions. The instance id is either supplied via config or auto-generated
|
|
6
6
|
* on startup and remains stable for the process lifetime.
|
|
7
|
+
*
|
|
8
|
+
* `metadata` is mutated by `instances.create` calls (last write wins). It is
|
|
9
|
+
* a single-tenant local-dev convenience; consumers that need per-instance
|
|
10
|
+
* isolation must run separate processes.
|
|
7
11
|
*/
|
|
8
12
|
export function createInstance(options) {
|
|
9
13
|
return {
|
|
@@ -11,6 +15,7 @@ export function createInstance(options) {
|
|
|
11
15
|
name: options.name ?? 'standalone',
|
|
12
16
|
createdAt: new Date().toISOString(),
|
|
13
17
|
presetIds: options.presetIds,
|
|
18
|
+
metadata: options.metadata ?? null,
|
|
14
19
|
};
|
|
15
20
|
}
|
|
16
21
|
//# sourceMappingURL=instance.js.map
|
package/dist/instance.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instance.js","sourceRoot":"","sources":["../src/instance.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"instance.js","sourceRoot":"","sources":["../src/instance.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAUH,MAAM,UAAU,cAAc,CAAC,OAK9B;IACA,OAAO;QACN,EAAE,EAAE,OAAO,CAAC,EAAE,IAAI,SAAS;QAC3B,IAAI,EAAE,OAAO,CAAC,IAAI,IAAI,YAAY;QAClC,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACnC,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,IAAI;KAClC,CAAA;AACF,CAAC"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* On-disk file + resource registry for the standalone server.
|
|
3
|
+
*
|
|
4
|
+
* Stands in for the platform's Contember-backed file/resource service. State
|
|
5
|
+
* lives in `{dataPath}/.roj-platform-state.json` (JSON manifest) plus
|
|
6
|
+
* `{dataPath}/.roj-platform-state/files/{fileId}` (raw bytes). Operations are
|
|
7
|
+
* append-only at the resource level (revisions accumulate), so re-running
|
|
8
|
+
* `roj-standalone` between sessions preserves uploads and revisions added
|
|
9
|
+
* during a previous run.
|
|
10
|
+
*
|
|
11
|
+
* Files are content-addressable by SHA-256 — re-uploading the same bytes
|
|
12
|
+
* returns the existing fileId with `deduped: true`, mirroring the platform's
|
|
13
|
+
* /files/upload contract used by `@roj-ai/client/platform.files.upload`.
|
|
14
|
+
*/
|
|
15
|
+
import type { LocalResource, Logger } from '@roj-ai/sdk';
|
|
16
|
+
interface FileMeta {
|
|
17
|
+
id: string;
|
|
18
|
+
filename: string;
|
|
19
|
+
mimeType: string;
|
|
20
|
+
size: number;
|
|
21
|
+
contentHash: string;
|
|
22
|
+
createdAt: string;
|
|
23
|
+
}
|
|
24
|
+
export interface ResourceOutput {
|
|
25
|
+
id: string;
|
|
26
|
+
slug: string;
|
|
27
|
+
name: string | null;
|
|
28
|
+
description: string | null;
|
|
29
|
+
latestRevision: {
|
|
30
|
+
id: string;
|
|
31
|
+
label: string | null;
|
|
32
|
+
file: {
|
|
33
|
+
id: string;
|
|
34
|
+
filename: string;
|
|
35
|
+
mimeType: string;
|
|
36
|
+
size: number;
|
|
37
|
+
};
|
|
38
|
+
createdAt: string;
|
|
39
|
+
} | null;
|
|
40
|
+
createdAt: string;
|
|
41
|
+
}
|
|
42
|
+
export interface UploadResult {
|
|
43
|
+
fileId: string;
|
|
44
|
+
filename: string;
|
|
45
|
+
mimeType: string;
|
|
46
|
+
size: number;
|
|
47
|
+
deduped: boolean;
|
|
48
|
+
}
|
|
49
|
+
export declare class LocalRegistry {
|
|
50
|
+
private readonly dataPath;
|
|
51
|
+
private readonly logger;
|
|
52
|
+
private state;
|
|
53
|
+
private statePath;
|
|
54
|
+
private filesDir;
|
|
55
|
+
constructor(dataPath: string, logger: Logger);
|
|
56
|
+
init(): Promise<void>;
|
|
57
|
+
uploadFile(args: {
|
|
58
|
+
buffer?: Buffer;
|
|
59
|
+
filename: string;
|
|
60
|
+
mimeType: string;
|
|
61
|
+
contentHash?: string;
|
|
62
|
+
}): Promise<UploadResult | {
|
|
63
|
+
error: 'file-required';
|
|
64
|
+
contentHash: string;
|
|
65
|
+
}>;
|
|
66
|
+
readFileById(fileId: string): Promise<{
|
|
67
|
+
meta: FileMeta;
|
|
68
|
+
buffer: Buffer;
|
|
69
|
+
} | null>;
|
|
70
|
+
createResource(args: {
|
|
71
|
+
slug: string;
|
|
72
|
+
name?: string;
|
|
73
|
+
description?: string;
|
|
74
|
+
fileId: string;
|
|
75
|
+
label?: string;
|
|
76
|
+
}): Promise<{
|
|
77
|
+
resourceId: string;
|
|
78
|
+
revisionId: string;
|
|
79
|
+
}>;
|
|
80
|
+
addRevision(args: {
|
|
81
|
+
resourceId?: string;
|
|
82
|
+
resourceSlug?: string;
|
|
83
|
+
fileId: string;
|
|
84
|
+
label?: string;
|
|
85
|
+
}): Promise<{
|
|
86
|
+
revisionId: string;
|
|
87
|
+
noop: boolean;
|
|
88
|
+
}>;
|
|
89
|
+
getResource(args: {
|
|
90
|
+
resourceId?: string;
|
|
91
|
+
resourceSlug?: string;
|
|
92
|
+
}): ResourceOutput | null;
|
|
93
|
+
listResources(): ResourceOutput[];
|
|
94
|
+
deleteResource(resourceId: string): Promise<{
|
|
95
|
+
ok: boolean;
|
|
96
|
+
}>;
|
|
97
|
+
/**
|
|
98
|
+
* Idempotent: ensures every config-declared local resource exists in the
|
|
99
|
+
* registry. Skips slugs that already have a record (so previously uploaded
|
|
100
|
+
* revisions survive restarts).
|
|
101
|
+
*/
|
|
102
|
+
bootstrapLocalResources(localResources: LocalResource[]): Promise<void>;
|
|
103
|
+
private persist;
|
|
104
|
+
private findResource;
|
|
105
|
+
private findBySlug;
|
|
106
|
+
}
|
|
107
|
+
export declare function clearLocalRegistry(dataPath: string): Promise<void>;
|
|
108
|
+
export {};
|
|
109
|
+
//# sourceMappingURL=local-registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-registry.d.ts","sourceRoot":"","sources":["../src/local-registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* On-disk file + resource registry for the standalone server.
|
|
3
|
+
*
|
|
4
|
+
* Stands in for the platform's Contember-backed file/resource service. State
|
|
5
|
+
* lives in `{dataPath}/.roj-platform-state.json` (JSON manifest) plus
|
|
6
|
+
* `{dataPath}/.roj-platform-state/files/{fileId}` (raw bytes). Operations are
|
|
7
|
+
* append-only at the resource level (revisions accumulate), so re-running
|
|
8
|
+
* `roj-standalone` between sessions preserves uploads and revisions added
|
|
9
|
+
* during a previous run.
|
|
10
|
+
*
|
|
11
|
+
* Files are content-addressable by SHA-256 — re-uploading the same bytes
|
|
12
|
+
* returns the existing fileId with `deduped: true`, mirroring the platform's
|
|
13
|
+
* /files/upload contract used by `@roj-ai/client/platform.files.upload`.
|
|
14
|
+
*/
|
|
15
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
16
|
+
import { existsSync } from 'node:fs';
|
|
17
|
+
import { mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises';
|
|
18
|
+
import { basename, extname, join } from 'node:path';
|
|
19
|
+
export class LocalRegistry {
|
|
20
|
+
dataPath;
|
|
21
|
+
logger;
|
|
22
|
+
state = { files: {}, resources: {} };
|
|
23
|
+
statePath;
|
|
24
|
+
filesDir;
|
|
25
|
+
constructor(dataPath, logger) {
|
|
26
|
+
this.dataPath = dataPath;
|
|
27
|
+
this.logger = logger;
|
|
28
|
+
this.statePath = join(dataPath, '.roj-platform-state.json');
|
|
29
|
+
this.filesDir = join(dataPath, '.roj-platform-state', 'files');
|
|
30
|
+
}
|
|
31
|
+
async init() {
|
|
32
|
+
await mkdir(this.filesDir, { recursive: true });
|
|
33
|
+
if (existsSync(this.statePath)) {
|
|
34
|
+
try {
|
|
35
|
+
const raw = await readFile(this.statePath, 'utf8');
|
|
36
|
+
this.state = JSON.parse(raw);
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
this.logger.warn('Local registry manifest is corrupt, starting fresh', {
|
|
40
|
+
err: err instanceof Error ? err.message : String(err),
|
|
41
|
+
});
|
|
42
|
+
this.state = { files: {}, resources: {} };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async uploadFile(args) {
|
|
47
|
+
// Phase 1 — preflight by hash. If we already have the bytes, return the
|
|
48
|
+
// existing fileId without needing the body.
|
|
49
|
+
if (args.contentHash) {
|
|
50
|
+
const existing = Object.values(this.state.files).find(f => f.contentHash === args.contentHash);
|
|
51
|
+
if (existing) {
|
|
52
|
+
return {
|
|
53
|
+
fileId: existing.id,
|
|
54
|
+
filename: existing.filename,
|
|
55
|
+
mimeType: existing.mimeType,
|
|
56
|
+
size: existing.size,
|
|
57
|
+
deduped: true,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
if (!args.buffer) {
|
|
61
|
+
return { error: 'file-required', contentHash: args.contentHash };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (!args.buffer) {
|
|
65
|
+
throw new Error('uploadFile requires a buffer when contentHash is unknown');
|
|
66
|
+
}
|
|
67
|
+
const contentHash = args.contentHash ?? sha256Hex(args.buffer);
|
|
68
|
+
// Re-check after computing — the client may not have provided a hash but
|
|
69
|
+
// we still want dedupe.
|
|
70
|
+
const matched = Object.values(this.state.files).find(f => f.contentHash === contentHash);
|
|
71
|
+
if (matched) {
|
|
72
|
+
return {
|
|
73
|
+
fileId: matched.id,
|
|
74
|
+
filename: matched.filename,
|
|
75
|
+
mimeType: matched.mimeType,
|
|
76
|
+
size: matched.size,
|
|
77
|
+
deduped: true,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
const id = randomUUID();
|
|
81
|
+
const meta = {
|
|
82
|
+
id,
|
|
83
|
+
filename: args.filename,
|
|
84
|
+
mimeType: args.mimeType,
|
|
85
|
+
size: args.buffer.length,
|
|
86
|
+
contentHash,
|
|
87
|
+
createdAt: new Date().toISOString(),
|
|
88
|
+
};
|
|
89
|
+
await writeFile(join(this.filesDir, id), args.buffer);
|
|
90
|
+
this.state.files[id] = meta;
|
|
91
|
+
await this.persist();
|
|
92
|
+
return { fileId: id, filename: meta.filename, mimeType: meta.mimeType, size: meta.size, deduped: false };
|
|
93
|
+
}
|
|
94
|
+
async readFileById(fileId) {
|
|
95
|
+
const meta = this.state.files[fileId];
|
|
96
|
+
if (!meta)
|
|
97
|
+
return null;
|
|
98
|
+
try {
|
|
99
|
+
const buffer = await readFile(join(this.filesDir, fileId));
|
|
100
|
+
return { meta, buffer };
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
async createResource(args) {
|
|
107
|
+
if (!this.state.files[args.fileId]) {
|
|
108
|
+
throw new Error(`File not found: ${args.fileId}`);
|
|
109
|
+
}
|
|
110
|
+
const existing = this.findBySlug(args.slug);
|
|
111
|
+
if (existing) {
|
|
112
|
+
throw new Error(`Resource with slug already exists: ${args.slug}`);
|
|
113
|
+
}
|
|
114
|
+
const resourceId = randomUUID();
|
|
115
|
+
const revisionId = randomUUID();
|
|
116
|
+
const now = new Date().toISOString();
|
|
117
|
+
this.state.resources[resourceId] = {
|
|
118
|
+
id: resourceId,
|
|
119
|
+
slug: args.slug,
|
|
120
|
+
name: args.name ?? null,
|
|
121
|
+
description: args.description ?? null,
|
|
122
|
+
revisions: [{ id: revisionId, fileId: args.fileId, label: args.label ?? null, createdAt: now }],
|
|
123
|
+
createdAt: now,
|
|
124
|
+
};
|
|
125
|
+
await this.persist();
|
|
126
|
+
return { resourceId, revisionId };
|
|
127
|
+
}
|
|
128
|
+
async addRevision(args) {
|
|
129
|
+
const record = this.findResource(args);
|
|
130
|
+
if (!record) {
|
|
131
|
+
throw new Error('Resource not found');
|
|
132
|
+
}
|
|
133
|
+
if (!this.state.files[args.fileId]) {
|
|
134
|
+
throw new Error(`File not found: ${args.fileId}`);
|
|
135
|
+
}
|
|
136
|
+
const latest = record.revisions[record.revisions.length - 1];
|
|
137
|
+
const incomingLabel = args.label ?? null;
|
|
138
|
+
if (latest && latest.fileId === args.fileId && latest.label === incomingLabel) {
|
|
139
|
+
return { revisionId: latest.id, noop: true };
|
|
140
|
+
}
|
|
141
|
+
const revisionId = randomUUID();
|
|
142
|
+
record.revisions.push({
|
|
143
|
+
id: revisionId,
|
|
144
|
+
fileId: args.fileId,
|
|
145
|
+
label: incomingLabel,
|
|
146
|
+
createdAt: new Date().toISOString(),
|
|
147
|
+
});
|
|
148
|
+
await this.persist();
|
|
149
|
+
return { revisionId, noop: false };
|
|
150
|
+
}
|
|
151
|
+
getResource(args) {
|
|
152
|
+
const record = this.findResource(args);
|
|
153
|
+
return record ? toResourceOutput(record, this.state.files) : null;
|
|
154
|
+
}
|
|
155
|
+
listResources() {
|
|
156
|
+
return Object.values(this.state.resources).map(r => toResourceOutput(r, this.state.files));
|
|
157
|
+
}
|
|
158
|
+
async deleteResource(resourceId) {
|
|
159
|
+
if (!this.state.resources[resourceId])
|
|
160
|
+
return { ok: true };
|
|
161
|
+
delete this.state.resources[resourceId];
|
|
162
|
+
await this.persist();
|
|
163
|
+
return { ok: true };
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Idempotent: ensures every config-declared local resource exists in the
|
|
167
|
+
* registry. Skips slugs that already have a record (so previously uploaded
|
|
168
|
+
* revisions survive restarts).
|
|
169
|
+
*/
|
|
170
|
+
async bootstrapLocalResources(localResources) {
|
|
171
|
+
for (const lr of localResources) {
|
|
172
|
+
if (this.findBySlug(lr.slug)) {
|
|
173
|
+
this.logger.info('Local resource already in registry, skipping bootstrap', { slug: lr.slug });
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
const buffer = await readFile(lr.path);
|
|
177
|
+
const filename = basename(lr.path);
|
|
178
|
+
const mimeType = guessMimeType(filename);
|
|
179
|
+
const upload = await this.uploadFile({ buffer, filename, mimeType });
|
|
180
|
+
if ('error' in upload) {
|
|
181
|
+
throw new Error(`Bootstrap upload failed for ${lr.slug}: ${upload.error}`);
|
|
182
|
+
}
|
|
183
|
+
await this.createResource({
|
|
184
|
+
slug: lr.slug,
|
|
185
|
+
name: lr.name ?? lr.slug,
|
|
186
|
+
fileId: upload.fileId,
|
|
187
|
+
});
|
|
188
|
+
this.logger.info('Bootstrapped local resource', { slug: lr.slug, fileId: upload.fileId, size: upload.size });
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
async persist() {
|
|
192
|
+
const tmp = `${this.statePath}.tmp`;
|
|
193
|
+
await writeFile(tmp, JSON.stringify(this.state, null, 2), 'utf8');
|
|
194
|
+
await rename(tmp, this.statePath);
|
|
195
|
+
}
|
|
196
|
+
findResource(args) {
|
|
197
|
+
if (args.resourceId)
|
|
198
|
+
return this.state.resources[args.resourceId] ?? null;
|
|
199
|
+
if (args.resourceSlug)
|
|
200
|
+
return this.findBySlug(args.resourceSlug);
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
findBySlug(slug) {
|
|
204
|
+
for (const r of Object.values(this.state.resources)) {
|
|
205
|
+
if (r.slug === slug)
|
|
206
|
+
return r;
|
|
207
|
+
}
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
function toResourceOutput(record, files) {
|
|
212
|
+
const latest = record.revisions[record.revisions.length - 1];
|
|
213
|
+
const file = latest ? files[latest.fileId] : undefined;
|
|
214
|
+
return {
|
|
215
|
+
id: record.id,
|
|
216
|
+
slug: record.slug,
|
|
217
|
+
name: record.name,
|
|
218
|
+
description: record.description,
|
|
219
|
+
latestRevision: latest && file
|
|
220
|
+
? {
|
|
221
|
+
id: latest.id,
|
|
222
|
+
label: latest.label,
|
|
223
|
+
file: { id: file.id, filename: file.filename, mimeType: file.mimeType, size: file.size },
|
|
224
|
+
createdAt: latest.createdAt,
|
|
225
|
+
}
|
|
226
|
+
: null,
|
|
227
|
+
createdAt: record.createdAt,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
function sha256Hex(buf) {
|
|
231
|
+
return createHash('sha256').update(buf).digest('hex');
|
|
232
|
+
}
|
|
233
|
+
function guessMimeType(filename) {
|
|
234
|
+
switch (extname(filename).toLowerCase()) {
|
|
235
|
+
case '.zip':
|
|
236
|
+
return 'application/zip';
|
|
237
|
+
case '.json':
|
|
238
|
+
return 'application/json';
|
|
239
|
+
default:
|
|
240
|
+
return 'application/octet-stream';
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
// Exported for tests / smoke scripts that want to nuke local state cleanly.
|
|
244
|
+
export async function clearLocalRegistry(dataPath) {
|
|
245
|
+
const statePath = join(dataPath, '.roj-platform-state.json');
|
|
246
|
+
const filesDir = join(dataPath, '.roj-platform-state');
|
|
247
|
+
if (existsSync(statePath))
|
|
248
|
+
await rm(statePath);
|
|
249
|
+
if (existsSync(filesDir))
|
|
250
|
+
await rm(filesDir, { recursive: true });
|
|
251
|
+
}
|
|
252
|
+
//# sourceMappingURL=local-registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-registry.js","sourceRoot":"","sources":["../src/local-registry.ts"],"names":[],"mappings":"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"}
|
package/dist/main.js
CHANGED
|
@@ -17,13 +17,19 @@ async function main() {
|
|
|
17
17
|
userConfig = await loadUserConfig(absoluteConfigPath);
|
|
18
18
|
console.log(`Loaded config from: ${absoluteConfigPath}`);
|
|
19
19
|
console.log(` Presets: ${userConfig.presets.map(p => p.id).join(', ')}`);
|
|
20
|
+
if (userConfig.localResources?.length) {
|
|
21
|
+
console.log(` Local resources: ${userConfig.localResources.map(r => r.slug).join(', ')}`);
|
|
22
|
+
}
|
|
20
23
|
}
|
|
21
24
|
catch (error) {
|
|
22
25
|
console.error(`Failed to load config from ${absoluteConfigPath}:`);
|
|
23
26
|
console.error(error instanceof Error ? error.message : String(error));
|
|
24
27
|
process.exit(1);
|
|
25
28
|
}
|
|
26
|
-
await startStandaloneServer({
|
|
29
|
+
await startStandaloneServer({
|
|
30
|
+
presets: userConfig.presets,
|
|
31
|
+
localResources: userConfig.localResources,
|
|
32
|
+
});
|
|
27
33
|
}
|
|
28
34
|
main().catch((error) => {
|
|
29
35
|
console.error('Fatal error:', error);
|
package/dist/main.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":";AACA;;;;;;GAMG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAA;AAExD,KAAK,UAAU,IAAI;IAClB,MAAM,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,eAAe,CAAA;IACrD,MAAM,kBAAkB,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,UAAU,CAAC,CAAA;IAE7D,IAAI,UAAsD,CAAA;IAC1D,IAAI,CAAC;QACJ,UAAU,GAAG,MAAM,cAAc,CAAC,kBAAkB,CAAC,CAAA;QACrD,OAAO,CAAC,GAAG,CAAC,uBAAuB,kBAAkB,EAAE,CAAC,CAAA;QACxD,OAAO,CAAC,GAAG,CAAC,cAAc,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":";AACA;;;;;;GAMG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAA;AAExD,KAAK,UAAU,IAAI;IAClB,MAAM,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,eAAe,CAAA;IACrD,MAAM,kBAAkB,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,UAAU,CAAC,CAAA;IAE7D,IAAI,UAAsD,CAAA;IAC1D,IAAI,CAAC;QACJ,UAAU,GAAG,MAAM,cAAc,CAAC,kBAAkB,CAAC,CAAA;QACrD,OAAO,CAAC,GAAG,CAAC,uBAAuB,kBAAkB,EAAE,CAAC,CAAA;QACxD,OAAO,CAAC,GAAG,CAAC,cAAc,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QACzE,IAAI,UAAU,CAAC,cAAc,EAAE,MAAM,EAAE,CAAC;YACvC,OAAO,CAAC,GAAG,CAAC,sBAAsB,UAAU,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QAC3F,CAAC;IACF,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,OAAO,CAAC,KAAK,CAAC,8BAA8B,kBAAkB,GAAG,CAAC,CAAA;QAClE,OAAO,CAAC,KAAK,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAA;QACrE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IAChB,CAAC;IAED,MAAM,qBAAqB,CAAC;QAC3B,OAAO,EAAE,UAAU,CAAC,OAAO;QAC3B,cAAc,EAAE,UAAU,CAAC,cAAc;KACzC,CAAC,CAAA;AACH,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACtB,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,KAAK,CAAC,CAAA;IACpC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;AAChB,CAAC,CAAC,CAAA"}
|
package/dist/platform-api.d.ts
CHANGED
|
@@ -13,13 +13,20 @@
|
|
|
13
13
|
* - sessions.publish — publishing requires the CF platform
|
|
14
14
|
* - instances.archive — no-op; shutdown the server instead
|
|
15
15
|
*/
|
|
16
|
-
import type { Logger, SessionManager } from '@roj-ai/sdk';
|
|
16
|
+
import type { Logger, Preset, SessionManager } from '@roj-ai/sdk';
|
|
17
17
|
import { Hono } from 'hono';
|
|
18
18
|
import type { InstanceState } from './instance.js';
|
|
19
|
+
import type { LocalRegistry } from './local-registry.js';
|
|
19
20
|
interface Deps {
|
|
20
21
|
instance: InstanceState;
|
|
21
22
|
sessionManager: SessionManager;
|
|
22
23
|
logger: Logger;
|
|
24
|
+
presets: Preset[];
|
|
25
|
+
registry: LocalRegistry;
|
|
26
|
+
/** HMAC secret for signing download tokens (`sessionFiles.createDownloadUrl`). */
|
|
27
|
+
tokenSecret: string;
|
|
28
|
+
/** Externally-reachable base URL (e.g. `http://localhost:8765`) used when minting download URLs. */
|
|
29
|
+
publicBaseUrl: string;
|
|
23
30
|
}
|
|
24
31
|
export declare function createPlatformApi(deps: Deps): Hono;
|
|
25
32
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"platform-api.d.ts","sourceRoot":"","sources":["../src/platform-api.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"platform-api.d.ts","sourceRoot":"","sources":["../src/platform-api.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAEjE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAC3B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAClD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAGxD,UAAU,IAAI;IACb,QAAQ,EAAE,aAAa,CAAA;IACvB,cAAc,EAAE,cAAc,CAAA;IAC9B,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,QAAQ,EAAE,aAAa,CAAA;IACvB,kFAAkF;IAClF,WAAW,EAAE,MAAM,CAAA;IACnB,oGAAoG;IACpG,aAAa,EAAE,MAAM,CAAA;CACrB;AAQD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,CAuBlD"}
|