@checkstack/scripts 0.0.2

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 (48) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/package.json +21 -0
  3. package/src/cli.ts +56 -0
  4. package/src/commands/create.ts +259 -0
  5. package/src/sync.ts +125 -0
  6. package/src/templates/backend/.changeset/initial.md.hbs +5 -0
  7. package/src/templates/backend/README.md.hbs +46 -0
  8. package/src/templates/backend/drizzle.config.ts.hbs +10 -0
  9. package/src/templates/backend/package.json.hbs +11 -0
  10. package/src/templates/backend/src/index.ts.hbs +35 -0
  11. package/src/templates/backend/src/router.ts.hbs +46 -0
  12. package/src/templates/backend/src/schema.ts.hbs +19 -0
  13. package/src/templates/backend/src/service.ts.hbs +69 -0
  14. package/src/templates/backend/tsconfig.json +6 -0
  15. package/src/templates/common/.changeset/initial.md.hbs +5 -0
  16. package/src/templates/common/README.md.hbs +11 -0
  17. package/src/templates/common/package.json.hbs +7 -0
  18. package/src/templates/common/src/index.ts.hbs +14 -0
  19. package/src/templates/common/src/permissions.ts.hbs +17 -0
  20. package/src/templates/common/src/plugin-metadata.ts.hbs +6 -0
  21. package/src/templates/common/src/routes.ts.hbs +6 -0
  22. package/src/templates/common/src/rpc-contract.ts.hbs +60 -0
  23. package/src/templates/common/src/schemas.ts.hbs +25 -0
  24. package/src/templates/common/tsconfig.json +6 -0
  25. package/src/templates/frontend/.changeset/initial.md.hbs +5 -0
  26. package/src/templates/frontend/README.md.hbs +29 -0
  27. package/src/templates/frontend/bunfig.toml.hbs +1 -0
  28. package/src/templates/frontend/package.json.hbs +12 -0
  29. package/src/templates/frontend/playwright.config.ts.hbs +3 -0
  30. package/src/templates/frontend/src/api.ts.hbs +15 -0
  31. package/src/templates/frontend/src/components/{{pluginNamePascal}}ListPage.tsx.hbs +81 -0
  32. package/src/templates/frontend/src/index.tsx.hbs +33 -0
  33. package/src/templates/frontend/tsconfig.json.hbs +1 -0
  34. package/src/templates/node/.changeset/initial.md.hbs +5 -0
  35. package/src/templates/node/README.md.hbs +8 -0
  36. package/src/templates/node/package.json.hbs +3 -0
  37. package/src/templates/node/src/index.ts.hbs +6 -0
  38. package/src/templates/node/tsconfig.json +6 -0
  39. package/src/templates/react/.changeset/initial.md.hbs +5 -0
  40. package/src/templates/react/README.md.hbs +23 -0
  41. package/src/templates/react/package.json.hbs +4 -0
  42. package/src/templates/react/src/components/{{pluginNamePascal}}Component.tsx.hbs +12 -0
  43. package/src/templates/react/src/index.tsx.hbs +1 -0
  44. package/src/templates/react/tsconfig.json +6 -0
  45. package/src/templates.test.ts +134 -0
  46. package/src/utils/template.ts +154 -0
  47. package/src/utils/validation.ts +110 -0
  48. package/tsconfig.json +6 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # @checkstack/scripts
2
+
3
+ ## 0.0.2
4
+
5
+ ### Patch Changes
6
+
7
+ - d20d274: Initial release of all @checkstack packages. Rebranded from Checkmate to Checkstack with new npm organization @checkstack and domain checkstack.dev.
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "@checkstack/scripts",
3
+ "version": "0.0.2",
4
+ "bin": {
5
+ "checkstack-scripts": "./src/cli.ts"
6
+ },
7
+ "scripts": {
8
+ "sync": "bun run src/sync.ts",
9
+ "typecheck": "tsc --noEmit"
10
+ },
11
+ "dependencies": {
12
+ "inquirer": "^8.1.0",
13
+ "handlebars": "^4.7.8"
14
+ },
15
+ "devDependencies": {
16
+ "@checkstack/tsconfig": "workspace:*",
17
+ "@types/inquirer": "^8.2.10",
18
+ "@types/handlebars": "^4.1.0",
19
+ "typescript": "^5.0.0"
20
+ }
21
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env bun
2
+ import { spawnSync } from "node:child_process";
3
+ import path from "node:path";
4
+
5
+ const command = process.argv[2];
6
+
7
+ if (!command) {
8
+ console.log("Usage: checkstack-scripts <command>");
9
+ console.log("\nCommands:");
10
+ console.log(" create - Create a new plugin interactively");
11
+ console.log(" sync - Synchronize package configurations");
12
+ console.log(" generate - Generate migrations and strip public schema");
13
+ console.log(" typecheck - Run TypeScript type checking");
14
+ console.log(" lint - Run linting checks");
15
+ process.exit(1);
16
+ }
17
+
18
+ const rootDir = process.cwd();
19
+
20
+ if (command === "sync") {
21
+ const result = spawnSync(
22
+ "bun",
23
+ ["run", path.join(rootDir, "core/scripts/src/sync.ts")],
24
+ {
25
+ stdio: "inherit",
26
+ }
27
+ );
28
+ process.exit(result.status ?? 0);
29
+ }
30
+
31
+ if (command === "create") {
32
+ const result = spawnSync(
33
+ "bun",
34
+ ["run", path.join(rootDir, "core/scripts/src/commands/create.ts")],
35
+ {
36
+ stdio: "inherit",
37
+ }
38
+ );
39
+ process.exit(result.status ?? 0);
40
+ }
41
+
42
+ if (command === "generate") {
43
+ const result = spawnSync(
44
+ "bun",
45
+ [path.join(rootDir, "core/scripts/src/commands/generate.ts")],
46
+ {
47
+ cwd: rootDir, // Keep cwd as root, script will handle paths
48
+ stdio: "inherit",
49
+ }
50
+ );
51
+ process.exit(result.status ?? 0);
52
+ }
53
+
54
+ // Fallback for other scripts if we want to centralize their execution logic
55
+ console.error(`Unknown command: ${command}`);
56
+ process.exit(1);
@@ -0,0 +1,259 @@
1
+ #!/usr/bin/env bun
2
+ import inquirer from "inquirer";
3
+ import path from "node:path";
4
+ import {
5
+ validatePluginName,
6
+ pluginExists,
7
+ packageExists,
8
+ extractBaseName,
9
+ } from "../utils/validation";
10
+ import {
11
+ registerHelpers,
12
+ copyTemplate,
13
+ prepareTemplateData,
14
+ } from "../utils/template";
15
+ import { fileURLToPath } from "node:url";
16
+
17
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
18
+
19
+ interface PluginTypeChoice {
20
+ name: string;
21
+ value: string;
22
+ description: string;
23
+ }
24
+
25
+ interface LocationChoice {
26
+ name: string;
27
+ value: "core" | "plugins";
28
+ description: string;
29
+ }
30
+
31
+ const PACKAGE_LOCATIONS: LocationChoice[] = [
32
+ {
33
+ name: "core/ - Core platform component (essential, non-replaceable)",
34
+ value: "core",
35
+ description: "Core platform component",
36
+ },
37
+ {
38
+ name: "plugins/ - Replaceable provider (optional, swappable)",
39
+ value: "plugins",
40
+ description: "Replaceable provider plugin",
41
+ },
42
+ ];
43
+
44
+ const PLUGIN_TYPES: PluginTypeChoice[] = [
45
+ {
46
+ name: "backend - Backend plugin with oRPC router",
47
+ value: "backend",
48
+ description: "Backend plugin with oRPC router",
49
+ },
50
+ {
51
+ name: "frontend - Frontend plugin with React components",
52
+ value: "frontend",
53
+ description: "Frontend plugin with React components",
54
+ },
55
+ {
56
+ name: "common - Common plugin with contracts and types",
57
+ value: "common",
58
+ description: "Common plugin with contracts and types",
59
+ },
60
+ {
61
+ name: "node - Node.js utility plugin",
62
+ value: "node",
63
+ description: "Node.js utility plugin",
64
+ },
65
+ {
66
+ name: "react - React component library plugin",
67
+ value: "react",
68
+ description: "React component library plugin",
69
+ },
70
+ ];
71
+
72
+ export async function createCommand() {
73
+ console.log("\n🚀 Checkstack Package Generator\n");
74
+
75
+ // Register Handlebars helpers
76
+ registerHelpers();
77
+
78
+ // Prompt for package location
79
+ const { packageLocation } = await inquirer.prompt<{
80
+ packageLocation: "core" | "plugins";
81
+ }>([
82
+ {
83
+ type: "list",
84
+ name: "packageLocation",
85
+ message: "Where should the package be created?",
86
+ choices: PACKAGE_LOCATIONS,
87
+ },
88
+ ]);
89
+
90
+ // Show guidance based on choice
91
+ if (packageLocation === "core") {
92
+ console.log(
93
+ "\n📦 Core packages are essential platform components that cannot be removed."
94
+ );
95
+ console.log(
96
+ " Examples: auth, catalog, notifications, queue, healthcheck, theme\n"
97
+ );
98
+ } else {
99
+ console.log(
100
+ "\n🔌 Plugins are replaceable providers that can be swapped or removed."
101
+ );
102
+ console.log(
103
+ " Examples: auth-github, auth-ldap, queue-bullmq, healthcheck-http\n"
104
+ );
105
+ }
106
+
107
+ // Prompt for plugin type
108
+ const { pluginType } = await inquirer.prompt<{ pluginType: string }>([
109
+ {
110
+ type: "list",
111
+ name: "pluginType",
112
+ message: "What type of package do you want to create?",
113
+ choices: PLUGIN_TYPES,
114
+ },
115
+ ]);
116
+
117
+ // Prompt for plugin name
118
+ const { pluginBaseName } = await inquirer.prompt<{ pluginBaseName: string }>([
119
+ {
120
+ type: "input",
121
+ name: "pluginBaseName",
122
+ message: `Package name (e.g., 'catalog' for 'catalog-${pluginType}'):`,
123
+ validate: (input: string) => {
124
+ const extracted = extractBaseName(input);
125
+ const validation = validatePluginName(extracted);
126
+ return validation.valid || validation.error || false;
127
+ },
128
+ filter: (input: string) => extractBaseName(input.trim()),
129
+ },
130
+ ]);
131
+
132
+ // Check if plugin already exists
133
+ const rootDir = process.cwd();
134
+ const existsInPlugins = pluginExists({
135
+ baseName: pluginBaseName,
136
+ pluginType,
137
+ rootDir,
138
+ });
139
+ const existsInPackages = packageExists({
140
+ baseName: pluginBaseName,
141
+ pluginType,
142
+ rootDir,
143
+ });
144
+
145
+ if (existsInPlugins || existsInPackages) {
146
+ const existingLocation = existsInPackages ? "core" : "plugins";
147
+ console.error(
148
+ `\n❌ Error: '${pluginBaseName}-${pluginType}' already exists in ${existingLocation}/!\n`
149
+ );
150
+ process.exit(1);
151
+ }
152
+
153
+ // Prompt for description
154
+ const { description } = await inquirer.prompt<{ description: string }>([
155
+ {
156
+ type: "input",
157
+ name: "description",
158
+ message: "Package description (optional):",
159
+ default: `${
160
+ pluginBaseName.charAt(0).toUpperCase() + pluginBaseName.slice(1)
161
+ } ${packageLocation === "core" ? "package" : "plugin"}`,
162
+ },
163
+ ]);
164
+
165
+ // Prepare template data
166
+ const templateData = prepareTemplateData({
167
+ baseName: pluginBaseName,
168
+ pluginType,
169
+ description,
170
+ });
171
+
172
+ // Confirm before generation
173
+ const locationLabel = packageLocation === "core" ? "package" : "plugin";
174
+ const { confirmed } = await inquirer.prompt<{ confirmed: boolean }>([
175
+ {
176
+ type: "confirm",
177
+ name: "confirmed",
178
+ message: `Create ${pluginType} ${locationLabel} '${templateData.pluginName}' in ${packageLocation}/?`,
179
+ default: true,
180
+ },
181
+ ]);
182
+
183
+ if (!confirmed) {
184
+ console.log("\n❌ Creation cancelled.\n");
185
+ process.exit(0);
186
+ }
187
+
188
+ // Generate plugin from template
189
+ console.log(
190
+ `\n📦 Creating ${pluginType} ${locationLabel}: ${templateData.pluginName}`
191
+ );
192
+
193
+ const templateDir = path.join(__dirname, "..", "templates", pluginType);
194
+ const targetDir = path.join(
195
+ rootDir,
196
+ packageLocation,
197
+ templateData.pluginName
198
+ );
199
+
200
+ try {
201
+ const createdFiles = copyTemplate({
202
+ templateDir,
203
+ targetDir,
204
+ data: templateData,
205
+ });
206
+
207
+ console.log(
208
+ ` ✓ Created directory: ${packageLocation}/${templateData.pluginName}`
209
+ );
210
+
211
+ // Show created files
212
+ const relativeFiles = createdFiles.map((file) =>
213
+ path.relative(targetDir, file)
214
+ );
215
+ for (const file of relativeFiles) {
216
+ console.log(` ✓ Generated ${file}`);
217
+ }
218
+
219
+ // Success message with next steps
220
+ console.log(
221
+ `\n✅ ${
222
+ locationLabel.charAt(0).toUpperCase() + locationLabel.slice(1)
223
+ } created successfully!\n`
224
+ );
225
+ console.log("Next steps:");
226
+ console.log(` 1. cd ${packageLocation}/${templateData.pluginName}`);
227
+ console.log(` 2. bun install`);
228
+
229
+ // Type-specific instructions
230
+ switch (pluginType) {
231
+ case "backend": {
232
+ console.log(` 3. Update src/schema.ts with your database schema`);
233
+ console.log(` 4. Update src/router.ts with your RPC procedures`);
234
+ console.log(` 5. Generate migrations: bun run drizzle-kit generate`);
235
+ break;
236
+ }
237
+ case "frontend": {
238
+ console.log(` 3. Update src/api.ts with your API client`);
239
+ console.log(` 4. Create your page components in src/components/`);
240
+ break;
241
+ }
242
+ case "common": {
243
+ console.log(` 3. Define your permissions in src/permissions.ts`);
244
+ console.log(` 4. Define your schemas in src/schemas.ts`);
245
+ console.log(` 5. Define your contract in src/rpc-contract.ts`);
246
+ break;
247
+ }
248
+ // No additional steps for node and react types
249
+ }
250
+
251
+ console.log(` 6. Review the initial changeset in .changeset/initial.md\n`);
252
+ } catch (error) {
253
+ console.error(`\n❌ Error creating ${locationLabel}: ${error}\n`);
254
+ process.exit(1);
255
+ }
256
+ }
257
+
258
+ // Run the command when executed directly
259
+ await createCommand();
package/src/sync.ts ADDED
@@ -0,0 +1,125 @@
1
+ import { readFileSync, writeFileSync, existsSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { Glob } from "bun";
4
+
5
+ function stripComments(text: string) {
6
+ return text.replaceAll(/\/\/.*|\/\*[\s\S]*?\*\//g, "");
7
+ }
8
+
9
+ const rootDir = process.cwd();
10
+
11
+ // Standard scripts we want to share
12
+ const STANDARD_SCRIPTS: Record<string, string> = {
13
+ typecheck: "tsc --noEmit",
14
+ lint: "bun run lint:code",
15
+ "lint:code": "eslint . --max-warnings 0",
16
+ };
17
+
18
+ const packageGlob = new Glob("{packages,plugins}/*/package.json");
19
+ const packages = [...packageGlob.scanSync({ cwd: rootDir })];
20
+
21
+ console.log(`Checking ${packages.length} packages for synchronization...`);
22
+
23
+ for (const pkgPath of packages) {
24
+ const fullPkgPath = path.join(rootDir, pkgPath);
25
+ const pkgDir = path.join(rootDir, pkgPath.replaceAll("/package.json", ""));
26
+ const tsconfigPath = path.join(pkgDir, "tsconfig.json");
27
+
28
+ const pkgContent = readFileSync(fullPkgPath, "utf8");
29
+ let pkg;
30
+ try {
31
+ pkg = JSON.parse(pkgContent);
32
+ } catch {
33
+ console.error(`Failed to parse ${fullPkgPath}`);
34
+ continue;
35
+ }
36
+
37
+ if (
38
+ pkg.name === "@checkstack/scripts" ||
39
+ pkg.name === "@checkstack/tsconfig"
40
+ )
41
+ continue;
42
+
43
+ let pkgChanged = false;
44
+ pkg.scripts = pkg.scripts || {};
45
+ pkg.devDependencies = pkg.devDependencies || {};
46
+
47
+ // Ensure @checkstack/scripts is present
48
+ if (pkg.devDependencies["@checkstack/scripts"] !== "workspace:*") {
49
+ console.log(
50
+ ` [${pkg.name}] Adding @checkstack/scripts to devDependencies`
51
+ );
52
+ pkg.devDependencies["@checkstack/scripts"] = "workspace:*";
53
+ pkgChanged = true;
54
+ }
55
+
56
+ // Update scripts
57
+ for (const [name, script] of Object.entries(STANDARD_SCRIPTS)) {
58
+ if (pkg.scripts[name] !== script) {
59
+ console.log(` [${pkg.name}] Updating script: ${name}`);
60
+ pkg.scripts[name] = script;
61
+ pkgChanged = true;
62
+ }
63
+ }
64
+
65
+ if (pkgChanged) {
66
+ writeFileSync(fullPkgPath, JSON.stringify(pkg, undefined, 2) + "\n");
67
+ }
68
+
69
+ // Manage tsconfig.json
70
+ if (existsSync(tsconfigPath)) {
71
+ const tsconfigContent = readFileSync(tsconfigPath, "utf8");
72
+ let tsconfig;
73
+ try {
74
+ tsconfig = JSON.parse(stripComments(tsconfigContent));
75
+ } catch {
76
+ continue;
77
+ }
78
+
79
+ let tsconfigChanged = false;
80
+
81
+ // Determine correct configType
82
+ let configType = "backend.json";
83
+ const isFrontend =
84
+ pkgPath.includes("frontend") ||
85
+ pkg.name.match(/frontend|ui|dashboard/) ||
86
+ (pkg.dependencies &&
87
+ (pkg.dependencies["react"] || pkg.dependencies["vite"]));
88
+ const isCommon = pkgPath.includes("common") || pkg.name.includes("common");
89
+
90
+ if (isFrontend) {
91
+ configType = "frontend.json";
92
+ } else if (isCommon) {
93
+ configType = "common.json";
94
+ }
95
+
96
+ const expectedExtends = `@checkstack/tsconfig/${configType}`;
97
+ if (tsconfig.extends !== expectedExtends) {
98
+ console.log(
99
+ ` [${pkg.name}] Updating tsconfig extends to ${expectedExtends}`
100
+ );
101
+ tsconfig.extends = expectedExtends;
102
+ tsconfigChanged = true;
103
+ }
104
+
105
+ // Repair corrupted include path
106
+ if (Array.isArray(tsconfig.include) && tsconfig.include.includes("src*")) {
107
+ console.log(
108
+ ` [${pkg.name}] Fixing corrupted include path in tsconfig.json`
109
+ );
110
+ tsconfig.include = tsconfig.include.map((i: string) =>
111
+ i === "src*" ? "src" : i
112
+ );
113
+ tsconfigChanged = true;
114
+ }
115
+
116
+ if (tsconfigChanged) {
117
+ writeFileSync(
118
+ tsconfigPath,
119
+ JSON.stringify(tsconfig, undefined, 2) + "\n"
120
+ );
121
+ }
122
+ }
123
+ }
124
+
125
+ console.log("Synchronization complete!");
@@ -0,0 +1,5 @@
1
+ --- "@checkstack/{{pluginName}}": patch --- Initial release of
2
+ {{pluginNamePascal}}
3
+ backend plugin
4
+
5
+ {{pluginDescription}}
@@ -0,0 +1,46 @@
1
+ # {{pluginNamePascal}} Backend
2
+
3
+ Backend plugin for {{pluginNamePascal}}. Implements the oRPC contract with database persistence.
4
+
5
+ ## Structure
6
+
7
+ - `src/index.ts` - Plugin entry point
8
+ - `src/router.ts` - oRPC router implementation
9
+ - `src/service.ts` - Business logic layer
10
+ - `src/schema.ts` - Drizzle database schema
11
+ - `drizzle.config.ts` - Drizzle Kit configuration
12
+ - `migrations/` - Database migrations (generated)
13
+
14
+ ## Development
15
+
16
+ ### Generate Migration
17
+
18
+ After modifying `src/schema.ts`:
19
+
20
+ ```bash
21
+ bun run drizzle-kit generate
22
+ ```
23
+
24
+ ### Run Migration
25
+
26
+ ```bash
27
+ bun run drizzle-kit migrate
28
+ ```
29
+
30
+ ### Type Check
31
+
32
+ ```bash
33
+ bun run typecheck
34
+ ```
35
+
36
+ ### Lint
37
+
38
+ ```bash
39
+ bun run lint
40
+ ```
41
+
42
+ ## Testing
43
+
44
+ ```bash
45
+ bun test
46
+ ```
@@ -0,0 +1,10 @@
1
+ import { defineConfig } from "drizzle-kit";
2
+
3
+ export default defineConfig({
4
+ schema: "./src/schema.ts",
5
+ out: "./migrations",
6
+ dialect: "postgresql",
7
+ dbCredentials: {
8
+ url: process.env.DATABASE_URL || "postgresql://localhost:5432/checkstack",
9
+ },
10
+ });
@@ -0,0 +1,11 @@
1
+ { "name": "@checkstack/{{pluginName}}", "version": "0.0.1",
2
+ "description": "{{pluginDescription}}", "type": "module", "exports": { ".": {
3
+ "import": "./src/index.ts" } }, "scripts": { "typecheck": "tsc --noEmit",
4
+ "lint": "bun run lint:code", "lint:code": "eslint . --max-warnings 0", "test":
5
+ "bun test" }, "dependencies": { "@checkstack/backend-api": "workspace:*",
6
+ "@checkstack/common": "workspace:*", "@checkstack/{{pluginBaseName}}-common":
7
+ "workspace:*", "@orpc/server": "^1.13.2", "drizzle-orm": "^0.45.1" },
8
+ "devDependencies": { "@checkstack/tsconfig": "workspace:*",
9
+ "drizzle-kit": "^0.28.1", "@checkstack/drizzle-helper": "workspace:*",
10
+ "@checkstack/test-utils-backend": "workspace:*", "typescript": "^5.7.2" }
11
+ }
@@ -0,0 +1,35 @@
1
+ import {
2
+ createBackendPlugin,
3
+ coreServices,
4
+ } from "@checkstack/backend-api";
5
+ import {
6
+ permissionList,
7
+ pluginMetadata,
8
+ {{pluginNameCamel}}Contract,
9
+ } from "@checkstack/{{pluginBaseName}}-common";
10
+ import * as schema from "./schema";
11
+ import { create{{pluginNamePascal}}Router } from "./router";
12
+
13
+ export default createBackendPlugin({
14
+ metadata: pluginMetadata,
15
+ register(env) {
16
+ env.registerPermissions(permissionList);
17
+
18
+ env.registerInit({
19
+ schema,
20
+ deps: {
21
+ rpc: coreServices.rpc,
22
+ logger: coreServices.logger,
23
+ },
24
+ init: async ({ database, rpc, logger }) => {
25
+ logger.debug("Initializing {{pluginNamePascal}} Backend...");
26
+
27
+ // Create and register router with plugin-scoped database
28
+ const router = create{{pluginNamePascal}}Router({ database });
29
+ rpc.registerRouter(router, {{pluginNameCamel}}Contract);
30
+
31
+ logger.debug("✅ {{pluginNamePascal}} Backend initialized.");
32
+ },
33
+ });
34
+ },
35
+ });
@@ -0,0 +1,46 @@
1
+ import { implement } from "@orpc/server";
2
+ import { autoAuthMiddleware, type RpcContext } from "@checkstack/backend-api";
3
+ import { {{pluginNameCamel}}Contract } from "@checkstack/{{pluginBaseName}}-common";
4
+ import type { NodePgDatabase } from "drizzle-orm/node-postgres";
5
+ import type * as schema from "./schema";
6
+ import { {{pluginNamePascal}}Service } from "./service";
7
+
8
+ /**
9
+ * Creates the {{pluginBaseName}} router using contract-based implementation.
10
+ *
11
+ * Auth and permissions are automatically enforced via autoAuthMiddleware
12
+ * based on the contract's meta.userType and meta.permissions.
13
+ */
14
+ const os = implement({{pluginNameCamel}}Contract)
15
+ .$context<RpcContext>()
16
+ .use(autoAuthMiddleware);
17
+
18
+ export function create{{pluginNamePascal}}Router({
19
+ database,
20
+ }: {
21
+ database: NodePgDatabase<typeof schema>;
22
+ }) {
23
+ const service = new {{pluginNamePascal}}Service(database);
24
+
25
+ return os.router({
26
+ getItems: os.getItems.handler(async () => {
27
+ return await service.getItems();
28
+ }),
29
+
30
+ getItem: os.getItem.handler(async ({ input }) => {
31
+ return await service.getItem(input);
32
+ }),
33
+
34
+ createItem: os.createItem.handler(async ({ input }) => {
35
+ return await service.createItem(input);
36
+ }),
37
+
38
+ updateItem: os.updateItem.handler(async ({ input }) => {
39
+ return await service.updateItem(input.id, input.data);
40
+ }),
41
+
42
+ deleteItem: os.deleteItem.handler(async ({ input }) => {
43
+ await service.deleteItem(input);
44
+ }),
45
+ });
46
+ }
@@ -0,0 +1,19 @@
1
+ import { pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";
2
+
3
+ /**
4
+ * Plugin database schema.
5
+ *
6
+ * Tables use `pgTable()` for schema-agnostic definitions.
7
+ * At runtime, the plugin's database connection uses `search_path`
8
+ * to route all queries to the plugin's isolated schema.
9
+ */
10
+ export const {{pluginNameCamel}}Items = pgTable("items", {
11
+ id: uuid("id").primaryKey().defaultRandom(),
12
+ name: text("name").notNull(),
13
+ description: text("description"),
14
+ createdAt: timestamp("created_at").notNull().defaultNow(),
15
+ updatedAt: timestamp("updated_at").notNull().defaultNow(),
16
+ });
17
+
18
+ export type {{pluginNamePascal}}Item = typeof {{pluginNameCamel}}Items.$inferSelect;
19
+ export type New{{pluginNamePascal}}Item = typeof {{pluginNameCamel}}Items.$inferInsert;