@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.
Files changed (42) hide show
  1. package/CONVENTION.md +168 -0
  2. package/DESIGN.md +1056 -0
  3. package/README.md +227 -5
  4. package/SPEC.md +1471 -0
  5. package/conformance/index.d.ts +303 -0
  6. package/conformance/index.mjs +2126 -0
  7. package/conformance/mock-provider.mjs +491 -0
  8. package/examples/entitlement-granted-no-subscription.json +12 -0
  9. package/examples/entitlement-revoked.json +21 -0
  10. package/examples/provider-capabilities.json +209 -0
  11. package/examples/store-event-mapping.json +287 -0
  12. package/examples/subscription-canceled.json +22 -0
  13. package/examples/subscription-product-changed.json +30 -0
  14. package/examples/subscription-renewed.json +29 -0
  15. package/examples/verify-purchase-request.json +6 -0
  16. package/examples/verify-purchase-result.json +7 -0
  17. package/generated/bindings/graphql-operations.json +87 -0
  18. package/generated/bindings/http-binding.json +143 -0
  19. package/generated/bindings/introspection-signature.json +320 -0
  20. package/generated/bindings/operations-sdl.json +4 -0
  21. package/generated/bindings/operations.graphql +366 -0
  22. package/generated/commerce-protocol.graphql +1219 -0
  23. package/generated/openapi/commerce-protocol.openapi.json +1413 -0
  24. package/generated/schemas/commerce-event.schema.json +499 -0
  25. package/generated/schemas/commerce-protocol.bundle.schema.json +1576 -0
  26. package/generated/schemas/operations.schema.json +578 -0
  27. package/generated/schemas/primitives.schema.json +101 -0
  28. package/generated/schemas/provider-capabilities.schema.json +205 -0
  29. package/generated/schemas/store-event-mapping.schema.json +211 -0
  30. package/generated/vectors/lifecycle.json +908 -0
  31. package/generated/vectors/operations.json +1122 -0
  32. package/package.json +62 -12
  33. package/schema/01-primitives.graphql +102 -0
  34. package/schema/02-commerce-event.graphql +195 -0
  35. package/schema/03-provider-capabilities.graphql +139 -0
  36. package/schema/04-store-event-mapping.graphql +98 -0
  37. package/schema/05-operations.graphql +461 -0
  38. package/schema/06-compiler-vocabulary.graphql +139 -0
  39. package/schema/07-protocol-metadata.graphql +76 -0
  40. package/src/index.d.ts +63 -0
  41. package/src/index.mjs +121 -0
  42. 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
+ };