@saasicat/core 1.0.0-rc.22 → 1.0.0-rc.23
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/.build-stamp +1 -1
- package/dist/index.cjs +169 -4
- package/dist/index.d.cts +312 -3
- package/dist/index.d.ts +312 -3
- package/dist/index.js +157 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -283,6 +283,100 @@ function appendNullableDecimalChange(out, field, oldValue, newValue, polarity) {
|
|
|
283
283
|
appendDecimalChange(out, field, oldValue, newValue, polarity);
|
|
284
284
|
}
|
|
285
285
|
|
|
286
|
+
// src/maintenance-window.types.ts
|
|
287
|
+
var MAINTENANCE_MESSAGE_MAX_LENGTH = 500;
|
|
288
|
+
var MAINTENANCE_STATE_MAX_AGE_SECONDS = 5;
|
|
289
|
+
var MAINTENANCE_CAPABILITY = "maintenance.manage";
|
|
290
|
+
|
|
291
|
+
// src/maintenance-window-mapping.ts
|
|
292
|
+
function toMaintenanceWindowRecord(row) {
|
|
293
|
+
return {
|
|
294
|
+
id: row.id,
|
|
295
|
+
startsAt: row.startsAt,
|
|
296
|
+
endsAt: row.endsAt,
|
|
297
|
+
message: row.message,
|
|
298
|
+
createdAt: row.createdAt,
|
|
299
|
+
createdBy: row.createdBy,
|
|
300
|
+
lockedAt: row.lockedAt,
|
|
301
|
+
lockedBy: row.lockedBy,
|
|
302
|
+
endedAt: row.endedAt,
|
|
303
|
+
endedBy: row.endedBy
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// src/maintenance-window-views.ts
|
|
308
|
+
var RETRY_AFTER_WITHOUT_END_SECONDS = 60;
|
|
309
|
+
var iso = (date) => date ? date.toISOString() : null;
|
|
310
|
+
function maintenanceWindowStatusOf(window) {
|
|
311
|
+
if (window.endedAt) return window.lockedAt ? "ended" : "cancelled";
|
|
312
|
+
return window.lockedAt ? "locked" : "announced";
|
|
313
|
+
}
|
|
314
|
+
function isMaintenanceOverrun(window, now) {
|
|
315
|
+
return maintenanceWindowStatusOf(window) === "locked" && window.endsAt !== null && window.endsAt.getTime() <= now.getTime();
|
|
316
|
+
}
|
|
317
|
+
function isMaintenanceAnnouncementLapsed(window, now) {
|
|
318
|
+
return maintenanceWindowStatusOf(window) === "announced" && window.endsAt !== null && window.endsAt.getTime() <= now.getTime();
|
|
319
|
+
}
|
|
320
|
+
function maintenanceStatusOf(open, now) {
|
|
321
|
+
if (!open) return { state: "none" };
|
|
322
|
+
const status = maintenanceWindowStatusOf(open);
|
|
323
|
+
if (status === "locked" && open.lockedAt) {
|
|
324
|
+
return {
|
|
325
|
+
state: "locked",
|
|
326
|
+
lockedAt: open.lockedAt.toISOString(),
|
|
327
|
+
endsAt: iso(open.endsAt),
|
|
328
|
+
message: open.message,
|
|
329
|
+
overrun: isMaintenanceOverrun(open, now)
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
if (status === "announced" && open.startsAt && open.endsAt && !isMaintenanceAnnouncementLapsed(open, now)) {
|
|
333
|
+
return {
|
|
334
|
+
state: "announced",
|
|
335
|
+
startsAt: open.startsAt.toISOString(),
|
|
336
|
+
endsAt: open.endsAt.toISOString(),
|
|
337
|
+
message: open.message
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
return { state: "none" };
|
|
341
|
+
}
|
|
342
|
+
function maintenanceWindowViewOf(window, now) {
|
|
343
|
+
return {
|
|
344
|
+
id: window.id,
|
|
345
|
+
status: maintenanceWindowStatusOf(window),
|
|
346
|
+
startsAt: iso(window.startsAt),
|
|
347
|
+
endsAt: iso(window.endsAt),
|
|
348
|
+
message: window.message,
|
|
349
|
+
createdAt: window.createdAt.toISOString(),
|
|
350
|
+
createdBy: window.createdBy,
|
|
351
|
+
lockedAt: iso(window.lockedAt),
|
|
352
|
+
lockedBy: window.lockedBy,
|
|
353
|
+
endedAt: iso(window.endedAt),
|
|
354
|
+
endedBy: window.endedBy,
|
|
355
|
+
overrun: isMaintenanceOverrun(window, now),
|
|
356
|
+
lapsed: isMaintenanceAnnouncementLapsed(window, now)
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
function maintenanceRetryAfterSeconds(window, now) {
|
|
360
|
+
if (!window.endsAt || window.endsAt.getTime() <= now.getTime()) {
|
|
361
|
+
return RETRY_AFTER_WITHOUT_END_SECONDS;
|
|
362
|
+
}
|
|
363
|
+
return Math.max(1, Math.ceil((window.endsAt.getTime() - now.getTime()) / 1e3));
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// src/zoned-instant.ts
|
|
367
|
+
function endsInOffset(text) {
|
|
368
|
+
const sign = text.charAt(text.length - 6);
|
|
369
|
+
return (sign === "+" || sign === "-") && text.charAt(text.length - 3) === ":";
|
|
370
|
+
}
|
|
371
|
+
function parseZonedInstant(text) {
|
|
372
|
+
const value = text.trim();
|
|
373
|
+
if (!value.includes("T")) return null;
|
|
374
|
+
const zoned = value.endsWith("Z") || value.endsWith("z") || endsInOffset(value);
|
|
375
|
+
if (!zoned) return null;
|
|
376
|
+
const moment = new Date(value);
|
|
377
|
+
return Number.isNaN(moment.getTime()) ? null : moment;
|
|
378
|
+
}
|
|
379
|
+
|
|
286
380
|
// src/settings-subtree.ts
|
|
287
381
|
var CATALOGUE_KEYS = /* @__PURE__ */ new Set([
|
|
288
382
|
"schemaVersion",
|
|
@@ -613,7 +707,15 @@ var CONTRACT_ERROR_CODES = {
|
|
|
613
707
|
CHECKOUT_OFFER_CHANGED: "CHECKOUT_OFFER_CHANGED",
|
|
614
708
|
SUBSCRIPTION_CONTRACT_NOT_FOUND: "SUBSCRIPTION_CONTRACT_NOT_FOUND",
|
|
615
709
|
NO_ACTIVE_SUBSCRIPTION_CONTRACT: "NO_ACTIVE_SUBSCRIPTION_CONTRACT",
|
|
616
|
-
SUBSCRIPTION_CONTRACT_ALREADY_CLOSED: "SUBSCRIPTION_CONTRACT_ALREADY_CLOSED"
|
|
710
|
+
SUBSCRIPTION_CONTRACT_ALREADY_CLOSED: "SUBSCRIPTION_CONTRACT_ALREADY_CLOSED",
|
|
711
|
+
/**
|
|
712
|
+
* A successor would run beside another contract of the tenant: the
|
|
713
|
+
* contract in force moved while it was being written — another successor
|
|
714
|
+
* took its place, or a cancellation capped it — and kept moving on a
|
|
715
|
+
* second attempt, or a contract begins after the moment the successor
|
|
716
|
+
* would take effect. Nothing was written.
|
|
717
|
+
*/
|
|
718
|
+
SUBSCRIPTION_CONTRACT_CHANGED: "SUBSCRIPTION_CONTRACT_CHANGED"
|
|
617
719
|
};
|
|
618
720
|
var SUBSCRIBER_ERROR_CODES = {
|
|
619
721
|
/**
|
|
@@ -724,6 +826,24 @@ var SETTINGS_ERROR_CODES = {
|
|
|
724
826
|
/** No recorded change has this id, or the installation keeps no record at all. */
|
|
725
827
|
SETTINGS_CHANGE_NOT_FOUND: "SETTINGS_CHANGE_NOT_FOUND"
|
|
726
828
|
};
|
|
829
|
+
var MAINTENANCE_ERROR_CODES = {
|
|
830
|
+
/** The application is locked for maintenance; the body carries the window. */
|
|
831
|
+
MAINTENANCE: "MAINTENANCE",
|
|
832
|
+
/** A window is already open: move or cancel it before announcing another. */
|
|
833
|
+
MAINTENANCE_WINDOW_ALREADY_OPEN: "MAINTENANCE_WINDOW_ALREADY_OPEN",
|
|
834
|
+
/** No open window has this id — it ended, or it never existed. */
|
|
835
|
+
MAINTENANCE_WINDOW_NOT_OPEN: "MAINTENANCE_WINDOW_NOT_OPEN",
|
|
836
|
+
/** The window is locked: its start is history, and unlocking is what ends it. */
|
|
837
|
+
MAINTENANCE_WINDOW_LOCKED: "MAINTENANCE_WINDOW_LOCKED",
|
|
838
|
+
/** The announced end is not after the announced start. */
|
|
839
|
+
MAINTENANCE_WINDOW_END_NOT_AFTER_START: "MAINTENANCE_WINDOW_END_NOT_AFTER_START",
|
|
840
|
+
/** The announced end has already passed. */
|
|
841
|
+
MAINTENANCE_WINDOW_END_IN_PAST: "MAINTENANCE_WINDOW_END_IN_PAST",
|
|
842
|
+
/** A time without its zone, or not a date and time at all; `field` names which. */
|
|
843
|
+
MAINTENANCE_TIME_INVALID: "MAINTENANCE_TIME_INVALID",
|
|
844
|
+
/** The message is longer than `MAINTENANCE_MESSAGE_MAX_LENGTH`. */
|
|
845
|
+
MAINTENANCE_MESSAGE_TOO_LONG: "MAINTENANCE_MESSAGE_TOO_LONG"
|
|
846
|
+
};
|
|
727
847
|
var PLATFORM_ERROR_CODES = {
|
|
728
848
|
...SETUP_ERROR_CODES,
|
|
729
849
|
...AUTH_ERROR_CODES,
|
|
@@ -734,7 +854,8 @@ var PLATFORM_ERROR_CODES = {
|
|
|
734
854
|
...SUBSCRIBER_ERROR_CODES,
|
|
735
855
|
...REGISTRATION_ERROR_CODES,
|
|
736
856
|
...PAYMENT_ERROR_CODES,
|
|
737
|
-
...SETTINGS_ERROR_CODES
|
|
857
|
+
...SETTINGS_ERROR_CODES,
|
|
858
|
+
...MAINTENANCE_ERROR_CODES
|
|
738
859
|
};
|
|
739
860
|
|
|
740
861
|
// src/errors.ts
|
|
@@ -1570,6 +1691,7 @@ var ERROR_MESSAGES_EN = {
|
|
|
1570
1691
|
SUBSCRIPTION_CONTRACT_NOT_FOUND: "SubscriptionContract '{contractId}' not found",
|
|
1571
1692
|
NO_ACTIVE_SUBSCRIPTION_CONTRACT: "No active subscription contract for tenant {tenantId}",
|
|
1572
1693
|
SUBSCRIPTION_CONTRACT_ALREADY_CLOSED: "SubscriptionContract '{contractId}' is already closed",
|
|
1694
|
+
SUBSCRIPTION_CONTRACT_CHANGED: "The contracts of tenant '{tenantId}' changed while a successor was being written, or one begins after the moment it would take effect, so it would run beside another. Nothing was written.",
|
|
1573
1695
|
// ── subscriber ──
|
|
1574
1696
|
SUBSCRIBER_REQUIRED: "Tenant '{tenantId}' has no subscriber. Nothing is agreed or charged without the party to it: create the tenant's subscriber first.",
|
|
1575
1697
|
SUBSCRIBER_ALREADY_EXISTS: "Tenant '{tenantId}' already has a subscriber.",
|
|
@@ -1605,7 +1727,16 @@ var ERROR_MESSAGES_EN = {
|
|
|
1605
1727
|
// ── entitlement (code lives in upsell.types.ts) ──
|
|
1606
1728
|
[FEATURE_NOT_LICENSED]: "Feature {featureKeys} is not included in the current plan.",
|
|
1607
1729
|
// ── settings ──
|
|
1608
|
-
SETTINGS_CHANGE_NOT_FOUND: "No recorded settings change has this id."
|
|
1730
|
+
SETTINGS_CHANGE_NOT_FOUND: "No recorded settings change has this id.",
|
|
1731
|
+
// ── maintenance ──
|
|
1732
|
+
MAINTENANCE: "The application is under maintenance.",
|
|
1733
|
+
MAINTENANCE_WINDOW_ALREADY_OPEN: "A maintenance window is already open. Move or cancel it before announcing another.",
|
|
1734
|
+
MAINTENANCE_WINDOW_NOT_OPEN: "No open maintenance window has this id.",
|
|
1735
|
+
MAINTENANCE_WINDOW_LOCKED: "This maintenance window is locked. Its start cannot move, and unlocking is what ends it.",
|
|
1736
|
+
MAINTENANCE_WINDOW_END_NOT_AFTER_START: "The end of a maintenance window has to be after its start.",
|
|
1737
|
+
MAINTENANCE_WINDOW_END_IN_PAST: "The end of a maintenance window cannot be in the past.",
|
|
1738
|
+
MAINTENANCE_TIME_INVALID: "The {field} has to be a date and time with its zone, such as 2026-10-02T22:00+02:00.",
|
|
1739
|
+
MAINTENANCE_MESSAGE_TOO_LONG: "The message may be at most {max} characters long."
|
|
1609
1740
|
};
|
|
1610
1741
|
function formatErrorMessage(template, params = {}) {
|
|
1611
1742
|
return template.replace(/\{(\w+)\}/g, (match, name) => {
|
|
@@ -1781,6 +1912,7 @@ var ERROR_MESSAGES_DE = {
|
|
|
1781
1912
|
SUBSCRIPTION_CONTRACT_NOT_FOUND: "SubscriptionContract '{contractId}' nicht gefunden",
|
|
1782
1913
|
NO_ACTIVE_SUBSCRIPTION_CONTRACT: "Kein aktiver Abo-Vertrag f\xFCr Mandant {tenantId}",
|
|
1783
1914
|
SUBSCRIPTION_CONTRACT_ALREADY_CLOSED: "SubscriptionContract '{contractId}' ist bereits geschlossen",
|
|
1915
|
+
SUBSCRIPTION_CONTRACT_CHANGED: "Die Vertr\xE4ge von Mandant '{tenantId}' haben sich ge\xE4ndert, w\xE4hrend ein Nachfolger geschrieben wurde, oder einer beginnt nach dem Zeitpunkt, ab dem er g\xE4lte, sodass er neben einem anderen liefe. Es wurde nichts geschrieben.",
|
|
1784
1916
|
// ── subscriber ──
|
|
1785
1917
|
SUBSCRIBER_REQUIRED: "Mandant '{tenantId}' hat keinen Vertragspartner. Ohne ihn wird nichts vereinbart oder berechnet: Zuerst muss der Vertragspartner des Mandanten angelegt werden.",
|
|
1786
1918
|
SUBSCRIBER_ALREADY_EXISTS: "Mandant '{tenantId}' hat bereits einen Vertragspartner.",
|
|
@@ -1816,7 +1948,16 @@ var ERROR_MESSAGES_DE = {
|
|
|
1816
1948
|
// ── entitlement (code lives in upsell.types.ts) ──
|
|
1817
1949
|
[FEATURE_NOT_LICENSED]: "Das Feature {featureKeys} ist im aktuellen Plan nicht enthalten.",
|
|
1818
1950
|
// ── settings ──
|
|
1819
|
-
SETTINGS_CHANGE_NOT_FOUND: "Keine aufgezeichnete Einstellungs\xE4nderung hat diese ID."
|
|
1951
|
+
SETTINGS_CHANGE_NOT_FOUND: "Keine aufgezeichnete Einstellungs\xE4nderung hat diese ID.",
|
|
1952
|
+
// ── maintenance ──
|
|
1953
|
+
MAINTENANCE: "Die Anwendung wird gerade gewartet.",
|
|
1954
|
+
MAINTENANCE_WINDOW_ALREADY_OPEN: "Es ist bereits ein Wartungsfenster offen. Verschiebe oder streiche es, bevor du ein weiteres ank\xFCndigst.",
|
|
1955
|
+
MAINTENANCE_WINDOW_NOT_OPEN: "Kein offenes Wartungsfenster hat diese ID.",
|
|
1956
|
+
MAINTENANCE_WINDOW_LOCKED: "Dieses Wartungsfenster ist gesperrt. Sein Beginn l\xE4sst sich nicht mehr verschieben, und beendet wird es durch Entsperren.",
|
|
1957
|
+
MAINTENANCE_WINDOW_END_NOT_AFTER_START: "Das Ende eines Wartungsfensters muss nach seinem Beginn liegen.",
|
|
1958
|
+
MAINTENANCE_WINDOW_END_IN_PAST: "Das Ende eines Wartungsfensters darf nicht in der Vergangenheit liegen.",
|
|
1959
|
+
MAINTENANCE_TIME_INVALID: "{field} muss Datum und Uhrzeit mit Zeitzone sein, etwa 2026-10-02T22:00+02:00.",
|
|
1960
|
+
MAINTENANCE_MESSAGE_TOO_LONG: "Die Nachricht darf h\xF6chstens {max} Zeichen lang sein."
|
|
1820
1961
|
};
|
|
1821
1962
|
export {
|
|
1822
1963
|
ACTIVE_SUBSCRIPTION_CONTRACT_STATUSES,
|
|
@@ -1831,6 +1972,10 @@ export {
|
|
|
1831
1972
|
FEATURE_NOT_LICENSED,
|
|
1832
1973
|
ForeignPaymentMethodReferenceError,
|
|
1833
1974
|
LEGAL_IDENTITY_FIELDS,
|
|
1975
|
+
MAINTENANCE_CAPABILITY,
|
|
1976
|
+
MAINTENANCE_ERROR_CODES,
|
|
1977
|
+
MAINTENANCE_MESSAGE_MAX_LENGTH,
|
|
1978
|
+
MAINTENANCE_STATE_MAX_AGE_SECONDS,
|
|
1834
1979
|
MARKETING_PRIORITY_MAX,
|
|
1835
1980
|
MARKETING_PRIORITY_MIN,
|
|
1836
1981
|
OTP_RATE_LIMIT_MAX_SENDS,
|
|
@@ -1876,13 +2021,20 @@ export {
|
|
|
1876
2021
|
identityCorrectionDelta,
|
|
1877
2022
|
isBundleRedundant,
|
|
1878
2023
|
isForeignPaymentMethodReferenceError,
|
|
2024
|
+
isMaintenanceAnnouncementLapsed,
|
|
2025
|
+
isMaintenanceOverrun,
|
|
1879
2026
|
isPaymentCallbackRejectedError,
|
|
1880
2027
|
isPlatformUserExistsError,
|
|
1881
2028
|
isVersionEditable,
|
|
1882
2029
|
issuerIdentityOf,
|
|
1883
2030
|
keepOneRecommended,
|
|
2031
|
+
maintenanceRetryAfterSeconds,
|
|
2032
|
+
maintenanceStatusOf,
|
|
2033
|
+
maintenanceWindowStatusOf,
|
|
2034
|
+
maintenanceWindowViewOf,
|
|
1884
2035
|
missingRequiresFor,
|
|
1885
2036
|
movedIdentityFields,
|
|
2037
|
+
parseZonedInstant,
|
|
1886
2038
|
pickActivePromo,
|
|
1887
2039
|
planCatalogSettingsOf,
|
|
1888
2040
|
previousUtcDay,
|
|
@@ -1903,6 +2055,7 @@ export {
|
|
|
1903
2055
|
subscriberPaymentMethodColumns,
|
|
1904
2056
|
toBundleStemRow,
|
|
1905
2057
|
toContractLineItemRecord,
|
|
2058
|
+
toMaintenanceWindowRecord,
|
|
1906
2059
|
toPlanRow,
|
|
1907
2060
|
toPlanVersionRow,
|
|
1908
2061
|
toRunningContractIssuer,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saasicat/core",
|
|
3
|
-
"version": "1.0.0-rc.
|
|
3
|
+
"version": "1.0.0-rc.23",
|
|
4
4
|
"description": "TypeScript contracts for SaaSiCat — capabilities, catalogs, subscriptions, entitlements, admin manifests, persistence ports — and the pure domain logic both server and browser run: error catalogues, plan-diff classification, promo arithmetic, feature requirements.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|