@fabricorg/ports 0.2.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.
- package/CHANGELOG.md +166 -0
- package/README.md +17 -1
- package/dist/catalog.cjs +457 -0
- package/dist/catalog.cjs.map +1 -0
- package/dist/catalog.d.cts +53 -0
- package/dist/catalog.d.ts +53 -0
- package/dist/catalog.js +77 -0
- package/dist/catalog.js.map +1 -0
- package/dist/chunk-FH6OL7JX.js +657 -0
- package/dist/chunk-FH6OL7JX.js.map +1 -0
- package/dist/index.cjs +480 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +309 -2
- package/dist/index.d.ts +309 -2
- package/dist/index.js +32 -187
- package/dist/index.js.map +1 -1
- package/package.json +13 -2
package/dist/index.d.ts
CHANGED
|
@@ -54,6 +54,270 @@ interface DesignTokensPort {
|
|
|
54
54
|
* and what makes a theme file readable.
|
|
55
55
|
*/
|
|
56
56
|
declare function resolveDesignTokens(document: DtcgGroup): ResolvedToken[];
|
|
57
|
+
/**
|
|
58
|
+
* Actor kinds a verified credential can resolve to. This mirrors `ActorType` in
|
|
59
|
+
* `@fabricorg/platform`, restated here so this package keeps zero runtime
|
|
60
|
+
* dependencies. `identityPortChecks` asserts the two stay identical.
|
|
61
|
+
*/
|
|
62
|
+
type IdentityActorType = "natural_person" | "agent" | "system" | "service_account" | "external_system" | "integration";
|
|
63
|
+
declare const IDENTITY_ACTOR_TYPES: readonly IdentityActorType[];
|
|
64
|
+
/**
|
|
65
|
+
* The scopes a credential covers. `"tenant-wide"` is spelled out rather than
|
|
66
|
+
* left as an absent field, so a credential that simply forgot to carry space
|
|
67
|
+
* coverage can never be read as covering everything.
|
|
68
|
+
*/
|
|
69
|
+
type ActorSpaceCoverage = readonly string[] | "tenant-wide";
|
|
70
|
+
/**
|
|
71
|
+
* Identity resolved from a verified credential. Every governed action,
|
|
72
|
+
* projection decision, grant and audit record derives from actor context, so
|
|
73
|
+
* this is the shape a gateway must produce before the platform is called.
|
|
74
|
+
*
|
|
75
|
+
* It carries no credential material. `credentialId` is an opaque, non-secret
|
|
76
|
+
* reference retained for audit, matching the convention used by
|
|
77
|
+
* `authorizationBindingId`.
|
|
78
|
+
*/
|
|
79
|
+
interface ActorClaims {
|
|
80
|
+
/** Stable subject identifier; becomes `actorId` on a governed submission. */
|
|
81
|
+
subject: string;
|
|
82
|
+
actorType: IdentityActorType;
|
|
83
|
+
tenantId: string;
|
|
84
|
+
spaceIds: ActorSpaceCoverage;
|
|
85
|
+
/** Granted scopes, if the issuer expresses authority that way. */
|
|
86
|
+
scopes?: readonly string[];
|
|
87
|
+
issuer: string;
|
|
88
|
+
/** RFC 3339 timestamps. */
|
|
89
|
+
issuedAt: string;
|
|
90
|
+
expiresAt: string;
|
|
91
|
+
/** Opaque, non-secret reference to the presented credential, for audit. */
|
|
92
|
+
credentialId?: string;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Verifies a presented credential and resolves it to actor claims.
|
|
96
|
+
*
|
|
97
|
+
* An implementation returns `null` for any credential it cannot positively
|
|
98
|
+
* verify — expired, malformed, wrong issuer, bad signature. It never returns
|
|
99
|
+
* partially trusted claims, and it never echoes credential material back.
|
|
100
|
+
*/
|
|
101
|
+
interface IdentityPort {
|
|
102
|
+
verify(credential: string): Promise<ActorClaims | null>;
|
|
103
|
+
}
|
|
104
|
+
type ActorScopeRejection = "malformed_claims" | "expired" | "not_yet_valid" | "tenant_mismatch" | "space_not_covered" | "unknown_actor_type";
|
|
105
|
+
declare class ActorScopeError extends Error {
|
|
106
|
+
readonly rejection: ActorScopeRejection;
|
|
107
|
+
readonly name = "ActorScopeError";
|
|
108
|
+
constructor(rejection: ActorScopeRejection, message: string);
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Confirm verified claims actually cover the tenant and space being acted on.
|
|
112
|
+
*
|
|
113
|
+
* Verification proves who the caller is. It does not prove they may act here.
|
|
114
|
+
* Calling this before a governed submission is what stops a valid credential
|
|
115
|
+
* for one tenant from being replayed against another.
|
|
116
|
+
*/
|
|
117
|
+
declare function assertActorClaimsCoverScope(claims: ActorClaims, scope: {
|
|
118
|
+
tenantId: string;
|
|
119
|
+
spaceId: string;
|
|
120
|
+
}, now?: Date): void;
|
|
121
|
+
/**
|
|
122
|
+
* The actor fields a governed submission needs, derived from verified claims
|
|
123
|
+
* after {@link assertActorClaimsCoverScope} has accepted them. Taking these
|
|
124
|
+
* from claims rather than from request input is what keeps the audit trail
|
|
125
|
+
* tied to something that was actually proven.
|
|
126
|
+
*/
|
|
127
|
+
declare function actorContextFromClaims(claims: ActorClaims, scope: {
|
|
128
|
+
tenantId: string;
|
|
129
|
+
spaceId: string;
|
|
130
|
+
}, now?: Date): {
|
|
131
|
+
actorId: string;
|
|
132
|
+
actorType: IdentityActorType;
|
|
133
|
+
tenantId: string;
|
|
134
|
+
spaceId: string;
|
|
135
|
+
};
|
|
136
|
+
/**
|
|
137
|
+
* A resolved piece of authored content.
|
|
138
|
+
*
|
|
139
|
+
* Content is copy and layout, never permission. A content system deciding what
|
|
140
|
+
* a screen says is expected; a content system deciding what a screen may reach
|
|
141
|
+
* is the failure this whole boundary exists to prevent, which is why nothing
|
|
142
|
+
* here carries a capability reference.
|
|
143
|
+
*/
|
|
144
|
+
interface ContentEntry {
|
|
145
|
+
key: string;
|
|
146
|
+
locale: string;
|
|
147
|
+
/** Opaque revision, stable for identical content. Use it for cache keys. */
|
|
148
|
+
revision: string;
|
|
149
|
+
value: JsonLike;
|
|
150
|
+
}
|
|
151
|
+
type JsonLike = string | number | boolean | null | JsonLike[] | {
|
|
152
|
+
[key: string]: JsonLike;
|
|
153
|
+
};
|
|
154
|
+
interface ContentQuery {
|
|
155
|
+
key: string;
|
|
156
|
+
locale: string;
|
|
157
|
+
/** Optional variant, for an experiment arm already enumerated in a release. */
|
|
158
|
+
variant?: string;
|
|
159
|
+
}
|
|
160
|
+
interface ContentPort {
|
|
161
|
+
/** Resolves to `null` when the key is not authored, rather than inventing a default. */
|
|
162
|
+
resolve(query: ContentQuery): Promise<ContentEntry | null>;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Money as an integer in the currency's minor unit.
|
|
166
|
+
*
|
|
167
|
+
* Never a float. A payment expressed as 10.1 is a payment that will eventually
|
|
168
|
+
* be off by a cent, and reconciling that costs more than the type ever did.
|
|
169
|
+
*/
|
|
170
|
+
interface MinorUnitAmount {
|
|
171
|
+
/** Integer in the minor unit: 1050 is USD 10.50. */
|
|
172
|
+
amount: number;
|
|
173
|
+
/** ISO 4217 alphabetic code. */
|
|
174
|
+
currency: string;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* The outcome of asking a provider to move money.
|
|
178
|
+
*
|
|
179
|
+
* `ambiguous` is the one that matters and the one most interfaces omit. A
|
|
180
|
+
* timeout tells you nothing about whether the charge landed, and guessing is
|
|
181
|
+
* how a customer gets billed twice. It maps onto the platform's own
|
|
182
|
+
* `AdapterOutcomeKind`, so an ambiguous payment routes to reconciliation with
|
|
183
|
+
* durable evidence instead of being retried.
|
|
184
|
+
*/
|
|
185
|
+
type PaymentOutcome = "succeeded" | "failed" | "ambiguous";
|
|
186
|
+
interface PaymentResult {
|
|
187
|
+
outcome: PaymentOutcome;
|
|
188
|
+
/** The provider's own identifier, for reconciliation. Required when known. */
|
|
189
|
+
providerReference?: string;
|
|
190
|
+
/** Provider-supplied reason, for evidence rather than for branching. */
|
|
191
|
+
reason?: string;
|
|
192
|
+
}
|
|
193
|
+
interface PaymentRequest {
|
|
194
|
+
/** Caller-stable key. The same key must never move money twice. */
|
|
195
|
+
idempotencyKey: string;
|
|
196
|
+
amount: MinorUnitAmount;
|
|
197
|
+
}
|
|
198
|
+
interface PaymentsPort {
|
|
199
|
+
charge(request: PaymentRequest): Promise<PaymentResult>;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* A latency-critical read path, deliberately outside the governed pipeline.
|
|
203
|
+
*
|
|
204
|
+
* Search results are pointers, not authority. An identifier appearing in a
|
|
205
|
+
* result set says the index knows about it, never that this actor may read it,
|
|
206
|
+
* so anything the viewer then opens still goes through `ProjectionHost`.
|
|
207
|
+
*/
|
|
208
|
+
interface SearchHit {
|
|
209
|
+
/** Identifier a governed read can resolve. Never the record itself. */
|
|
210
|
+
id: string;
|
|
211
|
+
score: number;
|
|
212
|
+
}
|
|
213
|
+
interface SearchQuery {
|
|
214
|
+
text: string;
|
|
215
|
+
limit: number;
|
|
216
|
+
/** Narrowing the index can apply cheaply. Never a substitute for authorization. */
|
|
217
|
+
filters?: Record<string, string>;
|
|
218
|
+
}
|
|
219
|
+
interface SearchPort {
|
|
220
|
+
query(query: SearchQuery): Promise<{
|
|
221
|
+
hits: SearchHit[];
|
|
222
|
+
total: number;
|
|
223
|
+
}>;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* An outbound record of something that already happened.
|
|
227
|
+
*
|
|
228
|
+
* A CRM is downstream of the governed pipeline, never upstream of it. It is
|
|
229
|
+
* told; it does not decide. Keeping this one-way is what stops a marketing
|
|
230
|
+
* system becoming a source of business truth.
|
|
231
|
+
*/
|
|
232
|
+
interface CustomerEvent {
|
|
233
|
+
/** Stable key so redelivery does not duplicate the record. */
|
|
234
|
+
eventId: string;
|
|
235
|
+
subjectId: string;
|
|
236
|
+
type: string;
|
|
237
|
+
occurredAt: string;
|
|
238
|
+
attributes?: Record<string, JsonLike>;
|
|
239
|
+
}
|
|
240
|
+
interface CustomerRecordPort {
|
|
241
|
+
record(event: CustomerEvent): Promise<void>;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* A port, as a first-class thing rather than a name in a string.
|
|
245
|
+
*
|
|
246
|
+
* Fabric ships a handful of port definitions because they speak open standards
|
|
247
|
+
* and everyone needs them. It cannot ship the rest: an enterprise integrates
|
|
248
|
+
* hundreds of external systems, and a framework that requires a pull request
|
|
249
|
+
* for each one is not a framework. A vertical, a partner or a vendor defines
|
|
250
|
+
* its own port with {@link definePort} and every mechanism here applies to it
|
|
251
|
+
* unchanged — Fabric never learns what the system behind it is.
|
|
252
|
+
*
|
|
253
|
+
* The definition binds an id to the suite that certifies an adapter for it.
|
|
254
|
+
* That binding is the point: without it, "this port is satisfied" only ever
|
|
255
|
+
* meant "somebody registered something claiming to be it".
|
|
256
|
+
*/
|
|
257
|
+
interface PortDefinition<TPort, TFixtures = void> {
|
|
258
|
+
/**
|
|
259
|
+
* Namespaced identifier, for example `fabric.flags` or `acme.docusign`.
|
|
260
|
+
* Namespacing is required so a port defined outside this repository cannot
|
|
261
|
+
* collide with one defined inside it.
|
|
262
|
+
*/
|
|
263
|
+
id: string;
|
|
264
|
+
/**
|
|
265
|
+
* Version of the port contract itself, not of any adapter.
|
|
266
|
+
*
|
|
267
|
+
* Bump it when the interface changes shape. Certification is recorded
|
|
268
|
+
* against a version, so raising it correctly invalidates every adapter
|
|
269
|
+
* certified against the old contract instead of silently carrying them
|
|
270
|
+
* forward.
|
|
271
|
+
*/
|
|
272
|
+
version: string;
|
|
273
|
+
/** The open standard this port speaks, where one exists. */
|
|
274
|
+
standard?: {
|
|
275
|
+
name: string;
|
|
276
|
+
version?: string;
|
|
277
|
+
};
|
|
278
|
+
/** One-line statement of what an adapter behind this port is responsible for. */
|
|
279
|
+
description: string;
|
|
280
|
+
/** The suite every adapter must pass. */
|
|
281
|
+
checks(fixtures: TFixtures): readonly PortCheck<TPort>[];
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Declare a port. Validates the definition itself, because a malformed port
|
|
285
|
+
* definition produces adapters that certify against nothing.
|
|
286
|
+
*/
|
|
287
|
+
declare function definePort<TPort, TFixtures = void>(definition: PortDefinition<TPort, TFixtures>): PortDefinition<TPort, TFixtures>;
|
|
288
|
+
/**
|
|
289
|
+
* Confirm a port's suite can actually fail, and that its check ids are unique.
|
|
290
|
+
*
|
|
291
|
+
* A suite whose checks all pass against a deliberately broken adapter certifies
|
|
292
|
+
* nothing while looking rigorous, which is worse than having no suite at all.
|
|
293
|
+
* Run this against a stub that does the wrong thing when you author a port.
|
|
294
|
+
*/
|
|
295
|
+
declare function assertPortSuiteHasTeeth<TPort, TFixtures>(definition: PortDefinition<TPort, TFixtures>, fixtures: TFixtures, brokenAdapters: TPort | readonly TPort[]): Promise<void>;
|
|
296
|
+
/** Evidence that an adapter passed a port's suite, and which contract it passed. */
|
|
297
|
+
interface AdapterCertification {
|
|
298
|
+
portId: string;
|
|
299
|
+
/** Port contract version the adapter was certified against. */
|
|
300
|
+
portVersion: string;
|
|
301
|
+
checks: string[];
|
|
302
|
+
certifiedAt: string;
|
|
303
|
+
}
|
|
304
|
+
interface CertifiedAdapter extends RegisteredAdapter {
|
|
305
|
+
certification: AdapterCertification;
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Run a port's suite against an adapter and record what it passed.
|
|
309
|
+
*
|
|
310
|
+
* Registration is a claim; certification is evidence. Deployment gates can then
|
|
311
|
+
* require the second rather than accepting the first.
|
|
312
|
+
*/
|
|
313
|
+
declare function certifyAdapter<TPort, TFixtures>(input: {
|
|
314
|
+
definition: PortDefinition<TPort, TFixtures>;
|
|
315
|
+
fixtures: TFixtures;
|
|
316
|
+
adapter: TPort;
|
|
317
|
+
vendor: string;
|
|
318
|
+
version?: string;
|
|
319
|
+
now?: Date;
|
|
320
|
+
}): Promise<CertifiedAdapter>;
|
|
57
321
|
interface PortRequirement {
|
|
58
322
|
name: string;
|
|
59
323
|
version?: string;
|
|
@@ -79,7 +343,7 @@ interface RegisteredAdapter {
|
|
|
79
343
|
version?: string;
|
|
80
344
|
};
|
|
81
345
|
}
|
|
82
|
-
type PortFindingCode = "unsatisfied_port" | "standard_not_spoken" | "version_mismatch";
|
|
346
|
+
type PortFindingCode = "unsatisfied_port" | "standard_not_spoken" | "version_mismatch" | "uncertified_adapter" | "stale_certification";
|
|
83
347
|
interface PortFinding {
|
|
84
348
|
code: PortFindingCode;
|
|
85
349
|
port: string;
|
|
@@ -100,10 +364,23 @@ interface PortValidationResult {
|
|
|
100
364
|
declare function validatePortRequirements(input: {
|
|
101
365
|
capability: PortRequiringCapability;
|
|
102
366
|
adapters: readonly RegisteredAdapter[];
|
|
367
|
+
/**
|
|
368
|
+
* Port definitions in force. Supplying them checks that whatever
|
|
369
|
+
* certification is present was issued against the current contract version.
|
|
370
|
+
*
|
|
371
|
+
* They do not by themselves require certification to exist: pass
|
|
372
|
+
* `requireCertification` for that. Definitions alone catch a stale
|
|
373
|
+
* certification, not a missing one, and an adapter carrying none passes.
|
|
374
|
+
*/
|
|
375
|
+
definitions?: readonly PortDefinition<never, never>[];
|
|
376
|
+
/** Require certification for every required port. Defaults to false. */
|
|
377
|
+
requireCertification?: boolean;
|
|
103
378
|
}): PortValidationResult;
|
|
104
379
|
declare function assertPortRequirementsSatisfied(input: {
|
|
105
380
|
capability: PortRequiringCapability;
|
|
106
381
|
adapters: readonly RegisteredAdapter[];
|
|
382
|
+
definitions?: readonly PortDefinition<never, never>[];
|
|
383
|
+
requireCertification?: boolean;
|
|
107
384
|
}): void;
|
|
108
385
|
interface PortCheck<TPort> {
|
|
109
386
|
id: string;
|
|
@@ -116,6 +393,36 @@ interface PortCheck<TPort> {
|
|
|
116
393
|
* plus a configuration change rather than a migration.
|
|
117
394
|
*/
|
|
118
395
|
declare function flagsPortChecks(): PortCheck<FlagsPort>[];
|
|
396
|
+
/**
|
|
397
|
+
* The suite an identity adapter must pass before it can stand behind
|
|
398
|
+
* `IdentityPort`. Every check here is a fail-closed property: the cost of
|
|
399
|
+
* getting one wrong is a credential being trusted further than it proves.
|
|
400
|
+
*
|
|
401
|
+
* `validCredential` must be a credential the adapter verifies successfully,
|
|
402
|
+
* and `expected` the claims it should resolve to.
|
|
403
|
+
*/
|
|
404
|
+
declare function identityPortChecks(fixtures: {
|
|
405
|
+
validCredential: string;
|
|
406
|
+
expected: Pick<ActorClaims, "subject" | "tenantId">;
|
|
407
|
+
expiredCredential?: string;
|
|
408
|
+
}): PortCheck<IdentityPort>[];
|
|
409
|
+
/** The suite a content adapter must pass. */
|
|
410
|
+
declare function contentPortChecks(fixtures: {
|
|
411
|
+
present: ContentQuery;
|
|
412
|
+
absentKey: string;
|
|
413
|
+
}): PortCheck<ContentPort>[];
|
|
414
|
+
/** The suite a payments adapter must pass. */
|
|
415
|
+
declare function paymentsPortChecks(fixtures: {
|
|
416
|
+
request: PaymentRequest;
|
|
417
|
+
}): PortCheck<PaymentsPort>[];
|
|
418
|
+
/** The suite a search adapter must pass. */
|
|
419
|
+
declare function searchPortChecks(fixtures: {
|
|
420
|
+
query: SearchQuery;
|
|
421
|
+
}): PortCheck<SearchPort>[];
|
|
422
|
+
/** The suite a customer-record adapter must pass. */
|
|
423
|
+
declare function customerRecordPortChecks(fixtures: {
|
|
424
|
+
event: CustomerEvent;
|
|
425
|
+
}): PortCheck<CustomerRecordPort>[];
|
|
119
426
|
declare function designTokensPortChecks(theme: string): PortCheck<DesignTokensPort>[];
|
|
120
427
|
/** Runs a contract suite and returns every failure, rather than stopping at the first. */
|
|
121
428
|
declare function runPortContract<TPort>(port: TPort, checks: readonly PortCheck<TPort>[]): Promise<{
|
|
@@ -126,4 +433,4 @@ declare function runPortContract<TPort>(port: TPort, checks: readonly PortCheck<
|
|
|
126
433
|
}>;
|
|
127
434
|
}>;
|
|
128
435
|
|
|
129
|
-
export { type DesignTokensPort, type DtcgGroup, type DtcgToken, type EvaluationContext, type FlagValue, type FlagsPort, type PortCheck, type PortFinding, type PortFindingCode, type PortRequirement, type PortRequiringCapability, type PortValidationResult, type RegisteredAdapter, type ResolutionDetails, type ResolutionReason, type ResolvedToken, assertPortRequirementsSatisfied, designTokensPortChecks, flagsPortChecks, resolveDesignTokens, runPortContract, validatePortRequirements };
|
|
436
|
+
export { type ActorClaims, ActorScopeError, type ActorScopeRejection, type ActorSpaceCoverage, type AdapterCertification, type CertifiedAdapter, type ContentEntry, type ContentPort, type ContentQuery, type CustomerEvent, type CustomerRecordPort, type DesignTokensPort, type DtcgGroup, type DtcgToken, type EvaluationContext, type FlagValue, type FlagsPort, IDENTITY_ACTOR_TYPES, type IdentityActorType, type IdentityPort, type JsonLike, type MinorUnitAmount, type PaymentOutcome, type PaymentRequest, type PaymentResult, type PaymentsPort, type PortCheck, type PortDefinition, type PortFinding, type PortFindingCode, type PortRequirement, type PortRequiringCapability, type PortValidationResult, type RegisteredAdapter, type ResolutionDetails, type ResolutionReason, type ResolvedToken, type SearchHit, type SearchPort, type SearchQuery, actorContextFromClaims, assertActorClaimsCoverScope, assertPortRequirementsSatisfied, assertPortSuiteHasTeeth, certifyAdapter, contentPortChecks, customerRecordPortChecks, definePort, designTokensPortChecks, flagsPortChecks, identityPortChecks, paymentsPortChecks, resolveDesignTokens, runPortContract, searchPortChecks, validatePortRequirements };
|
package/dist/index.js
CHANGED
|
@@ -1,196 +1,41 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const resolved = /* @__PURE__ */ new Map();
|
|
22
|
-
const valueOf = (name) => {
|
|
23
|
-
if (resolved.has(name)) return resolved.get(name);
|
|
24
|
-
const entry = flat.get(name);
|
|
25
|
-
if (!entry) throw new Error(`Design token alias "{${name}}" does not resolve to a declared token.`);
|
|
26
|
-
if (resolving.has(name)) {
|
|
27
|
-
throw new Error(`Design token alias cycle: ${[...resolving, name].join(" -> ")}.`);
|
|
28
|
-
}
|
|
29
|
-
const raw = entry.token.$value;
|
|
30
|
-
if (typeof raw !== "string") {
|
|
31
|
-
resolved.set(name, raw);
|
|
32
|
-
return raw;
|
|
33
|
-
}
|
|
34
|
-
const alias = ALIAS.exec(raw.trim());
|
|
35
|
-
if (!alias) {
|
|
36
|
-
resolved.set(name, raw);
|
|
37
|
-
return raw;
|
|
38
|
-
}
|
|
39
|
-
resolving.add(name);
|
|
40
|
-
const target = valueOf(alias[1]);
|
|
41
|
-
resolving.delete(name);
|
|
42
|
-
resolved.set(name, target);
|
|
43
|
-
return target;
|
|
44
|
-
};
|
|
45
|
-
return [...flat.entries()].map(([name, entry]) => {
|
|
46
|
-
const value = valueOf(name);
|
|
47
|
-
return {
|
|
48
|
-
name,
|
|
49
|
-
value,
|
|
50
|
-
...entry.type === void 0 ? {} : { type: entry.type },
|
|
51
|
-
...entry.token.$description === void 0 ? {} : { description: entry.token.$description }
|
|
52
|
-
};
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
function validatePortRequirements(input) {
|
|
56
|
-
const findings = [];
|
|
57
|
-
for (const requirement of input.capability.requirements?.ports ?? []) {
|
|
58
|
-
const candidates = input.adapters.filter((adapter) => adapter.port === requirement.name);
|
|
59
|
-
if (candidates.length === 0) {
|
|
60
|
-
findings.push({
|
|
61
|
-
code: "unsatisfied_port",
|
|
62
|
-
port: requirement.name,
|
|
63
|
-
message: `capability "${input.capability.namespace}" requires port "${requirement.name}" and no adapter is registered for it`
|
|
64
|
-
});
|
|
65
|
-
continue;
|
|
66
|
-
}
|
|
67
|
-
if (requirement.standard) {
|
|
68
|
-
const speaking = candidates.filter((adapter) => adapter.standard?.name === requirement.standard.name);
|
|
69
|
-
if (speaking.length === 0) {
|
|
70
|
-
findings.push({
|
|
71
|
-
code: "standard_not_spoken",
|
|
72
|
-
port: requirement.name,
|
|
73
|
-
message: `port "${requirement.name}" must speak "${requirement.standard.name}"; registered adapters are ${candidates.map((adapter) => `${adapter.vendor}(${adapter.standard?.name ?? "no standard"})`).join(", ")}`
|
|
74
|
-
});
|
|
75
|
-
continue;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
if (requirement.version && !candidates.some((adapter) => adapter.version === requirement.version)) {
|
|
79
|
-
findings.push({
|
|
80
|
-
code: "version_mismatch",
|
|
81
|
-
port: requirement.name,
|
|
82
|
-
message: `port "${requirement.name}" requires version "${requirement.version}"; registered adapters are ${candidates.map((adapter) => `${adapter.vendor}@${adapter.version ?? "unversioned"}`).join(", ")}`
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
return { valid: findings.length === 0, findings };
|
|
87
|
-
}
|
|
88
|
-
function assertPortRequirementsSatisfied(input) {
|
|
89
|
-
const result = validatePortRequirements(input);
|
|
90
|
-
if (result.valid) return;
|
|
91
|
-
throw new Error([
|
|
92
|
-
`Port requirements for "${input.capability.namespace}" are not satisfied:`,
|
|
93
|
-
...result.findings.map((finding) => ` - ${finding.message}`)
|
|
94
|
-
].join("\n"));
|
|
95
|
-
}
|
|
96
|
-
var PortContractFailure = class extends Error {
|
|
97
|
-
name = "PortContractFailure";
|
|
98
|
-
};
|
|
99
|
-
function expect(condition, message) {
|
|
100
|
-
if (!condition) throw new PortContractFailure(message);
|
|
101
|
-
}
|
|
102
|
-
function flagsPortChecks() {
|
|
103
|
-
const unknownKey = "fabric.contract.definitely-not-configured";
|
|
104
|
-
return [
|
|
105
|
-
{
|
|
106
|
-
id: "flags.default-on-unknown-key",
|
|
107
|
-
title: "an unknown flag resolves to the supplied default rather than throwing",
|
|
108
|
-
async run(port) {
|
|
109
|
-
const result = await port.resolveBoolean(unknownKey, true);
|
|
110
|
-
expect(result.value === true, `an unknown flag returned ${String(result.value)} instead of the supplied default`);
|
|
111
|
-
expect(
|
|
112
|
-
result.reason === "DEFAULT" || result.reason === "ERROR",
|
|
113
|
-
`an unknown flag resolved with reason "${result.reason}"; a default or error was expected`
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
|
-
},
|
|
117
|
-
{
|
|
118
|
-
id: "flags.default-is-typed",
|
|
119
|
-
title: "each typed resolver returns its own type",
|
|
120
|
-
async run(port) {
|
|
121
|
-
expect(typeof (await port.resolveBoolean(unknownKey, false)).value === "boolean", "resolveBoolean did not return a boolean");
|
|
122
|
-
expect(typeof (await port.resolveString(unknownKey, "fallback")).value === "string", "resolveString did not return a string");
|
|
123
|
-
expect(typeof (await port.resolveNumber(unknownKey, 42)).value === "number", "resolveNumber did not return a number");
|
|
124
|
-
}
|
|
125
|
-
},
|
|
126
|
-
{
|
|
127
|
-
id: "flags.evaluation-is-pure",
|
|
128
|
-
title: "the same key and context resolve the same way twice",
|
|
129
|
-
async run(port) {
|
|
130
|
-
const context = { targetingKey: "fabric-contract-subject" };
|
|
131
|
-
const first = await port.resolveString(unknownKey, "fallback", context);
|
|
132
|
-
const second = await port.resolveString(unknownKey, "fallback", context);
|
|
133
|
-
expect(first.value === second.value, `the same evaluation returned "${first.value}" then "${second.value}"`);
|
|
134
|
-
}
|
|
135
|
-
},
|
|
136
|
-
{
|
|
137
|
-
id: "flags.tolerates-absent-context",
|
|
138
|
-
title: "evaluation without a context does not throw",
|
|
139
|
-
async run(port) {
|
|
140
|
-
await port.resolveBoolean(unknownKey, false);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
];
|
|
144
|
-
}
|
|
145
|
-
function designTokensPortChecks(theme) {
|
|
146
|
-
return [
|
|
147
|
-
{
|
|
148
|
-
id: "tokens.theme-resolves",
|
|
149
|
-
title: "a known theme resolves to at least one token",
|
|
150
|
-
async run(port) {
|
|
151
|
-
const tokens = await port.resolve(theme);
|
|
152
|
-
expect(Array.isArray(tokens) && tokens.length > 0, `theme "${theme}" resolved to no tokens`);
|
|
153
|
-
}
|
|
154
|
-
},
|
|
155
|
-
{
|
|
156
|
-
id: "tokens.no-unresolved-aliases",
|
|
157
|
-
title: "no resolved token still carries an alias",
|
|
158
|
-
async run(port) {
|
|
159
|
-
for (const token of await port.resolve(theme)) {
|
|
160
|
-
expect(
|
|
161
|
-
typeof token.value !== "string" || !ALIAS.test(token.value.trim()),
|
|
162
|
-
`token "${token.name}" resolved to the unfollowed alias ${String(token.value)}`
|
|
163
|
-
);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
},
|
|
167
|
-
{
|
|
168
|
-
id: "tokens.names-are-unique",
|
|
169
|
-
title: "token names are unique within a theme",
|
|
170
|
-
async run(port) {
|
|
171
|
-
const names = (await port.resolve(theme)).map((token) => token.name);
|
|
172
|
-
expect(new Set(names).size === names.length, "the theme resolved duplicate token names");
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
];
|
|
176
|
-
}
|
|
177
|
-
async function runPortContract(port, checks) {
|
|
178
|
-
const failures = [];
|
|
179
|
-
for (const check of checks) {
|
|
180
|
-
try {
|
|
181
|
-
await check.run(port);
|
|
182
|
-
} catch (error) {
|
|
183
|
-
failures.push({ id: check.id, message: error instanceof Error ? error.message : String(error) });
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
return { passed: failures.length === 0, failures };
|
|
187
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
ActorScopeError,
|
|
3
|
+
IDENTITY_ACTOR_TYPES,
|
|
4
|
+
actorContextFromClaims,
|
|
5
|
+
assertActorClaimsCoverScope,
|
|
6
|
+
assertPortRequirementsSatisfied,
|
|
7
|
+
assertPortSuiteHasTeeth,
|
|
8
|
+
certifyAdapter,
|
|
9
|
+
contentPortChecks,
|
|
10
|
+
customerRecordPortChecks,
|
|
11
|
+
definePort,
|
|
12
|
+
designTokensPortChecks,
|
|
13
|
+
flagsPortChecks,
|
|
14
|
+
identityPortChecks,
|
|
15
|
+
paymentsPortChecks,
|
|
16
|
+
resolveDesignTokens,
|
|
17
|
+
runPortContract,
|
|
18
|
+
searchPortChecks,
|
|
19
|
+
validatePortRequirements
|
|
20
|
+
} from "./chunk-FH6OL7JX.js";
|
|
188
21
|
export {
|
|
22
|
+
ActorScopeError,
|
|
23
|
+
IDENTITY_ACTOR_TYPES,
|
|
24
|
+
actorContextFromClaims,
|
|
25
|
+
assertActorClaimsCoverScope,
|
|
189
26
|
assertPortRequirementsSatisfied,
|
|
27
|
+
assertPortSuiteHasTeeth,
|
|
28
|
+
certifyAdapter,
|
|
29
|
+
contentPortChecks,
|
|
30
|
+
customerRecordPortChecks,
|
|
31
|
+
definePort,
|
|
190
32
|
designTokensPortChecks,
|
|
191
33
|
flagsPortChecks,
|
|
34
|
+
identityPortChecks,
|
|
35
|
+
paymentsPortChecks,
|
|
192
36
|
resolveDesignTokens,
|
|
193
37
|
runPortContract,
|
|
38
|
+
searchPortChecks,
|
|
194
39
|
validatePortRequirements
|
|
195
40
|
};
|
|
196
41
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../index.ts"],"sourcesContent":["/**\n * Port interfaces are declared here and implemented by vendor adapters elsewhere.\n * Nothing in this package imports a vendor SDK: a port is a shape, and an adapter\n * is anything that satisfies it. Where an open standard exists the port speaks it,\n * so the incumbent vendor is already just one provider behind the interface.\n */\n\n// ── Feature flags (OpenFeature-shaped) ──────────────────────────────────────\n\nexport type FlagValue = boolean | string | number | { [key: string]: unknown };\n\nexport interface EvaluationContext {\n\t/** Stable identifier for the subject of evaluation, usually a tenant or actor. */\n\ttargetingKey?: string;\n\t[attribute: string]: unknown;\n}\n\nexport type ResolutionReason = \"STATIC\" | \"DEFAULT\" | \"TARGETING_MATCH\" | \"SPLIT\" | \"CACHED\" | \"ERROR\";\n\nexport interface ResolutionDetails<T extends FlagValue> {\n\tvalue: T;\n\treason: ResolutionReason;\n\tvariant?: string;\n\terrorCode?: string;\n}\n\n/**\n * Structurally compatible with an OpenFeature provider's evaluation surface, so an\n * OpenFeature provider is a thin adapter rather than a translation layer.\n */\nexport interface FlagsPort {\n\tresolveBoolean(flagKey: string, defaultValue: boolean, context?: EvaluationContext): Promise<ResolutionDetails<boolean>>;\n\tresolveString(flagKey: string, defaultValue: string, context?: EvaluationContext): Promise<ResolutionDetails<string>>;\n\tresolveNumber(flagKey: string, defaultValue: number, context?: EvaluationContext): Promise<ResolutionDetails<number>>;\n}\n\n// ── Design tokens (W3C DTCG) ────────────────────────────────────────────────\n\nexport interface DtcgToken {\n\t$value: unknown;\n\t$type?: string;\n\t$description?: string;\n}\n\nexport interface DtcgGroup {\n\t$type?: string;\n\t$description?: string;\n\t[member: string]: DtcgToken | DtcgGroup | string | undefined;\n}\n\nexport interface ResolvedToken {\n\tname: string;\n\tvalue: unknown;\n\ttype?: string;\n\tdescription?: string;\n}\n\nexport interface DesignTokensPort {\n\t/** Resolved tokens for one theme. Aliases are already followed. */\n\tresolve(theme: string): Promise<ResolvedToken[]>;\n}\n\nconst ALIAS = /^\\{([^}]+)\\}$/;\n\nconst isToken = (value: unknown): value is DtcgToken =>\n\t!!value && typeof value === \"object\" && !Array.isArray(value) && \"$value\" in value;\n\nconst isGroup = (value: unknown): value is DtcgGroup =>\n\t!!value && typeof value === \"object\" && !Array.isArray(value) && !(\"$value\" in value);\n\n/**\n * Flattens a DTCG document and follows aliases. `$type` is inherited from the\n * nearest ancestor group that declares one, which is what the format specifies\n * and what makes a theme file readable.\n */\nexport function resolveDesignTokens(document: DtcgGroup): ResolvedToken[] {\n\tconst flat = new Map<string, { token: DtcgToken; type?: string }>();\n\n\tconst walk = (node: DtcgGroup, path: string[], inheritedType?: string): void => {\n\t\tconst groupType = typeof node.$type === \"string\" ? node.$type : inheritedType;\n\t\tfor (const [key, member] of Object.entries(node)) {\n\t\t\tif (key.startsWith(\"$\")) continue;\n\t\t\tconst next = [...path, key];\n\t\t\tif (isToken(member)) {\n\t\t\t\tflat.set(next.join(\".\"), { token: member, type: member.$type ?? groupType });\n\t\t\t} else if (isGroup(member)) {\n\t\t\t\twalk(member, next, groupType);\n\t\t\t}\n\t\t}\n\t};\n\twalk(document, []);\n\n\tconst resolving = new Set<string>();\n\tconst resolved = new Map<string, unknown>();\n\n\tconst valueOf = (name: string): unknown => {\n\t\tif (resolved.has(name)) return resolved.get(name);\n\t\tconst entry = flat.get(name);\n\t\tif (!entry) throw new Error(`Design token alias \"{${name}}\" does not resolve to a declared token.`);\n\t\tif (resolving.has(name)) {\n\t\t\tthrow new Error(`Design token alias cycle: ${[...resolving, name].join(\" -> \")}.`);\n\t\t}\n\t\tconst raw = entry.token.$value;\n\t\tif (typeof raw !== \"string\") {\n\t\t\tresolved.set(name, raw);\n\t\t\treturn raw;\n\t\t}\n\t\tconst alias = ALIAS.exec(raw.trim());\n\t\tif (!alias) {\n\t\t\tresolved.set(name, raw);\n\t\t\treturn raw;\n\t\t}\n\t\tresolving.add(name);\n\t\tconst target = valueOf(alias[1]!);\n\t\tresolving.delete(name);\n\t\tresolved.set(name, target);\n\t\treturn target;\n\t};\n\n\treturn [...flat.entries()].map(([name, entry]) => {\n\t\tconst value = valueOf(name);\n\t\treturn {\n\t\t\tname,\n\t\t\tvalue,\n\t\t\t...(entry.type === undefined ? {} : { type: entry.type }),\n\t\t\t...(entry.token.$description === undefined ? {} : { description: entry.token.$description }),\n\t\t};\n\t});\n}\n\n// ── Requirement satisfaction ────────────────────────────────────────────────\n\nexport interface PortRequirement {\n\tname: string;\n\tversion?: string;\n\tstandard?: { name: string; version?: string };\n}\n\n/** The subset of capability metadata that declares what ports it needs. */\nexport interface PortRequiringCapability {\n\tnamespace: string;\n\trequirements?: { ports?: PortRequirement[] };\n}\n\nexport interface RegisteredAdapter {\n\t/** Port this adapter implements, matching the requirement's `name`. */\n\tport: string;\n\tvendor: string;\n\tversion?: string;\n\tstandard?: { name: string; version?: string };\n}\n\nexport type PortFindingCode = \"unsatisfied_port\" | \"standard_not_spoken\" | \"version_mismatch\";\n\nexport interface PortFinding {\n\tcode: PortFindingCode;\n\tport: string;\n\tmessage: string;\n}\n\nexport interface PortValidationResult {\n\tvalid: boolean;\n\tfindings: PortFinding[];\n}\n\n/**\n * Checks that every port a capability declares is backed by a registered adapter.\n * `CapabilityPortRequirement` is otherwise a name that resolves against nothing;\n * this is what turns the declaration into a deployment-time gate.\n *\n * Version matching is exact. Range negotiation belongs to whatever installs the\n * adapters, and pretending to do semver here would be worse than not doing it.\n */\nexport function validatePortRequirements(input: {\n\tcapability: PortRequiringCapability;\n\tadapters: readonly RegisteredAdapter[];\n}): PortValidationResult {\n\tconst findings: PortFinding[] = [];\n\tfor (const requirement of input.capability.requirements?.ports ?? []) {\n\t\tconst candidates = input.adapters.filter((adapter) => adapter.port === requirement.name);\n\t\tif (candidates.length === 0) {\n\t\t\tfindings.push({\n\t\t\t\tcode: \"unsatisfied_port\",\n\t\t\t\tport: requirement.name,\n\t\t\t\tmessage: `capability \"${input.capability.namespace}\" requires port \"${requirement.name}\" and no adapter is registered for it`,\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\t\tif (requirement.standard) {\n\t\t\tconst speaking = candidates.filter((adapter) => adapter.standard?.name === requirement.standard!.name);\n\t\t\tif (speaking.length === 0) {\n\t\t\t\tfindings.push({\n\t\t\t\t\tcode: \"standard_not_spoken\",\n\t\t\t\t\tport: requirement.name,\n\t\t\t\t\tmessage: `port \"${requirement.name}\" must speak \"${requirement.standard.name}\"; registered adapters are ${candidates.map((adapter) => `${adapter.vendor}(${adapter.standard?.name ?? \"no standard\"})`).join(\", \")}`,\n\t\t\t\t});\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\t\tif (requirement.version && !candidates.some((adapter) => adapter.version === requirement.version)) {\n\t\t\tfindings.push({\n\t\t\t\tcode: \"version_mismatch\",\n\t\t\t\tport: requirement.name,\n\t\t\t\tmessage: `port \"${requirement.name}\" requires version \"${requirement.version}\"; registered adapters are ${candidates.map((adapter) => `${adapter.vendor}@${adapter.version ?? \"unversioned\"}`).join(\", \")}`,\n\t\t\t});\n\t\t}\n\t}\n\treturn { valid: findings.length === 0, findings };\n}\n\nexport function assertPortRequirementsSatisfied(input: {\n\tcapability: PortRequiringCapability;\n\tadapters: readonly RegisteredAdapter[];\n}): void {\n\tconst result = validatePortRequirements(input);\n\tif (result.valid) return;\n\tthrow new Error([\n\t\t`Port requirements for \"${input.capability.namespace}\" are not satisfied:`,\n\t\t...result.findings.map((finding) => ` - ${finding.message}`),\n\t].join(\"\\n\"));\n}\n\n// ── Adapter contract kit ────────────────────────────────────────────────────\n\nexport interface PortCheck<TPort> {\n\tid: string;\n\ttitle: string;\n\t/** Throws on failure, exactly as an assertion would. */\n\trun(port: TPort): Promise<void>;\n}\n\nclass PortContractFailure extends Error {\n\toverride readonly name = \"PortContractFailure\";\n}\n\nfunction expect(condition: unknown, message: string): asserts condition {\n\tif (!condition) throw new PortContractFailure(message);\n}\n\n/**\n * Every flags adapter must pass these, so swapping vendors is an adapter build\n * plus a configuration change rather than a migration.\n */\nexport function flagsPortChecks(): PortCheck<FlagsPort>[] {\n\tconst unknownKey = \"fabric.contract.definitely-not-configured\";\n\treturn [\n\t\t{\n\t\t\tid: \"flags.default-on-unknown-key\",\n\t\t\ttitle: \"an unknown flag resolves to the supplied default rather than throwing\",\n\t\t\tasync run(port) {\n\t\t\t\tconst result = await port.resolveBoolean(unknownKey, true);\n\t\t\t\texpect(result.value === true, `an unknown flag returned ${String(result.value)} instead of the supplied default`);\n\t\t\t\texpect(\n\t\t\t\t\tresult.reason === \"DEFAULT\" || result.reason === \"ERROR\",\n\t\t\t\t\t`an unknown flag resolved with reason \"${result.reason}\"; a default or error was expected`,\n\t\t\t\t);\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tid: \"flags.default-is-typed\",\n\t\t\ttitle: \"each typed resolver returns its own type\",\n\t\t\tasync run(port) {\n\t\t\t\texpect(typeof (await port.resolveBoolean(unknownKey, false)).value === \"boolean\", \"resolveBoolean did not return a boolean\");\n\t\t\t\texpect(typeof (await port.resolveString(unknownKey, \"fallback\")).value === \"string\", \"resolveString did not return a string\");\n\t\t\t\texpect(typeof (await port.resolveNumber(unknownKey, 42)).value === \"number\", \"resolveNumber did not return a number\");\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tid: \"flags.evaluation-is-pure\",\n\t\t\ttitle: \"the same key and context resolve the same way twice\",\n\t\t\tasync run(port) {\n\t\t\t\tconst context = { targetingKey: \"fabric-contract-subject\" };\n\t\t\t\tconst first = await port.resolveString(unknownKey, \"fallback\", context);\n\t\t\t\tconst second = await port.resolveString(unknownKey, \"fallback\", context);\n\t\t\t\texpect(first.value === second.value, `the same evaluation returned \"${first.value}\" then \"${second.value}\"`);\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tid: \"flags.tolerates-absent-context\",\n\t\t\ttitle: \"evaluation without a context does not throw\",\n\t\t\tasync run(port) {\n\t\t\t\tawait port.resolveBoolean(unknownKey, false);\n\t\t\t},\n\t\t},\n\t];\n}\n\nexport function designTokensPortChecks(theme: string): PortCheck<DesignTokensPort>[] {\n\treturn [\n\t\t{\n\t\t\tid: \"tokens.theme-resolves\",\n\t\t\ttitle: \"a known theme resolves to at least one token\",\n\t\t\tasync run(port) {\n\t\t\t\tconst tokens = await port.resolve(theme);\n\t\t\t\texpect(Array.isArray(tokens) && tokens.length > 0, `theme \"${theme}\" resolved to no tokens`);\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tid: \"tokens.no-unresolved-aliases\",\n\t\t\ttitle: \"no resolved token still carries an alias\",\n\t\t\tasync run(port) {\n\t\t\t\tfor (const token of await port.resolve(theme)) {\n\t\t\t\t\texpect(\n\t\t\t\t\t\ttypeof token.value !== \"string\" || !ALIAS.test(token.value.trim()),\n\t\t\t\t\t\t`token \"${token.name}\" resolved to the unfollowed alias ${String(token.value)}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tid: \"tokens.names-are-unique\",\n\t\t\ttitle: \"token names are unique within a theme\",\n\t\t\tasync run(port) {\n\t\t\t\tconst names = (await port.resolve(theme)).map((token) => token.name);\n\t\t\t\texpect(new Set(names).size === names.length, \"the theme resolved duplicate token names\");\n\t\t\t},\n\t\t},\n\t];\n}\n\n/** Runs a contract suite and returns every failure, rather than stopping at the first. */\nexport async function runPortContract<TPort>(port: TPort, checks: readonly PortCheck<TPort>[]): Promise<{ passed: boolean; failures: Array<{ id: string; message: string }> }> {\n\tconst failures: Array<{ id: string; message: string }> = [];\n\tfor (const check of checks) {\n\t\ttry {\n\t\t\tawait check.run(port);\n\t\t} catch (error) {\n\t\t\tfailures.push({ id: check.id, message: error instanceof Error ? error.message : String(error) });\n\t\t}\n\t}\n\treturn { passed: failures.length === 0, failures };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fabricorg/ports",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Vendor-neutral port interfaces, a DTCG token resolver, and the contract-test kit every adapter must pass.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -17,6 +17,16 @@
|
|
|
17
17
|
"types": "./dist/index.d.cts",
|
|
18
18
|
"default": "./dist/index.cjs"
|
|
19
19
|
}
|
|
20
|
+
},
|
|
21
|
+
"./catalog": {
|
|
22
|
+
"import": {
|
|
23
|
+
"types": "./dist/catalog.d.ts",
|
|
24
|
+
"default": "./dist/catalog.js"
|
|
25
|
+
},
|
|
26
|
+
"require": {
|
|
27
|
+
"types": "./dist/catalog.d.cts",
|
|
28
|
+
"default": "./dist/catalog.cjs"
|
|
29
|
+
}
|
|
20
30
|
}
|
|
21
31
|
},
|
|
22
32
|
"files": [
|
|
@@ -29,7 +39,8 @@
|
|
|
29
39
|
"@types/node": "^22.10.0",
|
|
30
40
|
"tsup": "^8.5.0",
|
|
31
41
|
"typescript": "^5.7.3",
|
|
32
|
-
"vitest": "^4.1.5"
|
|
42
|
+
"vitest": "^4.1.5",
|
|
43
|
+
"@fabricorg/platform": "1.3.0"
|
|
33
44
|
},
|
|
34
45
|
"publishConfig": {
|
|
35
46
|
"access": "public"
|