@hyodotdev/openiap-commerce-protocol 0.0.0-bootstrap.0 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONVENTION.md +168 -0
- package/DESIGN.md +1056 -0
- package/README.md +227 -5
- package/SPEC.md +1471 -0
- package/conformance/index.d.ts +303 -0
- package/conformance/index.mjs +2126 -0
- package/conformance/mock-provider.mjs +491 -0
- package/examples/entitlement-granted-no-subscription.json +12 -0
- package/examples/entitlement-revoked.json +21 -0
- package/examples/provider-capabilities.json +209 -0
- package/examples/store-event-mapping.json +287 -0
- package/examples/subscription-canceled.json +22 -0
- package/examples/subscription-product-changed.json +30 -0
- package/examples/subscription-renewed.json +29 -0
- package/examples/verify-purchase-request.json +6 -0
- package/examples/verify-purchase-result.json +7 -0
- package/generated/bindings/graphql-operations.json +87 -0
- package/generated/bindings/http-binding.json +143 -0
- package/generated/bindings/introspection-signature.json +320 -0
- package/generated/bindings/operations-sdl.json +4 -0
- package/generated/bindings/operations.graphql +366 -0
- package/generated/commerce-protocol.graphql +1219 -0
- package/generated/openapi/commerce-protocol.openapi.json +1413 -0
- package/generated/schemas/commerce-event.schema.json +499 -0
- package/generated/schemas/commerce-protocol.bundle.schema.json +1576 -0
- package/generated/schemas/operations.schema.json +578 -0
- package/generated/schemas/primitives.schema.json +101 -0
- package/generated/schemas/provider-capabilities.schema.json +205 -0
- package/generated/schemas/store-event-mapping.schema.json +211 -0
- package/generated/vectors/lifecycle.json +908 -0
- package/generated/vectors/operations.json +1122 -0
- package/package.json +62 -12
- package/schema/01-primitives.graphql +102 -0
- package/schema/02-commerce-event.graphql +195 -0
- package/schema/03-provider-capabilities.graphql +139 -0
- package/schema/04-store-event-mapping.graphql +98 -0
- package/schema/05-operations.graphql +461 -0
- package/schema/06-compiler-vocabulary.graphql +139 -0
- package/schema/07-protocol-metadata.graphql +76 -0
- package/src/index.d.ts +63 -0
- package/src/index.mjs +121 -0
- package/vectors/signatures.json +139 -0
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
export interface ResultOutcome {
|
|
2
|
+
kind: "result";
|
|
3
|
+
status: number;
|
|
4
|
+
/** Normalized shape: null members stripped, binding-neutral. */
|
|
5
|
+
data: unknown;
|
|
6
|
+
/**
|
|
7
|
+
* REQUIRED on a GraphQL result: the pre-normalization shape. The runner
|
|
8
|
+
* fails the case (adapter contract) when it is undefined — presence alone
|
|
9
|
+
* is not enough; a fabricated `member: null` is only visible before
|
|
10
|
+
* null-stripping.
|
|
11
|
+
*/
|
|
12
|
+
rawData?: unknown;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface InvalidOutcome {
|
|
16
|
+
kind: "invalid";
|
|
17
|
+
status: number;
|
|
18
|
+
detail: string;
|
|
19
|
+
code?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A GraphQL error outcome. `errors` and `hasData` are REQUIRED and `errors`
|
|
24
|
+
* must be non-empty: the runner records an adapter-contract failure when
|
|
25
|
+
* either is missing or hollowed out (an empty array, a non-boolean), so an
|
|
26
|
+
* adapter cannot make the envelope rules pass vacuously. Message and code
|
|
27
|
+
* travel together per entry — the runner derives every code/message view it
|
|
28
|
+
* needs from this one structure.
|
|
29
|
+
*/
|
|
30
|
+
export interface GraphqlErrorOutcome {
|
|
31
|
+
kind: "error";
|
|
32
|
+
status: number;
|
|
33
|
+
/** Normalized first code (INVALID_REQUEST when the envelope is codeless). */
|
|
34
|
+
code: string;
|
|
35
|
+
/** The whole envelope, entry by entry, in order. */
|
|
36
|
+
errors: Array<{ message: string; code?: string }>;
|
|
37
|
+
/** Whether the response carried a `data` member (SPEC.md §7 pre-execution rule). */
|
|
38
|
+
hasData: boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* A REST error outcome. `errorBody` is REQUIRED: the runner records an
|
|
43
|
+
* adapter-contract failure when it is missing — the CLOSED
|
|
44
|
+
* ProtocolErrorResponse envelope cannot be validated without it.
|
|
45
|
+
*/
|
|
46
|
+
export interface RestErrorOutcome {
|
|
47
|
+
kind: "error";
|
|
48
|
+
status: number;
|
|
49
|
+
code: string;
|
|
50
|
+
/** The full parsed error body, validated against ProtocolErrorResponse. */
|
|
51
|
+
errorBody: unknown;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export type ConformanceOutcome =
|
|
55
|
+
| ResultOutcome
|
|
56
|
+
| InvalidOutcome
|
|
57
|
+
| GraphqlErrorOutcome
|
|
58
|
+
| RestErrorOutcome;
|
|
59
|
+
|
|
60
|
+
export interface ConformanceAdapter {
|
|
61
|
+
binding: string;
|
|
62
|
+
/**
|
|
63
|
+
* The configured credential VALUES. REQUIRED — the runner records an
|
|
64
|
+
* adapter-contract failure without them, because the SPEC.md §8
|
|
65
|
+
* credential-echo scan on error messages cannot run. Compared locally
|
|
66
|
+
* against message text only; never transmitted.
|
|
67
|
+
*/
|
|
68
|
+
secrets: string[];
|
|
69
|
+
request(args: {
|
|
70
|
+
operation: string;
|
|
71
|
+
input: unknown;
|
|
72
|
+
credential: string | null;
|
|
73
|
+
}): Promise<ConformanceOutcome>;
|
|
74
|
+
/**
|
|
75
|
+
* Sends an arbitrary raw GraphQL request body. REQUIRED on a `graphql`
|
|
76
|
+
* adapter: the executor probe drives malformed and non-canonical documents
|
|
77
|
+
* through it, and a GraphQL adapter without it fails the probe outright —
|
|
78
|
+
* a runner that cannot probe must not certify "GraphQL-conformant".
|
|
79
|
+
* `createGraphqlAdapter` provides it.
|
|
80
|
+
*/
|
|
81
|
+
rawGraphql?(
|
|
82
|
+
payload: unknown,
|
|
83
|
+
credential: string | null,
|
|
84
|
+
): Promise<{ status: number; body: unknown }>;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface AdapterFetch {
|
|
88
|
+
(url: string, init?: RequestInit): Promise<Response>;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface ConformanceCaseResult {
|
|
92
|
+
id: string;
|
|
93
|
+
binding: string;
|
|
94
|
+
ok: boolean;
|
|
95
|
+
failures: string[];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface ConformanceParityFailure {
|
|
99
|
+
id: string;
|
|
100
|
+
outcomes: Record<string, string>;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface ConformanceReport {
|
|
104
|
+
ok: boolean;
|
|
105
|
+
results: ConformanceCaseResult[];
|
|
106
|
+
parityFailures: ConformanceParityFailure[];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export declare function createRestAdapter(options: {
|
|
110
|
+
baseUrl: string;
|
|
111
|
+
fetch: AdapterFetch;
|
|
112
|
+
credentials?: Record<string, string>;
|
|
113
|
+
}): ConformanceAdapter;
|
|
114
|
+
|
|
115
|
+
export declare function createGraphqlAdapter(options: {
|
|
116
|
+
url: string;
|
|
117
|
+
fetch: AdapterFetch;
|
|
118
|
+
credentials?: Record<string, string>;
|
|
119
|
+
}): ConformanceAdapter;
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* The provider's outbound webhook implementation. A descriptor that declares
|
|
123
|
+
* the `events` profile MUST supply every method — a signing-only adapter does
|
|
124
|
+
* not implement it. The runner drives §9.4.2 signing and verification (with
|
|
125
|
+
* rotation and clock-skew), the §9.4.1 delivery envelope, §9.4.3 response
|
|
126
|
+
* semantics, the §2.3 entitlement gate, and the §9.1/§2.4 emission rules.
|
|
127
|
+
* SPEC.md §11.3 lists what stays outside this surface (§9.2 mapping, §9.3
|
|
128
|
+
* document schema, §9.4.4 backoff, §9.4.5 destination safety).
|
|
129
|
+
*/
|
|
130
|
+
export interface EventsAdapter {
|
|
131
|
+
/** §9.4.2 — HMAC-signs one payload, returning `v1=<hex>`. */
|
|
132
|
+
sign(args: {
|
|
133
|
+
secret: string;
|
|
134
|
+
timestamp: number;
|
|
135
|
+
body: string;
|
|
136
|
+
}): string | Promise<string>;
|
|
137
|
+
/** §9.4.2 — accepts a valid delivery and rejects tampered/stale/wrong-key ones. */
|
|
138
|
+
verify(args: {
|
|
139
|
+
body: string;
|
|
140
|
+
timestamp: number;
|
|
141
|
+
signature: string;
|
|
142
|
+
secrets: string[];
|
|
143
|
+
now: number;
|
|
144
|
+
}): boolean | Promise<boolean>;
|
|
145
|
+
/** §9.4.1 — composes the delivery envelope (POST, JSON, headers) for one attempt. */
|
|
146
|
+
delivery(args: {
|
|
147
|
+
event: { eventId: string };
|
|
148
|
+
body: string;
|
|
149
|
+
timestamp: number;
|
|
150
|
+
secrets: string[];
|
|
151
|
+
deliveryId: string;
|
|
152
|
+
}):
|
|
153
|
+
| { method: string; contentType: string; headers: Record<string, string> }
|
|
154
|
+
| Promise<{
|
|
155
|
+
method: string;
|
|
156
|
+
contentType: string;
|
|
157
|
+
headers: Record<string, string>;
|
|
158
|
+
}>;
|
|
159
|
+
/**
|
|
160
|
+
* §9.4.3 — maps a consumer response to the emitter's action. Besides HTTP
|
|
161
|
+
* statuses, the runner probes the no-response outcomes: `"timeout"` and
|
|
162
|
+
* `"connection-error"` must classify as retry.
|
|
163
|
+
*/
|
|
164
|
+
classifyResponse(
|
|
165
|
+
status: number | "connection-error" | "timeout",
|
|
166
|
+
):
|
|
167
|
+
| "delivered"
|
|
168
|
+
| "retry"
|
|
169
|
+
| "permanent-failure"
|
|
170
|
+
| Promise<"delivered" | "retry" | "permanent-failure">;
|
|
171
|
+
/** §2.3 — the entitlement gate, checked against every lifecycle vector. */
|
|
172
|
+
entitled(args: {
|
|
173
|
+
state: string;
|
|
174
|
+
expiresAt?: number;
|
|
175
|
+
processedAt: number;
|
|
176
|
+
}): boolean | Promise<boolean>;
|
|
177
|
+
/** §9.1 — the event types to emit for a lifecycle change. */
|
|
178
|
+
emission(args: {
|
|
179
|
+
lifecycleEvent: string | null;
|
|
180
|
+
entitledBefore: boolean;
|
|
181
|
+
entitledAfter: boolean;
|
|
182
|
+
}): string[] | Promise<string[]>;
|
|
183
|
+
/** §2.4 — the entitlement events to emit when a purchase first binds. */
|
|
184
|
+
coalesceAtBinding(args: {
|
|
185
|
+
unboundGateChanges: string[];
|
|
186
|
+
entitledAtBinding: boolean;
|
|
187
|
+
}): string[] | Promise<string[]>;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* `Ajv` is the Ajv 2020 class; the runner keeps zero dependencies itself.
|
|
192
|
+
* `eventsAdapter` is required when the provider's descriptor declares the
|
|
193
|
+
* events profile — the runner drives the EventsAdapter surface above through
|
|
194
|
+
* it; SPEC.md §11.3 lists the §9 rules that stay outside that surface.
|
|
195
|
+
*/
|
|
196
|
+
export declare function runConformance(options: {
|
|
197
|
+
adapters: ConformanceAdapter[];
|
|
198
|
+
Ajv: unknown;
|
|
199
|
+
eventsAdapter?: EventsAdapter;
|
|
200
|
+
/**
|
|
201
|
+
* REQUIRED: the AUTHORITATIVE role-to-credential map for the SPEC.md §8
|
|
202
|
+
* credential-echo scan. Every role THIS RUN exercises must be present —
|
|
203
|
+
* checked at first use, so a legal partial-profile provider that never
|
|
204
|
+
* uses the server role is not asked for a credential it does not have,
|
|
205
|
+
* while omitting a role the run does use throws. Pass the same values the
|
|
206
|
+
* adapters were configured with; an adapter-supplied list alone could be
|
|
207
|
+
* emptied by a non-conforming adapter. Compared locally against
|
|
208
|
+
* error-message text; never transmitted.
|
|
209
|
+
*/
|
|
210
|
+
credentials: Record<string, string>;
|
|
211
|
+
}): Promise<ConformanceReport>;
|
|
212
|
+
|
|
213
|
+
export declare const signatureVectors: {
|
|
214
|
+
algorithm: string;
|
|
215
|
+
toleranceSeconds: number;
|
|
216
|
+
headers: Record<string, string>;
|
|
217
|
+
cases: Array<Record<string, unknown>>;
|
|
218
|
+
rejections: Array<Record<string, unknown>>;
|
|
219
|
+
responseSemantics: {
|
|
220
|
+
connectionError: string;
|
|
221
|
+
cases: Array<{ status: number; action: string }>;
|
|
222
|
+
};
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
export declare const lifecycleVectors: Record<string, unknown>;
|
|
226
|
+
|
|
227
|
+
export declare function normalizeResultData<T>(value: T): T;
|
|
228
|
+
|
|
229
|
+
export declare const httpBindingManifest: {
|
|
230
|
+
protocolVersion: string;
|
|
231
|
+
profiles: Record<string, string>;
|
|
232
|
+
bindings: Record<string, string>;
|
|
233
|
+
errorStatus: Record<string, number>;
|
|
234
|
+
errorResponse: string;
|
|
235
|
+
operations: Array<{
|
|
236
|
+
name: string;
|
|
237
|
+
kind: "query" | "mutation";
|
|
238
|
+
profile: string;
|
|
239
|
+
auth: "none" | "verification" | "server";
|
|
240
|
+
method: "GET" | "POST";
|
|
241
|
+
path: string;
|
|
242
|
+
successStatus: number;
|
|
243
|
+
idempotent: boolean;
|
|
244
|
+
errors: string[];
|
|
245
|
+
input: string | null;
|
|
246
|
+
result: string;
|
|
247
|
+
}>;
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* The canonical full-selection shape of one operation result: `true` marks a
|
|
252
|
+
* leaf field, a nested object a sub-selection. Parity projects every
|
|
253
|
+
* non-GraphQL binding's result onto this tree, and the GraphQL result is
|
|
254
|
+
* checked raw against it (an unrequested member fails).
|
|
255
|
+
*/
|
|
256
|
+
export type SelectionTree = true | { [field: string]: SelectionTree };
|
|
257
|
+
|
|
258
|
+
export declare const graphqlOperations: {
|
|
259
|
+
protocolVersion: string;
|
|
260
|
+
operations: Record<
|
|
261
|
+
string,
|
|
262
|
+
{ kind: "query" | "mutation"; document: string; selection: SelectionTree }
|
|
263
|
+
>;
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Structural fingerprint of the executable projection, compared as a subset
|
|
268
|
+
* against a served schema's introspection: exact kinds, field/argument type
|
|
269
|
+
* strings (nullability included), input members, and closed enum/object members.
|
|
270
|
+
*/
|
|
271
|
+
export declare const introspectionSignature: {
|
|
272
|
+
protocolVersion: string;
|
|
273
|
+
queryType: string | null;
|
|
274
|
+
mutationType: string | null;
|
|
275
|
+
types: Record<
|
|
276
|
+
string,
|
|
277
|
+
| { kind: "SCALAR" }
|
|
278
|
+
| { kind: "ENUM"; values: string[] }
|
|
279
|
+
| { kind: "INPUT_OBJECT"; inputFields: Record<string, string> }
|
|
280
|
+
| {
|
|
281
|
+
kind: "OBJECT";
|
|
282
|
+
closed?: true;
|
|
283
|
+
fields: Record<string, { type: string; args?: Record<string, string> }>;
|
|
284
|
+
}
|
|
285
|
+
>;
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
export declare const operationVectors: {
|
|
289
|
+
protocolVersion: string;
|
|
290
|
+
fixtures: Record<string, string>;
|
|
291
|
+
credentialRoles: string[];
|
|
292
|
+
cases: Array<{
|
|
293
|
+
id: string;
|
|
294
|
+
operation: string;
|
|
295
|
+
credential: string | null;
|
|
296
|
+
input: unknown;
|
|
297
|
+
bindings?: string[];
|
|
298
|
+
requiresStore?: string;
|
|
299
|
+
requiresCapability?: string;
|
|
300
|
+
repeat?: number;
|
|
301
|
+
expect: Record<string, unknown>;
|
|
302
|
+
}>;
|
|
303
|
+
};
|