@automate.ax/api-contract 0.7.1 → 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.d.ts CHANGED
@@ -826,7 +826,7 @@ export declare const contract: {
826
826
  sendEmail: import("@orpc/contract").ContractProcedureBuilderWithInputOutput<import("zod").ZodObject<{
827
827
  subject: import("zod").ZodString;
828
828
  text: import("zod").ZodString;
829
- to: import("zod").ZodEmail;
829
+ to: import("zod").ZodUnion<readonly [import("zod").ZodEmail, import("zod").ZodLiteral<"*">]>;
830
830
  }, import("zod/v4/core").$strip>, import("zod").ZodObject<{
831
831
  id: import("zod").ZodNullable<import("zod").ZodString>;
832
832
  }, import("zod/v4/core").$strip>, Record<never, never>, Record<never, never>>;
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.1",
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.1",
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
+ }
package/src/index.ts ADDED
@@ -0,0 +1,100 @@
1
+ import { apiKeyContract } from "./api-key"
2
+ import { authContract } from "./auth"
3
+ import { authorizationContract } from "./authorization"
4
+ import { deploymentContract } from "./deployment"
5
+ import { eventsContract } from "./events"
6
+ import { orgContract } from "./org"
7
+ import { projectContract } from "./project"
8
+ import { runtimeContract } from "./runtime"
9
+
10
+ export {
11
+ AUTOMATION_SDK_VERSION,
12
+ deploymentTriggerInfoSchema,
13
+ type DeploymentTriggerInfo,
14
+ } from "./deployment"
15
+ export { runtimeContract } from "./runtime"
16
+ export {
17
+ createdOrganizationApiKeyOutputSchema,
18
+ organizationApiKeyOutputSchema,
19
+ } from "./api-key"
20
+ export {
21
+ integrationServiceManifestSchema,
22
+ type IntegrationServiceManifest,
23
+ } from "./authorization"
24
+ export {
25
+ activeSubscriptionOutputSchema,
26
+ createdInvitationOutputSchema,
27
+ organizationDetailsOutputSchema,
28
+ organizationInvitationOutputSchema,
29
+ organizationMemberOutputSchema,
30
+ organizationProjectOutputSchema,
31
+ organizationRoleSchema,
32
+ organizationSummaryOutputSchema,
33
+ publicOrganizationOutputSchema,
34
+ } from "./org"
35
+ export {
36
+ projectOutputSchema,
37
+ projectWithOrganizationOutputSchema,
38
+ } from "./project"
39
+ export { reasonableNameSchema } from "./schemas"
40
+ export { userOutputSchema } from "./auth"
41
+
42
+ /** Procedures exposed by the public Node client and OpenAPI document. */
43
+ export const publicContract = {
44
+ apiKey: apiKeyContract,
45
+ org: {
46
+ create: orgContract.create,
47
+ delete: orgContract.delete,
48
+ get: orgContract.get,
49
+ invite: orgContract.invite,
50
+ leave: orgContract.leave,
51
+ list: orgContract.list,
52
+ remove: orgContract.remove,
53
+ update: orgContract.update,
54
+ },
55
+ project: {
56
+ create: projectContract.create,
57
+ delete: projectContract.delete,
58
+ get: projectContract.get,
59
+ list: projectContract.list,
60
+ update: projectContract.update,
61
+ },
62
+ session: {
63
+ me: authContract.me,
64
+ },
65
+ }
66
+
67
+ /** Procedures used by first-party control-plane clients. */
68
+ export const firstPartyContract = {
69
+ ...publicContract,
70
+ authorization: {
71
+ beginConnection: authorizationContract.beginConnection,
72
+ get: authorizationContract.get,
73
+ selectAccount: authorizationContract.selectAccount,
74
+ submitCredentials: authorizationContract.submitCredentials,
75
+ },
76
+ deployment: deploymentContract,
77
+ org: {
78
+ ...publicContract.org,
79
+ canInvite: orgContract.canInvite,
80
+ },
81
+ project: {
82
+ ...publicContract.project,
83
+ canCreate: projectContract.canCreate,
84
+ },
85
+ session: {
86
+ ...publicContract.session,
87
+ linkDeviceSession: authContract.linkDeviceSession,
88
+ },
89
+ }
90
+
91
+ /** Complete contract implemented by the API service. */
92
+ export const contract = {
93
+ ...firstPartyContract,
94
+ authorization: {
95
+ ...firstPartyContract.authorization,
96
+ callback: authorizationContract.callback,
97
+ },
98
+ events: eventsContract,
99
+ runtime: runtimeContract,
100
+ }
package/src/org.ts ADDED
@@ -0,0 +1,205 @@
1
+ import { oc } from "@orpc/contract"
2
+ import { z } from "zod"
3
+ import { reasonableNameSchema } from "./schemas"
4
+
5
+ export const organizationRoleSchema = z.enum(["member", "admin", "owner"])
6
+
7
+ export const publicOrganizationOutputSchema = z.object({
8
+ id: z.string(),
9
+ name: z.string(),
10
+ })
11
+
12
+ export const activeSubscriptionOutputSchema = z.object({
13
+ id: z.string(),
14
+ plan: z.string(),
15
+ status: z.string(),
16
+ periodEnd: z.date().nullable(),
17
+ cancelAtPeriodEnd: z.boolean(),
18
+ })
19
+
20
+ export const organizationSummaryOutputSchema = z.object({
21
+ id: z.string(),
22
+ name: z.string(),
23
+ logo: z.string().nullable(),
24
+ metadata: z.string().nullable(),
25
+ createdAt: z.date(),
26
+ role: organizationRoleSchema,
27
+ memberCount: z.number(),
28
+ pendingInvitationCount: z.number(),
29
+ projectCount: z.number(),
30
+ activeSubscription: activeSubscriptionOutputSchema.nullable(),
31
+ plan: z.enum(["free", "pro", "enterprise"]),
32
+ limits: z.object({
33
+ projects: z.number(),
34
+ seats: z.number(),
35
+ }),
36
+ })
37
+
38
+ export const organizationMemberOutputSchema = z.object({
39
+ id: z.string(),
40
+ userId: z.string(),
41
+ role: organizationRoleSchema,
42
+ createdAt: z.date(),
43
+ email: z.email(),
44
+ name: z.string(),
45
+ image: z.string().nullable(),
46
+ })
47
+
48
+ export const organizationInvitationOutputSchema = z.object({
49
+ id: z.string(),
50
+ email: z.email(),
51
+ role: organizationRoleSchema.nullable(),
52
+ status: z.string(),
53
+ createdAt: z.date(),
54
+ expiresAt: z.date(),
55
+ })
56
+
57
+ export const organizationProjectOutputSchema = z.object({
58
+ id: z.string(),
59
+ name: z.string(),
60
+ createdAt: z.date(),
61
+ })
62
+
63
+ export const organizationDetailsOutputSchema =
64
+ organizationSummaryOutputSchema.extend({
65
+ members: organizationMemberOutputSchema.array(),
66
+ invitations: organizationInvitationOutputSchema.array(),
67
+ projects: organizationProjectOutputSchema.array(),
68
+ })
69
+
70
+ export const createdInvitationOutputSchema = z.object({
71
+ id: z.string(),
72
+ email: z.email(),
73
+ role: organizationRoleSchema.nullable(),
74
+ status: z.string(),
75
+ })
76
+
77
+ export const orgContract = {
78
+ canInvite: oc
79
+ .input(z.object({ organizationId: z.string() }))
80
+ .output(z.boolean()),
81
+ create: oc
82
+ .route({
83
+ method: "POST",
84
+ path: "/organizations",
85
+ operationId: "createOrganization",
86
+ summary: "Create organization",
87
+ description: "Creates an organization owned by the authenticated user.",
88
+ successStatus: 201,
89
+ successDescription: "Organization created.",
90
+ tags: ["Organizations"],
91
+ })
92
+ .input(z.object({ name: reasonableNameSchema }))
93
+ .output(publicOrganizationOutputSchema),
94
+ delete: oc
95
+ .route({
96
+ method: "DELETE",
97
+ path: "/organizations/{organizationId}",
98
+ operationId: "deleteOrganization",
99
+ summary: "Delete organization",
100
+ description:
101
+ "Deletes an organization. Caller must be the organization owner.",
102
+ tags: ["Organizations"],
103
+ successStatus: 204,
104
+ })
105
+ .input(z.object({ organizationId: z.string() }))
106
+ .output(z.void()),
107
+ get: oc
108
+ .route({
109
+ method: "GET",
110
+ path: "/organizations/{organizationId}",
111
+ operationId: "getOrganization",
112
+ summary: "Get organization",
113
+ description:
114
+ "Returns organization details, members, invitations, projects, plan, and usage.",
115
+ tags: ["Organizations"],
116
+ })
117
+ .input(z.object({ organizationId: z.string().min(1) }))
118
+ .output(organizationDetailsOutputSchema),
119
+ invite: oc
120
+ .route({
121
+ method: "POST",
122
+ path: "/organizations/{organizationId}/invitations",
123
+ operationId: "createOrganizationInvitation",
124
+ summary: "Invite organization member",
125
+ description:
126
+ "Creates a pending invitation for an email address. Caller must be an organization admin or owner.",
127
+ successStatus: 201,
128
+ successDescription: "Invitation created.",
129
+ tags: ["Organizations"],
130
+ })
131
+ .input(
132
+ z.object({
133
+ organizationId: z.string(),
134
+ email: z.email(),
135
+ role: organizationRoleSchema.default("member"),
136
+ }),
137
+ )
138
+ .output(createdInvitationOutputSchema),
139
+ leave: oc
140
+ .route({
141
+ method: "DELETE",
142
+ path: "/organizations/{organizationId}/membership",
143
+ operationId: "leaveOrganization",
144
+ summary: "Leave organization",
145
+ description:
146
+ "Removes the current user from an organization. Owners must transfer ownership or delete the organization instead.",
147
+ tags: ["Organizations"],
148
+ successStatus: 204,
149
+ })
150
+ .input(z.object({ organizationId: z.string() }))
151
+ .output(z.void()),
152
+ list: oc
153
+ .route({
154
+ method: "GET",
155
+ path: "/organizations",
156
+ operationId: "listOrganizations",
157
+ summary: "List organizations",
158
+ description:
159
+ "Lists organizations the authenticated user belongs to, optionally filtered by name or ID.",
160
+ tags: ["Organizations"],
161
+ })
162
+ .input(
163
+ z
164
+ .object({
165
+ query: z.string().min(1).optional(),
166
+ })
167
+ .default({}),
168
+ )
169
+ .output(organizationSummaryOutputSchema.array()),
170
+ remove: oc
171
+ .route({
172
+ method: "DELETE",
173
+ path: "/organizations/{organizationId}/members/{memberIdOrEmail}",
174
+ operationId: "removeOrganizationMember",
175
+ summary: "Remove organization member",
176
+ description:
177
+ "Removes a member from an organization by member ID or email.",
178
+ tags: ["Organizations"],
179
+ successStatus: 204,
180
+ })
181
+ .input(
182
+ z.object({
183
+ organizationId: z.string(),
184
+ memberIdOrEmail: z.string().min(1),
185
+ }),
186
+ )
187
+ .output(z.void()),
188
+ update: oc
189
+ .route({
190
+ method: "PATCH",
191
+ path: "/organizations/{organizationId}",
192
+ operationId: "updateOrganization",
193
+ summary: "Update organization",
194
+ description: "Updates organization fields the caller can manage.",
195
+ successStatus: 204,
196
+ tags: ["Organizations"],
197
+ })
198
+ .input(
199
+ z.object({
200
+ organizationId: z.string(),
201
+ name: reasonableNameSchema,
202
+ }),
203
+ )
204
+ .output(z.void()),
205
+ }
package/src/project.ts ADDED
@@ -0,0 +1,104 @@
1
+ import { oc } from "@orpc/contract"
2
+ import { z } from "zod"
3
+ import { reasonableNameSchema } from "./schemas"
4
+
5
+ export const projectOutputSchema = z.object({
6
+ id: z.string(),
7
+ name: z.string(),
8
+ organizationId: z.string(),
9
+ createdAt: z.date(),
10
+ createdBy: z.string().nullable(),
11
+ })
12
+
13
+ export const projectWithOrganizationOutputSchema = projectOutputSchema.extend({
14
+ org: z.object({
15
+ id: z.string(),
16
+ name: z.string(),
17
+ }),
18
+ })
19
+
20
+ export const projectContract = {
21
+ canCreate: oc
22
+ .input(z.object({ organizationId: z.string() }))
23
+ .output(z.boolean()),
24
+ create: oc
25
+ .route({
26
+ method: "POST",
27
+ path: "/organizations/{organizationId}/projects",
28
+ operationId: "createProject",
29
+ summary: "Create project",
30
+ description:
31
+ "Creates a project inside an organization the user belongs to.",
32
+ successStatus: 201,
33
+ successDescription: "Project created.",
34
+ tags: ["Projects"],
35
+ })
36
+ .input(
37
+ z.object({
38
+ organizationId: z.string(),
39
+ name: reasonableNameSchema,
40
+ }),
41
+ )
42
+ .output(projectOutputSchema),
43
+ list: oc
44
+ .route({
45
+ method: "GET",
46
+ path: "/projects",
47
+ operationId: "listProjects",
48
+ summary: "List projects",
49
+ description:
50
+ "Lists projects visible to the authenticated user, optionally filtered by organization or query.",
51
+ tags: ["Projects"],
52
+ })
53
+ .input(
54
+ z
55
+ .object({
56
+ organizationId: z.string().optional(),
57
+ query: z.string().min(1).optional(),
58
+ })
59
+ .optional(),
60
+ )
61
+ .output(projectWithOrganizationOutputSchema.array()),
62
+ get: oc
63
+ .route({
64
+ method: "GET",
65
+ path: "/projects/{projectId}",
66
+ operationId: "getProject",
67
+ summary: "Get project",
68
+ description: "Returns project details and its organization.",
69
+ tags: ["Projects"],
70
+ })
71
+ .input(z.object({ projectId: z.string() }))
72
+ .output(projectWithOrganizationOutputSchema),
73
+ update: oc
74
+ .route({
75
+ method: "PATCH",
76
+ path: "/projects/{projectId}",
77
+ operationId: "updateProject",
78
+ summary: "Update project",
79
+ description:
80
+ "Updates project fields. Caller must be an organization admin or owner.",
81
+ successStatus: 204,
82
+ tags: ["Projects"],
83
+ })
84
+ .input(
85
+ z.object({
86
+ projectId: z.string(),
87
+ name: reasonableNameSchema,
88
+ }),
89
+ )
90
+ .output(z.void()),
91
+ delete: oc
92
+ .route({
93
+ method: "DELETE",
94
+ path: "/projects/{projectId}",
95
+ operationId: "deleteProject",
96
+ summary: "Delete project",
97
+ description:
98
+ "Deletes a project. Caller must be an organization admin or owner.",
99
+ tags: ["Projects"],
100
+ successStatus: 204,
101
+ })
102
+ .input(z.object({ projectId: z.string() }))
103
+ .output(z.void()),
104
+ }
package/src/runtime.ts ADDED
@@ -0,0 +1,113 @@
1
+ import { encodableSchema } from "@automate.ax/codec"
2
+ import { oc } from "@orpc/contract"
3
+ import { z } from "zod"
4
+
5
+ export const runtimeContract = {
6
+ commit: oc
7
+ .input(
8
+ z.object({
9
+ actionInvocations: z
10
+ .object({
11
+ actionDependencyIds: z.string().array(),
12
+ description: z.string().nullable(),
13
+ eventDependencyIds: z.string().array(),
14
+ inputHash: z.string(),
15
+ name: z.string(),
16
+ slot: z.number().int().nonnegative(),
17
+ })
18
+ .array()
19
+ .default([]),
20
+ }),
21
+ )
22
+ .output(z.void()),
23
+ completeInvocation: oc
24
+ .input(
25
+ z.object({
26
+ output: encodableSchema,
27
+ }),
28
+ )
29
+ .output(z.void()),
30
+ loadInvocation: oc.output(
31
+ z.object({
32
+ context: z.object({
33
+ actionOutputs: z
34
+ .object({
35
+ actionInvocationId: z.string(),
36
+ output: encodableSchema,
37
+ slot: z.number().int(),
38
+ })
39
+ .array(),
40
+ contextId: z.string(),
41
+ events: z
42
+ .object({
43
+ automationEventId: z.string(),
44
+ payload: encodableSchema,
45
+ slot: z.number().int(),
46
+ })
47
+ .array(),
48
+ }),
49
+ id: z.string(),
50
+ slot: z.number().int(),
51
+ }),
52
+ ),
53
+ nextBatch: oc.output(
54
+ z.object({
55
+ context: z.object({
56
+ actionOutputs: z
57
+ .object({
58
+ actionDependencyIds: z.string().array(),
59
+ actionInvocationId: z.string(),
60
+ eventDependencyIds: z.string().array(),
61
+ inputHash: z.string(),
62
+ name: z.string(),
63
+ slot: z.number().int(),
64
+ })
65
+ .array(),
66
+ contextId: z.string(),
67
+ events: z
68
+ .object({
69
+ automationEventId: z.string(),
70
+ slot: z.number().int(),
71
+ })
72
+ .array(),
73
+ }),
74
+ }),
75
+ ),
76
+ resolveAccountInput: oc
77
+ .input(
78
+ z.strictObject({
79
+ binding: z.string().min(1),
80
+ serviceId: z.string().min(1),
81
+ }),
82
+ )
83
+ .output(
84
+ z.object({
85
+ secret: z.record(z.string(), z.unknown()),
86
+ serviceId: z.string(),
87
+ }),
88
+ ),
89
+ sendOutput: oc
90
+ .input(
91
+ z.object({
92
+ output: z.object({
93
+ data: encodableSchema,
94
+ type: z.string().min(1),
95
+ }),
96
+ outputIndex: z.number().int().nonnegative(),
97
+ }),
98
+ )
99
+ .output(z.void()),
100
+ sendEmail: oc
101
+ .input(
102
+ z.object({
103
+ subject: z.string().min(1),
104
+ text: z.string().min(1),
105
+ to: z.union([z.email(), z.literal("*")]),
106
+ }),
107
+ )
108
+ .output(
109
+ z.object({
110
+ id: z.string().nullable(),
111
+ }),
112
+ ),
113
+ }
package/src/schemas.ts ADDED
@@ -0,0 +1,3 @@
1
+ import { z } from "zod"
2
+
3
+ export const reasonableNameSchema = z.string().min(1).max(255).trim()