@revealui/core 0.5.0 → 0.5.3

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 CHANGED
@@ -145,6 +145,20 @@ pnpm test
145
145
  pnpm dev
146
146
  ```
147
147
 
148
+ ## When to Use This
149
+
150
+ - You're building a content-driven app and need collections, admin UI, and CRUD out of the box
151
+ - You need RBAC/ABAC access control, GDPR compliance, or feature gating by license tier
152
+ - You want a rich text editor (Lexical) integrated with your CMS
153
+ - **Not** for standalone UI components — use `@revealui/presentation`
154
+ - **Not** for raw database queries — use `@revealui/db` directly
155
+
156
+ ## JOSHUA Alignment
157
+
158
+ - **Sovereign**: Self-hosted CMS engine — no SaaS dependency for content management, auth, or storage
159
+ - **Unified**: One `buildConfig()` call wires collections, globals, plugins, security, and feature gates into a single configuration
160
+ - **Adaptive**: Plugin system and tier-based feature gating let the platform evolve without breaking existing deployments
161
+
148
162
  ## Related
149
163
 
150
164
  - [Contracts Package](../contracts/README.md) — Zod schemas and TypeScript types
@@ -3,5 +3,5 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import Head from 'next/head';
4
4
  import { ServerFunctionProvider } from './context/ServerFunctionContext.js';
5
5
  export function RootLayout({ children, serverFunction }) {
6
- return (_jsxs("html", { lang: "en", children: [_jsxs(Head, { children: [_jsx("meta", { charSet: "utf-8" }), _jsx("meta", { name: "viewport", content: "width=device-width, initial-scale=1" }), _jsx("title", { children: "RevealUI Admin" })] }), _jsx("body", { className: "antialiased", children: _jsx(ServerFunctionProvider, { serverFunction: serverFunction, children: _jsx("div", { id: "revealui-admin", className: "min-h-screen", children: children }) }) })] }));
6
+ return (_jsxs("html", { lang: "en", children: [_jsxs(Head, { children: [_jsx("meta", { charSet: "utf-8" }), _jsx("meta", { name: "viewport", content: "width=device-width, initial-scale=1" }), _jsx("title", { children: "RevealUI Admin" })] }), _jsx("body", { className: "antialiased", children: _jsx(ServerFunctionProvider, { serverFunction: serverFunction, children: _jsx("main", { id: "revealui-admin", className: "min-h-screen", children: children }) }) })] }));
7
7
  }
@@ -1 +1 @@
1
- {"version":3,"file":"sqlAdapter.d.ts","sourceRoot":"","sources":["../../../src/collections/operations/sqlAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D,MAAM,MAAM,qBAAqB,GAAG;IAClC,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;CACvE,CAAC;AAgBF,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAM/C;AAKD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAMvD;AAED,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAG1D;AAED,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAIpF;AAED,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,MAAM,EACrB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,GAClB,MAAM,CAER;AAED,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,CAKjF;AAED,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE9D;AAED,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE/D;AAED,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,CAI1E;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,CAKrF"}
1
+ {"version":3,"file":"sqlAdapter.d.ts","sourceRoot":"","sources":["../../../src/collections/operations/sqlAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D,MAAM,MAAM,qBAAqB,GAAG;IAClC,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;CACvE,CAAC;AA6BF,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAM/C;AAkBD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAMvD;AAED,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAG1D;AAED,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAIpF;AAED,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,MAAM,EACrB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,GAClB,MAAM,CAER;AAED,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,CAKjF;AAED,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE9D;AAED,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE/D;AAED,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,CAI1E;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,CAKrF"}
@@ -9,21 +9,53 @@
9
9
  * redesign the collection storage layer toward typed tables instead.
10
10
  */
11
11
  /** Only lowercase alphanumeric, hyphens, and underscores (1-63 chars, PostgreSQL identifier limit). */
12
- const VALID_SLUG = /^[a-z][a-z0-9_-]{0,62}$/;
12
+ function isValidSlug(s) {
13
+ if (s.length < 1 || s.length > 63)
14
+ return false;
15
+ // First char must be lowercase letter
16
+ const first = s.charCodeAt(0);
17
+ if (first < 97 || first > 122)
18
+ return false;
19
+ for (let i = 1; i < s.length; i++) {
20
+ const c = s.charCodeAt(i);
21
+ const isLower = c >= 97 && c <= 122;
22
+ const isDigit = c >= 48 && c <= 57;
23
+ // underscore = 95, hyphen = 45
24
+ if (!(isLower || isDigit || c === 95 || c === 45))
25
+ return false;
26
+ }
27
+ return true;
28
+ }
13
29
  export function validateSlug(slug) {
14
- if (!VALID_SLUG.test(slug)) {
30
+ if (!isValidSlug(slug)) {
15
31
  throw new Error(`Invalid collection slug: "${slug}". Slugs must start with a lowercase letter and contain only lowercase alphanumeric characters, hyphens, and underscores (max 63 chars).`);
16
32
  }
17
33
  }
18
34
  /** Only lowercase alphanumeric and underscores (PostgreSQL column name safe). */
19
- const VALID_COLUMN = /^[a-z_][a-z0-9_]{0,62}$/;
35
+ function isValidColumnName(s) {
36
+ if (s.length < 1 || s.length > 63)
37
+ return false;
38
+ // First char must be lowercase letter or underscore
39
+ const first = s.charCodeAt(0);
40
+ const firstIsLower = first >= 97 && first <= 122;
41
+ if (!(firstIsLower || first === 95))
42
+ return false;
43
+ for (let i = 1; i < s.length; i++) {
44
+ const c = s.charCodeAt(i);
45
+ const isLower = c >= 97 && c <= 122;
46
+ const isDigit = c >= 48 && c <= 57;
47
+ if (!(isLower || isDigit || c === 95))
48
+ return false;
49
+ }
50
+ return true;
51
+ }
20
52
  export function validateColumnName(column) {
21
- if (!VALID_COLUMN.test(column)) {
53
+ if (!isValidColumnName(column)) {
22
54
  throw new Error(`Invalid column name: "${column}". Column names must start with a lowercase letter or underscore and contain only lowercase alphanumeric characters and underscores.`);
23
55
  }
24
56
  }
25
57
  export function escapeIdentifier(identifier) {
26
- return identifier.replace(/"/g, '""');
58
+ return identifier.split('"').join('""');
27
59
  }
28
60
  export function collectionTable(configSlug) {
29
61
  validateSlug(configSlug);
@@ -43,6 +43,12 @@ export interface FeatureFlags {
43
43
  customDomain: boolean;
44
44
  /** Analytics and conversion tracking */
45
45
  analytics: boolean;
46
+ /** RevVault desktop app — Tauri companion for encrypted secret management (Pro+) */
47
+ vaultDesktop: boolean;
48
+ /** RevVault rotation engine — automated credential lifecycle (Pro+) */
49
+ vaultRotation: boolean;
50
+ /** RevKit environment provisioning — tiered dev profiles (Max+) */
51
+ devkitProfiles: boolean;
46
52
  }
47
53
  /**
48
54
  * Returns the current feature flags based on the active license tier.
@@ -1 +1 @@
1
- {"version":3,"file":"features.d.ts","sourceRoot":"","sources":["../src/features.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAc,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAE5D,qCAAqC;AACrC,MAAM,WAAW,YAAY;IAC3B,iFAAiF;IACjF,OAAO,EAAE,OAAO,CAAC;IACjB,yEAAyE;IACzE,EAAE,EAAE,OAAO,CAAC;IACZ,oFAAoF;IACpF,QAAQ,EAAE,OAAO,CAAC;IAClB,6BAA6B;IAC7B,GAAG,EAAE,OAAO,CAAC;IACb,yCAAyC;IACzC,QAAQ,EAAE,OAAO,CAAC;IAClB,mCAAmC;IACnC,WAAW,EAAE,OAAO,CAAC;IACrB,kEAAkE;IAClE,UAAU,EAAE,OAAO,CAAC;IACpB,8DAA8D;IAC9D,GAAG,EAAE,OAAO,CAAC;IACb,0CAA0C;IAC1C,cAAc,EAAE,OAAO,CAAC;IACxB,uEAAuE;IACvE,eAAe,EAAE,OAAO,CAAC;IACzB,yCAAyC;IACzC,QAAQ,EAAE,OAAO,CAAC;IAClB,wEAAwE;IACxE,UAAU,EAAE,OAAO,CAAC;IACpB,mDAAmD;IACnD,YAAY,EAAE,OAAO,CAAC;IACtB,2BAA2B;IAC3B,SAAS,EAAE,OAAO,CAAC;IACnB,4BAA4B;IAC5B,YAAY,EAAE,OAAO,CAAC;IACtB,wCAAwC;IACxC,SAAS,EAAE,OAAO,CAAC;CACpB;AAsBD;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,IAAI,YAAY,CAQ1C;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,YAAY,GAAG,OAAO,CAGrE;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,WAAW,GAAG,YAAY,CAelE;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,YAAY,GAAG,WAAW,CAExE;AAED;;GAEG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"features.d.ts","sourceRoot":"","sources":["../src/features.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAc,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAE5D,qCAAqC;AACrC,MAAM,WAAW,YAAY;IAC3B,iFAAiF;IACjF,OAAO,EAAE,OAAO,CAAC;IACjB,yEAAyE;IACzE,EAAE,EAAE,OAAO,CAAC;IACZ,oFAAoF;IACpF,QAAQ,EAAE,OAAO,CAAC;IAClB,6BAA6B;IAC7B,GAAG,EAAE,OAAO,CAAC;IACb,yCAAyC;IACzC,QAAQ,EAAE,OAAO,CAAC;IAClB,mCAAmC;IACnC,WAAW,EAAE,OAAO,CAAC;IACrB,kEAAkE;IAClE,UAAU,EAAE,OAAO,CAAC;IACpB,8DAA8D;IAC9D,GAAG,EAAE,OAAO,CAAC;IACb,0CAA0C;IAC1C,cAAc,EAAE,OAAO,CAAC;IACxB,uEAAuE;IACvE,eAAe,EAAE,OAAO,CAAC;IACzB,yCAAyC;IACzC,QAAQ,EAAE,OAAO,CAAC;IAClB,wEAAwE;IACxE,UAAU,EAAE,OAAO,CAAC;IACpB,mDAAmD;IACnD,YAAY,EAAE,OAAO,CAAC;IACtB,2BAA2B;IAC3B,SAAS,EAAE,OAAO,CAAC;IACnB,4BAA4B;IAC5B,YAAY,EAAE,OAAO,CAAC;IACtB,wCAAwC;IACxC,SAAS,EAAE,OAAO,CAAC;IACnB,oFAAoF;IACpF,YAAY,EAAE,OAAO,CAAC;IACtB,uEAAuE;IACvE,aAAa,EAAE,OAAO,CAAC;IACvB,mEAAmE;IACnE,cAAc,EAAE,OAAO,CAAC;CACzB;AA4BD;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,IAAI,YAAY,CAY1C;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,YAAY,GAAG,OAAO,CAMrE;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,WAAW,GAAG,YAAY,CAmBlE;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,YAAY,GAAG,WAAW,CAExE;AAED;;GAEG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC"}
package/dist/features.js CHANGED
@@ -25,8 +25,14 @@ const featureTierMap = {
25
25
  aiMultiProvider: 'max',
26
26
  auditLog: 'max',
27
27
  multiTenant: 'enterprise',
28
+ // NOTE: whiteLabel and sso are planned but not yet implemented.
29
+ // Forced to false below in getFeatures/getFeaturesForTier/isFeatureEnabled
30
+ // to avoid advertising features that don't exist. Re-enable when implemented.
28
31
  whiteLabel: 'enterprise',
29
32
  sso: 'enterprise',
33
+ vaultDesktop: 'pro',
34
+ vaultRotation: 'pro',
35
+ devkitProfiles: 'max',
30
36
  };
31
37
  /**
32
38
  * Returns the current feature flags based on the active license tier.
@@ -46,6 +52,9 @@ export function getFeatures() {
46
52
  for (const [feature, requiredTier] of Object.entries(featureTierMap)) {
47
53
  flags[feature] = isLicensed(requiredTier);
48
54
  }
55
+ // Planned but not yet implemented — force false to avoid false advertising
56
+ flags.whiteLabel = false;
57
+ flags.sso = false;
49
58
  return flags;
50
59
  }
51
60
  /**
@@ -61,6 +70,9 @@ export function getFeatures() {
61
70
  * ```
62
71
  */
63
72
  export function isFeatureEnabled(feature) {
73
+ // Planned but not yet implemented — always return false
74
+ if (feature === 'whiteLabel' || feature === 'sso')
75
+ return false;
64
76
  const requiredTier = featureTierMap[feature];
65
77
  return isLicensed(requiredTier);
66
78
  }
@@ -78,6 +90,9 @@ export function getFeaturesForTier(tier) {
78
90
  for (const [feature, requiredTier] of Object.entries(featureTierMap)) {
79
91
  flags[feature] = tierRank[tier] >= tierRank[requiredTier];
80
92
  }
93
+ // Planned but not yet implemented — force false to avoid false advertising
94
+ flags.whiteLabel = false;
95
+ flags.sso = false;
81
96
  return flags;
82
97
  }
83
98
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"GlobalOperations.d.ts","sourceRoot":"","sources":["../../src/globals/GlobalOperations.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,cAAc,EACd,cAAc,EACd,kBAAkB,EAClB,aAAa,EAEd,MAAM,mBAAmB,CAAC;AAG3B;;;;GAIG;AACH,qBAAa,cAAc;IACzB,MAAM,EAAE,kBAAkB,CAAC;IAC3B,EAAE,EAAE;QACF,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;KACvE,GAAG,IAAI,CAAC;gBAGP,MAAM,EAAE,kBAAkB,EAC1B,EAAE,EAAE;QACF,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;KACvE,GAAG,IAAI;IAMJ,IAAI,CACR,OAAO,GAAE;QACP,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,QAAQ,CAAC,EAAE,OAAO,mBAAmB,EAAE,YAAY,CAAC;QACpD,GAAG,CAAC,EAAE,aAAa,CAAC;KAChB,GACL,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IAsG3B,MAAM,CAAC,OAAO,EAAE;QAAE,IAAI,EAAE,OAAO,CAAC,cAAc,CAAC,CAAA;KAAE,GAAG,OAAO,CAAC,cAAc,CAAC;CAqElF"}
1
+ {"version":3,"file":"GlobalOperations.d.ts","sourceRoot":"","sources":["../../src/globals/GlobalOperations.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EACV,cAAc,EACd,cAAc,EACd,kBAAkB,EAClB,aAAa,EAEd,MAAM,mBAAmB,CAAC;AAG3B;;;;GAIG;AACH,qBAAa,cAAc;IACzB,MAAM,EAAE,kBAAkB,CAAC;IAC3B,EAAE,EAAE;QACF,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;KACvE,GAAG,IAAI,CAAC;gBAGP,MAAM,EAAE,kBAAkB,EAC1B,EAAE,EAAE;QACF,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;KACvE,GAAG,IAAI;IAMJ,IAAI,CACR,OAAO,GAAE;QACP,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,QAAQ,CAAC,EAAE,OAAO,mBAAmB,EAAE,YAAY,CAAC;QACpD,GAAG,CAAC,EAAE,aAAa,CAAC;KAChB,GACL,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IAwG3B,MAAM,CAAC,OAAO,EAAE;QAAE,IAAI,EAAE,OAAO,CAAC,cAAc,CAAC,CAAA;KAAE,GAAG,OAAO,CAAC,cAAc,CAAC;CAqElF"}
@@ -1,4 +1,4 @@
1
- import { validateColumnName, validateSlug } from '../collections/operations/sqlAdapter.js';
1
+ import { escapeIdentifier, validateColumnName, validateSlug, } from '../collections/operations/sqlAdapter.js';
2
2
  import { afterRead } from '../fields/hooks/afterRead/index.js';
3
3
  import { getRelationshipFields } from '../relationships/analyzer.js';
4
4
  import { flattenResult } from '../utils/flattenResult.js';
@@ -22,7 +22,10 @@ export class RevealUIGlobal {
22
22
  }
23
23
  if (this.db?.query) {
24
24
  const slug = this.config.slug;
25
- if (!/^[a-z][a-z0-9_-]{0,62}$/.test(slug)) {
25
+ try {
26
+ validateSlug(slug);
27
+ }
28
+ catch {
26
29
  throw new Error(`Invalid global slug: "${slug}". Must be lowercase alphanumeric with hyphens/underscores.`);
27
30
  }
28
31
  const tableName = `global_${slug}`;
@@ -101,7 +104,10 @@ export class RevealUIGlobal {
101
104
  const { data } = options;
102
105
  if (this.db?.query) {
103
106
  const slug = this.config.slug;
104
- if (!/^[a-z][a-z0-9_-]{0,62}$/.test(slug)) {
107
+ try {
108
+ validateSlug(slug);
109
+ }
110
+ catch {
105
111
  throw new Error(`Invalid global slug: "${slug}". Must be lowercase alphanumeric with hyphens/underscores.`);
106
112
  }
107
113
  const tableName = `global_${slug}`;
@@ -114,9 +120,7 @@ export class RevealUIGlobal {
114
120
  const keys = Object.keys(data);
115
121
  for (const key of keys)
116
122
  validateColumnName(key);
117
- const setClause = keys
118
- .map((key, i) => `"${key.replace(/"/g, '""')}" = $${i + 1}`)
119
- .join(', ');
123
+ const setClause = keys.map((key, i) => `"${escapeIdentifier(key)}" = $${i + 1}`).join(', ');
120
124
  const values = keys.map((key) => {
121
125
  const value = data[key];
122
126
  // Serialize non-primitive values to JSON strings for SQLite compatibility
@@ -147,7 +151,7 @@ export class RevealUIGlobal {
147
151
  }
148
152
  return value;
149
153
  });
150
- const query = `INSERT INTO "${tableName}" (id, ${columns.map((c) => `"${c.replace(/"/g, '""')}"`).join(', ')}) VALUES ($1, ${placeholders})`;
154
+ const query = `INSERT INTO "${tableName}" (id, ${columns.map((c) => `"${escapeIdentifier(c)}"`).join(', ')}) VALUES ($1, ${placeholders})`;
151
155
  await this.db.query(query, [id, ...values]);
152
156
  }
153
157
  const updatedDoc = await this.find();
@@ -1 +1 @@
1
- {"version":3,"file":"queryBuilder.d.ts","sourceRoot":"","sources":["../../src/queries/queryBuilder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAExE;;;;;GAKG;AAEH,MAAM,MAAM,cAAc,GAAG,UAAU,GAAG,YAAY,CAAC;AAEvD;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;OAGG;IACH,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC;;;OAGG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,WAAW,GAAG,iBAAiB,CAAC,OAAO,CAAC,GAAG,SAAS,EAC3D,MAAM,EAAE,OAAO,EAAE,EACjB,OAAO,GAAE,iBAAsB,GAC9B,MAAM,CA0NR;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,CAAC,EAAE,WAAW,GAAG,OAAO,EAAE,CAoEjE"}
1
+ {"version":3,"file":"queryBuilder.d.ts","sourceRoot":"","sources":["../../src/queries/queryBuilder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAExE;;;;;GAKG;AAEH,MAAM,MAAM,cAAc,GAAG,UAAU,GAAG,YAAY,CAAC;AAevD;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;OAGG;IACH,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC;;;OAGG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,WAAW,GAAG,iBAAiB,CAAC,OAAO,CAAC,GAAG,SAAS,EAC3D,MAAM,EAAE,OAAO,EAAE,EACjB,OAAO,GAAE,iBAAsB,GAC9B,MAAM,CA0NR;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,CAAC,EAAE,WAAW,GAAG,OAAO,EAAE,CAoEjE"}
@@ -1,3 +1,16 @@
1
+ /** Escape SQL LIKE wildcards (%, _, \) in user input */
2
+ function escapeLikeWildcards(value) {
3
+ let result = '';
4
+ for (const ch of value) {
5
+ if (ch === '%' || ch === '_' || ch === '\\') {
6
+ result += `\\${ch}`;
7
+ }
8
+ else {
9
+ result += ch;
10
+ }
11
+ }
12
+ return result;
13
+ }
1
14
  /**
2
15
  * Builds a WHERE clause from a RevealWhere query object.
3
16
  * Supports nested AND/OR conditions and various operators.
@@ -27,7 +40,7 @@ export function buildWhereClause(where, params, options = {}) {
27
40
  if (!quoteFields)
28
41
  return field;
29
42
  // Escape embedded double quotes to prevent SQL injection via identifier breakout
30
- const escaped = field.replace(/"/g, '""');
43
+ const escaped = field.split('"').join('""');
31
44
  return `"${escaped}"`;
32
45
  };
33
46
  const whereWithGroups = where;
@@ -149,7 +162,7 @@ export function buildWhereClause(where, params, options = {}) {
149
162
  if ('contains' in condition && typeof condition.contains === 'string') {
150
163
  const placeholder = getPlaceholder();
151
164
  // Escape LIKE wildcards (% and _) in user input to prevent wildcard injection
152
- const escaped = condition.contains.replace(/[%_\\]/g, '\\$&');
165
+ const escaped = escapeLikeWildcards(condition.contains);
153
166
  params.push(`%${escaped}%`);
154
167
  conditions.push(`${quotedField} LIKE ${placeholder} ESCAPE '\\'`);
155
168
  }
@@ -168,7 +181,7 @@ export function buildWhereClause(where, params, options = {}) {
168
181
  // like (escape wildcards to prevent blind LIKE probing)
169
182
  if ('like' in condition && typeof condition.like === 'string') {
170
183
  const placeholder = getPlaceholder();
171
- const escaped = condition.like.replace(/[%_\\]/g, '\\$&');
184
+ const escaped = escapeLikeWildcards(condition.like);
172
185
  params.push(escaped);
173
186
  conditions.push(`${quotedField} LIKE ${placeholder} ESCAPE '\\'`);
174
187
  }
@@ -219,7 +232,7 @@ export function extractWhereValues(where) {
219
232
  break;
220
233
  case 'contains':
221
234
  if (typeof value === 'string') {
222
- const escaped = value.replace(/[%_\\]/g, '\\$&');
235
+ const escaped = escapeLikeWildcards(value);
223
236
  values.push(`%${escaped}%`);
224
237
  }
225
238
  break;
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@revealui/core",
3
- "version": "0.5.0",
3
+ "version": "0.5.3",
4
+ "description": "CMS engine, REST API, auth, rich text, admin UI, and plugins for RevealUI",
4
5
  "license": "MIT",
5
6
  "dependencies": {
6
7
  "@electric-sql/pglite": "^0.4.2",
@@ -23,11 +24,11 @@
23
24
  "pg": "^8.18.0",
24
25
  "yjs": "^13.6.29",
25
26
  "zod": "^4.3.6",
26
- "@revealui/cache": "0.1.0",
27
- "@revealui/contracts": "1.3.1",
28
- "@revealui/resilience": "0.2.0",
29
- "@revealui/security": "0.2.1",
30
- "@revealui/utils": "0.3.0"
27
+ "@revealui/cache": "0.1.1",
28
+ "@revealui/contracts": "1.3.4",
29
+ "@revealui/resilience": "0.2.1",
30
+ "@revealui/security": "0.2.4",
31
+ "@revealui/utils": "0.3.1"
31
32
  },
32
33
  "devDependencies": {
33
34
  "@types/json-schema": "^7.0.15",