@donkeylabs/server 2.0.37 → 2.2.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/docs/cache.md +27 -34
- package/docs/code-organization.md +424 -0
- package/docs/project-structure.md +37 -26
- package/docs/rate-limiter.md +23 -28
- package/docs/swift-adapter.md +293 -0
- package/docs/versioning.md +351 -0
- package/package.json +6 -2
- package/src/client/base.ts +18 -5
- package/src/core/cache-adapter-redis.ts +113 -0
- package/src/core/events.ts +54 -7
- package/src/core/health.ts +165 -0
- package/src/core/index.ts +22 -0
- package/src/core/jobs.ts +11 -4
- package/src/core/rate-limit-adapter-redis.ts +109 -0
- package/src/core/subprocess-bootstrap.ts +3 -0
- package/src/core/workflow-executor.ts +1 -0
- package/src/core/workflow-state-machine.ts +6 -5
- package/src/core/workflows.ts +3 -2
- package/src/core.ts +9 -1
- package/src/generator/index.ts +4 -0
- package/src/harness.ts +17 -5
- package/src/index.ts +21 -0
- package/src/router.ts +22 -2
- package/src/server.ts +458 -117
- package/src/versioning.ts +154 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight semver utilities for API versioning.
|
|
3
|
+
* No external dependencies.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// ============================================
|
|
7
|
+
// Types
|
|
8
|
+
// ============================================
|
|
9
|
+
|
|
10
|
+
export interface SemVer {
|
|
11
|
+
major: number;
|
|
12
|
+
minor: number;
|
|
13
|
+
patch: number;
|
|
14
|
+
/** Original string (e.g., "2.1.0") */
|
|
15
|
+
raw: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface VersioningConfig {
|
|
19
|
+
/** What to do when no X-API-Version header is sent.
|
|
20
|
+
* - "latest": resolve to the highest registered version (default)
|
|
21
|
+
* - "unversioned": prefer unversioned route if available, else latest
|
|
22
|
+
* - "error": return 400 if no version header
|
|
23
|
+
*/
|
|
24
|
+
defaultBehavior?: "latest" | "unversioned" | "error";
|
|
25
|
+
/** Echo the resolved version in the response header (default: true) */
|
|
26
|
+
echoVersion?: boolean;
|
|
27
|
+
/** Custom header name (default: "X-API-Version") */
|
|
28
|
+
headerName?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface DeprecationInfo {
|
|
32
|
+
/** ISO date when this version will be removed */
|
|
33
|
+
sunsetDate?: string;
|
|
34
|
+
/** Human-readable deprecation message */
|
|
35
|
+
message?: string;
|
|
36
|
+
/** Suggested successor version (e.g., "2.0.0") */
|
|
37
|
+
successor?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface RouterOptions {
|
|
41
|
+
/** Semver version for this router (e.g., "1.0.0", "2.1.0") */
|
|
42
|
+
version?: string;
|
|
43
|
+
/** Mark this router version as deprecated */
|
|
44
|
+
deprecated?: DeprecationInfo;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ============================================
|
|
48
|
+
// Parsing
|
|
49
|
+
// ============================================
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Parse a semver string into a SemVer object.
|
|
53
|
+
* Accepts "2.1.0", "v2.1.0", "2.1", "2".
|
|
54
|
+
*/
|
|
55
|
+
export function parseSemVer(version: string): SemVer | null {
|
|
56
|
+
if (!version) return null;
|
|
57
|
+
|
|
58
|
+
// Strip leading "v" or "V"
|
|
59
|
+
const cleaned = version.trim().replace(/^[vV]/, "");
|
|
60
|
+
|
|
61
|
+
const parts = cleaned.split(".");
|
|
62
|
+
if (parts.length === 0 || parts.length > 3) return null;
|
|
63
|
+
|
|
64
|
+
const major = parseInt(parts[0]!, 10);
|
|
65
|
+
if (isNaN(major) || major < 0) return null;
|
|
66
|
+
|
|
67
|
+
const minor = parts.length >= 2 ? parseInt(parts[1]!, 10) : 0;
|
|
68
|
+
if (isNaN(minor) || minor < 0) return null;
|
|
69
|
+
|
|
70
|
+
const patch = parts.length >= 3 ? parseInt(parts[2]!, 10) : 0;
|
|
71
|
+
if (isNaN(patch) || patch < 0) return null;
|
|
72
|
+
|
|
73
|
+
return { major, minor, patch, raw: `${major}.${minor}.${patch}` };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ============================================
|
|
77
|
+
// Comparison
|
|
78
|
+
// ============================================
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Compare two SemVer objects.
|
|
82
|
+
* Returns negative if a < b, positive if a > b, 0 if equal.
|
|
83
|
+
*/
|
|
84
|
+
export function compareSemVer(a: SemVer, b: SemVer): number {
|
|
85
|
+
if (a.major !== b.major) return a.major - b.major;
|
|
86
|
+
if (a.minor !== b.minor) return a.minor - b.minor;
|
|
87
|
+
return a.patch - b.patch;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ============================================
|
|
91
|
+
// Matching
|
|
92
|
+
// ============================================
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Check if a registered version satisfies a requested version string.
|
|
96
|
+
*
|
|
97
|
+
* Supported request formats:
|
|
98
|
+
* - "2" → matches any 2.x.x (major only)
|
|
99
|
+
* - "2.1" → matches any 2.1.x (major.minor)
|
|
100
|
+
* - "2.1.0" → exact match
|
|
101
|
+
* - "2.x" → matches any 2.x.x (wildcard minor)
|
|
102
|
+
* - "2.1.x" → matches any 2.1.x (wildcard patch)
|
|
103
|
+
* - "2.x.x" → matches any 2.x.x (wildcard minor+patch)
|
|
104
|
+
*/
|
|
105
|
+
export function satisfies(registered: SemVer, requested: string): boolean {
|
|
106
|
+
if (!requested) return false;
|
|
107
|
+
|
|
108
|
+
const cleaned = requested.trim().replace(/^[vV]/, "");
|
|
109
|
+
const parts = cleaned.split(".");
|
|
110
|
+
|
|
111
|
+
// Parse major
|
|
112
|
+
const majorStr = parts[0];
|
|
113
|
+
if (!majorStr || majorStr === "x" || majorStr === "*") return true; // match all
|
|
114
|
+
const major = parseInt(majorStr, 10);
|
|
115
|
+
if (isNaN(major)) return false;
|
|
116
|
+
if (registered.major !== major) return false;
|
|
117
|
+
|
|
118
|
+
// Major only (e.g., "2") or wildcard minor (e.g., "2.x", "2.*")
|
|
119
|
+
if (parts.length === 1) return true;
|
|
120
|
+
const minorStr = parts[1];
|
|
121
|
+
if (!minorStr || minorStr === "x" || minorStr === "*") return true;
|
|
122
|
+
const minor = parseInt(minorStr, 10);
|
|
123
|
+
if (isNaN(minor)) return false;
|
|
124
|
+
if (registered.minor !== minor) return false;
|
|
125
|
+
|
|
126
|
+
// Major.minor only (e.g., "2.1") or wildcard patch (e.g., "2.1.x")
|
|
127
|
+
if (parts.length === 2) return true;
|
|
128
|
+
const patchStr = parts[2];
|
|
129
|
+
if (!patchStr || patchStr === "x" || patchStr === "*") return true;
|
|
130
|
+
const patch = parseInt(patchStr, 10);
|
|
131
|
+
if (isNaN(patch)) return false;
|
|
132
|
+
return registered.patch === patch;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Resolve the best matching version from a sorted list.
|
|
137
|
+
* Returns the highest version that satisfies the requested string.
|
|
138
|
+
* Versions should be sorted highest-first for efficiency.
|
|
139
|
+
*/
|
|
140
|
+
export function resolveVersion(
|
|
141
|
+
versions: SemVer[],
|
|
142
|
+
requested: string
|
|
143
|
+
): SemVer | null {
|
|
144
|
+
// Sort highest-first (in case caller didn't)
|
|
145
|
+
const sorted = [...versions].sort((a, b) => compareSemVer(b, a));
|
|
146
|
+
|
|
147
|
+
for (const ver of sorted) {
|
|
148
|
+
if (satisfies(ver, requested)) {
|
|
149
|
+
return ver;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return null;
|
|
154
|
+
}
|