@ysk-kit/cli 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.
Files changed (125) hide show
  1. package/LICENSE +21 -0
  2. package/dist/add-capability.d.ts +4 -0
  3. package/dist/add-capability.d.ts.map +1 -0
  4. package/dist/add-capability.js +104 -0
  5. package/dist/add-capability.js.map +1 -0
  6. package/dist/add-module.d.ts +5 -0
  7. package/dist/add-module.d.ts.map +1 -0
  8. package/dist/add-module.js +210 -0
  9. package/dist/add-module.js.map +1 -0
  10. package/dist/agent-stubs.d.ts +3 -0
  11. package/dist/agent-stubs.d.ts.map +1 -0
  12. package/dist/agent-stubs.js +25 -0
  13. package/dist/agent-stubs.js.map +1 -0
  14. package/dist/apply-capability.d.ts +3 -0
  15. package/dist/apply-capability.d.ts.map +1 -0
  16. package/dist/apply-capability.js +189 -0
  17. package/dist/apply-capability.js.map +1 -0
  18. package/dist/capability-catalog.d.ts +25 -0
  19. package/dist/capability-catalog.d.ts.map +1 -0
  20. package/dist/capability-catalog.js +123 -0
  21. package/dist/capability-catalog.js.map +1 -0
  22. package/dist/capability-patches.d.ts +40 -0
  23. package/dist/capability-patches.d.ts.map +1 -0
  24. package/dist/capability-patches.js +616 -0
  25. package/dist/capability-patches.js.map +1 -0
  26. package/dist/check-agent.d.ts +9 -0
  27. package/dist/check-agent.d.ts.map +1 -0
  28. package/dist/check-agent.js +85 -0
  29. package/dist/check-agent.js.map +1 -0
  30. package/dist/generate-openapi.d.ts +2 -0
  31. package/dist/generate-openapi.d.ts.map +1 -0
  32. package/dist/generate-openapi.js +52 -0
  33. package/dist/generate-openapi.js.map +1 -0
  34. package/dist/help.d.ts +2 -0
  35. package/dist/help.d.ts.map +1 -0
  36. package/dist/help.js +54 -0
  37. package/dist/help.js.map +1 -0
  38. package/dist/index.d.ts +2 -0
  39. package/dist/index.d.ts.map +1 -0
  40. package/dist/index.js +59 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/merge-prisma.d.ts +15 -0
  43. package/dist/merge-prisma.d.ts.map +1 -0
  44. package/dist/merge-prisma.js +90 -0
  45. package/dist/merge-prisma.js.map +1 -0
  46. package/dist/patch-text.d.ts +17 -0
  47. package/dist/patch-text.d.ts.map +1 -0
  48. package/dist/patch-text.js +72 -0
  49. package/dist/patch-text.js.map +1 -0
  50. package/dist/upgrade.d.ts +16 -0
  51. package/dist/upgrade.d.ts.map +1 -0
  52. package/dist/upgrade.js +110 -0
  53. package/dist/upgrade.js.map +1 -0
  54. package/package.json +41 -0
  55. package/templates/agent/skills/add-capability/SKILL.md +8 -0
  56. package/templates/agent/skills/add-module/SKILL.md +8 -0
  57. package/templates/agent/skills/envelope-api/SKILL.md +8 -0
  58. package/templates/agent/skills/fix-layers/SKILL.md +8 -0
  59. package/templates/agent/skills/new-product/SKILL.md +8 -0
  60. package/templates/agent/skills/verify-change/SKILL.md +8 -0
  61. package/templates/agent/ysk-kit.mdc +6 -0
  62. package/templates/capabilities/billing/apps/api/src/modules/billing/application/billing-service.ts +140 -0
  63. package/templates/capabilities/billing/apps/api/src/modules/billing/domain/billing-port.ts +23 -0
  64. package/templates/capabilities/billing/apps/api/src/modules/billing/domain/subscription-repository.ts +22 -0
  65. package/templates/capabilities/billing/apps/api/src/modules/billing/infra/billing-router.ts +85 -0
  66. package/templates/capabilities/billing/apps/api/src/modules/billing/infra/billing.app.test.ts +273 -0
  67. package/templates/capabilities/billing/apps/api/src/modules/billing/infra/log-billing.ts +31 -0
  68. package/templates/capabilities/billing/apps/api/src/modules/billing/infra/memory-subscription-repository.ts +27 -0
  69. package/templates/capabilities/billing/apps/api/src/modules/billing/infra/prisma-subscription-repository.ts +52 -0
  70. package/templates/capabilities/billing/apps/api/src/modules/billing/infra/stripe-billing.test.ts +127 -0
  71. package/templates/capabilities/billing/apps/api/src/modules/billing/infra/stripe-billing.ts +160 -0
  72. package/templates/capabilities/billing/apps/web/src/features/orgs/billing-page.tsx +183 -0
  73. package/templates/capabilities/billing/packages/contracts/src/api/billing.ts +85 -0
  74. package/templates/capabilities/billing/packages/contracts/src/dto/billing.ts +59 -0
  75. package/templates/capabilities/billing/packages/sdk/src/resources/billing.ts +38 -0
  76. package/templates/capabilities/billing/packages/web-sdk/src/billing-hooks.ts +56 -0
  77. package/templates/capabilities/llm/apps/api/src/modules/llm/application/llm-service.ts +48 -0
  78. package/templates/capabilities/llm/apps/api/src/modules/llm/domain/usage-repository.ts +10 -0
  79. package/templates/capabilities/llm/apps/api/src/modules/llm/infra/llm-router.ts +88 -0
  80. package/templates/capabilities/llm/apps/api/src/modules/llm/infra/llm.app.test.ts +89 -0
  81. package/templates/capabilities/llm/apps/api/src/modules/llm/infra/memory-usage-repository.ts +13 -0
  82. package/templates/capabilities/llm/apps/api/src/modules/llm/infra/prisma-usage-repository.ts +17 -0
  83. package/templates/capabilities/llm/apps/web/src/features/llm/llm-page.tsx +53 -0
  84. package/templates/capabilities/llm/packages/contracts/src/api/llm.ts +26 -0
  85. package/templates/capabilities/llm/packages/contracts/src/dto/llm.ts +33 -0
  86. package/templates/capabilities/llm/packages/sdk/src/resources/llm.ts +68 -0
  87. package/templates/capabilities/llm/packages/web-sdk/src/llm-hooks.ts +12 -0
  88. package/templates/capabilities/push/apps/api/src/modules/devices/application/device-service.ts +42 -0
  89. package/templates/capabilities/push/apps/api/src/modules/devices/domain/device-repository.ts +22 -0
  90. package/templates/capabilities/push/apps/api/src/modules/devices/infra/device-router.ts +42 -0
  91. package/templates/capabilities/push/apps/api/src/modules/devices/infra/device.app.test.ts +34 -0
  92. package/templates/capabilities/push/apps/api/src/modules/devices/infra/memory-device-repository.ts +33 -0
  93. package/templates/capabilities/push/apps/api/src/modules/devices/infra/prisma-device-repository.ts +42 -0
  94. package/templates/capabilities/push/apps/mobile/src/adapters/push.ts +21 -0
  95. package/templates/capabilities/push/packages/contracts/src/api/devices.ts +33 -0
  96. package/templates/capabilities/push/packages/contracts/src/dto/device.ts +18 -0
  97. package/templates/capabilities/push/packages/sdk/src/resources/devices.ts +10 -0
  98. package/templates/capabilities/team/apps/api/src/modules/organizations/application/organization-service.ts +272 -0
  99. package/templates/capabilities/team/apps/api/src/modules/organizations/domain/organization-repository.ts +58 -0
  100. package/templates/capabilities/team/apps/api/src/modules/organizations/infra/memory-organization-repository.ts +141 -0
  101. package/templates/capabilities/team/apps/api/src/modules/organizations/infra/organization-router.ts +130 -0
  102. package/templates/capabilities/team/apps/api/src/modules/organizations/infra/organization.app.test.ts +213 -0
  103. package/templates/capabilities/team/apps/api/src/modules/organizations/infra/prisma-organization-repository.ts +142 -0
  104. package/templates/capabilities/team/apps/mobile/src/screens/invite-screen.tsx +40 -0
  105. package/templates/capabilities/team/apps/mobile/src/screens/org-detail-screen.tsx +154 -0
  106. package/templates/capabilities/team/apps/mobile/src/screens/orgs-screen.tsx +53 -0
  107. package/templates/capabilities/team/apps/web/src/features/orgs/invite-page.tsx +77 -0
  108. package/templates/capabilities/team/apps/web/src/features/orgs/org-detail-page.tsx +138 -0
  109. package/templates/capabilities/team/apps/web/src/features/orgs/orgs-page.tsx +73 -0
  110. package/templates/capabilities/team/packages/contracts/src/api/organizations.ts +154 -0
  111. package/templates/capabilities/team/packages/contracts/src/dto/organization.ts +58 -0
  112. package/templates/capabilities/team/packages/sdk/src/resources/organizations.ts +53 -0
  113. package/templates/capabilities/team/packages/web-sdk/src/organizations-hooks.ts +80 -0
  114. package/templates/module/api.ts.tmpl +32 -0
  115. package/templates/module/application-service.ts.tmpl +9 -0
  116. package/templates/module/domain-repository.ts.tmpl +15 -0
  117. package/templates/module/dto.ts.tmpl +21 -0
  118. package/templates/module/memory-repository.ts.tmpl +36 -0
  119. package/templates/module/model.prisma.tmpl +8 -0
  120. package/templates/module/prisma-repository.ts.tmpl +38 -0
  121. package/templates/module/router.ts.tmpl +32 -0
  122. package/templates/module/sdk-resource.ts.tmpl +17 -0
  123. package/templates/module/service.test.ts.tmpl +33 -0
  124. package/templates/module/web-hooks.ts.tmpl +24 -0
  125. package/templates/module/web-page.tsx.tmpl +69 -0
@@ -0,0 +1,110 @@
1
+ import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync, } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { writeAgentStubs } from './agent-stubs';
4
+ export const UPGRADE_PATHS = [
5
+ 'AGENTS.md',
6
+ 'AGENTS.zh.md',
7
+ 'CLAUDE.md',
8
+ '.dependency-cruiser.cjs',
9
+ 'packages/typescript-config',
10
+ 'packages/biome-config',
11
+ 'docs/skills',
12
+ ];
13
+ const SKIP_ENTRIES = new Set(['node_modules', 'dist']);
14
+ export const readKitVersion = (kitRoot) => {
15
+ const pkg = JSON.parse(readFileSync(join(kitRoot, 'package.json'), 'utf8'));
16
+ if (!pkg.version)
17
+ throw new Error('kit package.json has no version');
18
+ return pkg.version;
19
+ };
20
+ const isProductRoot = (dir) => existsSync(join(dir, '.ysk-kit.json')) ||
21
+ existsSync(join(dir, 'pnpm-workspace.yaml')) ||
22
+ existsSync(join(dir, 'AGENTS.md'));
23
+ const readMarker = (productRoot) => {
24
+ const path = join(productRoot, '.ysk-kit.json');
25
+ if (!existsSync(path))
26
+ return undefined;
27
+ let parsed;
28
+ try {
29
+ parsed = JSON.parse(readFileSync(path, 'utf8'));
30
+ }
31
+ catch {
32
+ throw new Error('.ysk-kit.json is not valid JSON');
33
+ }
34
+ if (!parsed || typeof parsed !== 'object') {
35
+ throw new Error('.ysk-kit.json is not valid JSON');
36
+ }
37
+ const rec = parsed;
38
+ return {
39
+ kit: typeof rec.kit === 'string' ? rec.kit : 'ysk-kit',
40
+ version: typeof rec.version === 'string' ? rec.version : 'unknown',
41
+ flavor: typeof rec.flavor === 'string' ? rec.flavor : 'unknown',
42
+ preset: typeof rec.preset === 'string' ? rec.preset : 'unknown',
43
+ db: typeof rec.db === 'string' ? rec.db : 'unknown',
44
+ };
45
+ };
46
+ const copyEntry = (from, to) => {
47
+ const stat = statSync(from);
48
+ if (stat.isDirectory()) {
49
+ mkdirSync(to, { recursive: true });
50
+ for (const name of readdirSync(from)) {
51
+ if (SKIP_ENTRIES.has(name))
52
+ continue;
53
+ copyEntry(join(from, name), join(to, name));
54
+ }
55
+ return;
56
+ }
57
+ mkdirSync(dirname(to), { recursive: true });
58
+ cpSync(from, to);
59
+ };
60
+ export const upgrade = (opts) => {
61
+ if (!isProductRoot(opts.productRoot)) {
62
+ throw new Error('run ysk upgrade from a product root, or set YSK_ROOT');
63
+ }
64
+ const logs = [];
65
+ const previous = readMarker(opts.productRoot);
66
+ const workspaceProduct = existsSync(join(opts.productRoot, 'pnpm-workspace.yaml'));
67
+ for (const rel of UPGRADE_PATHS) {
68
+ const from = join(opts.kitRoot, rel);
69
+ const to = join(opts.productRoot, rel);
70
+ if (!existsSync(from)) {
71
+ logs.push(`skip ${rel} (missing in kit)`);
72
+ continue;
73
+ }
74
+ if (!existsSync(to) && !workspaceProduct) {
75
+ logs.push(`skip ${rel}`);
76
+ continue;
77
+ }
78
+ if (opts.dryRun) {
79
+ logs.push(`will copy ${rel}`);
80
+ continue;
81
+ }
82
+ copyEntry(from, to);
83
+ logs.push(`copied ${rel}`);
84
+ }
85
+ if (workspaceProduct) {
86
+ if (opts.dryRun) {
87
+ logs.push('will write agent stubs');
88
+ }
89
+ else {
90
+ writeAgentStubs(opts.productRoot, opts.kitRoot);
91
+ logs.push('wrote agent stubs');
92
+ }
93
+ }
94
+ const kitVersion = readKitVersion(opts.kitRoot);
95
+ const marker = {
96
+ kit: previous?.kit ?? 'ysk-kit',
97
+ version: kitVersion,
98
+ flavor: previous?.flavor ?? 'unknown',
99
+ preset: previous?.preset ?? 'unknown',
100
+ db: previous?.db ?? 'unknown',
101
+ };
102
+ if (opts.dryRun) {
103
+ logs.push('will write .ysk-kit.json');
104
+ return logs;
105
+ }
106
+ writeFileSync(join(opts.productRoot, '.ysk-kit.json'), `${JSON.stringify(marker, null, 2)}\n`);
107
+ logs.push('wrote .ysk-kit.json');
108
+ return logs;
109
+ };
110
+ //# sourceMappingURL=upgrade.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upgrade.js","sourceRoot":"","sources":["../src/upgrade.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@ysk-kit/cli",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "bin": {
6
+ "ysk": "./dist/index.js"
7
+ },
8
+ "dependencies": {
9
+ "@ysk-kit/api-http": "1.0.0",
10
+ "@ysk-kit/contracts": "1.0.0",
11
+ "zod": "^4.6.5"
12
+ },
13
+ "devDependencies": {
14
+ "@types/node": "^24.5.2",
15
+ "tsx": "^4.19.3",
16
+ "typescript": "^6.0.3",
17
+ "vitest": "^5.0.2"
18
+ },
19
+ "files": [
20
+ "dist",
21
+ "templates"
22
+ ],
23
+ "publishConfig": {
24
+ "registry": "https://registry.npmjs.org",
25
+ "access": "public"
26
+ },
27
+ "scripts": {
28
+ "start": "tsx src/index.ts",
29
+ "typecheck": "tsc --noEmit -p tsconfig.json",
30
+ "test": "vitest run --passWithNoTests",
31
+ "build": "tsc -p tsconfig.build.json"
32
+ },
33
+ "main": "./dist/index.js",
34
+ "types": "./dist/index.d.ts",
35
+ "exports": {
36
+ ".": {
37
+ "types": "./dist/index.d.ts",
38
+ "import": "./dist/index.js"
39
+ }
40
+ }
41
+ }
@@ -0,0 +1,8 @@
1
+ ---
2
+ name: add-capability
3
+ description: >
4
+ Merge a catalogued YSK Kit capability (llm, team, billing, push, jobs, mail, …) with ysk add.
5
+ Use when the user wants ysk add llm/team/billing/push, restore a thin capability, or /add-capability.
6
+ ---
7
+
8
+ Read `docs/skills/add-capability.md`. Law: `AGENTS.md`.
@@ -0,0 +1,8 @@
1
+ ---
2
+ name: add-module
3
+ description: >
4
+ Add a hexagonal HTTP module with ysk add module (contract, DTO, Express+Fastify, SDK, web page).
5
+ Use when the user wants a new resource, new API route, ysk add module, or /add-module.
6
+ ---
7
+
8
+ Read `docs/skills/add-module.md`. Law: `AGENTS.md`.
@@ -0,0 +1,8 @@
1
+ ---
2
+ name: envelope-api
3
+ description: >
4
+ Keep YSK Kit JSON routes in {ok,data}/{ok,error}; handle SSE, invoice PDF 302, and OpenAPI exceptions.
5
+ Use when adding a route, error shape, SSE, PDF, envelope, or /envelope-api.
6
+ ---
7
+
8
+ Read `docs/skills/envelope-api.md`. Law: `AGENTS.md`.
@@ -0,0 +1,8 @@
1
+ ---
2
+ name: fix-layers
3
+ description: >
4
+ Repair a failed pnpm layers / dependency-cruiser run (client imported Prisma, domain imported infra).
5
+ Use when layers is red, a client imports Express/Prisma, or /fix-layers.
6
+ ---
7
+
8
+ Read `docs/skills/fix-layers.md`. Law: `AGENTS.md`.
@@ -0,0 +1,8 @@
1
+ ---
2
+ name: new-product
3
+ description: >
4
+ Scaffold a new YSK Kit product with create-ysk-app (thin/full preset, flavors, database).
5
+ Use when the user wants a new app, new product, create-ysk-app, scaffold, or /new-product.
6
+ ---
7
+
8
+ Read `docs/skills/new-product.md`. Law: `AGENTS.md`.
@@ -0,0 +1,8 @@
1
+ ---
2
+ name: verify-change
3
+ description: >
4
+ Verify a YSK Kit change with pnpm layers, typecheck, test, gen:openapi, and ysk check agent.
5
+ Use when a feature is done, before finishing, after a module, or /verify-change.
6
+ ---
7
+
8
+ Read `docs/skills/verify-change.md`. Law: `AGENTS.md`.
@@ -0,0 +1,6 @@
1
+ ---
2
+ description: YSK Kit agent law — contracts, envelope, layers
3
+ alwaysApply: true
4
+ ---
5
+
6
+ Follow `AGENTS.md` at the repo root before writing code (Chinese: `AGENTS.zh.md`). Procedures live in `docs/skills/`. New HTTP features start with `pnpm ysk add module <kebab-name> --prisma --web`. Do not invent TypeScript enums, import Prisma from clients, or return JSON outside `{ ok, data }` / `{ ok, error }`. Run `pnpm layers && pnpm typecheck && pnpm test && pnpm gen:openapi` before finishing.
@@ -0,0 +1,140 @@
1
+ import type { CheckoutCommand, SubscriptionDto } from '@ysk-kit/contracts';
2
+ import { BILLING_PLANS, orgRoleCan } from '@ysk-kit/contracts';
3
+ import { AppError } from '@ysk-kit/domain-kernel';
4
+ import type { IOrganizationRepository } from '../../organizations/domain/organization-repository';
5
+ import type { IBillingPort } from '../domain/billing-port';
6
+ import type { ISubscriptionRepository } from '../domain/subscription-repository';
7
+
8
+ const PERIOD_MS = 1000 * 60 * 60 * 24 * 30;
9
+
10
+ const freeSub = (): SubscriptionDto => ({
11
+ planCode: 'free',
12
+ status: 'active',
13
+ currentPeriodEnd: null,
14
+ seatCount: 1,
15
+ });
16
+
17
+ const toDto = (row: {
18
+ planCode: SubscriptionDto['planCode'];
19
+ status: SubscriptionDto['status'];
20
+ currentPeriodEnd: Date | null;
21
+ seatCount: number;
22
+ }): SubscriptionDto => ({
23
+ planCode: row.planCode,
24
+ status: row.status,
25
+ currentPeriodEnd: row.currentPeriodEnd ? row.currentPeriodEnd.toISOString() : null,
26
+ seatCount: row.seatCount,
27
+ });
28
+
29
+ export const createBillingService = (opts: {
30
+ subscriptions: ISubscriptionRepository;
31
+ billing: IBillingPort;
32
+ orgs: IOrganizationRepository;
33
+ now?: () => Date;
34
+ }) => {
35
+ const now = () => opts.now?.() ?? new Date();
36
+
37
+ const requireBiller = async (userId: string, organizationId: string) => {
38
+ const org = await opts.orgs.findById(organizationId);
39
+ if (!org) throw new AppError('NOT_FOUND');
40
+ const membership = await opts.orgs.findMembership(organizationId, userId);
41
+ if (!membership || !orgRoleCan(membership.role, 'org.billing')) {
42
+ throw new AppError('FORBIDDEN');
43
+ }
44
+ return org;
45
+ };
46
+
47
+ return {
48
+ plans: () => BILLING_PLANS,
49
+ subscription: async (userId: string, organizationId: string): Promise<SubscriptionDto> => {
50
+ await requireBiller(userId, organizationId);
51
+ const row = await opts.subscriptions.findByOrganizationId(organizationId);
52
+ return row ? toDto(row) : freeSub();
53
+ },
54
+ invoices: async (userId: string, organizationId: string) => {
55
+ const org = await requireBiller(userId, organizationId);
56
+ if (!org.stripeCustomerId) return [];
57
+ return opts.billing.listInvoices({ customerId: org.stripeCustomerId });
58
+ },
59
+ invoicePdf: async (userId: string, organizationId: string, invoiceId: string) => {
60
+ const org = await requireBiller(userId, organizationId);
61
+ if (!org.stripeCustomerId) return null;
62
+ return opts.billing.getInvoicePdf({
63
+ customerId: org.stripeCustomerId,
64
+ invoiceId,
65
+ });
66
+ },
67
+ activate: async (
68
+ organizationId: string,
69
+ planCode: SubscriptionDto['planCode'],
70
+ customerId?: string,
71
+ seatCount = 1,
72
+ ) => {
73
+ if (planCode === 'free') return;
74
+ const org = await opts.orgs.findById(organizationId);
75
+ if (!org) throw new AppError('NOT_FOUND');
76
+ await opts.subscriptions.upsert({
77
+ organizationId,
78
+ planCode,
79
+ status: 'active',
80
+ currentPeriodEnd: new Date(now().getTime() + PERIOD_MS),
81
+ seatCount,
82
+ });
83
+ if (customerId) await opts.orgs.setStripeCustomerId(organizationId, customerId);
84
+ },
85
+ portal: async (userId: string, organizationId: string, returnUrl: string) => {
86
+ const org = await requireBiller(userId, organizationId);
87
+ const customerId = org.stripeCustomerId;
88
+ if (opts.billing.requiresCustomer && !customerId) {
89
+ throw new AppError('CONFLICT', 'Complete checkout first');
90
+ }
91
+ return opts.billing.portal({
92
+ returnUrl,
93
+ customerId: customerId ?? 'cus_stub',
94
+ });
95
+ },
96
+ checkout: async (userId: string, body: CheckoutCommand) => {
97
+ if (body.planCode === 'free') throw new AppError('VALIDATION_FAILED', 'Cannot checkout free');
98
+ await requireBiller(userId, body.organizationId);
99
+ const members = await opts.orgs.listMembers(body.organizationId);
100
+ if (body.seatCount < members.length) {
101
+ throw new AppError('VALIDATION_FAILED', 'seatCount below member count');
102
+ }
103
+ const org = await opts.orgs.findById(body.organizationId);
104
+ const result = await opts.billing.checkout({
105
+ organizationId: body.organizationId,
106
+ planCode: body.planCode,
107
+ successUrl: body.successUrl,
108
+ cancelUrl: body.cancelUrl,
109
+ seatCount: body.seatCount,
110
+ ...(org?.stripeCustomerId ? { customerId: org.stripeCustomerId } : {}),
111
+ });
112
+ if (!result.pending) {
113
+ await opts.subscriptions.upsert({
114
+ organizationId: body.organizationId,
115
+ planCode: body.planCode,
116
+ status: 'active',
117
+ currentPeriodEnd: new Date(now().getTime() + PERIOD_MS),
118
+ seatCount: body.seatCount,
119
+ });
120
+ }
121
+ return { url: result.url };
122
+ },
123
+ cancel: async (userId: string, organizationId: string) => {
124
+ await requireBiller(userId, organizationId);
125
+ const row = await opts.subscriptions.findByOrganizationId(organizationId);
126
+ if (row) {
127
+ await opts.subscriptions.upsert({
128
+ organizationId,
129
+ planCode: row.planCode,
130
+ status: 'canceled',
131
+ currentPeriodEnd: row.currentPeriodEnd,
132
+ seatCount: row.seatCount,
133
+ });
134
+ }
135
+ return { canceled: true as const };
136
+ },
137
+ };
138
+ };
139
+
140
+ export type BillingService = ReturnType<typeof createBillingService>;
@@ -0,0 +1,23 @@
1
+ export type CheckoutInput = {
2
+ organizationId: string;
3
+ planCode: string;
4
+ successUrl: string;
5
+ cancelUrl: string;
6
+ seatCount: number;
7
+ customerId?: string;
8
+ };
9
+
10
+ export type CheckoutResult = {
11
+ url: string;
12
+ pending?: true;
13
+ };
14
+
15
+ import type { InvoiceDto } from '@ysk-kit/contracts';
16
+
17
+ export interface IBillingPort {
18
+ checkout(input: CheckoutInput): Promise<CheckoutResult>;
19
+ portal(input: { returnUrl: string; customerId: string }): Promise<{ url: string }>;
20
+ listInvoices(input: { customerId: string }): Promise<InvoiceDto[]>;
21
+ getInvoicePdf(input: { customerId: string; invoiceId: string }): Promise<{ url: string } | null>;
22
+ requiresCustomer?: true;
23
+ }
@@ -0,0 +1,22 @@
1
+ import type { BillingPlanCode, SubscriptionStatus } from '@ysk-kit/contracts';
2
+
3
+ export type SubscriptionRecord = {
4
+ id: string;
5
+ organizationId: string;
6
+ planCode: BillingPlanCode;
7
+ status: SubscriptionStatus;
8
+ currentPeriodEnd: Date | null;
9
+ seatCount: number;
10
+ createdAt: Date;
11
+ };
12
+
13
+ export interface ISubscriptionRepository {
14
+ findByOrganizationId(organizationId: string): Promise<SubscriptionRecord | null>;
15
+ upsert(input: {
16
+ organizationId: string;
17
+ planCode: BillingPlanCode;
18
+ status: SubscriptionStatus;
19
+ currentPeriodEnd: Date | null;
20
+ seatCount: number;
21
+ }): Promise<SubscriptionRecord>;
22
+ }
@@ -0,0 +1,85 @@
1
+ import { type HttpHandler, mountContract } from '@ysk-kit/api-express';
2
+ import {
3
+ appContract,
4
+ type CancelCommand,
5
+ type CheckoutCommand,
6
+ type OrganizationIdQuery,
7
+ type PortalCommand,
8
+ } from '@ysk-kit/contracts';
9
+ import { AppError } from '@ysk-kit/domain-kernel';
10
+ import type { Express } from 'express';
11
+ import type { BillingService } from '../application/billing-service';
12
+
13
+ const orgQuery = (query: unknown): string => (query as OrganizationIdQuery).organizationId;
14
+
15
+ export const billingHandlers = (service: BillingService): Record<string, HttpHandler> => ({
16
+ plans: {
17
+ handle: async () => ({ status: 200, body: { ok: true, data: service.plans() } }),
18
+ },
19
+ subscription: {
20
+ auth: 'required',
21
+ permission: 'billing.checkout',
22
+ handle: async ({ auth, query }) => {
23
+ if (!auth) throw new AppError('UNAUTHENTICATED');
24
+ return {
25
+ status: 200,
26
+ body: { ok: true, data: await service.subscription(auth.sub, orgQuery(query)) },
27
+ };
28
+ },
29
+ },
30
+ invoices: {
31
+ auth: 'required',
32
+ permission: 'billing.checkout',
33
+ handle: async ({ auth, query }) => {
34
+ if (!auth) throw new AppError('UNAUTHENTICATED');
35
+ return {
36
+ status: 200,
37
+ body: { ok: true, data: await service.invoices(auth.sub, orgQuery(query)) },
38
+ };
39
+ },
40
+ },
41
+ checkout: {
42
+ auth: 'required',
43
+ permission: 'billing.checkout',
44
+ handle: async ({ body, auth }) => {
45
+ if (!auth) throw new AppError('UNAUTHENTICATED');
46
+ return {
47
+ status: 200,
48
+ body: { ok: true, data: await service.checkout(auth.sub, body as CheckoutCommand) },
49
+ };
50
+ },
51
+ },
52
+ cancel: {
53
+ auth: 'required',
54
+ permission: 'billing.checkout',
55
+ handle: async ({ auth, body }) => {
56
+ if (!auth) throw new AppError('UNAUTHENTICATED');
57
+ return {
58
+ status: 200,
59
+ body: {
60
+ ok: true,
61
+ data: await service.cancel(auth.sub, (body as CancelCommand).organizationId),
62
+ },
63
+ };
64
+ },
65
+ },
66
+ portal: {
67
+ auth: 'required',
68
+ permission: 'billing.checkout',
69
+ handle: async ({ body, auth }) => {
70
+ if (!auth) throw new AppError('UNAUTHENTICATED');
71
+ const command = body as PortalCommand;
72
+ return {
73
+ status: 200,
74
+ body: {
75
+ ok: true,
76
+ data: await service.portal(auth.sub, command.organizationId, command.returnUrl),
77
+ },
78
+ };
79
+ },
80
+ },
81
+ });
82
+
83
+ export const registerBillingRoutes = (app: Express, service: BillingService): void => {
84
+ mountContract(app, appContract.billing, billingHandlers(service));
85
+ };