@ima-jin/config 1.0.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/README.md +30 -0
- package/dist/index.js +593 -0
- package/dist/index.mjs +446 -0
- package/package.json +26 -0
- package/src/base-path.ts +73 -0
- package/src/cors.ts +43 -0
- package/src/handles.ts +51 -0
- package/src/index.ts +150 -0
- package/src/routes.ts +151 -0
- package/src/services.ts +196 -0
- package/src/session.ts +50 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
export { isAllowedOrigin, corsHeaders, corsOptions, withCors, validateOrigin } from "./cors";
|
|
2
|
+
|
|
3
|
+
export {
|
|
4
|
+
SERVICES,
|
|
5
|
+
SERVICE_NAMES,
|
|
6
|
+
getService,
|
|
7
|
+
getPort,
|
|
8
|
+
getServiceUrl,
|
|
9
|
+
getPublicUrl,
|
|
10
|
+
buildPublicUrl,
|
|
11
|
+
servicesByTier,
|
|
12
|
+
servicesByVisibility,
|
|
13
|
+
buildServiceUrlMap,
|
|
14
|
+
} from "./services";
|
|
15
|
+
export type {
|
|
16
|
+
ServiceDefinition,
|
|
17
|
+
ServiceTier,
|
|
18
|
+
ServiceVisibility,
|
|
19
|
+
ServiceCategory,
|
|
20
|
+
} from "./services";
|
|
21
|
+
|
|
22
|
+
export {
|
|
23
|
+
SESSION_COOKIE_NAME,
|
|
24
|
+
getSessionCookieName,
|
|
25
|
+
getSessionCookieOptions,
|
|
26
|
+
} from "./session";
|
|
27
|
+
|
|
28
|
+
export { apiFetch, apiUrl } from "./base-path";
|
|
29
|
+
|
|
30
|
+
export {
|
|
31
|
+
HANDLE_PATTERN,
|
|
32
|
+
HANDLE_INPUT_PATTERN,
|
|
33
|
+
HANDLE_ALLOWED_CHARS,
|
|
34
|
+
HANDLE_ERROR,
|
|
35
|
+
RESERVED_HANDLES,
|
|
36
|
+
isValidHandle,
|
|
37
|
+
isReservedHandle,
|
|
38
|
+
normalizeHandleInput,
|
|
39
|
+
} from "./handles";
|
|
40
|
+
|
|
41
|
+
export {
|
|
42
|
+
// Event routes
|
|
43
|
+
eventPath,
|
|
44
|
+
eventEditPath,
|
|
45
|
+
eventAdminPath,
|
|
46
|
+
eventRegisterPath,
|
|
47
|
+
eventMyTicketsPath,
|
|
48
|
+
eventCheckoutSuccessPath,
|
|
49
|
+
eventsDashboardPath,
|
|
50
|
+
eventsCreatePath,
|
|
51
|
+
eventsCheckoutSuccessPath,
|
|
52
|
+
eventsAdminListPath,
|
|
53
|
+
eventUrl,
|
|
54
|
+
eventEditUrl,
|
|
55
|
+
eventRegisterUrl,
|
|
56
|
+
eventMyTicketsUrl,
|
|
57
|
+
// Profile routes
|
|
58
|
+
profilePath,
|
|
59
|
+
profileEditPath,
|
|
60
|
+
profileLoginPath,
|
|
61
|
+
profileRegisterPath,
|
|
62
|
+
profileUrl,
|
|
63
|
+
// Auth routes
|
|
64
|
+
authLoginPath,
|
|
65
|
+
authRegisterPath,
|
|
66
|
+
authOnboardPath,
|
|
67
|
+
authSettingsPath,
|
|
68
|
+
authSecurityPath,
|
|
69
|
+
authGroupSettingsPath,
|
|
70
|
+
authGroupsPath,
|
|
71
|
+
authNewGroupPath,
|
|
72
|
+
authAgentsPath,
|
|
73
|
+
authAppsPath,
|
|
74
|
+
authAttestationsPath,
|
|
75
|
+
authAuthorizePath,
|
|
76
|
+
authDeveloperAppsPath,
|
|
77
|
+
authDeveloperAppPath,
|
|
78
|
+
authMembersPath,
|
|
79
|
+
authNotificationsPath,
|
|
80
|
+
authStubsPath,
|
|
81
|
+
authNewStubPath,
|
|
82
|
+
// Chat routes
|
|
83
|
+
chatConversationsPath,
|
|
84
|
+
chatConversationPath,
|
|
85
|
+
chatPath,
|
|
86
|
+
// Connections routes
|
|
87
|
+
connectionsPath,
|
|
88
|
+
connectionsInvitePath,
|
|
89
|
+
connectionsPodPath,
|
|
90
|
+
// Pay routes
|
|
91
|
+
payPath,
|
|
92
|
+
payHistoryPath,
|
|
93
|
+
payPayoutsPath,
|
|
94
|
+
payTopupPath,
|
|
95
|
+
payTopupSuccessPath,
|
|
96
|
+
// Media routes
|
|
97
|
+
mediaPath,
|
|
98
|
+
// Learn routes
|
|
99
|
+
learnCoursePath,
|
|
100
|
+
learnCourseLessonPath,
|
|
101
|
+
learnCoursePresentPath,
|
|
102
|
+
learnDashboardPath,
|
|
103
|
+
learnCourseDashboardPath,
|
|
104
|
+
learnCourseStudentsPath,
|
|
105
|
+
learnHandlePath,
|
|
106
|
+
// Coffee routes
|
|
107
|
+
coffeeHandlePath,
|
|
108
|
+
coffeeDashboardPath,
|
|
109
|
+
coffeeEditPath,
|
|
110
|
+
coffeeSuccessPath,
|
|
111
|
+
// Dykil routes
|
|
112
|
+
dykilHandlePath,
|
|
113
|
+
dykilSurveyPath,
|
|
114
|
+
dykilCreatePath,
|
|
115
|
+
dykilDashboardPath,
|
|
116
|
+
dykilResultsPath,
|
|
117
|
+
dykilEmbedPath,
|
|
118
|
+
// Links routes
|
|
119
|
+
linksHandlePath,
|
|
120
|
+
linksDashboardPath,
|
|
121
|
+
linksEditPath,
|
|
122
|
+
// Market routes
|
|
123
|
+
marketListingPath,
|
|
124
|
+
marketListingEditPath,
|
|
125
|
+
marketNewListingPath,
|
|
126
|
+
marketSellerPath,
|
|
127
|
+
marketCheckoutSuccessPath,
|
|
128
|
+
marketDashboardPath,
|
|
129
|
+
marketSettingsPath,
|
|
130
|
+
// Articles
|
|
131
|
+
articlesPath,
|
|
132
|
+
articleAuthorPath,
|
|
133
|
+
articlePath,
|
|
134
|
+
// Kernel / other
|
|
135
|
+
registryPath,
|
|
136
|
+
registryDocsPath,
|
|
137
|
+
buildPath,
|
|
138
|
+
bumpPath,
|
|
139
|
+
bugsPath,
|
|
140
|
+
bugsAdminPath,
|
|
141
|
+
healthPath,
|
|
142
|
+
privacyPath,
|
|
143
|
+
whitepaperPath,
|
|
144
|
+
subscribePath,
|
|
145
|
+
developerGuidePath,
|
|
146
|
+
docsPath,
|
|
147
|
+
projectPath,
|
|
148
|
+
notifyPath,
|
|
149
|
+
notifySettingsPath,
|
|
150
|
+
} from "./routes";
|
package/src/routes.ts
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized public route paths.
|
|
3
|
+
* Every service imports from here — change the prefix once, changes everywhere.
|
|
4
|
+
*
|
|
5
|
+
* Note: These are the *public-facing* URL paths (after any proxy/basePath
|
|
6
|
+
* stripping). Internal Next.js file paths may differ (e.g. kernel profiles
|
|
7
|
+
* are in `app/profile/p/[handle]` but the public URL is `/p/[handle]`).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// ─── Event routes (events.imajin.ai) ───────────────────────────────────────
|
|
11
|
+
export const eventPath = (eventId: string) => `/e/${eventId}`;
|
|
12
|
+
export const eventEditPath = (eventId: string) => `/e/${eventId}/edit`;
|
|
13
|
+
export const eventRegisterPath = (eventId: string, ticketId: string) =>
|
|
14
|
+
`/e/${eventId}/register/${ticketId}`;
|
|
15
|
+
export const eventMyTicketsPath = (eventId: string) => `/e/${eventId}/my-tickets`;
|
|
16
|
+
export const eventCheckoutSuccessPath = (eventId: string) =>
|
|
17
|
+
`/e/${eventId}#tickets`;
|
|
18
|
+
export const eventAdminPath = (eventId: string) => `/admin/${eventId}`;
|
|
19
|
+
export const eventsDashboardPath = () => `/dashboard`;
|
|
20
|
+
export const eventsCreatePath = () => `/create`;
|
|
21
|
+
export const eventsCheckoutSuccessPath = () => `/checkout/success`;
|
|
22
|
+
export const eventsAdminListPath = () => `/admin`;
|
|
23
|
+
|
|
24
|
+
// Event full URL builders (for cross-service links, emails, etc.)
|
|
25
|
+
export const eventUrl = (baseUrl: string, eventId: string) =>
|
|
26
|
+
`${baseUrl}/e/${eventId}`;
|
|
27
|
+
export const eventEditUrl = (baseUrl: string, eventId: string) =>
|
|
28
|
+
`${baseUrl}/e/${eventId}/edit`;
|
|
29
|
+
export const eventRegisterUrl = (
|
|
30
|
+
baseUrl: string,
|
|
31
|
+
eventId: string,
|
|
32
|
+
ticketId: string
|
|
33
|
+
) => `${baseUrl}/e/${eventId}/register/${ticketId}`;
|
|
34
|
+
export const eventMyTicketsUrl = (baseUrl: string, eventId: string) =>
|
|
35
|
+
`${baseUrl}/e/${eventId}/my-tickets`;
|
|
36
|
+
|
|
37
|
+
// ─── Profile routes (profile.imajin.ai) ────────────────────────────────────
|
|
38
|
+
export const profilePath = (handle: string) => `/p/${handle}`;
|
|
39
|
+
export const profileEditPath = () => `/p/edit`;
|
|
40
|
+
export const profileLoginPath = () => `/profile/login`;
|
|
41
|
+
export const profileRegisterPath = () => `/profile/register`;
|
|
42
|
+
|
|
43
|
+
export const profileUrl = (baseUrl: string, handle: string) =>
|
|
44
|
+
`${baseUrl}/p/${handle}`;
|
|
45
|
+
|
|
46
|
+
// ─── Auth routes (auth.imajin.ai) ──────────────────────────────────────────
|
|
47
|
+
export const authLoginPath = () => `/auth/login`;
|
|
48
|
+
export const authRegisterPath = () => `/auth/register`;
|
|
49
|
+
export const authOnboardPath = () => `/auth/onboard`;
|
|
50
|
+
export const authSettingsPath = () => `/auth/settings`;
|
|
51
|
+
export const authSecurityPath = () => `/auth/settings/security`;
|
|
52
|
+
export const authGroupSettingsPath = (groupDid: string) =>
|
|
53
|
+
`/auth/groups/${groupDid}/settings`;
|
|
54
|
+
export const authGroupsPath = () => `/auth/groups`;
|
|
55
|
+
export const authNewGroupPath = () => `/auth/groups/new`;
|
|
56
|
+
export const authAgentsPath = () => `/auth/agents`;
|
|
57
|
+
export const authAppsPath = () => `/auth/apps`;
|
|
58
|
+
export const authAttestationsPath = () => `/auth/attestations`;
|
|
59
|
+
export const authAuthorizePath = () => `/auth/authorize`;
|
|
60
|
+
export const authDeveloperAppsPath = () => `/auth/developer/apps`;
|
|
61
|
+
export const authDeveloperAppPath = (appId: string) =>
|
|
62
|
+
`/auth/developer/apps/${appId}`;
|
|
63
|
+
export const authMembersPath = () => `/auth/members`;
|
|
64
|
+
export const authNotificationsPath = () => `/auth/notifications`;
|
|
65
|
+
export const authStubsPath = (did: string) => `/auth/stubs/${did}`;
|
|
66
|
+
export const authNewStubPath = () => `/auth/stubs/new`;
|
|
67
|
+
|
|
68
|
+
// ─── Chat routes (chat.imajin.ai) ──────────────────────────────────────────
|
|
69
|
+
export const chatConversationsPath = () => `/chat/conversations`;
|
|
70
|
+
export const chatConversationPath = (type: string, slug: string) =>
|
|
71
|
+
`/chat/conversations/${type}/${slug}`;
|
|
72
|
+
export const chatPath = () => `/chat`;
|
|
73
|
+
|
|
74
|
+
// ─── Connections routes (connections.imajin.ai) ────────────────────────────
|
|
75
|
+
export const connectionsPath = () => `/connections`;
|
|
76
|
+
export const connectionsInvitePath = (did: string, code: string) =>
|
|
77
|
+
`/connections/invite/${did}/${code}`;
|
|
78
|
+
export const connectionsPodPath = (id: string) => `/connections/pods/${id}`;
|
|
79
|
+
|
|
80
|
+
// ─── Pay routes (pay.imajin.ai) ────────────────────────────────────────────
|
|
81
|
+
export const payPath = () => `/pay`;
|
|
82
|
+
export const payHistoryPath = () => `/pay/history`;
|
|
83
|
+
export const payPayoutsPath = () => `/pay/payouts`;
|
|
84
|
+
export const payTopupPath = () => `/pay/topup`;
|
|
85
|
+
export const payTopupSuccessPath = () => `/pay/topup/success`;
|
|
86
|
+
|
|
87
|
+
// ─── Media routes (media.imajin.ai) ────────────────────────────────────────
|
|
88
|
+
export const mediaPath = () => `/media`;
|
|
89
|
+
|
|
90
|
+
// ─── Learn routes (learn.imajin.ai) ────────────────────────────────────────
|
|
91
|
+
export const learnCoursePath = (slug: string) => `/course/${slug}`;
|
|
92
|
+
export const learnCourseLessonPath = (slug: string, lessonId: string) =>
|
|
93
|
+
`/course/${slug}/${lessonId}`;
|
|
94
|
+
export const learnCoursePresentPath = (slug: string) => `/course/${slug}/present`;
|
|
95
|
+
export const learnDashboardPath = () => `/dashboard`;
|
|
96
|
+
export const learnCourseDashboardPath = (slug: string) => `/dashboard/${slug}`;
|
|
97
|
+
export const learnCourseStudentsPath = (slug: string) =>
|
|
98
|
+
`/dashboard/${slug}/students`;
|
|
99
|
+
export const learnHandlePath = (handle: string) => `/${handle}`;
|
|
100
|
+
|
|
101
|
+
// ─── Coffee routes (coffee.imajin.ai) ──────────────────────────────────────
|
|
102
|
+
export const coffeeHandlePath = (handle: string) => `/${handle}`;
|
|
103
|
+
export const coffeeDashboardPath = () => `/dashboard`;
|
|
104
|
+
export const coffeeEditPath = () => `/edit`;
|
|
105
|
+
export const coffeeSuccessPath = () => `/success`;
|
|
106
|
+
|
|
107
|
+
// ─── Dykil routes (dykil.imajin.ai) ────────────────────────────────────────
|
|
108
|
+
export const dykilHandlePath = (handle: string) => `/${handle}`;
|
|
109
|
+
export const dykilSurveyPath = (handle: string, surveyId: string) =>
|
|
110
|
+
`/${handle}/${surveyId}`;
|
|
111
|
+
export const dykilCreatePath = () => `/create`;
|
|
112
|
+
export const dykilDashboardPath = () => `/dashboard`;
|
|
113
|
+
export const dykilResultsPath = (id: string) => `/survey/${id}/results`;
|
|
114
|
+
export const dykilEmbedPath = (surveyId: string) => `/embed/${surveyId}`;
|
|
115
|
+
|
|
116
|
+
// ─── Links routes (links.imajin.ai) ────────────────────────────────────────
|
|
117
|
+
export const linksHandlePath = (handle: string) => `/${handle}`;
|
|
118
|
+
export const linksDashboardPath = () => `/dashboard`;
|
|
119
|
+
export const linksEditPath = () => `/edit`;
|
|
120
|
+
|
|
121
|
+
// ─── Market routes (market.imajin.ai) ──────────────────────────────────────
|
|
122
|
+
export const marketListingPath = (id: string) => `/listings/${id}`;
|
|
123
|
+
export const marketListingEditPath = (id: string) => `/listings/${id}/edit`;
|
|
124
|
+
export const marketNewListingPath = () => `/listings/new`;
|
|
125
|
+
export const marketSellerPath = (handle: string) => `/seller/${handle}`;
|
|
126
|
+
export const marketCheckoutSuccessPath = () => `/checkout/success`;
|
|
127
|
+
export const marketDashboardPath = () => `/dashboard`;
|
|
128
|
+
export const marketSettingsPath = () => `/settings`;
|
|
129
|
+
|
|
130
|
+
// ─── Kernel / articles ─────────────────────────────────────────────────────
|
|
131
|
+
export const articlesPath = () => `/articles`;
|
|
132
|
+
export const articleAuthorPath = (handle: string) => `/articles/${handle}`;
|
|
133
|
+
export const articlePath = (handle: string, slug: string) =>
|
|
134
|
+
`/articles/${handle}/${slug}`;
|
|
135
|
+
|
|
136
|
+
// ─── Kernel / other ────────────────────────────────────────────────────────
|
|
137
|
+
export const registryPath = () => `/registry`;
|
|
138
|
+
export const registryDocsPath = () => `/registry/docs`;
|
|
139
|
+
export const buildPath = () => `/build`;
|
|
140
|
+
export const bumpPath = () => `/bump`;
|
|
141
|
+
export const bugsPath = () => `/bugs`;
|
|
142
|
+
export const bugsAdminPath = () => `/bugs/admin`;
|
|
143
|
+
export const healthPath = () => `/health`;
|
|
144
|
+
export const privacyPath = () => `/privacy`;
|
|
145
|
+
export const whitepaperPath = () => `/whitepaper`;
|
|
146
|
+
export const subscribePath = () => `/subscribe`;
|
|
147
|
+
export const developerGuidePath = () => `/developer-guide`;
|
|
148
|
+
export const docsPath = () => `/docs`;
|
|
149
|
+
export const projectPath = () => `/project`;
|
|
150
|
+
export const notifyPath = () => `/notify`;
|
|
151
|
+
export const notifySettingsPath = () => `/notify/settings`;
|
package/src/services.ts
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical service manifest — single source of truth for all Imajin services.
|
|
3
|
+
*
|
|
4
|
+
* Consumers: registry specs, Caddy config, pm2 ecosystem, docs sync, shared nav.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export type ServiceTier = "core" | "imajin";
|
|
8
|
+
export type ServiceVisibility = "public" | "authenticated" | "creator" | "internal";
|
|
9
|
+
export type ServiceCategory = "kernel" | "core" | "creator" | "developer" | "infrastructure" | "meta";
|
|
10
|
+
|
|
11
|
+
export interface ServiceDefinition {
|
|
12
|
+
/** Subdomain / app directory name */
|
|
13
|
+
name: string;
|
|
14
|
+
/** Human-readable label */
|
|
15
|
+
label: string;
|
|
16
|
+
/** Description */
|
|
17
|
+
description: string;
|
|
18
|
+
/** Emoji icon */
|
|
19
|
+
icon: string;
|
|
20
|
+
/** Dev port (3xxx) */
|
|
21
|
+
devPort: number;
|
|
22
|
+
/** Prod port (7xxx) */
|
|
23
|
+
prodPort: number;
|
|
24
|
+
/** Postgres schema name (null if no DB) */
|
|
25
|
+
schema: string | null;
|
|
26
|
+
/** Deployment tier */
|
|
27
|
+
tier: ServiceTier;
|
|
28
|
+
/** Visibility in launcher / nav */
|
|
29
|
+
visibility: ServiceVisibility;
|
|
30
|
+
/** Grouping category */
|
|
31
|
+
category: ServiceCategory;
|
|
32
|
+
/** External URL override (bypasses subdomain convention) */
|
|
33
|
+
externalUrl?: string;
|
|
34
|
+
/** Path on www service (env-aware — resolves against www's URL at runtime) */
|
|
35
|
+
wwwPath?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const SERVICES: readonly ServiceDefinition[] = [
|
|
39
|
+
// Kernel services — individually visible, all run on the kernel process (port 3000/7000)
|
|
40
|
+
{ name: "kernel", label: "Home", icon: "🏠", description: "Network home — launcher, articles, stats", devPort: 3000, prodPort: 7000, schema: null, tier: "core", visibility: "public", category: "kernel" },
|
|
41
|
+
{ name: "auth", label: "Identity", icon: "🔑", description: "Authentication, keys, and identity management", devPort: 3000, prodPort: 7000, schema: "auth", tier: "core", visibility: "authenticated", category: "kernel" },
|
|
42
|
+
{ name: "profile", label: "Profile", icon: "👤", description: "Your profile, settings, and display preferences", devPort: 3000, prodPort: 7000, schema: "profile", tier: "core", visibility: "authenticated", category: "kernel" },
|
|
43
|
+
{ name: "connections", label: "Connections", icon: "🤝", description: "Your network — people you know and trust", devPort: 3000, prodPort: 7000, schema: "connections", tier: "core", visibility: "authenticated", category: "kernel" },
|
|
44
|
+
{ name: "chat", label: "Chat", icon: "💬", description: "Conversations and group messaging", devPort: 3000, prodPort: 7000, schema: "chat", tier: "core", visibility: "authenticated", category: "kernel" },
|
|
45
|
+
{ name: "pay", label: "Wallet", icon: "💰", description: "Payments, settlements, and MJN balance", devPort: 3000, prodPort: 7000, schema: "pay", tier: "core", visibility: "authenticated", category: "kernel" },
|
|
46
|
+
{ name: "media", label: "Media", icon: "📁", description: "Files, images, and .fair attribution", devPort: 3000, prodPort: 7000, schema: "media", tier: "core", visibility: "authenticated", category: "kernel" },
|
|
47
|
+
{ name: "registry", label: "Registry", icon: "📡", description: "Service registry and DFOS relay", devPort: 3000, prodPort: 7000, schema: "registry", tier: "core", visibility: "public", category: "kernel" },
|
|
48
|
+
{ name: "notify", label: "Notify", icon: "🔔", description: "Notifications and preferences", devPort: 3000, prodPort: 7000, schema: "notify", tier: "core", visibility: "internal", category: "kernel" },
|
|
49
|
+
|
|
50
|
+
// Core apps — separate processes
|
|
51
|
+
{ name: "events", label: "Events", icon: "🎫", description: "Event creation, ticketing, and management", devPort: 3006, prodPort: 7006, schema: "events", tier: "core", visibility: "public", category: "core" },
|
|
52
|
+
|
|
53
|
+
// Imajin apps
|
|
54
|
+
{ name: "coffee", label: "Coffee", icon: "☕", description: "Tipping and creator support pages", devPort: 3100, prodPort: 7100, schema: "coffee", tier: "imajin", visibility: "creator", category: "creator" },
|
|
55
|
+
{ name: "dykil", label: "Surveys", icon: "📋", description: "Surveys and do-you-know-if-I-like polls", devPort: 3101, prodPort: 7101, schema: "dykil", tier: "imajin", visibility: "creator", category: "creator" },
|
|
56
|
+
{ name: "links", label: "Links", icon: "🔗", description: "Link-in-bio pages and click tracking", devPort: 3102, prodPort: 7102, schema: "links", tier: "imajin", visibility: "creator", category: "creator" },
|
|
57
|
+
{ name: "learn", label: "Learn", icon: "📚", description: "Courses, lessons, and learning progress", devPort: 3103, prodPort: 7103, schema: "learn", tier: "imajin", visibility: "public", category: "core" },
|
|
58
|
+
{ name: "market", label: "Market", icon: "🏪", description: "Local commerce — buy and sell with trust", devPort: 3104, prodPort: 7104, schema: "market", tier: "imajin", visibility: "public", category: "core" },
|
|
59
|
+
|
|
60
|
+
// Meta — project info and external resources surfaced in the launcher
|
|
61
|
+
{ name: 'project', label: 'Project', icon: '📖', description: 'Thesis, whitepaper, essays, RFCs', devPort: 3000, prodPort: 7000, schema: "public", tier: 'core', visibility: 'public', category: 'meta', wwwPath: '/project' },
|
|
62
|
+
{ name: 'github', label: 'GitHub', icon: '🐙', description: 'Source code', devPort: 3000, prodPort: 7000, schema: "public", tier: 'core', visibility: 'public', category: 'meta', externalUrl: 'https://github.com/ima-jin/imajin-ai' },
|
|
63
|
+
{ name: 'docs', label: 'Docs', icon: '📄', description: 'API documentation', devPort: 3000, prodPort: 7000, schema: "public", tier: 'core', visibility: 'public', category: 'meta', wwwPath: '/developer-guide' },
|
|
64
|
+
|
|
65
|
+
// Connected apps (separate repos) will use the plugin architecture (#249).
|
|
66
|
+
// Not included here — they authenticate via delegated sessions, not the monorepo manifest.
|
|
67
|
+
] as const satisfies readonly ServiceDefinition[];
|
|
68
|
+
|
|
69
|
+
// ── Lookup helpers ──────────────────────────────────────────────────────────
|
|
70
|
+
|
|
71
|
+
/** Get a service definition by name */
|
|
72
|
+
export function getService(name: string): ServiceDefinition | undefined {
|
|
73
|
+
return SERVICES.find((s) => s.name === name);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Get the port for a service in a given environment */
|
|
77
|
+
export function getPort(name: string, env: "dev" | "prod" = "dev"): number | undefined {
|
|
78
|
+
const svc = getService(name);
|
|
79
|
+
return svc ? (env === "prod" ? svc.prodPort : svc.devPort) : undefined;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Get internal URL for a service (server-side calls) */
|
|
83
|
+
export function getServiceUrl(name: string, env: "dev" | "prod" = "dev"): string | undefined {
|
|
84
|
+
const port = getPort(name, env);
|
|
85
|
+
return port ? `http://localhost:${port}` : undefined;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Get the public URL for a service.
|
|
89
|
+
* Localhost-aware: if domain contains "localhost" or prefix is "http://localhost:",
|
|
90
|
+
* returns http://localhost:{devPort} using the canonical port map.
|
|
91
|
+
*/
|
|
92
|
+
export function getPublicUrl(
|
|
93
|
+
name: string,
|
|
94
|
+
options?: { prefix?: string; domain?: string }
|
|
95
|
+
): string {
|
|
96
|
+
const svc = SERVICES.find((s) => s.name === name);
|
|
97
|
+
|
|
98
|
+
// External URL takes absolute priority (e.g. GitHub)
|
|
99
|
+
if (svc?.externalUrl) return svc.externalUrl;
|
|
100
|
+
|
|
101
|
+
const domain = options?.domain || "imajin.ai";
|
|
102
|
+
const prefix = options?.prefix;
|
|
103
|
+
|
|
104
|
+
// wwwPath: resolve against www's URL (env-aware)
|
|
105
|
+
if (svc?.wwwPath) {
|
|
106
|
+
const wwwUrl = getPublicUrl("www", options);
|
|
107
|
+
return `${wwwUrl}${svc.wwwPath}`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Detect localhost dev environment
|
|
111
|
+
if (domain.includes("localhost") || prefix?.includes("localhost")) {
|
|
112
|
+
const port = getPort(name, "dev");
|
|
113
|
+
return port ? `http://localhost:${port}` : `http://localhost:3000`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Single-domain mode: prefix contains dots (e.g. "dev-jin.imajin.ai/")
|
|
117
|
+
// Build https://{prefix}{name} instead of https://{prefix}-{name}.{domain}
|
|
118
|
+
if (prefix && prefix.includes(".")) {
|
|
119
|
+
const base = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
|
|
120
|
+
// "www" or "kernel" = root of the node, no suffix
|
|
121
|
+
if (name === "www" || name === "kernel") return `https://${base}`;
|
|
122
|
+
return `https://${base}/${name}`;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const subdomain = prefix ? `${prefix}-${name}` : name;
|
|
126
|
+
return `https://${subdomain}.${domain}`;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Build the public URL for a service using raw env vars.
|
|
131
|
+
* Convenience wrapper around getPublicUrl that handles NEXT_PUBLIC_SERVICE_PREFIX
|
|
132
|
+
* and NEXT_PUBLIC_DOMAIN directly (no need to strip protocol).
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* buildPublicUrl('www') → uses env vars, localhost-aware
|
|
136
|
+
* buildPublicUrl('auth', prefix, domain) → explicit values
|
|
137
|
+
*/
|
|
138
|
+
export function buildPublicUrl(
|
|
139
|
+
name: string,
|
|
140
|
+
servicePrefix?: string,
|
|
141
|
+
domain?: string
|
|
142
|
+
): string {
|
|
143
|
+
// Check for explicit NEXT_PUBLIC_{NAME}_URL env var first (kernel single-domain mode)
|
|
144
|
+
if (!servicePrefix && !domain && typeof process !== "undefined") {
|
|
145
|
+
const envKey = `NEXT_PUBLIC_${name.toUpperCase()}_URL`;
|
|
146
|
+
const explicit = process.env[envKey];
|
|
147
|
+
if (explicit) return explicit;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const p = servicePrefix ?? (typeof process !== "undefined" ? process.env.NEXT_PUBLIC_SERVICE_PREFIX : undefined) ?? "https://";
|
|
151
|
+
const d = domain ?? (typeof process !== "undefined" ? process.env.NEXT_PUBLIC_DOMAIN : undefined) ?? "imajin.ai";
|
|
152
|
+
|
|
153
|
+
// Localhost detection
|
|
154
|
+
if (p.includes("localhost") || d.includes("localhost")) {
|
|
155
|
+
const port = getPort(name, "dev");
|
|
156
|
+
return port ? `http://localhost:${port}` : `http://localhost:3000`;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// All services live at /{name} on the same domain (single-node architecture).
|
|
160
|
+
// Subdomain construction is legacy — only used if explicit prefix/domain args
|
|
161
|
+
// are passed (e.g. generating external links for a different node).
|
|
162
|
+
if (!servicePrefix && !domain) {
|
|
163
|
+
return name === "kernel" ? "" : `/${name}`;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Explicit prefix/domain passed — caller wants a full URL (e.g. cross-node links)
|
|
167
|
+
const match = p.replace(/^https?:\/\//, "").replace(/-$/, "") || undefined;
|
|
168
|
+
return getPublicUrl(name, { prefix: match, domain: d });
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** All service names */
|
|
172
|
+
export const SERVICE_NAMES = SERVICES.map((s) => s.name);
|
|
173
|
+
|
|
174
|
+
/** Services filtered by tier */
|
|
175
|
+
export function servicesByTier(tier: ServiceTier): ServiceDefinition[] {
|
|
176
|
+
return SERVICES.filter((s) => s.tier === tier);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** Services filtered by visibility */
|
|
180
|
+
export function servicesByVisibility(visibility: ServiceVisibility): ServiceDefinition[] {
|
|
181
|
+
return SERVICES.filter((s) => s.visibility === visibility);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Build a map of service name → internal URL from env vars with fallback to port convention.
|
|
186
|
+
* Env var pattern: `{NAME}_SERVICE_URL` (e.g. `AUTH_SERVICE_URL`)
|
|
187
|
+
*/
|
|
188
|
+
export function buildServiceUrlMap(env: Record<string, string | undefined>, mode: "dev" | "prod" = "dev"): Record<string, string> {
|
|
189
|
+
const map: Record<string, string> = {};
|
|
190
|
+
for (const svc of SERVICES) {
|
|
191
|
+
const envKey = `${svc.name.toUpperCase()}_SERVICE_URL`;
|
|
192
|
+
const port = mode === "prod" ? svc.prodPort : svc.devPort;
|
|
193
|
+
map[svc.name] = env[envKey] || `http://localhost:${port}`;
|
|
194
|
+
}
|
|
195
|
+
return map;
|
|
196
|
+
}
|
package/src/session.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session cookie configuration — single source of truth.
|
|
3
|
+
*
|
|
4
|
+
* Every service that reads or writes the session cookie should import from here
|
|
5
|
+
* instead of hardcoding 'imajin_session'.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** The session cookie name. Use a different name per environment to prevent collisions. */
|
|
9
|
+
export function getSessionCookieName(env?: "dev" | "prod"): string {
|
|
10
|
+
// Auto-detect from common env vars if not specified
|
|
11
|
+
const resolved =
|
|
12
|
+
env ??
|
|
13
|
+
(typeof process !== "undefined" && (process.env.IMAJIN_ENV === "dev" || process.env.NODE_ENV === "development")
|
|
14
|
+
? "dev"
|
|
15
|
+
: "prod");
|
|
16
|
+
|
|
17
|
+
return resolved === "dev" ? "imajin_session_dev" : "imajin_session";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Default cookie name — resolves at import time based on environment */
|
|
21
|
+
export const SESSION_COOKIE_NAME = getSessionCookieName();
|
|
22
|
+
|
|
23
|
+
/** Detect if running on localhost (not deployed to *.imajin.ai) */
|
|
24
|
+
function isLocalhost(): boolean {
|
|
25
|
+
if (typeof process === "undefined") return false;
|
|
26
|
+
const prefix = process.env.NEXT_PUBLIC_SERVICE_PREFIX ?? "";
|
|
27
|
+
return prefix.includes("localhost");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Cookie options for cross-subdomain sessions.
|
|
31
|
+
* When called with no argument, auto-detects from IMAJIN_ENV (same logic as getSessionCookieName).
|
|
32
|
+
* Accepts optional "dev" | "prod" override for explicit control.
|
|
33
|
+
*
|
|
34
|
+
* Localhost-aware: when SERVICE_PREFIX contains "localhost", uses
|
|
35
|
+
* settings compatible with HTTP on localhost (no domain, not secure, lax sameSite).
|
|
36
|
+
*/
|
|
37
|
+
export function getSessionCookieOptions(env?: "dev" | "prod") {
|
|
38
|
+
const local = isLocalhost();
|
|
39
|
+
return {
|
|
40
|
+
name: getSessionCookieName(env),
|
|
41
|
+
options: {
|
|
42
|
+
httpOnly: true,
|
|
43
|
+
secure: !local,
|
|
44
|
+
sameSite: local ? ("lax" as const) : ("none" as const),
|
|
45
|
+
path: "/",
|
|
46
|
+
...(local ? {} : { domain: ".imajin.ai" }),
|
|
47
|
+
maxAge: 60 * 60 * 24, // 24 hours
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|