@builder-builder/builder 0.0.21 → 0.0.23
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/README.md +67 -24
- package/dist/bb.js +1 -1
- package/dist/client/client.d.ts +7 -0
- package/dist/client/client.js +26 -0
- package/dist/client/index.d.ts +4 -0
- package/dist/client/index.js +2 -0
- package/dist/client/schema.d.ts +396 -0
- package/dist/client/schema.js +13 -0
- package/dist/errors/check.d.ts +2 -2
- package/dist/errors/check.js +14 -9
- package/dist/errors/exception.d.ts +19 -2
- package/dist/errors/exception.js +74 -4
- package/dist/errors/index.d.ts +1 -0
- package/dist/validate/brand.js +1 -1
- package/package.json +13 -6
package/README.md
CHANGED
|
@@ -1,38 +1,81 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Builder Builder
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## Setup
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
```bash
|
|
6
|
+
npm install
|
|
7
|
+
supabase start
|
|
8
|
+
npm run dev
|
|
9
|
+
```
|
|
6
10
|
|
|
7
|
-
|
|
11
|
+
## Deployment environments
|
|
8
12
|
|
|
9
|
-
|
|
10
|
-
# create a new project in the current directory
|
|
11
|
-
npx sv create
|
|
13
|
+
Where BB runs and which backing services it talks to.
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
| Deployment | Clerk instance | Supabase instance |
|
|
16
|
+
| ----------------- | ---------------- | ----------------------- |
|
|
17
|
+
| local dev | Clerk test | local supabase (docker) |
|
|
18
|
+
| Vercel production | Clerk production | Supabase cloud project |
|
|
16
19
|
|
|
17
|
-
##
|
|
20
|
+
## What lives where
|
|
18
21
|
|
|
19
|
-
|
|
22
|
+
Knowing what's stored in Clerk vs Supabase matters because `npm run db:reset` wipes Supabase but not Clerk.
|
|
20
23
|
|
|
21
|
-
|
|
22
|
-
|
|
24
|
+
| Lives in Clerk (survives `db:reset`) | Lives in Supabase (wiped by `db:reset`) |
|
|
25
|
+
| ------------------------------------ | --------------------------------------- |
|
|
26
|
+
| Users, orgs, roles, permissions | `entities`, `entity_edits` |
|
|
27
|
+
| API keys (with all their claims) | `builder_versions` |
|
|
28
|
+
| | `variants`, `variant_edits` |
|
|
29
|
+
| | `entity_references` |
|
|
23
30
|
|
|
24
|
-
|
|
25
|
-
npm run dev -- --open
|
|
26
|
-
```
|
|
31
|
+
After a reset, you typically re-seed entities from a consumer repo. API keys keep working — re-seeding only repopulates Supabase.
|
|
27
32
|
|
|
28
|
-
##
|
|
33
|
+
## Builder environments
|
|
29
34
|
|
|
30
|
-
|
|
35
|
+
Every entity carries an `environment` of `development`, `staging`, or `production`. Same database, different tables/rows. `development` rows are mutable; `staging` and `production` are immutable snapshots written by promotion.
|
|
31
36
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
37
|
+
| Environment | `GET /api/builder/[id]` returns | `GET /api/builder/[id]/variants` returns |
|
|
38
|
+
| ------------- | ------------------------------- | ---------------------------------------- |
|
|
39
|
+
| `development` | live editable entity | live editable variants |
|
|
40
|
+
| `staging` | staging snapshot | staging snapshot variants |
|
|
41
|
+
| `production` | production snapshot | production snapshot variants |
|
|
42
|
+
|
|
43
|
+
## Promotion
|
|
44
|
+
|
|
45
|
+
The only path from `development` (mutable working copy) into `staging` or `production` (immutable snapshots) is through the **Promote** action in BB's UI. Promotion:
|
|
46
|
+
|
|
47
|
+
1. Runs validation on the entity and its references.
|
|
48
|
+
2. If validation passes, writes a new immutable row into the target environment's snapshot table.
|
|
49
|
+
3. If validation fails, the promote is rejected — nothing changes.
|
|
50
|
+
|
|
51
|
+
So `builder_versions` is, by construction, always free of validation errors. Consumers reading with `environment: production` never see broken data; consumers reading with `environment: development` may see in-flight errors and should expect to handle them.
|
|
52
|
+
|
|
53
|
+
Bulk writes that bypass the UI (like a seed script) land in `entities` only — they still need a UI promote afterwards to become visible to `production`-env consumers.
|
|
54
|
+
|
|
55
|
+
## API key scoping
|
|
56
|
+
|
|
57
|
+
Each API key is minted in `/settings` with three properties:
|
|
58
|
+
|
|
59
|
+
| Property | What it controls |
|
|
60
|
+
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
61
|
+
| **Environment** | Which dataset the key reads from: `development`, `staging`, or `production`. |
|
|
62
|
+
| **Profile** | What the key can do. `read` keys can fetch; `readWrite` keys can also write variants. |
|
|
63
|
+
| **Allowed origins** | Origin patterns (e.g. `https://*.example.com`) the key may be used from. Set this for browser keys; leave empty for server-to-server keys. The two modes are exclusive — a key with allowed origins requires an Origin header that matches, and a key without them rejects any request that has an Origin header. |
|
|
64
|
+
|
|
65
|
+
Keys are scoped to the org they were minted in and never see other orgs' data.
|
|
66
|
+
|
|
67
|
+
### Clerk permissions
|
|
68
|
+
|
|
69
|
+
API key management is gated by `org:api_keys:{create,read,update,delete}`. These are **custom organization permissions** — Clerk doesn't ship them. In the Clerk dashboard:
|
|
70
|
+
|
|
71
|
+
1. **Configure → Roles & Permissions → Permissions** — define `api_keys:create`, `api_keys:read`, `api_keys:update`, `api_keys:delete` (Clerk prefixes them with `org:` automatically).
|
|
72
|
+
2. **Edit the org Admin role** — check all four, then **save** (defining a permission and granting it to a role are separate steps).
|
|
73
|
+
|
|
74
|
+
Members need to sign out/in for new role permissions to land in their session token. Setup is per-Clerk-instance, so repeat for Clerk test and Clerk production.
|
|
35
75
|
|
|
36
|
-
|
|
76
|
+
## Scripts
|
|
37
77
|
|
|
38
|
-
|
|
78
|
+
- `npm run verify` — tests, check, format, lint.
|
|
79
|
+
- `npm run test:integration:local` — integration tests against local Supabase.
|
|
80
|
+
- `npm run db:types:local` — regenerate `database.types.ts` from local schema.
|
|
81
|
+
- `npm run db:reset` — wipe local Supabase, re-diff migrations, regenerate types. **Does not** touch Clerk.
|
package/dist/bb.js
CHANGED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { BuilderBuilderClientOptions, BuilderBuilderGetResponse } from './schema.js';
|
|
2
|
+
export type BuilderBuilderClient = {
|
|
3
|
+
builder: {
|
|
4
|
+
get(id: string): Promise<BuilderBuilderGetResponse>;
|
|
5
|
+
};
|
|
6
|
+
};
|
|
7
|
+
export declare function client(options: BuilderBuilderClientOptions): BuilderBuilderClient;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as v from 'valibot';
|
|
2
|
+
import { BuilderException } from '../errors/index.js';
|
|
3
|
+
import { BuilderBuilderClientOptionsSchema, BuilderBuilderGetResponseSchema } from './schema.js';
|
|
4
|
+
export function client(options) {
|
|
5
|
+
const { url, apiKey } = v.parse(BuilderBuilderClientOptionsSchema, options);
|
|
6
|
+
return {
|
|
7
|
+
builder: {
|
|
8
|
+
get: async (id) => {
|
|
9
|
+
const requestUrl = `${url}/api/builder/${id}`;
|
|
10
|
+
const response = await fetch(requestUrl, {
|
|
11
|
+
headers: { 'X-Builder-Builder-Key': apiKey }
|
|
12
|
+
});
|
|
13
|
+
if (!response.ok) {
|
|
14
|
+
throw new BuilderException({
|
|
15
|
+
category: 'request',
|
|
16
|
+
url: requestUrl,
|
|
17
|
+
status: response.status,
|
|
18
|
+
statusText: response.statusText,
|
|
19
|
+
body: await response.text()
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
return v.parse(BuilderBuilderGetResponseSchema, await response.json());
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export type { BuilderBuilderClientOptions, BuilderBuilderGetResponse } from './schema.js';
|
|
2
|
+
export type { BuilderBuilderClient } from './client.js';
|
|
3
|
+
export { BuilderBuilderClientOptionsSchema, BuilderBuilderGetResponseSchema } from './schema.js';
|
|
4
|
+
export { client } from './client.js';
|
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
import * as v from 'valibot';
|
|
2
|
+
export declare const BuilderBuilderClientOptionsSchema: v.ObjectSchema<{
|
|
3
|
+
readonly url: v.StringSchema<undefined>;
|
|
4
|
+
readonly apiKey: v.StringSchema<undefined>;
|
|
5
|
+
}, undefined>;
|
|
6
|
+
export type BuilderBuilderClientOptions = v.InferOutput<typeof BuilderBuilderClientOptionsSchema>;
|
|
7
|
+
export declare const BuilderBuilderGetResponseSchema: v.ObjectSchema<{
|
|
8
|
+
readonly name: v.StringSchema<undefined>;
|
|
9
|
+
readonly serialised: v.GenericSchema<import("../index.js").BuilderSerialised>;
|
|
10
|
+
readonly references: v.ArraySchema<v.ObjectSchema<{
|
|
11
|
+
readonly id: v.StringSchema<undefined>;
|
|
12
|
+
readonly serialised: v.GenericSchema<string | number | boolean | readonly (string | number)[] | readonly (readonly (string | number)[])[] | import("../index.js").BuilderSerialised | import("../index.js").BuilderModelSerialised | Readonly<{
|
|
13
|
+
type: "select";
|
|
14
|
+
readonly options: readonly [string, ...string[]];
|
|
15
|
+
defaultValue: string | null;
|
|
16
|
+
isOptional: boolean;
|
|
17
|
+
optionLabels: {
|
|
18
|
+
[x: string]: string;
|
|
19
|
+
};
|
|
20
|
+
tags?: readonly string[] | undefined;
|
|
21
|
+
}> | Readonly<{
|
|
22
|
+
type: "toggle";
|
|
23
|
+
valueType: "string" | "number" | "boolean";
|
|
24
|
+
defaultValue: string | number | boolean | null;
|
|
25
|
+
isOptional: boolean;
|
|
26
|
+
tags?: readonly string[] | undefined;
|
|
27
|
+
}> | import("../index.js").BuilderMatchSelectMap<Readonly<{
|
|
28
|
+
type: "parameter";
|
|
29
|
+
id: string;
|
|
30
|
+
name: string;
|
|
31
|
+
}> | Readonly<Readonly<{
|
|
32
|
+
type: "select";
|
|
33
|
+
readonly options: readonly [string, ...string[]];
|
|
34
|
+
defaultValue: string | null;
|
|
35
|
+
isOptional: boolean;
|
|
36
|
+
optionLabels: {
|
|
37
|
+
[x: string]: string;
|
|
38
|
+
};
|
|
39
|
+
tags?: readonly string[] | undefined;
|
|
40
|
+
}>> | Readonly<Readonly<{
|
|
41
|
+
type: "toggle";
|
|
42
|
+
valueType: "string" | "number" | "boolean";
|
|
43
|
+
defaultValue: string | number | boolean | null;
|
|
44
|
+
isOptional: boolean;
|
|
45
|
+
tags?: readonly string[] | undefined;
|
|
46
|
+
}>>> | import("../index.js").BuilderWhenSerialised<Readonly<Readonly<{
|
|
47
|
+
type: "select";
|
|
48
|
+
readonly options: readonly [string, ...string[]];
|
|
49
|
+
defaultValue: string | null;
|
|
50
|
+
isOptional: boolean;
|
|
51
|
+
optionLabels: {
|
|
52
|
+
[x: string]: string;
|
|
53
|
+
};
|
|
54
|
+
tags?: readonly string[] | undefined;
|
|
55
|
+
}>> | Readonly<Readonly<{
|
|
56
|
+
type: "toggle";
|
|
57
|
+
valueType: "string" | "number" | "boolean";
|
|
58
|
+
defaultValue: string | number | boolean | null;
|
|
59
|
+
isOptional: boolean;
|
|
60
|
+
tags?: readonly string[] | undefined;
|
|
61
|
+
}>>> | Readonly<{
|
|
62
|
+
fields: Readonly<{
|
|
63
|
+
type: "parameter";
|
|
64
|
+
id: string;
|
|
65
|
+
name: string;
|
|
66
|
+
}> | Readonly<{
|
|
67
|
+
type: "ref";
|
|
68
|
+
id: string;
|
|
69
|
+
}> | readonly Readonly<{
|
|
70
|
+
type: "component-field";
|
|
71
|
+
name: string;
|
|
72
|
+
valueType: "string" | "number" | "boolean";
|
|
73
|
+
isOptional: boolean;
|
|
74
|
+
tags?: readonly string[] | undefined;
|
|
75
|
+
}>[];
|
|
76
|
+
tags?: readonly string[] | undefined;
|
|
77
|
+
}> | import("../index.js").BuilderMatchSelectMap<Readonly<{
|
|
78
|
+
type: "parameter";
|
|
79
|
+
id: string;
|
|
80
|
+
name: string;
|
|
81
|
+
}> | Readonly<{
|
|
82
|
+
fields: Readonly<{
|
|
83
|
+
type: "parameter";
|
|
84
|
+
id: string;
|
|
85
|
+
name: string;
|
|
86
|
+
}> | Readonly<{
|
|
87
|
+
type: "ref";
|
|
88
|
+
id: string;
|
|
89
|
+
}> | readonly Readonly<{
|
|
90
|
+
type: "component-field";
|
|
91
|
+
name: string;
|
|
92
|
+
valueType: "string" | "number" | "boolean";
|
|
93
|
+
isOptional: boolean;
|
|
94
|
+
tags?: readonly string[] | undefined;
|
|
95
|
+
}>[];
|
|
96
|
+
tags?: readonly string[] | undefined;
|
|
97
|
+
}>> | import("../index.js").BuilderWhenSerialised<Readonly<{
|
|
98
|
+
fields: Readonly<{
|
|
99
|
+
type: "parameter";
|
|
100
|
+
id: string;
|
|
101
|
+
name: string;
|
|
102
|
+
}> | Readonly<{
|
|
103
|
+
type: "ref";
|
|
104
|
+
id: string;
|
|
105
|
+
}> | readonly Readonly<{
|
|
106
|
+
type: "component-field";
|
|
107
|
+
name: string;
|
|
108
|
+
valueType: "string" | "number" | "boolean";
|
|
109
|
+
isOptional: boolean;
|
|
110
|
+
tags?: readonly string[] | undefined;
|
|
111
|
+
}>[];
|
|
112
|
+
tags?: readonly string[] | undefined;
|
|
113
|
+
}>> | Readonly<{
|
|
114
|
+
model: Readonly<{
|
|
115
|
+
type: "parameter";
|
|
116
|
+
id: string;
|
|
117
|
+
name: string;
|
|
118
|
+
}> | Readonly<{
|
|
119
|
+
type: "ref";
|
|
120
|
+
id: string;
|
|
121
|
+
}> | import("../index.js").BuilderModelSerialised;
|
|
122
|
+
min: number | Readonly<{
|
|
123
|
+
type: "parameter";
|
|
124
|
+
id: string;
|
|
125
|
+
name: string;
|
|
126
|
+
}> | Readonly<{
|
|
127
|
+
type: "ref";
|
|
128
|
+
id: string;
|
|
129
|
+
}>;
|
|
130
|
+
max: number | Readonly<{
|
|
131
|
+
type: "parameter";
|
|
132
|
+
id: string;
|
|
133
|
+
name: string;
|
|
134
|
+
}> | Readonly<{
|
|
135
|
+
type: "ref";
|
|
136
|
+
id: string;
|
|
137
|
+
}>;
|
|
138
|
+
tags?: readonly string[] | undefined;
|
|
139
|
+
}> | import("../index.js").BuilderMatchSelectMap<Readonly<{
|
|
140
|
+
type: "parameter";
|
|
141
|
+
id: string;
|
|
142
|
+
name: string;
|
|
143
|
+
}> | Readonly<{
|
|
144
|
+
model: Readonly<{
|
|
145
|
+
type: "parameter";
|
|
146
|
+
id: string;
|
|
147
|
+
name: string;
|
|
148
|
+
}> | Readonly<{
|
|
149
|
+
type: "ref";
|
|
150
|
+
id: string;
|
|
151
|
+
}> | import("../index.js").BuilderModelSerialised;
|
|
152
|
+
min: number | Readonly<{
|
|
153
|
+
type: "parameter";
|
|
154
|
+
id: string;
|
|
155
|
+
name: string;
|
|
156
|
+
}> | Readonly<{
|
|
157
|
+
type: "ref";
|
|
158
|
+
id: string;
|
|
159
|
+
}>;
|
|
160
|
+
max: number | Readonly<{
|
|
161
|
+
type: "parameter";
|
|
162
|
+
id: string;
|
|
163
|
+
name: string;
|
|
164
|
+
}> | Readonly<{
|
|
165
|
+
type: "ref";
|
|
166
|
+
id: string;
|
|
167
|
+
}>;
|
|
168
|
+
tags?: readonly string[] | undefined;
|
|
169
|
+
}>> | import("../index.js").BuilderWhenSerialised<Readonly<{
|
|
170
|
+
model: Readonly<{
|
|
171
|
+
type: "parameter";
|
|
172
|
+
id: string;
|
|
173
|
+
name: string;
|
|
174
|
+
}> | Readonly<{
|
|
175
|
+
type: "ref";
|
|
176
|
+
id: string;
|
|
177
|
+
}> | import("../index.js").BuilderModelSerialised;
|
|
178
|
+
min: number | Readonly<{
|
|
179
|
+
type: "parameter";
|
|
180
|
+
id: string;
|
|
181
|
+
name: string;
|
|
182
|
+
}> | Readonly<{
|
|
183
|
+
type: "ref";
|
|
184
|
+
id: string;
|
|
185
|
+
}>;
|
|
186
|
+
max: number | Readonly<{
|
|
187
|
+
type: "parameter";
|
|
188
|
+
id: string;
|
|
189
|
+
name: string;
|
|
190
|
+
}> | Readonly<{
|
|
191
|
+
type: "ref";
|
|
192
|
+
id: string;
|
|
193
|
+
}>;
|
|
194
|
+
tags?: readonly string[] | undefined;
|
|
195
|
+
}>> | import("../index.js").BuilderUISerialised | Readonly<{
|
|
196
|
+
type: "input";
|
|
197
|
+
path: readonly (string | number)[] | Readonly<{
|
|
198
|
+
type: "parameter";
|
|
199
|
+
id: string;
|
|
200
|
+
name: string;
|
|
201
|
+
}> | Readonly<{
|
|
202
|
+
type: "ref";
|
|
203
|
+
id: string;
|
|
204
|
+
}>;
|
|
205
|
+
displayName?: string | Readonly<{
|
|
206
|
+
type: "parameter";
|
|
207
|
+
id: string;
|
|
208
|
+
name: string;
|
|
209
|
+
}> | Readonly<{
|
|
210
|
+
type: "ref";
|
|
211
|
+
id: string;
|
|
212
|
+
}> | undefined;
|
|
213
|
+
kind?: string | Readonly<{
|
|
214
|
+
type: "parameter";
|
|
215
|
+
id: string;
|
|
216
|
+
name: string;
|
|
217
|
+
}> | Readonly<{
|
|
218
|
+
type: "ref";
|
|
219
|
+
id: string;
|
|
220
|
+
}> | undefined;
|
|
221
|
+
metadata?: Readonly<{
|
|
222
|
+
type: "parameter";
|
|
223
|
+
id: string;
|
|
224
|
+
name: string;
|
|
225
|
+
}> | Readonly<{
|
|
226
|
+
type: "ref";
|
|
227
|
+
id: string;
|
|
228
|
+
}> | Readonly<{
|
|
229
|
+
[x: string]: unknown;
|
|
230
|
+
}> | undefined;
|
|
231
|
+
tags?: readonly string[] | undefined;
|
|
232
|
+
}> | Readonly<{
|
|
233
|
+
type: "page";
|
|
234
|
+
label: string | Readonly<{
|
|
235
|
+
type: "parameter";
|
|
236
|
+
id: string;
|
|
237
|
+
name: string;
|
|
238
|
+
}> | Readonly<{
|
|
239
|
+
type: "ref";
|
|
240
|
+
id: string;
|
|
241
|
+
}>;
|
|
242
|
+
inputs: Readonly<{
|
|
243
|
+
type: "parameter";
|
|
244
|
+
id: string;
|
|
245
|
+
name: string;
|
|
246
|
+
}> | Readonly<{
|
|
247
|
+
type: "ref";
|
|
248
|
+
id: string;
|
|
249
|
+
}> | readonly (Readonly<{
|
|
250
|
+
type: "parameter";
|
|
251
|
+
id: string;
|
|
252
|
+
name: string;
|
|
253
|
+
}> | Readonly<{
|
|
254
|
+
type: "ref";
|
|
255
|
+
id: string;
|
|
256
|
+
}> | Readonly<{
|
|
257
|
+
type: "input";
|
|
258
|
+
path: readonly (string | number)[] | Readonly<{
|
|
259
|
+
type: "parameter";
|
|
260
|
+
id: string;
|
|
261
|
+
name: string;
|
|
262
|
+
}> | Readonly<{
|
|
263
|
+
type: "ref";
|
|
264
|
+
id: string;
|
|
265
|
+
}>;
|
|
266
|
+
displayName?: string | Readonly<{
|
|
267
|
+
type: "parameter";
|
|
268
|
+
id: string;
|
|
269
|
+
name: string;
|
|
270
|
+
}> | Readonly<{
|
|
271
|
+
type: "ref";
|
|
272
|
+
id: string;
|
|
273
|
+
}> | undefined;
|
|
274
|
+
kind?: string | Readonly<{
|
|
275
|
+
type: "parameter";
|
|
276
|
+
id: string;
|
|
277
|
+
name: string;
|
|
278
|
+
}> | Readonly<{
|
|
279
|
+
type: "ref";
|
|
280
|
+
id: string;
|
|
281
|
+
}> | undefined;
|
|
282
|
+
metadata?: Readonly<{
|
|
283
|
+
type: "parameter";
|
|
284
|
+
id: string;
|
|
285
|
+
name: string;
|
|
286
|
+
}> | Readonly<{
|
|
287
|
+
type: "ref";
|
|
288
|
+
id: string;
|
|
289
|
+
}> | Readonly<{
|
|
290
|
+
[x: string]: unknown;
|
|
291
|
+
}> | undefined;
|
|
292
|
+
tags?: readonly string[] | undefined;
|
|
293
|
+
}>)[];
|
|
294
|
+
tags?: readonly string[] | undefined;
|
|
295
|
+
}> | Readonly<{
|
|
296
|
+
type: "describe";
|
|
297
|
+
label: string | Readonly<{
|
|
298
|
+
type: "parameter";
|
|
299
|
+
id: string;
|
|
300
|
+
name: string;
|
|
301
|
+
}> | Readonly<{
|
|
302
|
+
type: "ref";
|
|
303
|
+
id: string;
|
|
304
|
+
}>;
|
|
305
|
+
inputs: Readonly<{
|
|
306
|
+
type: "parameter";
|
|
307
|
+
id: string;
|
|
308
|
+
name: string;
|
|
309
|
+
}> | Readonly<{
|
|
310
|
+
type: "ref";
|
|
311
|
+
id: string;
|
|
312
|
+
}> | readonly (Readonly<{
|
|
313
|
+
type: "parameter";
|
|
314
|
+
id: string;
|
|
315
|
+
name: string;
|
|
316
|
+
}> | Readonly<{
|
|
317
|
+
type: "ref";
|
|
318
|
+
id: string;
|
|
319
|
+
}> | Readonly<{
|
|
320
|
+
type: "input";
|
|
321
|
+
path: readonly (string | number)[] | Readonly<{
|
|
322
|
+
type: "parameter";
|
|
323
|
+
id: string;
|
|
324
|
+
name: string;
|
|
325
|
+
}> | Readonly<{
|
|
326
|
+
type: "ref";
|
|
327
|
+
id: string;
|
|
328
|
+
}>;
|
|
329
|
+
displayName?: string | Readonly<{
|
|
330
|
+
type: "parameter";
|
|
331
|
+
id: string;
|
|
332
|
+
name: string;
|
|
333
|
+
}> | Readonly<{
|
|
334
|
+
type: "ref";
|
|
335
|
+
id: string;
|
|
336
|
+
}> | undefined;
|
|
337
|
+
kind?: string | Readonly<{
|
|
338
|
+
type: "parameter";
|
|
339
|
+
id: string;
|
|
340
|
+
name: string;
|
|
341
|
+
}> | Readonly<{
|
|
342
|
+
type: "ref";
|
|
343
|
+
id: string;
|
|
344
|
+
}> | undefined;
|
|
345
|
+
metadata?: Readonly<{
|
|
346
|
+
type: "parameter";
|
|
347
|
+
id: string;
|
|
348
|
+
name: string;
|
|
349
|
+
}> | Readonly<{
|
|
350
|
+
type: "ref";
|
|
351
|
+
id: string;
|
|
352
|
+
}> | Readonly<{
|
|
353
|
+
[x: string]: unknown;
|
|
354
|
+
}> | undefined;
|
|
355
|
+
tags?: readonly string[] | undefined;
|
|
356
|
+
}>)[];
|
|
357
|
+
tags?: readonly string[] | undefined;
|
|
358
|
+
}> | import("../index.js").BuilderUIPagesSerialised | readonly (Readonly<{
|
|
359
|
+
type: "parameter";
|
|
360
|
+
id: string;
|
|
361
|
+
name: string;
|
|
362
|
+
}> | Readonly<{
|
|
363
|
+
type: "ref";
|
|
364
|
+
id: string;
|
|
365
|
+
}> | import("../entities/index.js").BuilderUIItemSerialised)[] | import("../index.js").BuilderPricingSerialised | import("../entities/index.js").BuilderRates | readonly Readonly<{
|
|
366
|
+
name: string;
|
|
367
|
+
kind: "option" | "component" | "collection";
|
|
368
|
+
}>[]>;
|
|
369
|
+
}, undefined>, undefined>;
|
|
370
|
+
readonly variants: v.SchemaWithPipe<readonly [v.RecordSchema<v.StringSchema<undefined>, v.SchemaWithPipe<readonly [v.ArraySchema<v.SchemaWithPipe<readonly [v.ObjectSchema<{
|
|
371
|
+
readonly instance: v.GenericSchema<import("../instance.js").BuilderInstance>;
|
|
372
|
+
readonly details: v.OptionalSchema<v.RecordSchema<v.StringSchema<undefined>, v.NullableSchema<v.UnionSchema<[v.StringSchema<undefined>, v.BooleanSchema<undefined>, v.NumberSchema<undefined>], undefined>, undefined>, undefined>, undefined>;
|
|
373
|
+
readonly tags: v.OptionalSchema<v.SchemaWithPipe<readonly [v.ArraySchema<v.StringSchema<undefined>, undefined>, v.ReadonlyAction<string[]>]>, undefined>;
|
|
374
|
+
}, undefined>, v.ReadonlyAction<{
|
|
375
|
+
instance: import("../instance.js").BuilderInstance;
|
|
376
|
+
details?: {
|
|
377
|
+
[x: string]: string | number | boolean | null;
|
|
378
|
+
} | undefined;
|
|
379
|
+
tags?: readonly string[] | undefined;
|
|
380
|
+
}>]>, undefined>, v.ReadonlyAction<Readonly<{
|
|
381
|
+
instance: import("../instance.js").BuilderInstance;
|
|
382
|
+
details?: {
|
|
383
|
+
[x: string]: string | number | boolean | null;
|
|
384
|
+
} | undefined;
|
|
385
|
+
tags?: readonly string[] | undefined;
|
|
386
|
+
}>[]>]>, undefined>, v.ReadonlyAction<{
|
|
387
|
+
readonly [x: string]: readonly Readonly<{
|
|
388
|
+
instance: import("../instance.js").BuilderInstance;
|
|
389
|
+
details?: {
|
|
390
|
+
[x: string]: string | number | boolean | null;
|
|
391
|
+
} | undefined;
|
|
392
|
+
tags?: readonly string[] | undefined;
|
|
393
|
+
}>[];
|
|
394
|
+
}>]>;
|
|
395
|
+
}, undefined>;
|
|
396
|
+
export type BuilderBuilderGetResponse = v.InferOutput<typeof BuilderBuilderGetResponseSchema>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as v from 'valibot';
|
|
2
|
+
import { BuilderReferencesSchema, BuilderSerialisedSchema } from '../entities/index.js';
|
|
3
|
+
import { BuilderVariantsSchema } from '../instance.js';
|
|
4
|
+
export const BuilderBuilderClientOptionsSchema = v.object({
|
|
5
|
+
url: v.string(),
|
|
6
|
+
apiKey: v.string()
|
|
7
|
+
});
|
|
8
|
+
export const BuilderBuilderGetResponseSchema = v.object({
|
|
9
|
+
name: v.string(),
|
|
10
|
+
serialised: BuilderSerialisedSchema,
|
|
11
|
+
references: BuilderReferencesSchema,
|
|
12
|
+
variants: BuilderVariantsSchema
|
|
13
|
+
});
|
package/dist/errors/check.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as v from 'valibot';
|
|
2
2
|
declare class Check {
|
|
3
|
-
truthy<Input>(input: Input, message
|
|
4
|
-
falsy(input: unknown, message
|
|
3
|
+
truthy<Input>(input: Input, message: `${string}! ❌`): asserts input is Exclude<Input, null | undefined | '' | 0 | false>;
|
|
4
|
+
falsy(input: unknown, message: `${string}! ❌`): void;
|
|
5
5
|
is<const Schema extends v.BaseSchema<unknown, unknown, v.BaseIssue<unknown>>>(schema: Schema, input: unknown): input is v.InferOutput<Schema>;
|
|
6
6
|
assert<const Schema extends v.BaseSchema<unknown, unknown, v.BaseIssue<unknown>>>(schema: Schema, input: unknown, message?: `${string}! ❌`): asserts input is v.InferOutput<Schema>;
|
|
7
7
|
}
|
package/dist/errors/check.js
CHANGED
|
@@ -1,24 +1,29 @@
|
|
|
1
1
|
import * as v from 'valibot';
|
|
2
|
-
import { BuilderException } from './exception.js';
|
|
3
|
-
const DEFAULT_MESSAGE = 'Unexpected value! ❌';
|
|
2
|
+
import { BuilderException, formatSchemaIssues } from './exception.js';
|
|
4
3
|
class Check {
|
|
5
|
-
truthy(input, message
|
|
4
|
+
truthy(input, message) {
|
|
6
5
|
if (!input) {
|
|
7
|
-
throw new BuilderException(
|
|
6
|
+
throw new BuilderException({ category: 'assertion', summary: message });
|
|
8
7
|
}
|
|
9
8
|
}
|
|
10
|
-
falsy(input, message
|
|
9
|
+
falsy(input, message) {
|
|
11
10
|
if (input) {
|
|
12
|
-
throw new BuilderException(
|
|
11
|
+
throw new BuilderException({ category: 'assertion', summary: message });
|
|
13
12
|
}
|
|
14
13
|
}
|
|
15
14
|
is(schema, input) {
|
|
16
15
|
return v.is(schema, input);
|
|
17
16
|
}
|
|
18
|
-
assert(schema, input, message
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
assert(schema, input, message) {
|
|
18
|
+
const result = v.safeParse(schema, input);
|
|
19
|
+
if (result.success) {
|
|
20
|
+
return;
|
|
21
21
|
}
|
|
22
|
+
throw new BuilderException({
|
|
23
|
+
category: 'assertion',
|
|
24
|
+
summary: message ?? formatSchemaIssues(result.issues),
|
|
25
|
+
issues: result.issues
|
|
26
|
+
});
|
|
22
27
|
}
|
|
23
28
|
}
|
|
24
29
|
export const check = new Check();
|
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
import type { BuilderErrors } from './errors';
|
|
2
|
-
|
|
2
|
+
import * as v from 'valibot';
|
|
3
|
+
export type BuilderExceptionPayload = {
|
|
4
|
+
readonly category: 'validation';
|
|
3
5
|
readonly errors: BuilderErrors;
|
|
4
|
-
|
|
6
|
+
} | {
|
|
7
|
+
readonly category: 'assertion';
|
|
8
|
+
readonly summary: string;
|
|
9
|
+
readonly issues?: ReadonlyArray<v.BaseIssue<unknown>>;
|
|
10
|
+
} | {
|
|
11
|
+
readonly category: 'request';
|
|
12
|
+
readonly url: string;
|
|
13
|
+
readonly status: number;
|
|
14
|
+
readonly statusText: string;
|
|
15
|
+
readonly body: string;
|
|
16
|
+
};
|
|
17
|
+
export declare class BuilderException extends globalThis.Error {
|
|
18
|
+
readonly payload: BuilderExceptionPayload;
|
|
19
|
+
constructor(payload: BuilderExceptionPayload);
|
|
20
|
+
get errors(): BuilderErrors;
|
|
5
21
|
}
|
|
22
|
+
export declare function formatSchemaIssues(issues: ReadonlyArray<v.BaseIssue<unknown>>): `${string}! ❌`;
|
package/dist/errors/exception.js
CHANGED
|
@@ -1,7 +1,77 @@
|
|
|
1
|
+
import * as v from 'valibot';
|
|
1
2
|
export class BuilderException extends globalThis.Error {
|
|
2
|
-
|
|
3
|
-
constructor(
|
|
4
|
-
super(
|
|
5
|
-
this.
|
|
3
|
+
payload;
|
|
4
|
+
constructor(payload) {
|
|
5
|
+
super(formatMessage(payload));
|
|
6
|
+
this.payload = payload;
|
|
6
7
|
}
|
|
8
|
+
get errors() {
|
|
9
|
+
return this.payload.category === 'validation' ? this.payload.errors : [];
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export function formatSchemaIssues(issues) {
|
|
13
|
+
const [first, ...rest] = issues;
|
|
14
|
+
if (!first) {
|
|
15
|
+
return 'Schema validation failed! ❌';
|
|
16
|
+
}
|
|
17
|
+
const path = formatIssuePath(first);
|
|
18
|
+
const location = path ? ` at ${path}` : '';
|
|
19
|
+
const more = rest.length > 0 ? ` (+${rest.length} more)` : '';
|
|
20
|
+
return `${first.message}${location}${more}! ❌`;
|
|
21
|
+
}
|
|
22
|
+
const HEADER = 'BuilderBuilder error 🏗️';
|
|
23
|
+
function formatMessage(payload) {
|
|
24
|
+
if (payload.category === 'validation') {
|
|
25
|
+
return formatValidation(payload.errors);
|
|
26
|
+
}
|
|
27
|
+
if (payload.category === 'assertion') {
|
|
28
|
+
return `${HEADER} — ${payload.summary}`;
|
|
29
|
+
}
|
|
30
|
+
return formatRequest(payload);
|
|
31
|
+
}
|
|
32
|
+
function formatValidation(errors) {
|
|
33
|
+
if (errors.length === 0) {
|
|
34
|
+
return `${HEADER} — validation failed`;
|
|
35
|
+
}
|
|
36
|
+
const noun = errors.length === 1 ? 'issue' : 'issues';
|
|
37
|
+
const lines = errors.map((error) => ` • ${formatBuilderError(error)}`);
|
|
38
|
+
return `${HEADER} — ${errors.length} validation ${noun}:\n${lines.join('\n')}`;
|
|
39
|
+
}
|
|
40
|
+
function formatBuilderError(error) {
|
|
41
|
+
const { kind, location, ...rest } = error;
|
|
42
|
+
const locationLabel = location.length > 0 ? ` at ${location.join('.')}` : '';
|
|
43
|
+
const detailEntries = Object.entries(rest);
|
|
44
|
+
if (detailEntries.length === 0) {
|
|
45
|
+
return `${kind}${locationLabel}`;
|
|
46
|
+
}
|
|
47
|
+
const details = detailEntries.map(([key, value]) => `${key}=${formatValue(value)}`).join(', ');
|
|
48
|
+
return `${kind}${locationLabel}: ${details}`;
|
|
49
|
+
}
|
|
50
|
+
function formatRequest(payload) {
|
|
51
|
+
const { url, status, statusText, body } = payload;
|
|
52
|
+
const statusLine = statusText ? `${status} ${statusText}` : `${status}`;
|
|
53
|
+
return `${HEADER} — Builder API request failed: ${statusLine}\n url: ${url}\n body: ${body}`;
|
|
54
|
+
}
|
|
55
|
+
function formatIssuePath(issue) {
|
|
56
|
+
if (!issue.path) {
|
|
57
|
+
return '';
|
|
58
|
+
}
|
|
59
|
+
return issue.path.map((segment) => segment.key).join('.');
|
|
60
|
+
}
|
|
61
|
+
function formatValue(value) {
|
|
62
|
+
if (typeof value === 'string') {
|
|
63
|
+
return JSON.stringify(value);
|
|
64
|
+
}
|
|
65
|
+
if (value === null || value === undefined) {
|
|
66
|
+
return String(value);
|
|
67
|
+
}
|
|
68
|
+
if (typeof value === 'object') {
|
|
69
|
+
try {
|
|
70
|
+
return JSON.stringify(value);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return String(value);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return String(value);
|
|
7
77
|
}
|
package/dist/errors/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export type { BuilderError, BuilderErrorKind, BuilderErrorLocation, BuilderErrors } from './errors';
|
|
2
|
+
export type { BuilderExceptionPayload } from './exception.js';
|
|
2
3
|
export { check } from './check.js';
|
|
3
4
|
export { BuilderValidateErrors } from './errors.js';
|
|
4
5
|
export { BuilderException } from './exception.js';
|
package/dist/validate/brand.js
CHANGED
|
@@ -8,6 +8,6 @@ export function assertValidated(value) {
|
|
|
8
8
|
if (typeof value !== 'object' || value === null || !VALIDATED_ENTITIES.has(value)) {
|
|
9
9
|
const errors = new BuilderValidateErrors();
|
|
10
10
|
errors.unvalidated(value);
|
|
11
|
-
throw new BuilderException(errors.errors);
|
|
11
|
+
throw new BuilderException({ category: 'validation', errors: errors.errors });
|
|
12
12
|
}
|
|
13
13
|
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@builder-builder/builder",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.23",
|
|
4
4
|
"type": "module",
|
|
5
|
+
"engines": {
|
|
6
|
+
"node": ">=24"
|
|
7
|
+
},
|
|
5
8
|
"exports": {
|
|
6
9
|
".": {
|
|
7
10
|
"types": "./dist/index.d.ts",
|
|
8
11
|
"default": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./client": {
|
|
14
|
+
"types": "./dist/client/index.d.ts",
|
|
15
|
+
"default": "./dist/client/index.js"
|
|
9
16
|
}
|
|
10
17
|
},
|
|
11
18
|
"files": [
|
|
@@ -18,9 +25,9 @@
|
|
|
18
25
|
},
|
|
19
26
|
"scripts": {
|
|
20
27
|
"dev": "vite dev",
|
|
21
|
-
"predev": "npm run types:
|
|
28
|
+
"predev": "npm run db:types:local",
|
|
22
29
|
"dev:v": "NO_UPDATE_NOTIFIER=false VERCEL_ENV=development vercel dev",
|
|
23
|
-
"build": "vite build",
|
|
30
|
+
"build": "NODE_ENV=production vite build",
|
|
24
31
|
"package": "svelte-kit sync && svelte-package --input src/lib/builder",
|
|
25
32
|
"preview": "vite preview",
|
|
26
33
|
"prepare": "svelte-kit sync && npm run messages || echo ''",
|
|
@@ -32,10 +39,10 @@
|
|
|
32
39
|
"lint": "prettier --check . && eslint .",
|
|
33
40
|
"test": "npm run test:unit -- --run",
|
|
34
41
|
"test:unit": "npm run messages && vitest --project client --project server",
|
|
35
|
-
"test:integration": "npm run messages && vitest run --project integration",
|
|
42
|
+
"test:integration:local": "npm run messages && dotenv -e .env.development.local -- sh -c 'npx tsx scripts/integration-clean.ts && vitest run --project integration'",
|
|
36
43
|
"verify": "npm run test && npm run check && npm run lint",
|
|
37
|
-
"db:types": "dotenv -e .env -- sh -c 'supabase gen types typescript --project-id \"$SUPABASE_DEV_PROJECT_ID\" --schema public > ./src/lib/db/database.types.ts'",
|
|
38
|
-
"db:types:local": "supabase gen types typescript --local > ./src/lib/db/database.types.ts",
|
|
44
|
+
"db:types": "dotenv -e .env -- sh -c 'supabase gen types typescript --project-id \"$SUPABASE_DEV_PROJECT_ID\" --schema public > ./src/lib/server/db/database.types.ts'",
|
|
45
|
+
"db:types:local": "supabase gen types typescript --local > ./src/lib/server/db/database.types.ts",
|
|
39
46
|
"db:reset": "rm -f supabase/migrations/*_initial.sql && supabase db diff --use-pg-delta -f initial && supabase db reset && npm run db:types:local"
|
|
40
47
|
},
|
|
41
48
|
"devDependencies": {
|