@rebasepro/studio 0.15.0 → 0.16.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.
Files changed (36) hide show
  1. package/dist/{ApiExplorer-CDOlUQzk.js → ApiExplorer-9iwGvNnt.js} +5 -5
  2. package/dist/ApiExplorer-9iwGvNnt.js.map +1 -0
  3. package/dist/{JSEditor-BaSrzWhF.js → JSEditor-BZnvK1n6.js} +14 -14
  4. package/dist/JSEditor-BZnvK1n6.js.map +1 -0
  5. package/dist/{RLSEditor-CV8R1G3W.js → RLSEditor-CBX4uD66.js} +163 -49
  6. package/dist/RLSEditor-CBX4uD66.js.map +1 -0
  7. package/dist/{SQLEditor-BmI0iIWK.js → SQLEditor-ByecwX8B.js} +16 -16
  8. package/dist/SQLEditor-ByecwX8B.js.map +1 -0
  9. package/dist/{SchemaVisualizer-vxKSG80C.js → SchemaVisualizer-D6tMZgeC.js} +4 -4
  10. package/dist/SchemaVisualizer-D6tMZgeC.js.map +1 -0
  11. package/dist/{StorageView-CwDI4sBG.js → StorageView-CGm-cacx.js} +9 -9
  12. package/dist/StorageView-CGm-cacx.js.map +1 -0
  13. package/dist/components/RLSEditor/PolicyEditor.d.ts +9 -1
  14. package/dist/components/RLSEditor/policy-presets.d.ts +74 -0
  15. package/dist/index.es.js +6 -6
  16. package/package.json +9 -9
  17. package/src/components/ApiExplorer/ApiExplorer.tsx +2 -2
  18. package/src/components/ApiExplorer/EndpointDetail.tsx +1 -1
  19. package/src/components/ApiExplorer/TryItPanel.tsx +1 -1
  20. package/src/components/JSEditor/JSEditor.tsx +7 -7
  21. package/src/components/JSEditor/JSEditorSidebar.tsx +6 -6
  22. package/src/components/RLSEditor/PolicyEditor.tsx +31 -96
  23. package/src/components/RLSEditor/RLSEditor.tsx +46 -4
  24. package/src/components/RLSEditor/policy-presets.ts +176 -0
  25. package/src/components/SQLEditor/SQLEditor.tsx +10 -10
  26. package/src/components/SQLEditor/SQLEditorSidebar.tsx +4 -4
  27. package/src/components/SQLEditor/SchemaBrowser.tsx +1 -1
  28. package/src/components/SchemaVisualizer/SchemaVisualizer.tsx +1 -1
  29. package/src/components/SchemaVisualizer/TableNode.tsx +2 -2
  30. package/src/components/StorageView/StorageView.tsx +8 -8
  31. package/dist/ApiExplorer-CDOlUQzk.js.map +0 -1
  32. package/dist/JSEditor-BaSrzWhF.js.map +0 -1
  33. package/dist/RLSEditor-CV8R1G3W.js.map +0 -1
  34. package/dist/SQLEditor-BmI0iIWK.js.map +0 -1
  35. package/dist/SchemaVisualizer-vxKSG80C.js.map +0 -1
  36. package/dist/StorageView-CwDI4sBG.js.map +0 -1
@@ -3,10 +3,18 @@ import { ErrorView, useApiBase, useApiConfig, useRebaseContext, useSnackbarContr
3
3
  import { useCallback, useEffect, useMemo, useState } from "react";
4
4
  import { Alert, AlertTriangleIcon, Button, Chip, CircularProgress, DatabaseIcon, Dialog, DialogActions, DialogContent, DialogTitle, HelpCircleIcon, IconButton, KeyIcon, Link2Icon, LockIcon, MultiSelect, MultiSelectItem, Paper, RefreshCwIcon, ResizablePanels, Select, SelectItem, ShieldIcon, Tab, Tabs, TextField, Tooltip, Trash2Icon, Typography, cls, defaultBorderMixin, iconSize } from "@rebasepro/ui";
5
5
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
6
- import { isPostgresCollectionConfig } from "@rebasepro/types";
6
+ import { RLS_JWT_SQL, RLS_ROLES_SQL, RLS_UID_SQL, isPostgresCollectionConfig, policy } from "@rebasepro/types";
7
7
  import { getPolicyNamesForRule, getPolicyNamesForRules, getPolicyOperations } from "@rebasepro/utils";
8
- import { JUNCTION_TABLES_SQL, REBASE_INTERNAL_PREFIXES, REBASE_INTERNAL_SCHEMAS, getEffectiveSecurityRules, getJunctionSecurityRules, resolveJunctionSpecs } from "@rebasepro/common";
9
- //#region src/components/RLSEditor/PolicyEditor.tsx
8
+ import { JUNCTION_TABLES_SQL, REBASE_INTERNAL_PREFIXES, REBASE_INTERNAL_SCHEMAS, REBASE_USER_ROLE, getEffectiveSecurityRules, getJunctionSecurityRules, policyToPostgres, resolveJunctionSpecs } from "@rebasepro/common";
9
+ //#region src/components/RLSEditor/policy-presets.ts
10
+ /**
11
+ * What the RLS editor offers before the user types anything: the roles a `TO`
12
+ * list may name, and the ready-made policies.
13
+ *
14
+ * Separated from the component because these are the parts that have to agree
15
+ * with the *server*, not with the UI — and the ways they can silently disagree
16
+ * are the reason this file has tests.
17
+ */
10
18
  var COMMAND_OPTIONS = [
11
19
  "ALL",
12
20
  "SELECT",
@@ -14,12 +22,72 @@ var COMMAND_OPTIONS = [
14
22
  "UPDATE",
15
23
  "DELETE"
16
24
  ];
17
- var ROLE_OPTIONS = [
18
- "public",
19
- "authenticated",
20
- "anon",
21
- "admin"
22
- ];
25
+ /**
26
+ * The roles a policy's `TO` list can name when nothing better is known.
27
+ *
28
+ * Not `authenticated` / `anon` / `admin`, which is what this list used to be.
29
+ * The first two are Supabase's role names and Rebase never creates them, so
30
+ * `CREATE POLICY ... TO authenticated` — which is literally what the editor
31
+ * builds and executes — fails with `role "authenticated" does not exist`. The
32
+ * driver's `validatePolicyPgRoles` rejects the same three names in
33
+ * `SecurityRule.pgRoles` and says why: they are another platform's convention,
34
+ * and application roles belong in a condition, not in the `TO` list.
35
+ *
36
+ * `admin` was the more dangerous entry, because it is a plausible *application*
37
+ * role. Had one existed as a database role too, the policy would have been
38
+ * created successfully and then matched nothing at all: requests run as
39
+ * {@link REBASE_USER_ROLE} after a `SET LOCAL ROLE`, and a `TO` list naming a
40
+ * role the request never assumes filters every row without erroring.
41
+ *
42
+ * So: `public` (what the framework's own generator emits) and the role requests
43
+ * actually arrive as. "Signed-in users" and "admins" are not roles here — they
44
+ * are conditions over `rebase.uid()` and `rebase.roles()`, which is what the
45
+ * presets below now compile to.
46
+ */
47
+ var FALLBACK_ROLE_OPTIONS = ["public", REBASE_USER_ROLE];
48
+ /**
49
+ * The `TO` list the editor offers, given what the database reported and what
50
+ * the policy being edited already targets.
51
+ *
52
+ * Three sources, and each is there for a reason the others do not cover:
53
+ *
54
+ * - {@link FALLBACK_ROLE_OPTIONS} always, because `public` is a **keyword, not
55
+ * a row in `pg_roles`** — `fetchAvailableRoles` cannot return it, so seeding
56
+ * from the live fetch alone would drop the one role every generated policy
57
+ * targets.
58
+ * - the live roles, so an operator's own `app_read` is reachable without
59
+ * hand-editing SQL.
60
+ * - the edited policy's own roles, because a `MultiSelect` silently drops a
61
+ * value with no matching item. That value is the `TO` list of a policy
62
+ * already enforcing something, so opening an unrelated policy for a one-word
63
+ * rename would have quietly rewritten who it applies to.
64
+ */
65
+ function roleOptionsFor(fetched, policyRoles) {
66
+ return [.../* @__PURE__ */ new Set([
67
+ ...FALLBACK_ROLE_OPTIONS,
68
+ ...fetched ?? [],
69
+ ...policyRoles ?? []
70
+ ])];
71
+ }
72
+ /**
73
+ * Preset conditions, compiled by the framework's own policy compiler rather
74
+ * than written out here.
75
+ *
76
+ * `policyToPostgres` is the function `db push` generates policies with, so a
77
+ * preset cannot drift from what the framework emits — and the one that matters
78
+ * has drifted before: `authenticated()` used to compile to a bare
79
+ * `IS NOT NULL`, which is true for anonymous visitors, and any copy of that
80
+ * string sitting in a preset would still be handing out the old grant today.
81
+ */
82
+ var SIGNED_IN_SQL = policyToPostgres(policy.authenticated());
83
+ var IS_ADMIN_SQL = policyToPostgres(policy.rolesOverlap(["admin"]));
84
+ var OWNS_ROW_SQL = policyToPostgres(policy.compare(policy.authUid(), "eq", policy.field("uid")));
85
+ /**
86
+ * Every preset targets `TO public`, which is what the framework's generator
87
+ * emits and what actually reaches a request running as `rebase_user`. The part
88
+ * that used to be expressed as a role — "authenticated", "admin" — is a
89
+ * condition now, so it is enforced where Postgres will actually evaluate it.
90
+ */
23
91
  var POLICY_PRESETS = [
24
92
  {
25
93
  id: "public_read",
@@ -34,61 +102,74 @@ var POLICY_PRESETS = [
34
102
  },
35
103
  {
36
104
  id: "auth_read",
37
- label: "Enable read access for authenticated users only",
38
- description: "Only logged-in users are allowed to read data.",
39
- policyname: "Enable read access for authenticated users",
105
+ label: "Enable read access for signed-in users only",
106
+ description: "Only signed-in users are allowed to read data. Anonymous requests carry a sentinel uid, so the condition excludes them explicitly.",
107
+ policyname: "Enable read access for signed-in users",
40
108
  cmd: "SELECT",
41
109
  permissive: "PERMISSIVE",
42
- roles: ["authenticated"],
43
- qual: "true",
110
+ roles: ["public"],
111
+ qual: SIGNED_IN_SQL,
44
112
  with_check: ""
45
113
  },
46
114
  {
47
115
  id: "auth_insert",
48
- label: "Enable insert for authenticated users only",
49
- description: "Only logged-in users are allowed to insert new data.",
50
- policyname: "Enable insert for authenticated users only",
116
+ label: "Enable insert for signed-in users only",
117
+ description: "Only signed-in users are allowed to insert new data.",
118
+ policyname: "Enable insert for signed-in users only",
51
119
  cmd: "INSERT",
52
120
  permissive: "PERMISSIVE",
53
- roles: ["authenticated"],
121
+ roles: ["public"],
54
122
  qual: "",
55
- with_check: "true"
123
+ with_check: SIGNED_IN_SQL
124
+ },
125
+ {
126
+ id: "admin_all",
127
+ label: "Admins can do anything",
128
+ description: "Restricted to users holding the `admin` application role, matched inside the policy via rebase.roles().",
129
+ policyname: "Admins have full access",
130
+ cmd: "ALL",
131
+ permissive: "PERMISSIVE",
132
+ roles: ["public"],
133
+ qual: IS_ADMIN_SQL,
134
+ with_check: IS_ADMIN_SQL
56
135
  },
57
136
  {
58
137
  id: "user_select_own",
59
138
  label: "Users can read their own rows",
60
- description: "Users can only read rows where the uid matches their auth.uid()",
139
+ description: "Users can only read rows whose uid column matches their rebase.uid()",
61
140
  policyname: "Users can select their own data",
62
141
  cmd: "SELECT",
63
142
  permissive: "PERMISSIVE",
64
- roles: ["authenticated"],
65
- qual: "auth.uid() = uid",
143
+ roles: ["public"],
144
+ qual: OWNS_ROW_SQL,
66
145
  with_check: ""
67
146
  },
68
147
  {
69
148
  id: "user_update_own",
70
149
  label: "Users can update their own rows",
71
- description: "Users can only update rows where the uid matches their auth.uid()",
150
+ description: "Users can only update rows whose uid column matches their rebase.uid()",
72
151
  policyname: "Users can update their own data",
73
152
  cmd: "UPDATE",
74
153
  permissive: "PERMISSIVE",
75
- roles: ["authenticated"],
76
- qual: "auth.uid() = uid",
77
- with_check: "auth.uid() = uid"
154
+ roles: ["public"],
155
+ qual: OWNS_ROW_SQL,
156
+ with_check: OWNS_ROW_SQL
78
157
  },
79
158
  {
80
159
  id: "user_delete_own",
81
160
  label: "Users can delete their own rows",
82
- description: "Users can only delete rows where the uid matches their auth.uid()",
161
+ description: "Users can only delete rows whose uid column matches their rebase.uid()",
83
162
  policyname: "Users can delete their own data",
84
163
  cmd: "DELETE",
85
164
  permissive: "PERMISSIVE",
86
- roles: ["authenticated"],
87
- qual: "auth.uid() = uid",
165
+ roles: ["public"],
166
+ qual: OWNS_ROW_SQL,
88
167
  with_check: ""
89
168
  }
90
169
  ];
91
- var PolicyEditor = ({ policy, schema, table, onSave, onCancel }) => {
170
+ //#endregion
171
+ //#region src/components/RLSEditor/PolicyEditor.tsx
172
+ var PolicyEditor = ({ policy, schema, table, roleOptions, onSave, onCancel }) => {
92
173
  const { t } = useTranslation();
93
174
  const [name, setName] = useState("");
94
175
  const [helpOpen, setHelpOpen] = useState(false);
@@ -98,6 +179,7 @@ var PolicyEditor = ({ policy, schema, table, onSave, onCancel }) => {
98
179
  const [usingExpr, setUsingExpr] = useState("");
99
180
  const [checkExpr, setCheckExpr] = useState("");
100
181
  const [selectedPreset, setSelectedPreset] = useState("");
182
+ const availableRoles = useMemo(() => roleOptionsFor(roleOptions, policy?.roles), [roleOptions, policy]);
101
183
  useEffect(() => {
102
184
  if (policy) {
103
185
  setName(policy.policyname || "");
@@ -303,7 +385,7 @@ var PolicyEditor = ({ policy, schema, table, onSave, onCancel }) => {
303
385
  value: roles,
304
386
  onValueChange: setRoles,
305
387
  placeholder: t("studio_policy_roles_placeholder"),
306
- children: ROLE_OPTIONS.map((role) => /* @__PURE__ */ jsx(MultiSelectItem, {
388
+ children: availableRoles.map((role) => /* @__PURE__ */ jsx(MultiSelectItem, {
307
389
  value: role,
308
390
  children: role
309
391
  }, role))
@@ -457,9 +539,13 @@ var PolicyEditor = ({ policy, schema, table, onSave, onCancel }) => {
457
539
  className: "text-text-secondary dark:text-text-secondary-dark mb-1",
458
540
  children: t("studio_policy_help_step3_desc")
459
541
  }),
460
- /* @__PURE__ */ jsx("div", {
542
+ /* @__PURE__ */ jsxs("div", {
461
543
  className: cls("bg-surface-100 dark:bg-surface-950 px-3 py-2 rounded-md font-mono text-sm my-2", defaultBorderMixin),
462
- children: "Example: auth.uid() = uid"
544
+ children: [
545
+ "Example: ",
546
+ RLS_UID_SQL,
547
+ " = uid"
548
+ ]
463
549
  }),
464
550
  /* @__PURE__ */ jsx(Typography, {
465
551
  variant: "caption",
@@ -481,9 +567,13 @@ var PolicyEditor = ({ policy, schema, table, onSave, onCancel }) => {
481
567
  className: "text-text-secondary dark:text-text-secondary-dark mb-1",
482
568
  children: t("studio_policy_help_step4_desc")
483
569
  }),
484
- /* @__PURE__ */ jsx("div", {
570
+ /* @__PURE__ */ jsxs("div", {
485
571
  className: cls("bg-surface-100 dark:bg-surface-950 px-3 py-2 rounded-md font-mono text-sm my-2", defaultBorderMixin),
486
- children: "Example: auth.uid() = uid"
572
+ children: [
573
+ "Example: ",
574
+ RLS_UID_SQL,
575
+ " = uid"
576
+ ]
487
577
  }),
488
578
  /* @__PURE__ */ jsx(Typography, {
489
579
  variant: "caption",
@@ -510,32 +600,36 @@ var PolicyEditor = ({ policy, schema, table, onSave, onCancel }) => {
510
600
  children: [
511
601
  /* @__PURE__ */ jsxs("li", { children: [/* @__PURE__ */ jsx("code", {
512
602
  className: "bg-surface-100 dark:bg-surface-950 px-1.5 py-0.5 rounded mr-1 whitespace-nowrap",
513
- children: "auth.uid()"
603
+ children: RLS_UID_SQL
514
604
  }), /* @__PURE__ */ jsxs("span", {
515
605
  className: "block mt-0.5",
516
- children: ["Returns the current user's ID as text. Example: ", /* @__PURE__ */ jsx("code", {
606
+ children: ["Returns the current user's ID as text. Example: ", /* @__PURE__ */ jsxs("code", {
517
607
  className: "bg-surface-100 dark:bg-surface-950 px-1 py-0.5 rounded text-[11px]",
518
- children: "auth.uid() = uid"
608
+ children: [RLS_UID_SQL, " = uid"]
519
609
  })]
520
610
  })] }),
521
611
  /* @__PURE__ */ jsxs("li", { children: [/* @__PURE__ */ jsx("code", {
522
612
  className: "bg-surface-100 dark:bg-surface-950 px-1.5 py-0.5 rounded mr-1 whitespace-nowrap",
523
- children: "auth.jwt()"
613
+ children: RLS_JWT_SQL
524
614
  }), /* @__PURE__ */ jsxs("span", {
525
615
  className: "block mt-0.5",
526
- children: ["Returns the full JWT payload as JSONB so you can check custom claims. Example: ", /* @__PURE__ */ jsx("code", {
616
+ children: ["Returns the full JWT payload as JSONB so you can check custom claims. Example: ", /* @__PURE__ */ jsxs("code", {
527
617
  className: "bg-surface-100 dark:bg-surface-950 px-1 py-0.5 rounded text-[11px]",
528
- children: "auth.jwt() ->> 'email' = 'admin@example.com'"
618
+ children: [RLS_JWT_SQL, " ->> 'email' = 'admin@example.com'"]
529
619
  })]
530
620
  })] }),
531
621
  /* @__PURE__ */ jsxs("li", { children: [/* @__PURE__ */ jsx("code", {
532
622
  className: "bg-surface-100 dark:bg-surface-950 px-1.5 py-0.5 rounded mr-1 whitespace-nowrap",
533
- children: "auth.roles()"
623
+ children: RLS_ROLES_SQL
534
624
  }), /* @__PURE__ */ jsxs("span", {
535
625
  className: "block mt-0.5",
536
- children: ["Returns the user's role IDs as a comma-separated string. Best used with: ", /* @__PURE__ */ jsx("code", {
626
+ children: ["Returns the user's role IDs as a comma-separated string. Best used with: ", /* @__PURE__ */ jsxs("code", {
537
627
  className: "bg-surface-100 dark:bg-surface-950 px-1 py-0.5 rounded text-[11px]",
538
- children: "string_to_array(auth.roles(), ',') @> ARRAY['admin']"
628
+ children: [
629
+ "string_to_array(",
630
+ RLS_ROLES_SQL,
631
+ ", ',') @> ARRAY['admin']"
632
+ ]
539
633
  })]
540
634
  })] })
541
635
  ]
@@ -729,6 +823,25 @@ var RLSEditor = ({ apiUrl = "" }) => {
729
823
  const [activeTab, setActiveTab] = useState(0);
730
824
  const [junctionTableNames, setJunctionTableNames] = useState(/* @__PURE__ */ new Set());
731
825
  const [editingPolicy, setEditingPolicy] = useState(null);
826
+ /**
827
+ * Native PostgreSQL roles, for the policy editor's `TO` list.
828
+ *
829
+ * `fetchAvailableRoles` and not `fetchApplicationRoles`: a policy's `TO`
830
+ * clause names database roles, and an application role put there produces a
831
+ * policy that either fails to create or matches nothing. The editor falls
832
+ * back to a static pair when the driver cannot answer.
833
+ */
834
+ const [pgRoleOptions, setPgRoleOptions] = useState([]);
835
+ useEffect(() => {
836
+ let mounted = true;
837
+ if (!databaseAdmin?.fetchAvailableRoles) return;
838
+ databaseAdmin.fetchAvailableRoles().then((roles) => {
839
+ if (mounted) setPgRoleOptions(roles);
840
+ }).catch((e) => console.error("Failed to fetch database roles", e));
841
+ return () => {
842
+ mounted = false;
843
+ };
844
+ }, [databaseAdmin]);
732
845
  const [sidebarSize, setSidebarSize] = useState(() => {
733
846
  try {
734
847
  const saved = localStorage.getItem("rebase_rls_editor_sidebar_size");
@@ -899,7 +1012,7 @@ var RLSEditor = ({ apiUrl = "" }) => {
899
1012
  tablename: activeTableData.tableName,
900
1013
  permissive: (rule.mode || "permissive").toUpperCase(),
901
1014
  cmd: (ops[opIdx] ?? rule.operation ?? "ALL").toUpperCase(),
902
- roles: [...rule.roles ?? ["public"]],
1015
+ roles: [...rule.pgRoles ?? ["public"]],
903
1016
  qual: rule.using || null,
904
1017
  with_check: rule.withCheck || null,
905
1018
  status: policiesMap[policyName] ? "both" : "code_only"
@@ -968,7 +1081,7 @@ var RLSEditor = ({ apiUrl = "" }) => {
968
1081
  className: cls("flex items-center justify-between px-3 py-2 border-b bg-surface-50 dark:bg-surface-900 min-h-[48px]", defaultBorderMixin),
969
1082
  children: [/* @__PURE__ */ jsx(Typography, {
970
1083
  variant: "caption",
971
- className: "font-bold uppercase tracking-wider text-text-disabled dark:text-text-disabled-dark",
1084
+ className: "font-semibold uppercase tracking-wider text-text-disabled dark:text-text-disabled-dark",
972
1085
  children: t("studio_schema_tables")
973
1086
  }), /* @__PURE__ */ jsx(IconButton, {
974
1087
  size: "small",
@@ -1060,7 +1173,7 @@ var RLSEditor = ({ apiUrl = "" }) => {
1060
1173
  className: cls("flex items-center justify-between px-3 py-2 border-b bg-surface-50 dark:bg-surface-900 min-h-[48px]", defaultBorderMixin),
1061
1174
  children: /* @__PURE__ */ jsx(Typography, {
1062
1175
  variant: "caption",
1063
- className: "font-bold uppercase tracking-wider text-text-disabled dark:text-text-disabled-dark",
1176
+ className: "font-semibold uppercase tracking-wider text-text-disabled dark:text-text-disabled-dark",
1064
1177
  children: "Overview"
1065
1178
  })
1066
1179
  }), /* @__PURE__ */ jsxs("div", {
@@ -1285,6 +1398,7 @@ var RLSEditor = ({ apiUrl = "" }) => {
1285
1398
  policy: editingPolicy === "new" ? void 0 : editingPolicy,
1286
1399
  schema: activeTableData.schemaName,
1287
1400
  table: activeTableData.tableName,
1401
+ roleOptions: pgRoleOptions,
1288
1402
  onSave: async (newPolicy) => {
1289
1403
  if (activeCollection && hasCodebase) {
1290
1404
  const rule = {
@@ -1293,7 +1407,7 @@ var RLSEditor = ({ apiUrl = "" }) => {
1293
1407
  mode: newPolicy.permissive?.toLowerCase(),
1294
1408
  using: newPolicy.qual || void 0,
1295
1409
  withCheck: newPolicy.with_check || void 0,
1296
- roles: newPolicy.roles
1410
+ ...newPolicy.roles && !(newPolicy.roles.length === 1 && newPolicy.roles[0] === "public") ? { pgRoles: newPolicy.roles } : {}
1297
1411
  };
1298
1412
  const existingRules = (isPostgresCollectionConfig(activeCollection) ? activeCollection.securityRules : void 0) || [];
1299
1413
  let newRules;
@@ -1595,4 +1709,4 @@ var RLSEditor = ({ apiUrl = "" }) => {
1595
1709
  //#endregion
1596
1710
  export { RLSEditor };
1597
1711
 
1598
- //# sourceMappingURL=RLSEditor-CV8R1G3W.js.map
1712
+ //# sourceMappingURL=RLSEditor-CBX4uD66.js.map