@kalada/provider-routing 0.0.0-stage → 0.1.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/dist/index.js ADDED
@@ -0,0 +1,509 @@
1
+ // src/composition-meter.ts
2
+ var Meter = class {
3
+ work = 0;
4
+ depth = 0;
5
+ diagnostics = 0;
6
+ exhausted = false;
7
+ active = 0;
8
+ limits;
9
+ constructor(limits) {
10
+ this.limits = limits;
11
+ }
12
+ debit(key, amount, limit) {
13
+ if (this.exhausted || !Number.isSafeInteger(amount) || amount < 0 || this[key] + amount > limit) {
14
+ this.exhausted = true;
15
+ return false;
16
+ }
17
+ this[key] += amount;
18
+ return true;
19
+ }
20
+ charge(units) {
21
+ return this.debit("work", units, this.limits.work);
22
+ }
23
+ report(count) {
24
+ return this.debit("diagnostics", count, this.limits.diagnostics);
25
+ }
26
+ enter() {
27
+ if (this.exhausted || this.active >= this.limits.depth) {
28
+ this.exhausted = true;
29
+ return false;
30
+ }
31
+ this.active++;
32
+ this.depth = Math.max(this.depth, this.active);
33
+ return true;
34
+ }
35
+ leave() {
36
+ this.active--;
37
+ }
38
+ };
39
+
40
+ // src/composition-validation.ts
41
+ var MAX_ID = 2048;
42
+ var MAX_DIAGNOSTICS = 100;
43
+ var id = (s) => typeof s === "string" && !!s.trim() && s.length <= MAX_ID;
44
+ var offset = (n, length) => Number.isSafeInteger(n) && n >= 0 && n <= length;
45
+ var range = (start, end) => Object.freeze({ start, end });
46
+ function outcome(status, reason, document, diagnostics = []) {
47
+ return Object.freeze({
48
+ status,
49
+ reason,
50
+ snapshot: document,
51
+ diagnostics: Object.freeze([...diagnostics])
52
+ });
53
+ }
54
+ function checkpoint(request, document) {
55
+ if (request.isCancelled?.()) return "cancelled";
56
+ const original = request.snapshot;
57
+ if (original.uri !== document.uri || original.text !== document.text || original.version !== document.version || original.environmentGeneration !== document.environmentGeneration)
58
+ return "stale";
59
+ if (!request.isCurrent(document)) return "stale";
60
+ }
61
+ function declaredSlotsClear(slots, index, boundary, length) {
62
+ let previous = slots[index]?.start;
63
+ if (previous === void 0 || !offset(previous, length)) return false;
64
+ for (let i = index + 1; i < slots.length; i++) {
65
+ const start = slots[i]?.start;
66
+ if (start === void 0 || !offset(start, length)) return false;
67
+ if (start <= previous || start < boundary) return false;
68
+ previous = start;
69
+ }
70
+ return true;
71
+ }
72
+ function boundedExit(result, owner, start, length, maxStop) {
73
+ const stop = result?.stop;
74
+ return result?.owner === owner && offset(stop, length) && stop >= start && (maxStop === void 0 || stop <= maxStop) && result.range?.start === start && result.range?.end === stop && typeof result.reason === "string" && !!result.reason && (result.status === "valid" || result.status === "invalid" || result.status === "partial" || result.status === "unsupported");
75
+ }
76
+ function snapshot(input) {
77
+ const copy = Object.freeze({
78
+ uri: input?.uri,
79
+ text: input?.text,
80
+ version: input?.version,
81
+ environmentGeneration: input?.environmentGeneration
82
+ });
83
+ if (!id(copy.uri) || typeof copy.text !== "string" || !Number.isSafeInteger(copy.version) || copy.version < 0 || !id(copy.environmentGeneration))
84
+ throw new Error("Invalid snapshot");
85
+ return copy;
86
+ }
87
+ function profileCopy(p) {
88
+ const allowedGuests = [...p.allowedGuests];
89
+ if (p.version !== 1 || !id(p.hostLanguageId) || !id(p.position) || !id(p.open) || !id(p.close) || !allowedGuests.length || allowedGuests.some((guest) => !id(guest)) || new Set(allowedGuests).size !== allowedGuests.length || p.defaultGuest !== void 0 && !allowedGuests.includes(p.defaultGuest))
90
+ throw new Error("Invalid or ambiguous composition profile");
91
+ return Object.freeze({
92
+ version: 1,
93
+ hostLanguageId: p.hostLanguageId,
94
+ position: p.position,
95
+ open: p.open,
96
+ close: p.close,
97
+ defaultGuest: p.defaultGuest,
98
+ allowedGuests: Object.freeze(allowedGuests)
99
+ });
100
+ }
101
+ function diagnosticCopy(items, regionStart, stop, owner) {
102
+ if (!Array.isArray(items) || items.length > MAX_DIAGNOSTICS) return;
103
+ const result = [];
104
+ for (const item of items) {
105
+ const code = item?.code, start = item?.range?.start, end = item?.range?.end;
106
+ if (typeof code !== "string" || code.length > 128 || !/^[A-Z][A-Z0-9_]*$/u.test(code) || item?.owner !== owner || !offset(start, stop) || !offset(end, stop) || start < regionStart || end < start)
107
+ return;
108
+ result.push(Object.freeze({ owner, code, range: range(start, end) }));
109
+ }
110
+ return Object.freeze(result);
111
+ }
112
+
113
+ // src/composition-recovery.ts
114
+ function clearBoundary(request, document, index, end) {
115
+ return declaredSlotsClear(request.slots, index, end, document.text.length);
116
+ }
117
+ function clearFailure(request, document, index, failed) {
118
+ if (!failed.attempt) return true;
119
+ const end = failed.safe ? failed.safe.stop + failed.safe.profile.close.length : failed.attempt.range.end;
120
+ return clearBoundary(request, document, index, end);
121
+ }
122
+ function recoveryFailure(request, document, index, failed, attempts, candidates, reason) {
123
+ if (["budget", "stale", "cancelled"].includes(failed.outcome.status))
124
+ return { outcome: failed.outcome };
125
+ if (!clearFailure(request, document, index, failed))
126
+ return { outcome: outcome("invalid", "OVERLAPPING_SLOT", document) };
127
+ if (failed.attempt) attempts.push(failed.attempt);
128
+ if (failed.safe && index + 1 < request.slots.length) return { safe: failed.safe };
129
+ return { outcome: partial(document, reason, attempts, candidates) };
130
+ }
131
+ function attempt(result, diagnostics) {
132
+ return Object.freeze({
133
+ owner: result.owner,
134
+ range: Object.freeze({ start: result.range.start, end: result.stop }),
135
+ status: result.status,
136
+ reason: result.reason,
137
+ diagnostics
138
+ });
139
+ }
140
+ function partial(document, reason, attempts, candidates) {
141
+ return Object.freeze({
142
+ status: "partial",
143
+ reason,
144
+ snapshot: document,
145
+ diagnostics: Object.freeze(attempts.flatMap((item) => item.diagnostics)),
146
+ attempts: Object.freeze([...attempts]),
147
+ candidates: Object.freeze([...candidates])
148
+ });
149
+ }
150
+ function connectorInput(document, meter, stop, start, next) {
151
+ return Object.freeze({
152
+ snapshot: document,
153
+ close: Object.freeze({ start: stop, end: start }),
154
+ nextSlot: Object.freeze({
155
+ position: next.position,
156
+ start: next.start,
157
+ maxStop: next.maxStop,
158
+ explicitGuest: next.explicitGuest
159
+ }),
160
+ meter
161
+ });
162
+ }
163
+ function connector(request, document, meter, stop, profile, next) {
164
+ const continuation = request.hostContinuation;
165
+ const start = stop + profile.close.length;
166
+ if (!continuation || typeof continuation.validate !== "function" || !Number.isSafeInteger(next?.start) || next.start < start || next.start > document.text.length)
167
+ return false;
168
+ if (!meter.enter()) return false;
169
+ let value;
170
+ const before = meter.work;
171
+ try {
172
+ value = continuation.validate(connectorInput(document, meter, stop, start, next));
173
+ } finally {
174
+ meter.leave();
175
+ }
176
+ const owner = value?.owner;
177
+ const range2 = value?.range;
178
+ const from = range2?.start;
179
+ const to = range2?.end;
180
+ return !meter.exhausted && owner === request.hostLanguageId && from === start && to === next.start && meter.work - before >= to - from;
181
+ }
182
+
183
+ // src/composition.ts
184
+ var MAX_TEXT = 1e5;
185
+ var MAX_WORK = 1e6;
186
+ var MAX_DEPTH = 32;
187
+ var MAX_DIAGNOSTICS2 = 100;
188
+ function select(slot, host, profiles, guests) {
189
+ const profile = profiles.find((p) => p.hostLanguageId === host && p.position === slot.position);
190
+ if (!profile) return;
191
+ const chosen = slot.explicitGuest ?? profile.defaultGuest;
192
+ if (!chosen || !profile.allowedGuests.includes(chosen)) return;
193
+ const guest = guests.get(chosen);
194
+ return guest ? { profile, guest } : void 0;
195
+ }
196
+ function node(owner, start, end, children = [], subtree) {
197
+ return Object.freeze({
198
+ owner,
199
+ range: range(start, end),
200
+ children: Object.freeze([...children]),
201
+ subtree
202
+ });
203
+ }
204
+ function createCompositionRouter(profiles, providers) {
205
+ const registered = profiles.map(profileCopy);
206
+ const keys = registered.map((p) => `${p.hostLanguageId}\0${p.position}`);
207
+ if (new Set(keys).size !== keys.length) throw new Error("Overlapping host positions");
208
+ const guests = /* @__PURE__ */ new Map();
209
+ for (const provider of providers) {
210
+ const languageId = provider?.languageId, parse = provider?.parse;
211
+ if (!id(languageId) || typeof parse !== "function" || guests.has(languageId))
212
+ throw new Error("Invalid or duplicate guest");
213
+ guests.set(languageId, Object.freeze({ languageId, parse: parse.bind(provider) }));
214
+ }
215
+ return Object.freeze({
216
+ compose(request) {
217
+ const document = snapshot(request.snapshot);
218
+ const limits = Object.freeze({ ...request.limits });
219
+ if (!Number.isSafeInteger(limits.work) || limits.work < 1 || limits.work > MAX_WORK || !Number.isSafeInteger(limits.depth) || limits.depth < 1 || limits.depth > MAX_DEPTH || !Number.isSafeInteger(limits.diagnostics) || limits.diagnostics < 0 || limits.diagnostics > MAX_DIAGNOSTICS2)
220
+ throw new Error("Invalid budget");
221
+ const meter = request.meter ?? new Meter(limits);
222
+ try {
223
+ return compose(request, document, meter, registered, guests);
224
+ } catch {
225
+ return outcome("invalid", "COMPOSITION_FAILURE", document);
226
+ }
227
+ }
228
+ });
229
+ }
230
+ function compose(request, document, meter, profiles, guests) {
231
+ const state = checkpoint(request, document);
232
+ if (state) return outcome(state, state, document);
233
+ if (document.text.length > MAX_TEXT) return outcome("budget", "SOURCE_LIMIT", document);
234
+ if (!id(request.hostLanguageId) || !Array.isArray(request.slots) || request.slots.length > MAX_DIAGNOSTICS2)
235
+ return outcome("invalid", "INVALID_HOST", document);
236
+ if (!meter.enter()) return outcome("budget", "DEPTH_LIMIT", document);
237
+ try {
238
+ return walk(request, document, meter, profiles, guests);
239
+ } finally {
240
+ meter.leave();
241
+ }
242
+ }
243
+ function walk(request, document, meter, profiles, guests) {
244
+ const children = [];
245
+ let cursor = 0;
246
+ for (const [index, slot] of request.slots.entries()) {
247
+ const state = checkpoint(request, document);
248
+ if (state) return outcome(state, state, document);
249
+ if (!offset(slot?.start, document.text.length) || slot.start < cursor)
250
+ return outcome("invalid", "OVERLAPPING_SLOT", document);
251
+ const processed = processSlot(request, document, meter, profiles, guests, slot, cursor);
252
+ if ("outcome" in processed)
253
+ return walkFailure(request, document, meter, profiles, guests, index, processed);
254
+ const { start, stop, close, guest, subtree } = processed;
255
+ if (!clearBoundary(request, document, index, stop + close.length))
256
+ return outcome("invalid", "OVERLAPPING_SLOT", document);
257
+ if (slot.start > cursor) children.push(node(request.hostLanguageId, cursor, slot.start));
258
+ children.push(node(request.hostLanguageId, slot.start, start));
259
+ children.push(node(guest, start, stop, [], subtree));
260
+ cursor = stop + close.length;
261
+ children.push(node(request.hostLanguageId, stop, cursor));
262
+ }
263
+ return finishWalk(request, document, meter, children, cursor);
264
+ }
265
+ function walkFailure(request, document, meter, profiles, guests, index, failed) {
266
+ if (!clearFailure(request, document, index, failed))
267
+ return outcome("invalid", "OVERLAPPING_SLOT", document);
268
+ return failed.safe && request.hostContinuation && index + 1 < request.slots.length ? recover(request, document, meter, profiles, guests, index, failed) : failed.outcome;
269
+ }
270
+ function finishWalk(request, document, meter, children, cursor) {
271
+ if (!meter.charge(document.text.length - cursor) || meter.exhausted)
272
+ return outcome("budget", "WORK_LIMIT", document);
273
+ const state = checkpoint(request, document);
274
+ if (state) return outcome(state, state, document);
275
+ if (cursor < document.text.length)
276
+ children.push(node(request.hostLanguageId, cursor, document.text.length));
277
+ return Object.freeze({
278
+ status: "valid",
279
+ reason: "complete",
280
+ snapshot: document,
281
+ diagnostics: Object.freeze([]),
282
+ tree: node(request.hostLanguageId, 0, document.text.length, children)
283
+ });
284
+ }
285
+ function resumeSlot(request, document, meter, profiles, guests, slot, previous) {
286
+ const state = checkpoint(request, document);
287
+ if (state) return { outcome: outcome(state, state, document) };
288
+ if (!slot) return { outcome: outcome("invalid", "MISSING_SLOT", document) };
289
+ const entry = select(slot, request.hostLanguageId, profiles, guests);
290
+ if (!entry || !document.text.startsWith(entry.profile.open, slot.start))
291
+ return { outcome: outcome("invalid", "MISSING_NEXT_OPEN", document) };
292
+ if (!previous.closeCharged && !meter.charge(previous.profile.close.length))
293
+ return { outcome: outcome("budget", "WORK_OR_DIAGNOSTIC_LIMIT", document) };
294
+ if (!connector(request, document, meter, previous.stop, previous.profile, slot))
295
+ return {
296
+ outcome: outcome(meter.exhausted ? "budget" : "invalid", "CONNECTOR_REJECTED", document)
297
+ };
298
+ const after = checkpoint(request, document);
299
+ if (after) return { outcome: outcome(after, after, document) };
300
+ return processSlot(request, document, meter, profiles, guests, slot, slot.start);
301
+ }
302
+ function recover(request, document, meter, profiles, guests, index, failed) {
303
+ const attempts = [failed.attempt];
304
+ const candidates = [];
305
+ let previous = {
306
+ ...failed.safe,
307
+ closeCharged: false
308
+ };
309
+ for (let i = index + 1; i < request.slots.length; i++) {
310
+ const slot = request.slots[i];
311
+ const processed = resumeSlot(request, document, meter, profiles, guests, slot, previous);
312
+ if ("outcome" in processed) {
313
+ const decision = recoveryFailure(
314
+ request,
315
+ document,
316
+ i,
317
+ processed,
318
+ attempts,
319
+ candidates,
320
+ failed.outcome.reason
321
+ );
322
+ if (decision.outcome) return decision.outcome;
323
+ previous = { ...decision.safe, closeCharged: false };
324
+ continue;
325
+ }
326
+ if (!clearBoundary(request, document, i, processed.stop + processed.close.length))
327
+ return outcome("invalid", "OVERLAPPING_SLOT", document);
328
+ candidates.push(node(processed.guest, processed.start, processed.stop, [], processed.subtree));
329
+ previous = { stop: processed.stop, profile: processed.profile, closeCharged: true };
330
+ }
331
+ if (meter.exhausted) return outcome("budget", "WORK_LIMIT", document);
332
+ const state = checkpoint(request, document);
333
+ return state ? outcome(state, state, document) : partial(document, failed.outcome.reason, attempts, candidates);
334
+ }
335
+ function admit(result, entry, start, document, meter, maxStop, guestWork) {
336
+ const guestRange = result.range;
337
+ const value = {
338
+ owner: result.owner,
339
+ stop: result.stop,
340
+ range: { start: guestRange.start, end: guestRange.end },
341
+ reason: result.reason,
342
+ status: result.status,
343
+ diagnostics: result.diagnostics
344
+ };
345
+ if (!boundedExit(value, entry.guest.languageId, start, document.text.length, maxStop))
346
+ return { outcome: outcome("invalid", "INVALID_GUEST_EXIT", document) };
347
+ const copied = diagnosticCopy(value.diagnostics, start, value.stop, entry.guest.languageId);
348
+ if (!copied) return { outcome: outcome("invalid", "INVALID_GUEST_DIAGNOSTIC", document) };
349
+ if (!meter.report(copied.length) || meter.exhausted)
350
+ return { outcome: outcome("budget", "WORK_OR_DIAGNOSTIC_LIMIT", document) };
351
+ if (value.status !== "valid")
352
+ return {
353
+ outcome: outcome(value.status, value.reason, document, copied),
354
+ attempt: attempt(value, copied),
355
+ safe: value.status === "partial" && value.reason === "safe-host-close" && value.stop > start && guestWork >= value.stop - start && document.text.startsWith(entry.profile.close, value.stop) ? { stop: value.stop, profile: entry.profile } : void 0
356
+ };
357
+ return admitValid(result, value, copied, entry, start, document, meter, guestWork);
358
+ }
359
+ function admitValid(result, value, copied, entry, start, document, meter, guestWork) {
360
+ if (value.stop <= start || !document.text.startsWith(entry.profile.close, value.stop) || copied.length || value.reason !== "host-close" || !Number.isSafeInteger(guestWork) || guestWork < 1)
361
+ return { outcome: outcome("invalid", "INVALID_GUEST_EXIT", document) };
362
+ const subtree = result.subtree;
363
+ if (subtree === void 0)
364
+ return { outcome: outcome("invalid", "MISSING_GUEST_SUBTREE", document) };
365
+ if (!meter.charge(entry.profile.close.length))
366
+ return { outcome: outcome("budget", "WORK_OR_DIAGNOSTIC_LIMIT", document) };
367
+ return {
368
+ start,
369
+ stop: value.stop,
370
+ close: entry.profile.close,
371
+ profile: entry.profile,
372
+ guest: entry.guest.languageId,
373
+ subtree
374
+ };
375
+ }
376
+ function processSlot(request, document, meter, profiles, guests, slot, cursor) {
377
+ const entry = select(slot, request.hostLanguageId, profiles, guests);
378
+ if (!entry) return { outcome: outcome("unsupported", "UNDECLARED_GUEST_OR_POSITION", document) };
379
+ if (!document.text.startsWith(entry.profile.open, slot.start))
380
+ return { outcome: outcome("invalid", "MISSING_OPEN", document) };
381
+ const start = slot.start + entry.profile.open.length;
382
+ if (slot.maxStop !== void 0 && (!offset(slot.maxStop, document.text.length) || slot.maxStop < start))
383
+ return { outcome: outcome("invalid", "INVALID_MAX_STOP", document) };
384
+ if (!meter.charge(start - cursor) || !meter.enter())
385
+ return { outcome: outcome("budget", "WORK_OR_DEPTH_LIMIT", document) };
386
+ const workBefore = meter.work;
387
+ let result;
388
+ try {
389
+ result = entry.guest.parse(
390
+ Object.freeze({
391
+ snapshot: document,
392
+ start,
393
+ close: entry.profile.close,
394
+ context: slot.context && Object.freeze({
395
+ expectedType: slot.context.expectedType,
396
+ location: slot.context.location
397
+ }),
398
+ meter
399
+ })
400
+ );
401
+ } finally {
402
+ meter.leave();
403
+ }
404
+ const state = checkpoint(request, document);
405
+ if (state) return { outcome: outcome(state, state, document) };
406
+ if (meter.exhausted) return { outcome: outcome("budget", "GUEST_BUDGET", document) };
407
+ const work = meter.work - workBefore;
408
+ const admitted = admit(result, entry, start, document, meter, slot.maxStop, work);
409
+ const afterAdmission = checkpoint(request, document);
410
+ return afterAdmission ? { outcome: outcome(afterAdmission, afterAdmission, document) } : admitted;
411
+ }
412
+
413
+ // src/contracts.ts
414
+ var COMPOSITION_CONTRACT_VERSION = 1;
415
+
416
+ // src/index.ts
417
+ var DIAGNOSTIC_CONTRACT_VERSION = 1;
418
+ var MAX_TEXT2 = 1e5;
419
+ var MAX_IDENTITY = 2048;
420
+ var MAX_DIAGNOSTICS3 = 100;
421
+ var MAX_CODE = 128;
422
+ function invalid(code) {
423
+ return Object.freeze({
424
+ status: "invalid",
425
+ diagnostics: Object.freeze([
426
+ Object.freeze({ code, range: Object.freeze({ start: 0, end: 0 }) })
427
+ ])
428
+ });
429
+ }
430
+ function validDocument(value) {
431
+ return typeof value?.uri === "string" && value.uri.length > 0 && value.uri.length <= MAX_IDENTITY && typeof value.text === "string" && Number.isSafeInteger(value.version) && value.version >= 0 && typeof value.environmentGeneration === "string" && value.environmentGeneration.length > 0 && value.environmentGeneration.length <= MAX_IDENTITY;
432
+ }
433
+ function validDiagnostic(code, start, end, length) {
434
+ return /^[A-Z][A-Z0-9_]*$/u.test(code) && code.length <= MAX_CODE && Number.isSafeInteger(start) && Number.isSafeInteger(end) && start >= 0 && end >= start && end <= length;
435
+ }
436
+ function copyDiagnostic(item, length) {
437
+ const code = item?.code;
438
+ const range2 = item?.range;
439
+ const start = range2?.start;
440
+ const end = range2?.end;
441
+ if (typeof code !== "string" || typeof start !== "number" || typeof end !== "number" || !validDiagnostic(code, start, end, length))
442
+ return void 0;
443
+ return Object.freeze({ code, range: Object.freeze({ start, end }) });
444
+ }
445
+ function safeOutcome(value, length) {
446
+ const status = value?.status;
447
+ if (status === "unsupported") {
448
+ if ("diagnostics" in value) return invalid("PROVIDER_INVALID_RESULT");
449
+ return Object.freeze({ status: "unsupported" });
450
+ }
451
+ if (status !== "supported" && status !== "invalid") return invalid("PROVIDER_INVALID_RESULT");
452
+ const source = value.diagnostics;
453
+ if (!Array.isArray(source)) return invalid("PROVIDER_INVALID_RESULT");
454
+ const count = source.length;
455
+ if (!Number.isSafeInteger(count) || count < 0 || count > MAX_DIAGNOSTICS3)
456
+ return invalid("PROVIDER_INVALID_RESULT");
457
+ const diagnostics = [];
458
+ for (let index = 0; index < count; index++) {
459
+ const item = source[index];
460
+ const copied = copyDiagnostic(item, length);
461
+ if (!copied) return invalid("PROVIDER_INVALID_RESULT");
462
+ diagnostics.push(copied);
463
+ }
464
+ if (status === "supported" !== (diagnostics.length === 0))
465
+ return invalid("PROVIDER_INVALID_RESULT");
466
+ if (status === "supported")
467
+ return Object.freeze({ status: "supported", diagnostics: Object.freeze(diagnostics) });
468
+ return Object.freeze({ status: "invalid", diagnostics: Object.freeze(diagnostics) });
469
+ }
470
+ function createDiagnosticRouter(providers) {
471
+ const registered = /* @__PURE__ */ new Map();
472
+ for (const provider of providers) {
473
+ const languageId = provider?.languageId;
474
+ if (typeof languageId !== "string" || !languageId.trim())
475
+ throw new Error("Invalid language ID");
476
+ if (registered.has(languageId)) throw new Error("Duplicate language ID");
477
+ if (typeof provider.diagnose !== "function") throw new Error("Invalid provider");
478
+ registered.set(languageId, provider);
479
+ }
480
+ return Object.freeze({
481
+ diagnose(languageId, input) {
482
+ const provider = registered.get(languageId);
483
+ if (!provider) throw new Error("Unknown language ID");
484
+ const document = Object.freeze({
485
+ uri: input?.uri,
486
+ text: input?.text,
487
+ version: input?.version,
488
+ environmentGeneration: input?.environmentGeneration
489
+ });
490
+ if (!validDocument(document)) throw new Error("Invalid document identity");
491
+ let outcome2;
492
+ if (document.text.length > MAX_TEXT2) outcome2 = invalid("PROVIDER_SOURCE_LIMIT");
493
+ else {
494
+ try {
495
+ outcome2 = safeOutcome(provider.diagnose(document), document.text.length);
496
+ } catch {
497
+ outcome2 = invalid("PROVIDER_FAILURE");
498
+ }
499
+ }
500
+ return Object.freeze({ ...outcome2, languageId, document });
501
+ }
502
+ });
503
+ }
504
+ export {
505
+ COMPOSITION_CONTRACT_VERSION,
506
+ DIAGNOSTIC_CONTRACT_VERSION,
507
+ createCompositionRouter,
508
+ createDiagnosticRouter
509
+ };
package/package.json CHANGED
@@ -1,6 +1,43 @@
1
1
  {
2
2
  "name": "@kalada/provider-routing",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Experimental neutral whole-document diagnostic routing for Kalada consumers",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/surikaterna/kalada",
9
+ "directory": "packages/provider-routing"
10
+ },
11
+ "type": "module",
12
+ "sideEffects": false,
13
+ "files": [
14
+ "dist",
15
+ "README.md"
16
+ ],
17
+ "main": "./dist/index.cjs",
18
+ "module": "./dist/index.js",
19
+ "types": "./dist/index.d.ts",
20
+ "exports": {
21
+ ".": {
22
+ "import": {
23
+ "types": "./dist/index.d.ts",
24
+ "default": "./dist/index.js"
25
+ },
26
+ "require": {
27
+ "types": "./dist/index.d.cts",
28
+ "default": "./dist/index.cjs"
29
+ }
30
+ },
31
+ "./package.json": "./package.json"
32
+ },
33
+ "scripts": {
34
+ "build": "tsup src/index.ts --format esm,cjs --dts --target node22 --clean",
35
+ "typecheck": "tsc --noEmit"
36
+ },
37
+ "publishConfig": {
38
+ "access": "public"
39
+ },
40
+ "engines": {
41
+ "node": ">=22.0.0"
42
+ }
43
+ }