@guard-rail/core 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/README.md +339 -0
- package/dist/adapters/supabase/auth.d.ts +9 -0
- package/dist/adapters/supabase/auth.js +57 -0
- package/dist/adapters/supabase/auth.js.map +1 -0
- package/dist/adapters/supabase/client.d.ts +28 -0
- package/dist/adapters/supabase/client.js +44 -0
- package/dist/adapters/supabase/client.js.map +1 -0
- package/dist/adapters/supabase/confirm.d.ts +19 -0
- package/dist/adapters/supabase/confirm.js +27 -0
- package/dist/adapters/supabase/confirm.js.map +1 -0
- package/dist/adapters/supabase/db.d.ts +9 -0
- package/dist/adapters/supabase/db.js +49 -0
- package/dist/adapters/supabase/db.js.map +1 -0
- package/dist/adapters/supabase/index.d.ts +25 -0
- package/dist/adapters/supabase/index.js +29 -0
- package/dist/adapters/supabase/index.js.map +1 -0
- package/dist/adapters/supabase/members.d.ts +9 -0
- package/dist/adapters/supabase/members.js +73 -0
- package/dist/adapters/supabase/members.js.map +1 -0
- package/dist/adapters/supabase/repeaters.d.ts +9 -0
- package/dist/adapters/supabase/repeaters.js +73 -0
- package/dist/adapters/supabase/repeaters.js.map +1 -0
- package/dist/adapters/supabase/storage.d.ts +18 -0
- package/dist/adapters/supabase/storage.js +93 -0
- package/dist/adapters/supabase/storage.js.map +1 -0
- package/dist/core/adapter-types.d.ts +212 -0
- package/dist/core/adapter-types.js +1 -0
- package/dist/core/adapter-types.js.map +1 -0
- package/dist/core/bailout.d.ts +9 -0
- package/dist/core/bailout.js +14 -0
- package/dist/core/bailout.js.map +1 -0
- package/dist/core/config.d.ts +27 -0
- package/dist/core/config.js +46 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/defs.d.ts +22 -0
- package/dist/core/defs.js +16 -0
- package/dist/core/defs.js.map +1 -0
- package/dist/core/types.d.ts +136 -0
- package/dist/core/types.js +1 -0
- package/dist/core/types.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +43 -0
- package/dist/index.js.map +1 -0
- package/dist/react/Admin.d.ts +8 -0
- package/dist/react/Admin.js +116 -0
- package/dist/react/Admin.js.map +1 -0
- package/dist/react/GRail/client.d.ts +11 -0
- package/dist/react/GRail/client.js +242 -0
- package/dist/react/GRail/client.js.map +1 -0
- package/dist/react/GRail/index.d.ts +12 -0
- package/dist/react/GRail/index.js +19 -0
- package/dist/react/GRail/index.js.map +1 -0
- package/dist/react/GRail/render.d.ts +23 -0
- package/dist/react/GRail/render.js +67 -0
- package/dist/react/GRail/render.js.map +1 -0
- package/dist/react/Icons.d.ts +13 -0
- package/dist/react/Icons.js +68 -0
- package/dist/react/Icons.js.map +1 -0
- package/dist/react/MediaPicker.d.ts +14 -0
- package/dist/react/MediaPicker.js +172 -0
- package/dist/react/MediaPicker.js.map +1 -0
- package/dist/react/Provider.d.ts +11 -0
- package/dist/react/Provider.js +26 -0
- package/dist/react/Provider.js.map +1 -0
- package/dist/react/Repeater/client.d.ts +16 -0
- package/dist/react/Repeater/client.js +148 -0
- package/dist/react/Repeater/client.js.map +1 -0
- package/dist/react/Repeater/index.d.ts +35 -0
- package/dist/react/Repeater/index.js +22 -0
- package/dist/react/Repeater/index.js.map +1 -0
- package/dist/react/Syncer.d.ts +17 -0
- package/dist/react/Syncer.js +23 -0
- package/dist/react/Syncer.js.map +1 -0
- package/dist/react/Team.d.ts +7 -0
- package/dist/react/Team.js +190 -0
- package/dist/react/Team.js.map +1 -0
- package/dist/react/resizeImage.d.ts +25 -0
- package/dist/react/resizeImage.js +52 -0
- package/dist/react/resizeImage.js.map +1 -0
- package/dist/react/styles.d.ts +421 -0
- package/dist/react/styles.js +402 -0
- package/dist/react/styles.js.map +1 -0
- package/dist/react/useAnchorRect.d.ts +17 -0
- package/dist/react/useAnchorRect.js +32 -0
- package/dist/react/useAnchorRect.js.map +1 -0
- package/dist/server/actions.d.ts +41 -0
- package/dist/server/actions.js +214 -0
- package/dist/server/actions.js.map +1 -0
- package/dist/server/data.d.ts +10 -0
- package/dist/server/data.js +24 -0
- package/dist/server/data.js.map +1 -0
- package/dist/server/fieldData.d.ts +26 -0
- package/dist/server/fieldData.js +9 -0
- package/dist/server/fieldData.js.map +1 -0
- package/dist/server/loader.d.ts +6 -0
- package/dist/server/loader.js +53 -0
- package/dist/server/loader.js.map +1 -0
- package/dist/server/repeaterActions.d.ts +20 -0
- package/dist/server/repeaterActions.js +72 -0
- package/dist/server/repeaterActions.js.map +1 -0
- package/dist/server/repeaterData.d.ts +18 -0
- package/dist/server/repeaterData.js +27 -0
- package/dist/server/repeaterData.js.map +1 -0
- package/dist/server/session.d.ts +14 -0
- package/dist/server/session.js +37 -0
- package/dist/server/session.js.map +1 -0
- package/dist/server/wrapper.d.ts +12 -0
- package/dist/server/wrapper.js +42 -0
- package/dist/server/wrapper.js.map +1 -0
- package/package.json +58 -0
- package/supabase/migrations/20260911000000_guardrail_init.sql +288 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
let _config = null;
|
|
2
|
+
function configureGuardrail(config) {
|
|
3
|
+
_config = config;
|
|
4
|
+
}
|
|
5
|
+
function required(key) {
|
|
6
|
+
const value = _config?.[key];
|
|
7
|
+
if (!value) {
|
|
8
|
+
throw new Error(
|
|
9
|
+
`Guardrail: no ${String(key)} adapter configured. Pass one to withGuardrail(Layout, { db, auth, members }).`
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
14
|
+
function getDB() {
|
|
15
|
+
return required("db");
|
|
16
|
+
}
|
|
17
|
+
function getAuth() {
|
|
18
|
+
return required("auth");
|
|
19
|
+
}
|
|
20
|
+
function getMembers() {
|
|
21
|
+
return required("members");
|
|
22
|
+
}
|
|
23
|
+
function getStorage() {
|
|
24
|
+
return _config?.storage;
|
|
25
|
+
}
|
|
26
|
+
function getRepeaters() {
|
|
27
|
+
return required("repeaters");
|
|
28
|
+
}
|
|
29
|
+
function getInviteHandler() {
|
|
30
|
+
return _config?.onInvite;
|
|
31
|
+
}
|
|
32
|
+
async function resolveSiteId() {
|
|
33
|
+
const sid = _config?.siteId ?? "default";
|
|
34
|
+
return typeof sid === "function" ? await sid() : sid || "default";
|
|
35
|
+
}
|
|
36
|
+
export {
|
|
37
|
+
configureGuardrail,
|
|
38
|
+
getAuth,
|
|
39
|
+
getDB,
|
|
40
|
+
getInviteHandler,
|
|
41
|
+
getMembers,
|
|
42
|
+
getRepeaters,
|
|
43
|
+
getStorage,
|
|
44
|
+
resolveSiteId
|
|
45
|
+
};
|
|
46
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/core/config.ts"],"sourcesContent":["import type {\n GRConfig,\n GRAuthAdapter,\n GRDBAdapter,\n GRMembershipAdapter,\n GRRepeaterAdapter,\n GRStorageAdapter,\n} from \"./adapter-types\";\n\nlet _config: GRConfig | null = null;\n\n/**\n * Registers the adapters Guardrail should use.\n *\n * Idempotent, and re-applied on every render by withGuardrail: Next.js does not\n * guarantee one shared module instance across the SSR graph, the server-action\n * graph, and the workers used for static generation, so config registered once\n * at startup is simply absent in some of them.\n *\n * Core has no knowledge of any particular backend — there is no default and no\n * fallback. An app names its adapters explicitly or Guardrail refuses to run.\n */\nexport function configureGuardrail(config: GRConfig): void {\n _config = config;\n}\n\nfunction required<K extends keyof GRConfig>(key: K): GRConfig[K] {\n const value = _config?.[key];\n if (!value) {\n throw new Error(\n `Guardrail: no ${String(key)} adapter configured. Pass one to ` +\n `withGuardrail(Layout, { db, auth, members }).`,\n );\n }\n return value;\n}\n\nexport function getDB(): GRDBAdapter {\n return required(\"db\") as GRDBAdapter;\n}\n\nexport function getAuth(): GRAuthAdapter {\n return required(\"auth\") as GRAuthAdapter;\n}\n\nexport function getMembers(): GRMembershipAdapter {\n return required(\"members\") as GRMembershipAdapter;\n}\n\n/** Optional — sites without editable images need no storage. */\nexport function getStorage(): GRStorageAdapter | undefined {\n return _config?.storage;\n}\n\n/** Throws with a clear message if no page on this site uses <GRepeater>. */\nexport function getRepeaters(): GRRepeaterAdapter {\n return required(\"repeaters\") as GRRepeaterAdapter;\n}\n\nexport function getInviteHandler(): GRConfig[\"onInvite\"] {\n return _config?.onInvite;\n}\n\nexport async function resolveSiteId(): Promise<string> {\n const sid = _config?.siteId ?? \"default\";\n return typeof sid === \"function\" ? await sid() : sid || \"default\";\n}\n"],"mappings":"AASA,IAAI,UAA2B;AAaxB,SAAS,mBAAmB,QAAwB;AACzD,YAAU;AACZ;AAEA,SAAS,SAAmC,KAAqB;AAC/D,QAAM,QAAQ,UAAU,GAAG;AAC3B,MAAI,CAAC,OAAO;AACV,UAAM,IAAI;AAAA,MACR,iBAAiB,OAAO,GAAG,CAAC;AAAA,IAE9B;AAAA,EACF;AACA,SAAO;AACT;AAEO,SAAS,QAAqB;AACnC,SAAO,SAAS,IAAI;AACtB;AAEO,SAAS,UAAyB;AACvC,SAAO,SAAS,MAAM;AACxB;AAEO,SAAS,aAAkC;AAChD,SAAO,SAAS,SAAS;AAC3B;AAGO,SAAS,aAA2C;AACzD,SAAO,SAAS;AAClB;AAGO,SAAS,eAAkC;AAChD,SAAO,SAAS,WAAW;AAC7B;AAEO,SAAS,mBAAyC;AACvD,SAAO,SAAS;AAClB;AAEA,eAAsB,gBAAiC;AACrD,QAAM,MAAM,SAAS,UAAU;AAC/B,SAAO,OAAO,QAAQ,aAAa,MAAM,IAAI,IAAI,OAAO;AAC1D;","names":[]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
declare const GR_COOKIES: {
|
|
2
|
+
/** Signals intent to edit. Not a credential — carries no authority on its own. */
|
|
3
|
+
readonly EDIT_MODE: "gr_edit_mode";
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* Dedicated Postgres schema, not `public`. A name prefix alone only guards
|
|
7
|
+
* against accidental collisions; it does nothing against a `supabase db
|
|
8
|
+
* reset`, a broad `DROP ... CASCADE`, or a migration tool that diffs and
|
|
9
|
+
* "reconciles" everything in `public` as if it owns that namespace. A
|
|
10
|
+
* separate schema keeps Guardrail's tables outside that blast radius —
|
|
11
|
+
* the same reason auth.* and storage.* are their own schemas, not public.*.
|
|
12
|
+
*
|
|
13
|
+
* Requires "guardrail" to be added under Settings > API > Exposed schemas in
|
|
14
|
+
* the Supabase dashboard — PostgREST only serves schemas on that list, and no
|
|
15
|
+
* migration can add itself to it.
|
|
16
|
+
*/
|
|
17
|
+
declare const GR_SCHEMA = "guardrail";
|
|
18
|
+
declare const GR_TABLE = "gr_content";
|
|
19
|
+
declare const GR_MEMBERS_TABLE = "gr_members";
|
|
20
|
+
declare const GR_REPEATER_TABLE = "gr_repeater_items";
|
|
21
|
+
|
|
22
|
+
export { GR_COOKIES, GR_MEMBERS_TABLE, GR_REPEATER_TABLE, GR_SCHEMA, GR_TABLE };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const GR_COOKIES = {
|
|
2
|
+
/** Signals intent to edit. Not a credential — carries no authority on its own. */
|
|
3
|
+
EDIT_MODE: "gr_edit_mode"
|
|
4
|
+
};
|
|
5
|
+
const GR_SCHEMA = "guardrail";
|
|
6
|
+
const GR_TABLE = "gr_content";
|
|
7
|
+
const GR_MEMBERS_TABLE = "gr_members";
|
|
8
|
+
const GR_REPEATER_TABLE = "gr_repeater_items";
|
|
9
|
+
export {
|
|
10
|
+
GR_COOKIES,
|
|
11
|
+
GR_MEMBERS_TABLE,
|
|
12
|
+
GR_REPEATER_TABLE,
|
|
13
|
+
GR_SCHEMA,
|
|
14
|
+
GR_TABLE
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=defs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/core/defs.ts"],"sourcesContent":["export const GR_COOKIES = {\n /** Signals intent to edit. Not a credential — carries no authority on its own. */\n EDIT_MODE: \"gr_edit_mode\",\n} as const;\n\n/**\n * Dedicated Postgres schema, not `public`. A name prefix alone only guards\n * against accidental collisions; it does nothing against a `supabase db\n * reset`, a broad `DROP ... CASCADE`, or a migration tool that diffs and\n * \"reconciles\" everything in `public` as if it owns that namespace. A\n * separate schema keeps Guardrail's tables outside that blast radius —\n * the same reason auth.* and storage.* are their own schemas, not public.*.\n *\n * Requires \"guardrail\" to be added under Settings > API > Exposed schemas in\n * the Supabase dashboard — PostgREST only serves schemas on that list, and no\n * migration can add itself to it.\n */\nexport const GR_SCHEMA = \"guardrail\";\n\nexport const GR_TABLE = \"gr_content\";\nexport const GR_MEMBERS_TABLE = \"gr_members\";\nexport const GR_REPEATER_TABLE = \"gr_repeater_items\";\n"],"mappings":"AAAO,MAAM,aAAa;AAAA;AAAA,EAExB,WAAW;AACb;AAcO,MAAM,YAAY;AAElB,MAAM,WAAW;AACjB,MAAM,mBAAmB;AACzB,MAAM,oBAAoB;","names":[]}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { JSX, ReactNode, ComponentPropsWithoutRef } from 'react';
|
|
2
|
+
|
|
3
|
+
type HTMLKeys = keyof JSX.IntrinsicElements;
|
|
4
|
+
type OverrideType = "text" | "link" | "image" | "button";
|
|
5
|
+
interface OverrideValue {
|
|
6
|
+
type: OverrideType;
|
|
7
|
+
text?: string;
|
|
8
|
+
href?: string;
|
|
9
|
+
src?: string;
|
|
10
|
+
/** Lives on the content row, not the asset: one image, different pages, different alt. */
|
|
11
|
+
alt?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Natural size of the chosen image, measured in the browser when it is picked.
|
|
14
|
+
* Stored with the usage so rendering needs no lookup and storage adapters need
|
|
15
|
+
* no metadata table.
|
|
16
|
+
*/
|
|
17
|
+
width?: number;
|
|
18
|
+
height?: number;
|
|
19
|
+
}
|
|
20
|
+
/** A stored file as a storage adapter describes it. */
|
|
21
|
+
interface GRAsset {
|
|
22
|
+
/** Provider-side identifier — for Supabase, the object path. */
|
|
23
|
+
id: string;
|
|
24
|
+
/** Public URL, ready to render. */
|
|
25
|
+
url: string;
|
|
26
|
+
name: string;
|
|
27
|
+
size?: number;
|
|
28
|
+
contentType?: string;
|
|
29
|
+
createdAt?: string;
|
|
30
|
+
}
|
|
31
|
+
interface OverrideRow {
|
|
32
|
+
id: string;
|
|
33
|
+
pathKey: string;
|
|
34
|
+
value: OverrideValue;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* next/image props that have no intrinsic-element equivalent. A field rendered
|
|
38
|
+
* with type="image" goes through next/image, so these have to be accepted even
|
|
39
|
+
* when `as` is "img".
|
|
40
|
+
*/
|
|
41
|
+
interface NextImageExtras {
|
|
42
|
+
priority?: boolean;
|
|
43
|
+
fill?: boolean;
|
|
44
|
+
sizes?: string;
|
|
45
|
+
quality?: number;
|
|
46
|
+
placeholder?: "blur" | "empty";
|
|
47
|
+
blurDataURL?: string;
|
|
48
|
+
unoptimized?: boolean;
|
|
49
|
+
}
|
|
50
|
+
type PolymorphicProps<T extends HTMLKeys> = {
|
|
51
|
+
as?: T;
|
|
52
|
+
id: string;
|
|
53
|
+
type?: OverrideType;
|
|
54
|
+
children?: ReactNode;
|
|
55
|
+
/**
|
|
56
|
+
* Set false to always render plain markup, even for a signed-in editor —
|
|
57
|
+
* the same output an anonymous visitor gets, with no click-to-edit
|
|
58
|
+
* affordance at all. For a field inside a link to that content's own page
|
|
59
|
+
* (a product card, say), this keeps the card purely a link and pushes
|
|
60
|
+
* editing to the canonical route instead of fighting the link for clicks.
|
|
61
|
+
*/
|
|
62
|
+
editable?: boolean;
|
|
63
|
+
} & NextImageExtras & Omit<ComponentPropsWithoutRef<T>, "id" | "type" | "children" | "editable" | keyof NextImageExtras>;
|
|
64
|
+
/**
|
|
65
|
+
* Both roles may edit content; only admin manages the team. There is no
|
|
66
|
+
* read-only "viewer" — anyone who can't edit sees exactly what an anonymous
|
|
67
|
+
* visitor sees, which is already the site's own frontend.
|
|
68
|
+
*/
|
|
69
|
+
type GRRole = "admin" | "editor";
|
|
70
|
+
interface GRMember {
|
|
71
|
+
/** Null until a pending invite is claimed by a sign-in. */
|
|
72
|
+
userId: string | null;
|
|
73
|
+
email: string;
|
|
74
|
+
role: GRRole;
|
|
75
|
+
status: "active" | "pending";
|
|
76
|
+
invitedAt?: string;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* A signed-in person as seen by one site: identity plus the role they hold
|
|
80
|
+
* there. Identity comes from the auth provider, the role from Guardrail's own
|
|
81
|
+
* membership store, so the two are never conflated.
|
|
82
|
+
*/
|
|
83
|
+
interface GRViewer {
|
|
84
|
+
userId: string;
|
|
85
|
+
email?: string;
|
|
86
|
+
/** Null when signed in but not a member of this site. */
|
|
87
|
+
role: GRRole | null;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* What the configured auth adapter can actually do, resolved on the server and
|
|
91
|
+
* passed down. Client components cannot inspect a server-side adapter, so the
|
|
92
|
+
* capabilities have to travel with the state.
|
|
93
|
+
*/
|
|
94
|
+
interface GRAuthCapabilities {
|
|
95
|
+
/** The adapter accepts email/password, so the built-in form is usable. */
|
|
96
|
+
password: boolean;
|
|
97
|
+
/** Where to send people when it does not. */
|
|
98
|
+
signInUrl?: string;
|
|
99
|
+
}
|
|
100
|
+
interface GRState {
|
|
101
|
+
isAuth: boolean;
|
|
102
|
+
isEditMode: boolean;
|
|
103
|
+
siteId: string;
|
|
104
|
+
/** Null when the signed-in user holds no role on this site. */
|
|
105
|
+
role: GRRole | null;
|
|
106
|
+
email?: string;
|
|
107
|
+
auth: GRAuthCapabilities;
|
|
108
|
+
/** A storage adapter is configured, so images can be uploaded and picked. */
|
|
109
|
+
storage: boolean;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* One instance inside a repeater — a service card, a blog post, a product.
|
|
113
|
+
* Its content fields are ordinary OverrideRows, namespaced under
|
|
114
|
+
* `${repeaterId}.${item.id}.${fieldId}` by convention. There is no schema
|
|
115
|
+
* beyond that: the shape of an item is whatever JSX the developer's template
|
|
116
|
+
* component renders, identical to any other Guardrail field.
|
|
117
|
+
*/
|
|
118
|
+
interface GRRepeaterItem {
|
|
119
|
+
/** Stable, internal, never shown — only used to namespace this item's fields. */
|
|
120
|
+
id: string;
|
|
121
|
+
/**
|
|
122
|
+
* Present only when the repeater is routed: the URL segment for this item's
|
|
123
|
+
* own page, e.g. "wool-throw" for /products/wool-throw. A repeater with no
|
|
124
|
+
* routing (a row of cards on one page) leaves every item's slug undefined.
|
|
125
|
+
*/
|
|
126
|
+
slug?: string;
|
|
127
|
+
order: number;
|
|
128
|
+
}
|
|
129
|
+
type ActionState = {
|
|
130
|
+
success?: boolean;
|
|
131
|
+
error?: string;
|
|
132
|
+
email?: string;
|
|
133
|
+
password?: string;
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
export type { ActionState, GRAsset, GRAuthCapabilities, GRMember, GRRepeaterItem, GRRole, GRState, GRViewer, HTMLKeys, NextImageExtras, OverrideRow, OverrideType, OverrideValue, PolymorphicProps };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export { withGuardrail } from './server/wrapper.js';
|
|
2
|
+
export { createAssetUpload, inviteMember, listAssets, listTeam, removeMember, updateMemberRole } from './server/actions.js';
|
|
3
|
+
export { GRail } from './react/GRail/index.js';
|
|
4
|
+
export { GRepeater, GRepeaterProps, RepeaterItemProps } from './react/Repeater/index.js';
|
|
5
|
+
export { configureGuardrail } from './core/config.js';
|
|
6
|
+
export { getRepeaterItemBySlug, listRepeaterItems } from './server/repeaterData.js';
|
|
7
|
+
export { getFieldValue } from './server/fieldData.js';
|
|
8
|
+
export { addRepeaterItem, removeRepeaterItem, reorderRepeaterItems } from './server/repeaterActions.js';
|
|
9
|
+
export { useGRail } from './react/Provider.js';
|
|
10
|
+
export { GRAsset, GRMember, GRRepeaterItem, GRRole, GRState, GRViewer, HTMLKeys, OverrideRow, OverrideType, OverrideValue, PolymorphicProps } from './core/types.js';
|
|
11
|
+
export { AuthResult, GRAuthAdapter, GRConfig, GRDBAdapter, GRInviteEvent, GRMembershipAdapter, GRRepeaterAdapter, GRSession, GRStorageAdapter, GRStorageRow, GRUploadTarget } from './core/adapter-types.js';
|
|
12
|
+
import 'react/jsx-runtime';
|
|
13
|
+
import 'react';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { withGuardrail } from "./server/wrapper";
|
|
2
|
+
import {
|
|
3
|
+
listTeam,
|
|
4
|
+
inviteMember,
|
|
5
|
+
updateMemberRole,
|
|
6
|
+
removeMember,
|
|
7
|
+
createAssetUpload,
|
|
8
|
+
listAssets
|
|
9
|
+
} from "./server/actions";
|
|
10
|
+
import { GRail } from "./react/GRail";
|
|
11
|
+
import { GRepeater } from "./react/Repeater";
|
|
12
|
+
import { configureGuardrail } from "./core/config";
|
|
13
|
+
import {
|
|
14
|
+
listRepeaterItems,
|
|
15
|
+
getRepeaterItemBySlug
|
|
16
|
+
} from "./server/repeaterData";
|
|
17
|
+
import { getFieldValue } from "./server/fieldData";
|
|
18
|
+
import {
|
|
19
|
+
addRepeaterItem,
|
|
20
|
+
removeRepeaterItem,
|
|
21
|
+
reorderRepeaterItems
|
|
22
|
+
} from "./server/repeaterActions";
|
|
23
|
+
import { useGRail } from "./react/Provider";
|
|
24
|
+
export {
|
|
25
|
+
GRail,
|
|
26
|
+
GRepeater,
|
|
27
|
+
addRepeaterItem,
|
|
28
|
+
configureGuardrail,
|
|
29
|
+
createAssetUpload,
|
|
30
|
+
getFieldValue,
|
|
31
|
+
getRepeaterItemBySlug,
|
|
32
|
+
inviteMember,
|
|
33
|
+
listAssets,
|
|
34
|
+
listRepeaterItems,
|
|
35
|
+
listTeam,
|
|
36
|
+
removeMember,
|
|
37
|
+
removeRepeaterItem,
|
|
38
|
+
reorderRepeaterItems,
|
|
39
|
+
updateMemberRole,
|
|
40
|
+
useGRail,
|
|
41
|
+
withGuardrail
|
|
42
|
+
};
|
|
43
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["// Server-safe entry. Client components are reached through the files that\n// carry their own \"use client\" directive, so importing this from a Server\n// Component does not pull the editor into the client bundle.\nexport { withGuardrail } from \"./server/wrapper\";\n\n// Team management, exported so an app can build its own UI instead of using\n// the bundled drawer.\nexport {\n listTeam,\n inviteMember,\n updateMemberRole,\n removeMember,\n createAssetUpload,\n listAssets,\n} from \"./server/actions\";\nexport { GRail } from \"./react/GRail\";\nexport { GRepeater } from \"./react/Repeater\";\nexport type { GRepeaterProps, RepeaterItemProps } from \"./react/Repeater\";\nexport { configureGuardrail } from \"./core/config\";\nexport {\n listRepeaterItems,\n getRepeaterItemBySlug,\n} from \"./server/repeaterData\";\nexport { getFieldValue } from \"./server/fieldData\";\n// Mutations, exported so an app can build its own add/remove/reorder UI\n// instead of the bundled chrome — same rationale as the team actions above.\nexport {\n addRepeaterItem,\n removeRepeaterItem,\n reorderRepeaterItems,\n} from \"./server/repeaterActions\";\nexport { useGRail } from \"./react/Provider\";\n\nexport type {\n GRState,\n GRRole,\n GRMember,\n GRViewer,\n GRAsset,\n GRRepeaterItem,\n OverrideRow,\n OverrideValue,\n OverrideType,\n PolymorphicProps,\n HTMLKeys,\n} from \"./core/types\";\nexport type {\n GRConfig,\n GRMembershipAdapter,\n GRInviteEvent,\n GRStorageAdapter,\n GRUploadTarget,\n GRRepeaterAdapter,\n GRDBAdapter,\n GRAuthAdapter,\n GRSession,\n GRStorageRow,\n AuthResult,\n} from \"./core/adapter-types\";\n"],"mappings":"AAGA,SAAS,qBAAqB;AAI9B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,aAAa;AACtB,SAAS,iBAAiB;AAE1B,SAAS,0BAA0B;AACnC;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,qBAAqB;AAG9B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,gBAAgB;","names":[]}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import React, { useActionState, useEffect, useState } from "react";
|
|
4
|
+
import { usePathname, useRouter } from "next/navigation";
|
|
5
|
+
import { loginAction, logoutAction } from "../server/actions";
|
|
6
|
+
import { useGRail } from "./Provider";
|
|
7
|
+
import { TeamDrawer } from "./Team";
|
|
8
|
+
import { LogOutIcon, PeopleIcon } from "./Icons";
|
|
9
|
+
import { S } from "./styles";
|
|
10
|
+
const initialState = { email: "", password: "" };
|
|
11
|
+
function GRailAdmin({ toolbarExtra }) {
|
|
12
|
+
const { isAuth, isEditMode, role, auth } = useGRail();
|
|
13
|
+
const [teamOpen, setTeamOpen] = useState(false);
|
|
14
|
+
const [formState, formAction, isPending] = useActionState(
|
|
15
|
+
loginAction,
|
|
16
|
+
initialState
|
|
17
|
+
);
|
|
18
|
+
const router = useRouter();
|
|
19
|
+
const pathname = usePathname();
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (formState.success) router.refresh();
|
|
22
|
+
}, [formState.success, router]);
|
|
23
|
+
const signOut = async () => {
|
|
24
|
+
await logoutAction();
|
|
25
|
+
document.cookie = "gr_edit_mode=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT";
|
|
26
|
+
window.location.href = pathname;
|
|
27
|
+
};
|
|
28
|
+
if (!isEditMode) return null;
|
|
29
|
+
if (!isAuth) {
|
|
30
|
+
if (!auth.password) {
|
|
31
|
+
return /* @__PURE__ */ jsx("div", { "data-guardrail": true, style: S.banner, children: /* @__PURE__ */ jsxs("div", { style: S.bannerForm, children: [
|
|
32
|
+
/* @__PURE__ */ jsx("span", { style: S.brand, children: "Guardrail" }),
|
|
33
|
+
auth.signInUrl ? /* @__PURE__ */ jsx("a", { href: auth.signInUrl, style: { ...S.primaryBtn, textDecoration: "none" }, children: "Sign in" }) : /* @__PURE__ */ jsx("span", { style: { fontSize: 12, color: "#6b7280" }, children: "No sign-in method is configured for this site." })
|
|
34
|
+
] }) });
|
|
35
|
+
}
|
|
36
|
+
return /* @__PURE__ */ jsx("div", { "data-guardrail": true, style: S.banner, children: /* @__PURE__ */ jsxs("form", { action: formAction, style: S.bannerForm, children: [
|
|
37
|
+
/* @__PURE__ */ jsx("span", { style: S.brand, children: "Guardrail" }),
|
|
38
|
+
formState.error && /* @__PURE__ */ jsx("span", { style: S.error, children: formState.error }),
|
|
39
|
+
/* @__PURE__ */ jsx(
|
|
40
|
+
"input",
|
|
41
|
+
{
|
|
42
|
+
name: "email",
|
|
43
|
+
type: "email",
|
|
44
|
+
placeholder: "Email",
|
|
45
|
+
defaultValue: formState.email,
|
|
46
|
+
required: true,
|
|
47
|
+
style: S.input
|
|
48
|
+
}
|
|
49
|
+
),
|
|
50
|
+
/* @__PURE__ */ jsx(
|
|
51
|
+
"input",
|
|
52
|
+
{
|
|
53
|
+
name: "password",
|
|
54
|
+
type: "password",
|
|
55
|
+
placeholder: "Password",
|
|
56
|
+
required: true,
|
|
57
|
+
style: S.input
|
|
58
|
+
}
|
|
59
|
+
),
|
|
60
|
+
/* @__PURE__ */ jsx("button", { type: "submit", disabled: isPending, style: S.primaryBtn, children: isPending ? "Authenticating\u2026" : "Sign In" })
|
|
61
|
+
] }) });
|
|
62
|
+
}
|
|
63
|
+
if (!role) {
|
|
64
|
+
return /* @__PURE__ */ jsxs("div", { "data-guardrail": true, style: S.toolbar, children: [
|
|
65
|
+
/* @__PURE__ */ jsx("span", { style: S.toolbarLabel, children: "No access to this site" }),
|
|
66
|
+
/* @__PURE__ */ jsx("span", { style: S.divider }),
|
|
67
|
+
/* @__PURE__ */ jsx(
|
|
68
|
+
"button",
|
|
69
|
+
{
|
|
70
|
+
style: { ...S.iconBtn, color: "#9ca3af" },
|
|
71
|
+
onClick: signOut,
|
|
72
|
+
title: "Log out",
|
|
73
|
+
"aria-label": "Log out",
|
|
74
|
+
children: /* @__PURE__ */ jsx(LogOutIcon, {})
|
|
75
|
+
}
|
|
76
|
+
)
|
|
77
|
+
] });
|
|
78
|
+
}
|
|
79
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
80
|
+
teamOpen && /* @__PURE__ */ jsx(TeamDrawer, { onClose: () => setTeamOpen(false) }),
|
|
81
|
+
/* @__PURE__ */ jsxs("div", { "data-guardrail": true, style: S.toolbar, children: [
|
|
82
|
+
/* @__PURE__ */ jsx("span", { style: S.dot }),
|
|
83
|
+
/* @__PURE__ */ jsx("span", { style: S.toolbarLabel, children: "Edit Mode" }),
|
|
84
|
+
/* @__PURE__ */ jsx("span", { style: S.divider }),
|
|
85
|
+
/* @__PURE__ */ jsx(
|
|
86
|
+
"button",
|
|
87
|
+
{
|
|
88
|
+
style: { ...S.iconBtn, color: "#9ca3af", width: 24, height: 24 },
|
|
89
|
+
onClick: () => setTeamOpen(true),
|
|
90
|
+
title: "Team",
|
|
91
|
+
"aria-label": "Team",
|
|
92
|
+
children: /* @__PURE__ */ jsx(PeopleIcon, {})
|
|
93
|
+
}
|
|
94
|
+
),
|
|
95
|
+
toolbarExtra && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
96
|
+
/* @__PURE__ */ jsx("span", { style: S.divider }),
|
|
97
|
+
toolbarExtra
|
|
98
|
+
] }),
|
|
99
|
+
/* @__PURE__ */ jsx("span", { style: S.divider }),
|
|
100
|
+
/* @__PURE__ */ jsx(
|
|
101
|
+
"button",
|
|
102
|
+
{
|
|
103
|
+
style: { ...S.iconBtn, color: "#9ca3af" },
|
|
104
|
+
onClick: signOut,
|
|
105
|
+
title: "Log out",
|
|
106
|
+
"aria-label": "Log out",
|
|
107
|
+
children: /* @__PURE__ */ jsx(LogOutIcon, {})
|
|
108
|
+
}
|
|
109
|
+
)
|
|
110
|
+
] })
|
|
111
|
+
] });
|
|
112
|
+
}
|
|
113
|
+
export {
|
|
114
|
+
GRailAdmin
|
|
115
|
+
};
|
|
116
|
+
//# sourceMappingURL=Admin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/react/Admin.tsx"],"sourcesContent":["\"use client\";\n\nimport React, { useActionState, useEffect, useState } from \"react\";\nimport { usePathname, useRouter } from \"next/navigation\";\nimport { loginAction, logoutAction } from \"../server/actions\";\nimport { useGRail } from \"./Provider\";\nimport { TeamDrawer } from \"./Team\";\nimport { LogOutIcon, PeopleIcon } from \"./Icons\";\nimport { S } from \"./styles\";\nimport type { ActionState } from \"../core/types\";\n\nconst initialState: ActionState = { email: \"\", password: \"\" };\n\nexport function GRailAdmin({ toolbarExtra }: { toolbarExtra?: React.ReactNode }) {\n const { isAuth, isEditMode, role, auth } = useGRail();\n const [teamOpen, setTeamOpen] = useState(false);\n const [formState, formAction, isPending] = useActionState(\n loginAction,\n initialState,\n );\n const router = useRouter();\n const pathname = usePathname();\n\n useEffect(() => {\n if (formState.success) router.refresh();\n }, [formState.success, router]);\n\n const signOut = async () => {\n await logoutAction();\n document.cookie =\n \"gr_edit_mode=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT\";\n window.location.href = pathname;\n };\n\n // No intent signalled — render nothing at all.\n if (!isEditMode) return null;\n\n if (!isAuth) {\n // The adapter cannot take a password here, so hand sign-in off rather than\n // showing a form that could never succeed.\n if (!auth.password) {\n return (\n <div data-guardrail style={S.banner}>\n <div style={S.bannerForm}>\n <span style={S.brand}>Guardrail</span>\n {auth.signInUrl ? (\n <a href={auth.signInUrl} style={{ ...S.primaryBtn, textDecoration: \"none\" }}>\n Sign in\n </a>\n ) : (\n <span style={{ fontSize: 12, color: \"#6b7280\" }}>\n No sign-in method is configured for this site.\n </span>\n )}\n </div>\n </div>\n );\n }\n\n return (\n <div data-guardrail style={S.banner}>\n <form action={formAction} style={S.bannerForm}>\n <span style={S.brand}>Guardrail</span>\n {formState.error && <span style={S.error}>{formState.error}</span>}\n <input\n name=\"email\"\n type=\"email\"\n placeholder=\"Email\"\n defaultValue={formState.email}\n required\n style={S.input}\n />\n <input\n name=\"password\"\n type=\"password\"\n placeholder=\"Password\"\n required\n style={S.input}\n />\n <button type=\"submit\" disabled={isPending} style={S.primaryBtn}>\n {isPending ? \"Authenticating…\" : \"Sign In\"}\n </button>\n </form>\n </div>\n );\n }\n\n // Signed in, but holds no role on this site.\n if (!role) {\n return (\n <div data-guardrail style={S.toolbar}>\n <span style={S.toolbarLabel}>No access to this site</span>\n <span style={S.divider} />\n <button\n style={{ ...S.iconBtn, color: \"#9ca3af\" }}\n onClick={signOut}\n title=\"Log out\"\n aria-label=\"Log out\"\n >\n <LogOutIcon />\n </button>\n </div>\n );\n }\n\n return (\n <>\n {teamOpen && <TeamDrawer onClose={() => setTeamOpen(false)} />}\n <div data-guardrail style={S.toolbar}>\n <span style={S.dot} />\n <span style={S.toolbarLabel}>Edit Mode</span>\n <span style={S.divider} />\n <button\n style={{ ...S.iconBtn, color: \"#9ca3af\", width: 24, height: 24 }}\n onClick={() => setTeamOpen(true)}\n title=\"Team\"\n aria-label=\"Team\"\n >\n <PeopleIcon />\n </button>\n\n {toolbarExtra && (\n <>\n <span style={S.divider} />\n {toolbarExtra}\n </>\n )}\n\n <span style={S.divider} />\n <button\n style={{ ...S.iconBtn, color: \"#9ca3af\" }}\n onClick={signOut}\n title=\"Log out\"\n aria-label=\"Log out\"\n >\n <LogOutIcon />\n </button>\n </div>\n </>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { HTMLKeys, OverrideRow } from '../../core/types.js';
|
|
3
|
+
import 'react';
|
|
4
|
+
|
|
5
|
+
declare function GRailClient({ as, initialOverride, ...htmlProps }: {
|
|
6
|
+
as?: HTMLKeys;
|
|
7
|
+
initialOverride: OverrideRow;
|
|
8
|
+
[key: string]: unknown;
|
|
9
|
+
}): react_jsx_runtime.JSX.Element;
|
|
10
|
+
|
|
11
|
+
export { GRailClient };
|