@revealui/core 0.7.0 → 0.9.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 (54) hide show
  1. package/README.md +50 -6
  2. package/dist/collections/operations/delete.d.ts +2 -4
  3. package/dist/collections/operations/delete.d.ts.map +1 -1
  4. package/dist/collections/operations/delete.js +21 -4
  5. package/dist/collections/operations/findById.d.ts.map +1 -1
  6. package/dist/collections/operations/findById.js +18 -3
  7. package/dist/collections/operations/sqlAdapter.d.ts.map +1 -1
  8. package/dist/collections/operations/sqlAdapter.js +7 -1
  9. package/dist/collections/operations/update.d.ts.map +1 -1
  10. package/dist/collections/operations/update.js +21 -4
  11. package/dist/index.d.ts +1 -1
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +1 -1
  14. package/dist/license.d.ts +20 -0
  15. package/dist/license.d.ts.map +1 -1
  16. package/dist/license.js +30 -0
  17. package/dist/observability/health-check.d.ts.map +1 -1
  18. package/dist/observability/health-check.js +8 -1
  19. package/dist/observability/metrics.d.ts +6 -16
  20. package/dist/observability/metrics.d.ts.map +1 -1
  21. package/dist/observability/metrics.js +5 -10
  22. package/dist/richtext/exports/server/rsc.d.ts +1 -1
  23. package/dist/richtext/exports/server/rsc.d.ts.map +1 -1
  24. package/dist/richtext/exports/server/rsc.js +6 -1
  25. package/dist/security/index.d.ts +9 -2
  26. package/dist/security/index.d.ts.map +1 -1
  27. package/dist/security/index.js +9 -2
  28. package/dist/server/index.d.ts +1 -1
  29. package/dist/server/index.d.ts.map +1 -1
  30. package/dist/server/index.js +1 -1
  31. package/dist/storage/_helpers.d.ts +14 -0
  32. package/dist/storage/_helpers.d.ts.map +1 -0
  33. package/dist/storage/_helpers.js +40 -0
  34. package/dist/storage/index.d.ts +18 -1
  35. package/dist/storage/index.d.ts.map +1 -1
  36. package/dist/storage/index.js +46 -2
  37. package/dist/storage/mock.d.ts +39 -0
  38. package/dist/storage/mock.d.ts.map +1 -0
  39. package/dist/storage/mock.js +91 -0
  40. package/dist/storage/object-storage.d.ts +41 -0
  41. package/dist/storage/object-storage.d.ts.map +1 -0
  42. package/dist/storage/{vercel-blob.js → object-storage.js} +38 -18
  43. package/dist/storage/r2.d.ts +13 -0
  44. package/dist/storage/r2.d.ts.map +1 -0
  45. package/dist/storage/r2.js +117 -0
  46. package/dist/storage/types.d.ts +110 -0
  47. package/dist/storage/types.d.ts.map +1 -0
  48. package/dist/storage/types.js +16 -0
  49. package/dist/storage/vercel-blob-provider.d.ts +28 -0
  50. package/dist/storage/vercel-blob-provider.d.ts.map +1 -0
  51. package/dist/storage/vercel-blob-provider.js +88 -0
  52. package/package.json +8 -7
  53. package/dist/storage/vercel-blob.d.ts +0 -17
  54. package/dist/storage/vercel-blob.d.ts.map +0 -1
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Provider-agnostic storage interface for the RevealUI fleet.
3
+ *
4
+ * Per `feedback_revealui_agnosticism_principle` + `feedback_pluggable_provider_pattern`:
5
+ * slim interface, tag + factory, explicit "no-X" opt-in, mock provider for tests.
6
+ *
7
+ * Providers implement this interface; consumers (apps/server media routes,
8
+ * apps/admin health probes, packages/core Payload-style plugin shims) depend
9
+ * only on this contract. Switching providers becomes a config change.
10
+ *
11
+ * GAP-208 introduces this abstraction during the Vercel Blob → Cloudflare R2
12
+ * swap (2026-05-18). The vercel-blob Payload-style plugin (vercel-blob.ts)
13
+ * stays in place during migration for backward compat; once consumers migrate
14
+ * to the interface, the legacy plugin is dropped.
15
+ */
16
+ export interface StorageProvider {
17
+ /** Upload binary data. Returns a fully-qualified URL the value is reachable at. */
18
+ put(key: string, data: Blob | ArrayBuffer | Uint8Array | ReadableStream<Uint8Array>, opts?: PutOptions): Promise<PutResult>;
19
+ /** Delete by either provider URL or storage key. Best-effort; missing keys are not errors. */
20
+ del(keyOrUrl: string): Promise<void>;
21
+ /** List items, optionally filtered by key prefix. Used by health probes + admin browsing. */
22
+ list(opts?: ListOptions): Promise<ListResult>;
23
+ /** Provider tag (e.g. "r2", "vercel-blob", "mock") — exposed so consumers can adapt URL handling. */
24
+ readonly provider: string;
25
+ }
26
+ export interface PutOptions {
27
+ /** MIME type — written to the object's Content-Type header. Defaults to "application/octet-stream". */
28
+ contentType?: string;
29
+ /**
30
+ * Public read access (no auth required for GET on the returned URL).
31
+ * Defaults to "public" because the current media-upload flow needs public CDN URLs.
32
+ * Providers that don't support public access (e.g. private-bucket-only configs)
33
+ * MUST throw when access: "public" is requested.
34
+ */
35
+ access?: 'public' | 'private';
36
+ /** Cache-Control header value. Provider may apply a sensible default if omitted. */
37
+ cacheControl?: string;
38
+ /** Custom user-metadata. Keys are lowercased; values are strings. Limits vary by provider. */
39
+ metadata?: Record<string, string>;
40
+ }
41
+ export interface PutResult {
42
+ /** The storage key the object was written to (provider-relative, e.g. "media/uuid.jpg"). */
43
+ key: string;
44
+ /** Fully-qualified URL the object is reachable at for GET. */
45
+ url: string;
46
+ /** Storage size in bytes. Always populated by every provider. */
47
+ size: number;
48
+ /** Provider tag that wrote this object (matches StorageProvider.provider). */
49
+ provider: string;
50
+ }
51
+ export interface ListOptions {
52
+ /** Filter to keys starting with this prefix. */
53
+ prefix?: string;
54
+ /** Maximum items per page. Providers cap this internally (typically 1000). */
55
+ limit?: number;
56
+ /** Opaque pagination cursor from a previous ListResult. */
57
+ cursor?: string;
58
+ }
59
+ export interface ListResult {
60
+ items: ListItem[];
61
+ /** Next-page cursor if hasMore is true; undefined when listing is exhausted. */
62
+ cursor?: string;
63
+ hasMore: boolean;
64
+ }
65
+ export interface ListItem {
66
+ key: string;
67
+ url: string;
68
+ size: number;
69
+ uploadedAt: Date;
70
+ }
71
+ /**
72
+ * Discriminated union over `provider`. Adding a new provider means:
73
+ * 1. add a tag here + the per-provider config shape
74
+ * 2. add a case in the createStorage factory
75
+ * 3. implement the StorageProvider interface in a new file
76
+ *
77
+ * No silent fallback per `feedback_pluggable_provider_pattern`: unknown tags
78
+ * throw with a clear "valid tags: ..." message.
79
+ */
80
+ export type StorageConfig = {
81
+ provider: 'r2';
82
+ r2: R2Config;
83
+ } | {
84
+ provider: 'vercel-blob';
85
+ vercelBlob: VercelBlobConfig;
86
+ } | {
87
+ provider: 'mock';
88
+ };
89
+ export interface R2Config {
90
+ /** Cloudflare account ID (visible at top of the R2 dashboard). */
91
+ accountId: string;
92
+ /** R2 API token Access Key ID (NOT the global account API token). */
93
+ accessKeyId: string;
94
+ /** R2 API token Secret Access Key (paired with accessKeyId). */
95
+ secretAccessKey: string;
96
+ /** Bucket name (per-account-globally-unique within R2). */
97
+ bucket: string;
98
+ /**
99
+ * Optional public base URL for objects (e.g. "https://media.revealui.com" if a
100
+ * custom domain is bound, or "https://<account-id>.r2.cloudflarestorage.com/<bucket>"
101
+ * for the dev URL). When set, PutResult.url uses this base. When omitted, PutResult.url
102
+ * falls back to a presigned URL (private bucket pattern).
103
+ */
104
+ publicBaseUrl?: string;
105
+ }
106
+ export interface VercelBlobConfig {
107
+ /** vercel_blob_rw_* token from the Vercel Blob dashboard. */
108
+ token: string;
109
+ }
110
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/storage/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,MAAM,WAAW,eAAe;IAC9B,mFAAmF;IACnF,GAAG,CACD,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,IAAI,GAAG,WAAW,GAAG,UAAU,GAAG,cAAc,CAAC,UAAU,CAAC,EAClE,IAAI,CAAC,EAAE,UAAU,GAChB,OAAO,CAAC,SAAS,CAAC,CAAC;IAEtB,8FAA8F;IAC9F,GAAG,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAErC,6FAA6F;IAC7F,IAAI,CAAC,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAE9C,qGAAqG;IACrG,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAID,MAAM,WAAW,UAAU;IACzB,uGAAuG;IACvG,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IAE9B,oFAAoF;IACpF,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB,8FAA8F;IAC9F,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,SAAS;IACxB,4FAA4F;IAC5F,GAAG,EAAE,MAAM,CAAC;IAEZ,8DAA8D;IAC9D,GAAG,EAAE,MAAM,CAAC;IAEZ,iEAAiE;IACjE,IAAI,EAAE,MAAM,CAAC;IAEb,8EAA8E;IAC9E,QAAQ,EAAE,MAAM,CAAC;CAClB;AAID,MAAM,WAAW,WAAW;IAC1B,gDAAgD;IAChD,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,2DAA2D;IAC3D,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,QAAQ,EAAE,CAAC;IAElB,gFAAgF;IAChF,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,QAAQ;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,IAAI,CAAC;CAClB;AAID;;;;;;;;GAQG;AACH,MAAM,MAAM,aAAa,GACrB;IAAE,QAAQ,EAAE,IAAI,CAAC;IAAC,EAAE,EAAE,QAAQ,CAAA;CAAE,GAChC;IAAE,QAAQ,EAAE,aAAa,CAAC;IAAC,UAAU,EAAE,gBAAgB,CAAA;CAAE,GACzD;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzB,MAAM,WAAW,QAAQ;IACvB,kEAAkE;IAClE,SAAS,EAAE,MAAM,CAAC;IAElB,qEAAqE;IACrE,WAAW,EAAE,MAAM,CAAC;IAEpB,gEAAgE;IAChE,eAAe,EAAE,MAAM,CAAC;IAExB,2DAA2D;IAC3D,MAAM,EAAE,MAAM,CAAC;IAEf;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,gBAAgB;IAC/B,6DAA6D;IAC7D,KAAK,EAAE,MAAM,CAAC;CACf"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Provider-agnostic storage interface for the RevealUI fleet.
3
+ *
4
+ * Per `feedback_revealui_agnosticism_principle` + `feedback_pluggable_provider_pattern`:
5
+ * slim interface, tag + factory, explicit "no-X" opt-in, mock provider for tests.
6
+ *
7
+ * Providers implement this interface; consumers (apps/server media routes,
8
+ * apps/admin health probes, packages/core Payload-style plugin shims) depend
9
+ * only on this contract. Switching providers becomes a config change.
10
+ *
11
+ * GAP-208 introduces this abstraction during the Vercel Blob → Cloudflare R2
12
+ * swap (2026-05-18). The vercel-blob Payload-style plugin (vercel-blob.ts)
13
+ * stays in place during migration for backward compat; once consumers migrate
14
+ * to the interface, the legacy plugin is dropped.
15
+ */
16
+ export {};
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Vercel Blob implementation of StorageProvider.
3
+ *
4
+ * Legacy backend retained during the GAP-208 Cloudflare R2 cutover so the
5
+ * media-upload path can fall back to Vercel Blob when R2 is not configured.
6
+ * Wraps @vercel/blob's put/del/list behind the provider-agnostic
7
+ * StorageProvider interface. Cloudflare R2 (r2.ts) is the canonical backend;
8
+ * this provider is removed once the Blob store is decommissioned.
9
+ *
10
+ * NOTE: distinct from vercel-blob.ts in this directory, which is the legacy
11
+ * Payload-style upload *plugin* still wired into apps/admin's
12
+ * revealui.config.ts. This file is the StorageProvider the createStorage
13
+ * factory returns for the 'vercel-blob' tag.
14
+ *
15
+ * Server-only. Do NOT import from client-side code or edge runtime.
16
+ */
17
+ import type { ListOptions, ListResult, PutOptions, PutResult, StorageProvider, VercelBlobConfig } from './types.js';
18
+ declare class VercelBlobProvider implements StorageProvider {
19
+ readonly provider = "vercel-blob";
20
+ private readonly token;
21
+ constructor(config: VercelBlobConfig);
22
+ put(key: string, data: Blob | ArrayBuffer | Uint8Array | ReadableStream<Uint8Array>, opts?: PutOptions): Promise<PutResult>;
23
+ del(keyOrUrl: string): Promise<void>;
24
+ list(opts?: ListOptions): Promise<ListResult>;
25
+ }
26
+ export declare function createVercelBlobProvider(config: VercelBlobConfig): StorageProvider;
27
+ export type { VercelBlobProvider };
28
+ //# sourceMappingURL=vercel-blob-provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vercel-blob-provider.d.ts","sourceRoot":"","sources":["../../src/storage/vercel-blob-provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,OAAO,KAAK,EAEV,WAAW,EACX,UAAU,EACV,UAAU,EACV,SAAS,EACT,eAAe,EACf,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAKpB,cAAM,kBAAmB,YAAW,eAAe;IACjD,QAAQ,CAAC,QAAQ,iBAAgB;IACjC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;gBAEnB,MAAM,EAAE,gBAAgB;IAW9B,GAAG,CACP,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,IAAI,GAAG,WAAW,GAAG,UAAU,GAAG,cAAc,CAAC,UAAU,CAAC,EAClE,IAAI,CAAC,EAAE,UAAU,GAChB,OAAO,CAAC,SAAS,CAAC;IAmCf,GAAG,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOpC,IAAI,CAAC,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC;CAqBpD;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,gBAAgB,GAAG,eAAe,CAElF;AAED,YAAY,EAAE,kBAAkB,EAAE,CAAC"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Vercel Blob implementation of StorageProvider.
3
+ *
4
+ * Legacy backend retained during the GAP-208 Cloudflare R2 cutover so the
5
+ * media-upload path can fall back to Vercel Blob when R2 is not configured.
6
+ * Wraps @vercel/blob's put/del/list behind the provider-agnostic
7
+ * StorageProvider interface. Cloudflare R2 (r2.ts) is the canonical backend;
8
+ * this provider is removed once the Blob store is decommissioned.
9
+ *
10
+ * NOTE: distinct from vercel-blob.ts in this directory, which is the legacy
11
+ * Payload-style upload *plugin* still wired into apps/admin's
12
+ * revealui.config.ts. This file is the StorageProvider the createStorage
13
+ * factory returns for the 'vercel-blob' tag.
14
+ *
15
+ * Server-only. Do NOT import from client-side code or edge runtime.
16
+ */
17
+ import { del, list, put } from '@vercel/blob';
18
+ import { toUint8Array } from './_helpers.js';
19
+ const PROVIDER_TAG = 'vercel-blob';
20
+ const DEFAULT_LIST_LIMIT = 1000;
21
+ class VercelBlobProvider {
22
+ provider = PROVIDER_TAG;
23
+ token;
24
+ constructor(config) {
25
+ if (!config.token) {
26
+ throw new Error('Vercel Blob provider requires VercelBlobConfig.token (the ' +
27
+ 'BLOB_READ_WRITE_TOKEN value). For new deployments prefer Cloudflare ' +
28
+ 'R2 (provider: "r2").');
29
+ }
30
+ this.token = config.token;
31
+ }
32
+ async put(key, data, opts) {
33
+ if (opts?.access === 'private') {
34
+ throw new Error("Vercel Blob provider does not support access: 'private' — every blob " +
35
+ 'is publicly readable. Use Cloudflare R2 for private-access patterns.');
36
+ }
37
+ // Normalize to bytes so the result carries an accurate size regardless of
38
+ // input shape (ReadableStream has no length until consumed). Re-wrap in a
39
+ // fresh Uint8Array so the Blob part is backed by a non-shared ArrayBuffer,
40
+ // satisfying the DOM BlobPart type under TS strict typed-array generics.
41
+ const body = await toUint8Array(data);
42
+ const result = await put(key, new Blob([new Uint8Array(body)]), {
43
+ access: 'public',
44
+ token: this.token,
45
+ contentType: opts?.contentType,
46
+ // Keys are caller-controlled (callers pre-randomize, e.g. media/<uuid>.ext);
47
+ // never append a second random suffix or the returned URL won't match `key`.
48
+ addRandomSuffix: false,
49
+ });
50
+ // Vercel Blob has no per-object user-metadata or Cache-Control-header API
51
+ // (only cacheControlMaxAge); opts.metadata / opts.cacheControl are ignored
52
+ // by this legacy provider. R2 honors both.
53
+ return {
54
+ key,
55
+ url: result.url,
56
+ size: body.byteLength,
57
+ provider: PROVIDER_TAG,
58
+ };
59
+ }
60
+ async del(keyOrUrl) {
61
+ // Vercel's del is idempotent (deleting an absent blob is a no-op), so this
62
+ // satisfies the best-effort contract. It requires the full blob URL, which
63
+ // is what media records persist.
64
+ await del(keyOrUrl, { token: this.token });
65
+ }
66
+ async list(opts) {
67
+ const response = await list({
68
+ token: this.token,
69
+ prefix: opts?.prefix,
70
+ limit: opts?.limit ?? DEFAULT_LIST_LIMIT,
71
+ cursor: opts?.cursor,
72
+ });
73
+ const items = response.blobs.map((blob) => ({
74
+ key: blob.pathname,
75
+ url: blob.url,
76
+ size: blob.size,
77
+ uploadedAt: blob.uploadedAt,
78
+ }));
79
+ return {
80
+ items,
81
+ cursor: response.cursor,
82
+ hasMore: response.hasMore,
83
+ };
84
+ }
85
+ }
86
+ export function createVercelBlobProvider(config) {
87
+ return new VercelBlobProvider(config);
88
+ }
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@revealui/core",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "description": "RevealUI's runtime engine — admin UI, REST API, auth, rich text, plugin system, and access control. The heart of the open-source platform.",
5
5
  "license": "MIT",
6
6
  "dependencies": {
7
- "@electric-sql/pglite": "^0.4.4",
7
+ "@aws-sdk/client-s3": "^3.1053.0",
8
+ "@electric-sql/pglite": "^0.4.5",
8
9
  "@lexical/clipboard": "^0.44.0",
9
10
  "@lexical/code": "^0.44.0",
10
11
  "@lexical/html": "^0.44.0",
@@ -21,19 +22,19 @@
21
22
  "dataloader": "^2.2.3",
22
23
  "jose": "^6.2.3",
23
24
  "lexical": "^0.44.0",
24
- "pg": "^8.20.0",
25
+ "pg": "^8.21.0",
25
26
  "yjs": "^13.6.30",
26
27
  "zod": "^4.4.3",
27
- "@revealui/cache": "0.1.5",
28
- "@revealui/contracts": "0.5.0",
28
+ "@revealui/cache": "0.2.1",
29
+ "@revealui/contracts": "0.6.0",
29
30
  "@revealui/resilience": "0.2.4",
30
- "@revealui/security": "0.3.1",
31
+ "@revealui/security": "0.4.0",
31
32
  "@revealui/utils": "0.3.5"
32
33
  },
33
34
  "devDependencies": {
34
35
  "@types/json-schema": "^7.0.15",
35
36
  "@types/pg": "^8.20.0",
36
- "@types/react": "^19.2.14",
37
+ "@types/react": "^19.2.15",
37
38
  "@types/react-dom": "^19.2.3",
38
39
  "@vitest/coverage-v8": "^4.1.5",
39
40
  "expect-type": "^1.3.0",
@@ -1,17 +0,0 @@
1
- /**
2
- * Vercel Blob Storage Plugin
3
- *
4
- * WARNING: This module is server-only.
5
- * Do NOT import in client-side code or edge runtime.
6
- */
7
- import type { Plugin } from '../types/index.js';
8
- export interface VercelBlobStorageConfig {
9
- enabled?: boolean;
10
- collections?: {
11
- [key: string]: boolean;
12
- };
13
- token?: string;
14
- prefix?: string;
15
- }
16
- export declare function vercelBlobStorage(config: VercelBlobStorageConfig): Plugin;
17
- //# sourceMappingURL=vercel-blob.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"vercel-blob.d.ts","sourceRoot":"","sources":["../../src/storage/vercel-blob.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAEhD,MAAM,WAAW,uBAAuB;IACtC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,WAAW,CAAC,EAAE;QACZ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KACxB,CAAC;IACF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,uBAAuB,GAAG,MAAM,CAyFzE"}