backless-google-drive 0.9.3 → 0.9.5

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/index.d.ts CHANGED
@@ -9,6 +9,13 @@ declare class GoogleDriveApi implements CloudStorageApi {
9
9
  getOrCreateAppFolder(name: string): Promise<string>;
10
10
  ensureFolder(parentId: string, name: string): Promise<string>;
11
11
  listFiles(folderId: string): Promise<DriveFile[]>;
12
+ /**
13
+ * Batched form of {@link listFiles}: one query per chunk of folders instead of
14
+ * one per folder. Drive has no documented cap on OR'd `in parents` terms, but
15
+ * the `q` parameter is URL-length bound — 25 folders per query is comfortably
16
+ * inside it. `parents` is requested so results can be grouped back per folder.
17
+ */
18
+ listFilesInFolders(folderIds: string[]): Promise<Map<string, DriveFile[]>>;
12
19
  listFolders(parentId: string): Promise<DriveFile[]>;
13
20
  uploadJsonFile(folderId: string, name: string, data: unknown): Promise<string>;
14
21
  downloadJsonFile<T = unknown>(fileId: string): Promise<T>;
package/dist/index.js CHANGED
@@ -91,6 +91,38 @@ var GoogleDriveApi = class {
91
91
  } while (pageToken);
92
92
  return files;
93
93
  }
94
+ /**
95
+ * Batched form of {@link listFiles}: one query per chunk of folders instead of
96
+ * one per folder. Drive has no documented cap on OR'd `in parents` terms, but
97
+ * the `q` parameter is URL-length bound — 25 folders per query is comfortably
98
+ * inside it. `parents` is requested so results can be grouped back per folder.
99
+ */
100
+ async listFilesInFolders(folderIds) {
101
+ const CHUNK = 25;
102
+ const result = /* @__PURE__ */ new Map();
103
+ for (const id of folderIds) result.set(id, []);
104
+ for (let i = 0; i < folderIds.length; i += CHUNK) {
105
+ const chunk = folderIds.slice(i, i + CHUNK);
106
+ const parentsClause = chunk.map((id) => `'${id}' in parents`).join(" or ");
107
+ const q = `(${parentsClause}) and trashed = false`;
108
+ const fields = "files(id,name,mimeType,modifiedTime,parents)";
109
+ let pageToken;
110
+ do {
111
+ let url = `${DRIVE_API}/files?q=${encodeURIComponent(q)}&fields=nextPageToken,${fields}&spaces=drive&orderBy=name&pageSize=1000`;
112
+ if (pageToken) url += `&pageToken=${pageToken}`;
113
+ const resp = await this.request(url);
114
+ const data = await resp.json();
115
+ for (const file of data.files ?? []) {
116
+ for (const parent of file.parents ?? []) {
117
+ const bucket = result.get(parent);
118
+ if (bucket) bucket.push({ id: file.id, name: file.name, mimeType: file.mimeType, modifiedTime: file.modifiedTime });
119
+ }
120
+ }
121
+ pageToken = data.nextPageToken;
122
+ } while (pageToken);
123
+ }
124
+ return result;
125
+ }
94
126
  async listFolders(parentId) {
95
127
  const q = `'${parentId}' in parents and mimeType = '${FOLDER_MIME}' and trashed = false`;
96
128
  const fields = "files(id,name,mimeType,modifiedTime)";
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/google-drive-api.ts"],"sourcesContent":["import type { CloudStorageApi, DriveFile } from \"backless-core\";\n\nconst DRIVE_API = \"https://www.googleapis.com/drive/v3\";\nconst UPLOAD_API = \"https://www.googleapis.com/upload/drive/v3\";\nconst FOLDER_MIME = \"application/vnd.google-apps.folder\";\n\n// Drive API query strings use single-quoted literals; escape embedded single quotes as \\'\nfunction escapeDriveQuery(value: string): string {\n return value.replace(/'/g, \"\\\\'\");\n}\n\nexport class GoogleDriveApi implements CloudStorageApi {\n private readonly folderCache = new Map<string, string>();\n\n constructor(private getToken: () => Promise<string | null>) {}\n\n private async headers(): Promise<HeadersInit> {\n const token = await this.getToken();\n if (!token) throw new Error(\"Not authenticated\");\n return { Authorization: `Bearer ${token}` };\n }\n\n private async request(url: string, init?: RequestInit): Promise<Response> {\n const MAX_RETRIES = 3;\n\n for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {\n // headers() is outside the try so auth errors propagate immediately (not retried).\n // It is inside the loop so a fresh token is fetched on each retry attempt.\n const h = await this.headers();\n let resp: Response;\n try {\n resp = await fetch(url, { ...init, headers: { ...h, ...init?.headers } });\n } catch (err) {\n if (attempt < MAX_RETRIES) {\n await sleep(1000 * 2 ** attempt);\n continue;\n }\n throw err;\n }\n\n if (resp.ok) return resp;\n\n const retryable =\n resp.status === 429 ||\n resp.status === 500 ||\n resp.status === 502 ||\n resp.status === 503;\n\n if (retryable && attempt < MAX_RETRIES) {\n let delayMs = 1000 * 2 ** attempt;\n if (resp.status === 429) {\n const ra = resp.headers.get(\"Retry-After\");\n if (ra && /^\\d+$/.test(ra)) delayMs = parseInt(ra, 10) * 1000;\n }\n await sleep(delayMs);\n continue;\n }\n\n const body = await resp.text();\n throw new Error(`Drive API ${resp.status}: ${body}`);\n }\n\n // Unreachable — loop always returns or throws within MAX_RETRIES iterations\n throw new Error(\"Drive API: retry loop exhausted\");\n }\n\n async getOrCreateAppFolder(name: string): Promise<string> {\n return this.ensureFolder(\"root\", name);\n }\n\n async ensureFolder(parentId: string, name: string): Promise<string> {\n const cacheKey = `${parentId}:${name}`;\n const cached = this.folderCache.get(cacheKey);\n if (cached) return cached;\n\n const q = `'${parentId}' in parents and name = '${escapeDriveQuery(name)}' and mimeType = '${FOLDER_MIME}' and trashed = false`;\n const resp = await this.request(\n `${DRIVE_API}/files?q=${encodeURIComponent(q)}&fields=files(id,name)&spaces=drive`\n );\n const data = await resp.json();\n\n let folderId: string;\n if (data.files?.length > 0) {\n folderId = data.files[0].id;\n } else {\n const createResp = await this.request(`${DRIVE_API}/files`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n name,\n mimeType: FOLDER_MIME,\n parents: [parentId],\n }),\n });\n const created = await createResp.json();\n folderId = created.id;\n }\n\n this.folderCache.set(cacheKey, folderId);\n return folderId;\n }\n\n async listFiles(folderId: string): Promise<DriveFile[]> {\n const q = `'${folderId}' in parents and trashed = false`;\n const fields = \"files(id,name,mimeType,modifiedTime)\";\n let files: DriveFile[] = [];\n let pageToken: string | undefined;\n\n do {\n let url = `${DRIVE_API}/files?q=${encodeURIComponent(q)}&fields=nextPageToken,${fields}&spaces=drive&orderBy=name`;\n if (pageToken) url += `&pageToken=${pageToken}`;\n\n const resp = await this.request(url);\n const data = await resp.json();\n files = files.concat(data.files ?? []);\n pageToken = data.nextPageToken;\n } while (pageToken);\n\n return files;\n }\n\n async listFolders(parentId: string): Promise<DriveFile[]> {\n const q = `'${parentId}' in parents and mimeType = '${FOLDER_MIME}' and trashed = false`;\n const fields = \"files(id,name,mimeType,modifiedTime)\";\n let files: DriveFile[] = [];\n let pageToken: string | undefined;\n\n do {\n let url = `${DRIVE_API}/files?q=${encodeURIComponent(q)}&fields=nextPageToken,${fields}&spaces=drive`;\n if (pageToken) url += `&pageToken=${pageToken}`;\n const resp = await this.request(url);\n const data = await resp.json();\n files = files.concat(data.files ?? []);\n pageToken = data.nextPageToken;\n } while (pageToken);\n\n return files;\n }\n\n async uploadJsonFile(folderId: string, name: string, data: unknown): Promise<string> {\n const metadata = {\n name,\n parents: [folderId],\n mimeType: \"application/json\",\n };\n\n const boundary = \"backless_boundary\";\n const body = [\n `--${boundary}`,\n \"Content-Type: application/json; charset=UTF-8\",\n \"\",\n JSON.stringify(metadata),\n `--${boundary}`,\n \"Content-Type: application/json\",\n \"\",\n JSON.stringify(data),\n `--${boundary}--`,\n ].join(\"\\r\\n\");\n\n const resp = await this.request(\n `${UPLOAD_API}/files?uploadType=multipart&fields=id`,\n {\n method: \"POST\",\n headers: {\n \"Content-Type\": `multipart/related; boundary=${boundary}`,\n },\n body,\n }\n );\n const result = await resp.json();\n return result.id;\n }\n\n async downloadJsonFile<T = unknown>(fileId: string): Promise<T> {\n const resp = await this.request(`${DRIVE_API}/files/${fileId}?alt=media`);\n return resp.json();\n }\n\n async uploadBinaryFile(folderId: string, name: string, mimeType: string, data: Blob): Promise<string> {\n const metadata = JSON.stringify({ name, parents: [folderId], mimeType });\n const metadataBlob = new Blob([metadata], { type: \"application/json; charset=UTF-8\" });\n const boundary = \"backless_boundary\";\n const body = new Blob([\n `--${boundary}\\r\\nContent-Type: application/json; charset=UTF-8\\r\\n\\r\\n`,\n metadataBlob,\n `\\r\\n--${boundary}\\r\\nContent-Type: ${mimeType}\\r\\n\\r\\n`,\n data,\n `\\r\\n--${boundary}--`,\n ]);\n\n const token = await this.getToken();\n if (!token) throw new Error(\"Not authenticated\");\n const resp = await fetch(`${UPLOAD_API}/files?uploadType=multipart&fields=id`, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": `multipart/related; boundary=${boundary}`,\n },\n body,\n });\n if (!resp.ok) {\n const errBody = await resp.text();\n throw new Error(`Drive API ${resp.status}: ${errBody}`);\n }\n const result = await resp.json();\n return result.id;\n }\n\n async downloadBinaryFile(fileId: string): Promise<Blob> {\n const resp = await this.request(`${DRIVE_API}/files/${fileId}?alt=media`);\n return resp.blob();\n }\n\n async deleteFile(fileId: string): Promise<void> {\n await this.request(`${DRIVE_API}/files/${fileId}`, { method: \"DELETE\" });\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/google-drive-api.ts"],"sourcesContent":["import type { CloudStorageApi, DriveFile } from \"backless-core\";\n\nconst DRIVE_API = \"https://www.googleapis.com/drive/v3\";\nconst UPLOAD_API = \"https://www.googleapis.com/upload/drive/v3\";\nconst FOLDER_MIME = \"application/vnd.google-apps.folder\";\n\n// Drive API query strings use single-quoted literals; escape embedded single quotes as \\'\nfunction escapeDriveQuery(value: string): string {\n return value.replace(/'/g, \"\\\\'\");\n}\n\nexport class GoogleDriveApi implements CloudStorageApi {\n private readonly folderCache = new Map<string, string>();\n\n constructor(private getToken: () => Promise<string | null>) {}\n\n private async headers(): Promise<HeadersInit> {\n const token = await this.getToken();\n if (!token) throw new Error(\"Not authenticated\");\n return { Authorization: `Bearer ${token}` };\n }\n\n private async request(url: string, init?: RequestInit): Promise<Response> {\n const MAX_RETRIES = 3;\n\n for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {\n // headers() is outside the try so auth errors propagate immediately (not retried).\n // It is inside the loop so a fresh token is fetched on each retry attempt.\n const h = await this.headers();\n let resp: Response;\n try {\n resp = await fetch(url, { ...init, headers: { ...h, ...init?.headers } });\n } catch (err) {\n if (attempt < MAX_RETRIES) {\n await sleep(1000 * 2 ** attempt);\n continue;\n }\n throw err;\n }\n\n if (resp.ok) return resp;\n\n const retryable =\n resp.status === 429 ||\n resp.status === 500 ||\n resp.status === 502 ||\n resp.status === 503;\n\n if (retryable && attempt < MAX_RETRIES) {\n let delayMs = 1000 * 2 ** attempt;\n if (resp.status === 429) {\n const ra = resp.headers.get(\"Retry-After\");\n if (ra && /^\\d+$/.test(ra)) delayMs = parseInt(ra, 10) * 1000;\n }\n await sleep(delayMs);\n continue;\n }\n\n const body = await resp.text();\n throw new Error(`Drive API ${resp.status}: ${body}`);\n }\n\n // Unreachable — loop always returns or throws within MAX_RETRIES iterations\n throw new Error(\"Drive API: retry loop exhausted\");\n }\n\n async getOrCreateAppFolder(name: string): Promise<string> {\n return this.ensureFolder(\"root\", name);\n }\n\n async ensureFolder(parentId: string, name: string): Promise<string> {\n const cacheKey = `${parentId}:${name}`;\n const cached = this.folderCache.get(cacheKey);\n if (cached) return cached;\n\n const q = `'${parentId}' in parents and name = '${escapeDriveQuery(name)}' and mimeType = '${FOLDER_MIME}' and trashed = false`;\n const resp = await this.request(\n `${DRIVE_API}/files?q=${encodeURIComponent(q)}&fields=files(id,name)&spaces=drive`\n );\n const data = await resp.json();\n\n let folderId: string;\n if (data.files?.length > 0) {\n folderId = data.files[0].id;\n } else {\n const createResp = await this.request(`${DRIVE_API}/files`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n name,\n mimeType: FOLDER_MIME,\n parents: [parentId],\n }),\n });\n const created = await createResp.json();\n folderId = created.id;\n }\n\n this.folderCache.set(cacheKey, folderId);\n return folderId;\n }\n\n async listFiles(folderId: string): Promise<DriveFile[]> {\n const q = `'${folderId}' in parents and trashed = false`;\n const fields = \"files(id,name,mimeType,modifiedTime)\";\n let files: DriveFile[] = [];\n let pageToken: string | undefined;\n\n do {\n let url = `${DRIVE_API}/files?q=${encodeURIComponent(q)}&fields=nextPageToken,${fields}&spaces=drive&orderBy=name`;\n if (pageToken) url += `&pageToken=${pageToken}`;\n\n const resp = await this.request(url);\n const data = await resp.json();\n files = files.concat(data.files ?? []);\n pageToken = data.nextPageToken;\n } while (pageToken);\n\n return files;\n }\n\n /**\n * Batched form of {@link listFiles}: one query per chunk of folders instead of\n * one per folder. Drive has no documented cap on OR'd `in parents` terms, but\n * the `q` parameter is URL-length bound — 25 folders per query is comfortably\n * inside it. `parents` is requested so results can be grouped back per folder.\n */\n async listFilesInFolders(folderIds: string[]): Promise<Map<string, DriveFile[]>> {\n const CHUNK = 25;\n const result = new Map<string, DriveFile[]>();\n for (const id of folderIds) result.set(id, []);\n\n for (let i = 0; i < folderIds.length; i += CHUNK) {\n const chunk = folderIds.slice(i, i + CHUNK);\n const parentsClause = chunk.map((id) => `'${id}' in parents`).join(\" or \");\n const q = `(${parentsClause}) and trashed = false`;\n const fields = \"files(id,name,mimeType,modifiedTime,parents)\";\n\n let pageToken: string | undefined;\n do {\n let url = `${DRIVE_API}/files?q=${encodeURIComponent(q)}&fields=nextPageToken,${fields}&spaces=drive&orderBy=name&pageSize=1000`;\n if (pageToken) url += `&pageToken=${pageToken}`;\n\n const resp = await this.request(url);\n const data = await resp.json();\n\n for (const file of data.files ?? []) {\n // A file can technically have several parents; attribute it to whichever\n // of the requested folders it belongs to.\n for (const parent of file.parents ?? []) {\n const bucket = result.get(parent);\n if (bucket) bucket.push({ id: file.id, name: file.name, mimeType: file.mimeType, modifiedTime: file.modifiedTime });\n }\n }\n pageToken = data.nextPageToken;\n } while (pageToken);\n }\n\n return result;\n }\n\n async listFolders(parentId: string): Promise<DriveFile[]> {\n const q = `'${parentId}' in parents and mimeType = '${FOLDER_MIME}' and trashed = false`;\n const fields = \"files(id,name,mimeType,modifiedTime)\";\n let files: DriveFile[] = [];\n let pageToken: string | undefined;\n\n do {\n let url = `${DRIVE_API}/files?q=${encodeURIComponent(q)}&fields=nextPageToken,${fields}&spaces=drive`;\n if (pageToken) url += `&pageToken=${pageToken}`;\n const resp = await this.request(url);\n const data = await resp.json();\n files = files.concat(data.files ?? []);\n pageToken = data.nextPageToken;\n } while (pageToken);\n\n return files;\n }\n\n async uploadJsonFile(folderId: string, name: string, data: unknown): Promise<string> {\n const metadata = {\n name,\n parents: [folderId],\n mimeType: \"application/json\",\n };\n\n const boundary = \"backless_boundary\";\n const body = [\n `--${boundary}`,\n \"Content-Type: application/json; charset=UTF-8\",\n \"\",\n JSON.stringify(metadata),\n `--${boundary}`,\n \"Content-Type: application/json\",\n \"\",\n JSON.stringify(data),\n `--${boundary}--`,\n ].join(\"\\r\\n\");\n\n const resp = await this.request(\n `${UPLOAD_API}/files?uploadType=multipart&fields=id`,\n {\n method: \"POST\",\n headers: {\n \"Content-Type\": `multipart/related; boundary=${boundary}`,\n },\n body,\n }\n );\n const result = await resp.json();\n return result.id;\n }\n\n async downloadJsonFile<T = unknown>(fileId: string): Promise<T> {\n const resp = await this.request(`${DRIVE_API}/files/${fileId}?alt=media`);\n return resp.json();\n }\n\n async uploadBinaryFile(folderId: string, name: string, mimeType: string, data: Blob): Promise<string> {\n const metadata = JSON.stringify({ name, parents: [folderId], mimeType });\n const metadataBlob = new Blob([metadata], { type: \"application/json; charset=UTF-8\" });\n const boundary = \"backless_boundary\";\n const body = new Blob([\n `--${boundary}\\r\\nContent-Type: application/json; charset=UTF-8\\r\\n\\r\\n`,\n metadataBlob,\n `\\r\\n--${boundary}\\r\\nContent-Type: ${mimeType}\\r\\n\\r\\n`,\n data,\n `\\r\\n--${boundary}--`,\n ]);\n\n const token = await this.getToken();\n if (!token) throw new Error(\"Not authenticated\");\n const resp = await fetch(`${UPLOAD_API}/files?uploadType=multipart&fields=id`, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": `multipart/related; boundary=${boundary}`,\n },\n body,\n });\n if (!resp.ok) {\n const errBody = await resp.text();\n throw new Error(`Drive API ${resp.status}: ${errBody}`);\n }\n const result = await resp.json();\n return result.id;\n }\n\n async downloadBinaryFile(fileId: string): Promise<Blob> {\n const resp = await this.request(`${DRIVE_API}/files/${fileId}?alt=media`);\n return resp.blob();\n }\n\n async deleteFile(fileId: string): Promise<void> {\n await this.request(`${DRIVE_API}/files/${fileId}`, { method: \"DELETE\" });\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -8,7 +8,7 @@
8
8
  "url": "https://github.com/mikocot/backless/issues"
9
9
  },
10
10
  "homepage": "https://github.com/mikocot/backless#readme",
11
- "version": "0.9.3",
11
+ "version": "0.9.5",
12
12
  "type": "module",
13
13
  "main": "dist/index.js",
14
14
  "types": "dist/index.d.ts",
@@ -24,7 +24,7 @@
24
24
  "test:watch": "vitest"
25
25
  },
26
26
  "dependencies": {
27
- "backless-core": "^0.9.3"
27
+ "backless-core": "^0.9.5"
28
28
  },
29
29
  "devDependencies": {
30
30
  "tsup": "^8.0.0",