@nubitio/core 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/index.cjs ADDED
@@ -0,0 +1,946 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region \0rolldown/runtime.js
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
+ key = keys[i];
12
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
13
+ get: ((k) => from[k]).bind(null, key),
14
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
15
+ });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
20
+ value: mod,
21
+ enumerable: true
22
+ }) : target, mod));
23
+ //#endregion
24
+ let react = require("react");
25
+ react = __toESM(react, 1);
26
+ let react_jsx_runtime = require("react/jsx-runtime");
27
+ let react_i18next = require("react-i18next");
28
+ let i18next = require("i18next");
29
+ i18next = __toESM(i18next, 1);
30
+ //#region packages/core/config/CoreConfig.tsx
31
+ const _coreConfig = {
32
+ locale: "es",
33
+ timezone: "UTC",
34
+ apiBaseUrl: "/api/"
35
+ };
36
+ /**
37
+ * Configure core runtime values (locale, timezone, apiBaseUrl).
38
+ * This updates the module-level singleton so it can be read from non-React code
39
+ * (e.g. inside defineResource, entityField builders, DateUtils, etc.).
40
+ */
41
+ function configureCore(config) {
42
+ if (config.locale !== void 0) _coreConfig.locale = config.locale;
43
+ if (config.timezone !== void 0) _coreConfig.timezone = config.timezone;
44
+ if (config.apiBaseUrl !== void 0) _coreConfig.apiBaseUrl = config.apiBaseUrl;
45
+ }
46
+ /**
47
+ * @deprecated Use configureCore() instead.
48
+ */
49
+ const configureCoreDate = configureCore;
50
+ function getCoreLocale() {
51
+ return _coreConfig.locale;
52
+ }
53
+ function getCoreTimezone() {
54
+ return _coreConfig.timezone;
55
+ }
56
+ function getCoreApiBaseUrl() {
57
+ return _coreConfig.apiBaseUrl;
58
+ }
59
+ const CoreConfigContext = react.default.createContext(_coreConfig);
60
+ const CoreConfigProvider = ({ locale, timezone, apiBaseUrl, children }) => {
61
+ configureCore({
62
+ locale,
63
+ timezone,
64
+ apiBaseUrl
65
+ });
66
+ const value = react.default.useMemo(() => ({
67
+ locale,
68
+ timezone,
69
+ apiBaseUrl
70
+ }), [
71
+ locale,
72
+ timezone,
73
+ apiBaseUrl
74
+ ]);
75
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CoreConfigContext.Provider, {
76
+ value,
77
+ children
78
+ });
79
+ };
80
+ function useCoreConfig() {
81
+ return react.default.useContext(CoreConfigContext);
82
+ }
83
+ //#endregion
84
+ //#region packages/core/date/DateUtils.ts
85
+ /**
86
+ * Recommended default for new projects using @nubitio/core.
87
+ * You should almost always override this via CoreConfigProvider.
88
+ */
89
+ const DEFAULT_TIMEZONE = "UTC";
90
+ function coreDateParts(date) {
91
+ const parts = new Intl.DateTimeFormat("en-CA", {
92
+ timeZone: getCoreTimezone(),
93
+ year: "numeric",
94
+ month: "2-digit",
95
+ day: "2-digit"
96
+ }).formatToParts(date);
97
+ const get = (type) => parseInt(parts.find((part) => part.type === type).value, 10);
98
+ return {
99
+ year: get("year"),
100
+ month: get("month"),
101
+ day: get("day")
102
+ };
103
+ }
104
+ var DateUtils = class {
105
+ static dateFormatter(date) {
106
+ const { year, month, day } = coreDateParts(date);
107
+ return `${year}-${month < 10 ? `0${month}` : month}-${day < 10 ? `0${day}` : day}`;
108
+ }
109
+ static dateParser(dateString) {
110
+ if (!dateString) return /* @__PURE__ */ new Date();
111
+ const dateParts = dateString.split("-");
112
+ const year = parseInt(dateParts[0], 10);
113
+ const month = parseInt(dateParts[1], 10);
114
+ const day = parseInt(dateParts[2], 10);
115
+ if (!Number.isNaN(year) && !Number.isNaN(month) && !Number.isNaN(day)) return new Date(year, month - 1, day);
116
+ return /* @__PURE__ */ new Date();
117
+ }
118
+ static format(dateString, formatOptions) {
119
+ return new Date(dateString).toLocaleDateString(getCoreLocale(), {
120
+ timeZone: getCoreTimezone(),
121
+ ...formatOptions ?? {
122
+ day: "numeric",
123
+ month: "short",
124
+ year: "numeric"
125
+ }
126
+ });
127
+ }
128
+ static addDays(fieldValue, days) {
129
+ const date = new Date(fieldValue);
130
+ date.setDate(date.getDate() + days);
131
+ return date;
132
+ }
133
+ static addMonths(fieldValue, months) {
134
+ const date = new Date(fieldValue);
135
+ date.setMonth(date.getMonth() + months);
136
+ return date;
137
+ }
138
+ static addYears(fieldValue, years) {
139
+ const date = new Date(fieldValue);
140
+ date.setFullYear(date.getFullYear() + years);
141
+ return date;
142
+ }
143
+ static dayDiff(startDate, endDate) {
144
+ const dateStart = new Date(startDate);
145
+ const dateEnd = new Date(endDate);
146
+ const timeDiff = Math.abs(dateEnd.getTime() - dateStart.getTime());
147
+ return Math.ceil(timeDiff / (1e3 * 3600 * 24));
148
+ }
149
+ static monthDiff(startDate, endDate) {
150
+ const dateStart = new Date(startDate);
151
+ const dateEnd = new Date(endDate);
152
+ let months = (dateEnd.getFullYear() - dateStart.getFullYear()) * 12;
153
+ months -= dateStart.getMonth();
154
+ months += dateEnd.getMonth();
155
+ return months <= 0 ? 0 : months;
156
+ }
157
+ };
158
+ //#endregion
159
+ //#region packages/core/event/EventHook.ts
160
+ const listeners = /* @__PURE__ */ new Map();
161
+ function subscribe(name, handler) {
162
+ if (!listeners.has(name)) listeners.set(name, /* @__PURE__ */ new Set());
163
+ const set = listeners.get(name);
164
+ set.add(handler);
165
+ const subscription = {
166
+ unsubscribe: () => {
167
+ set.delete(handler);
168
+ },
169
+ add: (teardown) => {
170
+ const outer = subscription.unsubscribe;
171
+ subscription.unsubscribe = () => {
172
+ outer();
173
+ teardown();
174
+ };
175
+ return subscription;
176
+ }
177
+ };
178
+ return subscription;
179
+ }
180
+ const useEvents = () => {
181
+ const subsRef = (0, react.useRef)([]);
182
+ (0, react.useEffect)(() => {
183
+ return () => {
184
+ subsRef.current.forEach((sub) => sub.unsubscribe());
185
+ subsRef.current = [];
186
+ };
187
+ }, []);
188
+ const on = (name, handler) => {
189
+ const sub = subscribe(name, handler);
190
+ subsRef.current.push(sub);
191
+ return sub;
192
+ };
193
+ const emit = (name, payload) => {
194
+ dispatch(name, payload);
195
+ };
196
+ return [on, emit];
197
+ };
198
+ const dispatch = (name, payload) => {
199
+ listeners.get(name)?.forEach((handler) => {
200
+ try {
201
+ handler(payload);
202
+ } catch {}
203
+ });
204
+ };
205
+ //#endregion
206
+ //#region packages/core/event/createCrudEvents.ts
207
+ /**
208
+ * Generic factory that builds a typed set of CRUD event name strings for a
209
+ * given `prefix`. The returned object is `const`-asserted so every value is
210
+ * a template-literal type — callers get full type inference without having to
211
+ * cast anything.
212
+ *
213
+ * This helper belongs in `core/` because it has no feature-specific logic; it
214
+ * is used by both the core event bus and by every feature module that needs
215
+ * its own event namespace.
216
+ *
217
+ * @example
218
+ * const productEvents = createCrudEvents('product');
219
+ * // productEvents.ADD === 'product:add'
220
+ */
221
+ const createCrudEvents = (prefix) => ({
222
+ ADD: `${prefix}:add`,
223
+ EDIT: `${prefix}:edit`,
224
+ DELETE: `${prefix}:delete`,
225
+ SAVE: `${prefix}:save`,
226
+ CANCEL: `${prefix}:cancel`,
227
+ SUCCESS: `${prefix}:success`,
228
+ LOADING: `${prefix}:loading`
229
+ });
230
+ //#endregion
231
+ //#region packages/core/event/createScopedEventBus.ts
232
+ /**
233
+ * Creates a scoped event bus for the given resource.
234
+ *
235
+ * Internally this generates an 8-character random scope token and delegates to
236
+ * `createCrudEvents(`${resourceId}:${scopeId}`)`. This means each call produces
237
+ * a unique set of event name strings, even for the same `resourceId`, which
238
+ * prevents accidental cross-instance event coupling when multiple CRUD pages
239
+ * for the same resource are mounted simultaneously.
240
+ *
241
+ * @param resourceId - The resource id (e.g. `'product'`).
242
+ * @returns A `ScopedFormEventNames` object with a stable `_scopeId` tag.
243
+ */
244
+ function createScopedEventBus(resourceId) {
245
+ const scopeId = Math.random().toString(36).slice(2, 10);
246
+ return {
247
+ ...createCrudEvents(`${resourceId}:${scopeId}`),
248
+ _scopeId: scopeId
249
+ };
250
+ }
251
+ //#endregion
252
+ //#region packages/core/i18n/coreTranslations.ts
253
+ const coreTranslationsEs = {
254
+ "crudPage.selectedCount": "{{count}} seleccionados",
255
+ "crudPage.auditTrailButton": "Historial",
256
+ "crudPage.allColumns": "Todas",
257
+ "crudPage.columnsLabel": "Columnas:",
258
+ "crudPage.dialogTitleAdd": "Nuevo",
259
+ "crudPage.dialogTitleEdit": "Editar",
260
+ "crudPage.dialogTitleView": "Ver",
261
+ "crudPage.schemaErrorTitle": "No se pudo cargar esta vista",
262
+ "crudPage.schemaErrorMessage": "La configuración del recurso no está disponible. Intenta nuevamente o contacta a soporte si el problema continúa.",
263
+ "crudPage.schemaErrorDetails": "Detalle técnico",
264
+ "crudPage.schemaErrorRetry": "Reintentar",
265
+ "auditTrail.title": "Historial de cambios",
266
+ "auditTrail.closeButton": "Cerrar historial de cambios",
267
+ "auditTrail.selectRecord": "Seleccione un registro...",
268
+ "auditTrail.loading": "Cargando...",
269
+ "auditTrail.error": "Error al cargar el historial.",
270
+ "auditTrail.empty": "No hay entradas de historial.",
271
+ "auditTrail.action.create": "Creado",
272
+ "auditTrail.action.update": "Actualizado",
273
+ "auditTrail.action.delete": "Eliminado",
274
+ "form.detailRequired": "Agrega al menos un ítem para continuar",
275
+ "form.detailEmpty": "No hay ítems agregados",
276
+ "form.detailEmptyHint": "Usa el botón + para agregar el primero",
277
+ "form.detailAdd": "Agregar detalle",
278
+ "form.detailRemove": "Eliminar detalle",
279
+ "form.validationError": "Uno o más campos del formulario no son válidos",
280
+ "form.loading": "Cargando...",
281
+ "form.groupOther": "Otros",
282
+ "grid.noRecords": "No hay registros para mostrar",
283
+ "grid.selectRequired": "Debe seleccionar un registro",
284
+ "grid.loading": "Cargando...",
285
+ "grid.buttonNew": "Nuevo",
286
+ "grid.buttonEdit": "Editar",
287
+ "grid.buttonDelete": "Eliminar",
288
+ "grid.buttonRefresh": "Actualizar",
289
+ "grid.buttonActions": "Acciones",
290
+ "grid.buttonView": "Ver",
291
+ "grid.rowsPerPage": "Filas por página",
292
+ "grid.recordRange": "{{start}}-{{end}} de {{total}} registros",
293
+ "grid.noRecordCount": "0 registros",
294
+ "grid.selectedCount": "{{count}} seleccionados",
295
+ "grid.pageStatus": "Página {{page}} de {{total}}",
296
+ "grid.firstPage": "Primera página",
297
+ "grid.previousPage": "Página anterior",
298
+ "grid.nextPage": "Página siguiente",
299
+ "grid.lastPage": "Última página",
300
+ "grid.back": "Volver",
301
+ "grid.expandDetail": "Expandir detalle",
302
+ "grid.collapseDetail": "Contraer detalle",
303
+ "grid.selectRow": "Seleccionar fila",
304
+ "grid.resizeColumn": "Arrastrar para redimensionar",
305
+ "grid.allFilter": "Todos",
306
+ "grid.clearFilter": "Limpiar filtro",
307
+ "grid.filterColumn": "Filtrar {{column}}",
308
+ "grid.filterFrom": "Filtrar {{column}} desde",
309
+ "grid.filterTo": "Filtrar {{column}} hasta",
310
+ "grid.sortColumn": "Ordenar por {{column}}",
311
+ "grid.filters": "Filtros",
312
+ "grid.sortBy": "Ordenar por",
313
+ "grid.sortNone": "Sin ordenar",
314
+ "grid.sortAscending": "Ascendente",
315
+ "grid.sortDescending": "Descendente",
316
+ "grid.clearFilters": "Limpiar filtros",
317
+ "grid.done": "Listo",
318
+ "grid.searchPlaceholder": "Buscar por {{column}}",
319
+ "grid.showMore": "Ver más",
320
+ "grid.showLess": "Ver menos",
321
+ "grid.filterOperator.contains": "Contiene",
322
+ "grid.filterOperator.notcontains": "No contiene",
323
+ "grid.filterOperator.startswith": "Empieza con",
324
+ "grid.filterOperator.equals": "Igual",
325
+ "grid.filterOperator.notEquals": "No es igual",
326
+ "grid.filterOperator.greaterThan": "Mayor que",
327
+ "grid.filterOperator.greaterOrEqual": "Mayor o igual",
328
+ "grid.filterOperator.lessThan": "Menor que",
329
+ "grid.filterOperator.lessOrEqual": "Menor o igual",
330
+ "grid.filterOperator.between": "Entre",
331
+ "grid.filterOperator.reset": "Restablecer",
332
+ "dialog.buttonCancel": "Cancelar",
333
+ "dialog.buttonSave": "Guardar",
334
+ "dialog.close": "Cerrar",
335
+ "dialog.confirmDelete": "¿Está seguro de eliminar el registro?",
336
+ "dialog.confirmDeleteTitle": "Confirmar eliminación",
337
+ "common.yes": "Sí",
338
+ "common.no": "No",
339
+ "validation.defaultError": "Valor inválido",
340
+ "form.searchButton": "Buscar",
341
+ "form.actionButton": "Acción",
342
+ "form.detailTitle": "Detalle",
343
+ "form.lookupSearching": "Buscando...",
344
+ "form.lookupNoResults": "Sin resultados",
345
+ "form.lookupLoadingMore": "Cargando más...",
346
+ "form.fieldRequired": "{{label}} es requerido",
347
+ "form.invalidEmail": "Correo inválido",
348
+ "form.invalidNumeric": "Valor numérico inválido",
349
+ "form.invalidPattern": "Formato inválido",
350
+ "form.stringTooShort": "Texto demasiado corto",
351
+ "form.stringTooLong": "Texto demasiado largo",
352
+ "form.outOfRange": "Valor fuera de rango",
353
+ "form.fileUploadPrompt": "Arrastra un archivo o haz clic para seleccionar",
354
+ "form.fileUploadDrop": "Suelta el archivo aquí",
355
+ "form.fileUploadHint": "Se sube automáticamente al seleccionar.",
356
+ "form.fileUploading": "Subiendo archivo…",
357
+ "form.fileUploadReplace": "Cambiar",
358
+ "form.fileUploadRemove": "Quitar",
359
+ "form.fileUploadOpen": "Ver archivo",
360
+ "form.fileUploadFailed": "No se pudo subir el archivo",
361
+ "form.fileUploadInvalidResponse": "La respuesta del servidor no incluye el identificador del archivo",
362
+ "form.imageUploadPrompt": "Arrastra una imagen o haz clic para seleccionar",
363
+ "form.imageUploadHint": "PNG, JPG, WEBP o GIF. Se sube automáticamente al seleccionar.",
364
+ "crudPage.confirmActionTitle": "Confirmar acción"
365
+ };
366
+ const coreTranslationsEn = {
367
+ "crudPage.selectedCount": "{{count}} selected",
368
+ "crudPage.auditTrailButton": "History",
369
+ "crudPage.allColumns": "All",
370
+ "crudPage.columnsLabel": "Columns:",
371
+ "crudPage.dialogTitleAdd": "New",
372
+ "crudPage.dialogTitleEdit": "Edit",
373
+ "crudPage.dialogTitleView": "View",
374
+ "crudPage.schemaErrorTitle": "This view could not be loaded",
375
+ "crudPage.schemaErrorMessage": "The resource configuration is not available. Try again or contact support if the problem continues.",
376
+ "crudPage.schemaErrorDetails": "Technical details",
377
+ "crudPage.schemaErrorRetry": "Retry",
378
+ "auditTrail.title": "Change history",
379
+ "auditTrail.closeButton": "Close change history",
380
+ "auditTrail.selectRecord": "Select a record...",
381
+ "auditTrail.loading": "Loading...",
382
+ "auditTrail.error": "Error loading history.",
383
+ "auditTrail.empty": "No history entries.",
384
+ "auditTrail.action.create": "Created",
385
+ "auditTrail.action.update": "Updated",
386
+ "auditTrail.action.delete": "Deleted",
387
+ "form.detailRequired": "Add at least one item to continue",
388
+ "form.detailEmpty": "No items added",
389
+ "form.detailEmptyHint": "Use the + button to add the first one",
390
+ "form.detailAdd": "Add item",
391
+ "form.detailRemove": "Remove item",
392
+ "form.validationError": "One or more form fields are invalid",
393
+ "form.loading": "Loading...",
394
+ "form.groupOther": "Other",
395
+ "grid.noRecords": "No records to display",
396
+ "grid.selectRequired": "You must select a record",
397
+ "grid.loading": "Loading...",
398
+ "grid.buttonNew": "New",
399
+ "grid.buttonEdit": "Edit",
400
+ "grid.buttonDelete": "Delete",
401
+ "grid.buttonRefresh": "Refresh",
402
+ "grid.buttonActions": "Actions",
403
+ "grid.buttonView": "View",
404
+ "grid.rowsPerPage": "Rows per page",
405
+ "grid.recordRange": "{{start}}-{{end}} of {{total}} records",
406
+ "grid.noRecordCount": "0 records",
407
+ "grid.selectedCount": "{{count}} selected",
408
+ "grid.pageStatus": "Page {{page}} of {{total}}",
409
+ "grid.firstPage": "First page",
410
+ "grid.previousPage": "Previous page",
411
+ "grid.nextPage": "Next page",
412
+ "grid.lastPage": "Last page",
413
+ "grid.back": "Back",
414
+ "grid.expandDetail": "Expand detail",
415
+ "grid.collapseDetail": "Collapse detail",
416
+ "grid.selectRow": "Select row",
417
+ "grid.resizeColumn": "Drag to resize",
418
+ "grid.allFilter": "All",
419
+ "grid.clearFilter": "Clear filter",
420
+ "grid.filterColumn": "Filter {{column}}",
421
+ "grid.filterFrom": "Filter {{column}} from",
422
+ "grid.filterTo": "Filter {{column}} to",
423
+ "grid.sortColumn": "Sort by {{column}}",
424
+ "grid.filters": "Filters",
425
+ "grid.sortBy": "Sort by",
426
+ "grid.sortNone": "Unsorted",
427
+ "grid.sortAscending": "Ascending",
428
+ "grid.sortDescending": "Descending",
429
+ "grid.clearFilters": "Clear filters",
430
+ "grid.done": "Done",
431
+ "grid.searchPlaceholder": "Search by {{column}}",
432
+ "grid.showMore": "Show more",
433
+ "grid.showLess": "Show less",
434
+ "grid.filterOperator.contains": "Contains",
435
+ "grid.filterOperator.notcontains": "Does not contain",
436
+ "grid.filterOperator.startswith": "Starts with",
437
+ "grid.filterOperator.equals": "Equals",
438
+ "grid.filterOperator.notEquals": "Does not equal",
439
+ "grid.filterOperator.greaterThan": "Greater than",
440
+ "grid.filterOperator.greaterOrEqual": "Greater or equal",
441
+ "grid.filterOperator.lessThan": "Less than",
442
+ "grid.filterOperator.lessOrEqual": "Less or equal",
443
+ "grid.filterOperator.between": "Between",
444
+ "grid.filterOperator.reset": "Reset",
445
+ "dialog.buttonCancel": "Cancel",
446
+ "dialog.buttonSave": "Save",
447
+ "dialog.close": "Close",
448
+ "dialog.confirmDelete": "Are you sure you want to delete this record?",
449
+ "dialog.confirmDeleteTitle": "Confirm deletion",
450
+ "common.yes": "Yes",
451
+ "common.no": "No",
452
+ "validation.defaultError": "Invalid value",
453
+ "form.searchButton": "Search",
454
+ "form.actionButton": "Action",
455
+ "form.detailTitle": "Detail",
456
+ "form.lookupSearching": "Searching...",
457
+ "form.lookupNoResults": "No results",
458
+ "form.lookupLoadingMore": "Loading more...",
459
+ "form.fieldRequired": "{{label}} is required",
460
+ "form.invalidEmail": "Invalid email",
461
+ "form.invalidNumeric": "Invalid numeric value",
462
+ "form.invalidPattern": "Invalid format",
463
+ "form.stringTooShort": "Text too short",
464
+ "form.stringTooLong": "Text too long",
465
+ "form.outOfRange": "Value out of range",
466
+ "form.fileUploadPrompt": "Drag a file here or click to browse",
467
+ "form.fileUploadDrop": "Drop the file here",
468
+ "form.fileUploadHint": "Uploads automatically on selection.",
469
+ "form.fileUploading": "Uploading file…",
470
+ "form.fileUploadReplace": "Replace",
471
+ "form.fileUploadRemove": "Remove",
472
+ "form.fileUploadOpen": "Open file",
473
+ "form.fileUploadFailed": "Could not upload the file",
474
+ "form.fileUploadInvalidResponse": "Server response did not include the file identifier",
475
+ "form.imageUploadPrompt": "Drag an image here or click to browse",
476
+ "form.imageUploadHint": "PNG, JPG, WEBP or GIF. Uploads automatically on selection.",
477
+ "crudPage.confirmActionTitle": "Confirm action"
478
+ };
479
+ //#endregion
480
+ //#region packages/core/i18n/useCoreTranslation.ts
481
+ function useCoreTranslation() {
482
+ const { t } = (0, react_i18next.useTranslation)("core");
483
+ return { t: (key, options) => t(key, options) };
484
+ }
485
+ //#endregion
486
+ //#region packages/core/i18n/initCoreI18n.ts
487
+ function initCoreI18n() {
488
+ i18next.default.addResourceBundle("es", "core", coreTranslationsEs, true, false);
489
+ i18next.default.addResourceBundle("en", "core", coreTranslationsEn, true, false);
490
+ }
491
+ //#endregion
492
+ //#region packages/core/http/CoreHttpClient.ts
493
+ function joinUrl(baseUrl, url) {
494
+ if (/^https?:\/\//.test(url) || url.startsWith("/")) return url;
495
+ return `${baseUrl.replace(/\/+$/, "")}/${url.replace(/^\/+/, "")}`;
496
+ }
497
+ function serializeParams(params) {
498
+ const parts = [];
499
+ function append(prefix, value) {
500
+ if (value === null || value === void 0) return;
501
+ if (Array.isArray(value)) {
502
+ value.forEach((item, index) => append(`${prefix}[${index}]`, item));
503
+ return;
504
+ }
505
+ if (typeof value === "object") {
506
+ Object.entries(value).forEach(([key, nestedValue]) => {
507
+ append(`${prefix}[${key}]`, nestedValue);
508
+ });
509
+ return;
510
+ }
511
+ parts.push(`${encodeURIComponent(prefix)}=${encodeURIComponent(String(value))}`);
512
+ }
513
+ Object.entries(params).forEach(([key, value]) => append(key, value));
514
+ return parts.join("&");
515
+ }
516
+ function withParams(url, params) {
517
+ if (!params || Object.keys(params).length === 0) return url;
518
+ const query = serializeParams(params);
519
+ if (!query) return url;
520
+ return `${url}${url.includes("?") ? "&" : "?"}${query}`;
521
+ }
522
+ function createHttpError(message, status, data) {
523
+ return Object.assign(new Error(message), {
524
+ status,
525
+ data
526
+ });
527
+ }
528
+ async function readResponseBody(response, responseType) {
529
+ if (response.status === 204) return;
530
+ if (responseType === "arraybuffer") return await response.arrayBuffer();
531
+ if (responseType === "blob") return await response.blob();
532
+ if (responseType === "text") return await response.text();
533
+ return await response.json();
534
+ }
535
+ var CoreHttpClient = class {
536
+ config;
537
+ refreshPromise = null;
538
+ constructor(config = {}) {
539
+ this.config = config;
540
+ }
541
+ headers(extraHeaders) {
542
+ const browserLocale = typeof navigator !== "undefined" ? navigator.language?.split("-")[0] : void 0;
543
+ return {
544
+ "Accept-Language": this.config.locale ?? browserLocale ?? "es",
545
+ ...extraHeaders
546
+ };
547
+ }
548
+ /**
549
+ * Built-in cookie-based refresh (the original behavior).
550
+ * Only used when `refreshFn` is not provided in config.
551
+ */
552
+ async performBuiltInRefresh() {
553
+ const refreshPath = this.config.refreshPath ?? "auth/refresh";
554
+ const refreshUrl = joinUrl(this.config.baseUrl ?? "/api/", refreshPath);
555
+ this.refreshPromise ??= globalThis.fetch(refreshUrl, {
556
+ method: "POST",
557
+ credentials: this.config.credentials ?? "include"
558
+ }).then(async (response) => {
559
+ if (!response.ok) throw createHttpError("Session refresh failed", response.status, await this.safeErrorData(response));
560
+ }).finally(() => {
561
+ this.refreshPromise = null;
562
+ });
563
+ return this.refreshPromise;
564
+ }
565
+ async performRefresh() {
566
+ if (this.config.refreshFn) return this.config.refreshFn(this);
567
+ return this.performBuiltInRefresh();
568
+ }
569
+ async safeErrorData(response) {
570
+ try {
571
+ return await response.json();
572
+ } catch {
573
+ return {};
574
+ }
575
+ }
576
+ async request(method, url, body, config, retryOnUnauthorized = true) {
577
+ const requestUrl = withParams(joinUrl(this.config.baseUrl ?? "/api/", url), config?.params);
578
+ const headers = this.headers(config?.headers);
579
+ if (body !== void 0 && body !== null && !(body instanceof FormData)) headers["Content-Type"] = headers["Content-Type"] ?? "application/json";
580
+ const response = await globalThis.fetch(requestUrl, {
581
+ method,
582
+ headers,
583
+ credentials: this.config.credentials ?? "include",
584
+ signal: config?.signal,
585
+ body: body === void 0 || body === null ? void 0 : body instanceof FormData ? body : JSON.stringify(body)
586
+ });
587
+ if (response.ok) return {
588
+ response,
589
+ data: await readResponseBody(response, config?.responseType),
590
+ headers: response.headers,
591
+ status: response.status
592
+ };
593
+ const errorData = await this.safeErrorData(response);
594
+ const error = createHttpError(errorData.detail ?? errorData.message ?? "HTTP request failed", response.status, errorData);
595
+ const loginPath = this.config.loginPath ?? "auth/login";
596
+ const refreshPath = this.config.refreshPath ?? "auth/refresh";
597
+ const isAuthEndpoint = url.includes(loginPath) || url.includes(refreshPath);
598
+ const shouldAutoRefresh = this.config.autoRefresh !== false;
599
+ if (response.status === 401 && retryOnUnauthorized && !isAuthEndpoint && shouldAutoRefresh) try {
600
+ await this.performRefresh();
601
+ return this.request(method, url, body, config, false);
602
+ } catch (refreshError) {
603
+ const unauthorizedError = refreshError instanceof Error ? Object.assign(refreshError, { status: 401 }) : error;
604
+ this.config.onUnauthorized?.(unauthorizedError);
605
+ throw unauthorizedError;
606
+ }
607
+ if (response.status === 401) this.config.onUnauthorized?.(error);
608
+ else this.config.onError?.(error);
609
+ throw error;
610
+ }
611
+ get(url, config) {
612
+ return this.request("GET", url, void 0, config);
613
+ }
614
+ post(url, data, config) {
615
+ return this.request("POST", url, data, config);
616
+ }
617
+ put(url, data, config) {
618
+ return this.request("PUT", url, data, config);
619
+ }
620
+ patch(url, data, config) {
621
+ return this.request("PATCH", url, data, {
622
+ ...config,
623
+ headers: {
624
+ ...config?.headers,
625
+ "Content-Type": "application/merge-patch+json"
626
+ }
627
+ });
628
+ }
629
+ delete(url, config) {
630
+ return this.request("DELETE", url, void 0, config);
631
+ }
632
+ };
633
+ function createCoreHttpClient(config) {
634
+ return new CoreHttpClient(config);
635
+ }
636
+ //#endregion
637
+ //#region packages/core/http/CoreHttpContext.tsx
638
+ const CoreHttpContext = (0, react.createContext)(createCoreHttpClient());
639
+ function CoreHttpProvider({ children, client, config }) {
640
+ const value = (0, react.useMemo)(() => {
641
+ if (client) return client;
642
+ return createCoreHttpClient({
643
+ ...config,
644
+ baseUrl: config?.baseUrl ?? getCoreApiBaseUrl()
645
+ });
646
+ }, [client, config]);
647
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CoreHttpContext.Provider, {
648
+ value,
649
+ children
650
+ });
651
+ }
652
+ function useCoreHttpClient() {
653
+ return (0, react.useContext)(CoreHttpContext);
654
+ }
655
+ //#endregion
656
+ //#region packages/core/runtime/CoreRuntimeContext.tsx
657
+ const defaultRuntime = {
658
+ notify: () => void 0,
659
+ confirm: (message) => {
660
+ if (typeof window === "undefined") return false;
661
+ return window.confirm(message);
662
+ }
663
+ };
664
+ const CoreRuntimeContext = (0, react.createContext)(defaultRuntime);
665
+ function CoreRuntimeProvider({ children, runtime }) {
666
+ const value = (0, react.useMemo)(() => ({
667
+ ...defaultRuntime,
668
+ ...runtime
669
+ }), [runtime]);
670
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CoreRuntimeContext.Provider, {
671
+ value,
672
+ children
673
+ });
674
+ }
675
+ function useCoreRuntime() {
676
+ return (0, react.useContext)(CoreRuntimeContext);
677
+ }
678
+ //#endregion
679
+ //#region packages/core/provider/CoreProvider.tsx
680
+ function CoreProvider({ children, http, httpClient, runtime }) {
681
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CoreHttpProvider, {
682
+ client: httpClient,
683
+ config: http,
684
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CoreRuntimeProvider, {
685
+ runtime,
686
+ children
687
+ })
688
+ });
689
+ }
690
+ //#endregion
691
+ //#region packages/core/mercure/MercureManager.ts
692
+ var MercureManager = class {
693
+ hubUrl = null;
694
+ topics = /* @__PURE__ */ new Map();
695
+ hubUrlListeners = /* @__PURE__ */ new Set();
696
+ /**
697
+ * Update the hub URL (called by the Axios interceptor when the `Link` header is discovered,
698
+ * or by MercureProvider). Notifies all registered listeners.
699
+ *
700
+ * Idempotent: if the URL is already set to the same value, listeners are NOT re-notified.
701
+ */
702
+ setHubUrl(url) {
703
+ if (this.hubUrl === url) return;
704
+ this.disconnectAll();
705
+ this.hubUrl = url;
706
+ this.hubUrlListeners.forEach((cb) => cb(url));
707
+ }
708
+ getHubUrl() {
709
+ return this.hubUrl;
710
+ }
711
+ /**
712
+ * Register a callback that fires whenever the hub URL changes.
713
+ *
714
+ * Returns an unsubscribe function — call it in a `useEffect` cleanup to avoid memory leaks.
715
+ *
716
+ * @example
717
+ * ```tsx
718
+ * useEffect(() => {
719
+ * return MercureManager.onHubUrlChange(setHubUrl);
720
+ * }, []);
721
+ * ```
722
+ */
723
+ onHubUrlChange(callback) {
724
+ this.hubUrlListeners.add(callback);
725
+ return () => {
726
+ this.hubUrlListeners.delete(callback);
727
+ };
728
+ }
729
+ /**
730
+ * Subscribe to a Mercure topic.
731
+ *
732
+ * @param topic Full topic URI or URI Template, e.g. `https://host/api/products/{id}`.
733
+ * @param callback Called with the parsed JSON payload on each SSE message.
734
+ *
735
+ * If `hubUrl` is null (hub not configured), this is a no-op (graceful degradation).
736
+ * If an EventSource for this topic already exists, it is reused (ref-counting).
737
+ */
738
+ subscribe(topic, callback) {
739
+ if (this.hubUrl === null) return;
740
+ const existing = this.topics.get(topic);
741
+ if (existing) {
742
+ const listener = (e) => {
743
+ try {
744
+ callback(JSON.parse(e.data));
745
+ } catch {
746
+ callback(e.data);
747
+ }
748
+ };
749
+ existing.eventSource.addEventListener("message", listener);
750
+ existing.listeners.set(callback, listener);
751
+ existing.count += 1;
752
+ return;
753
+ }
754
+ const url = new URL(this.hubUrl);
755
+ url.searchParams.append("topic", topic);
756
+ const eventSource = new EventSource(url.toString(), { withCredentials: true });
757
+ const listener = (e) => {
758
+ try {
759
+ callback(JSON.parse(e.data));
760
+ } catch {
761
+ callback(e.data);
762
+ }
763
+ };
764
+ eventSource.addEventListener("message", listener);
765
+ const listeners = /* @__PURE__ */ new Map();
766
+ listeners.set(callback, listener);
767
+ this.topics.set(topic, {
768
+ eventSource,
769
+ count: 1,
770
+ listeners
771
+ });
772
+ }
773
+ /**
774
+ * Unsubscribe a specific callback from a topic.
775
+ *
776
+ * Decrements the ref count. When count reaches 0, the EventSource is closed
777
+ * and removed from the internal map.
778
+ */
779
+ unsubscribe(topic, callback) {
780
+ const entry = this.topics.get(topic);
781
+ if (!entry) return;
782
+ const listener = entry.listeners.get(callback);
783
+ if (listener) {
784
+ entry.eventSource.removeEventListener("message", listener);
785
+ entry.listeners.delete(callback);
786
+ }
787
+ entry.count -= 1;
788
+ if (entry.count <= 0) {
789
+ entry.eventSource.close();
790
+ this.topics.delete(topic);
791
+ }
792
+ }
793
+ /**
794
+ * Close all active EventSource connections and clear the topic map.
795
+ *
796
+ * Called internally when the hub URL changes so stale connections
797
+ * bound to the previous hub are not leaked.
798
+ */
799
+ disconnectAll() {
800
+ this.topics.forEach((entry) => {
801
+ entry.eventSource.close();
802
+ });
803
+ this.topics.clear();
804
+ }
805
+ };
806
+ /** Singleton instance — shared across the entire application. */
807
+ const mercureManager = new MercureManager();
808
+ //#endregion
809
+ //#region packages/core/mercure/MercureProvider.tsx
810
+ /**
811
+ * MercureProvider — React context provider for the Mercure hub URL.
812
+ *
813
+ * Wraps the application (or a subtree) and makes the hub URL available to all
814
+ * descendant hooks via `useMercureHub()`.
815
+ *
816
+ * Also keeps `MercureManager` in sync with the hub URL whenever it changes.
817
+ *
818
+ * ## Usage
819
+ *
820
+ * ```tsx
821
+ * // In your app root:
822
+ * <MercureProvider hubUrl={discoveredHubUrl}>
823
+ * <App />
824
+ * </MercureProvider>
825
+ * ```
826
+ *
827
+ * The `hubUrl` is typically discovered from the `Link` header of API responses
828
+ * (common pattern with API Platform + Mercure).
829
+ */
830
+ /** The hub URL, or null if Mercure is not configured / not yet discovered. */
831
+ const MercureContext = (0, react.createContext)(null);
832
+ /**
833
+ * Provides the Mercure hub URL to the React tree and keeps `MercureManager`
834
+ * in sync whenever the URL changes.
835
+ */
836
+ function MercureProvider({ hubUrl, children }) {
837
+ (0, react.useEffect)(() => {
838
+ mercureManager.setHubUrl(hubUrl);
839
+ }, [hubUrl]);
840
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MercureContext.Provider, {
841
+ value: hubUrl,
842
+ children
843
+ });
844
+ }
845
+ //#endregion
846
+ //#region packages/core/mercure/useMercureHub.ts
847
+ /**
848
+ * useMercureHub — Hook that returns the Mercure hub URL from context.
849
+ *
850
+ * Returns `null` if:
851
+ * - The component is rendered outside a `<MercureProvider>` (default context value).
852
+ * - The hub URL has not been discovered yet (header `Link` not received).
853
+ * - Mercure is not configured in the backend.
854
+ *
855
+ * This hook does NOT perform any fetch or side effect — it only reads from context.
856
+ * Hub discovery (usually from a `Link` header) must be done by the host application.
857
+ *
858
+ * ## Usage
859
+ *
860
+ * ```tsx
861
+ * const hubUrl = useMercureHub();
862
+ * // hubUrl is string | null
863
+ * ```
864
+ */
865
+ /**
866
+ * Returns the Mercure hub URL from the nearest `<MercureProvider>`,
867
+ * or `null` if the hub is not available.
868
+ */
869
+ function useMercureHub() {
870
+ return (0, react.useContext)(MercureContext);
871
+ }
872
+ //#endregion
873
+ //#region packages/core/mercure/useMercureSubscription.ts
874
+ /**
875
+ * useMercureSubscription — Hook that subscribes to a Mercure topic and calls
876
+ * a callback whenever an SSE message is received for that topic.
877
+ *
878
+ * ## Behaviour
879
+ * - If `hubUrl` is null (Mercure not configured) or `enabled` is false → no-op.
880
+ * - Subscribes to the wildcard topic `<origin>/<apiUrl>/{id}` (URI Template RFC 6570),
881
+ * which captures any item-level event (create / update / delete) for the collection.
882
+ * - Cleanup: unsubscribes on unmount or when dependencies change (no memory leaks).
883
+ *
884
+ * ## Usage
885
+ *
886
+ * ```tsx
887
+ * useMercureSubscription(
888
+ * resource.apiUrl,
889
+ * () => { gridRef.current?.instance().refresh(); },
890
+ * resource.mercure !== false,
891
+ * );
892
+ * ```
893
+ *
894
+ * @param apiUrl The resource API URL (e.g. `'api/products'` or `'/api/products'`).
895
+ * Used to build the wildcard topic URI.
896
+ * @param onUpdate Callback invoked on every SSE message for the topic.
897
+ * @param enabled When false, the subscription is skipped entirely. Defaults to true.
898
+ */
899
+ function useMercureSubscription(apiUrl, onUpdate, enabled = true) {
900
+ const hubUrl = useMercureHub();
901
+ (0, react.useEffect)(() => {
902
+ if (!hubUrl || !enabled || !apiUrl) return;
903
+ const normalizedPath = apiUrl.replace(/^\//, "");
904
+ const topic = `${window.location.origin}/${normalizedPath}/{id}`;
905
+ const handler = (_data) => {
906
+ onUpdate();
907
+ };
908
+ mercureManager.subscribe(topic, handler);
909
+ return () => {
910
+ mercureManager.unsubscribe(topic, handler);
911
+ };
912
+ }, [
913
+ hubUrl,
914
+ enabled,
915
+ apiUrl
916
+ ]);
917
+ }
918
+ //#endregion
919
+ exports.CoreConfigProvider = CoreConfigProvider;
920
+ exports.CoreHttpClient = CoreHttpClient;
921
+ exports.CoreHttpProvider = CoreHttpProvider;
922
+ exports.CoreProvider = CoreProvider;
923
+ exports.CoreRuntimeProvider = CoreRuntimeProvider;
924
+ exports.DEFAULT_TIMEZONE = DEFAULT_TIMEZONE;
925
+ exports.DateUtils = DateUtils;
926
+ exports.MercureManager = mercureManager;
927
+ exports.MercureProvider = MercureProvider;
928
+ exports.configureCore = configureCore;
929
+ exports.configureCoreDate = configureCoreDate;
930
+ exports.coreTranslationsEn = coreTranslationsEn;
931
+ exports.coreTranslationsEs = coreTranslationsEs;
932
+ exports.createCoreHttpClient = createCoreHttpClient;
933
+ exports.createCrudEvents = createCrudEvents;
934
+ exports.createScopedEventBus = createScopedEventBus;
935
+ exports.dispatch = dispatch;
936
+ exports.getCoreApiBaseUrl = getCoreApiBaseUrl;
937
+ exports.getCoreLocale = getCoreLocale;
938
+ exports.getCoreTimezone = getCoreTimezone;
939
+ exports.initCoreI18n = initCoreI18n;
940
+ exports.useCoreConfig = useCoreConfig;
941
+ exports.useCoreHttpClient = useCoreHttpClient;
942
+ exports.useCoreRuntime = useCoreRuntime;
943
+ exports.useCoreTranslation = useCoreTranslation;
944
+ exports.useEvents = useEvents;
945
+ exports.useMercureHub = useMercureHub;
946
+ exports.useMercureSubscription = useMercureSubscription;