@rjromeoent/ein-supabase 0.1.0 → 0.1.2
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 +281 -8
- package/dist/db-enums.d.ts +10 -0
- package/dist/db-enums.js +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/json.d.ts +13 -0
- package/dist/json.js +40 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,6 +5,7 @@ Shared Supabase contract package for EIN-connected apps.
|
|
|
5
5
|
This package is the canonical home for:
|
|
6
6
|
|
|
7
7
|
- Generated Supabase database types across EIN schemas.
|
|
8
|
+
- Type aliases for canonical database-owned enums.
|
|
8
9
|
- Registered app slugs and the `x-ein-app` header helper.
|
|
9
10
|
- Shared org-role helpers for `core.app_organization_memberships`.
|
|
10
11
|
- Typed schema-client helpers.
|
|
@@ -79,13 +80,285 @@ Use semver intentionally:
|
|
|
79
80
|
- Minor: additive contracts or new schemas/functions.
|
|
80
81
|
- Major: renamed or removed contracts, breaking permission/app context changes.
|
|
81
82
|
|
|
82
|
-
##
|
|
83
|
+
## Package Boundary
|
|
83
84
|
|
|
84
|
-
|
|
85
|
-
|
|
85
|
+
This package is the shared contract layer between Event Intelligence Network and
|
|
86
|
+
independent app repos. It should stay small, stable, and backend-oriented.
|
|
86
87
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
88
|
+
Use this rule:
|
|
89
|
+
|
|
90
|
+
> Put a thing in this package only when more than one app should depend on the
|
|
91
|
+
> same backend contract or helper. Keep app workflow, UI, and feature-specific
|
|
92
|
+
> shapes inside the app until reuse is real.
|
|
93
|
+
|
|
94
|
+
### Belongs In This Package
|
|
95
|
+
|
|
96
|
+
#### Generated Database Types
|
|
97
|
+
|
|
98
|
+
Examples:
|
|
99
|
+
|
|
100
|
+
- `Database`
|
|
101
|
+
- schema table/view/function types generated from EIN Supabase
|
|
102
|
+
- schema-aware Supabase helpers like `schemaClient`
|
|
103
|
+
|
|
104
|
+
Why:
|
|
105
|
+
|
|
106
|
+
These represent the actual EIN database contract. Every connected app should
|
|
107
|
+
compile against the same source of truth instead of copying partial generated
|
|
108
|
+
types.
|
|
109
|
+
|
|
110
|
+
#### Database-Owned Enum Aliases
|
|
111
|
+
|
|
112
|
+
Examples:
|
|
113
|
+
|
|
114
|
+
- `CoreArtistGenre`
|
|
115
|
+
- `RadDbAvailabilityStatus`
|
|
116
|
+
- `RadDbAssignmentState`
|
|
117
|
+
- `RadDbDecisionType`
|
|
118
|
+
- `BookingOfferStatus`
|
|
119
|
+
|
|
120
|
+
Why:
|
|
121
|
+
|
|
122
|
+
These are type aliases over generated Supabase enum types. They give apps a
|
|
123
|
+
stable import name for canonical database-owned values without copying enum
|
|
124
|
+
strings into each app.
|
|
125
|
+
|
|
126
|
+
Use these aliases at API, adapter, and database-boundary layers when the value
|
|
127
|
+
is the raw backend contract:
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
import type { RadDbDecisionType } from "@rjromeoent/ein-supabase";
|
|
131
|
+
|
|
132
|
+
interface DecisionRow {
|
|
133
|
+
decision_type: RadDbDecisionType | null;
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Do not use these aliases for app-specific display buckets or UI commands. For
|
|
138
|
+
example, RAD's dashboard label `"Available"` and command action `"shortlist"`
|
|
139
|
+
are app vocabulary, while database values like `available` and `shortlisted`
|
|
140
|
+
are backend vocabulary.
|
|
141
|
+
|
|
142
|
+
#### App Registry And Request Context
|
|
143
|
+
|
|
144
|
+
Examples:
|
|
145
|
+
|
|
146
|
+
- `EIN_APP_SLUGS`
|
|
147
|
+
- `EIN_APP_HEADER`
|
|
148
|
+
- `einAppHeaders("rad")`
|
|
149
|
+
|
|
150
|
+
Why:
|
|
151
|
+
|
|
152
|
+
RLS policies and app-scoped RPCs depend on the same `x-ein-app` slug everywhere.
|
|
153
|
+
If each app hand-types the slug/header, policy mismatches are easy to create and
|
|
154
|
+
hard to debug.
|
|
155
|
+
|
|
156
|
+
#### Shared Auth And Organization Vocabulary
|
|
157
|
+
|
|
158
|
+
Examples:
|
|
159
|
+
|
|
160
|
+
- `CoreOrgRole`
|
|
161
|
+
- role helpers for `core.app_organization_memberships`
|
|
162
|
+
- helpers that answer "can this user access this app/org?"
|
|
163
|
+
|
|
164
|
+
Why:
|
|
165
|
+
|
|
166
|
+
All apps should interpret core org membership the same way. An app can map
|
|
167
|
+
canonical roles to local workflow roles, but the canonical role vocabulary
|
|
168
|
+
belongs here.
|
|
169
|
+
|
|
170
|
+
Example local mapping:
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
import type { CoreOrgRole } from "@rjromeoent/ein-supabase";
|
|
174
|
+
|
|
175
|
+
type BookingWorkflowRole = "admin" | "rep" | "viewer";
|
|
176
|
+
|
|
177
|
+
export function mapBookingRole(role: CoreOrgRole): BookingWorkflowRole {
|
|
178
|
+
if (role === "owner" || role === "admin") return "admin";
|
|
179
|
+
if (role === "member") return "rep";
|
|
180
|
+
return "viewer";
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
#### Generic JSON Guards
|
|
185
|
+
|
|
186
|
+
Examples:
|
|
187
|
+
|
|
188
|
+
- `isJsonRecord`
|
|
189
|
+
- `toJsonRecord`
|
|
190
|
+
- `stringOrNull`
|
|
191
|
+
- `numberOrNull`
|
|
192
|
+
- `booleanOrFalse`
|
|
193
|
+
- `stringArrayOrEmpty`
|
|
194
|
+
|
|
195
|
+
Why:
|
|
196
|
+
|
|
197
|
+
Every EIN-connected app reads JSON metadata, Edge Function payloads, and
|
|
198
|
+
Supabase JSON columns. These low-level primitives are generic, stable, and not
|
|
199
|
+
tied to any app workflow. Keeping them here avoids each app recreating slightly
|
|
200
|
+
different JSON parsing behavior.
|
|
201
|
+
|
|
202
|
+
These helpers should stay small and dependency-free. App-specific parsers should
|
|
203
|
+
compose them locally.
|
|
204
|
+
|
|
205
|
+
#### Cross-App Backend Contracts
|
|
206
|
+
|
|
207
|
+
Examples:
|
|
208
|
+
|
|
209
|
+
- one RPC request/response type consumed by Atlas and RAD
|
|
210
|
+
- one Edge Function request/response type consumed by Launchpad and Event Ops
|
|
211
|
+
- canonical event-bus envelope types used by multiple apps
|
|
212
|
+
|
|
213
|
+
Why:
|
|
214
|
+
|
|
215
|
+
When two or more apps call the same backend function, keeping the contract here
|
|
216
|
+
prevents drift. A backend breaking change should become one package version
|
|
217
|
+
change, not several unrelated app edits.
|
|
218
|
+
|
|
219
|
+
Promotion test:
|
|
220
|
+
|
|
221
|
+
- Is the backend endpoint used by more than one app today?
|
|
222
|
+
- Would a contract drift bug affect more than one app?
|
|
223
|
+
- Is the payload stable enough that changing it should require a package
|
|
224
|
+
version bump?
|
|
225
|
+
|
|
226
|
+
If the answer is yes, it belongs here.
|
|
227
|
+
|
|
228
|
+
#### Shared Status Vocabularies
|
|
229
|
+
|
|
230
|
+
Examples:
|
|
231
|
+
|
|
232
|
+
- canonical app slugs
|
|
233
|
+
- canonical org roles
|
|
234
|
+
- shared lifecycle statuses consumed across multiple apps
|
|
235
|
+
- shared event-bus event names
|
|
236
|
+
|
|
237
|
+
Why:
|
|
238
|
+
|
|
239
|
+
String vocabularies are high-risk drift points. If multiple apps compare the
|
|
240
|
+
same status string, the source of truth should be shared.
|
|
241
|
+
|
|
242
|
+
### Belongs In The App
|
|
243
|
+
|
|
244
|
+
#### Feature View Models
|
|
245
|
+
|
|
246
|
+
Examples:
|
|
247
|
+
|
|
248
|
+
- RAD dashboard row models
|
|
249
|
+
- Atlas artist search result cards
|
|
250
|
+
- Launchpad event hierarchy screen state
|
|
251
|
+
- Event Ops table/filter state
|
|
252
|
+
|
|
253
|
+
Why:
|
|
254
|
+
|
|
255
|
+
These are presentation shapes. They change as the UI changes and should not
|
|
256
|
+
force package releases.
|
|
257
|
+
|
|
258
|
+
#### App-Specific Edge Function Contracts
|
|
259
|
+
|
|
260
|
+
Examples:
|
|
261
|
+
|
|
262
|
+
- `rad-get-event-edition-grid`
|
|
263
|
+
- `rad-get-artist-availability-context`
|
|
264
|
+
- `rad-capture-search-snapshot`
|
|
265
|
+
- RAD saved event edition view payloads
|
|
266
|
+
|
|
267
|
+
Why:
|
|
268
|
+
|
|
269
|
+
These are RAD workflow contracts. They include UI restoration state, snapshots,
|
|
270
|
+
and RAD-specific orchestration. They should stay in RAD until another app
|
|
271
|
+
actually consumes the same payload.
|
|
272
|
+
|
|
273
|
+
If Event Ops or Atlas later needs the exact same contract, promote the stable
|
|
274
|
+
request/response shape into this package and update both apps to import it.
|
|
275
|
+
|
|
276
|
+
#### Adapters And Runtime Normalizers
|
|
277
|
+
|
|
278
|
+
Examples:
|
|
279
|
+
|
|
280
|
+
- `adaptEventEditionGrid`
|
|
281
|
+
- `adaptSearchResults`
|
|
282
|
+
- `adaptArtistAvailabilityContext`
|
|
283
|
+
- local parsing of nullable dates for UI display
|
|
284
|
+
- `parseSearchFilters`
|
|
285
|
+
- `isSearchResultSet`
|
|
286
|
+
|
|
287
|
+
Why:
|
|
288
|
+
|
|
289
|
+
Adapters convert backend transport into app/domain/view models. That conversion
|
|
290
|
+
usually reflects app-specific UI decisions. Share only small generic helpers
|
|
291
|
+
when multiple apps need identical parsing behavior.
|
|
292
|
+
|
|
293
|
+
#### App-Specific Permission Mapping
|
|
294
|
+
|
|
295
|
+
Examples:
|
|
296
|
+
|
|
297
|
+
- Booking Engine maps `owner/admin` to local `admin`
|
|
298
|
+
- Booking Engine maps `member` to local `rep`
|
|
299
|
+
- RAD maps org roles to internal/client mode affordances
|
|
300
|
+
|
|
301
|
+
Why:
|
|
302
|
+
|
|
303
|
+
Core org roles are shared. Workflow permissions are app-specific. Keep the
|
|
304
|
+
canonical role type here, but keep local permission policy near the feature that
|
|
305
|
+
uses it unless the same policy is shared.
|
|
306
|
+
|
|
307
|
+
#### Persisted UI State And Snapshots
|
|
308
|
+
|
|
309
|
+
Examples:
|
|
310
|
+
|
|
311
|
+
- RAD saved search filter payloads
|
|
312
|
+
- RAD search snapshot result blobs
|
|
313
|
+
- RAD saved event edition grid snapshots
|
|
314
|
+
- dashboard state tokens
|
|
315
|
+
|
|
316
|
+
Why:
|
|
317
|
+
|
|
318
|
+
These are intentionally flexible JSON blobs for restoring app state. They are
|
|
319
|
+
not stable backend schema contracts and should not be versioned through the
|
|
320
|
+
shared package unless multiple apps must read/write the same blob format.
|
|
321
|
+
|
|
322
|
+
### Decision Checklist
|
|
323
|
+
|
|
324
|
+
Before adding a type/helper to this package, answer:
|
|
325
|
+
|
|
326
|
+
1. Is it sourced from EIN backend schema, auth, RLS, app registry, or a shared
|
|
327
|
+
backend endpoint?
|
|
328
|
+
2. Is it used by at least two apps, or is it foundational enough that every app
|
|
329
|
+
should use the same implementation?
|
|
330
|
+
3. Would duplicating it in apps create real drift or security risk?
|
|
331
|
+
4. Is it stable enough to version with semver?
|
|
332
|
+
5. Does it avoid app UI state, feature-specific view models, and one-off screen
|
|
333
|
+
behavior?
|
|
334
|
+
|
|
335
|
+
Add it here only when the answer is yes to the relevant shared-contract
|
|
336
|
+
questions. Otherwise, keep it local in the app and revisit when reuse appears.
|
|
337
|
+
|
|
338
|
+
### Examples
|
|
339
|
+
|
|
340
|
+
| Item | Location | Reason |
|
|
341
|
+
| --- | --- | --- |
|
|
342
|
+
| `Database` generated Supabase type | Package | Every app should use the same DB schema contract. |
|
|
343
|
+
| `EIN_APP_SLUGS.rad` | Package | RLS/app policies require consistent slugs. |
|
|
344
|
+
| `CoreOrgRole` | Package | Org role meaning must be consistent across apps. |
|
|
345
|
+
| `schemaClient(supabase, "rad")` | Package | Shared typed helper for schema-scoped Supabase access. |
|
|
346
|
+
| `rad-get-event-edition-grid` response | RAD app | RAD-only workflow and UI grid payload today. |
|
|
347
|
+
| RAD saved search filters | RAD app | Persisted UI state blob, not a shared backend contract. |
|
|
348
|
+
| Booking Engine role-to-workflow mapping | Booking app | Local workflow interpretation of shared core role. |
|
|
349
|
+
| Event bus envelope used by all apps | Package | Cross-app backend integration contract. |
|
|
350
|
+
| ARTEMIS recommendation card view model | RAD app | Presentation model, not shared transport. |
|
|
351
|
+
|
|
352
|
+
### Promotion Process
|
|
353
|
+
|
|
354
|
+
When a local app contract becomes shared:
|
|
355
|
+
|
|
356
|
+
1. Move the stable request/response or vocabulary into this package.
|
|
357
|
+
2. Export it from `src/index.ts`.
|
|
358
|
+
3. Add README usage notes if the boundary is non-obvious.
|
|
359
|
+
4. Bump package version:
|
|
360
|
+
- patch for regenerated/additive types
|
|
361
|
+
- minor for new shared contracts
|
|
362
|
+
- major for renamed/removed/breaking contracts
|
|
363
|
+
5. Update all consuming apps to import from the package.
|
|
364
|
+
6. Remove local duplicate types from app repos.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Database } from "./generated/database.types.js";
|
|
2
|
+
export type CoreArtistGenre = Database["core"]["Enums"]["artist_genre"];
|
|
3
|
+
export type RadDbAssignmentState = Database["rad"]["Enums"]["assignment_state"];
|
|
4
|
+
export type RadDbAssignmentStatus = Database["rad"]["Enums"]["assignment_status"];
|
|
5
|
+
export type RadDbAvailabilityStatus = Database["rad"]["Enums"]["availability_status"];
|
|
6
|
+
export type RadDbDecisionActorType = Database["rad"]["Enums"]["decision_actor_type"];
|
|
7
|
+
export type RadDbDecisionType = Database["rad"]["Enums"]["decision_type"];
|
|
8
|
+
export type RadDbOfferLinkStatus = Database["rad"]["Enums"]["offer_link_status"];
|
|
9
|
+
export type BookingHoldLevel = Database["booking_engine"]["Enums"]["hold_level"];
|
|
10
|
+
export type BookingOfferStatus = Database["booking_engine"]["Enums"]["offer_status"];
|
package/dist/db-enums.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export * from "./app-context.js";
|
|
2
2
|
export * from "./auth.js";
|
|
3
|
+
export type * from "./db-enums.js";
|
|
4
|
+
export * from "./json.js";
|
|
3
5
|
export * from "./schema-client.js";
|
|
4
6
|
export type { Constants, Database, Enums, Json, Tables, TablesInsert, TablesUpdate, } from "./generated/database.types.js";
|
package/dist/index.js
CHANGED
package/dist/json.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type JsonRecord = Record<string, unknown>;
|
|
2
|
+
export declare function isJsonRecord(value: unknown): value is JsonRecord;
|
|
3
|
+
export declare function toJsonRecord(value: unknown): JsonRecord;
|
|
4
|
+
export declare function stringValue(value: unknown): string | undefined;
|
|
5
|
+
export declare function stringOrNull(value: unknown): string | null;
|
|
6
|
+
export declare function nullableStringValue(value: unknown): string | null | undefined;
|
|
7
|
+
export declare function numberValue(value: unknown): number | undefined;
|
|
8
|
+
export declare function numberOrNull(value: unknown): number | null;
|
|
9
|
+
export declare function booleanValue(value: unknown): boolean | undefined;
|
|
10
|
+
export declare function booleanOrNull(value: unknown): boolean | null;
|
|
11
|
+
export declare function booleanOrFalse(value: unknown): boolean;
|
|
12
|
+
export declare function stringArrayValue(value: unknown): string[] | undefined;
|
|
13
|
+
export declare function stringArrayOrEmpty(value: unknown): string[];
|
package/dist/json.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export function isJsonRecord(value) {
|
|
2
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3
|
+
}
|
|
4
|
+
export function toJsonRecord(value) {
|
|
5
|
+
return isJsonRecord(value) ? value : {};
|
|
6
|
+
}
|
|
7
|
+
export function stringValue(value) {
|
|
8
|
+
return typeof value === "string" ? value : undefined;
|
|
9
|
+
}
|
|
10
|
+
export function stringOrNull(value) {
|
|
11
|
+
return stringValue(value) ?? null;
|
|
12
|
+
}
|
|
13
|
+
export function nullableStringValue(value) {
|
|
14
|
+
return value === null ? null : stringValue(value);
|
|
15
|
+
}
|
|
16
|
+
export function numberValue(value) {
|
|
17
|
+
return typeof value === "number" && Number.isFinite(value)
|
|
18
|
+
? value
|
|
19
|
+
: undefined;
|
|
20
|
+
}
|
|
21
|
+
export function numberOrNull(value) {
|
|
22
|
+
return numberValue(value) ?? null;
|
|
23
|
+
}
|
|
24
|
+
export function booleanValue(value) {
|
|
25
|
+
return typeof value === "boolean" ? value : undefined;
|
|
26
|
+
}
|
|
27
|
+
export function booleanOrNull(value) {
|
|
28
|
+
return booleanValue(value) ?? null;
|
|
29
|
+
}
|
|
30
|
+
export function booleanOrFalse(value) {
|
|
31
|
+
return value === true;
|
|
32
|
+
}
|
|
33
|
+
export function stringArrayValue(value) {
|
|
34
|
+
return Array.isArray(value)
|
|
35
|
+
? value.filter((item) => typeof item === "string")
|
|
36
|
+
: undefined;
|
|
37
|
+
}
|
|
38
|
+
export function stringArrayOrEmpty(value) {
|
|
39
|
+
return stringArrayValue(value) ?? [];
|
|
40
|
+
}
|