@qrush/types 2.1.32 → 2.1.33

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.
@@ -0,0 +1,147 @@
1
+ /**
2
+ * CSV Import Types and Utilities
3
+ *
4
+ * This module provides types, validation rules, and utility functions
5
+ * for importing events from CSV files.
6
+ */
7
+ import { TicketType, IFireEvent, EventCategorySlug } from "./Events.js";
8
+ import { SupportedLanguage } from "./Common.js";
9
+ import musicGenres from "./Genres.js";
10
+ /**
11
+ * Represents a single row from the CSV import file.
12
+ * Column names are human-readable for ease of use by non-technical users.
13
+ */
14
+ export interface CSVEventRow {
15
+ "Event Title": string;
16
+ Description: string;
17
+ "Start Date": string;
18
+ "End Date": string;
19
+ "Venue Name": string;
20
+ City: string;
21
+ "Image URL": string;
22
+ "Ticket Link"?: string;
23
+ "Base Price"?: string;
24
+ "Ticket Options"?: string;
25
+ "Sold Out"?: string;
26
+ Artists?: string;
27
+ Categories?: string;
28
+ "Main Genre"?: string;
29
+ "Sub Genres"?: string;
30
+ Tags?: string;
31
+ "Host Name"?: string;
32
+ Language?: string;
33
+ "Description (DE)"?: string;
34
+ "Description (EN)"?: string;
35
+ "Description (ES)"?: string;
36
+ }
37
+ /**
38
+ * Maps CSV column names to internal IFireEvent field paths.
39
+ * Used for transforming CSV data to Firestore documents.
40
+ */
41
+ export declare const CSV_COLUMN_MAPPING: {
42
+ readonly "Event Title": "name";
43
+ readonly Description: "descriptionMarkdown";
44
+ readonly "Start Date": "eventStart";
45
+ readonly "End Date": "eventEnd";
46
+ readonly "Venue Name": "_venueNameForMatching";
47
+ readonly City: "_cityForMatching";
48
+ readonly "Image URL": "imageURI";
49
+ readonly "Ticket Link": "properties.ticketURL";
50
+ readonly "Base Price": "properties.price";
51
+ readonly "Ticket Options": "properties.tickets";
52
+ readonly "Sold Out": "properties.ticketsSoldOut";
53
+ readonly Artists: "artists";
54
+ readonly Categories: "categorySlug";
55
+ readonly "Main Genre": "genres.mainGenre";
56
+ readonly "Sub Genres": "genres.subGenres";
57
+ readonly Tags: "tags";
58
+ readonly "Host Name": "hostName";
59
+ readonly Language: "primaryLang";
60
+ readonly "Description (DE)": "descriptionTranslations.de";
61
+ readonly "Description (EN)": "descriptionTranslations.en";
62
+ readonly "Description (ES)": "descriptionTranslations.es";
63
+ };
64
+ export type CSVColumnName = keyof typeof CSV_COLUMN_MAPPING;
65
+ export type CSVFieldPath = (typeof CSV_COLUMN_MAPPING)[CSVColumnName];
66
+ export type CSVFieldType = "string" | "date" | "url" | "number" | "boolean" | "list";
67
+ export interface ValidationResult {
68
+ valid: boolean;
69
+ error?: string;
70
+ transformed?: unknown;
71
+ }
72
+ export interface CSVValidationRule {
73
+ required: boolean;
74
+ type: CSVFieldType;
75
+ validate?: (value: string) => ValidationResult;
76
+ }
77
+ export interface CSVRowValidationResult {
78
+ rowIndex: number;
79
+ valid: boolean;
80
+ errors: {
81
+ field: string;
82
+ message: string;
83
+ }[];
84
+ warnings: {
85
+ field: string;
86
+ message: string;
87
+ }[];
88
+ data?: Partial<IFireEvent>;
89
+ }
90
+ export interface CSVImportSummary {
91
+ totalRows: number;
92
+ validRows: number;
93
+ invalidRows: number;
94
+ warningRows: number;
95
+ results: CSVRowValidationResult[];
96
+ }
97
+ /**
98
+ * Validation rules for each CSV column.
99
+ * Defines required/optional status, type, and custom validation functions.
100
+ */
101
+ export declare const CSV_VALIDATION_RULES: Record<keyof CSVEventRow, CSVValidationRule>;
102
+ /**
103
+ * Parses a date string in EU format (DD.MM.YYYY HH:mm) or ISO 8601.
104
+ * @param dateStr The date string to parse
105
+ * @returns A Date object or null if parsing fails
106
+ */
107
+ export declare function parseEUDate(dateStr: string): Date | null;
108
+ /**
109
+ * Parses ticket options string in format "Name:Price;Name:Price"
110
+ * @param optionsStr The ticket options string
111
+ * @returns Array of TicketType objects
112
+ */
113
+ export declare function parseTicketOptions(optionsStr: string): TicketType[];
114
+ /**
115
+ * Parses a comma-separated list into an array of trimmed strings
116
+ * @param str The comma-separated string
117
+ * @returns Array of trimmed strings
118
+ */
119
+ export declare function parseCommaSeparatedList(str: string): string[];
120
+ /**
121
+ * Validates if a string is a valid event category slug
122
+ */
123
+ export declare function isValidEventCategorySlug(slug: string): slug is EventCategorySlug;
124
+ /**
125
+ * Validates if a string is a valid music genre
126
+ */
127
+ export declare function isValidGenre(genre: string): genre is keyof typeof musicGenres;
128
+ /**
129
+ * Validates if a string is a valid supported language
130
+ */
131
+ export declare function isValidLanguage(lang: string): lang is SupportedLanguage;
132
+ /**
133
+ * Validates a single CSV row and returns validation result
134
+ */
135
+ export declare function validateCSVRow(row: Partial<CSVEventRow>, rowIndex: number): CSVRowValidationResult;
136
+ /**
137
+ * Returns the required CSV headers for import
138
+ */
139
+ export declare function getCSVHeaders(): string[];
140
+ /**
141
+ * Returns only the required CSV headers
142
+ */
143
+ export declare function getRequiredCSVHeaders(): string[];
144
+ /**
145
+ * Sample CSV template row with example data
146
+ */
147
+ export declare const CSV_TEMPLATE_EXAMPLE: CSVEventRow;
@@ -0,0 +1,529 @@
1
+ /**
2
+ * CSV Import Types and Utilities
3
+ *
4
+ * This module provides types, validation rules, and utility functions
5
+ * for importing events from CSV files.
6
+ */
7
+ import { EVENT_CATEGORY_SLUGS } from "./Common.js";
8
+ import musicGenres from "./Genres.js";
9
+ // ==========================================
10
+ // B. CSV_COLUMN_MAPPING Constant
11
+ // ==========================================
12
+ /**
13
+ * Maps CSV column names to internal IFireEvent field paths.
14
+ * Used for transforming CSV data to Firestore documents.
15
+ */
16
+ export const CSV_COLUMN_MAPPING = {
17
+ "Event Title": "name",
18
+ Description: "descriptionMarkdown",
19
+ "Start Date": "eventStart",
20
+ "End Date": "eventEnd",
21
+ "Venue Name": "_venueNameForMatching", // Internal use for location matching
22
+ City: "_cityForMatching", // Internal use for location matching
23
+ "Image URL": "imageURI",
24
+ "Ticket Link": "properties.ticketURL",
25
+ "Base Price": "properties.price",
26
+ "Ticket Options": "properties.tickets",
27
+ "Sold Out": "properties.ticketsSoldOut",
28
+ Artists: "artists",
29
+ Categories: "categorySlug",
30
+ "Main Genre": "genres.mainGenre",
31
+ "Sub Genres": "genres.subGenres",
32
+ Tags: "tags",
33
+ "Host Name": "hostName",
34
+ Language: "primaryLang",
35
+ "Description (DE)": "descriptionTranslations.de",
36
+ "Description (EN)": "descriptionTranslations.en",
37
+ "Description (ES)": "descriptionTranslations.es",
38
+ };
39
+ // ==========================================
40
+ // D. CSV_VALIDATION_RULES Constant
41
+ // ==========================================
42
+ /**
43
+ * Validation rules for each CSV column.
44
+ * Defines required/optional status, type, and custom validation functions.
45
+ */
46
+ export const CSV_VALIDATION_RULES = {
47
+ "Event Title": {
48
+ required: true,
49
+ type: "string",
50
+ validate: (value) => {
51
+ if (!value || value.trim().length === 0) {
52
+ return { valid: false, error: "Event title is required" };
53
+ }
54
+ if (value.length > 200) {
55
+ return { valid: false, error: "Event title must be 200 characters or less" };
56
+ }
57
+ return { valid: true, transformed: value.trim() };
58
+ },
59
+ },
60
+ Description: {
61
+ required: true,
62
+ type: "string",
63
+ validate: (value) => {
64
+ if (!value || value.trim().length === 0) {
65
+ return { valid: false, error: "Description is required" };
66
+ }
67
+ return { valid: true, transformed: value.trim() };
68
+ },
69
+ },
70
+ "Start Date": {
71
+ required: true,
72
+ type: "date",
73
+ validate: (value) => {
74
+ const parsed = parseEUDate(value);
75
+ if (!parsed) {
76
+ return {
77
+ valid: false,
78
+ error: "Invalid date format. Use DD.MM.YYYY HH:mm or ISO 8601",
79
+ };
80
+ }
81
+ return { valid: true, transformed: parsed };
82
+ },
83
+ },
84
+ "End Date": {
85
+ required: true,
86
+ type: "date",
87
+ validate: (value) => {
88
+ const parsed = parseEUDate(value);
89
+ if (!parsed) {
90
+ return {
91
+ valid: false,
92
+ error: "Invalid date format. Use DD.MM.YYYY HH:mm or ISO 8601",
93
+ };
94
+ }
95
+ return { valid: true, transformed: parsed };
96
+ },
97
+ },
98
+ "Venue Name": {
99
+ required: true,
100
+ type: "string",
101
+ validate: (value) => {
102
+ if (!value || value.trim().length === 0) {
103
+ return { valid: false, error: "Venue name is required" };
104
+ }
105
+ return { valid: true, transformed: value.trim() };
106
+ },
107
+ },
108
+ City: {
109
+ required: true,
110
+ type: "string",
111
+ validate: (value) => {
112
+ if (!value || value.trim().length === 0) {
113
+ return { valid: false, error: "City is required" };
114
+ }
115
+ return { valid: true, transformed: value.trim() };
116
+ },
117
+ },
118
+ "Image URL": {
119
+ required: true,
120
+ type: "url",
121
+ validate: (value) => {
122
+ if (!value || value.trim().length === 0) {
123
+ return { valid: false, error: "Image URL is required" };
124
+ }
125
+ try {
126
+ new URL(value);
127
+ return { valid: true, transformed: value.trim() };
128
+ }
129
+ catch {
130
+ return { valid: false, error: "Invalid URL format" };
131
+ }
132
+ },
133
+ },
134
+ "Ticket Link": {
135
+ required: false,
136
+ type: "url",
137
+ validate: (value) => {
138
+ if (!value || value.trim().length === 0) {
139
+ return { valid: true, transformed: undefined };
140
+ }
141
+ try {
142
+ new URL(value);
143
+ return { valid: true, transformed: value.trim() };
144
+ }
145
+ catch {
146
+ return { valid: false, error: "Invalid URL format" };
147
+ }
148
+ },
149
+ },
150
+ "Base Price": {
151
+ required: false,
152
+ type: "number",
153
+ validate: (value) => {
154
+ if (!value || value.trim().length === 0) {
155
+ return { valid: true, transformed: undefined };
156
+ }
157
+ const num = parseFloat(value.replace(",", "."));
158
+ if (isNaN(num) || num < 0) {
159
+ return { valid: false, error: "Price must be a positive number" };
160
+ }
161
+ return { valid: true, transformed: num };
162
+ },
163
+ },
164
+ "Ticket Options": {
165
+ required: false,
166
+ type: "string",
167
+ validate: (value) => {
168
+ if (!value || value.trim().length === 0) {
169
+ return { valid: true, transformed: undefined };
170
+ }
171
+ const tickets = parseTicketOptions(value);
172
+ if (tickets.length === 0) {
173
+ return {
174
+ valid: false,
175
+ error: "Invalid ticket format. Use Name:Price;Name:Price",
176
+ };
177
+ }
178
+ return { valid: true, transformed: tickets };
179
+ },
180
+ },
181
+ "Sold Out": {
182
+ required: false,
183
+ type: "boolean",
184
+ validate: (value) => {
185
+ if (!value || value.trim().length === 0) {
186
+ return { valid: true, transformed: undefined };
187
+ }
188
+ const lower = value.toLowerCase().trim();
189
+ if (lower === "true" || lower === "yes" || lower === "1") {
190
+ return { valid: true, transformed: true };
191
+ }
192
+ if (lower === "false" || lower === "no" || lower === "0") {
193
+ return { valid: true, transformed: false };
194
+ }
195
+ return {
196
+ valid: false,
197
+ error: 'Invalid boolean. Use true/false, yes/no, or 1/0',
198
+ };
199
+ },
200
+ },
201
+ Artists: {
202
+ required: false,
203
+ type: "list",
204
+ validate: (value) => {
205
+ if (!value || value.trim().length === 0) {
206
+ return { valid: true, transformed: [] };
207
+ }
208
+ const artists = parseCommaSeparatedList(value);
209
+ return { valid: true, transformed: artists };
210
+ },
211
+ },
212
+ Categories: {
213
+ required: false,
214
+ type: "list",
215
+ validate: (value) => {
216
+ if (!value || value.trim().length === 0) {
217
+ return { valid: true, transformed: [] };
218
+ }
219
+ const categories = parseCommaSeparatedList(value);
220
+ // Validate against known category slugs
221
+ const validCategories = categories.filter((cat) => isValidEventCategorySlug(cat));
222
+ const invalidCategories = categories.filter((cat) => !isValidEventCategorySlug(cat));
223
+ if (invalidCategories.length > 0) {
224
+ return {
225
+ valid: false,
226
+ error: `Invalid categories: ${invalidCategories.join(", ")}`,
227
+ };
228
+ }
229
+ return { valid: true, transformed: validCategories };
230
+ },
231
+ },
232
+ "Main Genre": {
233
+ required: false,
234
+ type: "string",
235
+ validate: (value) => {
236
+ if (!value || value.trim().length === 0) {
237
+ return { valid: true, transformed: undefined };
238
+ }
239
+ const genre = value.trim().toLowerCase();
240
+ if (!isValidGenre(genre)) {
241
+ return {
242
+ valid: false,
243
+ error: `Invalid genre: ${genre}. Check allowed genres.`,
244
+ };
245
+ }
246
+ return { valid: true, transformed: genre };
247
+ },
248
+ },
249
+ "Sub Genres": {
250
+ required: false,
251
+ type: "list",
252
+ validate: (value) => {
253
+ if (!value || value.trim().length === 0) {
254
+ return { valid: true, transformed: [] };
255
+ }
256
+ const genres = parseCommaSeparatedList(value);
257
+ return { valid: true, transformed: genres };
258
+ },
259
+ },
260
+ Tags: {
261
+ required: false,
262
+ type: "list",
263
+ validate: (value) => {
264
+ if (!value || value.trim().length === 0) {
265
+ return { valid: true, transformed: [] };
266
+ }
267
+ return { valid: true, transformed: parseCommaSeparatedList(value) };
268
+ },
269
+ },
270
+ "Host Name": {
271
+ required: false,
272
+ type: "string",
273
+ validate: (value) => {
274
+ if (!value || value.trim().length === 0) {
275
+ return { valid: true, transformed: undefined };
276
+ }
277
+ return { valid: true, transformed: value.trim() };
278
+ },
279
+ },
280
+ Language: {
281
+ required: false,
282
+ type: "string",
283
+ validate: (value) => {
284
+ if (!value || value.trim().length === 0) {
285
+ return { valid: true, transformed: undefined };
286
+ }
287
+ const lang = value.toLowerCase().trim();
288
+ if (!isValidLanguage(lang)) {
289
+ return {
290
+ valid: false,
291
+ error: `Invalid language: ${lang}. Use de, en, or es`,
292
+ };
293
+ }
294
+ return { valid: true, transformed: lang };
295
+ },
296
+ },
297
+ "Description (DE)": {
298
+ required: false,
299
+ type: "string",
300
+ validate: (value) => {
301
+ if (!value || value.trim().length === 0) {
302
+ return { valid: true, transformed: undefined };
303
+ }
304
+ return { valid: true, transformed: value.trim() };
305
+ },
306
+ },
307
+ "Description (EN)": {
308
+ required: false,
309
+ type: "string",
310
+ validate: (value) => {
311
+ if (!value || value.trim().length === 0) {
312
+ return { valid: true, transformed: undefined };
313
+ }
314
+ return { valid: true, transformed: value.trim() };
315
+ },
316
+ },
317
+ "Description (ES)": {
318
+ required: false,
319
+ type: "string",
320
+ validate: (value) => {
321
+ if (!value || value.trim().length === 0) {
322
+ return { valid: true, transformed: undefined };
323
+ }
324
+ return { valid: true, transformed: value.trim() };
325
+ },
326
+ },
327
+ };
328
+ // ==========================================
329
+ // E. Parsing Utilities
330
+ // ==========================================
331
+ /**
332
+ * Parses a date string in EU format (DD.MM.YYYY HH:mm) or ISO 8601.
333
+ * @param dateStr The date string to parse
334
+ * @returns A Date object or null if parsing fails
335
+ */
336
+ export function parseEUDate(dateStr) {
337
+ if (!dateStr || typeof dateStr !== "string") {
338
+ return null;
339
+ }
340
+ const trimmed = dateStr.trim();
341
+ // Try ISO 8601 first
342
+ const isoDate = new Date(trimmed);
343
+ if (!isNaN(isoDate.getTime()) && trimmed.includes("-")) {
344
+ return isoDate;
345
+ }
346
+ // Try EU format: DD.MM.YYYY HH:mm or DD.MM.YYYY
347
+ const euPattern = /^(\d{1,2})\.(\d{1,2})\.(\d{4})(?:\s+(\d{1,2}):(\d{2}))?$/;
348
+ const match = trimmed.match(euPattern);
349
+ if (!match) {
350
+ return null;
351
+ }
352
+ const day = parseInt(match[1], 10);
353
+ const month = parseInt(match[2], 10) - 1; // JavaScript months are 0-indexed
354
+ const year = parseInt(match[3], 10);
355
+ const hour = match[4] ? parseInt(match[4], 10) : 0;
356
+ const minute = match[5] ? parseInt(match[5], 10) : 0;
357
+ // Validate ranges
358
+ if (month < 0 ||
359
+ month > 11 ||
360
+ day < 1 ||
361
+ day > 31 ||
362
+ hour < 0 ||
363
+ hour > 23 ||
364
+ minute < 0 ||
365
+ minute > 59) {
366
+ return null;
367
+ }
368
+ const date = new Date(year, month, day, hour, minute);
369
+ // Check if the date is valid (e.g., catches Feb 30)
370
+ if (date.getFullYear() !== year ||
371
+ date.getMonth() !== month ||
372
+ date.getDate() !== day) {
373
+ return null;
374
+ }
375
+ return date;
376
+ }
377
+ /**
378
+ * Parses ticket options string in format "Name:Price;Name:Price"
379
+ * @param optionsStr The ticket options string
380
+ * @returns Array of TicketType objects
381
+ */
382
+ export function parseTicketOptions(optionsStr) {
383
+ if (!optionsStr || typeof optionsStr !== "string") {
384
+ return [];
385
+ }
386
+ const tickets = [];
387
+ const options = optionsStr.split(";").map((s) => s.trim());
388
+ for (const option of options) {
389
+ if (!option)
390
+ continue;
391
+ const parts = option.split(":").map((s) => s.trim());
392
+ if (parts.length !== 2)
393
+ continue;
394
+ const [name, priceStr] = parts;
395
+ const price = parseFloat(priceStr.replace(",", "."));
396
+ if (!name || isNaN(price) || price < 0)
397
+ continue;
398
+ tickets.push({
399
+ id: generateTicketId(),
400
+ name,
401
+ price,
402
+ });
403
+ }
404
+ return tickets;
405
+ }
406
+ /**
407
+ * Parses a comma-separated list into an array of trimmed strings
408
+ * @param str The comma-separated string
409
+ * @returns Array of trimmed strings
410
+ */
411
+ export function parseCommaSeparatedList(str) {
412
+ if (!str || typeof str !== "string") {
413
+ return [];
414
+ }
415
+ return str
416
+ .split(",")
417
+ .map((s) => s.trim())
418
+ .filter((s) => s.length > 0);
419
+ }
420
+ // ==========================================
421
+ // F. Helper Functions
422
+ // ==========================================
423
+ /**
424
+ * Generates a unique ID for ticket types
425
+ */
426
+ function generateTicketId() {
427
+ return `ticket_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
428
+ }
429
+ /**
430
+ * Validates if a string is a valid event category slug
431
+ */
432
+ export function isValidEventCategorySlug(slug) {
433
+ return EVENT_CATEGORY_SLUGS.includes(slug.toLowerCase());
434
+ }
435
+ /**
436
+ * Validates if a string is a valid music genre
437
+ */
438
+ export function isValidGenre(genre) {
439
+ return genre in musicGenres;
440
+ }
441
+ /**
442
+ * Validates if a string is a valid supported language
443
+ */
444
+ export function isValidLanguage(lang) {
445
+ return ["de", "en", "es"].includes(lang);
446
+ }
447
+ /**
448
+ * Validates a single CSV row and returns validation result
449
+ */
450
+ export function validateCSVRow(row, rowIndex) {
451
+ const errors = [];
452
+ const warnings = [];
453
+ const data = {};
454
+ for (const [field, rule] of Object.entries(CSV_VALIDATION_RULES)) {
455
+ const columnName = field;
456
+ const value = row[columnName] ?? "";
457
+ // Check required fields
458
+ if (rule.required && (!value || String(value).trim().length === 0)) {
459
+ errors.push({ field: columnName, message: `${columnName} is required` });
460
+ continue;
461
+ }
462
+ // Run custom validation if provided
463
+ if (rule.validate && value) {
464
+ const result = rule.validate(String(value));
465
+ if (!result.valid) {
466
+ if (rule.required) {
467
+ errors.push({ field: columnName, message: result.error ?? "Validation failed" });
468
+ }
469
+ else {
470
+ warnings.push({ field: columnName, message: result.error ?? "Validation failed" });
471
+ }
472
+ }
473
+ else if (result.transformed !== undefined) {
474
+ const fieldPath = CSV_COLUMN_MAPPING[columnName];
475
+ data[fieldPath] = result.transformed;
476
+ }
477
+ }
478
+ }
479
+ return {
480
+ rowIndex,
481
+ valid: errors.length === 0,
482
+ errors,
483
+ warnings,
484
+ data: errors.length === 0 ? data : undefined,
485
+ };
486
+ }
487
+ // ==========================================
488
+ // G. CSV Template
489
+ // ==========================================
490
+ /**
491
+ * Returns the required CSV headers for import
492
+ */
493
+ export function getCSVHeaders() {
494
+ return Object.keys(CSV_VALIDATION_RULES);
495
+ }
496
+ /**
497
+ * Returns only the required CSV headers
498
+ */
499
+ export function getRequiredCSVHeaders() {
500
+ return Object.entries(CSV_VALIDATION_RULES)
501
+ .filter(([, rule]) => rule.required)
502
+ .map(([field]) => field);
503
+ }
504
+ /**
505
+ * Sample CSV template row with example data
506
+ */
507
+ export const CSV_TEMPLATE_EXAMPLE = {
508
+ "Event Title": "Summer Night Party",
509
+ Description: "Join us for an unforgettable summer night!",
510
+ "Start Date": "14.06.2025 22:00",
511
+ "End Date": "15.06.2025 06:00",
512
+ "Venue Name": "Club Matrix",
513
+ City: "Berlin",
514
+ "Image URL": "https://example.com/event-image.jpg",
515
+ "Ticket Link": "https://example.com/tickets",
516
+ "Base Price": "15.00",
517
+ "Ticket Options": "Early Bird:12.00;Regular:15.00;VIP:30.00",
518
+ "Sold Out": "false",
519
+ Artists: "DJ Shadow, Bonobo, Four Tet",
520
+ Categories: "clubnight,party",
521
+ "Main Genre": "techno",
522
+ "Sub Genres": "house,minimal",
523
+ Tags: "summer,nightlife,electronic",
524
+ "Host Name": "Matrix Events",
525
+ Language: "de",
526
+ "Description (DE)": "Feiert mit uns eine unvergessliche Sommernacht!",
527
+ "Description (EN)": "Join us for an unforgettable summer night!",
528
+ "Description (ES)": "Celebra con nosotros una noche de verano inolvidable!",
529
+ };
package/dist/index.d.ts CHANGED
@@ -9,3 +9,4 @@ export * from './Meta.js';
9
9
  export * from './firebase.js';
10
10
  export * from './WebappUsers.js';
11
11
  export * from './Artist.js';
12
+ export * from './CSVImport.js';
package/dist/index.js CHANGED
@@ -9,3 +9,4 @@ export * from './Meta.js';
9
9
  export * from './firebase.js';
10
10
  export * from './WebappUsers.js';
11
11
  export * from './Artist.js';
12
+ export * from './CSVImport.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qrush/types",
3
- "version": "2.1.32",
3
+ "version": "2.1.33",
4
4
  "description": "Shared TypeScript types for the QRush ecosystem",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",