@teamscala/service-boot 0.23.349 → 0.23.352
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 +479 -567
- package/src/agreement-gate.ts +1 -1
- package/src/auth-gate.ts +0 -11
- package/src/fn-routers/index.ts +1 -12
- package/src/fn-routers/public-agreement-router.ts +37 -49
- package/src/mount-orpc-router.test.ts +46 -0
- package/src/mount-orpc-router.ts +16 -3
- package/src/mutable-app-ref.ts +0 -20
- package/src/page-server-orpc/puck-pages-router.ts +0 -102
- package/src/register-page-server-client.ts +0 -4
- package/src/register-ws-routes.ts +10 -3
- package/src/two-phase-boot.ts +2 -1
- package/src/ui-service.ts +11 -18
- package/src/fn-routers/ai-health-router.ts +0 -64
- package/src/fn-routers/panel-data-router.ts +0 -93
- package/src/fn-routers/plans-router.ts +0 -141
- package/src/fn-routers/task-sync-assign.test.ts +0 -483
- package/src/fn-routers/task-sync-assign.ts +0 -485
- package/src/fn-routers/task-sync-bind.test.ts +0 -110
- package/src/fn-routers/task-sync-brief.test.ts +0 -120
- package/src/fn-routers/task-sync-brief.ts +0 -151
- package/src/fn-routers/task-sync-classify.test.ts +0 -109
- package/src/fn-routers/task-sync-close.test.ts +0 -44
- package/src/fn-routers/task-sync-close.ts +0 -82
- package/src/fn-routers/task-sync-created-status.test.ts +0 -58
- package/src/fn-routers/task-sync-dispatch.test.ts +0 -62
- package/src/fn-routers/task-sync-hook-lifecycle.test.ts +0 -204
- package/src/fn-routers/task-sync-lane.test.ts +0 -36
- package/src/fn-routers/task-sync-projection.test.ts +0 -172
- package/src/fn-routers/task-sync-projection.ts +0 -273
- package/src/fn-routers/task-sync-rename.test.ts +0 -53
- package/src/fn-routers/task-sync-rename.ts +0 -111
- package/src/fn-routers/tasks-router.ts +0 -77
- package/src/page-server-orpc/portal-diagnostics-router.ts +0 -170
- package/src/page-server-orpc/templates-router.ts +0 -116
- package/src/serve/serve-ws.test.ts +0 -135
- package/src/serve/serve.ts +0 -148
- package/src/serve/stamp-transport-identity.test.ts +0 -24
- package/src/serve/stamp-transport-identity.ts +0 -109
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @system service-boot
|
|
3
|
-
* @status handwritten
|
|
4
|
-
* @edit edit directly
|
|
5
|
-
*
|
|
6
|
-
* `regions.resolvePanelData` — resolves a page-kind panel's OWN loaderData so a
|
|
7
|
-
* panel region renders blocks against the panel page's data, not the host
|
|
8
|
-
* page's. RegionPageSwap calls this on panel-open + injects the result via
|
|
9
|
-
* LiveDataProvider("page-data"), making panel-embedded data blocks (e.g. the
|
|
10
|
-
* workload Chart) self-sufficient: each panel carries its own data, no host
|
|
11
|
-
* coupling + no preloading the host route.
|
|
12
|
-
*
|
|
13
|
-
* Flow (registry reads via the loadRegistryConfig helper — registry-via-helper):
|
|
14
|
-
* panel_view.content.loader_slug → resource_loader config → orgId derived from
|
|
15
|
-
* the serving website (builda_websites.organisation_id) → resolveFromLoaderConfig
|
|
16
|
-
* → the loader's resolved sources. Panels without a loader_slug (e.g. the AI
|
|
17
|
-
* panel, whose block self-loads) return null — no extra round-trip, no behavior
|
|
18
|
-
* change.
|
|
19
|
-
*/
|
|
20
|
-
import { os } from "@orpc/server";
|
|
21
|
-
import * as v from "valibot";
|
|
22
|
-
import { loadRegistryConfig } from "@teamscala/db/registry/load-config";
|
|
23
|
-
import { resolveFromLoaderConfig } from "../resolve-from-loader-config.ts";
|
|
24
|
-
|
|
25
|
-
interface Context {
|
|
26
|
-
prisma: {
|
|
27
|
-
builda_websites: {
|
|
28
|
-
findFirst: (args: unknown) => Promise<{
|
|
29
|
-
organisation_id?: string | null;
|
|
30
|
-
} | null>;
|
|
31
|
-
};
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const panelViewSchema = v.looseObject({
|
|
36
|
-
content: v.looseObject({ loader_slug: v.optional(v.string()) }),
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
export const resolvePanelDataHandler = os
|
|
40
|
-
.$context<Context>()
|
|
41
|
-
.input(
|
|
42
|
-
v.object({
|
|
43
|
-
panelSlug: v.string(),
|
|
44
|
-
websiteSlug: v.string(),
|
|
45
|
-
/** Tenant orgId (preferred over the website's organisation_id, which is
|
|
46
|
-
* the platform org on a multi-tenant portal — not the tenant). The
|
|
47
|
-
* caller (RegionPageSwap) threads this from __PAGE_DATA__.orgId. */
|
|
48
|
-
orgId: v.optional(v.string()),
|
|
49
|
-
}),
|
|
50
|
-
)
|
|
51
|
-
.handler(async ({ input, context }) => {
|
|
52
|
-
// panel_view → loader_slug (null when missing or the panel has no loader).
|
|
53
|
-
let loaderSlug: string | undefined;
|
|
54
|
-
try {
|
|
55
|
-
const pv = await loadRegistryConfig(
|
|
56
|
-
"panel_view",
|
|
57
|
-
input.panelSlug,
|
|
58
|
-
panelViewSchema,
|
|
59
|
-
);
|
|
60
|
-
loaderSlug = pv.content?.loader_slug;
|
|
61
|
-
} catch {
|
|
62
|
-
return null;
|
|
63
|
-
}
|
|
64
|
-
if (!loaderSlug) return null;
|
|
65
|
-
// resource_loader → its config (loose schema; resolveFromLoaderConfig owns
|
|
66
|
-
// the typed LoaderConfig contract).
|
|
67
|
-
let loader: unknown;
|
|
68
|
-
try {
|
|
69
|
-
loader = await loadRegistryConfig(
|
|
70
|
-
"resource_loader",
|
|
71
|
-
loaderSlug,
|
|
72
|
-
v.unknown(),
|
|
73
|
-
);
|
|
74
|
-
} catch {
|
|
75
|
-
return null;
|
|
76
|
-
}
|
|
77
|
-
// Tenant orgId: prefer the caller-provided value (the route's tenant org,
|
|
78
|
-
// threaded from __PAGE_DATA__.orgId); fall back to the website's
|
|
79
|
-
// organisation_id (the platform org — wrong for multi-tenant portals, but
|
|
80
|
-
// the only derivable value when the caller omits it).
|
|
81
|
-
let orgId = input.orgId;
|
|
82
|
-
if (!orgId) {
|
|
83
|
-
const site = await context.prisma.builda_websites.findFirst({
|
|
84
|
-
where: { OR: [{ slug: input.websiteSlug }, { id: input.websiteSlug }] },
|
|
85
|
-
select: { organisation_id: true },
|
|
86
|
-
});
|
|
87
|
-
orgId = site?.organisation_id ?? undefined;
|
|
88
|
-
}
|
|
89
|
-
return await resolveFromLoaderConfig(
|
|
90
|
-
loader as Parameters<typeof resolveFromLoaderConfig>[0],
|
|
91
|
-
{ orgId },
|
|
92
|
-
);
|
|
93
|
-
});
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @system service-boot
|
|
3
|
-
* @status handwritten
|
|
4
|
-
* @edit edit directly
|
|
5
|
-
*
|
|
6
|
-
* ORPC procedures for plan creation with tasks and subtasks.
|
|
7
|
-
* Extracted from @teamscala/orpc/fn-router — primitives tier must not
|
|
8
|
-
* contain application-specific work_items logic.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { authed, type Context } from "./auth-router.ts";
|
|
12
|
-
|
|
13
|
-
function toSession(ctx: Context): { userId: string; orgId: string | null } {
|
|
14
|
-
const user = ctx.user as { id: string; organisationId?: string | null };
|
|
15
|
-
return {
|
|
16
|
-
userId: user.id,
|
|
17
|
-
orgId: ctx.orgId || user.organisationId || null,
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export const plansRouter = {
|
|
22
|
-
save: authed.handler(async ({ input, context }) => {
|
|
23
|
-
const data = input as {
|
|
24
|
-
project_id?: string;
|
|
25
|
-
initiative_id?: string;
|
|
26
|
-
title: string;
|
|
27
|
-
description?: string;
|
|
28
|
-
status?: string;
|
|
29
|
-
due_date?: string;
|
|
30
|
-
start_date?: string;
|
|
31
|
-
tasks?: Array<{
|
|
32
|
-
title: string;
|
|
33
|
-
description?: string;
|
|
34
|
-
status?: string;
|
|
35
|
-
position?: number;
|
|
36
|
-
depends_on?: Array<string | number>;
|
|
37
|
-
subtasks?: Array<{
|
|
38
|
-
title: string;
|
|
39
|
-
completed?: boolean;
|
|
40
|
-
position?: number;
|
|
41
|
-
}>;
|
|
42
|
-
}>;
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
if (!data.title) throw new Error("title is required");
|
|
46
|
-
if (!data.initiative_id && !data.project_id)
|
|
47
|
-
throw new Error("initiative_id is required when creating a new plan");
|
|
48
|
-
|
|
49
|
-
const userId = toSession(context).userId;
|
|
50
|
-
const result = await (context.prisma as any).$transaction(
|
|
51
|
-
async (tx: any) => {
|
|
52
|
-
let pid = data.project_id;
|
|
53
|
-
if (!pid) {
|
|
54
|
-
const project = await tx.work_items.create({
|
|
55
|
-
data: {
|
|
56
|
-
kind: "project",
|
|
57
|
-
initiative_id: data.initiative_id!,
|
|
58
|
-
title: data.title,
|
|
59
|
-
description: data.description || null,
|
|
60
|
-
status: data.status || "active",
|
|
61
|
-
due_date: data.due_date || null,
|
|
62
|
-
start_date: data.start_date || null,
|
|
63
|
-
created_by: userId,
|
|
64
|
-
updated_by: userId,
|
|
65
|
-
},
|
|
66
|
-
select: { id: true },
|
|
67
|
-
});
|
|
68
|
-
pid = project.id;
|
|
69
|
-
} else {
|
|
70
|
-
await tx.work_items.update({
|
|
71
|
-
where: { kind: "project", id: pid },
|
|
72
|
-
data: {
|
|
73
|
-
title: data.title,
|
|
74
|
-
description: data.description || undefined,
|
|
75
|
-
status: data.status || undefined,
|
|
76
|
-
due_date: data.due_date || undefined,
|
|
77
|
-
start_date: data.start_date || undefined,
|
|
78
|
-
updated_by: userId,
|
|
79
|
-
},
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const createdTaskIds: string[] = [];
|
|
84
|
-
const createdTasks: any[] = [];
|
|
85
|
-
|
|
86
|
-
if (data.tasks && data.tasks.length > 0) {
|
|
87
|
-
for (let i = 0; i < data.tasks.length; i++) {
|
|
88
|
-
const t = data.tasks[i]!;
|
|
89
|
-
const resolvedDeps = (t.depends_on || []).map((dep) => {
|
|
90
|
-
if (typeof dep === "number") {
|
|
91
|
-
const resolved = createdTaskIds[dep];
|
|
92
|
-
if (!resolved)
|
|
93
|
-
throw new Error(
|
|
94
|
-
`depends_on index ${dep} is out of range (only ${createdTaskIds.length} tasks created so far)`,
|
|
95
|
-
);
|
|
96
|
-
return resolved;
|
|
97
|
-
}
|
|
98
|
-
return dep;
|
|
99
|
-
});
|
|
100
|
-
const task = await tx.work_items.create({
|
|
101
|
-
data: {
|
|
102
|
-
kind: "task",
|
|
103
|
-
project_id: pid!,
|
|
104
|
-
title: t.title,
|
|
105
|
-
description: t.description || null,
|
|
106
|
-
status: t.status || "todo",
|
|
107
|
-
position: String(t.position ?? i),
|
|
108
|
-
depends_on: resolvedDeps,
|
|
109
|
-
assignee_type: "agent",
|
|
110
|
-
automation_type: "manual",
|
|
111
|
-
responsible_party: "team",
|
|
112
|
-
created_by: userId,
|
|
113
|
-
updated_by: userId,
|
|
114
|
-
},
|
|
115
|
-
select: { id: true },
|
|
116
|
-
});
|
|
117
|
-
createdTaskIds.push(task.id);
|
|
118
|
-
createdTasks.push({ ...t, id: task.id, _tempIndex: i });
|
|
119
|
-
|
|
120
|
-
if (t.subtasks && t.subtasks.length > 0) {
|
|
121
|
-
for (let j = 0; j < t.subtasks.length; j++) {
|
|
122
|
-
const st = t.subtasks[j]!;
|
|
123
|
-
await tx.work_items.create({
|
|
124
|
-
data: {
|
|
125
|
-
kind: "subtask",
|
|
126
|
-
task_id: task.id,
|
|
127
|
-
title: st.title,
|
|
128
|
-
completed: st.completed ?? false,
|
|
129
|
-
position: st.position ?? j,
|
|
130
|
-
},
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
return { project_id: pid, tasks_created: createdTasks.length };
|
|
137
|
-
},
|
|
138
|
-
);
|
|
139
|
-
return { success: true, data: result };
|
|
140
|
-
}),
|
|
141
|
-
};
|