@cosmicdrift/kumiko-samples 0.395.0 → 0.397.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-samples",
3
- "version": "0.395.0",
3
+ "version": "0.397.0",
4
4
  "description": "Source trees of the Kumiko sample recipes, sample apps and bundled features in repo layout, for tooling such as few-shot corpus builds.",
5
5
  "license": "MIT",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-bundled-features",
3
- "version": "0.395.0",
3
+ "version": "0.397.0",
4
4
  "description": "Built-in features — tenant, user, auth, delivery. The stuff you'd rewrite anyway, already typed.",
5
5
  "license": "MIT",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
@@ -1,4 +1,10 @@
1
1
  [
2
+ {
3
+ "version": "0.397.0",
4
+ "type": "improvement",
5
+ "title": "Cap limits see the tenant and can read its add-ons",
6
+ "detail": "`caps-usage` and `tenant-caps-list` resolved each limit from the tier and the tenant's config only, so an app whose limit depends on booked add-ons had to show the bare tier limit while enforcement already counted the add-ons (a 300 % bar for a tenant that may still provision). Both queries now pass `tenantId` (the target tenant, also on the SystemAdmin override and per listed row) and `db`. The new context type is exported as `CapSpecLimitContext`; existing limits that take the narrower `CapLimitContext` keep compiling."
7
+ },
2
8
  {
3
9
  "version": "0.394.0",
4
10
  "type": "improvement",
@@ -81,7 +81,10 @@ export function createCapsUsageQuery(
81
81
  const rows: CapUsageWithMeta[] = await Promise.all(
82
82
  caps.map(async (cap) => {
83
83
  const used = await cap.usage(db, targetTenantId);
84
- const limit = cap.showLimit === false ? null : await cap.limit(tier, { config });
84
+ const limit =
85
+ cap.showLimit === false
86
+ ? null
87
+ : await cap.limit(tier, { config, tenantId: targetTenantId, db });
85
88
  const base = {
86
89
  id: cap.id,
87
90
  label: cap.label,
@@ -9,10 +9,9 @@ import {
9
9
  import { InternalError, ValidationError } from "@cosmicdrift/kumiko-framework/errors";
10
10
  import * as z from "zod";
11
11
  import { subscriptionsProjectionTable } from "../../billing-foundation/index.ts";
12
- import type { CapLimitContext } from "../../cap-counter/index.ts";
13
12
  import { decryptTenantNames, tenantTable } from "../../tenant/index.ts";
14
13
  import { tierAssignmentEntity } from "../../tier-engine/index.ts";
15
- import type { CapSpec, CapUsage } from "../types.ts";
14
+ import type { CapSpec, CapSpecLimitContext, CapUsage } from "../types.ts";
16
15
  import { capFieldName } from "../web/constants.ts";
17
16
  import { computeFraction } from "../web/usage-math.ts";
18
17
 
@@ -81,12 +80,13 @@ function tenantCapKey(tenantId: string, capId: string): string {
81
80
  // a limit shared across tenants of the same tier would show the wrong value.
82
81
  async function resolveLimitsByTenantAndCap(
83
82
  caps: readonly CapSpec[],
84
- tenants: readonly { readonly tenantId: string; readonly tier: string }[],
83
+ tenants: readonly { readonly tenantId: TenantId; readonly tier: string }[],
85
84
  configFor: ((tenantId: TenantId) => ConfigAccessor) | undefined,
85
+ db: CapSpecLimitContext["db"],
86
86
  ): Promise<ReadonlyMap<string, number | null>> {
87
87
  const entries = await Promise.all(
88
88
  tenants.flatMap(({ tenantId, tier }) => {
89
- const context: CapLimitContext = { config: configFor?.(tenantId) };
89
+ const context: CapSpecLimitContext = { config: configFor?.(tenantId), tenantId, db };
90
90
  return caps.map(
91
91
  async (cap) => [tenantCapKey(tenantId, cap.id), await cap.limit(tier, context)] as const,
92
92
  );
@@ -252,6 +252,7 @@ export function createTenantCapsListQuery(caps: readonly CapSpec[], listCaps: re
252
252
  listedCaps,
253
253
  page,
254
254
  ctx.tenants.config,
255
+ db,
255
256
  );
256
257
 
257
258
  const rows: TenantCapsListRow[] = page.map((row) => {
@@ -7,7 +7,13 @@ export {
7
7
  createTenantCapListScreen,
8
8
  platformTenantCapsScreen,
9
9
  } from "./screens.ts";
10
- export type { CapSpec, CapUsage, CapUsageTone, CapUsageWithMeta } from "./types.ts";
10
+ export type {
11
+ CapSpec,
12
+ CapSpecLimitContext,
13
+ CapUsage,
14
+ CapUsageTone,
15
+ CapUsageWithMeta,
16
+ } from "./types.ts";
11
17
  export {
12
18
  CAP_CARDS_PANEL_COMPONENT,
13
19
  CAP_OVERVIEW_FEATURE,
@@ -9,6 +9,14 @@ export type CapUnit = "count" | "mb" | "tokens";
9
9
  // both together when a cap needs a shape not covered here.
10
10
  export type CapIconKey = "file" | "hash" | "database" | "users" | "mail" | "gauge";
11
11
 
12
+ // What a limit sees besides the tier: the target tenant, the same system-mode db `usage` gets
13
+ // (app-owned rows such as booked add-ons are read from it, scoped by tenantId), and the target
14
+ // tenant's config.
15
+ export type CapSpecLimitContext = CapLimitContext & {
16
+ readonly tenantId: TenantId;
17
+ readonly db: TenantDb;
18
+ };
19
+
12
20
  // Consumer-owned cap definition. `usage`/`usageBatch` receive the SAME
13
21
  // unfiltered system-mode TenantDb the handler itself reads through — the
14
22
  // callback is responsible for scoping its own app-owned tables by tenantId,
@@ -22,7 +30,7 @@ export type CapSpec = {
22
30
  // Resolved once per (cap, tier) per request, so a config read here is cheap.
23
31
  readonly limit: (
24
32
  tier: string,
25
- context: CapLimitContext,
33
+ context: CapSpecLimitContext,
26
34
  ) => number | null | Promise<number | null>;
27
35
  // `null` means "no measurement exists yet for this cap" (not "0 used") —
28
36
  // callers that haven't wired up instrumentation return null instead of a
@@ -35,6 +35,6 @@
35
35
  },
36
36
  "devDependencies": {
37
37
  "@cosmicdrift/kumiko-testing": "workspace:*",
38
- "fflate": "^0.8.2"
38
+ "fflate": "^0.8.3"
39
39
  }
40
40
  }
@@ -15,8 +15,8 @@
15
15
  "@cosmicdrift/kumiko-server-runtime": "workspace:*",
16
16
  "@cosmicdrift/kumiko-testing": "workspace:*",
17
17
  "@playwright/test": "^1.62.1",
18
- "@tailwindcss/cli": "^4.3.0",
18
+ "@tailwindcss/cli": "^4.3.3",
19
19
  "@types/react": "^19.2.14",
20
- "tailwindcss": "^4.3.0"
20
+ "tailwindcss": "^4.3.3"
21
21
  }
22
22
  }
@@ -16,8 +16,8 @@
16
16
  "@cosmicdrift/kumiko-server-runtime": "workspace:*",
17
17
  "@cosmicdrift/kumiko-testing": "workspace:*",
18
18
  "@playwright/test": "^1.62.1",
19
- "@tailwindcss/cli": "^4.3.0",
19
+ "@tailwindcss/cli": "^4.3.3",
20
20
  "@types/react": "^19.2.14",
21
- "tailwindcss": "^4.3.0"
21
+ "tailwindcss": "^4.3.3"
22
22
  }
23
23
  }
@@ -16,8 +16,8 @@
16
16
  "@cosmicdrift/kumiko-server-runtime": "workspace:*",
17
17
  "@cosmicdrift/kumiko-testing": "workspace:*",
18
18
  "@playwright/test": "^1.62.1",
19
- "@tailwindcss/cli": "^4.3.0",
19
+ "@tailwindcss/cli": "^4.3.3",
20
20
  "@types/react": "^19.2.14",
21
- "tailwindcss": "^4.3.0"
21
+ "tailwindcss": "^4.3.3"
22
22
  }
23
23
  }