@cosmicdrift/kumiko-framework 0.350.0 → 0.351.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/bun-db/query.js +3 -3
- package/dist/db/dialect.d.ts +1 -1
- package/dist/db/dialect.js +4 -3
- package/dist/db/located-timestamp.js +1 -1
- package/dist/db/table-builder.d.ts +5 -5
- package/dist/engine/boot-validator/query-output-columns.js +3 -3
- package/dist/engine/boot-validator/query-refs.js +4 -5
- package/dist/engine/boot-validator/screens.js +74 -2
- package/dist/engine/steps/_duration-utils.js +1 -1
- package/dist/engine/types/index.d.ts +1 -1
- package/dist/event-store/event-store.d.ts +1 -0
- package/dist/event-store/event-store.js +4 -17
- package/dist/i18n/required-surface-keys.js +7 -1
- package/dist/pipeline/event-dispatcher-delivery.d.ts +1 -0
- package/dist/pipeline/event-dispatcher-delivery.js +3 -10
- package/dist/rate-limit/resolver.js +3 -6
- package/dist/time/legacy-date.d.ts +1 -1
- package/dist/time/legacy-date.js +1 -1
- package/dist/time/polyfill.d.ts +1 -0
- package/dist/time/polyfill.js +14 -33
- package/dist/time/tz-context.d.ts +1 -1
- package/dist/time/tz-context.js +8 -11
- package/dist/ui-types/index.d.ts +1 -1
- package/dist/upgrade-cli.js +39 -35
- package/package.json +4 -4
package/dist/bun-db/query.js
CHANGED
|
@@ -19,9 +19,9 @@
|
|
|
19
19
|
// drizzle's getTableName + getTableColumns (drizzle kept only as a type
|
|
20
20
|
// reference, never as a runtime API call)
|
|
21
21
|
import { KUMIKO_META_SYMBOL } from "@cosmicdrift/kumiko-types/schema-table-types";
|
|
22
|
-
// Static
|
|
23
|
-
// Temporal
|
|
24
|
-
import { Temporal } from "temporal
|
|
22
|
+
// Static import (not getTemporal()): coercion works without an installed
|
|
23
|
+
// global and returns the same Temporal classes as the rest of the repo (#1480).
|
|
24
|
+
import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
25
25
|
import { requestContext } from "../api/request-context.js";
|
|
26
26
|
import { computeBlindIndex, configuredBlindIndexKey } from "../crypto/blind-index.js";
|
|
27
27
|
import { isPiiCiphertext } from "../crypto/pii-ciphertext-format.js";
|
package/dist/db/dialect.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ColumnHandle, type SchemaTable } from "@cosmicdrift/kumiko-types/schema-table-types";
|
|
2
|
-
import { Temporal } from "temporal
|
|
2
|
+
import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
3
3
|
import type { PgType } from "./entity-table-meta.js";
|
|
4
4
|
export type { ColumnHandle, SchemaTable } from "@cosmicdrift/kumiko-types/schema-table-types";
|
|
5
5
|
export type TableColumns<_T = any> = SchemaTable;
|
package/dist/db/dialect.js
CHANGED
|
@@ -13,10 +13,11 @@
|
|
|
13
13
|
// The framework no longer imports drizzle-orm at runtime — schema-files
|
|
14
14
|
// use only this module.
|
|
15
15
|
//
|
|
16
|
-
// Static import, not the ambient global:
|
|
17
|
-
//
|
|
16
|
+
// Static import of the shared Temporal module, not the ambient global: the
|
|
17
|
+
// global may be absent (Hermes/Safari) or torn down in tests, and instantToDriver
|
|
18
|
+
// crashed on timestamptz writes without it (#1480).
|
|
18
19
|
import { KUMIKO_COLUMNS_SYMBOL, KUMIKO_META_SYMBOL, KUMIKO_NAME_SYMBOL, } from "@cosmicdrift/kumiko-types/schema-table-types";
|
|
19
|
-
import { Temporal } from "temporal
|
|
20
|
+
import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
20
21
|
function isNumericPgType(t) {
|
|
21
22
|
return t.startsWith("numeric(");
|
|
22
23
|
}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// Static import, not the ambient global: Bun doesn't expose Temporal on
|
|
12
12
|
// globalThis, so this crashed with "Temporal is not defined" outside boot
|
|
13
13
|
// paths that install it (#1480).
|
|
14
|
-
import { Temporal } from "temporal
|
|
14
|
+
import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
15
15
|
// Sprint F: <name>Utc-Spalte ist jetzt instant() (siehe dialect.ts) —
|
|
16
16
|
// Drizzle gibt direkt Temporal.Instant zurück. Vor Sprint F kam ein PG-
|
|
17
17
|
// Wire-Format-String "2026-04-15 09:00:00+00" rein der via String-Massage
|
|
@@ -167,20 +167,20 @@ export declare function buildBaseColumns(softDelete: boolean, idType?: "serial"
|
|
|
167
167
|
id: ColumnBuilder<string> | ColumnBuilder<number>;
|
|
168
168
|
tenantId: ColumnBuilder<string>;
|
|
169
169
|
version: ColumnBuilder<number>;
|
|
170
|
-
insertedAt: ColumnBuilder<
|
|
171
|
-
modifiedAt: ColumnBuilder<
|
|
170
|
+
insertedAt: ColumnBuilder<Temporal.Instant>;
|
|
171
|
+
modifiedAt: ColumnBuilder<Temporal.Instant>;
|
|
172
172
|
insertedById: ColumnBuilder<string>;
|
|
173
173
|
modifiedById: ColumnBuilder<string>;
|
|
174
174
|
} | {
|
|
175
175
|
id: ColumnBuilder<string> | ColumnBuilder<number>;
|
|
176
176
|
tenantId: ColumnBuilder<string>;
|
|
177
177
|
version: ColumnBuilder<number>;
|
|
178
|
-
insertedAt: ColumnBuilder<
|
|
179
|
-
modifiedAt: ColumnBuilder<
|
|
178
|
+
insertedAt: ColumnBuilder<Temporal.Instant>;
|
|
179
|
+
modifiedAt: ColumnBuilder<Temporal.Instant>;
|
|
180
180
|
insertedById: ColumnBuilder<string>;
|
|
181
181
|
modifiedById: ColumnBuilder<string>;
|
|
182
182
|
isDeleted: ColumnBuilder<boolean>;
|
|
183
|
-
deletedAt: ColumnBuilder<
|
|
183
|
+
deletedAt: ColumnBuilder<Temporal.Instant>;
|
|
184
184
|
deletedById: ColumnBuilder<string>;
|
|
185
185
|
};
|
|
186
186
|
export declare function rowMetaFieldNames(softDelete: boolean): ReadonlySet<string>;
|
|
@@ -175,6 +175,9 @@ function checkVisibleWhenField(queryHandlers, visibleWhen, owner) {
|
|
|
175
175
|
}
|
|
176
176
|
function checkDashboardOutputFields(queryHandlers, featureName, screenId, screen) {
|
|
177
177
|
for (const panel of screen.panels) {
|
|
178
|
+
if (panel.kind !== "custom") {
|
|
179
|
+
checkVisibleWhenField(queryHandlers, panel.visibleWhen, `[Feature ${featureName}] Screen "${screenId}" (dashboard) ${panel.kind === "screen" ? "screen-panel" : "panel"} "${panel.id}"`);
|
|
180
|
+
}
|
|
178
181
|
if (panel.kind === "stat") {
|
|
179
182
|
checkDashboardStatPanelFields(queryHandlers, featureName, screenId, panel);
|
|
180
183
|
}
|
|
@@ -183,9 +186,6 @@ function checkDashboardOutputFields(queryHandlers, featureName, screenId, screen
|
|
|
183
186
|
checkDashboardStatPanelFields(queryHandlers, featureName, screenId, stat);
|
|
184
187
|
}
|
|
185
188
|
}
|
|
186
|
-
else if (panel.kind === "screen") {
|
|
187
|
-
checkVisibleWhenField(queryHandlers, panel.visibleWhen, `[Feature ${featureName}] Screen "${screenId}" (dashboard) screen-panel "${panel.id}"`);
|
|
188
|
-
}
|
|
189
189
|
else if (panel.kind === "list") {
|
|
190
190
|
const rowShape = resolveListRowShape(queryHandlers, panel.query, `[Feature ${featureName}] Screen "${screenId}" (dashboard) panel "${panel.id}"`);
|
|
191
191
|
for (const column of panel.columns) {
|
|
@@ -26,12 +26,11 @@ function checkDashboardQueryRefs(queryHandlers, featureName, screenId, screen) {
|
|
|
26
26
|
for (const panel of screen.panels) {
|
|
27
27
|
if (panel.kind === "custom")
|
|
28
28
|
continue;
|
|
29
|
-
if (panel.
|
|
30
|
-
|
|
31
|
-
checkQueryRef(queryHandlers, panel.visibleWhen.query, () => `[Feature ${featureName}] Screen "${screenId}" (dashboard) screen-panel "${panel.id}" visibleWhen`);
|
|
32
|
-
}
|
|
33
|
-
continue;
|
|
29
|
+
if (panel.visibleWhen !== undefined) {
|
|
30
|
+
checkQueryRef(queryHandlers, panel.visibleWhen.query, () => `[Feature ${featureName}] Screen "${screenId}" (dashboard) ${panel.kind === "screen" ? "screen-panel" : "panel"} "${panel.id}" visibleWhen`);
|
|
34
31
|
}
|
|
32
|
+
if (panel.kind === "screen")
|
|
33
|
+
continue;
|
|
35
34
|
if (panel.kind === "stat-group") {
|
|
36
35
|
for (const stat of panel.stats) {
|
|
37
36
|
checkQueryRef(queryHandlers, stat.query, () => `[Feature ${featureName}] Screen "${screenId}" (dashboard) stat-group "${panel.id}" child "${stat.id}"`);
|
|
@@ -1729,17 +1729,27 @@ export function validateDashboardScreenPanel(featureName, screenId, panel, featu
|
|
|
1729
1729
|
`navigate away from the dashboard on submit or cancel. Embed a screen without redirect and ` +
|
|
1730
1730
|
`with cancelTarget: false (or omitted).`);
|
|
1731
1731
|
}
|
|
1732
|
-
|
|
1732
|
+
validateDashboardPanelVisibleWhen(context, panel.visibleWhen);
|
|
1733
|
+
}
|
|
1734
|
+
function validateDashboardPanelVisibleWhen(context, visibleWhen) {
|
|
1733
1735
|
if (visibleWhen !== undefined && (visibleWhen.query === "" || visibleWhen.field === "")) {
|
|
1734
1736
|
throw new Error(`${context} visibleWhen needs a non-empty query and field.`);
|
|
1735
1737
|
}
|
|
1736
1738
|
}
|
|
1737
1739
|
function validateDashboardStatGroupPanel(featureName, screenId, panel, addPanelId) {
|
|
1740
|
+
const context = `[Feature ${featureName}] Screen "${screenId}" (dashboard) stat-group "${panel.id}"`;
|
|
1738
1741
|
if (panel.stats.length === 0) {
|
|
1739
|
-
throw new Error(
|
|
1742
|
+
throw new Error(`${context} has an empty stats list.`);
|
|
1743
|
+
}
|
|
1744
|
+
if (panel.subtitle !== undefined && panel.label === undefined) {
|
|
1745
|
+
throw new Error(`${context} sets subtitle without label — an unlabeled group renders as a KPI strip without a header.`);
|
|
1740
1746
|
}
|
|
1747
|
+
validateDashboardPanelVisibleWhen(context, panel.visibleWhen);
|
|
1741
1748
|
for (const stat of panel.stats) {
|
|
1742
1749
|
addPanelId(stat.id, "stat-group child");
|
|
1750
|
+
if (stat.visibleWhen !== undefined) {
|
|
1751
|
+
throw new Error(`${context} child "${stat.id}" sets visibleWhen — gate the whole group instead; a hidden cell would leave a gap in the strip.`);
|
|
1752
|
+
}
|
|
1743
1753
|
if (!stat.query || typeof stat.query !== "string") {
|
|
1744
1754
|
throw new Error(`[Feature ${featureName}] Screen "${screenId}" (dashboard) stat-group "${panel.id}" child "${stat.id}" has empty or non-string query.`);
|
|
1745
1755
|
}
|
|
@@ -1758,6 +1768,67 @@ function validateDashboardChartDisplay(featureName, screenId, panel) {
|
|
|
1758
1768
|
if (panel.scrollable === true && panel.chart !== "stacked-area") {
|
|
1759
1769
|
throw new Error(`${where} sets scrollable on chart "${panel.chart}" — only "stacked-area" supports it.`);
|
|
1760
1770
|
}
|
|
1771
|
+
validateStackedAreaOnlyProps(where, panel);
|
|
1772
|
+
if (panel.legendTotals !== undefined && typeof panel.legendTotals !== "boolean") {
|
|
1773
|
+
throw new Error(`${where} has a non-boolean legendTotals.`);
|
|
1774
|
+
}
|
|
1775
|
+
for (const [key, color] of Object.entries(panel.seriesColors ?? {})) {
|
|
1776
|
+
if (typeof color !== "string" || color.trim() === "") {
|
|
1777
|
+
throw new Error(`${where} seriesColors["${key}"] must be a non-empty CSS color.`);
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
for (const [kind, look] of Object.entries(panel.markerKinds ?? {})) {
|
|
1781
|
+
validateDashboardChartMarkerKind(`${where} markerKinds["${kind}"]`, look);
|
|
1782
|
+
}
|
|
1783
|
+
if (panel.ranges !== undefined)
|
|
1784
|
+
validateDashboardChartRanges(`${where} ranges`, panel.ranges);
|
|
1785
|
+
}
|
|
1786
|
+
function validateStackedAreaOnlyProps(where, panel) {
|
|
1787
|
+
// skip: stacked-area is the one chart kind that supports these props
|
|
1788
|
+
if (panel.chart === "stacked-area")
|
|
1789
|
+
return;
|
|
1790
|
+
const stackedAreaOnly = ["markerKinds", "legendTotals", "ranges"].filter((prop) => panel[prop] !== undefined);
|
|
1791
|
+
if (stackedAreaOnly.length > 0) {
|
|
1792
|
+
throw new Error(`${where} sets ${stackedAreaOnly.join(", ")} on chart "${panel.chart}" — only "stacked-area" supports it.`);
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
const DASHBOARD_CHART_TONES = new Set([
|
|
1796
|
+
"positive",
|
|
1797
|
+
"negative",
|
|
1798
|
+
"active",
|
|
1799
|
+
"neutral",
|
|
1800
|
+
]);
|
|
1801
|
+
function validateDashboardChartMarkerKind(where, look) {
|
|
1802
|
+
if (look.tone === undefined && look.color === undefined) {
|
|
1803
|
+
throw new Error(`${where} needs a tone or a color.`);
|
|
1804
|
+
}
|
|
1805
|
+
if (look.tone !== undefined && !DASHBOARD_CHART_TONES.has(look.tone)) {
|
|
1806
|
+
throw new Error(`${where}.tone "${String(look.tone)}" is not one of ${[...DASHBOARD_CHART_TONES].join(", ")}.`);
|
|
1807
|
+
}
|
|
1808
|
+
if (look.color !== undefined && (typeof look.color !== "string" || look.color.trim() === "")) {
|
|
1809
|
+
throw new Error(`${where}.color must be a non-empty CSS color.`);
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
function validateDashboardChartRanges(where, ranges) {
|
|
1813
|
+
if (ranges.options.length === 0) {
|
|
1814
|
+
throw new Error(`${where}.options is empty — declare at least one option.`);
|
|
1815
|
+
}
|
|
1816
|
+
const values = new Set();
|
|
1817
|
+
for (const option of ranges.options) {
|
|
1818
|
+
if (option.value.length === 0 || option.label.length === 0) {
|
|
1819
|
+
throw new Error(`${where} option needs a non-empty value and label.`);
|
|
1820
|
+
}
|
|
1821
|
+
if (values.has(option.value)) {
|
|
1822
|
+
throw new Error(`${where}.options has duplicate value "${option.value}".`);
|
|
1823
|
+
}
|
|
1824
|
+
if (option.months !== undefined && (!Number.isInteger(option.months) || option.months <= 0)) {
|
|
1825
|
+
throw new Error(`${where} option "${option.value}" months "${String(option.months)}" must be a positive integer.`);
|
|
1826
|
+
}
|
|
1827
|
+
values.add(option.value);
|
|
1828
|
+
}
|
|
1829
|
+
if (!values.has(ranges.default)) {
|
|
1830
|
+
throw new Error(`${where}.default "${ranges.default}" is not among the options (${[...values].join(", ")}).`);
|
|
1831
|
+
}
|
|
1761
1832
|
}
|
|
1762
1833
|
const CURRENCY_CODE_RE = /^[A-Z]{3}$/;
|
|
1763
1834
|
const MAX_VALUE_FORMAT_FRACTION_DIGITS = 4;
|
|
@@ -1797,6 +1868,7 @@ function validateDashboardQueryPanel(featureName, screenId, panel, featureMap) {
|
|
|
1797
1868
|
}
|
|
1798
1869
|
validateDashboardValueFormat(featureName, screenId, panel.id, panel.valueFormat);
|
|
1799
1870
|
}
|
|
1871
|
+
validateDashboardPanelVisibleWhen(`[Feature ${featureName}] Screen "${screenId}" (dashboard) panel "${panel.id}"`, panel.visibleWhen);
|
|
1800
1872
|
if (panel.kind === "chart")
|
|
1801
1873
|
validateDashboardChartDisplay(featureName, screenId, panel);
|
|
1802
1874
|
if (panel.kind === "list") {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// ISO-8601 duration arithmetic — shared by wait and waitForEvent steps.
|
|
2
2
|
// Accepts "P1D", "PT1H", "P1Y2M3DT4H5M6S" etc.
|
|
3
3
|
// Uses approximate calendar math (365d/year, 30d/month). See #23.
|
|
4
|
-
import { Temporal } from "temporal
|
|
4
|
+
import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
5
5
|
export function addDuration(baseIso, duration) {
|
|
6
6
|
const pattern = /^P(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?)?$/;
|
|
7
7
|
const match = duration.match(pattern);
|
|
@@ -17,7 +17,7 @@ export type { ContentCollectionDefinition, NavDefinition, NavIconKey, } from "@c
|
|
|
17
17
|
export type { FromRule, FromRuleKind, OwnershipClause, OwnershipMap, OwnershipRef, OwnershipRule, SqlFragment, WhereRule, WhereRuleContext, } from "@cosmicdrift/kumiko-types/ownership";
|
|
18
18
|
export type { EntityProjectionExtension, MspErrorMode, MspErrorPolicy, MultiStreamApplyFn, MultiStreamProjectionDefinition, ProjectionDefinition, ProjectionRowIdOf, ProjectionTable, SingleStreamApplyFn, } from "@cosmicdrift/kumiko-types/projection";
|
|
19
19
|
export type { BelongsToRelation, EntityRelations, HasManyRelation, ManyToManyRelation, RelationDefinition, } from "@cosmicdrift/kumiko-types/relations";
|
|
20
|
-
export type { ActionFormScreenDefinition, ConfigEditScreenDefinition, CustomScreenDefinition, CustomScreenRoute, DashboardChartPanel, DashboardCustomPanel, DashboardDateParam, DashboardFeedPanel, DashboardFilterDefinition, DashboardI18nText, DashboardListPanel, DashboardMoneyParam, DashboardPanelDefinition, DashboardPanelVisibility, DashboardProgressListPanel, DashboardScreenDefinition, DashboardScreenPanel, DashboardStatGroupPanel, DashboardStatPanel, DashboardText, DashboardTextParam, DashboardValueFormat, EditExtensionSection, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EditWriteFormSection, EntityEditScreenDefinition, EntityListExpandableRow, EntityListScreenDefinition, FieldCondition, FieldFormatRegistry, FieldRenderer, FormatSpec, FormWidth, ListColumnSpec, ListFacetSpec, ListPaginationMode, ListSortDir, ListSortSpec, MetricNavigate, MetricSpec, PlatformComponent, ProjectionDetailScreenDefinition, ProjectionListScreenDefinition, RecordHeaderSpec, RecordHeaderSubtitlePart, RowAction, RowActionNavigate, RowActionNavigateBase, RowActionWriteHandler, RowFieldExtractor, ScreenDefinition, ScreenFilter, ScreenFilterOp, ScreenNavSugar, ScreenSlots, SecretMintConfirmStep, SecretMintScreenDefinition, SecretReveal, SecretRevealField, SecretsEditScreenDefinition, SecretsEditSection, ToolbarAction, UnitKey, } from "@cosmicdrift/kumiko-types/screen";
|
|
20
|
+
export type { ActionFormScreenDefinition, ConfigEditScreenDefinition, CustomScreenDefinition, CustomScreenRoute, DashboardChartMarkerKind, DashboardChartPanel, DashboardChartRangeOption, DashboardChartRanges, DashboardCustomPanel, DashboardDateParam, DashboardFeedPanel, DashboardFilterDefinition, DashboardI18nText, DashboardListPanel, DashboardMoneyParam, DashboardPanelDefinition, DashboardPanelGate, DashboardPanelVisibility, DashboardProgressListPanel, DashboardScreenDefinition, DashboardScreenPanel, DashboardStatGroupPanel, DashboardStatPanel, DashboardText, DashboardTextParam, DashboardValueFormat, EditExtensionSection, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EditWriteFormSection, EntityEditScreenDefinition, EntityListExpandableRow, EntityListScreenDefinition, FieldCondition, FieldFormatRegistry, FieldRenderer, FormatSpec, FormWidth, ListColumnSpec, ListFacetSpec, ListPaginationMode, ListSortDir, ListSortSpec, MetricNavigate, MetricSpec, PlatformComponent, ProjectionDetailScreenDefinition, ProjectionListScreenDefinition, RecordHeaderSpec, RecordHeaderSubtitlePart, RowAction, RowActionNavigate, RowActionNavigateBase, RowActionWriteHandler, RowFieldExtractor, ScreenDefinition, ScreenFilter, ScreenFilterOp, ScreenNavSugar, ScreenSlots, SecretMintConfirmStep, SecretMintScreenDefinition, SecretReveal, SecretRevealField, SecretsEditScreenDefinition, SecretsEditSection, ToolbarAction, UnitKey, } from "@cosmicdrift/kumiko-types/screen";
|
|
21
21
|
export { metricField, relatedListGroupHeaderLabel, relatedListGroupKey, } from "@cosmicdrift/kumiko-types/screen";
|
|
22
22
|
export type { TargetRef } from "@cosmicdrift/kumiko-types/target-ref";
|
|
23
23
|
export type { Subscribe, TreeAction, TreeActionDef, TreeActionsHandle, TreeChildrenSubscribe, TreeNode, TreeNodeState, } from "@cosmicdrift/kumiko-types/tree-node";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/// <reference types="temporal-polyfill/global" preserve="true" />
|
|
2
2
|
import { type EventMetadata, type StoredEvent } from "@cosmicdrift/kumiko-types/event-store-types";
|
|
3
|
+
import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
3
4
|
import type { DbRunner } from "../db/index.js";
|
|
4
5
|
import type { TenantId } from "../engine/types/index.js";
|
|
5
6
|
export type { EventMetadata, StoredEvent } from "@cosmicdrift/kumiko-types/event-store-types";
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
/// <reference types="temporal-polyfill/global" preserve="true" />
|
|
2
2
|
import { isPersonalDataGated, UNATTRIBUTED_ORIGIN, } from "@cosmicdrift/kumiko-types/event-store-types";
|
|
3
|
-
|
|
4
|
-
// `Temporal` TYPE this file's other Temporal.Instant annotations resolve
|
|
5
|
-
// against (StoredEvent.createdAt et al. — importing the bare name here
|
|
6
|
-
// conflicts with that ambient type, see #1438). Bun doesn't expose Temporal
|
|
7
|
-
// as a runtime value on globalThis, so the un-aliased call below crashed
|
|
8
|
-
// with "Temporal is not defined" outside boot paths that install it (#1480).
|
|
9
|
-
import { Temporal as TemporalPolyfill } from "temporal-polyfill";
|
|
3
|
+
import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
10
4
|
import { requestContext } from "../api/request-context.js";
|
|
11
5
|
import { encryptEventPayloadPii } from "../crypto/event-pii.js";
|
|
12
6
|
import { constraintOf, isUniqueViolation } from "../db/pg-error.js";
|
|
@@ -150,18 +144,11 @@ async function insertSubsequentEvent(db, event, newVersion, eventVersion, seedCr
|
|
|
150
144
|
if (!row)
|
|
151
145
|
throw new VersionConflictError(event.aggregateId, event.expectedVersion);
|
|
152
146
|
const createdAt = row.created_at instanceof Date
|
|
153
|
-
?
|
|
154
|
-
:
|
|
147
|
+
? Temporal.Instant.fromEpochMilliseconds(row.created_at.getTime())
|
|
148
|
+
: Temporal.Instant.from(row.created_at);
|
|
155
149
|
return {
|
|
156
150
|
id: typeof row.id === "bigint" ? row.id : BigInt(row.id),
|
|
157
|
-
|
|
158
|
-
// at runtime, two separate .d.ts sources (temporal-polyfill's bundled
|
|
159
|
-
// types vs the ambient temporal-spec global) disagree on a couple of
|
|
160
|
-
// method-overload signatures (until/equals argument types), so TS treats
|
|
161
|
-
// them as nominally distinct. Safe: this value is only ever consumed as
|
|
162
|
-
// a Temporal.Instant (comparisons, .toString(), arithmetic), never
|
|
163
|
-
// narrowed on the specific overload shape that differs.
|
|
164
|
-
createdAt: createdAt,
|
|
151
|
+
createdAt,
|
|
165
152
|
};
|
|
166
153
|
}
|
|
167
154
|
function buildStoredEvent(event, newVersion, eventVersion, row) {
|
|
@@ -90,8 +90,10 @@ function pushDashboardPanelKeys(out, panel) {
|
|
|
90
90
|
if (panel.kind === "custom")
|
|
91
91
|
return;
|
|
92
92
|
pushKey(out, panel.label);
|
|
93
|
-
if (panel.kind === "chart")
|
|
93
|
+
if (panel.kind === "chart" || panel.kind === "stat-group")
|
|
94
94
|
pushKey(out, panel.subtitle);
|
|
95
|
+
if (panel.kind === "chart")
|
|
96
|
+
pushDashboardChartRangeKeys(out, panel);
|
|
95
97
|
if (panel.kind === "chart" ||
|
|
96
98
|
panel.kind === "list" ||
|
|
97
99
|
panel.kind === "feed" ||
|
|
@@ -111,6 +113,10 @@ function pushDashboardPanelKeys(out, panel) {
|
|
|
111
113
|
}
|
|
112
114
|
}
|
|
113
115
|
}
|
|
116
|
+
function pushDashboardChartRangeKeys(out, panel) {
|
|
117
|
+
for (const option of panel.ranges?.options ?? [])
|
|
118
|
+
pushKey(out, option.label);
|
|
119
|
+
}
|
|
114
120
|
function pushDashboardFilterKeys(out, filter) {
|
|
115
121
|
pushKey(out, filter.label);
|
|
116
122
|
if (filter.allLabel !== undefined)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/// <reference types="temporal-polyfill/global" preserve="true" />
|
|
2
|
+
import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
2
3
|
import type { DbConnection, DbRunner, DbTx } from "../db/connection.js";
|
|
3
4
|
import { type PendingIdRange } from "../db/queries/event-store.js";
|
|
4
5
|
import type { AppContext } from "../engine/types/index.js";
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
/// <reference types="temporal-polyfill/global" preserve="true" />
|
|
2
|
-
|
|
3
|
-
// `Temporal` TYPE that ConsumerStateRow.updatedAt/StoredEventRow.createdAt
|
|
4
|
-
// resolve against (same #1438 dual-package-hazard pattern as event-store.ts).
|
|
5
|
-
import { Temporal as TemporalPolyfill } from "temporal-polyfill";
|
|
2
|
+
import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
6
3
|
import { requestContext } from "../api/request-context.js";
|
|
7
4
|
import { insertConsumerIfAbsent, markConsumerProcessing, rearmDeadConsumer, selectConsumerForUpdate, selectConsumerForUpdateSkipLocked, selectProvablyIdleConsumerPairs, updateConsumerDeliveryOutcome, } from "../db/queries/event-consumer.js";
|
|
8
5
|
import { selectEventsHeadId, selectPendingAndNewEventRows, } from "../db/queries/event-store.js";
|
|
@@ -54,14 +51,10 @@ export async function acquireConsumerState(tx, name, instanceId, rearmCooldownMs
|
|
|
54
51
|
// a prior re-arm) gates the retry; maxRearmCount stops a poison event
|
|
55
52
|
// from looping forever (re-arm → same event fails → dead → re-arm →
|
|
56
53
|
// ...) — after the cap it stays dead until a human intervenes.
|
|
57
|
-
const cooldownDeadline =
|
|
54
|
+
const cooldownDeadline = Temporal.Now.instant().subtract({
|
|
58
55
|
milliseconds: rearmCooldownMs,
|
|
59
56
|
});
|
|
60
|
-
|
|
61
|
-
// at runtime — state.updatedAt is DB-row-typed against the ambient
|
|
62
|
-
// global, two distinct nominal types across the two .d.ts sources (see
|
|
63
|
-
// event-store.ts).
|
|
64
|
-
const cooldownElapsed = TemporalPolyfill.Instant.compare(state.updatedAt, cooldownDeadline) <= 0;
|
|
57
|
+
const cooldownElapsed = Temporal.Instant.compare(state.updatedAt, cooldownDeadline) <= 0;
|
|
65
58
|
if (cooldownElapsed && state.rearmCount < maxRearmCount) {
|
|
66
59
|
const rearmed = await rearmDeadConsumer(tx, name, instanceId);
|
|
67
60
|
const rearmedState = rearmed &&
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
// `Temporal` TYPE that RateLimitDecision.resetAt resolves against (see
|
|
3
|
-
// event-store.ts for the same #1438 dual-package-hazard pattern).
|
|
4
|
-
import { Temporal as TemporalPolyfill } from "temporal-polyfill";
|
|
1
|
+
import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
5
2
|
import { RateLimitError } from "../errors/index.js";
|
|
6
3
|
import { RedisKeys } from "../pipeline/redis-keys.js";
|
|
7
4
|
import { toPublicBucketName } from "./bucket.js";
|
|
@@ -134,7 +131,7 @@ export function createRateLimitResolver(opts) {
|
|
|
134
131
|
const nowMs = now();
|
|
135
132
|
const [allowedFlag, remaining, retryAfterMs] = await client.kumikoRateLimit(`${prefix}${bucket}`, String(config.limit), String(refillRatePerMs), String(cost), String(nowMs), String(ttlSeconds));
|
|
136
133
|
const retryAfterSeconds = Math.ceil(retryAfterMs / 1000);
|
|
137
|
-
const resetAt =
|
|
134
|
+
const resetAt = Temporal.Instant.fromEpochMilliseconds(nowMs + retryAfterMs);
|
|
138
135
|
return {
|
|
139
136
|
allowed: allowedFlag === 1,
|
|
140
137
|
limit: config.limit,
|
|
@@ -162,7 +159,7 @@ export function createRateLimitResolver(opts) {
|
|
|
162
159
|
const nowMs = now();
|
|
163
160
|
const [remaining, retryAfterMs] = await client.kumikoRateLimitPeek(`${prefix}${bucket}`, String(config.limit), String(refillRatePerMs), String(nowMs));
|
|
164
161
|
const retryAfterSeconds = Math.ceil(retryAfterMs / 1000);
|
|
165
|
-
const resetAt =
|
|
162
|
+
const resetAt = Temporal.Instant.fromEpochMilliseconds(nowMs + retryAfterMs);
|
|
166
163
|
return {
|
|
167
164
|
// peek doesn't deduct, so a "would-be" allowed flag is meaningful:
|
|
168
165
|
// true iff at least one token is available right now.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Temporal } from "temporal
|
|
1
|
+
import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
2
2
|
export declare function instantToLegacyDate(instant: Temporal.Instant): Date;
|
|
3
3
|
/** Gegenrichtung: Date aus einer Fremd-Lib (imapflow INTERNALDATE,
|
|
4
4
|
* mailparser Date-Header) → Temporal.Instant. */
|
package/dist/time/legacy-date.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Temporal } from "temporal
|
|
1
|
+
import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
2
2
|
// Temporal→Date-Bridge für Lib-APIs die JS-Date verlangen (imapflow
|
|
3
3
|
// search({since}), nodemailer-Header, ...). Gegenstück zur
|
|
4
4
|
// dateToInstant-Richtung der DB-/Event-Store-Boundaries: Feature-Code
|
package/dist/time/polyfill.d.ts
CHANGED
package/dist/time/polyfill.js
CHANGED
|
@@ -1,45 +1,26 @@
|
|
|
1
1
|
/// <reference types="temporal-polyfill/global" preserve="true" />
|
|
2
|
-
// Temporal
|
|
2
|
+
// Temporal bootstrap.
|
|
3
3
|
//
|
|
4
|
-
// Temporal is native in
|
|
5
|
-
//
|
|
6
|
-
//
|
|
4
|
+
// Temporal is native in Bun >= 1.4 and current Chromium/Firefox, but missing in
|
|
5
|
+
// Safari, iOS and Hermes. `@cosmicdrift/kumiko-types/temporal` resolves one
|
|
6
|
+
// implementation (native first, polyfill otherwise) so framework, consumer and
|
|
7
|
+
// ambient-global code all see the same classes.
|
|
7
8
|
//
|
|
8
9
|
// Idempotent: if `globalThis.Temporal` already exists the call is a no-op.
|
|
9
|
-
// The
|
|
10
|
-
//
|
|
11
|
-
// Re-install
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
|
|
10
|
+
// The check is the live global — not a sticky module flag — so tests that
|
|
11
|
+
// delete the ambient global (fw#1550) still get it back on the next call.
|
|
12
|
+
// Re-install assigns the already-resolved module value: re-importing
|
|
13
|
+
// `temporal-polyfill` here would put a polyfill instance on the global while
|
|
14
|
+
// the rest of the code uses the native classes (instanceof mismatch).
|
|
15
|
+
import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
15
16
|
/**
|
|
16
17
|
* Ensure `globalThis.Temporal` is available. Idempotent.
|
|
17
18
|
*/
|
|
18
19
|
export async function ensureTemporalPolyfill() {
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
// skip: Temporal global already present (native or installed by an earlier call)
|
|
21
|
+
if ("Temporal" in globalThis)
|
|
21
22
|
return;
|
|
22
|
-
}
|
|
23
|
-
if (polyfillPromise) {
|
|
24
|
-
await polyfillPromise;
|
|
25
|
-
if ("Temporal" in globalThis) {
|
|
26
|
-
// skip: Concurrent boot — peer call finished install
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
// Peer resolved but global still missing (torn down mid-flight).
|
|
30
|
-
polyfillPromise = null;
|
|
31
|
-
}
|
|
32
|
-
polyfillPromise = (async () => {
|
|
33
|
-
if ("Temporal" in globalThis) {
|
|
34
|
-
// skip: raced native/peer install
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
// Value export — assign ourselves so teardown + re-call still works
|
|
38
|
-
// (side-effect `temporal-polyfill/global` is ESM-cached and silent on re-import).
|
|
39
|
-
const { Temporal } = await import("temporal-polyfill");
|
|
40
|
-
Object.assign(globalThis, { Temporal });
|
|
41
|
-
})();
|
|
42
|
-
await polyfillPromise;
|
|
23
|
+
Object.assign(globalThis, { Temporal });
|
|
43
24
|
}
|
|
44
25
|
/**
|
|
45
26
|
* Type-safe access to globalThis.Temporal. Throws if the polyfill has not
|
|
@@ -2,7 +2,7 @@ import type { TzContext, TzContextOptions } from "@cosmicdrift/kumiko-types/tz-c
|
|
|
2
2
|
export type { LocatedTimestampJson, TzContext, TzContextOptions, } from "@cosmicdrift/kumiko-types/tz-context";
|
|
3
3
|
/**
|
|
4
4
|
* Factory: creates a TzContext for the current request.
|
|
5
|
-
* Uses
|
|
5
|
+
* Uses the kumiko-types Temporal module export (not globalThis.Temporal) so
|
|
6
6
|
* buildHandlerContext stays ambient-free (fw#1525/#1550).
|
|
7
7
|
*/
|
|
8
8
|
export declare function createTzContext(options?: TzContextOptions): TzContext;
|
package/dist/time/tz-context.js
CHANGED
|
@@ -17,33 +17,30 @@
|
|
|
17
17
|
//
|
|
18
18
|
// The pure type contracts (TzContext, TzContextOptions, LocatedTimestampJson)
|
|
19
19
|
// live in @cosmicdrift/kumiko-types/tz-context — only the factories are here.
|
|
20
|
-
import { Temporal
|
|
20
|
+
import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
21
21
|
import { ensureTemporalPolyfill } from "./polyfill.js";
|
|
22
22
|
/**
|
|
23
23
|
* Factory: creates a TzContext for the current request.
|
|
24
|
-
* Uses
|
|
24
|
+
* Uses the kumiko-types Temporal module export (not globalThis.Temporal) so
|
|
25
25
|
* buildHandlerContext stays ambient-free (fw#1525/#1550).
|
|
26
26
|
*/
|
|
27
27
|
export function createTzContext(options = {}) {
|
|
28
28
|
const tenant = options.tenant ?? "UTC";
|
|
29
29
|
const user = options.user ?? tenant;
|
|
30
30
|
const geoTz = options.geoTz;
|
|
31
|
-
// @cast-boundary temporal-polyfill-vs-ambient: same TC39 Temporal values at
|
|
32
|
-
// runtime; TzContext is typed against ambient Temporal from temporal-spec.
|
|
33
|
-
const T = TemporalPolyfill;
|
|
34
31
|
return {
|
|
35
32
|
tenant,
|
|
36
33
|
user,
|
|
37
|
-
now: () =>
|
|
38
|
-
nowIn: (tz) =>
|
|
39
|
-
today: (tz) =>
|
|
34
|
+
now: () => Temporal.Now.instant(), // @wrapper-known semantic-alias
|
|
35
|
+
nowIn: (tz) => Temporal.Now.zonedDateTimeISO(tz), // @wrapper-known semantic-alias
|
|
36
|
+
today: (tz) => Temporal.Now.plainDateISO(tz), // @wrapper-known semantic-alias
|
|
40
37
|
todayRange: (tz) => {
|
|
41
|
-
const today =
|
|
38
|
+
const today = Temporal.Now.plainDateISO(tz);
|
|
42
39
|
const startZdt = today.toZonedDateTime({ timeZone: tz });
|
|
43
40
|
const endZdt = today.add({ days: 1 }).toZonedDateTime({ timeZone: tz });
|
|
44
41
|
return { start: startZdt.toInstant(), end: endZdt.toInstant() };
|
|
45
42
|
},
|
|
46
|
-
parse: (wallClock, tz) =>
|
|
43
|
+
parse: (wallClock, tz) => Temporal.PlainDateTime.from(wallClock).toZonedDateTime(tz),
|
|
47
44
|
toInstant: (zdt) => zdt.toInstant(),
|
|
48
45
|
toLocatedJson: (zdt) => ({
|
|
49
46
|
// Wall-clock WITHOUT offset (no "Z", no "+01:00") plus the IANA name.
|
|
@@ -52,7 +49,7 @@ export function createTzContext(options = {}) {
|
|
|
52
49
|
at: zdt.toPlainDateTime().toString(),
|
|
53
50
|
tz: zdt.timeZoneId,
|
|
54
51
|
}),
|
|
55
|
-
fromLocatedJson: (obj) =>
|
|
52
|
+
fromLocatedJson: (obj) => Temporal.PlainDateTime.from(obj.at).toZonedDateTime(obj.tz),
|
|
56
53
|
fromCoordinates: async (coords) => {
|
|
57
54
|
if (geoTz === undefined) {
|
|
58
55
|
throw new Error("ctx.tz.fromCoordinates requires a GeoTzProvider — inject one via the app context (e.g. buildServer({ context: { geoTzProvider } }) or runProdApp({ extraContext: { geoTzProvider } })) or install a provider package.");
|
package/dist/ui-types/index.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ export type { BooleanFieldDef, DateFieldDef, EmbeddedDerivedCellDef, EntityDefin
|
|
|
9
9
|
export type { AccessRule, OpenToAllAccessRule, OpenToAllDeclaration, OpenToAllPersonalData, RoleAccessPersonalData, RoleAccessRule, } from "../engine/types/handlers.js";
|
|
10
10
|
export { isOpenToAllGranted, isUiAccessGranted } from "../engine/types/handlers.js";
|
|
11
11
|
export type { IconKey, NavDefinition, NavIconKey } from "../engine/types/nav.js";
|
|
12
|
-
export type { ActionFormFooterAction, ActionFormRedirect, ActionFormScreenDefinition, ConfigEditScreenDefinition, CustomScreenDefinition, CustomScreenRoute, DashboardChartKind, DashboardChartPanel, DashboardChartTone, DashboardCustomPanel, DashboardDateParam, DashboardFeedPanel, DashboardFilterDefinition, DashboardI18nText, DashboardListPanel, DashboardMoneyParam, DashboardPanelDefinition, DashboardPanelEmptyState, DashboardPanelQueryOptions, DashboardPanelSpan, DashboardPanelVisibility, DashboardProgressListPanel, DashboardScopeDefinition, DashboardScreenDefinition, DashboardScreenPanel, DashboardStatGroupPanel, DashboardStatPanel, DashboardText, DashboardTextParam, DashboardTimeRangeDefinition, DashboardValueFormat, EditExtensionSection, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EditWriteFormSection, EntityEditScreenDefinition, EntityListExpandableRow, EntityListFacetConfig, EntityListFacetExtraOption, EntityListScreenDefinition, FieldCondition, FieldIconKey, FieldRenderer, FormWidth, ListBadgeTone, ListColumnDisplay, ListColumnSpec, ListFacetSpec, ListSortSpec, MetricNavigate, MetricSpec, PlatformComponent, ProjectionDetailScreenDefinition, ProjectionListScreenDefinition, RecordHeaderSubtitlePart, RelatedListGroupBy, RelatedListRowTone, RelatedListToolbarAction, RowAction, RowActionDisplay, RowActionDrawer, RowActionMode, RowActionNavigate, RowActionWriteHandler, RowFieldExtractor, ScreenDefinition, ScreenFilter, ScreenFilterOp, ScreenSlots, SecretMintConfirmStep, SecretMintScreenDefinition, SecretReveal, SecretRevealField, SecretsEditScreenDefinition, SecretsEditSection, ToolbarAction, } from "../engine/types/screen.js";
|
|
12
|
+
export type { ActionFormFooterAction, ActionFormRedirect, ActionFormScreenDefinition, ConfigEditScreenDefinition, CustomScreenDefinition, CustomScreenRoute, DashboardChartKind, DashboardChartMarkerKind, DashboardChartPanel, DashboardChartRangeOption, DashboardChartRanges, DashboardChartTone, DashboardCustomPanel, DashboardDateParam, DashboardFeedPanel, DashboardFilterDefinition, DashboardI18nText, DashboardListPanel, DashboardMoneyParam, DashboardPanelDefinition, DashboardPanelEmptyState, DashboardPanelGate, DashboardPanelQueryOptions, DashboardPanelSpan, DashboardPanelVisibility, DashboardProgressListPanel, DashboardScopeDefinition, DashboardScreenDefinition, DashboardScreenPanel, DashboardStatGroupPanel, DashboardStatPanel, DashboardText, DashboardTextParam, DashboardTimeRangeDefinition, DashboardValueFormat, EditExtensionSection, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EditWriteFormSection, EntityEditScreenDefinition, EntityListExpandableRow, EntityListFacetConfig, EntityListFacetExtraOption, EntityListScreenDefinition, FieldCondition, FieldIconKey, FieldRenderer, FormWidth, ListBadgeTone, ListColumnDisplay, ListColumnSpec, ListFacetSpec, ListSortSpec, MetricNavigate, MetricSpec, PlatformComponent, ProjectionDetailScreenDefinition, ProjectionListScreenDefinition, RecordHeaderSubtitlePart, RelatedListGroupBy, RelatedListRowTone, RelatedListToolbarAction, RowAction, RowActionDisplay, RowActionDrawer, RowActionMode, RowActionNavigate, RowActionWriteHandler, RowFieldExtractor, ScreenDefinition, ScreenFilter, ScreenFilterOp, ScreenSlots, SecretMintConfirmStep, SecretMintScreenDefinition, SecretReveal, SecretRevealField, SecretsEditScreenDefinition, SecretsEditSection, ToolbarAction, } from "../engine/types/screen.js";
|
|
13
13
|
export { metricField, relatedListGroupHeaderLabel, relatedListGroupKey, } from "../engine/types/screen.js";
|
|
14
14
|
export type { TargetRef } from "../engine/types/target-ref.js";
|
|
15
15
|
export type { TreeAction, TreeNode, TreeNodeState } from "../engine/types/tree-node.js";
|
package/dist/upgrade-cli.js
CHANGED
|
@@ -6,45 +6,42 @@ import { compareVersions, filterEntriesAfter, parseFeatureChangelog, sortEntries
|
|
|
6
6
|
import { ensureTemporalPolyfill } from "./time/index.js";
|
|
7
7
|
const SEMVER_RE = /^\d+\.\d+\.\d+$/;
|
|
8
8
|
const CODEMOD_SUBDIR = "scripts/codemod";
|
|
9
|
-
function
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
if (existsSync(nmPath)) {
|
|
16
|
-
try {
|
|
17
|
-
const pkg = JSON.parse(readFileSync(nmPath, "utf-8"));
|
|
18
|
-
return pkg.version ?? null;
|
|
19
|
-
}
|
|
20
|
-
catch {
|
|
21
|
-
return null;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
const parent = join(dir, "..");
|
|
25
|
-
if (parent === dir)
|
|
26
|
-
break;
|
|
27
|
-
dir = parent;
|
|
28
|
-
}
|
|
29
|
-
// Fallback: repo-local package root
|
|
30
|
-
const repoPath = join(cwd, repoLocalPath);
|
|
31
|
-
if (existsSync(repoPath)) {
|
|
32
|
-
try {
|
|
33
|
-
const pkg = JSON.parse(readFileSync(repoPath, "utf-8"));
|
|
34
|
-
return pkg.version ?? null;
|
|
35
|
-
}
|
|
36
|
-
catch {
|
|
9
|
+
function readPackageJsonVersion(packageJsonPath) {
|
|
10
|
+
if (!existsSync(packageJsonPath))
|
|
11
|
+
return null;
|
|
12
|
+
try {
|
|
13
|
+
const pkg = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
14
|
+
if (typeof pkg !== "object" || pkg === null || !("version" in pkg))
|
|
37
15
|
return null;
|
|
38
|
-
|
|
16
|
+
return typeof pkg.version === "string" ? pkg.version : null;
|
|
39
17
|
}
|
|
40
|
-
|
|
18
|
+
catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
// bun.lock pins transitive packages too, so a repo that only depends on
|
|
23
|
+
// kumiko-cli/-guards still records the bundled-features version it resolved.
|
|
24
|
+
function readLockfileVersion(root, pkgName) {
|
|
25
|
+
const lockPath = join(root, "bun.lock");
|
|
26
|
+
if (!existsSync(lockPath))
|
|
27
|
+
return null;
|
|
28
|
+
const pinned = new RegExp(`"@cosmicdrift/${pkgName}": \\["@cosmicdrift/${pkgName}@(\\d+\\.\\d+\\.\\d+)"`).exec(readFileSync(lockPath, "utf-8"));
|
|
29
|
+
return pinned?.[1] ?? null;
|
|
30
|
+
}
|
|
31
|
+
// Only the repo's own install, package dir and lockfile count. Walking up
|
|
32
|
+
// from a worktree nested in a parent workspace read that workspace's version,
|
|
33
|
+
// and the isolated linker keeps transitive packages out of node_modules.
|
|
34
|
+
function readPackageVersion(root, pkgName, repoLocalPath) {
|
|
35
|
+
return (readPackageJsonVersion(join(root, `node_modules/@cosmicdrift/${pkgName}/package.json`)) ??
|
|
36
|
+
readPackageJsonVersion(join(root, repoLocalPath)) ??
|
|
37
|
+
readLockfileVersion(root, pkgName));
|
|
41
38
|
}
|
|
42
39
|
// Changelog entries come from @cosmicdrift/kumiko-bundled-features (see
|
|
43
40
|
// findFeaturesDirs); comparing against the framework version instead
|
|
44
41
|
// compares unrelated packages once the two stop being versioned in lockstep.
|
|
45
|
-
function readCurrentVersion(
|
|
46
|
-
return (readPackageVersion(
|
|
47
|
-
readPackageVersion(
|
|
42
|
+
function readCurrentVersion(root) {
|
|
43
|
+
return (readPackageVersion(root, "kumiko-bundled-features", "packages/bundled-features/package.json") ??
|
|
44
|
+
readPackageVersion(root, "kumiko-framework", "packages/framework/package.json"));
|
|
48
45
|
}
|
|
49
46
|
function readChangelogFile(filePath) {
|
|
50
47
|
if (!existsSync(filePath))
|
|
@@ -677,11 +674,18 @@ export async function runUpgradeCli(argv, cwd, out, options) {
|
|
|
677
674
|
for (const changelogFile of packageChangelogFiles) {
|
|
678
675
|
allEntries.push(...readChangelogFile(changelogFile));
|
|
679
676
|
}
|
|
680
|
-
|
|
677
|
+
// Installed version of the target the marker belongs to (cwd unless --dir).
|
|
678
|
+
const targetInstalledVersion = dirFlag ? readCurrentVersion(targetDir) : installedVersion;
|
|
679
|
+
// Changelogs can be found in a parent workspace's newer install. A change
|
|
680
|
+
// the repo has not installed yet is not pending for it.
|
|
681
|
+
const installedEntries = targetInstalledVersion === null
|
|
682
|
+
? allEntries
|
|
683
|
+
: allEntries.filter((entry) => compareVersions(entry.version, targetInstalledVersion) <= 0);
|
|
684
|
+
const pending = sortEntries(filterEntriesAfter(installedEntries, currentVersion));
|
|
681
685
|
if (getFlag(args, "apply")) {
|
|
682
686
|
// Marker must reflect what is actually installed under the target (or
|
|
683
687
|
// cwd), not the filter baseline above — otherwise CI stays green forever.
|
|
684
|
-
const markerVersion =
|
|
688
|
+
const markerVersion = targetInstalledVersion;
|
|
685
689
|
const dryRun = getFlag(args, "dry-run");
|
|
686
690
|
if (markerVersion === null && !dryRun) {
|
|
687
691
|
out.err("");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-framework",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.351.0",
|
|
4
4
|
"description": "Framework core — engine, pipeline, API, DB, and every other bit that makes Kumiko go.",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
@@ -206,8 +206,8 @@
|
|
|
206
206
|
"./package.json": "./package.json"
|
|
207
207
|
},
|
|
208
208
|
"dependencies": {
|
|
209
|
-
"@cosmicdrift/kumiko-http": "0.
|
|
210
|
-
"@cosmicdrift/kumiko-types": "0.
|
|
209
|
+
"@cosmicdrift/kumiko-http": "0.351.0",
|
|
210
|
+
"@cosmicdrift/kumiko-types": "0.351.0",
|
|
211
211
|
"bullmq": "^5.76.7",
|
|
212
212
|
"bun-types": "^1.3.13",
|
|
213
213
|
"hono": "^4.13.1",
|
|
@@ -223,7 +223,7 @@
|
|
|
223
223
|
"zod": "^4.4.3"
|
|
224
224
|
},
|
|
225
225
|
"devDependencies": {
|
|
226
|
-
"@cosmicdrift/kumiko-dispatcher-live": "0.
|
|
226
|
+
"@cosmicdrift/kumiko-dispatcher-live": "0.351.0",
|
|
227
227
|
"bun-types": "^1.3.13",
|
|
228
228
|
"pino-pretty": "^13.1.3"
|
|
229
229
|
},
|