@openlimiter/core 0.1.0 → 0.4.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.
@@ -0,0 +1,304 @@
1
+ /**
2
+ * The connection lifecycle, as one pure function and two human tables.
3
+ *
4
+ * A parser is not a connection. The product's central honesty problem is that
5
+ * an implemented parser has been able to look like a live account, so the state
6
+ * a connection is in has to be a value the whole product agrees on rather than
7
+ * a sentence each surface invents. Everything here is data and arithmetic: no
8
+ * clock, no network, no storage, so a surface can replay a connection's whole
9
+ * history and get the same answer every time.
10
+ */
11
+ export const CONNECTION_STATES = [
12
+ "NOT_CONFIGURED",
13
+ "DETECTED",
14
+ "NEEDS_AUTH",
15
+ "READY_TO_ENABLE",
16
+ "CONNECTING",
17
+ "CONNECTED",
18
+ "DEGRADED",
19
+ "STALE",
20
+ "AUTH_EXPIRED",
21
+ "IMPORT_ONLY",
22
+ "MANUAL",
23
+ "UNSUPPORTED",
24
+ "ERROR"
25
+ ];
26
+ /**
27
+ * How many consecutive network failures turn a degraded connection into a
28
+ * broken one. One timeout is a bad moment; three in a row is a fault worth
29
+ * showing a person.
30
+ */
31
+ export const NETWORK_FAILURE_ERROR_THRESHOLD = 3;
32
+ /**
33
+ * States that describe something the product declared rather than something it
34
+ * attempted. A response cannot arrive in any of them, so a stray one is ignored
35
+ * instead of quietly promoting an import only source to Connected.
36
+ */
37
+ const declaredStates = new Set([
38
+ "NOT_CONFIGURED",
39
+ "IMPORT_ONLY",
40
+ "MANUAL",
41
+ "UNSUPPORTED"
42
+ ]);
43
+ function afterResponse(state, status, parsed, context) {
44
+ if (declaredStates.has(state))
45
+ return state;
46
+ if (status === 401 || status === 403) {
47
+ return context.everConnected ? "AUTH_EXPIRED" : "NEEDS_AUTH";
48
+ }
49
+ if (status === 429 || (status >= 500 && status <= 599))
50
+ return "DEGRADED";
51
+ if (status < 200 || status > 299)
52
+ return "ERROR";
53
+ /*
54
+ * The response arrived and was refused by our own parser. The connection is
55
+ * healthy and our understanding of it is not, which is the exact failure the
56
+ * Claude statusline contract mismatch was, so it reads as an error rather
57
+ * than as a provider problem.
58
+ */
59
+ return parsed ? "CONNECTED" : "ERROR";
60
+ }
61
+ /**
62
+ * Advance a connection by one event.
63
+ *
64
+ * Total and pure: every state, event and context triple has an answer, and an
65
+ * event that makes no sense where it arrived leaves the state exactly as it was
66
+ * rather than inventing a transition. The context is required rather than
67
+ * defaulted, because a caller that forgets it would silently get the wrong
68
+ * authentication answer, and a compiler error is cheaper than that.
69
+ */
70
+ export function nextConnectionState(state, event, context) {
71
+ switch (event.kind) {
72
+ case "disconnected":
73
+ return "NOT_CONFIGURED";
74
+ case "declared_import_only":
75
+ return "IMPORT_ONLY";
76
+ case "declared_manual":
77
+ return "MANUAL";
78
+ case "declared_unsupported":
79
+ return "UNSUPPORTED";
80
+ case "detected":
81
+ return state === "NOT_CONFIGURED" ? "DETECTED" : state;
82
+ case "credential_required":
83
+ return state === "NOT_CONFIGURED" ||
84
+ state === "DETECTED" ||
85
+ state === "READY_TO_ENABLE"
86
+ ? "NEEDS_AUTH"
87
+ : state;
88
+ case "credential_stored":
89
+ return state === "NEEDS_AUTH" ||
90
+ state === "AUTH_EXPIRED" ||
91
+ state === "DETECTED"
92
+ ? "READY_TO_ENABLE"
93
+ : state;
94
+ case "enable_requested":
95
+ /*
96
+ * A connection missing its credential cannot start collecting no matter
97
+ * how many times the button is pressed, so those two states hold.
98
+ */
99
+ return state === "NEEDS_AUTH" ||
100
+ state === "AUTH_EXPIRED" ||
101
+ declaredStates.has(state)
102
+ ? state
103
+ : "CONNECTING";
104
+ case "http_response":
105
+ return afterResponse(state, event.status, event.parsed, context);
106
+ case "local_read":
107
+ if (declaredStates.has(state))
108
+ return state;
109
+ return event.parsed ? "CONNECTED" : "ERROR";
110
+ case "network_failure":
111
+ if (declaredStates.has(state))
112
+ return state;
113
+ return event.consecutive >= NETWORK_FAILURE_ERROR_THRESHOLD
114
+ ? "ERROR"
115
+ : "DEGRADED";
116
+ case "expiry_passed":
117
+ /*
118
+ * Stale means the last reading is too old to trust, which only makes
119
+ * sense once a reading exists. Nothing else ages.
120
+ */
121
+ return state === "CONNECTED" || state === "DEGRADED" ? "STALE" : state;
122
+ default: {
123
+ const unreachable = event;
124
+ return unreachable;
125
+ }
126
+ }
127
+ }
128
+ /** One sentence per state, written for a person rather than for a log. */
129
+ export const connectionSentence = {
130
+ NOT_CONFIGURED: "Not connected.",
131
+ DETECTED: "Found on this machine but not collecting yet.",
132
+ NEEDS_AUTH: "Waiting for a credential.",
133
+ READY_TO_ENABLE: "Ready to start collecting.",
134
+ CONNECTING: "Connecting.",
135
+ CONNECTED: "Connected and collecting.",
136
+ DEGRADED: "The provider is refusing reads for now.",
137
+ STALE: "The last reading is older than it should be.",
138
+ AUTH_EXPIRED: "The stored credential stopped working.",
139
+ IMPORT_ONLY: "Reads only what you import. Nothing is collected automatically.",
140
+ MANUAL: "Shows the plan you entered yourself.",
141
+ UNSUPPORTED: "No safe automatic source exists for this product yet.",
142
+ ERROR: "This connection failed and needs a look."
143
+ };
144
+ /** The one thing to do next, in the words of the button that does it. */
145
+ export const connectionNextAction = {
146
+ NOT_CONFIGURED: "Connect",
147
+ DETECTED: "Enable local integration",
148
+ NEEDS_AUTH: "Add credential",
149
+ READY_TO_ENABLE: "Enable",
150
+ CONNECTING: "Wait",
151
+ CONNECTED: "Refresh now",
152
+ DEGRADED: "Wait for the next retry",
153
+ STALE: "Refresh now",
154
+ AUTH_EXPIRED: "Reconnect",
155
+ IMPORT_ONLY: "Import a payload",
156
+ MANUAL: "Edit plan",
157
+ UNSUPPORTED: "Add manual plan",
158
+ ERROR: "View diagnostics"
159
+ };
160
+ /** The five providers on the Connect and See catalogue surface. */
161
+ export const CATALOGUE_PROVIDER_IDS = [
162
+ "claude",
163
+ "openrouter",
164
+ "codex",
165
+ "antigravity",
166
+ "opencode"
167
+ ];
168
+ const supported = {
169
+ mode: "automatic",
170
+ maturity: "supported",
171
+ label: "Supported"
172
+ };
173
+ const eventDriven = {
174
+ mode: "event_driven",
175
+ maturity: "supported",
176
+ label: "Event driven"
177
+ };
178
+ const experimental = {
179
+ mode: "automatic",
180
+ maturity: "experimental",
181
+ label: "Experimental"
182
+ };
183
+ const manual = {
184
+ mode: "manual",
185
+ maturity: "supported",
186
+ label: "Manual"
187
+ };
188
+ const manualExperimental = {
189
+ mode: "manual",
190
+ maturity: "experimental",
191
+ label: "Manual experimental"
192
+ };
193
+ const catalogueCapabilities = {
194
+ claude: { windows: eventDriven, macos: eventDriven, linux: eventDriven },
195
+ openrouter: { windows: supported, macos: supported, linux: supported },
196
+ codex: { windows: supported, macos: supported, linux: supported },
197
+ antigravity: { windows: experimental, macos: manual, linux: manual },
198
+ opencode: {
199
+ windows: manualExperimental,
200
+ macos: manualExperimental,
201
+ linux: manualExperimental
202
+ }
203
+ };
204
+ function isCatalogueProviderId(value) {
205
+ return typeof value === "string" &&
206
+ CATALOGUE_PROVIDER_IDS.includes(value);
207
+ }
208
+ function isCatalogueAuthMode(value) {
209
+ return value === "existing_local_cli" || value === "api_key" || value === "manual";
210
+ }
211
+ function catalogueState(value) {
212
+ return typeof value === "string" &&
213
+ CONNECTION_STATES.includes(value)
214
+ ? value
215
+ : "NOT_CONFIGURED";
216
+ }
217
+ function actionFor(providerId, state) {
218
+ if (providerId === "codex" && (state === "NEEDS_AUTH" || state === "AUTH_EXPIRED")) {
219
+ return "Run codex login";
220
+ }
221
+ return connectionNextAction[state];
222
+ }
223
+ /**
224
+ * Join generated provider facts to the closed connection state model.
225
+ *
226
+ * The generated document supplies identity, display name, auth mode and the
227
+ * platform set. Product capability maturity is the frozen overlay above. No
228
+ * site or local application is inspected at runtime.
229
+ */
230
+ export function queryProviderCatalogue(generated, states = {}) {
231
+ if (!Array.isArray(generated.providers))
232
+ return [];
233
+ const found = new Map();
234
+ for (const candidate of generated.providers) {
235
+ if (candidate === null || typeof candidate !== "object")
236
+ continue;
237
+ const entry = candidate;
238
+ const providerId = entry.honesty?.connectorId;
239
+ if (!isCatalogueProviderId(providerId))
240
+ continue;
241
+ found.set(providerId, entry);
242
+ }
243
+ const catalogue = [];
244
+ for (const providerId of CATALOGUE_PROVIDER_IDS) {
245
+ const source = found.get(providerId);
246
+ if (source === undefined || typeof source.displayName !== "string")
247
+ continue;
248
+ if (!Array.isArray(source.authModes) || !isCatalogueAuthMode(source.authModes[0]))
249
+ continue;
250
+ const platforms = source.platforms;
251
+ if (!Array.isArray(platforms))
252
+ continue;
253
+ const hasEveryPlatform = ["windows", "macos", "linux"]
254
+ .every((platform) => platforms.includes(platform));
255
+ if (!hasEveryPlatform)
256
+ continue;
257
+ const connectionState = catalogueState(states[providerId]);
258
+ catalogue.push({
259
+ providerId,
260
+ displayName: source.displayName,
261
+ connectionState,
262
+ capabilities: catalogueCapabilities[providerId],
263
+ authMode: source.authModes[0],
264
+ action: actionFor(providerId, connectionState)
265
+ });
266
+ }
267
+ return catalogue;
268
+ }
269
+ /**
270
+ * The connections surface showing connectable providers and planned products.
271
+ *
272
+ * The product catalogue presents every supported or planned provider in document
273
+ * order after the connectable set. Planned rows carry no connection state because
274
+ * no background collector or credential exists for them.
275
+ */
276
+ export function queryCatalogueRows(generated, states = {}) {
277
+ const connectableRows = queryProviderCatalogue(generated, states).map((entry) => ({
278
+ availability: "connectable",
279
+ ...entry
280
+ }));
281
+ if (!Array.isArray(generated.providers))
282
+ return connectableRows;
283
+ const plannedRows = [];
284
+ for (const candidate of generated.providers) {
285
+ if (candidate === null || typeof candidate !== "object")
286
+ continue;
287
+ const entry = candidate;
288
+ const connectorId = entry.honesty?.connectorId;
289
+ if (connectorId === "manual")
290
+ continue;
291
+ if (isCatalogueProviderId(connectorId))
292
+ continue;
293
+ if (typeof entry.id !== "string" || typeof entry.displayName !== "string")
294
+ continue;
295
+ plannedRows.push({
296
+ availability: "planned",
297
+ specId: entry.id,
298
+ displayName: entry.displayName,
299
+ action: "Planned"
300
+ });
301
+ }
302
+ return [...connectableRows, ...plannedRows];
303
+ }
304
+ //# sourceMappingURL=connection-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection-state.js","sourceRoot":"","sources":["../src/connection-state.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ import type { ConnectorResult, ProviderCode } from "./types.js";
2
+ /**
3
+ * Why a provider has nothing to show, in our own words.
4
+ *
5
+ * SECURITY. Not one character of a provider's own text ever reaches a human
6
+ * surface. A connector can hand back a payload full of anything at all, and the
7
+ * only thing that survives contact with this module is one of the four codes
8
+ * below, chosen by our code from what the connector and the normalizer can
9
+ * actually tell apart today. The sentences are constants written here, so a
10
+ * failure line on a card, in a window, or in a terminal is the same fixed
11
+ * string whatever the provider said.
12
+ *
13
+ * The four categories are exactly the four distinguishable outcomes:
14
+ *
15
+ * NOT_CONFIGURED the connector reported it has nothing set up
16
+ * SESSION_EXPIRED the connector reported its credential no longer works
17
+ * PAYLOAD_UNREADABLE the connector could not recognise what it was given
18
+ * VALIDATION_REJECTED the connector recognised it, the normalizer refused it
19
+ *
20
+ * Nothing finer is claimed, because nothing finer can be told apart without
21
+ * reading provider text, which is the one thing this module exists to prevent.
22
+ */
23
+ export declare const FAILURE_CATEGORIES: readonly ["NOT_CONFIGURED", "SESSION_EXPIRED", "PAYLOAD_UNREADABLE", "VALIDATION_REJECTED"];
24
+ export type FailureCategory = (typeof FAILURE_CATEGORIES)[number];
25
+ /**
26
+ * The one sentence each category is ever shown as.
27
+ *
28
+ * Short enough to sit on one line of a card, plain enough to need no glossary,
29
+ * and fixed, so no surface can invent a variation of its own.
30
+ */
31
+ export declare const failureSentence: Record<FailureCategory, string>;
32
+ /** A provider and the one reason it has nothing readable. */
33
+ export interface ProviderFailure {
34
+ provider: ProviderCode;
35
+ category: FailureCategory;
36
+ }
37
+ /**
38
+ * Map a connector's own refusal onto the shared vocabulary.
39
+ *
40
+ * The connector contract already narrows every refusal to three enum values, so
41
+ * this is a total function over a closed set and can never see a surprise.
42
+ */
43
+ export declare function failureFromConnectorReason(reason: Extract<ConnectorResult, {
44
+ ok: false;
45
+ }>["reason"]): FailureCategory;
46
+ export declare function dedupeFailures(failures: readonly ProviderFailure[]): readonly ProviderFailure[];
47
+ //# sourceMappingURL=failures.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"failures.d.ts","sourceRoot":"","sources":["../src/failures.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAEhE;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,kBAAkB,6FAKrB,CAAC;AAEX,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAC;AAElE;;;;;GAKG;AACH,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,eAAe,EAAE,MAAM,CAK3D,CAAC;AAEF,6DAA6D;AAC7D,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,YAAY,CAAC;IACvB,QAAQ,EAAE,eAAe,CAAC;CAC3B;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,OAAO,CAAC,eAAe,EAAE;IAAE,EAAE,EAAE,KAAK,CAAA;CAAE,CAAC,CAAC,QAAQ,CAAC,GACxD,eAAe,CAIjB;AAgBD,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,SAAS,eAAe,EAAE,GACnC,SAAS,eAAe,EAAE,CAS5B"}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Why a provider has nothing to show, in our own words.
3
+ *
4
+ * SECURITY. Not one character of a provider's own text ever reaches a human
5
+ * surface. A connector can hand back a payload full of anything at all, and the
6
+ * only thing that survives contact with this module is one of the four codes
7
+ * below, chosen by our code from what the connector and the normalizer can
8
+ * actually tell apart today. The sentences are constants written here, so a
9
+ * failure line on a card, in a window, or in a terminal is the same fixed
10
+ * string whatever the provider said.
11
+ *
12
+ * The four categories are exactly the four distinguishable outcomes:
13
+ *
14
+ * NOT_CONFIGURED the connector reported it has nothing set up
15
+ * SESSION_EXPIRED the connector reported its credential no longer works
16
+ * PAYLOAD_UNREADABLE the connector could not recognise what it was given
17
+ * VALIDATION_REJECTED the connector recognised it, the normalizer refused it
18
+ *
19
+ * Nothing finer is claimed, because nothing finer can be told apart without
20
+ * reading provider text, which is the one thing this module exists to prevent.
21
+ */
22
+ export const FAILURE_CATEGORIES = [
23
+ "NOT_CONFIGURED",
24
+ "SESSION_EXPIRED",
25
+ "PAYLOAD_UNREADABLE",
26
+ "VALIDATION_REJECTED"
27
+ ];
28
+ /**
29
+ * The one sentence each category is ever shown as.
30
+ *
31
+ * Short enough to sit on one line of a card, plain enough to need no glossary,
32
+ * and fixed, so no surface can invent a variation of its own.
33
+ */
34
+ export const failureSentence = {
35
+ NOT_CONFIGURED: "Not set up yet, so nothing was read.",
36
+ SESSION_EXPIRED: "Session expired.",
37
+ PAYLOAD_UNREADABLE: "Payload could not be read, so nothing is claimed.",
38
+ VALIDATION_REJECTED: "Payload failed validation, kept the last good reading."
39
+ };
40
+ /**
41
+ * Map a connector's own refusal onto the shared vocabulary.
42
+ *
43
+ * The connector contract already narrows every refusal to three enum values, so
44
+ * this is a total function over a closed set and can never see a surprise.
45
+ */
46
+ export function failureFromConnectorReason(reason) {
47
+ if (reason === "not_configured")
48
+ return "NOT_CONFIGURED";
49
+ if (reason === "unavailable")
50
+ return "SESSION_EXPIRED";
51
+ return "PAYLOAD_UNREADABLE";
52
+ }
53
+ /**
54
+ * Collapse a list of failures to one per provider, worst first.
55
+ *
56
+ * A provider can only ever show one line, so when two sources disagree the more
57
+ * specific answer wins: a reading that was rejected says more than a connector
58
+ * that never produced one.
59
+ */
60
+ const severity = {
61
+ NOT_CONFIGURED: 0,
62
+ PAYLOAD_UNREADABLE: 1,
63
+ SESSION_EXPIRED: 2,
64
+ VALIDATION_REJECTED: 3
65
+ };
66
+ export function dedupeFailures(failures) {
67
+ const byProvider = new Map();
68
+ for (const failure of failures) {
69
+ const held = byProvider.get(failure.provider);
70
+ if (held === undefined || severity[failure.category] > severity[held.category]) {
71
+ byProvider.set(failure.provider, failure);
72
+ }
73
+ }
74
+ return [...byProvider.values()];
75
+ }
76
+ //# sourceMappingURL=failures.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"failures.js","sourceRoot":"","sources":["../src/failures.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,gBAAgB;IAChB,iBAAiB;IACjB,oBAAoB;IACpB,qBAAqB;CACb,CAAC;AAIX;;;;;GAKG;AACH,MAAM,CAAC,MAAM,eAAe,GAAoC;IAC9D,cAAc,EAAE,sCAAsC;IACtD,eAAe,EAAE,kBAAkB;IACnC,kBAAkB,EAAE,mDAAmD;IACvE,mBAAmB,EAAE,wDAAwD;CAC9E,CAAC;AAQF;;;;;GAKG;AACH,MAAM,UAAU,0BAA0B,CACxC,MAAyD;IAEzD,IAAI,MAAM,KAAK,gBAAgB;QAAE,OAAO,gBAAgB,CAAC;IACzD,IAAI,MAAM,KAAK,aAAa;QAAE,OAAO,iBAAiB,CAAC;IACvD,OAAO,oBAAoB,CAAC;AAC9B,CAAC;AAED;;;;;;GAMG;AACH,MAAM,QAAQ,GAAoC;IAChD,cAAc,EAAE,CAAC;IACjB,kBAAkB,EAAE,CAAC;IACrB,eAAe,EAAE,CAAC;IAClB,mBAAmB,EAAE,CAAC;CACvB,CAAC;AAEF,MAAM,UAAU,cAAc,CAC5B,QAAoC;IAEpC,MAAM,UAAU,GAAG,IAAI,GAAG,EAAiC,CAAC;IAC5D,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,MAAM,IAAI,GAAG,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAC9C,IAAI,IAAI,KAAK,SAAS,IAAI,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC/E,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC;IACD,OAAO,CAAC,GAAG,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC;AAClC,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1,9 +1,13 @@
1
1
  export * from "./cache.js";
2
+ export * from "./collection.js";
3
+ export * from "./connection-state.js";
4
+ export * from "./failures.js";
2
5
  export * from "./forecast.js";
3
6
  export * from "./format.js";
4
7
  export * from "./freshness.js";
5
8
  export * from "./merge.js";
6
9
  export * from "./normalizer.js";
7
10
  export * from "./policy.js";
11
+ export * from "./schedule.js";
8
12
  export * from "./types.js";
9
13
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,cAAc,uBAAuB,CAAC;AACtC,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -1,9 +1,13 @@
1
1
  export * from "./cache.js";
2
+ export * from "./collection.js";
3
+ export * from "./connection-state.js";
4
+ export * from "./failures.js";
2
5
  export * from "./forecast.js";
3
6
  export * from "./format.js";
4
7
  export * from "./freshness.js";
5
8
  export * from "./merge.js";
6
9
  export * from "./normalizer.js";
7
10
  export * from "./policy.js";
11
+ export * from "./schedule.js";
8
12
  export * from "./types.js";
9
13
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,cAAc,uBAAuB,CAAC;AACtC,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC"}
package/dist/merge.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { Snapshot } from "./types.js";
2
2
  /** Hard ceiling on how many snapshots one cache file may carry. */
3
3
  export declare const MAX_CACHE_ENTRIES = 64;
4
+ export declare function snapshotIdentity(snapshot: Snapshot): string;
4
5
  /**
5
6
  * Combine cached snapshots with freshly observed ones.
6
7
  *
@@ -1 +1 @@
1
- {"version":3,"file":"merge.d.ts","sourceRoot":"","sources":["../src/merge.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,mEAAmE;AACnE,eAAO,MAAM,iBAAiB,KAAK,CAAC;AAqBpC;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,SAAS,QAAQ,EAAE,EAC7B,QAAQ,EAAE,SAAS,QAAQ,EAAE,EAC7B,KAAK,SAAoB,GACxB,QAAQ,EAAE,CAUZ"}
1
+ {"version":3,"file":"merge.d.ts","sourceRoot":"","sources":["../src/merge.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,mEAAmE;AACnE,eAAO,MAAM,iBAAiB,KAAK,CAAC;AAYpC,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAG3D;AAeD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,SAAS,QAAQ,EAAE,EAC7B,QAAQ,EAAE,SAAS,QAAQ,EAAE,EAC7B,KAAK,SAAoB,GACxB,QAAQ,EAAE,CAUZ"}
package/dist/merge.js CHANGED
@@ -1,12 +1,20 @@
1
1
  /** Hard ceiling on how many snapshots one cache file may carry. */
2
2
  export const MAX_CACHE_ENTRIES = 64;
3
3
  /*
4
- * A provider code and a meter name are both closed uppercase identifiers with
5
- * no spaces, so joining them with one space cannot collide.
4
+ * A provider code, a meter name and an account id are all closed identifiers
5
+ * with no spaces, so joining them with one space cannot collide.
6
+ *
7
+ * The account id joins the key only when the row states one. A row without an
8
+ * account therefore keys exactly as it always did, which is what lets a cache
9
+ * written before accounts existed merge with one written after without any row
10
+ * changing identity or duplicating itself. Two rows for the same meter under
11
+ * different accounts are two rows, which is the whole point of the field.
6
12
  */
7
- function identity(snapshot) {
8
- return snapshot.provider + " " + snapshot.meter;
13
+ export function snapshotIdentity(snapshot) {
14
+ const base = snapshot.provider + " " + snapshot.meter;
15
+ return snapshot.accountId === undefined ? base : base + " " + snapshot.accountId;
9
16
  }
17
+ const identity = snapshotIdentity;
10
18
  function compareIdentity(left, right) {
11
19
  const leftKey = identity(left);
12
20
  const rightKey = identity(right);
package/dist/merge.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"merge.js","sourceRoot":"","sources":["../src/merge.ts"],"names":[],"mappings":"AAEA,mEAAmE;AACnE,MAAM,CAAC,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAEpC;;;GAGG;AACH,SAAS,QAAQ,CAAC,QAAkB;IAClC,OAAO,QAAQ,CAAC,QAAQ,GAAG,GAAG,GAAG,QAAQ,CAAC,KAAK,CAAC;AAClD,CAAC;AAED,SAAS,eAAe,CAAC,IAAc,EAAE,KAAe;IACtD,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC/B,MAAM,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IACjC,OAAO,OAAO,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC9D,CAAC;AAED,SAAS,oBAAoB,CAAC,QAAkB;IAC9C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;IAC/C,OAAO,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;AAC9C,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAC5B,QAA6B,EAC7B,QAA6B,EAC7B,KAAK,GAAG,iBAAiB;IAEzB,MAAM,UAAU,GAAG,IAAI,GAAG,EAAoB,CAAC;IAC/C,KAAK,MAAM,QAAQ,IAAI,QAAQ;QAAE,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,CAAC;IAC9E,KAAK,MAAM,QAAQ,IAAI,QAAQ;QAAE,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,CAAC;IAC9E,MAAM,MAAM,GAAG,CAAC,GAAG,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC;IACxC,IAAI,MAAM,CAAC,MAAM,IAAI,KAAK;QAAE,OAAO,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;IAChE,OAAO,MAAM;SACV,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,oBAAoB,CAAC,KAAK,CAAC,GAAG,oBAAoB,CAAC,IAAI,CAAC,CAAC;SAC/E,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC;SACf,IAAI,CAAC,eAAe,CAAC,CAAC;AAC3B,CAAC"}
1
+ {"version":3,"file":"merge.js","sourceRoot":"","sources":["../src/merge.ts"],"names":[],"mappings":"AAEA,mEAAmE;AACnE,MAAM,CAAC,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAEpC;;;;;;;;;GASG;AACH,MAAM,UAAU,gBAAgB,CAAC,QAAkB;IACjD,MAAM,IAAI,GAAG,QAAQ,CAAC,QAAQ,GAAG,GAAG,GAAG,QAAQ,CAAC,KAAK,CAAC;IACtD,OAAO,QAAQ,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG,GAAG,GAAG,QAAQ,CAAC,SAAS,CAAC;AACnF,CAAC;AAED,MAAM,QAAQ,GAAG,gBAAgB,CAAC;AAElC,SAAS,eAAe,CAAC,IAAc,EAAE,KAAe;IACtD,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC/B,MAAM,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IACjC,OAAO,OAAO,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC9D,CAAC;AAED,SAAS,oBAAoB,CAAC,QAAkB;IAC9C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;IAC/C,OAAO,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;AAC9C,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAC5B,QAA6B,EAC7B,QAA6B,EAC7B,KAAK,GAAG,iBAAiB;IAEzB,MAAM,UAAU,GAAG,IAAI,GAAG,EAAoB,CAAC;IAC/C,KAAK,MAAM,QAAQ,IAAI,QAAQ;QAAE,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,CAAC;IAC9E,KAAK,MAAM,QAAQ,IAAI,QAAQ;QAAE,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,CAAC;IAC9E,MAAM,MAAM,GAAG,CAAC,GAAG,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC;IACxC,IAAI,MAAM,CAAC,MAAM,IAAI,KAAK;QAAE,OAAO,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;IAChE,OAAO,MAAM;SACV,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,oBAAoB,CAAC,KAAK,CAAC,GAAG,oBAAoB,CAAC,IAAI,CAAC,CAAC;SAC/E,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC;SACf,IAAI,CAAC,eAAe,CAAC,CAAC;AAC3B,CAAC"}
@@ -1,5 +1,25 @@
1
- import { type RawMeter, type Snapshot } from "./types.js";
1
+ import { type ProviderCode, type RawMeter, type Snapshot } from "./types.js";
2
2
  export declare function normalizeMeter(raw: RawMeter): Snapshot | null;
3
3
  export declare function normalizeMeters(rawMeters: readonly RawMeter[]): Snapshot[];
4
+ /**
5
+ * What survived validation, and who lost a reading to it.
6
+ *
7
+ * `normalizeMeters` above answers the only question the engine ever asks: what
8
+ * may be believed. A human surface has a second question, which is why a card
9
+ * is empty when a document clearly mentioned that provider, and this is the
10
+ * seam that answers it without changing the first.
11
+ *
12
+ * A rejected reading is only attributed when its provider code was itself
13
+ * valid. Anything else is counted and left anonymous rather than blamed on a
14
+ * provider that a hostile document happened to name.
15
+ */
16
+ export interface NormalizeReport {
17
+ snapshots: Snapshot[];
18
+ /** Providers that had at least one reading refused. Never a guess. */
19
+ rejected: readonly ProviderCode[];
20
+ /** How many readings were dropped in total, attributable or not. */
21
+ dropped: number;
22
+ }
23
+ export declare function normalizeMetersReport(rawMeters: readonly RawMeter[]): NormalizeReport;
4
24
  export declare function canonicalJson(value: unknown): string;
5
25
  //# sourceMappingURL=normalizer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"normalizer.d.ts","sourceRoot":"","sources":["../src/normalizer.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,QAAQ,EACb,KAAK,QAAQ,EAKd,MAAM,YAAY,CAAC;AAuEpB,wBAAgB,cAAc,CAAC,GAAG,EAAE,QAAQ,GAAG,QAAQ,GAAG,IAAI,CAmC7D;AAED,wBAAgB,eAAe,CAAC,SAAS,EAAE,SAAS,QAAQ,EAAE,GAAG,QAAQ,EAAE,CAK1E;AAUD,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAEpD"}
1
+ {"version":3,"file":"normalizer.d.ts","sourceRoot":"","sources":["../src/normalizer.ts"],"names":[],"mappings":"AAAA,OAAO,EAQL,KAAK,YAAY,EACjB,KAAK,QAAQ,EACb,KAAK,QAAQ,EAUd,MAAM,YAAY,CAAC;AAyIpB,wBAAgB,cAAc,CAAC,GAAG,EAAE,QAAQ,GAAG,QAAQ,GAAG,IAAI,CA0C7D;AAED,wBAAgB,eAAe,CAAC,SAAS,EAAE,SAAS,QAAQ,EAAE,GAAG,QAAQ,EAAE,CAK1E;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,sEAAsE;IACtE,QAAQ,EAAE,SAAS,YAAY,EAAE,CAAC;IAClC,oEAAoE;IACpE,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,SAAS,QAAQ,EAAE,GAC7B,eAAe,CAgBjB;AAUD,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAEpD"}