@pellux/goodvibes-sdk 0.25.1 → 0.25.2
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/_internal/daemon/otlp-protobuf.d.ts +3 -0
- package/dist/_internal/daemon/otlp-protobuf.d.ts.map +1 -0
- package/dist/_internal/daemon/otlp-protobuf.js +968 -0
- package/dist/_internal/daemon/telemetry-routes.d.ts.map +1 -1
- package/dist/_internal/daemon/telemetry-routes.js +22 -13
- package/dist/_internal/platform/version.js +1 -1
- package/package.json +1 -1
- package/dist/_internal/platform/runtime/contracts/index.d.ts +0 -40
- package/dist/_internal/platform/runtime/contracts/index.d.ts.map +0 -1
- package/dist/_internal/platform/runtime/contracts/index.js +0 -133
- package/dist/_internal/platform/runtime/contracts/migrations/index.d.ts +0 -75
- package/dist/_internal/platform/runtime/contracts/migrations/index.d.ts.map +0 -1
- package/dist/_internal/platform/runtime/contracts/migrations/index.js +0 -158
- package/dist/_internal/platform/runtime/contracts/migrations/schemas.d.ts +0 -57
- package/dist/_internal/platform/runtime/contracts/migrations/schemas.d.ts.map +0 -1
- package/dist/_internal/platform/runtime/contracts/migrations/schemas.js +0 -157
- package/dist/_internal/platform/runtime/contracts/types.d.ts +0 -123
- package/dist/_internal/platform/runtime/contracts/types.d.ts.map +0 -1
- package/dist/_internal/platform/runtime/contracts/types.js +0 -41
- package/dist/_internal/platform/runtime/contracts/validators/event-envelope.d.ts +0 -24
- package/dist/_internal/platform/runtime/contracts/validators/event-envelope.d.ts.map +0 -1
- package/dist/_internal/platform/runtime/contracts/validators/event-envelope.js +0 -104
- package/dist/_internal/platform/runtime/contracts/validators/index.d.ts +0 -11
- package/dist/_internal/platform/runtime/contracts/validators/index.d.ts.map +0 -1
- package/dist/_internal/platform/runtime/contracts/validators/index.js +0 -10
- package/dist/_internal/platform/runtime/contracts/validators/runtime-state.d.ts +0 -23
- package/dist/_internal/platform/runtime/contracts/validators/runtime-state.d.ts.map +0 -1
- package/dist/_internal/platform/runtime/contracts/validators/runtime-state.js +0 -101
- package/dist/_internal/platform/runtime/contracts/validators/session.d.ts +0 -24
- package/dist/_internal/platform/runtime/contracts/validators/session.d.ts.map +0 -1
- package/dist/_internal/platform/runtime/contracts/validators/session.js +0 -103
- package/dist/_internal/platform/runtime/contracts/version.d.ts +0 -84
- package/dist/_internal/platform/runtime/contracts/version.d.ts.map +0 -1
- package/dist/_internal/platform/runtime/contracts/version.js +0 -41
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"telemetry-routes.d.ts","sourceRoot":"","sources":["../../../src/_internal/daemon/telemetry-routes.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,EAAyB,KAAK,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"telemetry-routes.d.ts","sourceRoot":"","sources":["../../../src/_internal/daemon/telemetry-routes.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { buildMissingScopeBody } from './http-policy.js';
|
|
2
|
+
import { decodeOtlpProtobuf } from './otlp-protobuf.js';
|
|
2
3
|
import { DaemonErrorCategory } from '../errors/index.js';
|
|
3
4
|
function parseNumber(value) {
|
|
4
5
|
if (value === null || value.trim().length === 0)
|
|
@@ -45,10 +46,9 @@ function buildFilter(url) {
|
|
|
45
46
|
// ---------------------------------------------------------------------------
|
|
46
47
|
/** Max ingest payload (4 MiB) — reject larger bodies with 413 */
|
|
47
48
|
const OTLP_INGEST_MAX_BODY_BYTES = 4 * 1024 * 1024;
|
|
48
|
-
/** Accepted content-types for OTLP/HTTP
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
];
|
|
49
|
+
/** Accepted content-types for OTLP/HTTP ingest. */
|
|
50
|
+
const OTLP_JSON_CONTENT_TYPE = 'application/json';
|
|
51
|
+
const OTLP_PROTOBUF_CONTENT_TYPES = new Set(['application/x-protobuf', 'application/protobuf']);
|
|
52
52
|
const OTLP_PARTIAL_SUCCESS_KEYS = {
|
|
53
53
|
logs: 'partialSuccess',
|
|
54
54
|
traces: 'partialSuccess',
|
|
@@ -58,18 +58,19 @@ const OTLP_PARTIAL_SUCCESS_KEYS = {
|
|
|
58
58
|
* Validate and parse an OTLP HTTP ingest request body.
|
|
59
59
|
* Returns a parsed JSON Record on success, or a Response (error) on failure.
|
|
60
60
|
*
|
|
61
|
-
* Protocol: OTLP/HTTP spec §4.2 —
|
|
62
|
-
*
|
|
63
|
-
* for a future release.
|
|
61
|
+
* Protocol: OTLP/HTTP spec §4.2 — supports JSON and binary protobuf service
|
|
62
|
+
* requests for logs, traces, and metrics.
|
|
64
63
|
*/
|
|
65
|
-
async function parseOtlpBody(req) {
|
|
64
|
+
async function parseOtlpBody(req, kind) {
|
|
66
65
|
const contentType = (req.headers.get('content-type') ?? '').toLowerCase().split(';')[0].trim();
|
|
67
|
-
|
|
66
|
+
const acceptsJson = contentType === OTLP_JSON_CONTENT_TYPE;
|
|
67
|
+
const acceptsProtobuf = OTLP_PROTOBUF_CONTENT_TYPES.has(contentType);
|
|
68
|
+
if (!acceptsJson && !acceptsProtobuf) {
|
|
68
69
|
return Response.json({
|
|
69
70
|
error: `Unsupported Content-Type '${contentType}' for OTLP ingest`,
|
|
70
71
|
code: 'UNSUPPORTED_MEDIA_TYPE',
|
|
71
72
|
category: DaemonErrorCategory.BAD_REQUEST,
|
|
72
|
-
hint: `Use 'application/
|
|
73
|
+
hint: `Use '${OTLP_JSON_CONTENT_TYPE}' or 'application/x-protobuf'.`,
|
|
73
74
|
}, { status: 415 });
|
|
74
75
|
}
|
|
75
76
|
const raw = await req.arrayBuffer();
|
|
@@ -80,6 +81,14 @@ async function parseOtlpBody(req) {
|
|
|
80
81
|
category: DaemonErrorCategory.BAD_REQUEST,
|
|
81
82
|
}, { status: 413 });
|
|
82
83
|
}
|
|
84
|
+
if (acceptsProtobuf) {
|
|
85
|
+
try {
|
|
86
|
+
return decodeOtlpProtobuf(kind, new Uint8Array(raw));
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return Response.json({ error: 'OTLP ingest body is not valid protobuf', code: 'INVALID_PAYLOAD', category: DaemonErrorCategory.BAD_REQUEST }, { status: 400 });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
83
92
|
try {
|
|
84
93
|
const text = new TextDecoder().decode(raw);
|
|
85
94
|
const parsed = JSON.parse(text);
|
|
@@ -289,7 +298,7 @@ export function createDaemonTelemetryRouteHandlers(context) {
|
|
|
289
298
|
if (!auth) {
|
|
290
299
|
return Response.json({ error: 'Authentication required for OTLP ingest', code: 'AUTH_REQUIRED', category: DaemonErrorCategory.AUTHENTICATION, status: 401 }, { status: 401 });
|
|
291
300
|
}
|
|
292
|
-
const bodyOrErr = await parseOtlpBody(req);
|
|
301
|
+
const bodyOrErr = await parseOtlpBody(req, 'logs');
|
|
293
302
|
if (bodyOrErr instanceof Response)
|
|
294
303
|
return bodyOrErr;
|
|
295
304
|
context.ingestSink?.ingestLogs(bodyOrErr);
|
|
@@ -300,7 +309,7 @@ export function createDaemonTelemetryRouteHandlers(context) {
|
|
|
300
309
|
if (!auth) {
|
|
301
310
|
return Response.json({ error: 'Authentication required for OTLP ingest', code: 'AUTH_REQUIRED', category: DaemonErrorCategory.AUTHENTICATION, status: 401 }, { status: 401 });
|
|
302
311
|
}
|
|
303
|
-
const bodyOrErr = await parseOtlpBody(req);
|
|
312
|
+
const bodyOrErr = await parseOtlpBody(req, 'traces');
|
|
304
313
|
if (bodyOrErr instanceof Response)
|
|
305
314
|
return bodyOrErr;
|
|
306
315
|
context.ingestSink?.ingestTraces(bodyOrErr);
|
|
@@ -311,7 +320,7 @@ export function createDaemonTelemetryRouteHandlers(context) {
|
|
|
311
320
|
if (!auth) {
|
|
312
321
|
return Response.json({ error: 'Authentication required for OTLP ingest', code: 'AUTH_REQUIRED', category: DaemonErrorCategory.AUTHENTICATION, status: 401 }, { status: 401 });
|
|
313
322
|
}
|
|
314
|
-
const bodyOrErr = await parseOtlpBody(req);
|
|
323
|
+
const bodyOrErr = await parseOtlpBody(req, 'metrics');
|
|
315
324
|
if (bodyOrErr instanceof Response)
|
|
316
325
|
return bodyOrErr;
|
|
317
326
|
context.ingestSink?.ingestMetrics(bodyOrErr);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
-
let version = '0.25.
|
|
3
|
+
let version = '0.25.2';
|
|
4
4
|
try {
|
|
5
5
|
const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', '..', 'package.json'), 'utf-8'));
|
|
6
6
|
version = pkg.version ?? version;
|
package/package.json
CHANGED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Compatibility Contracts — Module barrel and factory
|
|
3
|
-
*
|
|
4
|
-
* Entry point for the schema versioning, validation, and migration
|
|
5
|
-
* infrastructure. Use `createContractRegistry()` to obtain a fully
|
|
6
|
-
* initialized registry with all domain contracts registered.
|
|
7
|
-
*
|
|
8
|
-
* Usage:
|
|
9
|
-
* ```ts
|
|
10
|
-
* import { createContractRegistry } from './contracts/index.js';
|
|
11
|
-
* const { registry, contracts } = createContractRegistry();
|
|
12
|
-
* const result = contracts.get('runtimeState')?.validate(rawData);
|
|
13
|
-
* ```
|
|
14
|
-
*
|
|
15
|
-
* @module contracts
|
|
16
|
-
*/
|
|
17
|
-
export { MigrationRegistry } from './migrations/index.js';
|
|
18
|
-
export * from './types.js';
|
|
19
|
-
export * from './version.js';
|
|
20
|
-
export * from './validators/index.js';
|
|
21
|
-
import { MigrationRegistry } from './migrations/index.js';
|
|
22
|
-
import type { SchemaContract } from './types.js';
|
|
23
|
-
import type { ContractName } from './version.js';
|
|
24
|
-
/**
|
|
25
|
-
* Creates a fully initialized contract registry with all domain migration
|
|
26
|
-
* steps registered and SchemaContract instances built for each domain.
|
|
27
|
-
*
|
|
28
|
-
* Each SchemaContract provides:
|
|
29
|
-
* - `validate(data)` — runtime shape validation returning a ValidationResult
|
|
30
|
-
* - `migrate(data, fromVersion)` — migration chain execution via MigrationRegistry
|
|
31
|
-
*
|
|
32
|
-
* @returns An object containing:
|
|
33
|
-
* - `registry` — the MigrationRegistry with all steps registered
|
|
34
|
-
* - `contracts` — a Map of contract name → SchemaContract
|
|
35
|
-
*/
|
|
36
|
-
export declare function createContractRegistry(): {
|
|
37
|
-
registry: MigrationRegistry;
|
|
38
|
-
contracts: Map<ContractName, SchemaContract>;
|
|
39
|
-
};
|
|
40
|
-
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/runtime/contracts/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,uBAAuB,CAAC;AAEtC,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAYjD;;;;;;;;;;;GAWG;AACH,wBAAgB,sBAAsB,IAAI;IACxC,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,SAAS,EAAE,GAAG,CAAC,YAAY,EAAE,cAAc,CAAC,CAAC;CAC9C,CAsGA"}
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Compatibility Contracts — Module barrel and factory
|
|
3
|
-
*
|
|
4
|
-
* Entry point for the schema versioning, validation, and migration
|
|
5
|
-
* infrastructure. Use `createContractRegistry()` to obtain a fully
|
|
6
|
-
* initialized registry with all domain contracts registered.
|
|
7
|
-
*
|
|
8
|
-
* Usage:
|
|
9
|
-
* ```ts
|
|
10
|
-
* import { createContractRegistry } from './contracts/index.js';
|
|
11
|
-
* const { registry, contracts } = createContractRegistry();
|
|
12
|
-
* const result = contracts.get('runtimeState')?.validate(rawData);
|
|
13
|
-
* ```
|
|
14
|
-
*
|
|
15
|
-
* @module contracts
|
|
16
|
-
*/
|
|
17
|
-
export { MigrationRegistry } from './migrations/index.js';
|
|
18
|
-
export * from './types.js';
|
|
19
|
-
export * from './version.js';
|
|
20
|
-
export * from './validators/index.js';
|
|
21
|
-
import { MigrationRegistry } from './migrations/index.js';
|
|
22
|
-
import { SCHEMA_VERSIONS, MIN_SUPPORTED_VERSIONS } from './version.js';
|
|
23
|
-
import { getRuntimeStateMigrationSteps, getEventEnvelopeMigrationSteps, getSessionMigrationSteps, getPluginManifestMigrationSteps, getTaskRecordMigrationSteps, } from './migrations/schemas.js';
|
|
24
|
-
import { validateRuntimeState } from './validators/runtime-state.js';
|
|
25
|
-
import { validateEventEnvelope } from './validators/event-envelope.js';
|
|
26
|
-
import { validateSession } from './validators/session.js';
|
|
27
|
-
/**
|
|
28
|
-
* Creates a fully initialized contract registry with all domain migration
|
|
29
|
-
* steps registered and SchemaContract instances built for each domain.
|
|
30
|
-
*
|
|
31
|
-
* Each SchemaContract provides:
|
|
32
|
-
* - `validate(data)` — runtime shape validation returning a ValidationResult
|
|
33
|
-
* - `migrate(data, fromVersion)` — migration chain execution via MigrationRegistry
|
|
34
|
-
*
|
|
35
|
-
* @returns An object containing:
|
|
36
|
-
* - `registry` — the MigrationRegistry with all steps registered
|
|
37
|
-
* - `contracts` — a Map of contract name → SchemaContract
|
|
38
|
-
*/
|
|
39
|
-
export function createContractRegistry() {
|
|
40
|
-
const registry = new MigrationRegistry();
|
|
41
|
-
// Register all domain migration steps
|
|
42
|
-
for (const step of getRuntimeStateMigrationSteps()) {
|
|
43
|
-
registry.register('runtimeState', step);
|
|
44
|
-
}
|
|
45
|
-
for (const step of getEventEnvelopeMigrationSteps()) {
|
|
46
|
-
registry.register('eventEnvelope', step);
|
|
47
|
-
}
|
|
48
|
-
for (const step of getSessionMigrationSteps()) {
|
|
49
|
-
registry.register('session', step);
|
|
50
|
-
}
|
|
51
|
-
for (const step of getPluginManifestMigrationSteps()) {
|
|
52
|
-
registry.register('pluginManifest', step);
|
|
53
|
-
}
|
|
54
|
-
for (const step of getTaskRecordMigrationSteps()) {
|
|
55
|
-
registry.register('taskRecord', step);
|
|
56
|
-
}
|
|
57
|
-
const contracts = new Map();
|
|
58
|
-
// runtimeState contract
|
|
59
|
-
contracts.set('runtimeState', {
|
|
60
|
-
name: 'runtimeState',
|
|
61
|
-
currentVersion: SCHEMA_VERSIONS.runtimeState,
|
|
62
|
-
minSupportedVersion: MIN_SUPPORTED_VERSIONS.runtimeState,
|
|
63
|
-
validate: validateRuntimeState,
|
|
64
|
-
migrate: (data, fromVersion) => registry.migrate('runtimeState', data, fromVersion),
|
|
65
|
-
});
|
|
66
|
-
// eventEnvelope contract
|
|
67
|
-
contracts.set('eventEnvelope', {
|
|
68
|
-
name: 'eventEnvelope',
|
|
69
|
-
currentVersion: SCHEMA_VERSIONS.eventEnvelope,
|
|
70
|
-
minSupportedVersion: MIN_SUPPORTED_VERSIONS.eventEnvelope,
|
|
71
|
-
validate: validateEventEnvelope,
|
|
72
|
-
migrate: (data, fromVersion) => registry.migrate('eventEnvelope', data, fromVersion),
|
|
73
|
-
});
|
|
74
|
-
// session contract
|
|
75
|
-
contracts.set('session', {
|
|
76
|
-
name: 'session',
|
|
77
|
-
currentVersion: SCHEMA_VERSIONS.session,
|
|
78
|
-
minSupportedVersion: MIN_SUPPORTED_VERSIONS.session,
|
|
79
|
-
validate: validateSession,
|
|
80
|
-
migrate: (data, fromVersion) => registry.migrate('session', data, fromVersion),
|
|
81
|
-
});
|
|
82
|
-
// pluginManifest contract
|
|
83
|
-
contracts.set('pluginManifest', {
|
|
84
|
-
name: 'pluginManifest',
|
|
85
|
-
currentVersion: SCHEMA_VERSIONS.pluginManifest,
|
|
86
|
-
minSupportedVersion: MIN_SUPPORTED_VERSIONS.pluginManifest,
|
|
87
|
-
validate: (data) => {
|
|
88
|
-
const errors = [];
|
|
89
|
-
if (data === null || typeof data !== 'object' || Array.isArray(data)) {
|
|
90
|
-
errors.push({ path: '', message: 'pluginManifest must be a non-null object', expected: 'object', actual: data === null ? 'null' : Array.isArray(data) ? 'array' : typeof data });
|
|
91
|
-
return { valid: false, errors };
|
|
92
|
-
}
|
|
93
|
-
const record = data;
|
|
94
|
-
if (typeof record['name'] !== 'string' || record['name'].length === 0) {
|
|
95
|
-
errors.push({ path: 'name', message: "Field 'name' must be a non-empty string", expected: 'non-empty string', actual: typeof record['name'] });
|
|
96
|
-
}
|
|
97
|
-
if (typeof record['version'] !== 'string' || record['version'].length === 0) {
|
|
98
|
-
errors.push({ path: 'version', message: "Field 'version' must be a non-empty string", expected: 'non-empty string', actual: typeof record['version'] });
|
|
99
|
-
}
|
|
100
|
-
if (!Array.isArray(record['capabilities'])) {
|
|
101
|
-
errors.push({ path: 'capabilities', message: "Field 'capabilities' must be an array", expected: 'array', actual: typeof record['capabilities'] });
|
|
102
|
-
}
|
|
103
|
-
return { valid: errors.length === 0, errors, version: SCHEMA_VERSIONS.pluginManifest };
|
|
104
|
-
},
|
|
105
|
-
migrate: (data, fromVersion) => registry.migrate('pluginManifest', data, fromVersion),
|
|
106
|
-
});
|
|
107
|
-
// taskRecord contract
|
|
108
|
-
contracts.set('taskRecord', {
|
|
109
|
-
name: 'taskRecord',
|
|
110
|
-
currentVersion: SCHEMA_VERSIONS.taskRecord,
|
|
111
|
-
minSupportedVersion: MIN_SUPPORTED_VERSIONS.taskRecord,
|
|
112
|
-
validate: (data) => {
|
|
113
|
-
const errors = [];
|
|
114
|
-
if (data === null || typeof data !== 'object' || Array.isArray(data)) {
|
|
115
|
-
errors.push({ path: '', message: 'taskRecord must be a non-null object', expected: 'object', actual: data === null ? 'null' : Array.isArray(data) ? 'array' : typeof data });
|
|
116
|
-
return { valid: false, errors };
|
|
117
|
-
}
|
|
118
|
-
const record = data;
|
|
119
|
-
if (typeof record['id'] !== 'string' || record['id'].length === 0) {
|
|
120
|
-
errors.push({ path: 'id', message: "Field 'id' must be a non-empty string", expected: 'non-empty string', actual: typeof record['id'] });
|
|
121
|
-
}
|
|
122
|
-
if (typeof record['type'] !== 'string' || record['type'].length === 0) {
|
|
123
|
-
errors.push({ path: 'type', message: "Field 'type' must be a non-empty string", expected: 'non-empty string', actual: typeof record['type'] });
|
|
124
|
-
}
|
|
125
|
-
if (typeof record['status'] !== 'string' || record['status'].length === 0) {
|
|
126
|
-
errors.push({ path: 'status', message: "Field 'status' must be a non-empty string", expected: 'non-empty string', actual: typeof record['status'] });
|
|
127
|
-
}
|
|
128
|
-
return { valid: errors.length === 0, errors, version: SCHEMA_VERSIONS.taskRecord };
|
|
129
|
-
},
|
|
130
|
-
migrate: (data, fromVersion) => registry.migrate('taskRecord', data, fromVersion),
|
|
131
|
-
});
|
|
132
|
-
return { registry, contracts };
|
|
133
|
-
}
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Compatibility Contracts — Migration Registry
|
|
3
|
-
*
|
|
4
|
-
* The MigrationRegistry holds all registered schema migration steps and
|
|
5
|
-
* provides path resolution and ordered execution for any version pair.
|
|
6
|
-
*
|
|
7
|
-
* @module contracts/migrations
|
|
8
|
-
*/
|
|
9
|
-
import { type MigrationStep, type MigrationResult, type SchemaVersion } from '../types.js';
|
|
10
|
-
/**
|
|
11
|
-
* Central registry for all schema migration steps.
|
|
12
|
-
*
|
|
13
|
-
* Migrations are registered per contract name and resolved as an ordered
|
|
14
|
-
* chain from a given source version to the current target version.
|
|
15
|
-
* All migration functions must be pure (no mutations, no side effects).
|
|
16
|
-
*/
|
|
17
|
-
export declare class MigrationRegistry {
|
|
18
|
-
private readonly steps;
|
|
19
|
-
/**
|
|
20
|
-
* Registers a migration step for a given contract.
|
|
21
|
-
*
|
|
22
|
-
* Steps are stored in insertion order and sorted at resolution time.
|
|
23
|
-
* Duplicate from→to pairs for the same contract are not permitted.
|
|
24
|
-
*
|
|
25
|
-
* @param contract - The contract name (e.g. 'runtimeState').
|
|
26
|
-
* @param step - The migration step to register.
|
|
27
|
-
* @throws If a step with the same from→to is already registered.
|
|
28
|
-
*/
|
|
29
|
-
register(contract: string, step: MigrationStep): void;
|
|
30
|
-
/**
|
|
31
|
-
* Returns true if any migration path exists from `fromVersion` toward
|
|
32
|
-
* a higher version for the given contract.
|
|
33
|
-
*
|
|
34
|
-
* @param contract - The contract name.
|
|
35
|
-
* @param fromVersion - The source schema version to migrate from.
|
|
36
|
-
*/
|
|
37
|
-
canMigrate(contract: string, fromVersion: SchemaVersion): boolean;
|
|
38
|
-
/**
|
|
39
|
-
* Resolves an ordered chain of migration steps from `from` to `to`.
|
|
40
|
-
*
|
|
41
|
-
* Uses a greedy forward walk: at each version, finds the step that advances
|
|
42
|
-
* closest to the target. Throws if no complete path exists.
|
|
43
|
-
*
|
|
44
|
-
* @param contract - The contract name.
|
|
45
|
-
* @param from - The starting schema version.
|
|
46
|
-
* @param to - The target schema version.
|
|
47
|
-
* @returns Ordered array of MigrationSteps forming the complete path.
|
|
48
|
-
* @throws If no migration path exists between the two versions.
|
|
49
|
-
*/
|
|
50
|
-
getMigrationPath(contract: string, from: SchemaVersion, to: SchemaVersion): MigrationStep[];
|
|
51
|
-
/**
|
|
52
|
-
* Migrates data from `fromVersion` to the latest version reachable via
|
|
53
|
-
* registered steps for the given contract.
|
|
54
|
-
*
|
|
55
|
-
* If `fromVersion` already matches the end of the migration chain,
|
|
56
|
-
* the data is returned as-is without transformation.
|
|
57
|
-
*
|
|
58
|
-
* @param contract - The contract name.
|
|
59
|
-
* @param data - The raw data to migrate.
|
|
60
|
-
* @param fromVersion - The schema version of the incoming data.
|
|
61
|
-
* @returns The migrated data and the version it was migrated to.
|
|
62
|
-
* @throws If the migration path is incomplete or a step throws.
|
|
63
|
-
*/
|
|
64
|
-
migrate(contract: string, data: unknown, fromVersion: SchemaVersion): MigrationResult;
|
|
65
|
-
/**
|
|
66
|
-
* Resolves the highest version reachable from `fromVersion` by following
|
|
67
|
-
* the chain of registered steps.
|
|
68
|
-
*/
|
|
69
|
-
private _resolveLatestVersion;
|
|
70
|
-
/**
|
|
71
|
-
* Returns all registered contract names.
|
|
72
|
-
*/
|
|
73
|
-
contractNames(): string[];
|
|
74
|
-
}
|
|
75
|
-
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../src/_internal/platform/runtime/contracts/migrations/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,aAAa,EAInB,MAAM,aAAa,CAAC;AAErB;;;;;;GAMG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAsC;IAE5D;;;;;;;;;OASG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,GAAG,IAAI;IAiBrD;;;;;;OAMG;IACH,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,aAAa,GAAG,OAAO;IAKjE;;;;;;;;;;;OAWG;IACH,gBAAgB,CACd,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,aAAa,EACnB,EAAE,EAAE,aAAa,GAChB,aAAa,EAAE;IAqDlB;;;;;;;;;;;;OAYG;IACH,OAAO,CACL,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,OAAO,EACb,WAAW,EAAE,aAAa,GACzB,eAAe;IAiBlB;;;OAGG;IACH,OAAO,CAAC,qBAAqB;IAwB7B;;OAEG;IACH,aAAa,IAAI,MAAM,EAAE;CAG1B"}
|
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Compatibility Contracts — Migration Registry
|
|
3
|
-
*
|
|
4
|
-
* The MigrationRegistry holds all registered schema migration steps and
|
|
5
|
-
* provides path resolution and ordered execution for any version pair.
|
|
6
|
-
*
|
|
7
|
-
* @module contracts/migrations
|
|
8
|
-
*/
|
|
9
|
-
import { compareVersions, versionsEqual, versionToString, } from '../types.js';
|
|
10
|
-
/**
|
|
11
|
-
* Central registry for all schema migration steps.
|
|
12
|
-
*
|
|
13
|
-
* Migrations are registered per contract name and resolved as an ordered
|
|
14
|
-
* chain from a given source version to the current target version.
|
|
15
|
-
* All migration functions must be pure (no mutations, no side effects).
|
|
16
|
-
*/
|
|
17
|
-
export class MigrationRegistry {
|
|
18
|
-
steps = new Map();
|
|
19
|
-
/**
|
|
20
|
-
* Registers a migration step for a given contract.
|
|
21
|
-
*
|
|
22
|
-
* Steps are stored in insertion order and sorted at resolution time.
|
|
23
|
-
* Duplicate from→to pairs for the same contract are not permitted.
|
|
24
|
-
*
|
|
25
|
-
* @param contract - The contract name (e.g. 'runtimeState').
|
|
26
|
-
* @param step - The migration step to register.
|
|
27
|
-
* @throws If a step with the same from→to is already registered.
|
|
28
|
-
*/
|
|
29
|
-
register(contract, step) {
|
|
30
|
-
if (!this.steps.has(contract)) {
|
|
31
|
-
this.steps.set(contract, []);
|
|
32
|
-
}
|
|
33
|
-
const existing = this.steps.get(contract);
|
|
34
|
-
const duplicate = existing.find((s) => versionsEqual(s.from, step.from) && versionsEqual(s.to, step.to));
|
|
35
|
-
if (duplicate) {
|
|
36
|
-
throw new Error(`Duplicate migration step for contract '${contract}': ` +
|
|
37
|
-
`${versionToString(step.from)} → ${versionToString(step.to)}`);
|
|
38
|
-
}
|
|
39
|
-
existing.push(step);
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* Returns true if any migration path exists from `fromVersion` toward
|
|
43
|
-
* a higher version for the given contract.
|
|
44
|
-
*
|
|
45
|
-
* @param contract - The contract name.
|
|
46
|
-
* @param fromVersion - The source schema version to migrate from.
|
|
47
|
-
*/
|
|
48
|
-
canMigrate(contract, fromVersion) {
|
|
49
|
-
const steps = this.steps.get(contract) ?? [];
|
|
50
|
-
return steps.some((s) => versionsEqual(s.from, fromVersion));
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Resolves an ordered chain of migration steps from `from` to `to`.
|
|
54
|
-
*
|
|
55
|
-
* Uses a greedy forward walk: at each version, finds the step that advances
|
|
56
|
-
* closest to the target. Throws if no complete path exists.
|
|
57
|
-
*
|
|
58
|
-
* @param contract - The contract name.
|
|
59
|
-
* @param from - The starting schema version.
|
|
60
|
-
* @param to - The target schema version.
|
|
61
|
-
* @returns Ordered array of MigrationSteps forming the complete path.
|
|
62
|
-
* @throws If no migration path exists between the two versions.
|
|
63
|
-
*/
|
|
64
|
-
getMigrationPath(contract, from, to) {
|
|
65
|
-
if (versionsEqual(from, to))
|
|
66
|
-
return [];
|
|
67
|
-
if (compareVersions(from, to) > 0) {
|
|
68
|
-
throw new Error(`Downgrade migration is not supported for contract '${contract}': ` +
|
|
69
|
-
`cannot migrate from ${versionToString(from)} to ${versionToString(to)}`);
|
|
70
|
-
}
|
|
71
|
-
const allSteps = this.steps.get(contract) ?? [];
|
|
72
|
-
const path = [];
|
|
73
|
-
let current = from;
|
|
74
|
-
while (!versionsEqual(current, to)) {
|
|
75
|
-
// Find all steps that start from the current version
|
|
76
|
-
const candidates = allSteps.filter((s) => versionsEqual(s.from, current));
|
|
77
|
-
if (candidates.length === 0) {
|
|
78
|
-
throw new Error(`No migration step found for contract '${contract}' ` +
|
|
79
|
-
`at version ${versionToString(current)} (target: ${versionToString(to)})`);
|
|
80
|
-
}
|
|
81
|
-
// Pick the step that advances closest to the target without overshooting
|
|
82
|
-
const step = candidates.reduce((best, candidate) => {
|
|
83
|
-
const cmpBest = compareVersions(best.to, to);
|
|
84
|
-
const cmpCandidate = compareVersions(candidate.to, to);
|
|
85
|
-
// Prefer steps that don't overshoot; among those, prefer the one closest to target
|
|
86
|
-
if (cmpBest > 0 && cmpCandidate <= 0)
|
|
87
|
-
return candidate;
|
|
88
|
-
if (cmpCandidate > 0 && cmpBest <= 0)
|
|
89
|
-
return best;
|
|
90
|
-
return compareVersions(candidate.to, best.to) > 0 ? candidate : best;
|
|
91
|
-
});
|
|
92
|
-
if (compareVersions(step.to, current) <= 0) {
|
|
93
|
-
throw new Error(`Migration step for contract '${contract}' does not advance version: ` +
|
|
94
|
-
`${versionToString(step.from)} → ${versionToString(step.to)}`);
|
|
95
|
-
}
|
|
96
|
-
path.push(step);
|
|
97
|
-
current = step.to;
|
|
98
|
-
if (path.length > 100) {
|
|
99
|
-
throw new Error(`Migration path for contract '${contract}' exceeded 100 steps — possible cycle detected`);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
return path;
|
|
103
|
-
}
|
|
104
|
-
/**
|
|
105
|
-
* Migrates data from `fromVersion` to the latest version reachable via
|
|
106
|
-
* registered steps for the given contract.
|
|
107
|
-
*
|
|
108
|
-
* If `fromVersion` already matches the end of the migration chain,
|
|
109
|
-
* the data is returned as-is without transformation.
|
|
110
|
-
*
|
|
111
|
-
* @param contract - The contract name.
|
|
112
|
-
* @param data - The raw data to migrate.
|
|
113
|
-
* @param fromVersion - The schema version of the incoming data.
|
|
114
|
-
* @returns The migrated data and the version it was migrated to.
|
|
115
|
-
* @throws If the migration path is incomplete or a step throws.
|
|
116
|
-
*/
|
|
117
|
-
migrate(contract, data, fromVersion) {
|
|
118
|
-
// Find the highest reachable version from this starting point
|
|
119
|
-
const targetVersion = this._resolveLatestVersion(contract, fromVersion);
|
|
120
|
-
if (versionsEqual(fromVersion, targetVersion)) {
|
|
121
|
-
return { data, version: fromVersion };
|
|
122
|
-
}
|
|
123
|
-
const path = this.getMigrationPath(contract, fromVersion, targetVersion);
|
|
124
|
-
let current = data;
|
|
125
|
-
for (const step of path) {
|
|
126
|
-
current = step.migrate(current);
|
|
127
|
-
}
|
|
128
|
-
return { data: current, version: targetVersion };
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* Resolves the highest version reachable from `fromVersion` by following
|
|
132
|
-
* the chain of registered steps.
|
|
133
|
-
*/
|
|
134
|
-
_resolveLatestVersion(contract, fromVersion) {
|
|
135
|
-
const allSteps = this.steps.get(contract) ?? [];
|
|
136
|
-
let current = fromVersion;
|
|
137
|
-
const visited = new Set();
|
|
138
|
-
while (true) {
|
|
139
|
-
const key = versionToString(current);
|
|
140
|
-
if (visited.has(key))
|
|
141
|
-
break;
|
|
142
|
-
visited.add(key);
|
|
143
|
-
const next = allSteps
|
|
144
|
-
.filter((s) => versionsEqual(s.from, current))
|
|
145
|
-
.sort((a, b) => compareVersions(b.to, a.to))[0];
|
|
146
|
-
if (!next)
|
|
147
|
-
break;
|
|
148
|
-
current = next.to;
|
|
149
|
-
}
|
|
150
|
-
return current;
|
|
151
|
-
}
|
|
152
|
-
/**
|
|
153
|
-
* Returns all registered contract names.
|
|
154
|
-
*/
|
|
155
|
-
contractNames() {
|
|
156
|
-
return [...this.steps.keys()];
|
|
157
|
-
}
|
|
158
|
-
}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Compatibility Contracts — Consolidated Migration Schemas
|
|
3
|
-
*
|
|
4
|
-
* All domain migration steps in one place. Each schema section:
|
|
5
|
-
* - Declares a private `_STEPS` array (MigrationStep[])
|
|
6
|
-
* - Exports a `_VERSION` constant (re-exported from SCHEMA_VERSIONS)
|
|
7
|
-
* - Exports a `get…MigrationSteps()` function for the contract registry
|
|
8
|
-
*
|
|
9
|
-
* Previously split across 5 stub files; consolidated here for DRY.
|
|
10
|
-
* Split back out if any individual contract accumulates > 10 migration steps.
|
|
11
|
-
*
|
|
12
|
-
* All migration functions must be pure — no mutations, no side effects.
|
|
13
|
-
*
|
|
14
|
-
* @module contracts/migrations/schemas
|
|
15
|
-
*/
|
|
16
|
-
import type { MigrationStep } from '../types.js';
|
|
17
|
-
/** The current EventEnvelope schema version (re-exported for convenience). */
|
|
18
|
-
export declare const EVENT_ENVELOPE_VERSION: {
|
|
19
|
-
readonly major: 1;
|
|
20
|
-
readonly minor: 1;
|
|
21
|
-
readonly patch: 0;
|
|
22
|
-
};
|
|
23
|
-
/** Returns all EventEnvelope migration steps. Used by the contract registry. */
|
|
24
|
-
export declare function getEventEnvelopeMigrationSteps(): MigrationStep[];
|
|
25
|
-
/** The current PluginManifest schema version (re-exported for convenience). */
|
|
26
|
-
export declare const PLUGIN_MANIFEST_VERSION: {
|
|
27
|
-
readonly major: 1;
|
|
28
|
-
readonly minor: 0;
|
|
29
|
-
readonly patch: 0;
|
|
30
|
-
};
|
|
31
|
-
/** Returns all PluginManifest migration steps. Used by the contract registry. */
|
|
32
|
-
export declare function getPluginManifestMigrationSteps(): MigrationStep[];
|
|
33
|
-
/** The current RuntimeState schema version (re-exported for convenience). */
|
|
34
|
-
export declare const RUNTIME_STATE_VERSION: {
|
|
35
|
-
readonly major: 1;
|
|
36
|
-
readonly minor: 0;
|
|
37
|
-
readonly patch: 0;
|
|
38
|
-
};
|
|
39
|
-
/** Returns all RuntimeState migration steps. Used by the contract registry. */
|
|
40
|
-
export declare function getRuntimeStateMigrationSteps(): MigrationStep[];
|
|
41
|
-
/** The current Session schema version (re-exported for convenience). */
|
|
42
|
-
export declare const SESSION_VERSION: {
|
|
43
|
-
readonly major: 1;
|
|
44
|
-
readonly minor: 0;
|
|
45
|
-
readonly patch: 0;
|
|
46
|
-
};
|
|
47
|
-
/** Returns all Session migration steps. Used by the contract registry. */
|
|
48
|
-
export declare function getSessionMigrationSteps(): MigrationStep[];
|
|
49
|
-
/** The current TaskRecord schema version (re-exported for convenience). */
|
|
50
|
-
export declare const TASK_RECORD_VERSION: {
|
|
51
|
-
readonly major: 1;
|
|
52
|
-
readonly minor: 0;
|
|
53
|
-
readonly patch: 0;
|
|
54
|
-
};
|
|
55
|
-
/** Returns all TaskRecord migration steps. Used by the contract registry. */
|
|
56
|
-
export declare function getTaskRecordMigrationSteps(): MigrationStep[];
|
|
57
|
-
//# sourceMappingURL=schemas.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../../../../../../src/_internal/platform/runtime/contracts/migrations/schemas.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AA2BjD,8EAA8E;AAC9E,eAAO,MAAM,sBAAsB;;;;CAAgC,CAAC;AAEpE,gFAAgF;AAChF,wBAAgB,8BAA8B,IAAI,aAAa,EAAE,CAEhE;AA0BD,+EAA+E;AAC/E,eAAO,MAAM,uBAAuB;;;;CAAiC,CAAC;AAEtE,iFAAiF;AACjF,wBAAgB,+BAA+B,IAAI,aAAa,EAAE,CAEjE;AA0BD,6EAA6E;AAC7E,eAAO,MAAM,qBAAqB;;;;CAA+B,CAAC;AAElE,+EAA+E;AAC/E,wBAAgB,6BAA6B,IAAI,aAAa,EAAE,CAE/D;AA2BD,wEAAwE;AACxE,eAAO,MAAM,eAAe;;;;CAA0B,CAAC;AAEvD,0EAA0E;AAC1E,wBAAgB,wBAAwB,IAAI,aAAa,EAAE,CAE1D;AA0BD,2EAA2E;AAC3E,eAAO,MAAM,mBAAmB;;;;CAA6B,CAAC;AAE9D,6EAA6E;AAC7E,wBAAgB,2BAA2B,IAAI,aAAa,EAAE,CAE7D"}
|