alabit-ui 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/dist/server.js ADDED
@@ -0,0 +1,622 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defProps = Object.defineProperties;
3
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
4
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
7
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
8
+ var __spreadValues = (a, b) => {
9
+ for (var prop in b || (b = {}))
10
+ if (__hasOwnProp.call(b, prop))
11
+ __defNormalProp(a, prop, b[prop]);
12
+ if (__getOwnPropSymbols)
13
+ for (var prop of __getOwnPropSymbols(b)) {
14
+ if (__propIsEnum.call(b, prop))
15
+ __defNormalProp(a, prop, b[prop]);
16
+ }
17
+ return a;
18
+ };
19
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
20
+
21
+ // src/lib/responsive/server.ts
22
+ import { randomUUID } from "crypto";
23
+ import { access } from "fs/promises";
24
+ import { constants } from "fs";
25
+ import { chromium } from "playwright-core";
26
+
27
+ // src/lib/responsive/shared.ts
28
+ var DEVICE_PRESETS = [
29
+ {
30
+ id: "mobile-sm",
31
+ name: "Mobile S",
32
+ width: 360,
33
+ height: 780,
34
+ kind: "phone",
35
+ category: "phone",
36
+ brand: "generic",
37
+ line: "generic-smartphone",
38
+ tags: ["sm", "360", "small"]
39
+ },
40
+ {
41
+ id: "iphone-12-pro",
42
+ name: "iPhone 12 Pro",
43
+ width: 390,
44
+ height: 844,
45
+ kind: "phone",
46
+ category: "phone",
47
+ brand: "apple",
48
+ line: "iphone",
49
+ tags: ["apple", "ios", "390"]
50
+ },
51
+ {
52
+ id: "mobile-lg",
53
+ name: "Mobile L",
54
+ width: 640,
55
+ height: 960,
56
+ kind: "phone",
57
+ category: "phone",
58
+ brand: "generic",
59
+ line: "generic-smartphone",
60
+ tags: ["lg", "640", "large"]
61
+ },
62
+ {
63
+ id: "ipad-mini",
64
+ name: "iPad Mini",
65
+ width: 744,
66
+ height: 1133,
67
+ kind: "tablet",
68
+ category: "tablet",
69
+ brand: "apple",
70
+ line: "ipad",
71
+ tags: ["apple", "ipad", "mini"]
72
+ },
73
+ {
74
+ id: "ipad",
75
+ name: "iPad",
76
+ width: 768,
77
+ height: 1024,
78
+ kind: "tablet",
79
+ category: "tablet",
80
+ brand: "apple",
81
+ line: "ipad",
82
+ tags: ["apple", "ipad", "768"]
83
+ },
84
+ {
85
+ id: "ipad-air",
86
+ name: "iPad Air",
87
+ width: 820,
88
+ height: 1180,
89
+ kind: "tablet",
90
+ category: "tablet",
91
+ brand: "apple",
92
+ line: "ipad",
93
+ tags: ["apple", "ipad", "air"]
94
+ },
95
+ {
96
+ id: "ipad-pro-11",
97
+ name: 'iPad Pro 11"',
98
+ width: 834,
99
+ height: 1194,
100
+ kind: "tablet",
101
+ category: "tablet",
102
+ brand: "apple",
103
+ line: "ipad",
104
+ tags: ["apple", "ipad", "pro", "11"]
105
+ },
106
+ {
107
+ id: "ipad-pro-12-9",
108
+ name: 'iPad Pro 12.9"',
109
+ width: 1024,
110
+ height: 1366,
111
+ kind: "tablet",
112
+ category: "tablet",
113
+ brand: "apple",
114
+ line: "ipad",
115
+ tags: ["apple", "ipad", "pro", "12"]
116
+ },
117
+ {
118
+ id: "galaxy-tab-s9",
119
+ name: "Galaxy Tab S9",
120
+ width: 847,
121
+ height: 1210,
122
+ kind: "tablet",
123
+ category: "tablet",
124
+ brand: "samsung",
125
+ line: "galaxy-tab",
126
+ tags: ["samsung", "android", "galaxy-tab"]
127
+ },
128
+ {
129
+ id: "tablet-generic",
130
+ name: "Tablet Gen\xE9rica",
131
+ width: 800,
132
+ height: 1280,
133
+ kind: "tablet",
134
+ category: "tablet",
135
+ brand: "generic",
136
+ line: "generic-tablet",
137
+ tags: ["generic", "android"]
138
+ },
139
+ {
140
+ id: "laptop",
141
+ name: "Laptop",
142
+ width: 1024,
143
+ height: 768,
144
+ kind: "desktop",
145
+ category: "laptop",
146
+ brand: "generic",
147
+ line: "generic-laptop",
148
+ tags: ["1024", "lg-screen"]
149
+ },
150
+ {
151
+ id: "macbook-air-13",
152
+ name: 'MacBook Air 13"',
153
+ width: 1280,
154
+ height: 832,
155
+ kind: "desktop",
156
+ category: "laptop",
157
+ brand: "apple",
158
+ line: "macbook",
159
+ tags: ["apple", "mac", "macbook", "air", "13"]
160
+ },
161
+ {
162
+ id: "macbook-pro-14",
163
+ name: 'MacBook Pro 14"',
164
+ width: 1512,
165
+ height: 982,
166
+ kind: "desktop",
167
+ category: "laptop",
168
+ brand: "apple",
169
+ line: "macbook",
170
+ tags: ["apple", "mac", "macbook", "pro", "14"]
171
+ },
172
+ {
173
+ id: "macbook-pro-16",
174
+ name: 'MacBook Pro 16"',
175
+ width: 1728,
176
+ height: 1117,
177
+ kind: "desktop",
178
+ category: "laptop",
179
+ brand: "apple",
180
+ line: "macbook",
181
+ tags: ["apple", "mac", "macbook", "pro", "16"]
182
+ },
183
+ {
184
+ id: "desktop",
185
+ name: "Desktop",
186
+ width: 1280,
187
+ height: 800,
188
+ kind: "desktop",
189
+ category: "desktop",
190
+ brand: "monitor",
191
+ line: "generic-desktop",
192
+ tags: ["1280", "hd", "screen"]
193
+ },
194
+ {
195
+ id: "imac-24",
196
+ name: 'iMac 24"',
197
+ width: 2240,
198
+ height: 1260,
199
+ kind: "desktop",
200
+ category: "aio",
201
+ brand: "apple",
202
+ line: "imac",
203
+ tags: ["apple", "mac", "imac", "24", "screen"]
204
+ },
205
+ {
206
+ id: "desktop-xl",
207
+ name: "Desktop XL",
208
+ width: 1536,
209
+ height: 960,
210
+ kind: "desktop",
211
+ category: "desktop",
212
+ brand: "monitor",
213
+ line: "pro-monitor",
214
+ tags: ["1536", "2xl", "screen"]
215
+ },
216
+ {
217
+ id: "imac-27",
218
+ name: 'iMac 27"',
219
+ width: 2560,
220
+ height: 1440,
221
+ kind: "desktop",
222
+ category: "aio",
223
+ brand: "apple",
224
+ line: "imac",
225
+ tags: ["apple", "mac", "imac", "27", "5k", "screen"]
226
+ },
227
+ {
228
+ id: "full-hd",
229
+ name: "Full HD",
230
+ width: 1920,
231
+ height: 1080,
232
+ kind: "desktop",
233
+ category: "desktop",
234
+ brand: "monitor",
235
+ line: "pro-monitor",
236
+ tags: ["1920", "1080", "fhd", "screen"]
237
+ },
238
+ {
239
+ id: "wqxga",
240
+ name: "WQXGA 2.5K",
241
+ width: 2560,
242
+ height: 1600,
243
+ kind: "desktop",
244
+ category: "desktop",
245
+ brand: "monitor",
246
+ line: "pro-monitor",
247
+ tags: ["2560", "1600", "2k", "qhd", "screen"]
248
+ },
249
+ {
250
+ id: "uhd-4k",
251
+ name: "4K UHD",
252
+ width: 3840,
253
+ height: 2160,
254
+ kind: "desktop",
255
+ category: "desktop",
256
+ brand: "monitor",
257
+ line: "pro-monitor",
258
+ tags: ["3840", "2160", "4k", "uhd", "screen"]
259
+ },
260
+ {
261
+ id: "custom",
262
+ name: "Personalizado",
263
+ width: 480,
264
+ height: 720,
265
+ kind: "phone",
266
+ category: "custom",
267
+ brand: "custom",
268
+ line: "custom",
269
+ tags: ["custom", "user"]
270
+ }
271
+ ];
272
+ function getViewportSize(device, orientation) {
273
+ const baseIsLandscape = device.width >= device.height;
274
+ if (orientation === "landscape") {
275
+ return baseIsLandscape ? { width: device.width, height: device.height } : { width: device.height, height: device.width };
276
+ }
277
+ return baseIsLandscape ? { width: device.height, height: device.width } : { width: device.width, height: device.height };
278
+ }
279
+
280
+ // src/lib/responsive/server.ts
281
+ var _a;
282
+ var DEFAULT_CHROME_PATH = (_a = process.env.RESPONSIVE_CHROME_PATH) != null ? _a : "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
283
+ var SESSION_TTL_MS = 1e3 * 60 * 15;
284
+ var ACTION_SETTLE_MS = 350;
285
+ var NAVIGATION_TIMEOUT_MS = 3e4;
286
+ var SCREENSHOT_TIMEOUT_MS = 6e4;
287
+ function getStore() {
288
+ if (!globalThis.__responsiveStore__) {
289
+ globalThis.__responsiveStore__ = {
290
+ browserPromise: null,
291
+ sessions: /* @__PURE__ */ new Map(),
292
+ cleanupTimer: null
293
+ };
294
+ }
295
+ if (!globalThis.__responsiveStore__.cleanupTimer) {
296
+ globalThis.__responsiveStore__.cleanupTimer = setInterval(() => {
297
+ void cleanupExpiredSessions();
298
+ }, 6e4);
299
+ }
300
+ return globalThis.__responsiveStore__;
301
+ }
302
+ async function resolveBrowser() {
303
+ const store = getStore();
304
+ if (!store.browserPromise) {
305
+ store.browserPromise = (async () => {
306
+ await access(DEFAULT_CHROME_PATH, constants.X_OK);
307
+ return chromium.launch({
308
+ headless: true,
309
+ executablePath: DEFAULT_CHROME_PATH,
310
+ args: [
311
+ "--disable-dev-shm-usage",
312
+ "--disable-background-timer-throttling",
313
+ "--disable-renderer-backgrounding"
314
+ ]
315
+ });
316
+ })().catch((error) => {
317
+ store.browserPromise = null;
318
+ throw error;
319
+ });
320
+ }
321
+ return store.browserPromise;
322
+ }
323
+ function sanitizeUrl(rawUrl) {
324
+ try {
325
+ const url = new URL(rawUrl);
326
+ if (!["http:", "https:"].includes(url.protocol)) {
327
+ throw new Error("Solo se permiten URLs http/https.");
328
+ }
329
+ return url.toString();
330
+ } catch (e) {
331
+ throw new Error("URL invalida. Usa una ruta local completa o una URL externa valida.");
332
+ }
333
+ }
334
+ function emptySnapshot(device, orientation, targetUrl) {
335
+ const viewport = getViewportSize(device, orientation);
336
+ return {
337
+ deviceId: device.id,
338
+ name: device.name,
339
+ width: viewport.width,
340
+ height: viewport.height,
341
+ screenshotDataUrl: null,
342
+ status: "loading",
343
+ currentUrl: targetUrl,
344
+ title: "",
345
+ error: null
346
+ };
347
+ }
348
+ async function waitForPageReady(page) {
349
+ await page.waitForLoadState("domcontentloaded", { timeout: NAVIGATION_TIMEOUT_MS });
350
+ try {
351
+ await page.waitForLoadState("networkidle", { timeout: 2e3 });
352
+ } catch (e) {
353
+ }
354
+ }
355
+ function getBrowserNavigationError(page) {
356
+ const currentUrl = page.url();
357
+ if (currentUrl.startsWith("chrome-error://")) {
358
+ return "Chrome no pudo cargar la URL destino.";
359
+ }
360
+ return null;
361
+ }
362
+ async function captureFrame(frame) {
363
+ const { page } = frame;
364
+ try {
365
+ const navigationError = getBrowserNavigationError(page);
366
+ if (navigationError) {
367
+ throw new Error(navigationError);
368
+ }
369
+ await page.waitForTimeout(60);
370
+ const buffer = await page.screenshot({
371
+ type: "jpeg",
372
+ quality: 65,
373
+ animations: "disabled",
374
+ caret: "hide",
375
+ timeout: SCREENSHOT_TIMEOUT_MS
376
+ });
377
+ frame.snapshot = __spreadProps(__spreadValues({}, frame.snapshot), {
378
+ screenshotDataUrl: `data:image/jpeg;base64,${buffer.toString("base64")}`,
379
+ status: "ready",
380
+ currentUrl: page.url(),
381
+ title: await page.title(),
382
+ error: null
383
+ });
384
+ } catch (error) {
385
+ frame.snapshot = __spreadProps(__spreadValues({}, frame.snapshot), {
386
+ screenshotDataUrl: null,
387
+ status: "error",
388
+ currentUrl: page.url(),
389
+ title: "",
390
+ error: error instanceof Error ? error.message : "No se pudo capturar el viewport."
391
+ });
392
+ }
393
+ }
394
+ async function refreshSession(session) {
395
+ for (const frame of session.frames.values()) {
396
+ await captureFrame(frame);
397
+ }
398
+ session.updatedAt = Date.now();
399
+ }
400
+ async function closeSessionResources(session) {
401
+ if (session.closed) {
402
+ return;
403
+ }
404
+ session.closed = true;
405
+ await Promise.all(
406
+ Array.from(session.frames.values()).map(async (frame) => {
407
+ await frame.context.close().catch(() => void 0);
408
+ })
409
+ );
410
+ session.frames.clear();
411
+ }
412
+ async function cleanupExpiredSessions() {
413
+ const store = getStore();
414
+ const now = Date.now();
415
+ const expired = Array.from(store.sessions.values()).filter(
416
+ (session) => now - session.updatedAt > SESSION_TTL_MS
417
+ );
418
+ await Promise.all(
419
+ expired.map(async (session) => {
420
+ store.sessions.delete(session.id);
421
+ await closeSessionResources(session);
422
+ })
423
+ );
424
+ }
425
+ function requireSession(sessionId) {
426
+ const store = getStore();
427
+ const session = store.sessions.get(sessionId);
428
+ if (!session || session.closed) {
429
+ throw new Error("La sesion ya no existe. Abri la URL de nuevo.");
430
+ }
431
+ session.updatedAt = Date.now();
432
+ return session;
433
+ }
434
+ function toSnapshot(session) {
435
+ return {
436
+ sessionId: session.id,
437
+ targetUrl: session.targetUrl,
438
+ orientation: session.orientation,
439
+ updatedAt: session.updatedAt,
440
+ frames: DEVICE_PRESETS.map((device) => {
441
+ var _a2;
442
+ const frame = session.frames.get(device.id);
443
+ return (_a2 = frame == null ? void 0 : frame.snapshot) != null ? _a2 : emptySnapshot(device, session.orientation, session.targetUrl);
444
+ })
445
+ };
446
+ }
447
+ async function createFrame(browser, device, targetUrl, orientation) {
448
+ const viewport = getViewportSize(device, orientation);
449
+ const context = await browser.newContext({
450
+ viewport,
451
+ screen: viewport,
452
+ deviceScaleFactor: 1,
453
+ isMobile: device.kind === "phone",
454
+ hasTouch: device.kind !== "desktop"
455
+ });
456
+ const page = await context.newPage();
457
+ const snapshot = emptySnapshot(device, orientation, targetUrl);
458
+ try {
459
+ await page.goto(targetUrl, {
460
+ waitUntil: "domcontentloaded",
461
+ timeout: NAVIGATION_TIMEOUT_MS
462
+ });
463
+ await waitForPageReady(page);
464
+ const navigationError = getBrowserNavigationError(page);
465
+ if (navigationError) {
466
+ throw new Error(navigationError);
467
+ }
468
+ } catch (error) {
469
+ snapshot.status = "error";
470
+ snapshot.error = error instanceof Error ? error.message : "No se pudo abrir la URL.";
471
+ }
472
+ const frame = {
473
+ device,
474
+ context,
475
+ page,
476
+ snapshot: __spreadProps(__spreadValues({}, snapshot), {
477
+ currentUrl: page.url() || targetUrl
478
+ })
479
+ };
480
+ await captureFrame(frame);
481
+ return frame;
482
+ }
483
+ async function createResponsiveSession(targetUrl, orientation) {
484
+ const sanitizedUrl = sanitizeUrl(targetUrl);
485
+ const browser = await resolveBrowser();
486
+ const frames = [];
487
+ for (const device of DEVICE_PRESETS) {
488
+ frames.push(await createFrame(browser, device, sanitizedUrl, orientation));
489
+ }
490
+ const session = {
491
+ id: randomUUID(),
492
+ targetUrl: sanitizedUrl,
493
+ orientation,
494
+ frames: new Map(frames.map((frame) => [frame.device.id, frame])),
495
+ updatedAt: Date.now(),
496
+ closed: false
497
+ };
498
+ getStore().sessions.set(session.id, session);
499
+ return toSnapshot(session);
500
+ }
501
+ async function getResponsiveSession(sessionId) {
502
+ const session = requireSession(sessionId);
503
+ await refreshSession(session);
504
+ return toSnapshot(session);
505
+ }
506
+ async function deleteResponsiveSession(sessionId) {
507
+ const store = getStore();
508
+ const session = store.sessions.get(sessionId);
509
+ if (!session) {
510
+ return;
511
+ }
512
+ store.sessions.delete(sessionId);
513
+ await closeSessionResources(session);
514
+ }
515
+ function clampRatio(value) {
516
+ return Math.max(0, Math.min(1, value));
517
+ }
518
+ async function settleSession(session) {
519
+ await Promise.all(
520
+ Array.from(session.frames.values()).map(async ({ page }) => {
521
+ try {
522
+ await page.waitForTimeout(ACTION_SETTLE_MS);
523
+ } catch (e) {
524
+ return void 0;
525
+ }
526
+ })
527
+ );
528
+ }
529
+ async function performResponsiveAction(sessionId, input) {
530
+ var _a2, _b;
531
+ const session = requireSession(sessionId);
532
+ if (input.type === "reload") {
533
+ await Promise.all(
534
+ Array.from(session.frames.values()).map(async ({ page, snapshot }) => {
535
+ snapshot.status = "loading";
536
+ try {
537
+ await page.reload({ waitUntil: "domcontentloaded", timeout: NAVIGATION_TIMEOUT_MS });
538
+ await waitForPageReady(page);
539
+ const navigationError = getBrowserNavigationError(page);
540
+ if (navigationError) {
541
+ throw new Error(navigationError);
542
+ }
543
+ } catch (error) {
544
+ snapshot.status = "error";
545
+ snapshot.error = error instanceof Error ? error.message : "No se pudo recargar.";
546
+ }
547
+ })
548
+ );
549
+ }
550
+ if (input.type === "set-orientation") {
551
+ session.orientation = input.orientation;
552
+ await Promise.all(
553
+ Array.from(session.frames.values()).map(async (frame) => {
554
+ const viewport = getViewportSize(frame.device, input.orientation);
555
+ frame.snapshot = __spreadProps(__spreadValues({}, frame.snapshot), {
556
+ width: viewport.width,
557
+ height: viewport.height,
558
+ status: "loading",
559
+ error: null
560
+ });
561
+ await frame.page.setViewportSize(viewport);
562
+ })
563
+ );
564
+ }
565
+ if (input.type === "navigate") {
566
+ const targetUrl = sanitizeUrl(input.targetUrl);
567
+ session.targetUrl = targetUrl;
568
+ await Promise.all(
569
+ Array.from(session.frames.values()).map(async ({ page, snapshot }) => {
570
+ snapshot.status = "loading";
571
+ try {
572
+ await page.goto(targetUrl, {
573
+ waitUntil: "domcontentloaded",
574
+ timeout: NAVIGATION_TIMEOUT_MS
575
+ });
576
+ await waitForPageReady(page);
577
+ const navigationError = getBrowserNavigationError(page);
578
+ if (navigationError) {
579
+ throw new Error(navigationError);
580
+ }
581
+ } catch (error) {
582
+ snapshot.status = "error";
583
+ snapshot.error = error instanceof Error ? error.message : "No se pudo navegar.";
584
+ }
585
+ })
586
+ );
587
+ }
588
+ if (input.type === "click") {
589
+ await Promise.all(
590
+ Array.from(session.frames.values()).map(async (frame) => {
591
+ const { width, height } = getViewportSize(frame.device, session.orientation);
592
+ await frame.page.mouse.click(
593
+ clampRatio(input.xRatio) * width,
594
+ clampRatio(input.yRatio) * height
595
+ );
596
+ })
597
+ );
598
+ }
599
+ if (input.type === "scroll") {
600
+ const deltaX = (_a2 = input.deltaX) != null ? _a2 : 0;
601
+ const deltaY = (_b = input.deltaY) != null ? _b : 0;
602
+ await Promise.all(
603
+ Array.from(session.frames.values()).map(
604
+ ({ page }) => page.evaluate(
605
+ ({ nextX, nextY }) => {
606
+ window.scrollBy(nextX, nextY);
607
+ },
608
+ { nextX: deltaX, nextY: deltaY }
609
+ )
610
+ )
611
+ );
612
+ }
613
+ await settleSession(session);
614
+ await refreshSession(session);
615
+ return toSnapshot(session);
616
+ }
617
+ export {
618
+ createResponsiveSession,
619
+ deleteResponsiveSession,
620
+ getResponsiveSession,
621
+ performResponsiveAction
622
+ };
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "alabit-ui",
3
+ "version": "0.1.0",
4
+ "description": "Alabit UI — React component library built with Tailwind v4 + @base-ui/react.",
5
+ "license": "MIT",
6
+ "private": false,
7
+ "type": "module",
8
+ "main": "./dist/index.cjs",
9
+ "module": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.js",
15
+ "require": "./dist/index.cjs"
16
+ },
17
+ "./server": {
18
+ "types": "./dist/server.d.ts",
19
+ "node": {
20
+ "import": "./dist/server.js",
21
+ "require": "./dist/server.cjs"
22
+ },
23
+ "import": "./dist/server.js",
24
+ "require": "./dist/server.cjs"
25
+ },
26
+ "./theme.css": "./dist/foundations/tokens/tokens.css"
27
+ },
28
+ "files": [
29
+ "dist",
30
+ "README.md",
31
+ "LICENSE"
32
+ ],
33
+ "sideEffects": [
34
+ "*.css"
35
+ ],
36
+ "scripts": {
37
+ "typecheck": "tsc --noEmit",
38
+ "build": "tsup",
39
+ "dev": "tsup --watch",
40
+ "clean": "rm -rf dist",
41
+ "prepublishOnly": "pnpm build",
42
+ "release": "pnpm typecheck && pnpm build && pnpm publish --no-git-checks --tag latest"
43
+ },
44
+ "peerDependencies": {
45
+ "react": ">=18",
46
+ "react-dom": ">=18",
47
+ "tailwindcss": ">=4",
48
+ "@base-ui/react": "*"
49
+ },
50
+ "dependencies": {
51
+ "lucide-react": "^0.460",
52
+ "clsx": "^2",
53
+ "tailwind-merge": "^2",
54
+ "class-variance-authority": "^0.7"
55
+ },
56
+ "devDependencies": {
57
+ "@types/react": "^19",
58
+ "@types/react-dom": "^19",
59
+ "tsup": "^8",
60
+ "typescript": "^5"
61
+ },
62
+ "publishConfig": {
63
+ "access": "public"
64
+ }
65
+ }