forked from CopilotKit/CopilotKit
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathslug-map.ts
More file actions
418 lines (402 loc) · 15.8 KB
/
Copy pathslug-map.ts
File metadata and controls
418 lines (402 loc) · 15.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
/**
* Shared slug / examples-directory mapping tables.
*
* Three tools consume these:
* - audit.ts (showcase slug → examples dir name[s])
* - validate-pins.ts (showcase slug → examples dir, via SLUG_MAP
* inverse + FALLBACK_MAP override)
* - validate-parity.ts (born-in-showcase membership)
*
* Everything here is immutable. We freeze the plain-object maps and
* install throwing replacement methods on the Set/Map via
* `Object.defineProperty` with `writable:false, configurable:false`
* (a frozen plain Object is not enough for Map/Set — their `.set`/`.add`
* methods don't respect Object.freeze). The consequence: any runtime
* mutation attempt throws, matching the TypeScript `Readonly*` types.
*/
/**
* Semantic alias: a slug that names a `showcase/integrations/<slug>/`
* directory. Structurally a plain string (so external callers can
* compare against arbitrary strings without ceremony) but named
* distinctly from `ExamplesDir` so the map signatures below document
* their direction of mapping. The runtime invariant — every
* ShowcaseSlug that appears in SLUG_MAP (as a value), SLUG_TO_EXAMPLES
* (as a key), or FALLBACK_MAP (as a key) is an actual directory under
* `showcase/integrations/` — is enforced by slug-map.test.ts, not by the
* type system.
*
* We deliberately keep this as a type alias (not a branded type) to
* avoid forcing `as` casts on every external caller that builds a
* slug from a `path.basename` or `fs.readdirSync` result. The safety
* this trades away is recovered by `isShowcaseSlug` — a runtime
* validator applied at API boundaries (see `ENTRIES` construction
* below and the BORN_IN_SHOWCASE / SLUG_MAP assertions).
*/
export type ShowcaseSlug = string;
/**
* Semantic alias: a directory name under `examples/integrations/` (or
* `integrations/` in older trees). Intentionally distinct at the type
* level from ShowcaseSlug so the SLUG_MAP / SLUG_TO_EXAMPLES /
* FALLBACK_MAP signatures read unambiguously. Structurally `string`
* for the same reason as ShowcaseSlug: external callers iterate these
* maps with plain strings and we don't want to force `as` casts on
* every caller.
*/
export type ExamplesDir = string;
/**
* Runtime validator for a ShowcaseSlug. Applied at API boundaries
* (SLUG entries construction, BORN_IN_SHOWCASE membership, callers
* that accept user-supplied slug strings) to catch obvious garbage —
* empty strings, whitespace, path separators — before it flows into
* a filesystem path or a slug-indexed Map.
*
* The parameter type is `unknown` — this function sits at an API
* boundary where TS guarantees are weakest (yaml.parse results, JSON
* roundtrips, user-supplied strings). A widened param keeps the typeof
* guard live rather than reducing to a dead check under `(s: string)`.
*
* The return signature is a user-defined type predicate (`s is
* ShowcaseSlug`), so callers can narrow an `unknown` or `string` local
* to a `ShowcaseSlug` without a cast after a successful check.
*/
export function isShowcaseSlug(s: unknown): s is ShowcaseSlug {
if (typeof s !== "string") return false;
if (s.length === 0) return false;
// Reject whitespace and path separators: these are the characters
// that would break `path.join(packages, slug)` most surprisingly
// (newlines, spaces, `/`, `\`). We intentionally don't enforce a
// strict kebab-case pattern — existing slugs include dots and
// uppercase in related repos, so a strict regex would over-constrain.
if (/[\s/\\]/.test(s)) return false;
return true;
}
/**
* Wrap a Set so mutation methods throw. Object.freeze on a Set does
* not prevent .add/.delete — the set itself is frozen but its internal
* slots are not. Casting to ReadonlySet is compile-time only.
*
* The returned type is ReadonlySet<T> (not Set<T>): callers that keep
* a Set handle would bypass the runtime guard, so we force the narrow
* type out of the helper.
*
* The replacement methods are installed with `writable: false` and
* `configurable: false` so they cannot themselves be re-replaced
* (`Object.defineProperty(set, "add", {value: realAdd})`) to restore
* mutation. Without those descriptors, a later caller could silently
* circumvent the freeze.
*/
function freezeSet<T>(s: Set<T>): ReadonlySet<T> {
// Replace mutation methods FIRST, THEN freeze — Object.freeze locks
// the object non-extensible, after which defineProperty throws.
// `add` is typed to return the set so we match that signature but
// throw before anything can observe the return value.
const fail = (method: string) => () => {
throw new TypeError(`Cannot ${method} frozen Set`);
};
const lock = { writable: false, configurable: false, enumerable: false };
Object.defineProperty(s, "add", { ...lock, value: fail("add") });
Object.defineProperty(s, "delete", { ...lock, value: fail("delete") });
Object.defineProperty(s, "clear", { ...lock, value: fail("clear") });
return Object.freeze(s);
}
function freezeMap<K, V>(m: Map<K, V>): ReadonlyMap<K, V> {
const fail = (method: string) => () => {
throw new TypeError(`Cannot ${method} frozen Map`);
};
const lock = { writable: false, configurable: false, enumerable: false };
Object.defineProperty(m, "set", { ...lock, value: fail("set") });
Object.defineProperty(m, "delete", { ...lock, value: fail("delete") });
Object.defineProperty(m, "clear", { ...lock, value: fail("clear") });
return Object.freeze(m);
}
/**
* Freeze a 2D record (outer object + inner arrays) in one call. The
* outer record is frozen so keys cannot be added/removed/reassigned;
* each inner array is also frozen so element assignment (`arr[0] = …`)
* throws in strict mode. Prevents the common "I froze the outer but
* forgot the inner" bug and ensures the Readonly<...> type matches the
* runtime behavior.
*/
function freezeMap2D<K extends string, V>(
obj: Record<K, readonly V[]>,
): Readonly<Record<K, readonly V[]>> {
for (const k of Object.keys(obj) as K[]) {
Object.freeze(obj[k]);
}
return Object.freeze(obj);
}
/**
* Single source of truth for the showcase/examples mapping tables.
* `SLUG_TO_EXAMPLES`, `FALLBACK_MAP`, and `BORN_IN_SHOWCASE` below are
* derived from this array via reducers. Adding / removing / renaming
* a slug happens in ONE place; before, three parallel maps had to be
* edited in lockstep and silently fell out of sync.
*
* Entry shape:
* - slug — the `showcase/integrations/<slug>/` directory name
* - bornInShowcase — true iff the package has no examples/integrations
* counterpart (skip instead of warn downstream).
* When true, `examples` MUST be empty.
* - examples — candidate dir names under `examples/integrations/`
* (or `integrations/` in older trees). The FIRST
* entry is treated as the preferred fallback.
* - fallback — if true, expose an entry in FALLBACK_MAP that
* points at `examples[0]`. FALLBACK_MAP documents
* known SLUG_MAP staleness where the slug under
* `showcase/integrations/` no longer matches SLUG_MAP's
* examples→slug direction.
*
* `SLUG_MAP` (examples → slug) is NOT derived — it reflects the
* historical migrate-integration-examples.ts intent and is kept as a
* standalone declaration so the "known stale" documentation at its
* call sites in validate-pins.ts continues to hold.
*/
/**
* Discriminated union form of SlugEntry.
*
* Three mutually-exclusive shapes:
* - born-in-showcase: no examples counterpart, no fallback. The
* `examples` tuple is statically empty and
* `fallback` is never set.
* - examples (no fallback): a real examples dir exists but the
* SLUG_MAP forward mapping is not stale.
* The `examples` tuple is non-empty and
* `fallback` is explicitly false.
* - examples + fallback: SLUG_MAP is stale for this slug;
* FALLBACK_MAP documents the correction.
* The `examples` tuple is non-empty and
* `fallback` is true; FALLBACK_MAP keys
* off this variant.
*
* Modeled as a discriminated union so consumers can read
* `e.examples[0]` on the fallback branch without a runtime length
* assertion — the tuple type guarantees at least one element, and the
* compiler narrows accordingly. Prior to this split, the flat
* `examples: readonly ExamplesDir[]` forced a runtime check at the
* FALLBACK_MAP reducer to rule out an empty tuple.
*/
type SlugEntry =
| {
readonly slug: ShowcaseSlug;
readonly bornInShowcase: true;
readonly examples: readonly [];
readonly fallback: false;
}
| {
readonly slug: ShowcaseSlug;
readonly bornInShowcase: false;
readonly examples: readonly [ExamplesDir, ...ExamplesDir[]];
readonly fallback: boolean;
};
const ENTRIES: readonly SlugEntry[] = [
// Born-in-showcase packages have no examples/integrations counterpart.
{ slug: "ag2", bornInShowcase: true, examples: [], fallback: false },
{
slug: "claude-sdk-python",
bornInShowcase: true,
examples: [],
fallback: false,
},
{
slug: "claude-sdk-typescript",
bornInShowcase: true,
examples: [],
fallback: false,
},
{ slug: "langroid", bornInShowcase: true, examples: [], fallback: false },
{
slug: "ms-agent-harness-dotnet",
bornInShowcase: true,
examples: [],
fallback: false,
},
{ slug: "spring-ai", bornInShowcase: true, examples: [], fallback: false },
// Packages with a straightforward examples/integrations counterpart
// whose dir name matches SLUG_MAP's examples→slug direction.
{
slug: "langgraph-python",
bornInShowcase: false,
examples: ["langgraph-python"],
fallback: false,
},
{
slug: "langgraph-typescript",
bornInShowcase: false,
examples: ["langgraph-js"],
fallback: false,
},
{
slug: "langgraph-fastapi",
bornInShowcase: false,
examples: ["langgraph-fastapi"],
fallback: false,
},
{
slug: "mastra",
bornInShowcase: false,
examples: ["mastra"],
fallback: false,
},
{
slug: "agno",
bornInShowcase: false,
examples: ["agno"],
fallback: false,
},
{
slug: "llamaindex",
bornInShowcase: false,
examples: ["llamaindex"],
fallback: false,
},
{
slug: "google-adk",
bornInShowcase: false,
examples: ["adk"],
fallback: false,
},
// Packages whose showcase slug no longer matches SLUG_MAP's
// examples→slug direction — these need FALLBACK_MAP entries so
// validate-pins.ts can still resolve them.
{
slug: "crewai-crews",
bornInShowcase: false,
examples: ["crewai-crews"],
fallback: true,
},
{
slug: "pydantic-ai",
bornInShowcase: false,
examples: ["pydantic-ai"],
fallback: true,
},
{
slug: "ms-agent-dotnet",
bornInShowcase: false,
examples: ["ms-agent-framework-dotnet"],
fallback: true,
},
{
slug: "ms-agent-python",
bornInShowcase: false,
examples: ["ms-agent-framework-python"],
fallback: true,
},
{
slug: "strands",
bornInShowcase: false,
examples: ["strands-python"],
fallback: true,
},
];
// API-boundary validation: every slug that enters the derived maps
// must pass `isShowcaseSlug`. The loop runs at module load so a bad
// entry trips construction immediately rather than on first use.
// The "bornInShowcase implies empty examples" and "fallback implies
// non-empty examples" invariants are enforced statically by the
// SlugEntry discriminated union above and no longer need a runtime
// check here.
for (const e of ENTRIES) {
if (!isShowcaseSlug(e.slug)) {
throw new Error(
`lib/slug-map: invalid ShowcaseSlug in ENTRIES: ${JSON.stringify(e.slug)}`,
);
}
}
/**
* Packages intentionally without a Dojo (examples/integrations)
* counterpart. They are the single source of truth for:
* - audit.ts → skip the "missing examples/integrations counterpart"
* anomaly;
* - validate-pins.ts → emit [SKIP] instead of [WARN].
*
* Derived from ENTRIES by filtering `bornInShowcase === true`.
*/
export const BORN_IN_SHOWCASE: ReadonlySet<ShowcaseSlug> = freezeSet(
new Set<ShowcaseSlug>(
ENTRIES.filter((e) => e.bornInShowcase).map((e) => e.slug),
),
);
/**
* Forward map: examples/integrations directory name → showcase slug.
* Mirrors migrate-integration-examples.ts (which does not export its
* SLUG_MAP). Kept as a Map for O(1) lookup and so `.get()` returns
* `ShowcaseSlug | undefined` unambiguously.
*
* This map is NOT derived from ENTRIES: it represents the historical
* migration intent at the time showcase was split from
* examples/integrations, and validate-pins.ts's comments explicitly
* call out that it is "known stale". FALLBACK_MAP (derived from
* ENTRIES) documents the corrections where SLUG_MAP no longer matches
* the current slug under `showcase/integrations/`.
*
* Only entries whose VALUES correspond to real `showcase/integrations/<slug>/`
* directories are included. Dead entries (crewai-flows → crewai,
* pydantic-ai → pydanticai, ms-agent-framework-dotnet → maf-dotnet,
* etc.) were removed because they broke validate-pins.ts's reverse
* lookup and forced FALLBACK_MAP to re-express the corrections. The
* slug-map.test.ts pins this invariant against the real integrations/ tree
* so future edits cannot reintroduce the drift.
*/
export const SLUG_MAP: ReadonlyMap<ExamplesDir, ShowcaseSlug> = freezeMap(
new Map<ExamplesDir, ShowcaseSlug>([
["langgraph-python", "langgraph-python"],
["langgraph-js", "langgraph-typescript"],
["langgraph-fastapi", "langgraph-fastapi"],
["mastra", "mastra"],
["agno", "agno"],
["llamaindex", "llamaindex"],
["adk", "google-adk"],
]),
);
// Construction-time assertion: every SLUG_MAP value is a valid slug.
for (const [, slug] of SLUG_MAP) {
if (!isShowcaseSlug(slug)) {
throw new Error(
`lib/slug-map: invalid SLUG_MAP value: ${JSON.stringify(slug)}`,
);
}
}
/**
* Reverse / corrected map used by audit.ts: showcase slug → candidate
* examples/integrations dir name(s). Dead entries that pointed at
* non-existent showcase packages (crewai-flows, agent-spec-langgraph,
* mcp-apps) are intentionally excluded so audit.ts no longer emits
* phantom "no examples source" anomalies for them.
*
* Derived from ENTRIES: each entry with non-empty `examples` becomes
* `SLUG_TO_EXAMPLES[slug] = examples`. freezeMap2D freezes BOTH the
* outer record (no adding/removing keys, no reassigning arrays) AND
* each inner array (no element assignment). The compile-time
* `Readonly<Record<..., readonly ExamplesDir[]>>` matches.
*/
export const SLUG_TO_EXAMPLES: Readonly<
Record<ShowcaseSlug, readonly ExamplesDir[]>
> = freezeMap2D<ShowcaseSlug, ExamplesDir>(
ENTRIES.reduce<Record<ShowcaseSlug, readonly ExamplesDir[]>>((acc, e) => {
if (e.examples.length > 0) {
acc[e.slug] = e.examples;
}
return acc;
}, {}),
);
/**
* Fallback map used by validate-pins.ts: showcase slug → examples dir
* name. These entries document known SLUG_MAP staleness — the slug
* under `showcase/integrations/` no longer matches the value in SLUG_MAP,
* so we override here. If SLUG_MAP is refreshed, clean up the
* `fallback: true` flag on the corresponding ENTRIES row and this
* map rebuilds to match.
*
* Derived from ENTRIES: each entry with `fallback: true` exposes
* `FALLBACK_MAP[slug] = examples[0]`.
*/
export const FALLBACK_MAP: Readonly<Record<ShowcaseSlug, ExamplesDir>> =
Object.freeze(
ENTRIES.reduce<Record<ShowcaseSlug, ExamplesDir>>((acc, e) => {
if (e.fallback) {
acc[e.slug] = e.examples[0];
}
return acc;
}, {}),
);