@voyant-travel/apps 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 (64) hide show
  1. package/dist/access-boundary.d.ts +29 -0
  2. package/dist/access-boundary.js +33 -0
  3. package/dist/api-runtime.d.ts +10 -0
  4. package/dist/api-runtime.js +8 -0
  5. package/dist/app-api-contracts.d.ts +51 -0
  6. package/dist/app-api-contracts.js +50 -0
  7. package/dist/app-api-routes.d.ts +26 -0
  8. package/dist/app-api-routes.js +133 -0
  9. package/dist/app-api-service.d.ts +1263 -0
  10. package/dist/app-api-service.js +231 -0
  11. package/dist/app-api-service.test.d.ts +1 -0
  12. package/dist/app-api-service.test.js +105 -0
  13. package/dist/compiler.d.ts +38 -0
  14. package/dist/compiler.js +118 -0
  15. package/dist/compiler.test.d.ts +1 -0
  16. package/dist/compiler.test.js +99 -0
  17. package/dist/consent.d.ts +15 -0
  18. package/dist/consent.js +50 -0
  19. package/dist/consent.test.d.ts +1 -0
  20. package/dist/consent.test.js +80 -0
  21. package/dist/contracts.d.ts +263 -0
  22. package/dist/contracts.js +273 -0
  23. package/dist/index.d.ts +13 -0
  24. package/dist/index.js +12 -0
  25. package/dist/ingestion.d.ts +15 -0
  26. package/dist/ingestion.js +232 -0
  27. package/dist/ingestion.test.d.ts +1 -0
  28. package/dist/ingestion.test.js +47 -0
  29. package/dist/installation-reconciliation.d.ts +18 -0
  30. package/dist/installation-reconciliation.js +254 -0
  31. package/dist/installation-service.d.ts +351 -0
  32. package/dist/installation-service.js +328 -0
  33. package/dist/installation-state.d.ts +15 -0
  34. package/dist/installation-state.js +17 -0
  35. package/dist/installation-state.test.d.ts +1 -0
  36. package/dist/installation-state.test.js +38 -0
  37. package/dist/oauth-crypto.d.ts +8 -0
  38. package/dist/oauth-crypto.js +23 -0
  39. package/dist/oauth-crypto.test.d.ts +1 -0
  40. package/dist/oauth-crypto.test.js +11 -0
  41. package/dist/oauth-service.d.ts +65 -0
  42. package/dist/oauth-service.js +381 -0
  43. package/dist/oauth-service.test.d.ts +1 -0
  44. package/dist/oauth-service.test.js +8 -0
  45. package/dist/routes.d.ts +17 -0
  46. package/dist/routes.js +131 -0
  47. package/dist/schema.d.ts +2937 -0
  48. package/dist/schema.js +342 -0
  49. package/dist/service.d.ts +95 -0
  50. package/dist/service.js +172 -0
  51. package/dist/service.test.d.ts +1 -0
  52. package/dist/service.test.js +178 -0
  53. package/dist/test-fixtures.d.ts +80 -0
  54. package/dist/test-fixtures.js +78 -0
  55. package/dist/voyant.d.ts +2 -0
  56. package/dist/voyant.js +115 -0
  57. package/dist/webhook-delivery.d.ts +69 -0
  58. package/dist/webhook-delivery.js +262 -0
  59. package/migrations/20260717000100_app_registry_foundation.sql +87 -0
  60. package/migrations/20260717111938_breezy_karma.sql +136 -0
  61. package/migrations/20260717123000_app_webhook_delivery_health.sql +4 -0
  62. package/migrations/20260717143000_app_oauth_authorization.sql +47 -0
  63. package/migrations/meta/_journal.json +34 -0
  64. package/package.json +159 -0
@@ -0,0 +1,80 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { computeAppConsent } from "./consent.js";
3
+ const catalog = {
4
+ resources: [
5
+ {
6
+ id: "bookings",
7
+ unitId: "bookings",
8
+ resource: "bookings",
9
+ label: "Bookings",
10
+ description: "Bookings",
11
+ wildcard: "allow",
12
+ actions: [{ action: "read", label: "Read", description: "Read" }],
13
+ },
14
+ {
15
+ id: "invoices",
16
+ unitId: "finance",
17
+ resource: "invoices",
18
+ label: "Invoices",
19
+ description: "Invoices",
20
+ wildcard: "allow",
21
+ actions: [{ action: "read", label: "Read", description: "Read" }],
22
+ },
23
+ ],
24
+ presets: [
25
+ {
26
+ id: "remote-safe",
27
+ kind: "api-token-grant",
28
+ audience: "staff",
29
+ label: "Remote safe",
30
+ description: "Remote app grants",
31
+ grants: ["bookings:read"],
32
+ },
33
+ ],
34
+ };
35
+ function release(requestedScopes, optionalScopes) {
36
+ return {
37
+ id: "release_1",
38
+ appId: "app_1",
39
+ releaseVersion: "1.0.0",
40
+ manifestSchemaVersion: "voyant.app-manifest.v1",
41
+ manifestDigest: "digest",
42
+ manifestSnapshot: {},
43
+ normalizedRecord: {
44
+ schemaVersion: "voyant.app-release.normalized.v1",
45
+ releaseVersion: "1.0.0",
46
+ digest: "digest",
47
+ requestedScopes,
48
+ optionalScopes,
49
+ adminPages: [],
50
+ slotExtensions: [],
51
+ webhooks: [],
52
+ customFields: [],
53
+ },
54
+ apiCompatibility: { min: "2026-01-01", max: "2026-12-31" },
55
+ defaultLocale: "en-US",
56
+ supportedLocales: ["en-US"],
57
+ state: "available",
58
+ createdBy: "user_1",
59
+ createdAt: new Date(),
60
+ };
61
+ }
62
+ describe("app OAuth consent computation", () => {
63
+ it("grants only scopes present in the catalog, remote-safe, and operator-granted", () => {
64
+ const consent = computeAppConsent({
65
+ release: release(["bookings:read"], ["invoices:read"]),
66
+ accessCatalog: catalog,
67
+ operatorGrantedScopes: ["bookings:read", "invoices:read"],
68
+ grantedOptionalScopes: ["invoices:read"],
69
+ });
70
+ expect(consent.grantedScopes).toEqual(["bookings:read"]);
71
+ expect(consent.deniedOptionalScopes).toEqual(["invoices:read"]);
72
+ });
73
+ it("rejects required scopes that are absent, unsafe, or not operator-granted", () => {
74
+ expect(() => computeAppConsent({
75
+ release: release(["invoices:read"], []),
76
+ accessCatalog: catalog,
77
+ operatorGrantedScopes: ["invoices:read"],
78
+ })).toThrow("Required app scopes are not grantable");
79
+ });
80
+ });
@@ -0,0 +1,263 @@
1
+ import { z } from "zod";
2
+ export declare const APP_MANIFEST_SCHEMA_VERSION: "voyant.app-manifest.v1";
3
+ export declare const APP_ADMIN_EXTENSION_SLOTS: readonly ["dashboard.header", "dashboard.after-kpis", "dashboard.footer", "booking.details.header", "booking.details.after-summary", "invoice.details.header", "invoice.details.after-summary", "workspace.header.actions"];
4
+ export declare const manifestDisallowedKeySchema: z.ZodRecord<z.ZodString, z.ZodUnknown>;
5
+ export declare const appOwnedCustomFieldDeclarationSchema: z.ZodObject<{
6
+ entityType: z.ZodString;
7
+ key: z.ZodString;
8
+ label: z.ZodString;
9
+ fieldType: z.ZodEnum<{
10
+ boolean: "boolean";
11
+ text: "text";
12
+ date: "date";
13
+ monetary: "monetary";
14
+ json: "json";
15
+ varchar: "varchar";
16
+ double: "double";
17
+ enum: "enum";
18
+ set: "set";
19
+ address: "address";
20
+ phone: "phone";
21
+ }>;
22
+ isRequired: z.ZodDefault<z.ZodBoolean>;
23
+ isSearchable: z.ZodDefault<z.ZodBoolean>;
24
+ isExportable: z.ZodDefault<z.ZodBoolean>;
25
+ isInvoiceable: z.ZodDefault<z.ZodBoolean>;
26
+ options: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodObject<{
27
+ label: z.ZodString;
28
+ value: z.ZodString;
29
+ }, z.core.$strip>>>>;
30
+ logicalNamespace: z.ZodOptional<z.ZodString>;
31
+ dataClassification: z.ZodDefault<z.ZodEnum<{
32
+ public: "public";
33
+ internal: "internal";
34
+ confidential: "confidential";
35
+ restricted: "restricted";
36
+ personal: "personal";
37
+ financial: "financial";
38
+ }>>;
39
+ }, z.core.$strict>;
40
+ export declare const appManifestSchema: z.ZodPipe<z.ZodRecord<z.ZodString, z.ZodUnknown>, z.ZodObject<{
41
+ schemaVersion: z.ZodLiteral<"voyant.app-manifest.v1">;
42
+ releaseVersion: z.ZodString;
43
+ apiCompatibility: z.ZodObject<{
44
+ min: z.ZodString;
45
+ max: z.ZodString;
46
+ }, z.core.$strict>;
47
+ scopes: z.ZodObject<{
48
+ requested: z.ZodDefault<z.ZodArray<z.ZodString>>;
49
+ optional: z.ZodDefault<z.ZodArray<z.ZodString>>;
50
+ }, z.core.$strict>;
51
+ admin: z.ZodDefault<z.ZodObject<{
52
+ pages: z.ZodDefault<z.ZodArray<z.ZodObject<{
53
+ key: z.ZodString;
54
+ titleKey: z.ZodString;
55
+ path: z.ZodString;
56
+ entryUrl: z.ZodString;
57
+ }, z.core.$strict>>>;
58
+ slotExtensions: z.ZodDefault<z.ZodArray<z.ZodObject<{
59
+ key: z.ZodString;
60
+ titleKey: z.ZodString;
61
+ version: z.ZodString;
62
+ extensionApi: z.ZodString;
63
+ entryUrl: z.ZodString;
64
+ slots: z.ZodArray<z.ZodEnum<{
65
+ "dashboard.header": "dashboard.header";
66
+ "dashboard.after-kpis": "dashboard.after-kpis";
67
+ "dashboard.footer": "dashboard.footer";
68
+ "booking.details.header": "booking.details.header";
69
+ "booking.details.after-summary": "booking.details.after-summary";
70
+ "invoice.details.header": "invoice.details.header";
71
+ "invoice.details.after-summary": "invoice.details.after-summary";
72
+ "workspace.header.actions": "workspace.header.actions";
73
+ }>>;
74
+ config: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
75
+ }, z.core.$strict>>>;
76
+ }, z.core.$strict>>;
77
+ webhooks: z.ZodDefault<z.ZodArray<z.ZodObject<{
78
+ eventType: z.ZodString;
79
+ eventVersion: z.ZodString;
80
+ endpointUrl: z.ZodString;
81
+ }, z.core.$strict>>>;
82
+ customFields: z.ZodDefault<z.ZodArray<z.ZodObject<{
83
+ entityType: z.ZodString;
84
+ key: z.ZodString;
85
+ label: z.ZodString;
86
+ fieldType: z.ZodEnum<{
87
+ boolean: "boolean";
88
+ text: "text";
89
+ date: "date";
90
+ monetary: "monetary";
91
+ json: "json";
92
+ varchar: "varchar";
93
+ double: "double";
94
+ enum: "enum";
95
+ set: "set";
96
+ address: "address";
97
+ phone: "phone";
98
+ }>;
99
+ isRequired: z.ZodDefault<z.ZodBoolean>;
100
+ isSearchable: z.ZodDefault<z.ZodBoolean>;
101
+ isExportable: z.ZodDefault<z.ZodBoolean>;
102
+ isInvoiceable: z.ZodDefault<z.ZodBoolean>;
103
+ options: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodObject<{
104
+ label: z.ZodString;
105
+ value: z.ZodString;
106
+ }, z.core.$strip>>>>;
107
+ logicalNamespace: z.ZodOptional<z.ZodString>;
108
+ dataClassification: z.ZodDefault<z.ZodEnum<{
109
+ public: "public";
110
+ internal: "internal";
111
+ confidential: "confidential";
112
+ restricted: "restricted";
113
+ personal: "personal";
114
+ financial: "financial";
115
+ }>>;
116
+ }, z.core.$strict>>>;
117
+ locales: z.ZodObject<{
118
+ default: z.ZodString;
119
+ supported: z.ZodArray<z.ZodString>;
120
+ host: z.ZodRecord<z.ZodString, z.ZodObject<{
121
+ name: z.ZodString;
122
+ summary: z.ZodString;
123
+ navigation: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
124
+ extensions: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
125
+ setup: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
126
+ }, z.core.$strict>>;
127
+ }, z.core.$strict>;
128
+ urls: z.ZodObject<{
129
+ setup: z.ZodOptional<z.ZodString>;
130
+ health: z.ZodString;
131
+ launch: z.ZodString;
132
+ privacy: z.ZodString;
133
+ support: z.ZodString;
134
+ }, z.core.$strict>;
135
+ data: z.ZodObject<{
136
+ classifications: z.ZodArray<z.ZodEnum<{
137
+ public: "public";
138
+ internal: "internal";
139
+ confidential: "confidential";
140
+ restricted: "restricted";
141
+ personal: "personal";
142
+ financial: "financial";
143
+ }>>;
144
+ retention: z.ZodString;
145
+ storesSecrets: z.ZodDefault<z.ZodLiteral<false>>;
146
+ }, z.core.$strict>;
147
+ }, z.core.$strict>>;
148
+ export declare const createCustomAppRegistrationSchema: z.ZodObject<{
149
+ ownerId: z.ZodString;
150
+ displayName: z.ZodString;
151
+ slug: z.ZodString;
152
+ redirectUris: z.ZodDefault<z.ZodArray<z.ZodString>>;
153
+ createdBy: z.ZodString;
154
+ }, z.core.$strict>;
155
+ export declare const releaseManifestUploadSchema: z.ZodObject<{
156
+ manifest: z.ZodUnknown;
157
+ createdBy: z.ZodString;
158
+ provenance: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
159
+ }, z.core.$strict>;
160
+ export declare const releaseManifestFetchSchema: z.ZodObject<{
161
+ manifestUrl: z.ZodString;
162
+ createdBy: z.ZodString;
163
+ }, z.core.$strict>;
164
+ export declare const appListQuerySchema: z.ZodObject<{
165
+ ownerId: z.ZodOptional<z.ZodString>;
166
+ distribution: z.ZodOptional<z.ZodEnum<{
167
+ custom: "custom";
168
+ marketplace: "marketplace";
169
+ }>>;
170
+ limit: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
171
+ offset: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
172
+ }, z.core.$strip>;
173
+ export declare const appWebhookReplaySchema: z.ZodObject<{
174
+ deliveryId: z.ZodString;
175
+ actorId: z.ZodString;
176
+ signingKeyId: z.ZodString;
177
+ signingSecret: z.ZodString;
178
+ }, z.core.$strict>;
179
+ export declare const appOAuthAuthorizeQuerySchema: z.ZodObject<{
180
+ response_type: z.ZodLiteral<"code">;
181
+ client_id: z.ZodString;
182
+ release_id: z.ZodString;
183
+ redirect_uri: z.ZodString;
184
+ state: z.ZodString;
185
+ code_challenge: z.ZodString;
186
+ code_challenge_method: z.ZodLiteral<"S256">;
187
+ actor_id: z.ZodString;
188
+ operator_scopes: z.ZodDefault<z.ZodString>;
189
+ optional_scopes: z.ZodDefault<z.ZodString>;
190
+ }, z.core.$strict>;
191
+ export declare const appOAuthTokenSchema: z.ZodPipe<z.ZodDiscriminatedUnion<[z.ZodObject<{
192
+ grant_type: z.ZodLiteral<"authorization_code">;
193
+ code: z.ZodString;
194
+ redirect_uri: z.ZodString;
195
+ code_verifier: z.ZodString;
196
+ client_id: z.ZodString;
197
+ client_secret: z.ZodOptional<z.ZodString>;
198
+ }, z.core.$strip>, z.ZodObject<{
199
+ grant_type: z.ZodLiteral<"refresh_token">;
200
+ refresh_token: z.ZodString;
201
+ client_id: z.ZodString;
202
+ client_secret: z.ZodOptional<z.ZodString>;
203
+ }, z.core.$strip>, z.ZodObject<{
204
+ grant_type: z.ZodLiteral<"urn:voyant:params:oauth:grant-type:actor-token-exchange">;
205
+ installation_id: z.ZodString;
206
+ viewer_id: z.ZodString;
207
+ viewer_scopes: z.ZodDefault<z.ZodArray<z.ZodString>>;
208
+ contextual_scopes: z.ZodOptional<z.ZodArray<z.ZodString>>;
209
+ client_id: z.ZodString;
210
+ client_secret: z.ZodOptional<z.ZodString>;
211
+ }, z.core.$strip>], "grant_type">, z.ZodTransform<{
212
+ client_secret: string | undefined;
213
+ grant_type: "authorization_code";
214
+ code: string;
215
+ redirect_uri: string;
216
+ code_verifier: string;
217
+ client_id: string;
218
+ } | {
219
+ client_secret: string | undefined;
220
+ grant_type: "refresh_token";
221
+ refresh_token: string;
222
+ client_id: string;
223
+ } | {
224
+ client_secret: string | undefined;
225
+ grant_type: "urn:voyant:params:oauth:grant-type:actor-token-exchange";
226
+ installation_id: string;
227
+ viewer_id: string;
228
+ viewer_scopes: string[];
229
+ client_id: string;
230
+ contextual_scopes?: string[] | undefined;
231
+ }, {
232
+ grant_type: "authorization_code";
233
+ code: string;
234
+ redirect_uri: string;
235
+ code_verifier: string;
236
+ client_id: string;
237
+ client_secret?: string | undefined;
238
+ } | {
239
+ grant_type: "refresh_token";
240
+ refresh_token: string;
241
+ client_id: string;
242
+ client_secret?: string | undefined;
243
+ } | {
244
+ grant_type: "urn:voyant:params:oauth:grant-type:actor-token-exchange";
245
+ installation_id: string;
246
+ viewer_id: string;
247
+ viewer_scopes: string[];
248
+ client_id: string;
249
+ contextual_scopes?: string[] | undefined;
250
+ client_secret?: string | undefined;
251
+ }>>;
252
+ export declare const appCredentialRevocationSchema: z.ZodObject<{
253
+ installationId: z.ZodString;
254
+ actorId: z.ZodString;
255
+ }, z.core.$strict>;
256
+ export type AppManifest = z.infer<typeof appManifestSchema>;
257
+ export type CreateCustomAppRegistrationInput = z.infer<typeof createCustomAppRegistrationSchema>;
258
+ export type ReleaseManifestUploadInput = z.infer<typeof releaseManifestUploadSchema>;
259
+ export type ReleaseManifestFetchInput = z.infer<typeof releaseManifestFetchSchema>;
260
+ export type AppListQuery = z.infer<typeof appListQuerySchema>;
261
+ export type AppWebhookReplayInput = z.infer<typeof appWebhookReplaySchema>;
262
+ export type AppOAuthAuthorizeQuery = z.infer<typeof appOAuthAuthorizeQuerySchema>;
263
+ export type AppOAuthTokenInput = z.infer<typeof appOAuthTokenSchema>;
@@ -0,0 +1,273 @@
1
+ import { customFieldDefinitionInputSchema } from "@voyant-travel/custom-fields/contracts";
2
+ import { assertOutboundWebhookEndpointUrl } from "@voyant-travel/webhook-delivery";
3
+ import { z } from "zod";
4
+ export const APP_MANIFEST_SCHEMA_VERSION = "voyant.app-manifest.v1";
5
+ export const APP_ADMIN_EXTENSION_SLOTS = [
6
+ "dashboard.header",
7
+ "dashboard.after-kpis",
8
+ "dashboard.footer",
9
+ "booking.details.header",
10
+ "booking.details.after-summary",
11
+ "invoice.details.header",
12
+ "invoice.details.after-summary",
13
+ "workspace.header.actions",
14
+ ];
15
+ const disallowedManifestKeys = {
16
+ schemas: "Database schemas are deployment-package authority and cannot appear in app manifests.",
17
+ migrations: "Database migrations are deployment-package authority and cannot appear in app manifests.",
18
+ hostRoutes: "Host routes cannot be declared by remote app manifests.",
19
+ routes: "Host routes cannot be declared by remote app manifests.",
20
+ runtimeFactories: "Runtime factories cannot be declared by remote app manifests.",
21
+ subscribers: "Subscribers cannot be declared by remote app manifests.",
22
+ providers: "Infrastructure providers cannot be declared by remote app manifests.",
23
+ scripts: "Package or lifecycle scripts are forbidden in app release manifests.",
24
+ dependencies: "Dependency declarations are forbidden in app release manifests.",
25
+ optionalDependencies: "Dependency declarations are forbidden in app release manifests.",
26
+ peerDependencies: "Dependency declarations are forbidden in app release manifests.",
27
+ bundledDependencies: "Dependency declarations are forbidden in app release manifests.",
28
+ binaries: "Binary declarations are forbidden in app release manifests.",
29
+ bin: "Binary declarations are forbidden in app release manifests.",
30
+ exports: "Executable package exports are forbidden in app release manifests.",
31
+ files: "Undeclared file inventories are forbidden in app release manifests.",
32
+ };
33
+ export const manifestDisallowedKeySchema = z
34
+ .record(z.string(), z.unknown())
35
+ .superRefine((value, context) => {
36
+ for (const [key, message] of Object.entries(disallowedManifestKeys)) {
37
+ if (Object.hasOwn(value, key)) {
38
+ context.addIssue({
39
+ code: "custom",
40
+ path: [key],
41
+ message,
42
+ });
43
+ }
44
+ }
45
+ });
46
+ const semverLikeSchema = z.string().trim().min(1).max(64);
47
+ const scopeSchema = z
48
+ .string()
49
+ .trim()
50
+ .regex(/^[a-z][a-z0-9-]*:[a-z][a-z0-9-]*$/);
51
+ const localeSchema = z.string().trim().min(2).max(35);
52
+ const httpsUrlSchema = z
53
+ .string()
54
+ .url()
55
+ .refine((value) => new URL(value).protocol === "https:", {
56
+ message: "URL must use https.",
57
+ });
58
+ const webhookEndpointUrlSchema = httpsUrlSchema.refine((value) => {
59
+ try {
60
+ assertOutboundWebhookEndpointUrl(value);
61
+ return true;
62
+ }
63
+ catch {
64
+ return false;
65
+ }
66
+ }, { message: "Webhook endpoint URL must be HTTPS and must not target local or private hosts." });
67
+ const extensionSlotSchema = z.enum(APP_ADMIN_EXTENSION_SLOTS);
68
+ const dataClassificationSchema = z.enum([
69
+ "public",
70
+ "internal",
71
+ "confidential",
72
+ "restricted",
73
+ "personal",
74
+ "financial",
75
+ ]);
76
+ const localizedHostMetadataSchema = z
77
+ .object({
78
+ name: z.string().trim().min(1).max(80),
79
+ summary: z.string().trim().min(1).max(280),
80
+ navigation: z.record(z.string(), z.string().trim().min(1).max(80)).default({}),
81
+ extensions: z.record(z.string(), z.string().trim().min(1).max(80)).default({}),
82
+ setup: z.record(z.string(), z.string().trim().min(1).max(80)).default({}),
83
+ })
84
+ .strict();
85
+ const adminPageSchema = z
86
+ .object({
87
+ key: z.string().trim().min(1).max(80),
88
+ titleKey: z.string().trim().min(1).max(120),
89
+ path: z
90
+ .string()
91
+ .trim()
92
+ .regex(/^\/[a-z0-9-_/]*$/),
93
+ entryUrl: httpsUrlSchema,
94
+ })
95
+ .strict();
96
+ const slotExtensionSchema = z
97
+ .object({
98
+ key: z.string().trim().min(1).max(80),
99
+ titleKey: z.string().trim().min(1).max(120),
100
+ version: semverLikeSchema,
101
+ extensionApi: semverLikeSchema,
102
+ entryUrl: httpsUrlSchema,
103
+ slots: z.array(extensionSlotSchema).min(1),
104
+ config: z.record(z.string(), z.unknown()).optional(),
105
+ })
106
+ .strict();
107
+ const webhookSubscriptionSchema = z
108
+ .object({
109
+ eventType: z.string().trim().min(1).max(160),
110
+ eventVersion: semverLikeSchema,
111
+ endpointUrl: webhookEndpointUrlSchema,
112
+ })
113
+ .strict();
114
+ export const appOwnedCustomFieldDeclarationSchema = customFieldDefinitionInputSchema
115
+ .extend({
116
+ logicalNamespace: z
117
+ .string()
118
+ .trim()
119
+ .regex(/^[a-z][a-z0-9-]*$/)
120
+ .optional(),
121
+ dataClassification: dataClassificationSchema.default("internal"),
122
+ })
123
+ .strict();
124
+ export const appManifestSchema = manifestDisallowedKeySchema.pipe(z
125
+ .object({
126
+ schemaVersion: z.literal(APP_MANIFEST_SCHEMA_VERSION),
127
+ releaseVersion: semverLikeSchema,
128
+ apiCompatibility: z.object({ min: semverLikeSchema, max: semverLikeSchema }).strict(),
129
+ scopes: z
130
+ .object({
131
+ requested: z.array(scopeSchema).default([]),
132
+ optional: z.array(scopeSchema).default([]),
133
+ })
134
+ .strict(),
135
+ admin: z
136
+ .object({
137
+ pages: z.array(adminPageSchema).default([]),
138
+ slotExtensions: z.array(slotExtensionSchema).default([]),
139
+ })
140
+ .strict()
141
+ .default({ pages: [], slotExtensions: [] }),
142
+ webhooks: z.array(webhookSubscriptionSchema).default([]),
143
+ customFields: z.array(appOwnedCustomFieldDeclarationSchema).default([]),
144
+ locales: z
145
+ .object({
146
+ default: localeSchema,
147
+ supported: z.array(localeSchema).min(1),
148
+ host: z.record(localeSchema, localizedHostMetadataSchema),
149
+ })
150
+ .strict(),
151
+ urls: z
152
+ .object({
153
+ setup: httpsUrlSchema.optional(),
154
+ health: httpsUrlSchema,
155
+ launch: httpsUrlSchema,
156
+ privacy: httpsUrlSchema,
157
+ support: httpsUrlSchema,
158
+ })
159
+ .strict(),
160
+ data: z
161
+ .object({
162
+ classifications: z.array(dataClassificationSchema).min(1),
163
+ retention: z.string().trim().min(1).max(280),
164
+ storesSecrets: z.literal(false).default(false),
165
+ })
166
+ .strict(),
167
+ })
168
+ .strict()
169
+ .superRefine((manifest, context) => {
170
+ if (!manifest.locales.supported.includes(manifest.locales.default)) {
171
+ context.addIssue({
172
+ code: "custom",
173
+ path: ["locales", "default"],
174
+ message: "The default locale must be present in supported locales.",
175
+ });
176
+ }
177
+ if (!manifest.locales.host[manifest.locales.default]) {
178
+ context.addIssue({
179
+ code: "custom",
180
+ path: ["locales", "host", manifest.locales.default],
181
+ message: "Host-rendered metadata is required for the default locale.",
182
+ });
183
+ }
184
+ }));
185
+ export const createCustomAppRegistrationSchema = z
186
+ .object({
187
+ ownerId: z.string().trim().min(1).max(160),
188
+ displayName: z.string().trim().min(1).max(120),
189
+ slug: z
190
+ .string()
191
+ .trim()
192
+ .regex(/^[a-z0-9][a-z0-9-]{0,78}[a-z0-9]$/),
193
+ redirectUris: z.array(httpsUrlSchema).default([]),
194
+ createdBy: z.string().trim().min(1).max(160),
195
+ })
196
+ .strict();
197
+ export const releaseManifestUploadSchema = z
198
+ .object({
199
+ manifest: z.unknown(),
200
+ createdBy: z.string().trim().min(1).max(160),
201
+ provenance: z.record(z.string(), z.unknown()).default({ source: "admin-upload" }),
202
+ })
203
+ .strict();
204
+ export const releaseManifestFetchSchema = z
205
+ .object({
206
+ manifestUrl: httpsUrlSchema,
207
+ createdBy: z.string().trim().min(1).max(160),
208
+ })
209
+ .strict();
210
+ export const appListQuerySchema = z.object({
211
+ ownerId: z.string().trim().min(1).optional(),
212
+ distribution: z.enum(["custom", "marketplace"]).optional(),
213
+ limit: z.coerce.number().int().positive().max(100).default(25),
214
+ offset: z.coerce.number().int().nonnegative().default(0),
215
+ });
216
+ export const appWebhookReplaySchema = z
217
+ .object({
218
+ deliveryId: z.string().trim().min(1),
219
+ actorId: z.string().trim().min(1).max(160),
220
+ signingKeyId: z.string().trim().min(1).max(160),
221
+ signingSecret: z.string().min(32),
222
+ })
223
+ .strict();
224
+ export const appOAuthAuthorizeQuerySchema = z
225
+ .object({
226
+ response_type: z.literal("code"),
227
+ client_id: z.string().trim().min(1),
228
+ release_id: z.string().trim().min(1),
229
+ redirect_uri: z.string().url(),
230
+ state: z.string().trim().min(1),
231
+ code_challenge: z.string().trim().min(1),
232
+ code_challenge_method: z.literal("S256"),
233
+ actor_id: z.string().trim().min(1),
234
+ operator_scopes: z.string().trim().default(""),
235
+ optional_scopes: z.string().trim().default(""),
236
+ })
237
+ .strict();
238
+ export const appOAuthTokenSchema = z
239
+ .discriminatedUnion("grant_type", [
240
+ z.object({
241
+ grant_type: z.literal("authorization_code"),
242
+ code: z.string().trim().min(1),
243
+ redirect_uri: z.string().url(),
244
+ code_verifier: z.string().trim().min(43).max(128),
245
+ client_id: z.string().trim().min(1),
246
+ client_secret: z.string().trim().optional(),
247
+ }),
248
+ z.object({
249
+ grant_type: z.literal("refresh_token"),
250
+ refresh_token: z.string().trim().min(1),
251
+ client_id: z.string().trim().min(1),
252
+ client_secret: z.string().trim().optional(),
253
+ }),
254
+ z.object({
255
+ grant_type: z.literal("urn:voyant:params:oauth:grant-type:actor-token-exchange"),
256
+ installation_id: z.string().trim().min(1),
257
+ viewer_id: z.string().trim().min(1),
258
+ viewer_scopes: z.array(scopeSchema).default([]),
259
+ contextual_scopes: z.array(scopeSchema).optional(),
260
+ client_id: z.string().trim().min(1),
261
+ client_secret: z.string().trim().optional(),
262
+ }),
263
+ ])
264
+ .transform((input) => ({
265
+ ...input,
266
+ client_secret: input.client_secret?.trim() || undefined,
267
+ }));
268
+ export const appCredentialRevocationSchema = z
269
+ .object({
270
+ installationId: z.string().trim().min(1),
271
+ actorId: z.string().trim().min(1),
272
+ })
273
+ .strict();
@@ -0,0 +1,13 @@
1
+ export * from "./access-boundary.js";
2
+ export * from "./compiler.js";
3
+ export * from "./consent.js";
4
+ export * from "./contracts.js";
5
+ export * from "./ingestion.js";
6
+ export * from "./installation-service.js";
7
+ export * from "./oauth-crypto.js";
8
+ export * from "./oauth-service.js";
9
+ export { createAppsAdminRoutes } from "./routes.js";
10
+ export { appAccessCredentialStatusEnum, appAccessCredentials, appAccessTokenModeEnum, appAuditEventKindEnum, appAuditEvents, appCredentialKindEnum, appCredentials, appDistributionEnum, appExtensionInstallations, appGrantStatusEnum, appGrants, appInstallationRegistrationStatusEnum, appInstallationSettings, appInstallationStatusEnum, appInstallations, appInstallationUpdatePolicyEnum, appLifecycleStateEnum, appOAuthAuthorizationCodes, appOAuthRefreshTokens, appRedirectUris, appReleaseArtifactStateEnum, appReleaseArtifacts, appReleaseLocalizations, appReleaseStateEnum, appReleases, appSecretReferences, apps, appWebhookSubscriptionStatusEnum, appWebhookSubscriptions, } from "./schema.js";
11
+ export { createAppsService } from "./service.js";
12
+ export type { AppWebhookDeliveryOptions } from "./webhook-delivery.js";
13
+ export { createAppWebhookDeliveryStore, createAppWebhookEventQueue, enqueueAppWebhookEvent, listAppWebhookHealth, replayAppWebhookDelivery, } from "./webhook-delivery.js";
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ export * from "./access-boundary.js";
2
+ export * from "./compiler.js";
3
+ export * from "./consent.js";
4
+ export * from "./contracts.js";
5
+ export * from "./ingestion.js";
6
+ export * from "./installation-service.js";
7
+ export * from "./oauth-crypto.js";
8
+ export * from "./oauth-service.js";
9
+ export { createAppsAdminRoutes } from "./routes.js";
10
+ export { appAccessCredentialStatusEnum, appAccessCredentials, appAccessTokenModeEnum, appAuditEventKindEnum, appAuditEvents, appCredentialKindEnum, appCredentials, appDistributionEnum, appExtensionInstallations, appGrantStatusEnum, appGrants, appInstallationRegistrationStatusEnum, appInstallationSettings, appInstallationStatusEnum, appInstallations, appInstallationUpdatePolicyEnum, appLifecycleStateEnum, appOAuthAuthorizationCodes, appOAuthRefreshTokens, appRedirectUris, appReleaseArtifactStateEnum, appReleaseArtifacts, appReleaseLocalizations, appReleaseStateEnum, appReleases, appSecretReferences, apps, appWebhookSubscriptionStatusEnum, appWebhookSubscriptions, } from "./schema.js";
11
+ export { createAppsService } from "./service.js";
12
+ export { createAppWebhookDeliveryStore, createAppWebhookEventQueue, enqueueAppWebhookEvent, listAppWebhookHealth, replayAppWebhookDelivery, } from "./webhook-delivery.js";
@@ -0,0 +1,15 @@
1
+ export interface ManifestFetchOptions {
2
+ fetch?: typeof fetch;
3
+ maxBytes?: number;
4
+ maxRedirects?: number;
5
+ timeoutMs?: number;
6
+ allowedContentTypes?: readonly string[];
7
+ resolveHost?: (hostname: string) => Promise<readonly string[]>;
8
+ }
9
+ export interface FetchedManifest {
10
+ url: string;
11
+ contentType: string;
12
+ body: unknown;
13
+ bytes: number;
14
+ }
15
+ export declare function fetchProtectedManifest(inputUrl: string, options?: ManifestFetchOptions): Promise<FetchedManifest>;