@remix-run/file-storage 0.9.0 → 0.11.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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2024 Michael Jackson
3
+ Copyright (c) 2025 Shopify Inc.
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -20,30 +20,30 @@ npm install @remix-run/file-storage
20
20
  ## Usage
21
21
 
22
22
  ```ts
23
- import { LocalFileStorage } from '@remix-run/file-storage/local';
23
+ import { LocalFileStorage } from '@remix-run/file-storage/local'
24
24
 
25
- let storage = new LocalFileStorage('./user/files');
25
+ let storage = new LocalFileStorage('./user/files')
26
26
 
27
- let file = new File(['hello world'], 'hello.txt', { type: 'text/plain' });
28
- let key = 'hello-key';
27
+ let file = new File(['hello world'], 'hello.txt', { type: 'text/plain' })
28
+ let key = 'hello-key'
29
29
 
30
30
  // Put the file in storage.
31
- await storage.set(key, file);
31
+ await storage.set(key, file)
32
32
 
33
33
  // Then, sometime later...
34
- let fileFromStorage = await storage.get(key);
34
+ let fileFromStorage = await storage.get(key)
35
35
  // All of the original file's metadata is intact
36
- fileFromStorage.name; // 'hello.txt'
37
- fileFromStorage.type; // 'text/plain'
36
+ fileFromStorage.name // 'hello.txt'
37
+ fileFromStorage.type // 'text/plain'
38
38
 
39
39
  // To remove from storage
40
- await storage.remove(key);
40
+ await storage.remove(key)
41
41
  ```
42
42
 
43
43
  The `FileStorage` interface allows you to implement your own file storage for custom storage backends:
44
44
 
45
45
  ```ts
46
- import { type FileStorage } from '@remix-run/file-storage';
46
+ import { type FileStorage } from '@remix-run/file-storage'
47
47
 
48
48
  class CustomFileStorage implements FileStorage {
49
49
  /**
@@ -75,9 +75,9 @@ class CustomFileStorage implements FileStorage {
75
75
 
76
76
  ## Related Packages
77
77
 
78
- - [`form-data-parser`](https://github.com/remix-run/remix/tree/v3/packages/form-data-parser) - Pairs well with this library for storing `FileUpload` objects received in `multipart/form-data` requests
79
- - [`lazy-file`](https://github.com/remix-run/remix/tree/v3/packages/lazy-file) - The streaming `File` implementation used internally to stream files from storage
78
+ - [`form-data-parser`](https://github.com/remix-run/remix/tree/main/packages/form-data-parser) - Pairs well with this library for storing `FileUpload` objects received in `multipart/form-data` requests
79
+ - [`lazy-file`](https://github.com/remix-run/remix/tree/main/packages/lazy-file) - The streaming `File` implementation used internally to stream files from storage
80
80
 
81
81
  ## License
82
82
 
83
- See [LICENSE](https://github.com/remix-run/remix/blob/v3/LICENSE)
83
+ See [LICENSE](https://github.com/remix-run/remix/blob/main/LICENSE)
@@ -1,2 +1,2 @@
1
1
  export type { FileStorage, FileKey, FileMetadata, ListOptions, ListResult, } from './lib/file-storage.ts';
2
- //# sourceMappingURL=file-storage.d.ts.map
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,WAAW,EACX,OAAO,EACP,YAAY,EACZ,WAAW,EACX,UAAU,GACX,MAAM,uBAAuB,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -1 +1 @@
1
- {"version":3,"file":"file-storage.d.ts","sourceRoot":"","sources":["../../src/lib/file-storage.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;IAErD;;;;OAIG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAE7C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA0DG;IACH,IAAI,CAAC,CAAC,SAAS,WAAW,EAAE,OAAO,CAAC,EAAE,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;IAEjF;;;;;;OAMG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEnD;;;;OAIG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE1C;;;;;;OAMG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACpD;AAED,MAAM,WAAW,OAAO;IACtB;;OAEG;IACH,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;GAEG;AACH,MAAM,WAAW,YAAa,SAAQ,OAAO;IAC3C;;OAEG;IACH,YAAY,EAAE,MAAM,CAAC;IACrB;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,WAAW;IAC1B;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;OAEG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,UAAU,CAAC,CAAC,SAAS,WAAW;IAC/C;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;OAEG;IACH,KAAK,EAAE,CAAC,CAAC,SAAS;QAAE,eAAe,EAAE,IAAI,CAAA;KAAE,GAAG,YAAY,GAAG,OAAO,CAAC,EAAE,CAAC;CACzE"}
1
+ {"version":3,"file":"file-storage.d.ts","sourceRoot":"","sources":["../../src/lib/file-storage.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CAAA;IAEpD;;;;OAIG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAE5C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA0DG;IACH,IAAI,CAAC,CAAC,SAAS,WAAW,EAAE,OAAO,CAAC,EAAE,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAA;IAEhF;;;;;;OAMG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAElD;;;;OAIG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAEzC;;;;;;OAMG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACnD;AAED,MAAM,WAAW,OAAO;IACtB;;OAEG;IACH,GAAG,EAAE,MAAM,CAAA;CACZ;AAED;;GAEG;AACH,MAAM,WAAW,YAAa,SAAQ,OAAO;IAC3C;;OAEG;IACH,YAAY,EAAE,MAAM,CAAA;IACpB;;OAEG;IACH,IAAI,EAAE,MAAM,CAAA;IACZ;;OAEG;IACH,IAAI,EAAE,MAAM,CAAA;IACZ;;OAEG;IACH,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,WAAW;IAC1B;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;OAEG;IACH,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,UAAU,CAAC,CAAC,SAAS,WAAW;IAC/C;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;OAEG;IACH,KAAK,EAAE,CAAC,CAAC,SAAS;QAAE,eAAe,EAAE,IAAI,CAAA;KAAE,GAAG,YAAY,GAAG,OAAO,CAAC,EAAE,CAAA;CACxE"}
@@ -0,0 +1 @@
1
+ export {};
@@ -1 +1 @@
1
- {"version":3,"file":"local-file-storage.d.ts","sourceRoot":"","sources":["../../src/lib/local-file-storage.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAgB,WAAW,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAI5F;;;;;;;;;;GAUG;AACH,qBAAa,gBAAiB,YAAW,WAAW;;IAGlD;;OAEG;gBACS,SAAS,EAAE,MAAM;IAkBvB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;IAoBtC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAWlC,IAAI,CAAC,CAAC,SAAS,WAAW,EAAE,OAAO,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IAgDhE,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAK3C,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBlC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;CA2BlD"}
1
+ {"version":3,"file":"local-file-storage.d.ts","sourceRoot":"","sources":["../../src/lib/local-file-storage.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAgB,WAAW,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAI3F;;;;;;;;;;GAUG;AACH,qBAAa,gBAAiB,YAAW,WAAW;;IAGlD;;OAEG;gBACS,SAAS,EAAE,MAAM;IAkBvB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;IAoBtC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAWlC,IAAI,CAAC,CAAC,SAAS,WAAW,EAAE,OAAO,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IAgDhE,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAK3C,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBlC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;CA2BlD"}
@@ -0,0 +1,158 @@
1
+ import * as fs from 'node:fs';
2
+ import * as fsp from 'node:fs/promises';
3
+ import * as path from 'node:path';
4
+ import { openFile, writeFile } from '@remix-run/lazy-file/fs';
5
+ /**
6
+ * A `FileStorage` that is backed by a directory on the local filesystem.
7
+ *
8
+ * Important: No attempt is made to avoid overwriting existing files, so the directory used should
9
+ * be a new directory solely dedicated to this storage object.
10
+ *
11
+ * Note: Keys have no correlation to file names on disk, so they may be any string including
12
+ * characters that are not valid in file names. Additionally, individual `File` names have no
13
+ * correlation to names of files on disk, so multiple files with the same name may be stored in the
14
+ * same storage object.
15
+ */
16
+ export class LocalFileStorage {
17
+ #dirname;
18
+ /**
19
+ * @param directory The directory where files are stored
20
+ */
21
+ constructor(directory) {
22
+ this.#dirname = path.resolve(directory);
23
+ try {
24
+ let stats = fs.statSync(this.#dirname);
25
+ if (!stats.isDirectory()) {
26
+ throw new Error(`Path "${this.#dirname}" is not a directory`);
27
+ }
28
+ }
29
+ catch (error) {
30
+ if (!isNoEntityError(error)) {
31
+ throw error;
32
+ }
33
+ fs.mkdirSync(this.#dirname, { recursive: true });
34
+ }
35
+ }
36
+ async get(key) {
37
+ let { filePath, metaPath } = await this.#getPaths(key);
38
+ try {
39
+ let meta = await readMetadata(metaPath);
40
+ return openFile(filePath, {
41
+ lastModified: meta.lastModified,
42
+ name: meta.name,
43
+ type: meta.type,
44
+ });
45
+ }
46
+ catch (error) {
47
+ if (!isNoEntityError(error)) {
48
+ throw error;
49
+ }
50
+ return null;
51
+ }
52
+ }
53
+ async has(key) {
54
+ let { metaPath } = await this.#getPaths(key);
55
+ try {
56
+ await fsp.access(metaPath);
57
+ return true;
58
+ }
59
+ catch {
60
+ return false;
61
+ }
62
+ }
63
+ async list(options) {
64
+ let { cursor, includeMetadata = false, limit = 32, prefix } = options ?? {};
65
+ let files = [];
66
+ let foundCursor = cursor === undefined;
67
+ let nextCursor;
68
+ let lastHash;
69
+ outerLoop: for await (let subdir of await fsp.opendir(this.#dirname)) {
70
+ if (!subdir.isDirectory())
71
+ continue;
72
+ for await (let file of await fsp.opendir(path.join(this.#dirname, subdir.name))) {
73
+ if (!file.isFile() || !file.name.endsWith('.meta.json'))
74
+ continue;
75
+ let hash = file.name.slice(0, -10); // Remove ".meta.json"
76
+ if (foundCursor) {
77
+ let meta = await readMetadata(path.join(this.#dirname, subdir.name, file.name));
78
+ if (prefix != null && !meta.key.startsWith(prefix)) {
79
+ continue;
80
+ }
81
+ if (files.length >= limit) {
82
+ nextCursor = lastHash;
83
+ break outerLoop;
84
+ }
85
+ if (includeMetadata) {
86
+ let size = (await fsp.stat(path.join(this.#dirname, subdir.name, `${hash}.dat`))).size;
87
+ files.push({ ...meta, size });
88
+ }
89
+ else {
90
+ files.push({ key: meta.key });
91
+ }
92
+ }
93
+ else if (hash === cursor) {
94
+ foundCursor = true;
95
+ }
96
+ lastHash = hash;
97
+ }
98
+ }
99
+ return {
100
+ cursor: nextCursor,
101
+ files,
102
+ };
103
+ }
104
+ async put(key, file) {
105
+ await this.set(key, file);
106
+ return (await this.get(key));
107
+ }
108
+ async remove(key) {
109
+ let { directory, filePath, metaPath } = await this.#getPaths(key);
110
+ try {
111
+ await Promise.all([fsp.unlink(filePath), fsp.unlink(metaPath)]);
112
+ // Check if directory is empty and remove it if so
113
+ let files = await fsp.readdir(directory);
114
+ if (files.length === 0) {
115
+ await fsp.rmdir(directory);
116
+ }
117
+ }
118
+ catch (error) {
119
+ if (!isNoEntityError(error)) {
120
+ throw error;
121
+ }
122
+ }
123
+ }
124
+ async set(key, file) {
125
+ let { directory, filePath, metaPath } = await this.#getPaths(key);
126
+ // Ensure directory exists
127
+ await fsp.mkdir(directory, { recursive: true });
128
+ await writeFile(filePath, file);
129
+ let meta = {
130
+ key,
131
+ lastModified: file.lastModified,
132
+ name: file.name,
133
+ type: file.type,
134
+ };
135
+ await fsp.writeFile(metaPath, JSON.stringify(meta));
136
+ }
137
+ async #getPaths(key) {
138
+ let hash = await computeHash(key);
139
+ let directory = path.join(this.#dirname, hash.slice(0, 2));
140
+ return {
141
+ directory,
142
+ filePath: path.join(directory, `${hash}.dat`),
143
+ metaPath: path.join(directory, `${hash}.meta.json`),
144
+ };
145
+ }
146
+ }
147
+ async function readMetadata(metaPath) {
148
+ return JSON.parse(await fsp.readFile(metaPath, 'utf-8'));
149
+ }
150
+ async function computeHash(key, algorithm = 'SHA-256') {
151
+ let digest = await crypto.subtle.digest(algorithm, new TextEncoder().encode(key));
152
+ return Array.from(new Uint8Array(digest))
153
+ .map((b) => b.toString(16).padStart(2, '0'))
154
+ .join('');
155
+ }
156
+ function isNoEntityError(obj) {
157
+ return obj instanceof Error && 'code' in obj && obj.code === 'ENOENT';
158
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"memory-file-storage.d.ts","sourceRoot":"","sources":["../../src/lib/memory-file-storage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAE9E;;GAEG;AACH,qBAAa,iBAAkB,YAAW,WAAW;;IAGnD,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAI7B,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAIzB,IAAI,CAAC,CAAC,SAAS,WAAW,EAAE,OAAO,CAAC,EAAE,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC;IAwCjD,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAKjD,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAInB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;CASlD"}
1
+ {"version":3,"file":"memory-file-storage.d.ts","sourceRoot":"","sources":["../../src/lib/memory-file-storage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAE7E;;GAEG;AACH,qBAAa,iBAAkB,YAAW,WAAW;;IAGnD,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAI7B,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAIzB,IAAI,CAAC,CAAC,SAAS,WAAW,EAAE,OAAO,CAAC,EAAE,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC;IAwCjD,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAKjD,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAInB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;CASlD"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * A simple, in-memory implementation of the `FileStorage` interface.
3
+ */
4
+ export class MemoryFileStorage {
5
+ #map = new Map();
6
+ get(key) {
7
+ return this.#map.get(key) ?? null;
8
+ }
9
+ has(key) {
10
+ return this.#map.has(key);
11
+ }
12
+ list(options) {
13
+ let { cursor, includeMetadata = false, limit = Infinity, prefix } = options ?? {};
14
+ let files = [];
15
+ let foundCursor = cursor === undefined;
16
+ let nextCursor;
17
+ for (let [key, file] of this.#map.entries()) {
18
+ if (foundCursor) {
19
+ if (prefix != null && !key.startsWith(prefix)) {
20
+ continue;
21
+ }
22
+ if (files.length >= limit) {
23
+ nextCursor = files[files.length - 1]?.key;
24
+ break;
25
+ }
26
+ if (includeMetadata) {
27
+ files.push({
28
+ key,
29
+ lastModified: file.lastModified,
30
+ name: file.name,
31
+ size: file.size,
32
+ type: file.type,
33
+ });
34
+ }
35
+ else {
36
+ files.push({ key });
37
+ }
38
+ }
39
+ else if (key === cursor) {
40
+ foundCursor = true;
41
+ }
42
+ }
43
+ return {
44
+ cursor: nextCursor,
45
+ files,
46
+ };
47
+ }
48
+ async put(key, file) {
49
+ await this.set(key, file);
50
+ return this.get(key);
51
+ }
52
+ remove(key) {
53
+ this.#map.delete(key);
54
+ }
55
+ async set(key, file) {
56
+ let buffer = await file.arrayBuffer();
57
+ let newFile = new File([buffer], file.name, {
58
+ lastModified: file.lastModified,
59
+ type: file.type,
60
+ });
61
+ this.#map.set(key, newFile);
62
+ }
63
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"local.d.ts","sourceRoot":"","sources":["../src/local.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC"}
1
+ {"version":3,"file":"local.d.ts","sourceRoot":"","sources":["../src/local.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAA"}