@objectstack/metadata 17.0.0-rc.3 → 17.0.0-rc.5
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/CHANGELOG.md +801 -7
- package/dist/errors.cjs +61 -0
- package/dist/errors.cjs.map +1 -0
- package/dist/errors.d.cts +18 -0
- package/dist/errors.d.ts +18 -0
- package/dist/errors.js +34 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.cjs +885 -60
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +578 -4
- package/dist/index.d.ts +578 -4
- package/dist/index.js +889 -57
- package/dist/index.js.map +1 -1
- package/dist/node.cjs +885 -60
- package/dist/node.cjs.map +1 -1
- package/dist/node.js +889 -57
- package/dist/node.js.map +1 -1
- package/package.json +13 -8
package/dist/index.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as System from '@objectstack/spec/system';
|
|
2
2
|
import { MetadataFormat, MetadataLoaderContract, MetadataLoadOptions, MetadataLoadResult, MetadataStats, MetadataSaveOptions, MetadataSaveResult, MetadataWatchEvent, MetadataManagerConfig, PackagePublishResult, MetadataHistoryQueryOptions, MetadataHistoryQueryResult, MetadataDiffResult, MetadataHistoryRecord, MetadataHistoryRetentionPolicy } from '@objectstack/spec/system';
|
|
3
3
|
export { MetadataCollectionInfo, MetadataDiffResult, MetadataFormat, MetadataHistoryQueryOptions, MetadataHistoryQueryResult, MetadataHistoryRecord, MetadataHistoryRetentionPolicy, MetadataLoadOptions, MetadataLoadResult, MetadataLoaderContract, MetadataManagerConfig, MetadataSaveOptions, MetadataSaveResult, MetadataStats, MetadataWatchEvent } from '@objectstack/spec/system';
|
|
4
|
-
import { IMetadataService, IDataDriver, IDataEngine, IRealtimeService, MetadataWriteOptions, MetadataWatchCallback, MetadataWatchHandle, MetadataExportOptions, MetadataImportOptions, MetadataImportResult, MetadataTypeInfo, IPubSub, ISchemaDriver } from '@objectstack/spec/contracts';
|
|
4
|
+
import { IMetadataService, IDataDriver, IDataEngine, IRealtimeService, MetadataWriteOptions, MetadataWatchCallback, MetadataWatchHandle, MetadataExportOptions, MetadataImportOptions, MetadataImportResult, MetadataTypeInfo, ApiEndpointMatch, IPubSub, ISchemaDriver } from '@objectstack/spec/contracts';
|
|
5
5
|
export { IMetadataService, MetadataExportOptions, MetadataImportOptions, MetadataImportResult, MetadataTypeInfo, MetadataWatchCallback, MetadataWatchHandle } from '@objectstack/spec/contracts';
|
|
6
6
|
import { MetadataTypeRegistryEntry, MetadataQuery, MetadataQueryResult, MetadataBulkResult, MetadataOverlay, MetadataValidationResult, MetadataDependency, MetadataPluginConfig } from '@objectstack/spec/kernel';
|
|
7
7
|
export { MetadataBulkResult, MetadataDependency, MetadataPluginConfig, MetadataPluginManifest, MetadataQuery, MetadataQueryResult, MetadataType, MetadataTypeRegistryEntry, MetadataValidationResult } from '@objectstack/spec/kernel';
|
|
@@ -117,13 +117,66 @@ interface MetadataLoader {
|
|
|
117
117
|
*/
|
|
118
118
|
list(type: string): Promise<string[]>;
|
|
119
119
|
/**
|
|
120
|
-
* Save metadata item
|
|
120
|
+
* Save metadata item into this loader's store.
|
|
121
|
+
*
|
|
122
|
+
* [#5654] Optional on the interface, **mandatory for a `datasource:` loader
|
|
123
|
+
* that declares `capabilities.write`** — `MetadataManager.registerLoader()`
|
|
124
|
+
* refuses to register such a loader when this method is missing, so the
|
|
125
|
+
* combination "declared writable, cannot persist" never reaches the runtime.
|
|
126
|
+
*
|
|
127
|
+
* The reason it is enforced at registration rather than tolerated at the write
|
|
128
|
+
* site: `MetadataManager.register()` persists into every writable
|
|
129
|
+
* `datasource:` loader, and it used to read `loader.save &&` first — a loader
|
|
130
|
+
* that declares it can be written to but has no `save()` made every write a
|
|
131
|
+
* silent lie. `register()` would skip it, then write the in-memory registry,
|
|
132
|
+
* invalidate the list cache, announce a `created`/`updated` event and notify
|
|
133
|
+
* watchers, so the caller is told the write succeeded; the item reads back
|
|
134
|
+
* correctly for the life of the process and is **gone at the next restart**,
|
|
135
|
+
* with nothing to retry it.
|
|
136
|
+
*
|
|
137
|
+
* Loaders on the other protocols (`file:`, `memory:`, `http:`, `s3:`) are not
|
|
138
|
+
* gated: `MetadataManager` never persists to them at runtime — `register()`
|
|
139
|
+
* filters on `datasource:` — so a missing `save()` there loses nothing.
|
|
140
|
+
*
|
|
121
141
|
* @param type The metadata type
|
|
122
142
|
* @param name The item name
|
|
123
143
|
* @param data The data to save
|
|
124
144
|
* @param options Save options
|
|
125
145
|
*/
|
|
126
146
|
save?(type: string, name: string, data: any, options?: MetadataSaveOptions): Promise<MetadataSaveResult>;
|
|
147
|
+
/**
|
|
148
|
+
* Delete a metadata item from this loader's store.
|
|
149
|
+
*
|
|
150
|
+
* [#5276, #5654] Optional on the interface, **mandatory for a `datasource:`
|
|
151
|
+
* loader that declares `capabilities.write`** — `MetadataManager.registerLoader()`
|
|
152
|
+
* refuses to register such a loader when this method is missing, so the
|
|
153
|
+
* combination "declared writable, cannot delete" never reaches the runtime.
|
|
154
|
+
*
|
|
155
|
+
* The reason it is enforced at registration rather than tolerated at the
|
|
156
|
+
* delete site: `MetadataManager.register()` persists into every writable
|
|
157
|
+
* `datasource:` loader, and `unregister()` has to take those rows back out
|
|
158
|
+
* again. A loader that can be written to but not deleted from makes every
|
|
159
|
+
* deletion a silent lie — `unregister()` would skip it, then drop the
|
|
160
|
+
* registry entry, invalidate the list cache and announce a `deleted` event,
|
|
161
|
+
* so the caller is told the delete succeeded while the row is read straight
|
|
162
|
+
* back out of this loader by the next `list()`/`get()`. `capabilities.write`
|
|
163
|
+
* therefore means *both* directions of the write, on both ends of the item's
|
|
164
|
+
* life — declared = enforced.
|
|
165
|
+
*
|
|
166
|
+
* One gate covers both halves: `assertWritableLoaderContract` in
|
|
167
|
+
* `metadata-manager.ts` requires `save()` **and** `delete()` for this
|
|
168
|
+
* combination and names whichever is missing. #5276 built it for `delete`;
|
|
169
|
+
* #5654 widened it to `save`, which had the identical silent skip in
|
|
170
|
+
* `register()` — see the note on `save?` above.
|
|
171
|
+
*
|
|
172
|
+
* Loaders on the other protocols (`file:`, `memory:`, `http:`, `s3:`) are not
|
|
173
|
+
* gated: `MetadataManager` never writes to them at runtime, so it never has a
|
|
174
|
+
* deletion of its own to take back.
|
|
175
|
+
*
|
|
176
|
+
* @param type The metadata type
|
|
177
|
+
* @param name The item name
|
|
178
|
+
*/
|
|
179
|
+
delete?(type: string, name: string): Promise<void>;
|
|
127
180
|
}
|
|
128
181
|
|
|
129
182
|
/**
|
|
@@ -159,6 +212,67 @@ declare class MetadataManager implements IMetadataService {
|
|
|
159
212
|
private dependencies;
|
|
160
213
|
private listCache;
|
|
161
214
|
private static readonly LIST_CACHE_TTL_MS;
|
|
215
|
+
/**
|
|
216
|
+
* [#5184] TTL for an entry produced by a degraded read (≥1 loader threw).
|
|
217
|
+
*
|
|
218
|
+
* Deliberately at the top of the 1–2s band: the point of keeping degraded
|
|
219
|
+
* results cached at all is to absorb a burst of `list()` calls issued from
|
|
220
|
+
* inside one open transaction, and those bursts are milliseconds apart but
|
|
221
|
+
* can be spread by per-row work. Two seconds covers that while still being
|
|
222
|
+
* 15× shorter than the healthy TTL.
|
|
223
|
+
*/
|
|
224
|
+
private static readonly DEGRADED_LIST_CACHE_TTL_MS;
|
|
225
|
+
/**
|
|
226
|
+
* [#5253] The `list()` read currently in flight for a metadata type — the
|
|
227
|
+
* concurrent half of the `listCache` policy above.
|
|
228
|
+
*
|
|
229
|
+
* `listCache` memoizes an answer only once a read has *finished*, so it can
|
|
230
|
+
* absorb the caller that arrives second in time but never the caller that
|
|
231
|
+
* arrives second in flight. Everything issued while the first read is still
|
|
232
|
+
* walking the loaders used to miss and start its own identical walk; on the
|
|
233
|
+
* knex/SQLite path the field comment above is built for, that is 60s burned
|
|
234
|
+
* per concurrent caller instead of once for all of them. A type is read once
|
|
235
|
+
* at a time: whoever finds a read already running joins it.
|
|
236
|
+
*
|
|
237
|
+
* **Sharers share the outcome. This is a contract, not an accident.** Every
|
|
238
|
+
* caller joining an in-flight read receives that read's exact result — the
|
|
239
|
+
* same array instance, and, when a loader was unreadable, the same
|
|
240
|
+
* known-partial set that gets memoized `degraded: true` on the short TTL.
|
|
241
|
+
* There is no per-caller retry: `list()` is the best-effort listing seam and
|
|
242
|
+
* does not throw (see {@link reportLoaderReadFailure}; the strict
|
|
243
|
+
* counterparts are `listForIndex()` and {@link loadDiagnosed}), so a lost
|
|
244
|
+
* loader is not an error to fail over from — it is the answer. Re-running the
|
|
245
|
+
* read privately for a joiner would walk the same loaders in the same window
|
|
246
|
+
* against the same outage, which is precisely what this map exists to
|
|
247
|
+
* prevent. Should the seam ever acquire a rejecting path, that rejection is
|
|
248
|
+
* shared by the same mechanism and for the same reason.
|
|
249
|
+
*
|
|
250
|
+
* **The registration is also the permission to cache.** An entry here says
|
|
251
|
+
* "this read still describes the current state". {@link invalidateListCache}
|
|
252
|
+
* retracts it, which is what makes a write landing mid-read safe in both
|
|
253
|
+
* directions:
|
|
254
|
+
* • the retracted read does NOT write its result into `listCache` when it
|
|
255
|
+
* settles, so an answer assembled before the write cannot outlive the
|
|
256
|
+
* write it predates (the invalidation wins — it is the later, better
|
|
257
|
+
* informed fact);
|
|
258
|
+
* • a `list()` issued after the invalidation starts a FRESH read instead of
|
|
259
|
+
* joining one that predates the write.
|
|
260
|
+
* That second point is the #5219 / #5229 ordering bar restated for
|
|
261
|
+
* concurrency: a consumer woken by a metadata change must not observe the
|
|
262
|
+
* event and pre-event state together, and handing a woken watcher an
|
|
263
|
+
* in-flight read that began before the event would be exactly that.
|
|
264
|
+
* Callers *already waiting* on the retracted read still receive its (now
|
|
265
|
+
* possibly stale) result — they asked before the write, and restarting the
|
|
266
|
+
* read under them would turn a write burst into an unbounded retry loop on
|
|
267
|
+
* the one path the cache exists to keep off the loaders.
|
|
268
|
+
*
|
|
269
|
+
* Self-cleaning: the entry is dropped when the read settles, by that read
|
|
270
|
+
* only, so a fresh read that already replaced it keeps its slot. Nothing
|
|
271
|
+
* accumulates — a wave of callers arriving after settle finds the cache the
|
|
272
|
+
* settle just wrote, and once that lapses it starts one new read.
|
|
273
|
+
*/
|
|
274
|
+
private readonly inflightListReads;
|
|
275
|
+
private readonly loaderReadFailureReported;
|
|
162
276
|
private realtimeService?;
|
|
163
277
|
private clusterPubSub?;
|
|
164
278
|
private clusterNodeId?;
|
|
@@ -167,6 +281,8 @@ declare class MetadataManager implements IMetadataService {
|
|
|
167
281
|
protected repository?: MetadataRepository;
|
|
168
282
|
private repoWatchIter?;
|
|
169
283
|
private repoWatchClosed;
|
|
284
|
+
private static readonly ENDPOINT_METADATA_TYPE;
|
|
285
|
+
private readonly endpointMatcher;
|
|
170
286
|
constructor(config: MetadataManagerOptions);
|
|
171
287
|
/**
|
|
172
288
|
* Set the type registry for metadata type discovery.
|
|
@@ -223,6 +339,13 @@ declare class MetadataManager implements IMetadataService {
|
|
|
223
339
|
private publishRealtimeMetadataEvent;
|
|
224
340
|
/**
|
|
225
341
|
* Register a new metadata loader (data source)
|
|
342
|
+
*
|
|
343
|
+
* [#5276, #5654] Rejects — loudly, before the loader is stored — a
|
|
344
|
+
* `datasource:` loader that declares `capabilities.write` without
|
|
345
|
+
* implementing `save()` **and** `delete()`. This is the **only** way into
|
|
346
|
+
* `this.loaders` (the constructor's `config.loaders` come through here too),
|
|
347
|
+
* which is what lets every later write-capability guard be defensive rather
|
|
348
|
+
* than load-bearing.
|
|
226
349
|
*/
|
|
227
350
|
registerLoader(loader: MetadataLoader): void;
|
|
228
351
|
/**
|
|
@@ -259,15 +382,183 @@ declare class MetadataManager implements IMetadataService {
|
|
|
259
382
|
/**
|
|
260
383
|
* Get a metadata item by type and name.
|
|
261
384
|
* Checks in-memory registry first, then falls back to loaders.
|
|
385
|
+
*
|
|
386
|
+
* Returns `undefined` both when nothing declares the item and when every
|
|
387
|
+
* loader that could have held it FAILED — see {@link getDiagnosed} when the
|
|
388
|
+
* caller must tell those apart. This is the same relationship {@link load}
|
|
389
|
+
* has with {@link loadDiagnosed}, so every existing caller keeps its exact
|
|
390
|
+
* behaviour and only callers that ASK for the verdict pay for it.
|
|
391
|
+
*
|
|
392
|
+
* [#5840] Deliberately NOT expressed as `(await getDiagnosed(…)).data`,
|
|
393
|
+
* although that is what it computes. The obvious delegation adds one
|
|
394
|
+
* `await` hop, and a registry hit here is observed one microtask sooner than
|
|
395
|
+
* it would be through a second async frame — which `register()`'s watchers
|
|
396
|
+
* depend on, because `notifyWatchers` does not await its handlers and
|
|
397
|
+
* ObjectQL's bridge re-reads through `get()` on the event rather than
|
|
398
|
+
* trusting the payload (`register-notifies-watchers.test.ts` pins it, and
|
|
399
|
+
* went red on the delegating version). The duplication is three lines and is
|
|
400
|
+
* pinned from the other side: `get()` and `getDiagnosed().data` are asserted
|
|
401
|
+
* to agree on every case in `metadata-manager-get-diagnosed.test.ts`.
|
|
262
402
|
*/
|
|
263
403
|
get(type: string, name: string): Promise<unknown | undefined>;
|
|
264
404
|
/**
|
|
265
|
-
*
|
|
405
|
+
* `get`, plus whether the answer can be trusted as complete.
|
|
406
|
+
*
|
|
407
|
+
* [#5840] {@link loadDiagnosed} already computes this verdict — and `get()`
|
|
408
|
+
* threw it away two hops later (`load` kept only `.data`, `get` turned that
|
|
409
|
+
* `null` into `undefined`), so no caller of `get` could reach the one fact
|
|
410
|
+
* ADR-0110 D3 exists to preserve: **a miss and an outage are different facts
|
|
411
|
+
* with opposite security meanings.** A consumer that gates on a declaration
|
|
412
|
+
* MUST NOT read `undefined` as "the author declared nothing" — an
|
|
413
|
+
* availability failure would silently widen access (the REST `/actions`
|
|
414
|
+
* fail-open branch, #3935) or make a positive claim about authorship from a
|
|
415
|
+
* read that never happened (`code: null` in the layered read, #5707/#5532).
|
|
416
|
+
*
|
|
417
|
+
* This is the registry-first counterpart of {@link loadDiagnosed}, and that
|
|
418
|
+
* difference is why callers of `get` cannot simply switch to `loadDiagnosed`:
|
|
419
|
+
* doing so would skip the in-memory registry and change what they resolve.
|
|
420
|
+
*
|
|
421
|
+
* `degraded` is true when at least one loader threw AND nothing answered with
|
|
422
|
+
* the item — never when the in-memory registry answered, because that answer
|
|
423
|
+
* needed no loader. A clean miss (every loader answered, none had it) is NOT
|
|
424
|
+
* degraded. The posture is deliberately conservative: with a loader down we
|
|
425
|
+
* cannot prove the item is absent, so we decline to claim it is.
|
|
426
|
+
*/
|
|
427
|
+
getDiagnosed(type: string, name: string): Promise<{
|
|
428
|
+
data: unknown | undefined;
|
|
429
|
+
degraded: boolean;
|
|
430
|
+
errors: string[];
|
|
431
|
+
}>;
|
|
432
|
+
/**
|
|
433
|
+
* List all metadata items of a given type.
|
|
434
|
+
*
|
|
435
|
+
* Best-effort by contract: a loader that cannot be read is reported once and
|
|
436
|
+
* skipped ({@link reportLoaderReadFailure}), so this resolves with what the
|
|
437
|
+
* reachable loaders hold rather than throwing.
|
|
438
|
+
*
|
|
439
|
+
* [#5253] Reads of one type are single-flight — concurrent callers join the
|
|
440
|
+
* read already running instead of each walking every loader. What they are
|
|
441
|
+
* promised, and what happens when a write lands mid-read, is the contract on
|
|
442
|
+
* `inflightListReads`; what is memoized afterwards is the contract on
|
|
443
|
+
* `listCache`.
|
|
266
444
|
*/
|
|
267
445
|
list(type: string): Promise<unknown[]>;
|
|
446
|
+
/**
|
|
447
|
+
* Assemble the `list()` answer for `type` from the in-memory registry plus
|
|
448
|
+
* every loader, reporting (but not rethrowing) loaders that could not be
|
|
449
|
+
* read.
|
|
450
|
+
*
|
|
451
|
+
* The body {@link list} used to inline, extracted so the caching and
|
|
452
|
+
* single-flight bookkeeping around it has one thing to run at most once per
|
|
453
|
+
* type (#5253). Deliberately does NOT touch `listCache` itself: whether this
|
|
454
|
+
* result may be memoized depends on what happened to the read's registration
|
|
455
|
+
* while it ran, which only `list()` can see.
|
|
456
|
+
*/
|
|
457
|
+
private readListUncached;
|
|
458
|
+
/**
|
|
459
|
+
* Report — at `error`, once per outage episode — that a loader could not be
|
|
460
|
+
* read while serving {@link list}.
|
|
461
|
+
*
|
|
462
|
+
* [#5108] This branch used to be dead for the loader that matters. Before
|
|
463
|
+
* #5108 `DatabaseLoader` caught its own read failures and answered `[]`, so
|
|
464
|
+
* `list()` received a *successful empty read* and never entered this `catch`
|
|
465
|
+
* at all: an unreachable `sys_metadata` and "this environment declares no
|
|
466
|
+
* `permission`" produced byte-identical results with not one line logged.
|
|
467
|
+
* With the loader rethrowing everything but the benign not-provisioned case,
|
|
468
|
+
* this is where the outage finally becomes speakable.
|
|
469
|
+
*
|
|
470
|
+
* `error`, not `warn`, per AGENTS.md → "Degradation log levels". Apply its
|
|
471
|
+
* one question — *does the system still look normal from outside while
|
|
472
|
+
* something it claims to know has not actually landed?* — and the answer is
|
|
473
|
+
* yes: `list()` still returns, callers still get an array, nothing 500s, and
|
|
474
|
+
* the set they gate on is quietly short. Which way that cuts depends on the
|
|
475
|
+
* consumer, and both ways are silent (#3935 is the fail-open precedent).
|
|
476
|
+
*
|
|
477
|
+
* Said **once** per loader, and un-said on recovery, because `list()` is a
|
|
478
|
+
* hot path — one line per outage, not one per read.
|
|
479
|
+
*
|
|
480
|
+
* [#5184] The once-only guard carries more weight than it used to: a
|
|
481
|
+
* degraded `list()` result is now memoized for `DEGRADED_LIST_CACHE_TTL_MS`
|
|
482
|
+
* rather than `LIST_CACHE_TTL_MS`, so during an outage the loader is
|
|
483
|
+
* re-asked (and this method re-entered) roughly every 2s instead of every
|
|
484
|
+
* 30s. That is the point — the outage stops being a 30s silent window and
|
|
485
|
+
* recovery is noticed within seconds — and it costs nothing in log volume
|
|
486
|
+
* precisely because `loaderReadFailureReported` still speaks only once.
|
|
487
|
+
*
|
|
488
|
+
* Deliberately does NOT rethrow: `list()` is the best-effort listing seam and
|
|
489
|
+
* must keep serving what the reachable loaders hold. The strict counterpart
|
|
490
|
+
* for callers whose answer is a security decision is `listForIndex()` (no
|
|
491
|
+
* `catch`, feeding `matchEndpoint`) and {@link loadDiagnosed} (ADR-0110 D3)
|
|
492
|
+
* for the singular read — both of which only became honest for
|
|
493
|
+
* `DatabaseLoader` with the same #5108 change.
|
|
494
|
+
*/
|
|
495
|
+
private reportLoaderReadFailure;
|
|
496
|
+
/** Un-say {@link reportLoaderReadFailure} once the loader answers again. */
|
|
497
|
+
private reportLoaderReadRecovered;
|
|
498
|
+
/**
|
|
499
|
+
* Memoize a completed {@link list} result.
|
|
500
|
+
*
|
|
501
|
+
* [#5184] `degraded` is not optional at the call site by accident — it is the
|
|
502
|
+
* one thing this cache used to throw away. A result assembled while a loader
|
|
503
|
+
* was unreadable is stored, but stored *as* what it is, so it expires on the
|
|
504
|
+
* degraded TTL and any reader can tell it apart from a complete answer.
|
|
505
|
+
*/
|
|
268
506
|
private cacheListResult;
|
|
269
|
-
/**
|
|
507
|
+
/**
|
|
508
|
+
* Read a still-fresh {@link listCache} entry, or `undefined` when there is
|
|
509
|
+
* none / it has expired.
|
|
510
|
+
*
|
|
511
|
+
* [#5184] The single place the TTL policy is applied, so "a degraded entry
|
|
512
|
+
* expires sooner" cannot be forgotten by a second reader. Returns the whole
|
|
513
|
+
* entry rather than just `items` so callers keep access to `degraded`.
|
|
514
|
+
*/
|
|
515
|
+
private readCachedList;
|
|
516
|
+
/**
|
|
517
|
+
* Internal helper: drop every memoized or in-progress `list()` answer for a
|
|
518
|
+
* type, so the next read observes the write that called this.
|
|
519
|
+
*
|
|
520
|
+
* [#5253] Retracting the in-flight read (not just the finished entry) is the
|
|
521
|
+
* whole mid-read story, and it is pinned by test: the read keeps running for
|
|
522
|
+
* the callers already waiting on it, but it loses the right to memoize its
|
|
523
|
+
* pre-write answer, and a caller arriving after this point gets a fresh read
|
|
524
|
+
* instead of joining a pre-write one. The reasoning — including why waiting
|
|
525
|
+
* callers are NOT restarted — is on the `inflightListReads` field.
|
|
526
|
+
*
|
|
527
|
+
* [#5259] Both halves are only as good as WHEN the caller invokes this. This
|
|
528
|
+
* clears what is stale *as of now*; it cannot pre-empt a store the caller has
|
|
529
|
+
* not finished updating yet. Callers must therefore invalidate only once
|
|
530
|
+
* every store already holds the state they are about to announce — see the
|
|
531
|
+
* `listCache` field comment and {@link unregister}, whose pre-#5259 ordering
|
|
532
|
+
* invalidated one await too early and let the next read cache a view in which
|
|
533
|
+
* the registry was empty and the loader was not.
|
|
534
|
+
*/
|
|
270
535
|
private invalidateListCache;
|
|
536
|
+
/**
|
|
537
|
+
* Enumerate stored items of `type` for an index build — like {@link list},
|
|
538
|
+
* but a store that cannot be read THROWS instead of contributing nothing.
|
|
539
|
+
*
|
|
540
|
+
* [#5089] `list()` deliberately logs a failing loader and skips it so a
|
|
541
|
+
* partially-available metadata plane still serves what it can. That posture
|
|
542
|
+
* is wrong for `matchEndpoint`: its `undefined` becomes an HTTP 404, and a
|
|
543
|
+
* store outage that silently yields "zero declarations" would turn every
|
|
544
|
+
* declared endpoint into a semantic "nothing declares this route". Same
|
|
545
|
+
* distinction {@link loadDiagnosed} draws on the singular read (ADR-0110
|
|
546
|
+
* D3) — a miss and an outage are different facts with opposite meanings.
|
|
547
|
+
*
|
|
548
|
+
* Deliberately private and single-purpose: it is not a second `list()`, it
|
|
549
|
+
* is `list()`'s failure posture inverted for the one caller whose answer is
|
|
550
|
+
* a security/availability decision rather than a best-effort listing.
|
|
551
|
+
*
|
|
552
|
+
* This surfaces only failures a loader actually reports — which, since
|
|
553
|
+
* #5108, includes `DatabaseLoader`: it used to swallow its own read errors
|
|
554
|
+
* into `[]`, making a DB outage invisible even here. It now rethrows every
|
|
555
|
+
* read failure except the benign "table not provisioned yet", so this seam
|
|
556
|
+
* holds against the real datasource-backed loader and not just the memory /
|
|
557
|
+
* remote ones. (`database-loader.test.ts` pins that end to end: a broken
|
|
558
|
+
* driver behind a real `DatabaseLoader` makes `matchEndpoint` reject rather
|
|
559
|
+
* than answer a 404-shaped `undefined`.)
|
|
560
|
+
*/
|
|
561
|
+
private listForIndex;
|
|
271
562
|
/**
|
|
272
563
|
* Unregister/remove a metadata item by type and name.
|
|
273
564
|
* Deletes from database-backed loaders only (same rationale as register()).
|
|
@@ -276,8 +567,122 @@ declare class MetadataManager implements IMetadataService {
|
|
|
276
567
|
* {@link MetadataWatchEvent} — the delete half of the {@link register}
|
|
277
568
|
* contract. Pass `{ notify: false }` only for teardown that announces by
|
|
278
569
|
* other means.
|
|
570
|
+
*
|
|
571
|
+
* ## [#5259] Storage FIRST, in-memory second — the order is the fix
|
|
572
|
+
*
|
|
573
|
+
* This method used to drop the registry entry and call
|
|
574
|
+
* {@link invalidateListCache} *before* awaiting `loader.delete()`. Those two
|
|
575
|
+
* steps are separated by a real await window (one DB round-trip per writable
|
|
576
|
+
* loader), and inside it the manager was in a state that exists nowhere else:
|
|
577
|
+
* **registry already empty, loader not yet empty**. `list()` merges the two,
|
|
578
|
+
* so a read arriving in that window
|
|
579
|
+
*
|
|
580
|
+
* • missed the cache (it had just been invalidated),
|
|
581
|
+
* • assembled the still-stored row into its answer, and
|
|
582
|
+
* • memoized that answer as a COMPLETE read — the full 30s healthy TTL,
|
|
583
|
+
* because no loader threw, so #5184's 2s degraded TTL never applied.
|
|
584
|
+
*
|
|
585
|
+
* Nothing invalidated again afterwards ({@link notifyWatchers} does not touch
|
|
586
|
+
* `listCache`), so a row that was gone from storage kept being enumerated for
|
|
587
|
+
* up to 30s — and `get()`, which never consulted that cache, disagreed with
|
|
588
|
+
* `list()` the whole time. For a gating type (`permission`, `api`) the two
|
|
589
|
+
* faces of the same manager answered opposite questions about whether a
|
|
590
|
+
* declaration exists.
|
|
591
|
+
*
|
|
592
|
+
* {@link register} never had this defect, and the reason is instructive: it
|
|
593
|
+
* writes the registry *first*, and the registry outranks every loader in the
|
|
594
|
+
* merge, so throughout its own save window the merged view already equals the
|
|
595
|
+
* post-write state. The invariant that makes register correct is not "where
|
|
596
|
+
* the invalidate sits" but **the invalidate must be the last thing after
|
|
597
|
+
* every store already holds the announced state**. Restated for delete, that
|
|
598
|
+
* means storage first:
|
|
599
|
+
*
|
|
600
|
+
* 1. `await loader.delete()` on every writable loader. Throughout this
|
|
601
|
+
* window registry AND loaders still hold the item, so a concurrent
|
|
602
|
+
* `list()` observes a coherent pre-delete state — which is the truth,
|
|
603
|
+
* because the delete has not landed and has not been announced.
|
|
604
|
+
* 2. Drop the registry entry and `invalidateListCache(type)` — with **no
|
|
605
|
+
* await between them**, so no read can interleave and observe the
|
|
606
|
+
* half-applied state that produced the bug. Everything cached or
|
|
607
|
+
* in-flight from step 1 is dropped here, at the moment the final state
|
|
608
|
+
* becomes true.
|
|
609
|
+
* 3. Publish + announce. #5219's invalidate-before-notify bar, unchanged:
|
|
610
|
+
* a watcher woken by the `deleted` event and re-reading through `list()`
|
|
611
|
+
* gets a fresh read of the post-delete state.
|
|
612
|
+
*
|
|
613
|
+
* **Composition with #5253's single-flight (this is the load-bearing half).**
|
|
614
|
+
* A `list()` that is still walking the loaders when step 2 runs cannot be
|
|
615
|
+
* fixed by dropping `listCache` alone — it has not written its entry yet, and
|
|
616
|
+
* it would write the pre-delete answer *after* the invalidation. The
|
|
617
|
+
* mechanism that covers it is `invalidateListCache()` also retracting the
|
|
618
|
+
* read's registration in `inflightListReads`: a retracted read still resolves
|
|
619
|
+
* for the callers already waiting on it (they asked before the delete) but
|
|
620
|
+
* loses the right to memoize, and any caller arriving after step 2 starts a
|
|
621
|
+
* fresh read rather than joining the pre-delete one. So every read is
|
|
622
|
+
* covered: one that FINISHED in the window has its entry deleted, one still
|
|
623
|
+
* IN FLIGHT loses its permission to cache, and one starting later reads the
|
|
624
|
+
* post-delete state. That is why the invalidate must come after the deletes
|
|
625
|
+
* rather than being duplicated on both sides of them — a second invalidate
|
|
626
|
+
* before the await would buy nothing and would re-open step 1's window.
|
|
279
627
|
*/
|
|
280
628
|
unregister(type: string, name: string, options?: MetadataWriteOptions): Promise<void>;
|
|
629
|
+
/**
|
|
630
|
+
* Delete one metadata item from one writable loader — the storage half of
|
|
631
|
+
* {@link unregister}.
|
|
632
|
+
*
|
|
633
|
+
* A one-line wrapper on purpose: it gives this durability seam a **name**.
|
|
634
|
+
* `check:durability-log-level` matches by callee name against an explicit
|
|
635
|
+
* vocabulary, and the raw call is `loader.delete(...)` — putting `delete` in
|
|
636
|
+
* that vocabulary would claim every `.delete()` in the monorepo (`Map`,
|
|
637
|
+
* `Set`, cache handles, `URLSearchParams`) and the gate would drown in false
|
|
638
|
+
* positives, which is exactly the failure mode its own header warns about.
|
|
639
|
+
* Named here, `deleteMetaItemFromLoader` is in `DURABILITY_CRITICAL_CALLEES`
|
|
640
|
+
* with a blast radius of precisely this call site, mirroring `saveMetaItem`
|
|
641
|
+
* on the write side (#4754).
|
|
642
|
+
*
|
|
643
|
+
* [#5276] `MetadataLoader` now declares `delete?`, so no cast is left here.
|
|
644
|
+
* It stays *optional* on the interface — `file:`/`memory:`/`http:`/`s3:`
|
|
645
|
+
* loaders legitimately have none — and the guard below is therefore a type
|
|
646
|
+
* narrowing rather than a policy decision. The policy lives at
|
|
647
|
+
* `registerLoader()`: a `datasource:` loader that declares
|
|
648
|
+
* `capabilities.write` cannot be registered without a `delete()`, which is
|
|
649
|
+
* exactly the set of loaders this method is ever called for.
|
|
650
|
+
*/
|
|
651
|
+
private deleteMetaItemFromLoader;
|
|
652
|
+
/**
|
|
653
|
+
* Report — at `error` — that a loader refused to delete an item the runtime
|
|
654
|
+
* has already dropped and announced as deleted.
|
|
655
|
+
*
|
|
656
|
+
* [#5259] This used to be a `logger.warn('Failed to delete …')` and continue.
|
|
657
|
+
* AGENTS.md → "Degradation log levels" decides the level with one question:
|
|
658
|
+
* *after the degradation, does the system still look normal from the outside
|
|
659
|
+
* while something it claims is persisted has not actually landed?* Here it is
|
|
660
|
+
* the deletion that did not land, which is the same class and the same
|
|
661
|
+
* silence: `unregister()` resolves normally, the caller is told the delete
|
|
662
|
+
* succeeded, and the surviving row is read straight back out of storage —
|
|
663
|
+
* permanently, since nothing ever retries this. Durability/consistency
|
|
664
|
+
* degradation ⇒ `error`, naming the **consequence** and the **fix**.
|
|
665
|
+
*
|
|
666
|
+
* **Why the registry entry is still dropped when this fires.** The
|
|
667
|
+
* alternative — keep the item registered so runtime state matches storage —
|
|
668
|
+
* looks safer and is not. The loader still holds the row, and `list()`/`get()`
|
|
669
|
+
* merge registry ∪ loaders, so the item is served either way; the only thing
|
|
670
|
+
* the surviving registry entry would change is *which copy wins*, pinning an
|
|
671
|
+
* in-memory definition that outranks the stored row nobody is maintaining
|
|
672
|
+
* anymore. Dropping it makes the very next read fall through to storage,
|
|
673
|
+
* which is the actual truth after a failed delete — the item still exists —
|
|
674
|
+
* and it surfaces that immediately (the item visibly reappears) instead of at
|
|
675
|
+
* the next restart. One truth, read from where it lives; the divergence is
|
|
676
|
+
* reported here rather than papered over with a second in-memory copy.
|
|
677
|
+
*
|
|
678
|
+
* **Said once per un-deleted item, not once per loader.** The once-per-outage
|
|
679
|
+
* discipline of {@link reportLoaderReadFailure} exists because `list()` is hot
|
|
680
|
+
* and its repeats are *identical*; these are not. Each line names a different
|
|
681
|
+
* item that is still in storage and that nothing will ever retry, so
|
|
682
|
+
* collapsing them would hand an operator the first casualty and silently drop
|
|
683
|
+
* the rest of the list — the failure this level was raised to prevent.
|
|
684
|
+
*/
|
|
685
|
+
private reportMetaItemDeleteFailure;
|
|
281
686
|
/**
|
|
282
687
|
* Check if a metadata item exists
|
|
283
688
|
*/
|
|
@@ -333,12 +738,70 @@ declare class MetadataManager implements IMetadataService {
|
|
|
333
738
|
* 2. Snapshot all items in the package (publishedDefinition = clone(metadata))
|
|
334
739
|
* 3. Increment version
|
|
335
740
|
* 4. Set all items state → active
|
|
741
|
+
*
|
|
742
|
+
* [#5189, #5040 E7b] Step 1 additionally runs the **endpoint publish gates**
|
|
743
|
+
* over every `api` item — see {@link gateApiItemsForPublish}. That pass is
|
|
744
|
+
* NOT governed by `options.validate`: the gates are a contract, not a
|
|
745
|
+
* lint (ADR-0121 D6 says publish REJECTS an unmetered anonymous endpoint),
|
|
746
|
+
* and an opt-out flag on a security gate is the bypass this issue closed.
|
|
336
747
|
*/
|
|
337
748
|
publishPackage(packageId: string, options?: {
|
|
338
749
|
changeNote?: string;
|
|
339
750
|
publishedBy?: string;
|
|
340
751
|
validate?: boolean;
|
|
752
|
+
/**
|
|
753
|
+
* [#5189] The package manifest's EXPLICIT `manifest.namespace` (ADR-0121
|
|
754
|
+
* D2), supplied by a caller that holds the manifest.
|
|
755
|
+
*
|
|
756
|
+
* `MetadataManager` has no manifest concept — it indexes items by
|
|
757
|
+
* `packageId` and nothing else — so it cannot prove a namespace on its
|
|
758
|
+
* own, and an `api` item's own `namespace`-ish fields are author-supplied
|
|
759
|
+
* data, not identity (reading them would make the D1/D2 carve-out gate
|
|
760
|
+
* vacuous: an author would simply declare the namespace their path
|
|
761
|
+
* already uses). Absent this option the namespace gate fails and `api`
|
|
762
|
+
* items in the package cannot publish through this path — which is the
|
|
763
|
+
* correct outcome, not a limitation to route around: a publish that
|
|
764
|
+
* cannot prove a namespace must not mint a URL under one.
|
|
765
|
+
*/
|
|
766
|
+
namespace?: string;
|
|
341
767
|
}): Promise<PackagePublishResult>;
|
|
768
|
+
/**
|
|
769
|
+
* [#5189, #5040 E7b] Run the endpoint publish gates over a package's `api`
|
|
770
|
+
* items and report every failure as a publish-blocking validation error.
|
|
771
|
+
*
|
|
772
|
+
* ## Why this exists at all
|
|
773
|
+
*
|
|
774
|
+
* E7 (#5111) hung the five per-endpoint gates on
|
|
775
|
+
* `ObjectStackDefinitionSchema`, which covers every path that parses a
|
|
776
|
+
* STACK — `defineStack`, `os validate`, the lint scorer, artifact ingest,
|
|
777
|
+
* `EnvironmentArtifactSchema.metadata`. It does not cover this one: an `api`
|
|
778
|
+
* item can be minted item-by-item (`metadata.register()`, a Studio write)
|
|
779
|
+
* and published here without a stack ever being parsed. Three of the gates
|
|
780
|
+
* degrade safely when bypassed (the executor answers a structured 501; a
|
|
781
|
+
* mis-namespaced path matches nothing), but **ADR-0121 D6 has no runtime
|
|
782
|
+
* counterpart**: `authRequired: false` is honoured faithfully and an
|
|
783
|
+
* unarmed `rateLimit` meters nothing, so the bypass mints an anonymous,
|
|
784
|
+
* zero-quota execution entry point. Hence a gate here, on the same
|
|
785
|
+
* function, rather than a second set of criteria that would drift.
|
|
786
|
+
*
|
|
787
|
+
* ## What it judges, and on what
|
|
788
|
+
*
|
|
789
|
+
* The registry stores either a raw spec document or a publish envelope
|
|
790
|
+
* (`{ name, packageId, state, metadata: {…spec} }`); the endpoint is read
|
|
791
|
+
* out with the SAME rule this method's caller uses for
|
|
792
|
+
* `publishedDefinition` (`data.metadata ?? data`), so publish gates exactly
|
|
793
|
+
* the document publish is about to snapshot. An item that does not satisfy
|
|
794
|
+
* `ApiEndpointSchema` fails here too — not extra strictness but a
|
|
795
|
+
* precondition: an unparsed shape cannot be gated, and it could never be
|
|
796
|
+
* served either (the matcher's own loud skip refuses it at load).
|
|
797
|
+
*
|
|
798
|
+
* @param packageItems every item collected for this package (all types).
|
|
799
|
+
* @param namespace the caller-supplied `manifest.namespace`; `undefined`
|
|
800
|
+
* fails the namespace gate, deliberately — see `publishPackage`'s option.
|
|
801
|
+
* @returns one entry per gate failure, `[]` when the package declares no
|
|
802
|
+
* `api` items (a package without endpoints is untouched by this pass).
|
|
803
|
+
*/
|
|
804
|
+
private gateApiItemsForPublish;
|
|
342
805
|
/**
|
|
343
806
|
* Revert entire package to last published state.
|
|
344
807
|
* Restores all metadata definitions from their published snapshots.
|
|
@@ -463,6 +926,36 @@ declare class MetadataManager implements IMetadataService {
|
|
|
463
926
|
* Duplicate dependencies (same source, target, and kind) are ignored.
|
|
464
927
|
*/
|
|
465
928
|
addDependency(dep: MetadataDependency): void;
|
|
929
|
+
/**
|
|
930
|
+
* Resolve a request's `method`+`path` to the declared `api` metadata item
|
|
931
|
+
* that owns it — `IMetadataService.matchEndpoint` (#5080 contract, #5089
|
|
932
|
+
* implementation, #5040 E2).
|
|
933
|
+
*
|
|
934
|
+
* The behaviour is specified by the contract text in
|
|
935
|
+
* `packages/spec/src/contracts/metadata-service.ts`; the mechanics
|
|
936
|
+
* (normalization, lazy index, loud parse-skip, duplicate resolution) live in
|
|
937
|
+
* `./endpoint-matcher.ts` and are documented there.
|
|
938
|
+
*
|
|
939
|
+
* Scope is THIS instance. There is no environment parameter, because callers
|
|
940
|
+
* already resolve the `metadata` service for the environment they serve —
|
|
941
|
+
* adding one here would create a second scoping mechanism.
|
|
942
|
+
*
|
|
943
|
+
* This method is reached over HTTP on a real boot. The dispatcher seam
|
|
944
|
+
* landed as #5090 (`packages/runtime/src/api-endpoint-step.ts`, called from
|
|
945
|
+
* the `setFallbackHandler` the dispatcher plugin installs), and #4936's
|
|
946
|
+
* wholesale publish refusal of a non-empty `apis:` was replaced by the
|
|
947
|
+
* #5040 E7 per-shape gates (`packages/spec/src/api/endpoint-publish-gate.ts`)
|
|
948
|
+
* — so declarations exist and requests arrive here. The showcase's two
|
|
949
|
+
* declared endpoints are matched and executed through this path in
|
|
950
|
+
* `packages/qa/dogfood/test/showcase-declarative-endpoints.dogfood.test.ts`.
|
|
951
|
+
*
|
|
952
|
+
* @throws when the metadata store cannot be read — an outage must never be
|
|
953
|
+
* reported as a miss, because a miss becomes a 404.
|
|
954
|
+
*/
|
|
955
|
+
matchEndpoint(query: {
|
|
956
|
+
path: string;
|
|
957
|
+
method: string;
|
|
958
|
+
}): Promise<ApiEndpointMatch | undefined>;
|
|
466
959
|
/**
|
|
467
960
|
* Load a single metadata item from loaders.
|
|
468
961
|
* Iterates through registered loaders until found.
|
|
@@ -539,6 +1032,42 @@ declare class MetadataManager implements IMetadataService {
|
|
|
539
1032
|
*/
|
|
540
1033
|
dispose(): Promise<void>;
|
|
541
1034
|
private startRepositoryWatch;
|
|
1035
|
+
/**
|
|
1036
|
+
* Drop every local cache of `type` (and of `name` within it) that a change
|
|
1037
|
+
* we did not perform ourselves has just invalidated, so the next read falls
|
|
1038
|
+
* through to the source of truth.
|
|
1039
|
+
*
|
|
1040
|
+
* The callers are the manager's *foreign-write* seams — the repository watch
|
|
1041
|
+
* loop ({@link applyRepoEvent}), the cluster peer replay in
|
|
1042
|
+
* {@link attachClusterPubSub}, and — since #5218 — `NodeMetadataManager`'s
|
|
1043
|
+
* chokidar handler, which is why this is `protected` rather than `private`.
|
|
1044
|
+
* All three learn about a write that landed somewhere else (the repo head;
|
|
1045
|
+
* another node's `sys_metadata`; an editor writing `rootDir/view/x.json`) and
|
|
1046
|
+
* hold caches that the write silently aged out. A file event qualifies on
|
|
1047
|
+
* exactly the definition that matters here: it did not come through this
|
|
1048
|
+
* manager's write API, so nothing has updated the caches on its behalf.
|
|
1049
|
+
* Local writes do not come through here: `register()` / `unregister()` /
|
|
1050
|
+
* `registerInMemory()` update the registry to the value they just wrote and
|
|
1051
|
+
* call `invalidateListCache()` themselves.
|
|
1052
|
+
*
|
|
1053
|
+
* **Delete, do not pre-fill.** Even when the event carries a body we drop the
|
|
1054
|
+
* registry entry rather than writing the body into it: the body reaching us
|
|
1055
|
+
* is a snapshot of *someone else's* write, already possibly superseded, and
|
|
1056
|
+
* pre-filling would race with the true head and require us to re-canonicalise
|
|
1057
|
+
* a definition we did not load. Lazy invalidation is the safer default —
|
|
1058
|
+
* `get()` then falls through to the loaders / repository, which is where the
|
|
1059
|
+
* truth is. (This paragraph is the rationale `applyRepoEvent` carried since
|
|
1060
|
+
* ADR-0008 PR-6; #5109 extended the same choice to the cluster path, #5218 to
|
|
1061
|
+
* the filesystem watcher — where "the truth" is the file chokidar just
|
|
1062
|
+
* reported, served by the `FilesystemLoader` the registry entry was shadowing.)
|
|
1063
|
+
*
|
|
1064
|
+
* `name` is optional because `MetadataWatchEvent.name` is: a nameless event
|
|
1065
|
+
* cannot address a registry entry, so it invalidates the list cache only.
|
|
1066
|
+
* Dropping the whole type store instead would evict `registerInMemory()`
|
|
1067
|
+
* artefacts (code-owned datasources, ADR-0015 Addendum) that no loader can
|
|
1068
|
+
* restore — an unrecoverable loss in exchange for a guess.
|
|
1069
|
+
*/
|
|
1070
|
+
protected invalidateForForeignWrite(type: string, name?: string): void;
|
|
542
1071
|
/** Translate a repo event to the legacy MetadataWatchEvent + invalidate caches. */
|
|
543
1072
|
private applyRepoEvent;
|
|
544
1073
|
protected notifyWatchers(type: string, event: MetadataWatchEvent): void;
|
|
@@ -1052,6 +1581,51 @@ declare class DatabaseLoader implements MetadataLoader {
|
|
|
1052
1581
|
* Convert a database row to a MetadataRecord-like object.
|
|
1053
1582
|
*/
|
|
1054
1583
|
private rowToRecord;
|
|
1584
|
+
/**
|
|
1585
|
+
* Decide what a failed READ against {@link tableName} means, and rethrow
|
|
1586
|
+
* unless it is the ONE benign reason.
|
|
1587
|
+
*
|
|
1588
|
+
* #5108 (rule from #4632; same shape as #4728 and #4825) — discriminate by
|
|
1589
|
+
* error TYPE. Every read method below used to `catch {}` into its own empty
|
|
1590
|
+
* value: `load` → `null`, `loadMany` → `[]`, `exists` → `false`, `stat` →
|
|
1591
|
+
* `null`, `list` → `[]`. That made a database the metadata plane cannot
|
|
1592
|
+
* reach **indistinguishable** from an environment where nothing of that type
|
|
1593
|
+
* was ever declared — and it erased the failure *inside the loader*, so
|
|
1594
|
+
* neither `MetadataManager`'s own `try/catch` degradation branches nor
|
|
1595
|
+
* {@link import('../metadata-manager.js').MetadataManager.loadDiagnosed}
|
|
1596
|
+
* (ADR-0110 D3, whose whole purpose is to tell a miss from an outage) could
|
|
1597
|
+
* report anything. Nowhere on the chain was there a line saying the read
|
|
1598
|
+
* failed.
|
|
1599
|
+
*
|
|
1600
|
+
* Why that is worse than a noisy error: every consumer that gates on a
|
|
1601
|
+
* *declared set* — permissions, sharing rules, policies, endpoint
|
|
1602
|
+
* declarations — reads the empty answer as "the author declared none". Some
|
|
1603
|
+
* then fail open (grant), some fail closed (lock out); both look healthy
|
|
1604
|
+
* from outside. This is the AGENTS.md → "Degradation log levels" shape the
|
|
1605
|
+
* repo has already paid for twice, one layer up from #4825.
|
|
1606
|
+
*
|
|
1607
|
+
* Exactly one failure reason is benign: `sys_metadata` has not been
|
|
1608
|
+
* provisioned yet. There are then genuinely no rows, so "nothing declared"
|
|
1609
|
+
* IS the truth, and a first boot must not explode. Every other reason —
|
|
1610
|
+
* connection drop, timeout, insufficient privileges, malformed query — means
|
|
1611
|
+
* the rows may well be there and simply were not seen.
|
|
1612
|
+
*
|
|
1613
|
+
* Classification is conservative in the same direction as
|
|
1614
|
+
* {@link isMissingTableError} itself: an unrecognised error is NOT benign.
|
|
1615
|
+
* A false "benign" silently mis-answers a security question; a false "real"
|
|
1616
|
+
* costs one loud error.
|
|
1617
|
+
*
|
|
1618
|
+
* @param error The value thrown by `_find` / `_findOne` / `_count`.
|
|
1619
|
+
* @throws The underlying driver error, unchanged — deliberately, matching
|
|
1620
|
+
* {@link nextEventSeq}. The loader does not log it: the caller owns
|
|
1621
|
+
* the consequence and is the only layer that knows what an
|
|
1622
|
+
* incomplete answer costs it (`MetadataManager.list()` reports it at
|
|
1623
|
+
* `error`; `listForIndex()`/`matchEndpoint` let it propagate so an
|
|
1624
|
+
* outage can never be served as a 404).
|
|
1625
|
+
* @returns normally ONLY for the benign case, licensing the caller to answer
|
|
1626
|
+
* with its empty value.
|
|
1627
|
+
*/
|
|
1628
|
+
private rethrowUnlessTableUnprovisioned;
|
|
1055
1629
|
load(type: string, name: string, _options?: MetadataLoadOptions): Promise<MetadataLoadResult>;
|
|
1056
1630
|
loadMany<T = any>(type: string, _options?: MetadataLoadOptions): Promise<T[]>;
|
|
1057
1631
|
exists(type: string, name: string): Promise<boolean>;
|