@smartcrab/contracts-management 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/dist/api-keys.d.ts +251 -0
- package/dist/api-keys.js +78 -0
- package/dist/audit-events.d.ts +94 -0
- package/dist/audit-events.js +45 -0
- package/dist/billing.d.ts +30 -0
- package/dist/billing.js +30 -0
- package/dist/clients.d.ts +328 -0
- package/dist/clients.js +141 -0
- package/dist/common.d.ts +95 -0
- package/dist/common.js +99 -0
- package/dist/connections.d.ts +201 -0
- package/dist/connections.js +107 -0
- package/dist/domains.d.ts +167 -0
- package/dist/domains.js +74 -0
- package/dist/environments.d.ts +194 -0
- package/dist/environments.js +101 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +27 -0
- package/dist/primitives.d.ts +49 -0
- package/dist/primitives.js +56 -0
- package/dist/projects.d.ts +78 -0
- package/dist/projects.js +49 -0
- package/dist/rbac.d.ts +222 -0
- package/dist/rbac.js +226 -0
- package/dist/usage.d.ts +72 -0
- package/dist/usage.js +52 -0
- package/dist/user-jobs.d.ts +253 -0
- package/dist/user-jobs.js +95 -0
- package/dist/user-subresources.d.ts +161 -0
- package/dist/user-subresources.js +85 -0
- package/dist/users.d.ts +193 -0
- package/dist/users.js +112 -0
- package/dist/webhooks.d.ts +306 -0
- package/dist/webhooks.js +134 -0
- package/dist/workspaces.d.ts +172 -0
- package/dist/workspaces.js +77 -0
- package/package.json +42 -0
- package/src/api-keys.test.ts +86 -0
- package/src/api-keys.ts +97 -0
- package/src/audit-events.test.ts +44 -0
- package/src/audit-events.ts +57 -0
- package/src/billing.test.ts +57 -0
- package/src/billing.ts +46 -0
- package/src/clients.test.ts +169 -0
- package/src/clients.ts +182 -0
- package/src/common.test.ts +102 -0
- package/src/common.ts +127 -0
- package/src/connections.test.ts +65 -0
- package/src/connections.ts +134 -0
- package/src/domains.test.ts +48 -0
- package/src/domains.ts +104 -0
- package/src/environments.test.ts +67 -0
- package/src/environments.ts +133 -0
- package/src/index.ts +27 -0
- package/src/primitives.ts +100 -0
- package/src/projects.test.ts +49 -0
- package/src/projects.ts +72 -0
- package/src/rbac.test.ts +148 -0
- package/src/rbac.ts +250 -0
- package/src/secret-fields.test.ts +84 -0
- package/src/usage.test.ts +57 -0
- package/src/usage.ts +66 -0
- package/src/user-jobs.test.ts +86 -0
- package/src/user-jobs.ts +120 -0
- package/src/user-subresources.test.ts +115 -0
- package/src/user-subresources.ts +128 -0
- package/src/users.test.ts +91 -0
- package/src/users.ts +149 -0
- package/src/webhooks.test.ts +187 -0
- package/src/webhooks.ts +182 -0
- package/src/workspaces.test.ts +127 -0
- package/src/workspaces.ts +106 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
workspaceCreateRequestSchema,
|
|
4
|
+
workspaceListResponseSchema,
|
|
5
|
+
workspaceMemberListResponseSchema,
|
|
6
|
+
workspaceMemberSchema,
|
|
7
|
+
workspaceSchema,
|
|
8
|
+
workspaceUpdateRequestSchema,
|
|
9
|
+
} from "./workspaces.js";
|
|
10
|
+
|
|
11
|
+
const VALID_WORKSPACE = {
|
|
12
|
+
id: `wsp_${"a".repeat(32)}`,
|
|
13
|
+
name: "Acme Inc",
|
|
14
|
+
slug: "acme-inc",
|
|
15
|
+
status: "active",
|
|
16
|
+
stripe_customer_id: "cus_abc123",
|
|
17
|
+
created_at: "2026-08-02T08:10:00Z",
|
|
18
|
+
updated_at: "2026-08-02T08:10:00Z",
|
|
19
|
+
deleted_at: null,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
describe("workspaceSchema (design.md §11.1)", () => {
|
|
23
|
+
test("accepts a valid workspace", () => {
|
|
24
|
+
expect(workspaceSchema.safeParse(VALID_WORKSPACE).success).toBe(true);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("accepts a null stripe_customer_id (no billing set up yet)", () => {
|
|
28
|
+
expect(
|
|
29
|
+
workspaceSchema.safeParse({ ...VALID_WORKSPACE, stripe_customer_id: null }).success,
|
|
30
|
+
).toBe(true);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("rejects an unknown top-level key", () => {
|
|
34
|
+
expect(
|
|
35
|
+
workspaceSchema.safeParse({ ...VALID_WORKSPACE, control_database_key: "control_01" }).success,
|
|
36
|
+
).toBe(false);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("rejects an unknown status value", () => {
|
|
40
|
+
expect(workspaceSchema.safeParse({ ...VALID_WORKSPACE, status: "archived" }).success).toBe(
|
|
41
|
+
false,
|
|
42
|
+
);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe("workspaceCreateRequestSchema", () => {
|
|
47
|
+
test("slug is optional", () => {
|
|
48
|
+
expect(workspaceCreateRequestSchema.safeParse({ name: "Acme Inc" }).success).toBe(true);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("rejects an unknown field (e.g. attempting to set status at creation)", () => {
|
|
52
|
+
expect(
|
|
53
|
+
workspaceCreateRequestSchema.safeParse({ name: "Acme Inc", status: "active" }).success,
|
|
54
|
+
).toBe(false);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe("workspaceUpdateRequestSchema", () => {
|
|
59
|
+
test("does not allow patching status (Owner-only lifecycle transitions, design.md §27.2)", () => {
|
|
60
|
+
expect(workspaceUpdateRequestSchema.safeParse({ status: "suspended" }).success).toBe(false);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("accepts an empty patch", () => {
|
|
64
|
+
expect(workspaceUpdateRequestSchema.safeParse({}).success).toBe(true);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe("workspaceListResponseSchema", () => {
|
|
69
|
+
test("wraps a cursor page of workspaces", () => {
|
|
70
|
+
expect(
|
|
71
|
+
workspaceListResponseSchema.safeParse({ items: [VALID_WORKSPACE], next_cursor: null })
|
|
72
|
+
.success,
|
|
73
|
+
).toBe(true);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const VALID_MEMBER = {
|
|
78
|
+
platform_user_id: "usr_owner",
|
|
79
|
+
role: "owner",
|
|
80
|
+
created_at: "2026-08-02T08:10:00Z",
|
|
81
|
+
updated_at: "2026-08-02T08:10:00Z",
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
describe("workspaceMemberSchema (design.md §11.2, §27.2)", () => {
|
|
85
|
+
test("accepts a valid member", () => {
|
|
86
|
+
expect(workspaceMemberSchema.safeParse(VALID_MEMBER).success).toBe(true);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("accepts every design.md §27.2 role", () => {
|
|
90
|
+
for (const role of ["owner", "admin", "developer", "support", "billing", "viewer"]) {
|
|
91
|
+
expect(workspaceMemberSchema.safeParse({ ...VALID_MEMBER, role }).success).toBe(true);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("rejects an unknown role", () => {
|
|
96
|
+
expect(workspaceMemberSchema.safeParse({ ...VALID_MEMBER, role: "superadmin" }).success).toBe(
|
|
97
|
+
false,
|
|
98
|
+
);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("rejects an unknown top-level key (no credential/session material belongs here)", () => {
|
|
102
|
+
expect(workspaceMemberSchema.safeParse({ ...VALID_MEMBER, password: "hunter2" }).success).toBe(
|
|
103
|
+
false,
|
|
104
|
+
);
|
|
105
|
+
expect(workspaceMemberSchema.safeParse({ ...VALID_MEMBER, session_token: "abc" }).success).toBe(
|
|
106
|
+
false,
|
|
107
|
+
);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe("workspaceMemberListResponseSchema", () => {
|
|
112
|
+
test("wraps a cursor page of members", () => {
|
|
113
|
+
expect(
|
|
114
|
+
workspaceMemberListResponseSchema.safeParse({ items: [VALID_MEMBER], next_cursor: null })
|
|
115
|
+
.success,
|
|
116
|
+
).toBe(true);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("rejects an item carrying a credential field", () => {
|
|
120
|
+
expect(
|
|
121
|
+
workspaceMemberListResponseSchema.safeParse({
|
|
122
|
+
items: [{ ...VALID_MEMBER, password_hash: "abc" }],
|
|
123
|
+
next_cursor: null,
|
|
124
|
+
}).success,
|
|
125
|
+
).toBe(false);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { cursorPageSchema, paginationQuerySchema } from "./common.js";
|
|
3
|
+
import { slugSchema, workspaceIdSchema } from "./primitives.js";
|
|
4
|
+
import { workspaceRoleSchema } from "./rbac.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* `workspaces` (design.md §11.1, §7.1). No secret/internal-routing columns
|
|
8
|
+
* exist on this table to exclude; `stripe_customer_id` is an opaque Stripe
|
|
9
|
+
* identifier (not a secret — it carries no ability to act on the customer)
|
|
10
|
+
* and is useful to dashboard/billing UI, so it is exposed as nullable.
|
|
11
|
+
*/
|
|
12
|
+
export const WORKSPACE_STATUSES = ["active", "past_due", "suspended", "deleted"] as const;
|
|
13
|
+
export const workspaceStatusSchema = z.enum(WORKSPACE_STATUSES);
|
|
14
|
+
export type WorkspaceStatus = (typeof WORKSPACE_STATUSES)[number];
|
|
15
|
+
|
|
16
|
+
export const workspaceSchema = z.strictObject({
|
|
17
|
+
id: workspaceIdSchema,
|
|
18
|
+
name: z.string().min(1).max(200),
|
|
19
|
+
slug: slugSchema,
|
|
20
|
+
status: workspaceStatusSchema,
|
|
21
|
+
stripe_customer_id: z.string().min(1).nullable(),
|
|
22
|
+
created_at: z.iso.datetime(),
|
|
23
|
+
updated_at: z.iso.datetime(),
|
|
24
|
+
deleted_at: z.iso.datetime().nullable(),
|
|
25
|
+
});
|
|
26
|
+
export type Workspace = z.infer<typeof workspaceSchema>;
|
|
27
|
+
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// GET /workspaces
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
export const workspaceListQuerySchema = paginationQuerySchema.extend({
|
|
33
|
+
status: workspaceStatusSchema.optional(),
|
|
34
|
+
});
|
|
35
|
+
export type WorkspaceListQuery = z.infer<typeof workspaceListQuerySchema>;
|
|
36
|
+
|
|
37
|
+
export const workspaceListResponseSchema = cursorPageSchema(workspaceSchema);
|
|
38
|
+
export type WorkspaceListResponse = z.infer<typeof workspaceListResponseSchema>;
|
|
39
|
+
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// POST /workspaces
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
/** `slug` is optional: when omitted, the server derives one from `name` (design.md §11.1 `slug UNIQUE`). */
|
|
45
|
+
export const workspaceCreateRequestSchema = z.strictObject({
|
|
46
|
+
name: z.string().min(1).max(200),
|
|
47
|
+
slug: slugSchema.optional(),
|
|
48
|
+
});
|
|
49
|
+
export type WorkspaceCreateRequest = z.infer<typeof workspaceCreateRequestSchema>;
|
|
50
|
+
|
|
51
|
+
export const workspaceCreateResponseSchema = workspaceSchema;
|
|
52
|
+
export type WorkspaceCreateResponse = z.infer<typeof workspaceCreateResponseSchema>;
|
|
53
|
+
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
// GET /workspaces/:workspaceId
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
|
|
58
|
+
export const workspaceGetResponseSchema = workspaceSchema;
|
|
59
|
+
export type WorkspaceGetResponse = z.infer<typeof workspaceGetResponseSchema>;
|
|
60
|
+
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
// PATCH /workspaces/:workspaceId
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* `status` is deliberately excluded: workspace suspension/deletion are
|
|
67
|
+
* billing- and Owner-driven lifecycle transitions (design.md §27.2 "Owner: 全
|
|
68
|
+
* 操作、課金、Workspace削除"), not a generic field patch.
|
|
69
|
+
*/
|
|
70
|
+
export const workspaceUpdateRequestSchema = z.strictObject({
|
|
71
|
+
name: z.string().min(1).max(200).optional(),
|
|
72
|
+
slug: slugSchema.optional(),
|
|
73
|
+
});
|
|
74
|
+
export type WorkspaceUpdateRequest = z.infer<typeof workspaceUpdateRequestSchema>;
|
|
75
|
+
|
|
76
|
+
export const workspaceUpdateResponseSchema = workspaceSchema;
|
|
77
|
+
export type WorkspaceUpdateResponse = z.infer<typeof workspaceUpdateResponseSchema>;
|
|
78
|
+
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
// GET /workspaces/:workspaceId/members
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* `workspace_members` (design.md §11.2, §27.2). Exposes only the fields the
|
|
85
|
+
* assignment enumerates — `platform_user_id`/`role`/`created_at`/
|
|
86
|
+
* `updated_at` — no credential or session material (this table carries none
|
|
87
|
+
* anyway; those four columns are its entire shape). `platform_user_id` is a
|
|
88
|
+
* plain non-empty string rather than `userIdSchema`: it identifies a
|
|
89
|
+
* platform (dashboard) user of this Control Plane's own dogfooded
|
|
90
|
+
* Environment (design.md §27.1), a distinct identifier space from the
|
|
91
|
+
* `usr_...`-pattern end users `userIdSchema` models elsewhere in this
|
|
92
|
+
* package.
|
|
93
|
+
*/
|
|
94
|
+
export const workspaceMemberSchema = z.strictObject({
|
|
95
|
+
platform_user_id: z.string().min(1),
|
|
96
|
+
role: workspaceRoleSchema,
|
|
97
|
+
created_at: z.iso.datetime(),
|
|
98
|
+
updated_at: z.iso.datetime(),
|
|
99
|
+
});
|
|
100
|
+
export type WorkspaceMember = z.infer<typeof workspaceMemberSchema>;
|
|
101
|
+
|
|
102
|
+
export const workspaceMemberListQuerySchema = paginationQuerySchema;
|
|
103
|
+
export type WorkspaceMemberListQuery = z.infer<typeof workspaceMemberListQuerySchema>;
|
|
104
|
+
|
|
105
|
+
export const workspaceMemberListResponseSchema = cursorPageSchema(workspaceMemberSchema);
|
|
106
|
+
export type WorkspaceMemberListResponse = z.infer<typeof workspaceMemberListResponseSchema>;
|