@topogram/cli 0.3.34
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/ARCHITECTURE.md +67 -0
- package/CHANGELOG.md +240 -0
- package/README.md +223 -0
- package/package.json +51 -0
- package/src/adoption/index.js +3 -0
- package/src/adoption/plan.js +702 -0
- package/src/adoption/reporting.js +464 -0
- package/src/adoption/review-groups.js +313 -0
- package/src/agent-ops/query-builders.js +5012 -0
- package/src/archive/archive.js +141 -0
- package/src/archive/compact.js +26 -0
- package/src/archive/jsonl.js +70 -0
- package/src/archive/resolver-bridge.js +82 -0
- package/src/archive/schema.js +87 -0
- package/src/archive/unarchive.js +108 -0
- package/src/catalog.js +752 -0
- package/src/cli/catalog-alias.js +166 -0
- package/src/cli.js +9738 -0
- package/src/component-behavior.js +173 -0
- package/src/example-implementation.js +91 -0
- package/src/format.js +19 -0
- package/src/generator/adapters.d.ts +4 -0
- package/src/generator/adapters.js +325 -0
- package/src/generator/api.d.ts +1 -0
- package/src/generator/api.js +1196 -0
- package/src/generator/check.js +355 -0
- package/src/generator/component-conformance.js +767 -0
- package/src/generator/components.js +39 -0
- package/src/generator/context/bundle.js +291 -0
- package/src/generator/context/diff.js +256 -0
- package/src/generator/context/digest.js +182 -0
- package/src/generator/context/domain-coverage.js +94 -0
- package/src/generator/context/domain-page.js +137 -0
- package/src/generator/context/index.js +42 -0
- package/src/generator/context/report.js +121 -0
- package/src/generator/context/shared.js +1397 -0
- package/src/generator/context/slice.js +703 -0
- package/src/generator/context/task-mode.js +466 -0
- package/src/generator/docs.js +327 -0
- package/src/generator/index.js +161 -0
- package/src/generator/native/parity-bundle.js +311 -0
- package/src/generator/output.js +300 -0
- package/src/generator/registry.js +482 -0
- package/src/generator/runtime/app-bundle.js +456 -0
- package/src/generator/runtime/bundle-shared.js +166 -0
- package/src/generator/runtime/compile-check.js +163 -0
- package/src/generator/runtime/deployment.js +287 -0
- package/src/generator/runtime/environment.js +635 -0
- package/src/generator/runtime/index.js +32 -0
- package/src/generator/runtime/runtime-check.js +554 -0
- package/src/generator/runtime/shared.js +515 -0
- package/src/generator/runtime/smoke.js +219 -0
- package/src/generator/schema.js +204 -0
- package/src/generator/sdlc/board.js +66 -0
- package/src/generator/sdlc/doc-page.js +53 -0
- package/src/generator/sdlc/index.js +23 -0
- package/src/generator/sdlc/release-notes.js +62 -0
- package/src/generator/sdlc/traceability-matrix.js +65 -0
- package/src/generator/shared.js +29 -0
- package/src/generator/surfaces/contracts.js +146 -0
- package/src/generator/surfaces/databases/contract.js +40 -0
- package/src/generator/surfaces/databases/index.js +84 -0
- package/src/generator/surfaces/databases/lifecycle-shared.d.ts +1 -0
- package/src/generator/surfaces/databases/lifecycle-shared.js +612 -0
- package/src/generator/surfaces/databases/migration-plan.js +281 -0
- package/src/generator/surfaces/databases/postgres/capabilities.js +14 -0
- package/src/generator/surfaces/databases/postgres/drizzle.js +99 -0
- package/src/generator/surfaces/databases/postgres/index.js +9 -0
- package/src/generator/surfaces/databases/postgres/lifecycle.js +16 -0
- package/src/generator/surfaces/databases/postgres/prisma.js +159 -0
- package/src/generator/surfaces/databases/postgres/sql-migration.js +102 -0
- package/src/generator/surfaces/databases/postgres/sql-schema.js +34 -0
- package/src/generator/surfaces/databases/shared.d.ts +1 -0
- package/src/generator/surfaces/databases/shared.js +350 -0
- package/src/generator/surfaces/databases/snapshot.js +96 -0
- package/src/generator/surfaces/databases/sqlite/capabilities.js +14 -0
- package/src/generator/surfaces/databases/sqlite/index.js +8 -0
- package/src/generator/surfaces/databases/sqlite/lifecycle.js +16 -0
- package/src/generator/surfaces/databases/sqlite/prisma.js +143 -0
- package/src/generator/surfaces/databases/sqlite/sql-migration.js +65 -0
- package/src/generator/surfaces/databases/sqlite/sql-schema.js +27 -0
- package/src/generator/surfaces/index.js +25 -0
- package/src/generator/surfaces/native/swiftui-app.js +38 -0
- package/src/generator/surfaces/native/swiftui-templates/Package.swift.txt +20 -0
- package/src/generator/surfaces/native/swiftui-templates/README.generated.md +26 -0
- package/src/generator/surfaces/native/swiftui-templates/runtime/DynamicScreens.swift +682 -0
- package/src/generator/surfaces/native/swiftui-templates/runtime/TodoAPIClient.swift +156 -0
- package/src/generator/surfaces/native/swiftui-templates/runtime/TodoSwiftUIApp.swift +44 -0
- package/src/generator/surfaces/native/swiftui-templates/runtime/Visibility.swift +183 -0
- package/src/generator/surfaces/services/express.d.ts +1 -0
- package/src/generator/surfaces/services/express.js +766 -0
- package/src/generator/surfaces/services/hono.d.ts +1 -0
- package/src/generator/surfaces/services/hono.js +204 -0
- package/src/generator/surfaces/services/index.js +42 -0
- package/src/generator/surfaces/services/persistence-wiring.js +240 -0
- package/src/generator/surfaces/services/runtime-helpers.js +631 -0
- package/src/generator/surfaces/services/server-contract.js +80 -0
- package/src/generator/surfaces/services/stateless.d.ts +1 -0
- package/src/generator/surfaces/services/stateless.js +97 -0
- package/src/generator/surfaces/shared.js +64 -0
- package/src/generator/surfaces/web/api-client.js +1 -0
- package/src/generator/surfaces/web/forms.js +1 -0
- package/src/generator/surfaces/web/index.d.ts +2 -0
- package/src/generator/surfaces/web/index.js +53 -0
- package/src/generator/surfaces/web/react-components.js +248 -0
- package/src/generator/surfaces/web/react.js +538 -0
- package/src/generator/surfaces/web/routes.js +1 -0
- package/src/generator/surfaces/web/screens.js +1 -0
- package/src/generator/surfaces/web/shared.js +369 -0
- package/src/generator/surfaces/web/sveltekit-actions.js +28 -0
- package/src/generator/surfaces/web/sveltekit-components.js +234 -0
- package/src/generator/surfaces/web/sveltekit.js +426 -0
- package/src/generator/surfaces/web/ui-web-contract.js +65 -0
- package/src/generator/surfaces/web/vanilla.js +239 -0
- package/src/generator/verification.js +84 -0
- package/src/generator.js +1 -0
- package/src/import/core/context.js +52 -0
- package/src/import/core/contracts.js +23 -0
- package/src/import/core/registry.js +81 -0
- package/src/import/core/runner.js +646 -0
- package/src/import/core/shared.js +910 -0
- package/src/import/enrichers/auth-session.js +18 -0
- package/src/import/enrichers/django-rest.js +226 -0
- package/src/import/enrichers/doc-linking.js +20 -0
- package/src/import/enrichers/rails-controllers.js +246 -0
- package/src/import/enrichers/rails-models.js +130 -0
- package/src/import/enrichers/workflow-target-state.js +10 -0
- package/src/import/extractors/api/aspnet-core.js +304 -0
- package/src/import/extractors/api/django-routes.js +318 -0
- package/src/import/extractors/api/express.js +154 -0
- package/src/import/extractors/api/fastify.js +371 -0
- package/src/import/extractors/api/flutter-dio.js +135 -0
- package/src/import/extractors/api/generic-route-fallback.js +90 -0
- package/src/import/extractors/api/graphql-code-first.js +565 -0
- package/src/import/extractors/api/graphql-sdl.js +309 -0
- package/src/import/extractors/api/jaxrs.js +303 -0
- package/src/import/extractors/api/micronaut.js +213 -0
- package/src/import/extractors/api/next-route.js +50 -0
- package/src/import/extractors/api/next-server-action.js +51 -0
- package/src/import/extractors/api/nextauth.js +52 -0
- package/src/import/extractors/api/openapi-code.js +242 -0
- package/src/import/extractors/api/openapi.js +232 -0
- package/src/import/extractors/api/rails-routes.js +230 -0
- package/src/import/extractors/api/react-native-repository.js +128 -0
- package/src/import/extractors/api/retrofit.js +103 -0
- package/src/import/extractors/api/spring-web.js +372 -0
- package/src/import/extractors/api/swift-webapi.js +116 -0
- package/src/import/extractors/api/trpc.js +212 -0
- package/src/import/extractors/db/django-models.js +232 -0
- package/src/import/extractors/db/dotnet-models.js +93 -0
- package/src/import/extractors/db/drizzle.js +242 -0
- package/src/import/extractors/db/ef-core.js +221 -0
- package/src/import/extractors/db/flutter-entities.js +120 -0
- package/src/import/extractors/db/jpa.js +120 -0
- package/src/import/extractors/db/liquibase.js +180 -0
- package/src/import/extractors/db/mybatis-xml.js +145 -0
- package/src/import/extractors/db/prisma.js +185 -0
- package/src/import/extractors/db/rails-schema.js +175 -0
- package/src/import/extractors/db/react-native-entities.js +95 -0
- package/src/import/extractors/db/room.js +193 -0
- package/src/import/extractors/db/snapshot.js +112 -0
- package/src/import/extractors/db/sql.js +180 -0
- package/src/import/extractors/db/swiftdata.js +137 -0
- package/src/import/extractors/ui/android-compose.js +230 -0
- package/src/import/extractors/ui/backend-only.js +70 -0
- package/src/import/extractors/ui/blazor.js +227 -0
- package/src/import/extractors/ui/flutter-screens.js +152 -0
- package/src/import/extractors/ui/maui-xaml.js +135 -0
- package/src/import/extractors/ui/next-app-router.js +83 -0
- package/src/import/extractors/ui/next-pages-router.js +141 -0
- package/src/import/extractors/ui/razor-pages.js +181 -0
- package/src/import/extractors/ui/react-native-screens.js +166 -0
- package/src/import/extractors/ui/react-router.js +139 -0
- package/src/import/extractors/ui/sveltekit.js +123 -0
- package/src/import/extractors/ui/swiftui.js +193 -0
- package/src/import/extractors/ui/uikit.js +175 -0
- package/src/import/extractors/verification/generic.js +290 -0
- package/src/import/extractors/workflows/generic.js +137 -0
- package/src/import/index.js +7 -0
- package/src/import/provenance.js +158 -0
- package/src/new-project.js +2107 -0
- package/src/parser.js +439 -0
- package/src/policy/review-boundaries.js +165 -0
- package/src/project-config.js +535 -0
- package/src/proofs/backend-parity.js +19 -0
- package/src/proofs/contract-audit.js +220 -0
- package/src/proofs/ios-parity.js +7 -0
- package/src/proofs/issues-parity.js +10 -0
- package/src/proofs/web-parity.js +50 -0
- package/src/realization/api/build-api-realization.js +5 -0
- package/src/realization/api/index.js +1 -0
- package/src/realization/backend/build-backend-runtime-realization.js +82 -0
- package/src/realization/backend/index.d.ts +1 -0
- package/src/realization/backend/index.js +4 -0
- package/src/realization/db/build-db-realization.js +17 -0
- package/src/realization/db/index.js +3 -0
- package/src/realization/db/migration-plan.js +5 -0
- package/src/realization/db/snapshot.js +5 -0
- package/src/realization/ui/build-ui-shared-realization.js +305 -0
- package/src/realization/ui/build-web-realization.js +189 -0
- package/src/realization/ui/index.js +2 -0
- package/src/reconcile/docs.js +280 -0
- package/src/reconcile/index.js +3 -0
- package/src/reconcile/journeys.js +441 -0
- package/src/resolver/docs.js +1 -0
- package/src/resolver/enrich/acceptance-criterion.js +14 -0
- package/src/resolver/enrich/bug.js +12 -0
- package/src/resolver/enrich/component.js +2 -0
- package/src/resolver/enrich/index.js +1 -0
- package/src/resolver/enrich/pitch.js +18 -0
- package/src/resolver/enrich/requirement.js +20 -0
- package/src/resolver/enrich/task.js +16 -0
- package/src/resolver/expressions.js +1 -0
- package/src/resolver/index.js +2422 -0
- package/src/resolver/normalize.js +1 -0
- package/src/resolver.js +1 -0
- package/src/sdlc/adopt.js +65 -0
- package/src/sdlc/check.js +86 -0
- package/src/sdlc/dod/acceptance-criterion.js +22 -0
- package/src/sdlc/dod/bug.js +26 -0
- package/src/sdlc/dod/document.js +23 -0
- package/src/sdlc/dod/index.js +25 -0
- package/src/sdlc/dod/pitch.js +23 -0
- package/src/sdlc/dod/requirement.js +34 -0
- package/src/sdlc/dod/task.js +39 -0
- package/src/sdlc/explain.js +116 -0
- package/src/sdlc/history.js +80 -0
- package/src/sdlc/paths.js +11 -0
- package/src/sdlc/release.js +106 -0
- package/src/sdlc/scaffold.js +89 -0
- package/src/sdlc/status-filter.js +54 -0
- package/src/sdlc/transition.js +112 -0
- package/src/sdlc/transitions/acceptance-criterion.js +28 -0
- package/src/sdlc/transitions/bug.js +31 -0
- package/src/sdlc/transitions/document.js +29 -0
- package/src/sdlc/transitions/index.js +56 -0
- package/src/sdlc/transitions/pitch.js +34 -0
- package/src/sdlc/transitions/requirement.js +31 -0
- package/src/sdlc/transitions/task.js +34 -0
- package/src/template-trust.js +597 -0
- package/src/validator/expressions.js +1 -0
- package/src/validator/index.js +3424 -0
- package/src/validator/kinds.js +346 -0
- package/src/validator/per-kind/acceptance-criterion.js +91 -0
- package/src/validator/per-kind/bug.js +77 -0
- package/src/validator/per-kind/component.js +274 -0
- package/src/validator/per-kind/domain.js +205 -0
- package/src/validator/per-kind/pitch.js +101 -0
- package/src/validator/per-kind/requirement.js +75 -0
- package/src/validator/per-kind/task.js +96 -0
- package/src/validator/registry.js +1 -0
- package/src/validator/utils.js +12 -0
- package/src/validator.js +1 -0
- package/src/workflows.js +7597 -0
- package/src/workspace-docs.js +265 -0
- package/template-helpers/react.js +5 -0
- package/template-helpers/sveltekit.js +5 -0
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
function authTokenExpression(target) {
|
|
2
|
+
return target === "sveltekit"
|
|
3
|
+
? 'publicEnv.PUBLIC_TOPOGRAM_AUTH_TOKEN || ""'
|
|
4
|
+
: 'readPublicEnv("PUBLIC_TOPOGRAM_AUTH_TOKEN") || readPublicEnv("VITE_PUBLIC_TOPOGRAM_AUTH_TOKEN")';
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function apiBaseExpression(target, defaultApiBaseUrl) {
|
|
8
|
+
return target === "sveltekit"
|
|
9
|
+
? `publicEnv.PUBLIC_TOPOGRAM_API_BASE_URL || "${defaultApiBaseUrl}"`
|
|
10
|
+
: `readPublicEnv("PUBLIC_TOPOGRAM_API_BASE_URL") || readPublicEnv("VITE_PUBLIC_TOPOGRAM_API_BASE_URL") || "${defaultApiBaseUrl}"`;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function publicEnvPreamble(target) {
|
|
14
|
+
return target === "sveltekit"
|
|
15
|
+
? 'import { env as publicEnv } from "$env/dynamic/public";\n'
|
|
16
|
+
: "";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function publicEnvHelper(target) {
|
|
20
|
+
if (target === "sveltekit") {
|
|
21
|
+
return "";
|
|
22
|
+
}
|
|
23
|
+
return `function readPublicEnv(name: string) {
|
|
24
|
+
return import.meta.env[name] || "";
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function renderVisibilityModule(target) {
|
|
31
|
+
const preamble = target === "sveltekit" ? `${publicEnvPreamble(target)}\n` : publicEnvPreamble(target);
|
|
32
|
+
return `${preamble}export interface VisibilityRule {
|
|
33
|
+
predicate?: string | null;
|
|
34
|
+
value?: string | null;
|
|
35
|
+
claimValue?: string | null;
|
|
36
|
+
ownershipField?: string | null;
|
|
37
|
+
capability?: { id?: string | null } | null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface VisibilityPrincipalOverride {
|
|
41
|
+
userId?: string | null;
|
|
42
|
+
permissions?: string | string[] | null;
|
|
43
|
+
roles?: string | string[] | null;
|
|
44
|
+
claims?: Record<string, unknown> | null;
|
|
45
|
+
isAdmin?: boolean | string | null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface AuthPrincipal {
|
|
49
|
+
userId: string;
|
|
50
|
+
permissions: Set<string>;
|
|
51
|
+
roles: Set<string>;
|
|
52
|
+
claims: Record<string, unknown>;
|
|
53
|
+
isAdmin: boolean;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
${publicEnvHelper(target)}function authToken() {
|
|
57
|
+
return ${authTokenExpression(target)};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function csvValues(value: string) {
|
|
61
|
+
return value
|
|
62
|
+
.split(",")
|
|
63
|
+
.map((entry) => entry.trim())
|
|
64
|
+
.filter(Boolean);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function normalizeOverrideList(value: string | string[] | null | undefined) {
|
|
68
|
+
if (Array.isArray(value)) {
|
|
69
|
+
return value.filter((entry): entry is string => typeof entry === "string" && entry.length > 0);
|
|
70
|
+
}
|
|
71
|
+
if (typeof value === "string") {
|
|
72
|
+
return csvValues(value);
|
|
73
|
+
}
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function readBoolean(value: string) {
|
|
78
|
+
return value === "true" || value === "1" || value === "yes";
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function parseClaimsJson(raw: string) {
|
|
82
|
+
if (!raw) return {};
|
|
83
|
+
try {
|
|
84
|
+
const parsed = JSON.parse(raw);
|
|
85
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed as Record<string, unknown> : {};
|
|
86
|
+
} catch {
|
|
87
|
+
return {};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function decodeJwtPayload(token: string) {
|
|
92
|
+
const parts = token.split(".");
|
|
93
|
+
if (parts.length < 2) return null;
|
|
94
|
+
try {
|
|
95
|
+
const normalized = parts[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
96
|
+
const padded = normalized.padEnd(normalized.length + ((4 - (normalized.length % 4)) % 4), "=");
|
|
97
|
+
return JSON.parse(atob(padded));
|
|
98
|
+
} catch {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function principalFromJwt(token: string): AuthPrincipal | null {
|
|
104
|
+
const payload = decodeJwtPayload(token);
|
|
105
|
+
if (!payload || typeof payload !== "object") return null;
|
|
106
|
+
return {
|
|
107
|
+
userId: typeof payload.sub === "string" ? payload.sub : "",
|
|
108
|
+
permissions: new Set(Array.isArray(payload.permissions) ? payload.permissions.filter((value: unknown): value is string => typeof value === "string") : []),
|
|
109
|
+
roles: new Set(Array.isArray(payload.roles) ? payload.roles.filter((value: unknown): value is string => typeof value === "string") : []),
|
|
110
|
+
claims: payload as Record<string, unknown>,
|
|
111
|
+
isAdmin: payload.admin === true
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function currentPrincipal(overrides?: VisibilityPrincipalOverride | null): AuthPrincipal | null {
|
|
116
|
+
const token = authToken();
|
|
117
|
+
const jwtPrincipal = token ? principalFromJwt(token) : null;
|
|
118
|
+
const envClaims = parseClaimsJson(${target === "sveltekit" ? 'publicEnv.PUBLIC_TOPOGRAM_AUTH_CLAIMS || ""' : 'readPublicEnv("PUBLIC_TOPOGRAM_AUTH_CLAIMS")'});
|
|
119
|
+
const userId = overrides?.userId || ${target === "sveltekit" ? "publicEnv.PUBLIC_TOPOGRAM_AUTH_USER_ID" : 'readPublicEnv("PUBLIC_TOPOGRAM_AUTH_USER_ID")'} || jwtPrincipal?.userId || "";
|
|
120
|
+
const permissions = new Set([
|
|
121
|
+
...normalizeOverrideList(overrides?.permissions),
|
|
122
|
+
...csvValues(${target === "sveltekit" ? 'publicEnv.PUBLIC_TOPOGRAM_AUTH_PERMISSIONS || ""' : 'readPublicEnv("PUBLIC_TOPOGRAM_AUTH_PERMISSIONS")'}),
|
|
123
|
+
...Array.from(jwtPrincipal?.permissions || [])
|
|
124
|
+
]);
|
|
125
|
+
const roles = new Set([
|
|
126
|
+
...normalizeOverrideList(overrides?.roles),
|
|
127
|
+
...csvValues(${target === "sveltekit" ? 'publicEnv.PUBLIC_TOPOGRAM_AUTH_ROLES || publicEnv.PUBLIC_TOPOGRAM_AUTH_ROLE || ""' : 'readPublicEnv("PUBLIC_TOPOGRAM_AUTH_ROLES") || readPublicEnv("PUBLIC_TOPOGRAM_AUTH_ROLE")'}),
|
|
128
|
+
...Array.from(jwtPrincipal?.roles || [])
|
|
129
|
+
]);
|
|
130
|
+
const isAdmin = (
|
|
131
|
+
typeof overrides?.isAdmin === "boolean"
|
|
132
|
+
? overrides.isAdmin
|
|
133
|
+
: readBoolean(String(overrides?.isAdmin || ""))
|
|
134
|
+
) || readBoolean(${target === "sveltekit" ? 'publicEnv.PUBLIC_TOPOGRAM_AUTH_ADMIN || ""' : 'readPublicEnv("PUBLIC_TOPOGRAM_AUTH_ADMIN")'}) || jwtPrincipal?.isAdmin === true;
|
|
135
|
+
|
|
136
|
+
const claims = {
|
|
137
|
+
...(jwtPrincipal?.claims || {}),
|
|
138
|
+
...envClaims,
|
|
139
|
+
...(overrides?.claims || {})
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
if (!token && !userId && permissions.size === 0 && roles.size === 0 && Object.keys(claims).length === 0 && !isAdmin) {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return { userId, permissions, roles, claims, isAdmin };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function claimMatches(principal: AuthPrincipal, claim: string | null | undefined, claimValue: string | null | undefined) {
|
|
150
|
+
if (!claim) return true;
|
|
151
|
+
const value = principal.claims[claim];
|
|
152
|
+
if (value == null) return false;
|
|
153
|
+
if (!claimValue) {
|
|
154
|
+
return value !== false && value !== "";
|
|
155
|
+
}
|
|
156
|
+
return String(value) === claimValue;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function ownerIdFromResource(resource: Record<string, unknown> | null | undefined, ownershipField?: string | null) {
|
|
160
|
+
if (!resource || typeof resource !== "object") {
|
|
161
|
+
return "";
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (ownershipField) {
|
|
165
|
+
const explicitValue = resource[ownershipField];
|
|
166
|
+
return typeof explicitValue === "string" ? explicitValue : "";
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
for (const field of ["owner_id", "assignee_id", "author_id", "user_id", "created_by_user_id"]) {
|
|
170
|
+
const value = resource[field];
|
|
171
|
+
if (typeof value === "string" && value.length > 0) {
|
|
172
|
+
return value;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return "";
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function canShowAction(
|
|
180
|
+
rule: VisibilityRule | null | undefined,
|
|
181
|
+
resource?: Record<string, unknown> | null,
|
|
182
|
+
overrides?: VisibilityPrincipalOverride | null
|
|
183
|
+
) {
|
|
184
|
+
if (!rule) return true;
|
|
185
|
+
|
|
186
|
+
const principal = currentPrincipal(overrides);
|
|
187
|
+
if (!principal) {
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (rule.predicate === "permission") {
|
|
192
|
+
if (!rule.value) return true;
|
|
193
|
+
return principal.permissions.has("*") || principal.permissions.has(rule.value);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (rule.predicate === "ownership") {
|
|
197
|
+
if (!rule.value || rule.value === "none") return true;
|
|
198
|
+
if (rule.value === "owner_or_admin" && principal.isAdmin) return true;
|
|
199
|
+
return ownerIdFromResource(resource, rule.ownershipField) === principal.userId;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (rule.predicate === "claim") {
|
|
203
|
+
return claimMatches(principal, rule.value, rule.claimValue);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
`;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function renderApiClientModule(target, webReference, options = {}) {
|
|
212
|
+
const defaultApiBaseUrl = webReference.defaultApiBaseUrl || "http://localhost:3000";
|
|
213
|
+
const supportsDownload = Boolean(options.supportsDownload);
|
|
214
|
+
const contractsImportPath = target === "sveltekit" ? "$lib/topogram/api-contracts.json" : "../topogram/api-contracts.json";
|
|
215
|
+
return `${publicEnvPreamble(target)}import apiContracts from "${contractsImportPath}";
|
|
216
|
+
|
|
217
|
+
type Fetcher = typeof fetch;
|
|
218
|
+
type ApiContract = (typeof apiContracts)[keyof typeof apiContracts];
|
|
219
|
+
type RequestOptions = {
|
|
220
|
+
headers?: Record<string, string>;
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
${target === "react" ? "" : publicEnvHelper(target)}function apiBase() {
|
|
224
|
+
return ${target === "react"
|
|
225
|
+
? `import.meta.env.PUBLIC_TOPOGRAM_API_BASE_URL || import.meta.env.VITE_PUBLIC_TOPOGRAM_API_BASE_URL || "${defaultApiBaseUrl}"`
|
|
226
|
+
: apiBaseExpression(target, defaultApiBaseUrl)};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function authToken() {
|
|
230
|
+
return ${target === "react"
|
|
231
|
+
? 'import.meta.env.PUBLIC_TOPOGRAM_AUTH_TOKEN || import.meta.env.VITE_PUBLIC_TOPOGRAM_AUTH_TOKEN || ""'
|
|
232
|
+
: authTokenExpression(target)};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function buildPath(contract: ApiContract, input: Record<string, unknown>) {
|
|
236
|
+
let path = contract.endpoint.path;
|
|
237
|
+
for (const field of contract.requestContract?.transport.path || []) {
|
|
238
|
+
const raw = input[field.name];
|
|
239
|
+
path = path.replace(\`:\${field.transport.wireName}\`, encodeURIComponent(String(raw ?? "")));
|
|
240
|
+
}
|
|
241
|
+
const params = new URLSearchParams();
|
|
242
|
+
for (const field of contract.requestContract?.transport.query || []) {
|
|
243
|
+
const raw = input[field.name];
|
|
244
|
+
if (raw !== undefined && raw !== null && raw !== "") {
|
|
245
|
+
params.set(field.transport.wireName, String(raw));
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
const query = params.toString();
|
|
249
|
+
return query ? \`\${path}?\${query}\` : path;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export async function requestCapability(fetcher: Fetcher, capabilityId: keyof typeof apiContracts, input: Record<string, unknown> = {}, options: RequestOptions = {}) {
|
|
253
|
+
const contract = apiContracts[capabilityId];
|
|
254
|
+
const url = new URL(buildPath(contract, input), apiBase()).toString();
|
|
255
|
+
const headers = new Headers();
|
|
256
|
+
for (const [name, value] of Object.entries(options.headers || {})) {
|
|
257
|
+
headers.set(name, value);
|
|
258
|
+
}
|
|
259
|
+
if ((contract.endpoint.authz || []).length > 0 && authToken() && !headers.has("Authorization")) {
|
|
260
|
+
headers.set("Authorization", "Bearer " + authToken());
|
|
261
|
+
}
|
|
262
|
+
let body: string | undefined;
|
|
263
|
+
if ((contract.requestContract?.transport.body || []).length > 0) {
|
|
264
|
+
headers.set("content-type", "application/json");
|
|
265
|
+
const payload: Record<string, unknown> = {};
|
|
266
|
+
for (const field of contract.requestContract?.transport.body || []) {
|
|
267
|
+
if (input[field.name] !== undefined) {
|
|
268
|
+
payload[field.transport.wireName] = input[field.name];
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
body = JSON.stringify(payload);
|
|
272
|
+
}
|
|
273
|
+
const response = await fetcher(url, { method: contract.endpoint.method, headers, body });
|
|
274
|
+
if (!response.ok) {
|
|
275
|
+
const detail = await response.text();
|
|
276
|
+
const error = new Error(\`\${contract.endpoint.method} \${contract.endpoint.path} failed (\${response.status}): \${detail}\`) as Error & { status?: number; detail?: string };
|
|
277
|
+
error.status = response.status;
|
|
278
|
+
error.detail = detail;
|
|
279
|
+
throw error;
|
|
280
|
+
}
|
|
281
|
+
if (response.status === 204) {
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
${supportsDownload ? 'if ((contract.endpoint.download || []).length > 0) {\n return response.arrayBuffer();\n }\n ' : ""}return response.json();
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export async function listPrimaryResources(fetcher: Fetcher, input: Record<string, unknown> = {}) {
|
|
288
|
+
return requestCapability(fetcher, "${webReference.client.capabilityIds.list}", input);
|
|
289
|
+
}
|
|
290
|
+
export async function ${webReference.client.functionNames.list}(fetcher: Fetcher, input: Record<string, unknown> = {}) {
|
|
291
|
+
return listPrimaryResources(fetcher, input);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export async function getPrimaryResource(fetcher: Fetcher, primary_id: string) {
|
|
295
|
+
return requestCapability(fetcher, "${webReference.client.capabilityIds.get}", { ${webReference.client.primaryParam}: primary_id });
|
|
296
|
+
}
|
|
297
|
+
export async function ${webReference.client.functionNames.get}(fetcher: Fetcher, primary_id: string) {
|
|
298
|
+
return getPrimaryResource(fetcher, primary_id);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export async function createPrimaryResource(fetcher: Fetcher, input: Record<string, unknown>, options: RequestOptions = {}) {
|
|
302
|
+
return requestCapability(fetcher, "${webReference.client.capabilityIds.create}", input, options);
|
|
303
|
+
}
|
|
304
|
+
export async function ${webReference.client.functionNames.create}(fetcher: Fetcher, input: Record<string, unknown>, options: RequestOptions = {}) {
|
|
305
|
+
return createPrimaryResource(fetcher, input, options);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export async function updatePrimaryResource(fetcher: Fetcher, primary_id: string, input: Record<string, unknown> = {}, options: RequestOptions = {}) {
|
|
309
|
+
return requestCapability(fetcher, "${webReference.client.capabilityIds.update}", { ${webReference.client.primaryParam}: primary_id, ...input }, options);
|
|
310
|
+
}
|
|
311
|
+
export async function ${webReference.client.functionNames.update}(fetcher: Fetcher, primary_id: string, input: Record<string, unknown> = {}, options: RequestOptions = {}) {
|
|
312
|
+
return updatePrimaryResource(fetcher, primary_id, input, options);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
export async function terminalPrimaryAction(fetcher: Fetcher, primary_id: string, input: Record<string, unknown> = {}, options: RequestOptions = {}) {
|
|
316
|
+
return requestCapability(fetcher, "${webReference.client.capabilityIds.terminal}", { ${webReference.client.primaryParam}: primary_id, ...input }, options);
|
|
317
|
+
}
|
|
318
|
+
export async function ${webReference.client.functionNames.terminal}(fetcher: Fetcher, primary_id: string, input: Record<string, unknown> = {}, options: RequestOptions = {}) {
|
|
319
|
+
return terminalPrimaryAction(fetcher, primary_id, input, options);
|
|
320
|
+
}
|
|
321
|
+
${(webReference.client.extraFunctions || []).map((entry) => {
|
|
322
|
+
const params = entry.primaryParam
|
|
323
|
+
? `fetcher: Fetcher, ${entry.primaryParam}: string, input: Record<string, unknown> = {}, options: RequestOptions = {}`
|
|
324
|
+
: "fetcher: Fetcher, input: Record<string, unknown> = {}, options: RequestOptions = {}";
|
|
325
|
+
const payload = entry.primaryParam
|
|
326
|
+
? `{ ${entry.primaryParam}, ...input }`
|
|
327
|
+
: "input";
|
|
328
|
+
return `
|
|
329
|
+
export async function ${entry.name}(${params}) {
|
|
330
|
+
return requestCapability(fetcher, "${entry.capabilityId}", ${payload}, options);
|
|
331
|
+
}`;
|
|
332
|
+
}).join("\n")}
|
|
333
|
+
`;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
export function renderLookupModule(target, defaultApiBaseUrl) {
|
|
337
|
+
const preamble = target === "sveltekit" ? `${publicEnvPreamble(target)}\n` : publicEnvPreamble(target);
|
|
338
|
+
const authTokenHelper = `function authToken() {
|
|
339
|
+
return ${target === "react"
|
|
340
|
+
? 'import.meta.env.PUBLIC_TOPOGRAM_AUTH_TOKEN || import.meta.env.VITE_PUBLIC_TOPOGRAM_AUTH_TOKEN || ""'
|
|
341
|
+
: authTokenExpression(target)};
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
`;
|
|
345
|
+
return `${preamble}export interface LookupOption {
|
|
346
|
+
value: string;
|
|
347
|
+
label: string;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
${target === "react" ? "" : publicEnvHelper(target)}function apiBase() {
|
|
351
|
+
return ${target === "react"
|
|
352
|
+
? `import.meta.env.PUBLIC_TOPOGRAM_API_BASE_URL || import.meta.env.VITE_PUBLIC_TOPOGRAM_API_BASE_URL || "${defaultApiBaseUrl}"`
|
|
353
|
+
: apiBaseExpression(target, defaultApiBaseUrl)};
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
${authTokenHelper}export async function listLookupOptions(fetcher: typeof fetch, route: string): Promise<LookupOption[]> {
|
|
357
|
+
const headers = new Headers();
|
|
358
|
+
if (authToken()) {
|
|
359
|
+
headers.set("Authorization", "Bearer " + authToken());
|
|
360
|
+
}
|
|
361
|
+
const response = await fetcher(new URL(route, apiBase()).toString(), { headers });
|
|
362
|
+
if (!response.ok) {
|
|
363
|
+
const detail = await response.text();
|
|
364
|
+
throw new Error(\`Lookup request failed (\${response.status}): \${detail}\`);
|
|
365
|
+
}
|
|
366
|
+
return response.json();
|
|
367
|
+
}
|
|
368
|
+
`;
|
|
369
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
function indentBlock(block, spaces) {
|
|
2
|
+
const indent = " ".repeat(spaces);
|
|
3
|
+
return block
|
|
4
|
+
.trim()
|
|
5
|
+
.split("\n")
|
|
6
|
+
.map((line) => (line ? `${indent}${line}` : ""))
|
|
7
|
+
.join("\n");
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function renderSvelteKitRedirectingAction({
|
|
11
|
+
actionName,
|
|
12
|
+
signature,
|
|
13
|
+
prelude,
|
|
14
|
+
tryStatement,
|
|
15
|
+
catchReturn,
|
|
16
|
+
successStatement
|
|
17
|
+
}) {
|
|
18
|
+
return ` ${actionName}: async (${signature}) => {
|
|
19
|
+
${indentBlock(prelude, 4)}
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
${indentBlock(tryStatement, 6)}
|
|
23
|
+
} catch (error) {
|
|
24
|
+
${indentBlock(catchReturn, 6)}
|
|
25
|
+
}
|
|
26
|
+
${successStatement}
|
|
27
|
+
}`;
|
|
28
|
+
}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {{ id?: string, name?: string }} ComponentReference
|
|
5
|
+
* @typedef {{ component?: ComponentReference, region?: string }} ComponentUsage
|
|
6
|
+
* @typedef {{ patterns?: string[] }} ComponentContract
|
|
7
|
+
* @typedef {Record<string, ComponentContract>} ComponentContractMap
|
|
8
|
+
* @typedef {{ itemsExpression?: string, componentContracts?: ComponentContractMap, useTypescript?: boolean }} RenderOptions
|
|
9
|
+
* @typedef {{ components?: ComponentUsage[] }} ScreenContract
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @param {unknown} value
|
|
14
|
+
* @returns {string}
|
|
15
|
+
*/
|
|
16
|
+
function escapeHtml(value) {
|
|
17
|
+
return String(value ?? "")
|
|
18
|
+
.replace(/&/g, "&")
|
|
19
|
+
.replace(/</g, "<")
|
|
20
|
+
.replace(/>/g, ">")
|
|
21
|
+
.replace(/"/g, """);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @param {ComponentUsage} usage
|
|
26
|
+
* @returns {string}
|
|
27
|
+
*/
|
|
28
|
+
function componentName(usage) {
|
|
29
|
+
return usage?.component?.name || usage?.component?.id || "Component";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @param {ComponentUsage} usage
|
|
34
|
+
* @returns {string}
|
|
35
|
+
*/
|
|
36
|
+
function componentId(usage) {
|
|
37
|
+
return usage?.component?.id || "component";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @param {ComponentUsage} usage
|
|
42
|
+
* @param {ComponentContractMap | undefined} componentContracts
|
|
43
|
+
* @returns {string[]}
|
|
44
|
+
*/
|
|
45
|
+
function componentPatterns(usage, componentContracts) {
|
|
46
|
+
const id = usage?.component?.id;
|
|
47
|
+
const contract = id ? componentContracts?.[id] : null;
|
|
48
|
+
return Array.isArray(contract?.patterns) ? contract.patterns : [];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @param {ComponentUsage} usage
|
|
53
|
+
* @param {ComponentContractMap | undefined} componentContracts
|
|
54
|
+
* @param {string} pattern
|
|
55
|
+
* @returns {boolean}
|
|
56
|
+
*/
|
|
57
|
+
function hasPattern(usage, componentContracts, pattern) {
|
|
58
|
+
return componentPatterns(usage, componentContracts).includes(pattern);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @param {ComponentUsage} usage
|
|
63
|
+
* @param {RenderOptions} options
|
|
64
|
+
* @returns {string}
|
|
65
|
+
*/
|
|
66
|
+
function renderSummaryStats(usage, options) {
|
|
67
|
+
const items = options.itemsExpression || "data.result.items";
|
|
68
|
+
return `<section class="component-card component-summary" data-topogram-component="${escapeHtml(componentId(usage))}">
|
|
69
|
+
<div>
|
|
70
|
+
<p class="component-eyebrow">Component</p>
|
|
71
|
+
<h2>${escapeHtml(componentName(usage))}</h2>
|
|
72
|
+
</div>
|
|
73
|
+
<div class="summary-grid">
|
|
74
|
+
<div>
|
|
75
|
+
<strong>{${items}.length}</strong>
|
|
76
|
+
<span>Total</span>
|
|
77
|
+
</div>
|
|
78
|
+
<div>
|
|
79
|
+
<strong>{Object.keys(${items}[0] ?? {}).length}</strong>
|
|
80
|
+
<span>Fields</span>
|
|
81
|
+
</div>
|
|
82
|
+
<div>
|
|
83
|
+
<strong>{${items}.filter((item) => item && (item.id ?? item.uuid ?? item.key)).length}</strong>
|
|
84
|
+
<span>Identified</span>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
</section>`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* @param {ComponentUsage} usage
|
|
92
|
+
* @param {RenderOptions} options
|
|
93
|
+
* @returns {string}
|
|
94
|
+
*/
|
|
95
|
+
function renderCollectionTable(usage, options) {
|
|
96
|
+
const items = options.itemsExpression || "data.result.items";
|
|
97
|
+
return `<div class="component-card component-table" data-topogram-component="${escapeHtml(componentId(usage))}">
|
|
98
|
+
<div class="component-header">
|
|
99
|
+
<div>
|
|
100
|
+
<p class="component-eyebrow">Component</p>
|
|
101
|
+
<h2>${escapeHtml(componentName(usage))}</h2>
|
|
102
|
+
</div>
|
|
103
|
+
<span class="badge">{${items}.length} items</span>
|
|
104
|
+
</div>
|
|
105
|
+
<div class="table-wrap component-table-wrap">
|
|
106
|
+
<table class="resource-table data-grid">
|
|
107
|
+
<thead>
|
|
108
|
+
<tr>
|
|
109
|
+
{#each Object.keys(${items}[0] ?? {}).slice(0, 4) as field}
|
|
110
|
+
<th>{field}</th>
|
|
111
|
+
{/each}
|
|
112
|
+
</tr>
|
|
113
|
+
</thead>
|
|
114
|
+
<tbody>
|
|
115
|
+
{#each ${items} as item}
|
|
116
|
+
<tr>
|
|
117
|
+
{#each Object.keys(${items}[0] ?? {}).slice(0, 4) as field}
|
|
118
|
+
<td>{String(item?.[field] ?? "")}</td>
|
|
119
|
+
{/each}
|
|
120
|
+
</tr>
|
|
121
|
+
{/each}
|
|
122
|
+
</tbody>
|
|
123
|
+
</table>
|
|
124
|
+
</div>
|
|
125
|
+
</div>`;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* @param {ComponentUsage} usage
|
|
130
|
+
* @param {RenderOptions} options
|
|
131
|
+
* @returns {string}
|
|
132
|
+
*/
|
|
133
|
+
function renderBoard(usage, options) {
|
|
134
|
+
const items = options.itemsExpression || "data.result.items";
|
|
135
|
+
return `<div class="component-card component-board" data-topogram-component="${escapeHtml(componentId(usage))}">
|
|
136
|
+
<div class="component-header">
|
|
137
|
+
<div>
|
|
138
|
+
<p class="component-eyebrow">Component</p>
|
|
139
|
+
<h2>${escapeHtml(componentName(usage))}</h2>
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
<div class="board-grid">
|
|
143
|
+
{#each Array.from(new Set(${items}.map((item) => item?.status ?? item?.state ?? item?.stage ?? item?.category ?? "items"))) as group}
|
|
144
|
+
<section class="board-column">
|
|
145
|
+
<h3>{group}</h3>
|
|
146
|
+
{#each ${items}.filter((item) => (item?.status ?? item?.state ?? item?.stage ?? item?.category ?? "items") === group) as item}
|
|
147
|
+
<div class="board-card">{item.title ?? item.name ?? item.label ?? item.message ?? item.id ?? JSON.stringify(item)}</div>
|
|
148
|
+
{/each}
|
|
149
|
+
</section>
|
|
150
|
+
{/each}
|
|
151
|
+
</div>
|
|
152
|
+
</div>`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* @param {ComponentUsage} usage
|
|
157
|
+
* @param {RenderOptions} options
|
|
158
|
+
* @returns {string}
|
|
159
|
+
*/
|
|
160
|
+
function renderCalendar(usage, options) {
|
|
161
|
+
const items = options.itemsExpression || "data.result.items";
|
|
162
|
+
return `<div class="component-card component-calendar" data-topogram-component="${escapeHtml(componentId(usage))}">
|
|
163
|
+
<div class="component-header">
|
|
164
|
+
<div>
|
|
165
|
+
<p class="component-eyebrow">Component</p>
|
|
166
|
+
<h2>${escapeHtml(componentName(usage))}</h2>
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
<div class="calendar-list">
|
|
170
|
+
{#each ${items}.filter((item) => item?.date || item?.due_at || item?.dueAt || item?.created_at || item?.createdAt || item?.updated_at || item?.updatedAt) as item}
|
|
171
|
+
<div class="calendar-card">
|
|
172
|
+
<span>{item.date ?? item.due_at ?? item.dueAt ?? item.created_at ?? item.createdAt ?? item.updated_at ?? item.updatedAt}</span>
|
|
173
|
+
<strong>{item.title ?? item.name ?? item.label ?? item.message ?? item.id ?? JSON.stringify(item)}</strong>
|
|
174
|
+
</div>
|
|
175
|
+
{/each}
|
|
176
|
+
</div>
|
|
177
|
+
</div>`;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* @param {ComponentUsage} usage
|
|
182
|
+
* @param {RenderOptions} options
|
|
183
|
+
* @returns {string}
|
|
184
|
+
*/
|
|
185
|
+
function renderUsage(usage, options) {
|
|
186
|
+
const componentContracts = options.componentContracts || {};
|
|
187
|
+
if (hasPattern(usage, componentContracts, "summary_stats")) {
|
|
188
|
+
return renderSummaryStats(usage, options);
|
|
189
|
+
}
|
|
190
|
+
if (hasPattern(usage, componentContracts, "board_view")) {
|
|
191
|
+
return renderBoard(usage, options);
|
|
192
|
+
}
|
|
193
|
+
if (hasPattern(usage, componentContracts, "calendar_view")) {
|
|
194
|
+
return renderCalendar(usage, options);
|
|
195
|
+
}
|
|
196
|
+
if (hasPattern(usage, componentContracts, "resource_table") || hasPattern(usage, componentContracts, "data_grid_view")) {
|
|
197
|
+
return renderCollectionTable(usage, options);
|
|
198
|
+
}
|
|
199
|
+
return "";
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* @param {ScreenContract} screen
|
|
204
|
+
* @param {string} region
|
|
205
|
+
* @returns {ComponentUsage[]}
|
|
206
|
+
*/
|
|
207
|
+
export function svelteKitComponentUsagesForRegion(screen, region) {
|
|
208
|
+
return (screen?.components || []).filter((usage) => usage?.region === region);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* @param {ScreenContract} screen
|
|
213
|
+
* @param {string} region
|
|
214
|
+
* @returns {boolean}
|
|
215
|
+
*/
|
|
216
|
+
export function hasSvelteKitComponentRegion(screen, region) {
|
|
217
|
+
return svelteKitComponentUsagesForRegion(screen, region).length > 0;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* @param {ScreenContract} screen
|
|
222
|
+
* @param {string} region
|
|
223
|
+
* @param {RenderOptions} [options]
|
|
224
|
+
* @returns {string}
|
|
225
|
+
*/
|
|
226
|
+
export function renderSvelteKitComponentRegion(screen, region, options = {}) {
|
|
227
|
+
const rendered = svelteKitComponentUsagesForRegion(screen, region)
|
|
228
|
+
.map((usage) => renderUsage(usage, options))
|
|
229
|
+
.filter(Boolean);
|
|
230
|
+
if (rendered.length === 0) {
|
|
231
|
+
return "";
|
|
232
|
+
}
|
|
233
|
+
return rendered.join("\n");
|
|
234
|
+
}
|