@divizend/scratch-core 1.0.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 (61) hide show
  1. package/basic/demo.ts +11 -0
  2. package/basic/index.ts +490 -0
  3. package/core/Auth.ts +63 -0
  4. package/core/Currency.ts +16 -0
  5. package/core/Env.ts +186 -0
  6. package/core/Fragment.ts +43 -0
  7. package/core/FragmentServingMode.ts +37 -0
  8. package/core/JsonSchemaValidator.ts +173 -0
  9. package/core/ProjectRoot.ts +76 -0
  10. package/core/Scratch.ts +44 -0
  11. package/core/URI.ts +203 -0
  12. package/core/Universe.ts +406 -0
  13. package/core/index.ts +27 -0
  14. package/gsuite/core/GSuite.ts +237 -0
  15. package/gsuite/core/GSuiteAdmin.ts +81 -0
  16. package/gsuite/core/GSuiteOrgConfig.ts +47 -0
  17. package/gsuite/core/GSuiteUser.ts +115 -0
  18. package/gsuite/core/index.ts +21 -0
  19. package/gsuite/documents/Document.ts +173 -0
  20. package/gsuite/documents/Documents.ts +52 -0
  21. package/gsuite/documents/index.ts +19 -0
  22. package/gsuite/drive/Drive.ts +118 -0
  23. package/gsuite/drive/DriveFile.ts +147 -0
  24. package/gsuite/drive/index.ts +19 -0
  25. package/gsuite/gmail/Gmail.ts +430 -0
  26. package/gsuite/gmail/GmailLabel.ts +55 -0
  27. package/gsuite/gmail/GmailMessage.ts +428 -0
  28. package/gsuite/gmail/GmailMessagePart.ts +298 -0
  29. package/gsuite/gmail/GmailThread.ts +97 -0
  30. package/gsuite/gmail/index.ts +5 -0
  31. package/gsuite/gmail/utils.ts +184 -0
  32. package/gsuite/index.ts +28 -0
  33. package/gsuite/spreadsheets/CellValue.ts +71 -0
  34. package/gsuite/spreadsheets/Sheet.ts +128 -0
  35. package/gsuite/spreadsheets/SheetValues.ts +12 -0
  36. package/gsuite/spreadsheets/Spreadsheet.ts +76 -0
  37. package/gsuite/spreadsheets/Spreadsheets.ts +52 -0
  38. package/gsuite/spreadsheets/index.ts +25 -0
  39. package/gsuite/spreadsheets/utils.ts +52 -0
  40. package/gsuite/utils.ts +104 -0
  41. package/http-server/HttpServer.ts +110 -0
  42. package/http-server/NativeHttpServer.ts +1084 -0
  43. package/http-server/index.ts +3 -0
  44. package/http-server/middlewares/01-cors.ts +33 -0
  45. package/http-server/middlewares/02-static.ts +67 -0
  46. package/http-server/middlewares/03-request-logger.ts +159 -0
  47. package/http-server/middlewares/04-body-parser.ts +54 -0
  48. package/http-server/middlewares/05-no-cache.ts +23 -0
  49. package/http-server/middlewares/06-response-handler.ts +39 -0
  50. package/http-server/middlewares/handler-wrapper.ts +250 -0
  51. package/http-server/middlewares/index.ts +37 -0
  52. package/http-server/middlewares/types.ts +27 -0
  53. package/index.ts +24 -0
  54. package/package.json +37 -0
  55. package/queue/EmailQueue.ts +228 -0
  56. package/queue/RateLimiter.ts +54 -0
  57. package/queue/index.ts +2 -0
  58. package/resend/Resend.ts +190 -0
  59. package/resend/index.ts +11 -0
  60. package/s2/S2.ts +335 -0
  61. package/s2/index.ts +11 -0
@@ -0,0 +1,298 @@
1
+ /**
2
+ * GmailMessagePart - Email Part and Attachment Management
3
+ *
4
+ * The GmailMessagePart class represents individual parts of an email message,
5
+ * including body text, HTML content, and file attachments. It implements the
6
+ * Fragment interface to provide consistent access to different content types.
7
+ *
8
+ * Key Features:
9
+ * - MIME part handling and content extraction
10
+ * - Attachment management and downloading
11
+ * - Content encoding and charset handling
12
+ * - Header parsing and metadata access
13
+ * - Multiple serving modes for different use cases
14
+ *
15
+ * This class handles the complex MIME structure of emails and provides
16
+ * a unified interface for accessing various content types within messages.
17
+ *
18
+ * @class GmailMessagePart
19
+ * @implements Fragment
20
+ * @version 1.0.0
21
+ * @author Divizend GmbH
22
+ */
23
+
24
+ import { gmail_v1 } from "googleapis";
25
+ import {
26
+ Gmail,
27
+ GmailMessage,
28
+ Fragment,
29
+ FragmentServingMode,
30
+ GmailMessagePartURI,
31
+ URI,
32
+ Universe,
33
+ } from "../..";
34
+ import { qpDecode } from "./utils";
35
+
36
+ /**
37
+ * Extended message part data with additional properties
38
+ *
39
+ * Extends the Gmail API message part with convenience properties
40
+ * for headers and body content management.
41
+ */
42
+ export type GmailMessagePartData = gmail_v1.Schema$MessagePart & {
43
+ /** Map of lowercase header names to values for easy access */
44
+ headersMap?: { [key: string]: string };
45
+ /** Extended body data with optional buffer for content */
46
+ body?: gmail_v1.Schema$MessagePartBody & {
47
+ buffer?: Buffer;
48
+ };
49
+ };
50
+
51
+ export class GmailMessagePart implements Fragment {
52
+ /**
53
+ * Creates a new GmailMessagePart instance
54
+ *
55
+ * The constructor automatically processes headers into a map for
56
+ * efficient lookup and initializes the part with the provided data.
57
+ *
58
+ * @param gmail - Reference to the Gmail service instance
59
+ * @param part - Raw message part data from Gmail API
60
+ * @param messageId - ID of the parent message
61
+ * @param isFull - Whether the part contains full content
62
+ */
63
+ constructor(
64
+ private readonly gmail: Gmail,
65
+ public readonly part: GmailMessagePartData,
66
+ public readonly messageId: string,
67
+ public readonly isFull: boolean
68
+ ) {
69
+ // Create a map of lowercase header names to values for efficient lookup
70
+ const headersMap: { [key: string]: string } = {};
71
+ for (const header of this.part.headers || []) {
72
+ headersMap[header.name!.toLowerCase()] = header.value!;
73
+ }
74
+ this.part.headersMap = headersMap;
75
+ }
76
+
77
+ /**
78
+ * Creates a GmailMessagePart from a URI
79
+ *
80
+ * This factory method parses the URI to extract the email address,
81
+ * message ID, and part ID, then creates the appropriate instances
82
+ * to fetch the message part.
83
+ *
84
+ * @param universe - Reference to the central Universe instance
85
+ * @param uri - URI identifying the message part to retrieve
86
+ * @returns Promise<GmailMessagePart> - The requested message part
87
+ */
88
+ static async fromURI(
89
+ universe: Universe,
90
+ uri: URI
91
+ ): Promise<GmailMessagePart> {
92
+ const gmailMessagePartUri = GmailMessagePartURI.fromURI(uri);
93
+ const gmail = universe.gsuite.user(gmailMessagePartUri.email).gmail();
94
+ return GmailMessagePart.fromMessageIdAndPartId(
95
+ gmail,
96
+ gmailMessagePartUri.messageId,
97
+ gmailMessagePartUri.partId
98
+ );
99
+ }
100
+
101
+ /**
102
+ * Creates a GmailMessagePart from message ID and part ID
103
+ *
104
+ * This factory method fetches the full message first, then
105
+ * locates the specific part within the message structure.
106
+ *
107
+ * @param gmail - Gmail service instance for the user
108
+ * @param messageId - Gmail message identifier
109
+ * @param partId - Gmail message part identifier
110
+ * @returns Promise<GmailMessagePart> - The requested message part
111
+ */
112
+ static async fromMessageIdAndPartId(
113
+ gmail: Gmail,
114
+ messageId: string,
115
+ partId: string
116
+ ): Promise<GmailMessagePart> {
117
+ const message = await GmailMessage.fromMessageId(gmail, messageId);
118
+ return GmailMessagePart.fromMessageAndPartId(gmail, message, partId);
119
+ }
120
+
121
+ /**
122
+ * Creates a GmailMessagePart from an existing message and part ID
123
+ *
124
+ * This factory method searches through the message's MIME structure
125
+ * to find the part with the specified ID, handling nested multipart
126
+ * messages recursively.
127
+ *
128
+ * @param gmail - Gmail service instance for the user
129
+ * @param message - Gmail message containing the part
130
+ * @param partId - Gmail message part identifier
131
+ * @returns Promise<GmailMessagePart> - The requested message part
132
+ * @throws Error if the part is not found in the message
133
+ */
134
+ static async fromMessageAndPartId(
135
+ gmail: Gmail,
136
+ message: GmailMessage,
137
+ partId: string
138
+ ): Promise<GmailMessagePart> {
139
+ // Recursive function to find a part by ID in the MIME structure
140
+ const findPart = (part: any) => {
141
+ if (part.partId === partId) {
142
+ return part;
143
+ }
144
+ if (Array.isArray(part.parts)) {
145
+ for (const subPart of part.parts) {
146
+ const body: any = findPart(subPart);
147
+ if (body) {
148
+ return body;
149
+ }
150
+ }
151
+ }
152
+ return null;
153
+ };
154
+
155
+ const part = findPart(message.data.payload);
156
+ if (!part) {
157
+ throw new Error(`Part not found: ${partId}`);
158
+ }
159
+
160
+ return GmailMessagePart.fromMessageAndPart(gmail, message, part);
161
+ }
162
+
163
+ /**
164
+ * Creates a GmailMessagePart from an existing message and part data
165
+ *
166
+ * This factory method creates a prototype part and then fetches
167
+ * the full content if needed.
168
+ *
169
+ * @param gmail - Gmail service instance for the user
170
+ * @param message - Gmail message containing the part
171
+ * @param part - Raw part data from the message
172
+ * @returns Promise<GmailMessagePart> - The full message part
173
+ */
174
+ static async fromMessageAndPart(
175
+ gmail: Gmail,
176
+ message: GmailMessage,
177
+ part: GmailMessagePartData
178
+ ): Promise<GmailMessagePart> {
179
+ const proto = new GmailMessagePart(gmail, part, message.message.id!, false);
180
+ return proto.fetch();
181
+ }
182
+
183
+ /**
184
+ * Fetches the full content of the message part
185
+ *
186
+ * This method retrieves the complete part content including
187
+ * body data and attachments. It handles different content
188
+ * encoding methods and attachment downloading.
189
+ *
190
+ * @returns Promise<GmailMessagePart> - Part with full content
191
+ */
192
+ async fetch(): Promise<GmailMessagePart> {
193
+ if (this.isFull) {
194
+ return this;
195
+ }
196
+
197
+ let buffer: Buffer | undefined = undefined;
198
+ if (this.part.body?.data) {
199
+ // Decode base64-encoded content
200
+ buffer = Buffer.from(this.part.body.data, "base64");
201
+ } else if (this.part.body?.attachmentId) {
202
+ // Download attachment if it's an attachment
203
+ buffer = Buffer.from(
204
+ (
205
+ await this.gmail.gmail.users.messages.attachments.get({
206
+ userId: "me",
207
+ messageId: this.messageId,
208
+ id: this.part.body.attachmentId,
209
+ })
210
+ ).data.data!,
211
+ "base64"
212
+ );
213
+ } else {
214
+ // If no data and no attachment, return an empty buffer
215
+ buffer = Buffer.alloc(0);
216
+ }
217
+
218
+ return new GmailMessagePart(
219
+ this.gmail,
220
+ {
221
+ ...this.part,
222
+ body: {
223
+ ...this.part.body,
224
+ buffer: buffer!,
225
+ },
226
+ },
227
+ this.messageId,
228
+ true
229
+ );
230
+ }
231
+
232
+ get uri() {
233
+ return `gmail://${this.gmail.email}/message/${this.messageId}/part/${this.part.partId}`;
234
+ }
235
+
236
+ async serve(format: FragmentServingMode): Promise<{
237
+ headers: { name: string; value: string }[];
238
+ data: Buffer;
239
+ }> {
240
+ if (!this.isFull) {
241
+ throw new Error("Part is not full");
242
+ }
243
+
244
+ if (format === FragmentServingMode.ORIGINAL) {
245
+ return {
246
+ headers: this.part.headers!.map((header) => ({
247
+ name: header.name!,
248
+ value: header.value!,
249
+ })),
250
+ data: this.part.body?.buffer || Buffer.alloc(0),
251
+ };
252
+ } else if (format === FragmentServingMode.JSON) {
253
+ return {
254
+ headers: [{ name: "Content-Type", value: "application/json" }],
255
+ data: Buffer.from(JSON.stringify(this.part)),
256
+ };
257
+ } else {
258
+ throw new Error(`Unknown serving mode: ${format}`);
259
+ }
260
+ }
261
+
262
+ getHeader(name: string): string | undefined {
263
+ return this.part.headersMap?.[name.toLowerCase()];
264
+ }
265
+
266
+ getCharset(): BufferEncoding {
267
+ const ct = this.getHeader("content-type") || "";
268
+ const m = /charset\s*=\s*"?([^";]+)"?/i.exec(ct);
269
+ const cs = (m?.[1] || "utf-8").toLowerCase();
270
+ if (/(utf-8|utf8)/i.test(cs)) return "utf8";
271
+ if (/(iso-8859-1|latin1)/i.test(cs)) return "latin1";
272
+ // default to utf-8
273
+ return "utf8";
274
+ }
275
+
276
+ async asText(): Promise<string> {
277
+ if (!this.isFull) {
278
+ throw new Error("Part is not full");
279
+ }
280
+
281
+ // decode transfer-encoding if necessary (ignore base64, it's already decoded)
282
+ let raw = this.part.body?.buffer!;
283
+ const cte = (
284
+ this.getHeader("content-transfer-encoding") || ""
285
+ ).toLowerCase();
286
+ if (cte === "quoted-printable") {
287
+ raw = qpDecode(raw);
288
+ }
289
+
290
+ // always decode with utf8 first (because some emails wrongly say that they are iso-8859-1, but they are actually utf8)
291
+ const enc = this.getCharset();
292
+ const ret = raw.toString("utf8");
293
+ if (Buffer.from(ret, "utf8").equals(raw)) {
294
+ return ret; // valid utf8
295
+ }
296
+ return raw.toString(enc);
297
+ }
298
+ }
@@ -0,0 +1,97 @@
1
+ /**
2
+ * GmailThread - Email Conversation Management
3
+ *
4
+ * The GmailThread class represents a conversation thread in Gmail, which
5
+ * groups related email messages together. Threads provide a way to view
6
+ * the complete conversation history in a single, organized view.
7
+ *
8
+ * Key Features:
9
+ * - Thread metadata and conversation overview
10
+ * - Access to all messages within the thread
11
+ * - Thread subject and snippet extraction
12
+ *
13
+ * This class simplifies working with email conversations by providing
14
+ * a high-level interface to thread properties and message collections.
15
+ *
16
+ * @class GmailThread
17
+ * @version 1.0.0
18
+ * @author Divizend GmbH
19
+ */
20
+
21
+ import { gmail_v1 } from "googleapis";
22
+ import { Gmail, GmailMessage } from "../..";
23
+
24
+ export class GmailThread {
25
+ /**
26
+ * Creates a new GmailThread instance
27
+ *
28
+ * @param gmail - Reference to the Gmail service instance
29
+ * @param thread - Raw Gmail API thread data
30
+ */
31
+ constructor(
32
+ private readonly gmail: Gmail,
33
+ public readonly thread: gmail_v1.Schema$Thread,
34
+ public readonly messages: GmailMessage[]
35
+ ) {}
36
+
37
+ async fetch(): Promise<GmailThread> {
38
+ if (this.messages.length > 0) {
39
+ return this;
40
+ }
41
+
42
+ const threadFull = await this.gmail.gmail.users.threads.get({
43
+ userId: "me",
44
+ id: this.id!,
45
+ });
46
+
47
+ if (!threadFull.data.messages) {
48
+ throw new Error("Thread has no messages: " + this.id);
49
+ }
50
+
51
+ const messages = threadFull.data.messages?.map((message) => {
52
+ return new GmailMessage(this.gmail, message, true);
53
+ });
54
+
55
+ return new GmailThread(this.gmail, threadFull.data, messages);
56
+ }
57
+
58
+ get id() {
59
+ return this.thread.id!;
60
+ }
61
+
62
+ /**
63
+ * Gets the conversation snippet/preview
64
+ *
65
+ * The snippet provides a brief preview of the conversation,
66
+ * typically showing the most recent message content or subject.
67
+ */
68
+ get snippet() {
69
+ return this.thread.snippet;
70
+ }
71
+
72
+ /**
73
+ * Gets the history ID for change tracking
74
+ *
75
+ * The history ID is used by Gmail to track changes to the thread
76
+ * and enable efficient synchronization of updates.
77
+ */
78
+ get historyId() {
79
+ return this.thread.historyId;
80
+ }
81
+
82
+ /**
83
+ * Gets the subject from the first message in the thread
84
+ *
85
+ * This provides the original conversation subject, which typically
86
+ * remains consistent throughout the thread's lifecycle.
87
+ *
88
+ * @returns The thread subject, or undefined if no messages exist
89
+ */
90
+ get subject(): string | undefined {
91
+ if (this.messages.length === 0) {
92
+ return "No messages";
93
+ }
94
+
95
+ return this.messages[0]!.subject;
96
+ }
97
+ }
@@ -0,0 +1,5 @@
1
+ export * from "./Gmail";
2
+ export * from "./GmailLabel";
3
+ export * from "./GmailMessage";
4
+ export * from "./GmailMessagePart";
5
+ export * from "./GmailThread";
@@ -0,0 +1,184 @@
1
+ /**
2
+ * Gmail Utility Functions
3
+ *
4
+ * This module provides utility functions for Gmail operations including:
5
+ * - Content encoding and decoding (quoted-printable, base64)
6
+ * - HTML processing and sanitization
7
+ * - URL rewriting for embedded content
8
+ * - MIME message formatting and composition
9
+ *
10
+ * These utilities handle the low-level details of email processing,
11
+ * content transformation, and message composition that are common
12
+ * across different Gmail operations.
13
+ *
14
+ * @module GmailUtils
15
+ * @version 1.0.0
16
+ * @author Divizend GmbH
17
+ */
18
+
19
+ /**
20
+ * Decodes quoted-printable encoded content
21
+ *
22
+ * Quoted-printable is a content transfer encoding used in email
23
+ * that represents 8-bit data using only 7-bit printable ASCII characters.
24
+ * This function handles both soft line breaks and hex-encoded bytes.
25
+ *
26
+ * @param buf - Buffer containing quoted-printable encoded data
27
+ * @returns Buffer with decoded content
28
+ */
29
+ export function qpDecode(buf: Buffer): Buffer {
30
+ // Convert to string for ease; handle soft line breaks and =XX hex
31
+ let s = buf.toString("utf8");
32
+ // remove soft line breaks
33
+ s = s.replace(/=\r?\n/g, "");
34
+ // replace =HH hex bytes
35
+ s = s.replace(/=([A-Fa-f0-9]{2})/g, (_, hex) =>
36
+ String.fromCharCode(parseInt(hex, 16))
37
+ );
38
+ return Buffer.from(s, "utf8");
39
+ }
40
+
41
+ /**
42
+ * Escapes special regex characters in a string
43
+ *
44
+ * This function escapes characters that have special meaning in regular
45
+ * expressions, allowing the string to be used safely in regex patterns.
46
+ *
47
+ * @param s - String to escape
48
+ * @returns String with regex special characters escaped
49
+ */
50
+ export function escapeRegExp(s: string) {
51
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
52
+ }
53
+
54
+ /**
55
+ * Rewrites CID URLs in HTML/CSS to fragment URIs
56
+ *
57
+ * Content-ID (CID) URLs are used in multipart emails to reference
58
+ * embedded content like images. This function converts them to
59
+ * fragment URIs that can be served by the system.
60
+ *
61
+ * @param html - HTML content containing CID URLs
62
+ * @param uri - Base URI for the message
63
+ * @param cidToPart - Mapping of CID values to part IDs
64
+ * @returns HTML with CID URLs rewritten to fragment URIs
65
+ */
66
+ export function rewriteCidUrls(
67
+ html: string,
68
+ uri: string,
69
+ cidToPart: { [key: string]: string }
70
+ ): string {
71
+ // To avoid partial overlaps, replace longest CIDs first
72
+ const keys = Object.keys(cidToPart).sort((a, b) => b.length - a.length);
73
+ for (const cid of keys) {
74
+ const partId = cidToPart[cid];
75
+ const url = `/fragment?uri=${encodeURIComponent(uri + "/part/" + partId)}`;
76
+ const pat = new RegExp(
77
+ `cid:(?:%3C|<)?${escapeRegExp(cid)}(?:%3E|>)?`,
78
+ "gi"
79
+ );
80
+ html = html.replace(pat, url);
81
+ }
82
+ return html;
83
+ }
84
+
85
+ /**
86
+ * Converts a buffer or string to base64url encoding
87
+ *
88
+ * Base64url is a URL-safe variant of base64 encoding that replaces
89
+ * '+' with '-' and '/' with '_', and removes padding characters.
90
+ * This is commonly used in JWT tokens and other web-safe encodings.
91
+ *
92
+ * @param buf - Buffer or string to encode
93
+ * @returns Base64url encoded string
94
+ */
95
+ export function base64Url(buf: Buffer | string) {
96
+ return (typeof buf === "string" ? Buffer.from(buf, "utf8") : buf)
97
+ .toString("base64")
98
+ .replace(/\+/g, "-")
99
+ .replace(/\//g, "_")
100
+ .replace(/=+$/g, "");
101
+ }
102
+
103
+ /**
104
+ * Chunks a string into 76-character lines
105
+ *
106
+ * This function breaks long strings into lines of 76 characters
107
+ * or less, which is the standard line length for MIME messages.
108
+ * Each line is terminated with CRLF (\r\n).
109
+ *
110
+ * @param s - String to chunk
111
+ * @returns String with line breaks every 76 characters
112
+ */
113
+ export function chunk76(s: string) {
114
+ return s.replace(/.{1,76}/g, "$&\r\n");
115
+ }
116
+
117
+ /**
118
+ * Escapes HTML special characters
119
+ *
120
+ * Converts HTML special characters to their entity equivalents
121
+ * to prevent HTML injection and ensure safe content display.
122
+ *
123
+ * @param s - String containing HTML content
124
+ * @returns String with HTML special characters escaped
125
+ */
126
+ export function escapeHtml(s: string) {
127
+ return s.replace(
128
+ /[&<>"']/g,
129
+ (c) =>
130
+ ({
131
+ "&": "&amp;",
132
+ "<": "&lt;",
133
+ ">": "&gt;",
134
+ '"': "&quot;",
135
+ "'": "&#39;",
136
+ }[c]!)
137
+ );
138
+ }
139
+
140
+ /**
141
+ * Converts plain text to HTML with line breaks
142
+ *
143
+ * This function wraps plain text in HTML div tags and converts
144
+ * line breaks to HTML br tags for proper display in web browsers.
145
+ *
146
+ * @param s - Plain text string
147
+ * @returns HTML string with line breaks converted to br tags
148
+ */
149
+ export function htmlFromText(s: string) {
150
+ return `<div>${escapeHtml(s).replace(/\r?\n/g, "<br>")}</div>`;
151
+ }
152
+
153
+ /**
154
+ * Strips HTML tags to extract plain text
155
+ *
156
+ * Removes all HTML tags and converts common HTML elements like
157
+ * br and p tags to appropriate line breaks for plain text output.
158
+ *
159
+ * @param html - HTML string to convert
160
+ * @returns Plain text with HTML tags removed
161
+ */
162
+ export function stripHtmlToText(html: string) {
163
+ return html
164
+ .replace(/<br\s*\/?>/gi, "\n")
165
+ .replace(/<\/p>/gi, "\n\n")
166
+ .replace(/<[^>]+>/g, "")
167
+ .trim();
168
+ }
169
+
170
+ /**
171
+ * Encodes display names for email headers
172
+ *
173
+ * This function handles display names in email headers, particularly
174
+ * for international characters. It uses base64 encoding for non-ASCII
175
+ * names and proper quoting for ASCII names.
176
+ *
177
+ * @param name - Display name to encode
178
+ * @returns Properly encoded display name for email headers
179
+ */
180
+ export function encodeDisplayName(name: string) {
181
+ return /[^\x00-\x7F]/.test(name)
182
+ ? `=?UTF-8?B?${Buffer.from(name, "utf8").toString("base64")}?=`
183
+ : `"${name.replace(/"/g, '\\"')}"`;
184
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * GSuite Module - Google Workspace Integration
3
+ *
4
+ * This module provides comprehensive integration with Google Workspace services
5
+ * including Gmail, Google Drive, Google Sheets, Google Docs, and administrative
6
+ * functions.
7
+ *
8
+ * The module is organized into specialized submodules:
9
+ * - core: Organization management and user authentication
10
+ * - gmail: Email processing and management
11
+ * - drive: File storage and management
12
+ * - spreadsheets: Data analysis and spreadsheet operations
13
+ * - documents: Document creation and processing
14
+ *
15
+ * All services support multi-organization deployments with proper isolation
16
+ * and enterprise-grade security through service account authentication.
17
+ *
18
+ * @module GSuite
19
+ * @version 1.0.0
20
+ * @author Divizend GmbH
21
+ */
22
+
23
+ export * from "./core";
24
+ export * from "./documents";
25
+ export * from "./drive";
26
+ export * from "./gmail";
27
+ export * from "./spreadsheets";
28
+ export * from "./utils";
@@ -0,0 +1,71 @@
1
+ import { sheets_v4 } from "googleapis";
2
+ import { jsDateToSheetsSerial } from "./utils";
3
+
4
+ export enum CellFormat {
5
+ Date_de = "Date_de",
6
+ Currency_EUR_de = "Currency_EUR_de",
7
+ RightAligned = "RightAligned",
8
+ }
9
+
10
+ const CellFormats: { [key in CellFormat]: sheets_v4.Schema$CellFormat } = {
11
+ [CellFormat.Date_de]: {
12
+ numberFormat: {
13
+ type: "DATE",
14
+ pattern: "dd.mm.yyyy",
15
+ },
16
+ },
17
+ [CellFormat.Currency_EUR_de]: {
18
+ numberFormat: {
19
+ type: "CURRENCY",
20
+ pattern: "#,##0.00 €",
21
+ },
22
+ },
23
+ [CellFormat.RightAligned]: {
24
+ horizontalAlignment: "RIGHT",
25
+ },
26
+ };
27
+
28
+ function isDateCellFormat(
29
+ format: CellFormat | CellFormat[] | undefined
30
+ ): boolean {
31
+ if (Array.isArray(format)) {
32
+ return format.some((f) => isDateCellFormat(f));
33
+ }
34
+ return format === CellFormat.Date_de;
35
+ }
36
+
37
+ export type CellValue =
38
+ | string
39
+ | number
40
+ | { value: any; format?: CellFormat | CellFormat[] };
41
+
42
+ export function transformCellValueForSheets(
43
+ value: CellValue
44
+ ): sheets_v4.Schema$CellData {
45
+ const newCell: sheets_v4.Schema$CellData = {};
46
+ if (typeof value === "string") {
47
+ newCell.userEnteredValue = { stringValue: value };
48
+ } else if (typeof value === "number") {
49
+ newCell.userEnteredValue = { numberValue: value };
50
+ } else if (typeof value === "object") {
51
+ if (isDateCellFormat(value.format)) {
52
+ newCell.userEnteredValue = {
53
+ numberValue: jsDateToSheetsSerial(value.value),
54
+ };
55
+ } else if (typeof value.value === "string") {
56
+ newCell.userEnteredValue = { stringValue: value.value };
57
+ } else if (typeof value.value === "number") {
58
+ newCell.userEnteredValue = { numberValue: value.value };
59
+ }
60
+
61
+ if (Array.isArray(value.format)) {
62
+ newCell.userEnteredFormat = Object.assign(
63
+ {},
64
+ ...value.format.map((f) => CellFormats[f])
65
+ );
66
+ } else if (value.format) {
67
+ newCell.userEnteredFormat = CellFormats[value.format];
68
+ }
69
+ }
70
+ return newCell;
71
+ }