@automate.ax/api-contract 0.7.0 → 0.8.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/dist/index.js CHANGED
@@ -7,19 +7,67 @@ import { orgContract } from "./org.js";
7
7
  import { projectContract } from "./project.js";
8
8
  import { runtimeContract } from "./runtime.js";
9
9
  export { AUTOMATION_SDK_VERSION, deploymentTriggerInfoSchema, } from "./deployment.js";
10
+ export { runtimeContract } from "./runtime.js";
10
11
  export { createdOrganizationApiKeyOutputSchema, organizationApiKeyOutputSchema, } from "./api-key.js";
11
12
  export { integrationServiceManifestSchema, } from "./authorization.js";
12
13
  export { activeSubscriptionOutputSchema, createdInvitationOutputSchema, organizationDetailsOutputSchema, organizationInvitationOutputSchema, organizationMemberOutputSchema, organizationProjectOutputSchema, organizationRoleSchema, organizationSummaryOutputSchema, publicOrganizationOutputSchema, } from "./org.js";
13
14
  export { projectOutputSchema, projectWithOrganizationOutputSchema, } from "./project.js";
14
15
  export { reasonableNameSchema } from "./schemas.js";
15
16
  export { userOutputSchema } from "./auth.js";
16
- export const contract = {
17
+ /** Procedures exposed by the public Node client and OpenAPI document. */
18
+ export const publicContract = {
17
19
  apiKey: apiKeyContract,
18
- authorization: authorizationContract,
20
+ org: {
21
+ create: orgContract.create,
22
+ delete: orgContract.delete,
23
+ get: orgContract.get,
24
+ invite: orgContract.invite,
25
+ leave: orgContract.leave,
26
+ list: orgContract.list,
27
+ remove: orgContract.remove,
28
+ update: orgContract.update,
29
+ },
30
+ project: {
31
+ create: projectContract.create,
32
+ delete: projectContract.delete,
33
+ get: projectContract.get,
34
+ list: projectContract.list,
35
+ update: projectContract.update,
36
+ },
37
+ session: {
38
+ me: authContract.me,
39
+ },
40
+ };
41
+ /** Procedures used by first-party control-plane clients. */
42
+ export const firstPartyContract = {
43
+ ...publicContract,
44
+ authorization: {
45
+ beginConnection: authorizationContract.beginConnection,
46
+ get: authorizationContract.get,
47
+ selectAccount: authorizationContract.selectAccount,
48
+ submitCredentials: authorizationContract.submitCredentials,
49
+ },
19
50
  deployment: deploymentContract,
51
+ org: {
52
+ ...publicContract.org,
53
+ canInvite: orgContract.canInvite,
54
+ },
55
+ project: {
56
+ ...publicContract.project,
57
+ canCreate: projectContract.canCreate,
58
+ },
59
+ session: {
60
+ ...publicContract.session,
61
+ linkDeviceSession: authContract.linkDeviceSession,
62
+ },
63
+ };
64
+ /** Complete contract implemented by the API service. */
65
+ export const contract = {
66
+ ...firstPartyContract,
67
+ authorization: {
68
+ ...firstPartyContract.authorization,
69
+ callback: authorizationContract.callback,
70
+ },
20
71
  events: eventsContract,
21
- org: orgContract,
22
- project: projectContract,
23
72
  runtime: runtimeContract,
24
- session: authContract,
25
73
  };
package/dist/runtime.d.ts CHANGED
@@ -64,7 +64,7 @@ export declare const runtimeContract: {
64
64
  sendEmail: import("@orpc/contract").ContractProcedureBuilderWithInputOutput<z.ZodObject<{
65
65
  subject: z.ZodString;
66
66
  text: z.ZodString;
67
- to: z.ZodEmail;
67
+ to: z.ZodUnion<readonly [z.ZodEmail, z.ZodLiteral<"*">]>;
68
68
  }, z.core.$strip>, z.ZodObject<{
69
69
  id: z.ZodNullable<z.ZodString>;
70
70
  }, z.core.$strip>, Record<never, never>, Record<never, never>>;
package/dist/runtime.js CHANGED
@@ -86,7 +86,7 @@ export const runtimeContract = {
86
86
  .input(z.object({
87
87
  subject: z.string().min(1),
88
88
  text: z.string().min(1),
89
- to: z.email(),
89
+ to: z.union([z.email(), z.literal("*")]),
90
90
  }))
91
91
  .output(z.object({
92
92
  id: z.string().nullable(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automate.ax/api-contract",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Public oRPC contract for the Automate.ax API.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -20,10 +20,13 @@
20
20
  "exports": {
21
21
  ".": "./src/index.ts"
22
22
  },
23
- "cjs": false
23
+ "cjs": false,
24
+ "conditions": {
25
+ "bun": "src"
26
+ }
24
27
  },
25
28
  "dependencies": {
26
- "@automate.ax/codec": "0.7.0",
29
+ "@automate.ax/codec": "0.8.0",
27
30
  "@orpc/contract": "1.13.14",
28
31
  "zod": "^4.3.6"
29
32
  },
@@ -48,6 +51,7 @@
48
51
  },
49
52
  "files": [
50
53
  "dist",
54
+ "src",
51
55
  "README.md",
52
56
  "LICENSE"
53
57
  ],
@@ -59,6 +63,7 @@
59
63
  },
60
64
  "exports": {
61
65
  ".": {
66
+ "bun": "./src/index.ts",
62
67
  "types": "./dist/index.d.ts",
63
68
  "default": "./dist/index.js"
64
69
  }
package/src/api-key.ts ADDED
@@ -0,0 +1,84 @@
1
+ import { oc } from "@orpc/contract"
2
+ import { z } from "zod"
3
+ import { reasonableNameSchema } from "./schemas"
4
+
5
+ export const organizationApiKeyOutputSchema = z.object({
6
+ id: z.string(),
7
+ name: z.string().nullable(),
8
+ start: z.string().nullable(),
9
+ createdAt: z.date(),
10
+ updatedAt: z.date(),
11
+ expiresAt: z.date().nullable(),
12
+ })
13
+
14
+ export const createdOrganizationApiKeyOutputSchema =
15
+ organizationApiKeyOutputSchema.extend({
16
+ key: z.string(),
17
+ })
18
+
19
+ export const apiKeyContract = {
20
+ create: oc
21
+ .route({
22
+ method: "POST",
23
+ path: "/organizations/{organizationId}/api-keys",
24
+ operationId: "createOrganizationApiKey",
25
+ summary: "Create organization API key",
26
+ description:
27
+ "Creates an API key owned by the organization. The secret is returned once.",
28
+ successStatus: 201,
29
+ successDescription: "API key created.",
30
+ tags: ["API Keys"],
31
+ })
32
+ .input(
33
+ z.object({
34
+ organizationId: z.string(),
35
+ name: reasonableNameSchema,
36
+ }),
37
+ )
38
+ .output(createdOrganizationApiKeyOutputSchema),
39
+ delete: oc
40
+ .route({
41
+ method: "DELETE",
42
+ path: "/organizations/{organizationId}/api-keys/{keyId}",
43
+ operationId: "deleteOrganizationApiKey",
44
+ summary: "Delete organization API key",
45
+ description: "Permanently revokes an organization API key.",
46
+ successStatus: 204,
47
+ tags: ["API Keys"],
48
+ })
49
+ .input(
50
+ z.object({
51
+ keyId: z.string(),
52
+ organizationId: z.string(),
53
+ }),
54
+ )
55
+ .output(z.void()),
56
+ list: oc
57
+ .route({
58
+ method: "GET",
59
+ path: "/organizations/{organizationId}/api-keys",
60
+ operationId: "listOrganizationApiKeys",
61
+ summary: "List organization API keys",
62
+ description: "Lists API keys owned by an organization.",
63
+ tags: ["API Keys"],
64
+ })
65
+ .input(z.object({ organizationId: z.string() }))
66
+ .output(organizationApiKeyOutputSchema.array()),
67
+ update: oc
68
+ .route({
69
+ method: "PATCH",
70
+ path: "/organizations/{organizationId}/api-keys/{keyId}",
71
+ operationId: "updateOrganizationApiKey",
72
+ summary: "Update organization API key",
73
+ description: "Updates an organization API key's display name.",
74
+ tags: ["API Keys"],
75
+ })
76
+ .input(
77
+ z.object({
78
+ keyId: z.string(),
79
+ name: reasonableNameSchema,
80
+ organizationId: z.string(),
81
+ }),
82
+ )
83
+ .output(organizationApiKeyOutputSchema),
84
+ }
package/src/auth.ts ADDED
@@ -0,0 +1,39 @@
1
+ import { oc } from "@orpc/contract"
2
+ import { z } from "zod"
3
+
4
+ export const userOutputSchema = z.object({
5
+ id: z.string(),
6
+ name: z.string(),
7
+ email: z.email(),
8
+ emailVerified: z.boolean(),
9
+ image: z.string().nullable().optional(),
10
+ createdAt: z.date(),
11
+ updatedAt: z.date(),
12
+ })
13
+
14
+ export const authContract = {
15
+ me: oc
16
+ .route({
17
+ method: "GET",
18
+ path: "/me",
19
+ operationId: "getCurrentUser",
20
+ summary: "Get current user",
21
+ description:
22
+ "Returns the authenticated user for the current bearer token or cookie session.",
23
+ tags: ["Session"],
24
+ })
25
+ .output(userOutputSchema),
26
+ linkDeviceSession: oc
27
+ .route({
28
+ method: "POST",
29
+ path: "/me/session/link-device",
30
+ operationId: "linkDeviceSession",
31
+ summary: "Link device session",
32
+ description:
33
+ "Migrates anonymous-session data into authenticated user context.",
34
+ tags: ["Session"],
35
+ successStatus: 204,
36
+ })
37
+ .input(z.object({ newAccessToken: z.string() }))
38
+ .output(z.void()),
39
+ }
@@ -0,0 +1,151 @@
1
+ import { oc } from "@orpc/contract"
2
+ import { z } from "zod"
3
+
4
+ const integrationFormFieldBaseSchema = z.object({
5
+ description: z.string().optional(),
6
+ label: z.string().min(1),
7
+ name: z.string().min(1),
8
+ required: z.boolean(),
9
+ })
10
+
11
+ const INTEGRATION_FORM_FIELD_SCHEMA = z.discriminatedUnion("input", [
12
+ integrationFormFieldBaseSchema.extend({
13
+ default: z.string().optional(),
14
+ input: z.enum(["email", "text", "url"]),
15
+ placeholder: z.string().optional(),
16
+ }),
17
+ integrationFormFieldBaseSchema.extend({
18
+ input: z.literal("password"),
19
+ placeholder: z.string().optional(),
20
+ }),
21
+ integrationFormFieldBaseSchema.extend({
22
+ default: z.number().optional(),
23
+ input: z.literal("number"),
24
+ placeholder: z.string().optional(),
25
+ }),
26
+ integrationFormFieldBaseSchema.extend({
27
+ default: z.boolean().optional(),
28
+ input: z.literal("boolean"),
29
+ }),
30
+ ])
31
+
32
+ export const integrationServiceManifestSchema = z.object({
33
+ connection: z.discriminatedUnion("type", [
34
+ z.object({
35
+ type: z.literal("redirect"),
36
+ }),
37
+ z.object({
38
+ fields: INTEGRATION_FORM_FIELD_SCHEMA.array().min(1),
39
+ type: z.literal("form"),
40
+ }),
41
+ ]),
42
+ description: z.string().optional(),
43
+ name: z.string().min(1),
44
+ })
45
+
46
+ export type IntegrationServiceManifest = z.infer<
47
+ typeof integrationServiceManifestSchema
48
+ >
49
+
50
+ type ScopeRequirement =
51
+ | string
52
+ | {
53
+ requirements: ScopeRequirement[]
54
+ type: "and" | "or"
55
+ }
56
+ const SCOPE_REQUIREMENT_SCHEMA: z.ZodType<ScopeRequirement> = z.lazy(() =>
57
+ z.union([
58
+ z.string(),
59
+ z.strictObject({
60
+ requirements: SCOPE_REQUIREMENT_SCHEMA.array(),
61
+ type: z.enum(["and", "or"]),
62
+ }),
63
+ ]),
64
+ )
65
+
66
+ export const authorizationContract = {
67
+ beginConnection: oc
68
+ .input(
69
+ z.object({
70
+ binding: z.string().min(1),
71
+ deploymentId: z.string(),
72
+ serviceId: z.string().min(1),
73
+ }),
74
+ )
75
+ .output(z.object({ url: z.url() })),
76
+ callback: oc
77
+ .route({
78
+ method: "GET",
79
+ path: "/integrations/{serviceId}/callback",
80
+ operationId: "completeIntegrationAuthorization",
81
+ summary: "Complete integration authorization",
82
+ description: "Completes a redirect-based integration authorization flow.",
83
+ tags: ["Integrations"],
84
+ })
85
+ .input(z.object({ serviceId: z.string().min(1) }))
86
+ .output(z.string()),
87
+ get: oc
88
+ .input(
89
+ z.object({
90
+ deploymentId: z.string(),
91
+ }),
92
+ )
93
+ .output(
94
+ z.object({
95
+ accounts: z
96
+ .object({
97
+ checkedAt: z.date().nullable(),
98
+ error: z
99
+ .object({
100
+ code: z.string(),
101
+ message: z.string(),
102
+ })
103
+ .nullable(),
104
+ id: z.string(),
105
+ label: z.string(),
106
+ scopes: z.string().array(),
107
+ serviceId: z.string(),
108
+ serviceSub: z.string(),
109
+ })
110
+ .array(),
111
+ requirements: z
112
+ .object({
113
+ accountId: z.string().nullable(),
114
+ binding: z.string(),
115
+ eligibleAccountIds: z.string().array(),
116
+ minimalScopes: z.string().array(),
117
+ requiredScopes: SCOPE_REQUIREMENT_SCHEMA.array(),
118
+ satisfied: z.boolean(),
119
+ serviceId: z.string(),
120
+ })
121
+ .array(),
122
+ services: integrationServiceManifestSchema
123
+ .extend({
124
+ id: z.string().min(1),
125
+ })
126
+ .array(),
127
+ }),
128
+ ),
129
+ submitCredentials: oc
130
+ .input(
131
+ z.object({
132
+ binding: z.string().min(1),
133
+ data: z.record(
134
+ z.string(),
135
+ z.union([z.string(), z.number(), z.boolean()]),
136
+ ),
137
+ deploymentId: z.string(),
138
+ serviceId: z.string().min(1),
139
+ }),
140
+ )
141
+ .output(z.void()),
142
+ selectAccount: oc
143
+ .input(
144
+ z.object({
145
+ accountId: z.string(),
146
+ binding: z.string().min(1),
147
+ deploymentId: z.string(),
148
+ }),
149
+ )
150
+ .output(z.void()),
151
+ }
@@ -0,0 +1,107 @@
1
+ import { oc } from "@orpc/contract"
2
+ import { z } from "zod"
3
+
4
+ /** SDK compatibility version used to select the immutable runtime image. */
5
+ export const AUTOMATION_SDK_VERSION = "1"
6
+
7
+ export const deploymentTriggerInfoSchema = z.object({
8
+ details: z
9
+ .object({
10
+ label: z.string(),
11
+ value: z.string(),
12
+ })
13
+ .array(),
14
+ name: z.string(),
15
+ type: z.string(),
16
+ })
17
+
18
+ export type DeploymentTriggerInfo = z.infer<typeof deploymentTriggerInfoSchema>
19
+
20
+ export const deploymentContract = {
21
+ create: oc
22
+ .input(
23
+ z.object({
24
+ artifact: z.instanceof(Blob),
25
+ manifest: z
26
+ .object({
27
+ entrypoint: z.string().min(1),
28
+ identityKey: z.string().min(1),
29
+ })
30
+ .array()
31
+ .min(1, "Deployment must include at least one automation.")
32
+ .superRefine((manifest, context) => {
33
+ const identityKeys = new Set<string>()
34
+
35
+ for (const [index, entry] of manifest.entries()) {
36
+ if (identityKeys.has(entry.identityKey)) {
37
+ context.addIssue({
38
+ code: "custom",
39
+ message: "Automation identity keys must be unique.",
40
+ path: [index, "identityKey"],
41
+ })
42
+ }
43
+ identityKeys.add(entry.identityKey)
44
+
45
+ if (
46
+ entry.entrypoint.includes("\\") ||
47
+ entry.entrypoint.startsWith("/") ||
48
+ entry.entrypoint
49
+ .split("/")
50
+ .some(
51
+ (segment) =>
52
+ segment === "" || segment === "." || segment === "..",
53
+ )
54
+ ) {
55
+ context.addIssue({
56
+ code: "custom",
57
+ message: "Entrypoint must be a safe relative POSIX path.",
58
+ path: [index, "entrypoint"],
59
+ })
60
+ }
61
+ }
62
+ }),
63
+ mode: z.enum(["reconcile", "upsert"]),
64
+ projectId: z.string(),
65
+ }),
66
+ )
67
+ .output(z.object({ id: z.string() })),
68
+ get: oc
69
+ .input(
70
+ z.object({
71
+ deploymentId: z.string(),
72
+ projectId: z.string(),
73
+ }),
74
+ )
75
+ .output(
76
+ z.object({
77
+ automations: z
78
+ .object({
79
+ description: z.string(),
80
+ identityKey: z.string(),
81
+ triggers: deploymentTriggerInfoSchema.array(),
82
+ })
83
+ .array(),
84
+ id: z.string(),
85
+ job: z
86
+ .object({
87
+ attempts: z.number().int().nonnegative(),
88
+ error: z.string().nullable(),
89
+ maxAttempts: z.number().int().positive(),
90
+ nextRunAt: z.date(),
91
+ })
92
+ .nullable(),
93
+ project: z.object({
94
+ id: z.string(),
95
+ name: z.string(),
96
+ }),
97
+ status: z.enum([
98
+ "planning",
99
+ "awaiting_authorization",
100
+ "configuring",
101
+ "publishing",
102
+ "succeeded",
103
+ "failed",
104
+ ]),
105
+ }),
106
+ ),
107
+ }
@@ -0,0 +1,34 @@
1
+ import { oc } from "@orpc/contract"
2
+ import { z } from "zod"
3
+
4
+ export const gmailEventsContract = {
5
+ gmailPush: oc
6
+ .route({
7
+ method: "POST",
8
+ path: "/events/gmail",
9
+ operationId: "receiveGmailPush",
10
+ summary: "Receive a Gmail push notification",
11
+ description:
12
+ "Receives and authenticates Gmail mailbox notifications delivered by Google Cloud Pub/Sub.",
13
+ tags: ["Events"],
14
+ successStatus: 204,
15
+ })
16
+ .input(
17
+ z.object({
18
+ /** Pub/Sub message containing the encoded Gmail notification. */
19
+ message: z.object({
20
+ /** Base64-encoded Gmail mailbox notification. */
21
+ data: z.string().min(1),
22
+
23
+ /** Provider-assigned identifier used to deduplicate delivery. */
24
+ messageId: z.string().min(1),
25
+ }),
26
+
27
+ /** Pub/Sub subscription that delivered the message. */
28
+ subscription: z.literal(
29
+ "projects/opkitty/subscriptions/automate-ax-gmail-push",
30
+ ),
31
+ }),
32
+ )
33
+ .output(z.void()),
34
+ }
@@ -0,0 +1,18 @@
1
+ import { oc } from "@orpc/contract"
2
+ import { z } from "zod"
3
+
4
+ export const googleCalendarEventsContract = {
5
+ googleCalendarPush: oc
6
+ .route({
7
+ method: "POST",
8
+ path: "/events/google-calendar",
9
+ operationId: "receiveGoogleCalendarPush",
10
+ summary: "Receive a Google Calendar push notification",
11
+ description:
12
+ "Receives header-only Google Calendar resource-change notifications.",
13
+ tags: ["Events"],
14
+ successStatus: 204,
15
+ })
16
+ .input(z.void())
17
+ .output(z.void()),
18
+ }
@@ -0,0 +1,41 @@
1
+ import { oc } from "@orpc/contract"
2
+ import { z } from "zod"
3
+
4
+ export const googleFormsEventsContract = {
5
+ googleFormsPush: oc
6
+ .route({
7
+ method: "POST",
8
+ path: "/events/google-forms",
9
+ operationId: "receiveGoogleFormsPush",
10
+ summary: "Receive a Google Forms push notification",
11
+ description:
12
+ "Receives and authenticates Google Forms notifications delivered by Google Cloud Pub/Sub.",
13
+ tags: ["Events"],
14
+ successStatus: 204,
15
+ })
16
+ .input(
17
+ z.object({
18
+ /** Pub/Sub message containing Google Forms notification attributes. */
19
+ message: z.object({
20
+ /** Provider routing fields for the watched form. */
21
+ attributes: z.object({
22
+ eventType: z.enum(["SCHEMA", "RESPONSES"]),
23
+ formId: z.string().min(1),
24
+ watchId: z.string().min(1),
25
+ }),
26
+
27
+ /** Provider-assigned identifier used to deduplicate delivery. */
28
+ messageId: z.string().min(1),
29
+
30
+ /** RFC 3339 time at which Pub/Sub published the notification. */
31
+ publishTime: z.iso.datetime({ offset: true }),
32
+ }),
33
+
34
+ /** Pub/Sub subscription that delivered the message. */
35
+ subscription: z.literal(
36
+ "projects/opkitty/subscriptions/automate-ax-google-forms-push",
37
+ ),
38
+ }),
39
+ )
40
+ .output(z.void()),
41
+ }
@@ -0,0 +1,9 @@
1
+ import { googleCalendarEventsContract } from "./google-calendar"
2
+ import { googleFormsEventsContract } from "./google-forms"
3
+ import { gmailEventsContract } from "./gmail"
4
+
5
+ export const eventsContract = {
6
+ ...googleCalendarEventsContract,
7
+ ...googleFormsEventsContract,
8
+ ...gmailEventsContract,
9
+ }