@apifuse/provider-sdk 2.2.0-beta.60 → 2.2.0-beta.62

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.
Files changed (85) hide show
  1. package/AUTHORING.md +17 -16
  2. package/CHANGELOG.md +8 -0
  3. package/bin/apifuse-dev.ts +5 -7
  4. package/bin/apifuse-pack-types.ts +26 -0
  5. package/bin/apifuse-perf.ts +4 -6
  6. package/bin/apifuse-record.ts +4 -7
  7. package/bin/apifuse-submit-check.ts +2 -2
  8. package/dist/define.d.ts +3 -2
  9. package/dist/define.js +1 -1
  10. package/dist/dev.js +3 -1
  11. package/dist/engine.d.ts +7 -6
  12. package/dist/engine.js +3 -4
  13. package/dist/error-resolution.js +21 -4
  14. package/dist/errors.d.ts +3 -0
  15. package/dist/errors.js +9 -0
  16. package/dist/handle-meta.d.ts +55 -0
  17. package/dist/handle-meta.js +57 -0
  18. package/dist/handle.d.ts +208 -0
  19. package/dist/handle.js +299 -0
  20. package/dist/i18n/catalog.js +21 -1
  21. package/dist/index.d.ts +7 -4
  22. package/dist/index.js +6 -3
  23. package/dist/lint.d.ts +3 -0
  24. package/dist/lint.js +372 -47
  25. package/dist/provider.d.ts +5 -3
  26. package/dist/provider.js +4 -2
  27. package/dist/runtime/handle-wordlist.d.ts +43 -0
  28. package/dist/runtime/{choice-wordlist.js → handle-wordlist.js} +127 -9
  29. package/dist/runtime/handle.d.ts +71 -0
  30. package/dist/runtime/handle.js +900 -0
  31. package/dist/runtime/http-telemetry-binding.d.ts +10 -0
  32. package/dist/runtime/http-telemetry-binding.js +152 -0
  33. package/dist/runtime/http-telemetry-guard.d.ts +6 -0
  34. package/dist/runtime/http-telemetry-guard.js +150 -0
  35. package/dist/runtime/http-telemetry.d.ts +104 -0
  36. package/dist/runtime/http-telemetry.js +190 -0
  37. package/dist/runtime/http.d.ts +31 -0
  38. package/dist/runtime/http.js +222 -146
  39. package/dist/runtime/native-network.d.ts +10 -1
  40. package/dist/runtime/native-network.js +230 -29
  41. package/dist/runtime/native-telemetry.d.ts +142 -0
  42. package/dist/runtime/native-telemetry.js +231 -0
  43. package/dist/runtime/state.d.ts +4 -1
  44. package/dist/runtime/state.js +32 -17
  45. package/dist/server/index.d.ts +3 -0
  46. package/dist/server/index.js +2 -0
  47. package/dist/server/sensitive-values.d.ts +1 -1
  48. package/dist/server/sensitive-values.js +0 -3
  49. package/dist/server/serve-implementation.d.ts +6 -11
  50. package/dist/server/serve-implementation.js +119 -52
  51. package/dist/testing/run.js +10 -7
  52. package/dist/types.d.ts +4 -107
  53. package/package.json +1 -1
  54. package/src/define.ts +4 -3
  55. package/src/dev.ts +3 -1
  56. package/src/engine.ts +5 -6
  57. package/src/error-resolution.ts +21 -4
  58. package/src/errors.ts +13 -0
  59. package/src/handle-meta.ts +93 -0
  60. package/src/handle.ts +612 -0
  61. package/src/i18n/catalog.ts +23 -1
  62. package/src/index.ts +45 -13
  63. package/src/lint.ts +437 -55
  64. package/src/provider.ts +19 -20
  65. package/src/runtime/{choice-wordlist.ts → handle-wordlist.ts} +130 -9
  66. package/src/runtime/handle.ts +1172 -0
  67. package/src/runtime/http-telemetry-binding.ts +162 -0
  68. package/src/runtime/http-telemetry-guard.ts +155 -0
  69. package/src/runtime/http-telemetry.ts +292 -0
  70. package/src/runtime/http.ts +295 -173
  71. package/src/runtime/native-network.ts +281 -33
  72. package/src/runtime/native-telemetry.ts +366 -0
  73. package/src/runtime/state.ts +44 -16
  74. package/src/server/index.ts +30 -0
  75. package/src/server/sensitive-values.ts +0 -3
  76. package/src/server/serve-implementation.ts +127 -89
  77. package/src/testing/run.ts +11 -7
  78. package/src/types.ts +4 -137
  79. package/dist/choice-token.d.ts +0 -24
  80. package/dist/choice-token.js +0 -74
  81. package/dist/runtime/choice-wordlist.d.ts +0 -9
  82. package/dist/runtime/choice.d.ts +0 -27
  83. package/dist/runtime/choice.js +0 -801
  84. package/src/choice-token.ts +0 -165
  85. package/src/runtime/choice.ts +0 -1197
@@ -118,21 +118,139 @@ wolverine womb woolworker workbasket wound wrangle wreckage wristwatch wrongdoin
118
118
  yacht yahoo yard yearbook yesterday yiddish yield yo-yo yodel yogurt yuppie zealot zebra zeppelin
119
119
  zestfully zigzagged zillion zipping zirconium zodiac zombie zookeeper zucchini
120
120
  `;
121
- export const CHOICE_WORDLIST_SIZE = 1_296;
122
- export const CHOICE_WORD_ENTROPY_BITS = Math.log2(CHOICE_WORDLIST_SIZE);
123
- export const STANDARD_CHOICE_WORD_COUNT = 4;
124
- export const HIGH_CHOICE_WORD_COUNT = 5;
125
- export const STANDARD_CHOICE_ENTROPY_BITS = STANDARD_CHOICE_WORD_COUNT * CHOICE_WORD_ENTROPY_BITS;
126
- export const HIGH_CHOICE_ENTROPY_BITS = HIGH_CHOICE_WORD_COUNT * CHOICE_WORD_ENTROPY_BITS;
121
+ export const HANDLE_WORDLIST_SIZE = 1_296;
122
+ export const HANDLE_WORD_ENTROPY_BITS = Math.log2(HANDLE_WORDLIST_SIZE);
123
+ /**
124
+ * Bound handles live under `state.forConnection(connectionId)`: another
125
+ * connection cannot address them at all, so no guessing analysis applies. The
126
+ * only requirement is uniqueness among live handles of one connection
127
+ * (1,296^2 = 1.68M keys, CAS-if-absent with retries), which two words satisfy.
128
+ */
129
+ export const BOUND_HANDLE_WORD_COUNT = 2;
130
+ /** Public handles are addressable by anyone; ADR 0006 §2 guessing numbers apply. */
131
+ export const PUBLIC_HANDLE_WORD_COUNT = 4;
132
+ /** Used when a public kind declares `strength: "high"` or its ttl exceeds one hour. */
133
+ export const HIGH_PUBLIC_HANDLE_WORD_COUNT = 5;
134
+ export const PUBLIC_HANDLE_ENTROPY_BITS = PUBLIC_HANDLE_WORD_COUNT * HANDLE_WORD_ENTROPY_BITS;
135
+ export const HIGH_PUBLIC_HANDLE_ENTROPY_BITS = HIGH_PUBLIC_HANDLE_WORD_COUNT * HANDLE_WORD_ENTROPY_BITS;
127
136
  export const EFF_SHORT_WORDLIST_2 = Object.freeze(EFF_SHORT_WORDLIST_2_TEXT.trim().split(/\s+/));
128
137
  const EFF_SHORT_WORD_SET = new Set(EFF_SHORT_WORDLIST_2);
129
- export function choiceWordAt(index) {
138
+ /**
139
+ * Words the runtime may issue. The handle grammar joins words with `-`, so the
140
+ * one hyphenated official entry (`yo-yo`) would fuse with the separator and is
141
+ * never issued. The official list itself stays embedded unchanged.
142
+ */
143
+ export const ISSUABLE_HANDLE_WORDS = Object.freeze(EFF_SHORT_WORDLIST_2.filter((word) => !word.includes("-")));
144
+ export function handleWordAt(index) {
130
145
  const word = EFF_SHORT_WORDLIST_2[index];
131
146
  if (word === undefined) {
132
- throw new RangeError(`Choice word index is out of range: ${index}`);
147
+ throw new RangeError(`Handle word index is out of range: ${index}`);
133
148
  }
134
149
  return word;
135
150
  }
136
- export function isChoiceWord(value) {
151
+ export function isHandleWord(value) {
137
152
  return EFF_SHORT_WORD_SET.has(value);
138
153
  }
154
+ /**
155
+ * Symmetric-deletion index (SymSpell, max distance 1). Two strings at
156
+ * Levenshtein distance <= 1 always share a form reachable by deleting at most
157
+ * one character from each side, so a lookup of the query and its one-deletion
158
+ * variants yields every candidate; a real distance check then removes the false
159
+ * positives (e.g. transpositions). ~10k entries, O(len) per lookup.
160
+ */
161
+ /** Shortest and longest words in the list (3 and 10 for the official list). */
162
+ export const MIN_HANDLE_WORD_LENGTH = Math.min(...EFF_SHORT_WORDLIST_2.map((word) => word.length));
163
+ export const MAX_HANDLE_WORD_LENGTH = Math.max(...EFF_SHORT_WORDLIST_2.map((word) => word.length));
164
+ const DELETION_INDEX = (() => {
165
+ const index = new Map();
166
+ const add = (form, word) => {
167
+ const bucket = index.get(form);
168
+ if (bucket === undefined)
169
+ index.set(form, [word]);
170
+ else if (!bucket.includes(word))
171
+ bucket.push(word);
172
+ };
173
+ for (const word of EFF_SHORT_WORDLIST_2) {
174
+ add(word, word);
175
+ for (const variant of oneDeletionVariants(word))
176
+ add(variant, word);
177
+ }
178
+ return index;
179
+ })();
180
+ function oneDeletionVariants(value) {
181
+ const variants = [];
182
+ for (let position = 0; position < value.length; position += 1) {
183
+ variants.push(value.slice(0, position) + value.slice(position + 1));
184
+ }
185
+ return variants;
186
+ }
187
+ /** True when Levenshtein distance between `left` and `right` is at most 1. */
188
+ function withinEditDistanceOne(left, right) {
189
+ if (left === right)
190
+ return true;
191
+ const lengthDelta = left.length - right.length;
192
+ if (lengthDelta > 1 || lengthDelta < -1)
193
+ return false;
194
+ if (lengthDelta === 0) {
195
+ let mismatches = 0;
196
+ for (let position = 0; position < left.length; position += 1) {
197
+ if (left[position] !== right[position]) {
198
+ mismatches += 1;
199
+ if (mismatches > 1)
200
+ return false;
201
+ }
202
+ }
203
+ return true;
204
+ }
205
+ const longer = lengthDelta > 0 ? left : right;
206
+ const shorter = lengthDelta > 0 ? right : left;
207
+ let longIndex = 0;
208
+ let shortIndex = 0;
209
+ let skipped = false;
210
+ while (shortIndex < shorter.length) {
211
+ if (longer[longIndex] === shorter[shortIndex]) {
212
+ longIndex += 1;
213
+ shortIndex += 1;
214
+ continue;
215
+ }
216
+ if (skipped)
217
+ return false;
218
+ skipped = true;
219
+ longIndex += 1;
220
+ }
221
+ return true;
222
+ }
223
+ /**
224
+ * Resolves one handle segment to a wordlist entry: the exact word, or the
225
+ * unique word at edit distance <= `maxDistance`. Returns null when no word or
226
+ * more than one word qualifies. The official list has pairwise edit distance
227
+ * >= 3, so at distance 1 every non-null answer is unique; the uniqueness check
228
+ * is kept as a guard so a wordlist change cannot silently make normalization
229
+ * map one input onto two different canonical handles.
230
+ */
231
+ export function findHandleWordWithinDistance(segment, maxDistance) {
232
+ if (segment.length === 0)
233
+ return null;
234
+ if (EFF_SHORT_WORD_SET.has(segment))
235
+ return segment;
236
+ if (maxDistance !== 1)
237
+ return null;
238
+ // A segment that is more than one character shorter or longer than every
239
+ // word cannot be within distance 1 of any word. Rejecting it here also bounds
240
+ // the variant generation below (which is O(len²) in memory) against
241
+ // client-supplied garbage of arbitrary length.
242
+ if (segment.length < MIN_HANDLE_WORD_LENGTH - maxDistance ||
243
+ segment.length > MAX_HANDLE_WORD_LENGTH + maxDistance) {
244
+ return null;
245
+ }
246
+ const matches = new Set();
247
+ for (const form of [segment, ...oneDeletionVariants(segment)]) {
248
+ for (const candidate of DELETION_INDEX.get(form) ?? []) {
249
+ if (withinEditDistanceOne(segment, candidate))
250
+ matches.add(candidate);
251
+ }
252
+ }
253
+ if (matches.size !== 1)
254
+ return null;
255
+ return matches.values().next().value ?? null;
256
+ }
@@ -0,0 +1,71 @@
1
+ import { type HandleContext, type HandleKind, type HandleStatus, type HandleTelemetryEvent } from "../handle.js";
2
+ import type { ProviderRequestContext, ProviderRuntimeState } from "../types.js";
3
+ declare const RECORD_VERSION = 1;
4
+ export interface CreateHandleContextOptions {
5
+ readonly providerId: string;
6
+ readonly request?: ProviderRequestContext;
7
+ readonly state?: ProviderRuntimeState;
8
+ /** Clock used for record timestamps and TTL math; defaults to `Date.now`. */
9
+ readonly nowMs?: () => number;
10
+ /** Receives allowlisted metadata only; handle values, data, and results are never included. */
11
+ readonly onTelemetry?: (event: HandleTelemetryEvent) => void;
12
+ /**
13
+ * Lease held (and renewed) by a `committing` claim while upstream work runs.
14
+ * Defaults to 5 minutes; exposed so tests can exercise renewal quickly.
15
+ */
16
+ readonly commitLeaseMs?: number;
17
+ }
18
+ export interface CreateTestHandleContextOptions {
19
+ readonly providerId?: string;
20
+ readonly state?: ProviderRuntimeState;
21
+ readonly request?: ProviderRequestContext;
22
+ readonly nowMs?: () => number;
23
+ readonly onTelemetry?: (event: HandleTelemetryEvent) => void;
24
+ }
25
+ /**
26
+ * Persisted record shape (spec §6). `expires_at_ms` is the logical expiry the
27
+ * caller sees (sliding for drafts); `max_expires_at_ms` is the hard lifetime.
28
+ * Records are stored past their logical expiry for a grace window so an expired
29
+ * handle can be told apart from an unknown one (`HANDLE_EXPIRED` vs
30
+ * `HANDLE_NOT_FOUND`); after the grace window they are simply gone.
31
+ */
32
+ export type StoredHandleRecord = {
33
+ readonly v: typeof RECORD_VERSION;
34
+ readonly kind: string;
35
+ readonly type: "cursor" | "draft";
36
+ /** `discarded` is an internal tombstone written by `discard`; reads treat it as absent. */
37
+ readonly status: HandleStatus | "discarded";
38
+ readonly data: unknown;
39
+ readonly result?: unknown;
40
+ readonly created_at_ms: number;
41
+ readonly expires_at_ms: number;
42
+ readonly max_expires_at_ms: number;
43
+ readonly committed_at_ms?: number;
44
+ /** Owner token of a `committing` claim; lets the claimant recognise its record after an ambiguous write. */
45
+ readonly claim_id?: string;
46
+ };
47
+ export type HandleNormalizationClass = "case" | "whitespace" | "separator" | "punctuation" | "typo";
48
+ export interface NormalizedHandle {
49
+ readonly canonical: string;
50
+ readonly words: readonly string[];
51
+ readonly normalization: readonly HandleNormalizationClass[];
52
+ }
53
+ /**
54
+ * Tolerant parse of a raw handle string into its canonical form. Throws
55
+ * `HANDLE_KIND_MISMATCH` when the string does not start with the kind name and
56
+ * `HANDLE_INVALID` when the body cannot be resolved to the kind's word count.
57
+ */
58
+ export declare function normalizeHandle(kind: HandleKind, raw: string): {
59
+ canonical: string;
60
+ words: string[];
61
+ normalization: string[];
62
+ };
63
+ export declare function formatNormalizationClasses(classes: readonly string[]): string;
64
+ export declare function createHandleContext(options: CreateHandleContextOptions): HandleContext;
65
+ /**
66
+ * Handle context for unit tests: memory state, a fixed connection, and an
67
+ * optional fake clock. Share one `state` between two contexts with different
68
+ * `request.connectionId`s to exercise bound isolation.
69
+ */
70
+ export declare function createTestHandleContext(options?: CreateTestHandleContextOptions): HandleContext;
71
+ export {};