@kelpie/server 0.3.1 → 0.4.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.
@@ -120,6 +120,13 @@
120
120
  "when": 1786410884983,
121
121
  "tag": "0016_misty_mentor",
122
122
  "breakpoints": true
123
+ },
124
+ {
125
+ "idx": 17,
126
+ "version": "7",
127
+ "when": 1786490413323,
128
+ "tag": "0017_drop_integration_connections",
129
+ "breakpoints": true
123
130
  }
124
131
  ]
125
- }
132
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kelpie/server",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "The Kelpie service as a library: module runtime, core CRM modules, REST API, MCP surface, and the shared migration pipeline.",
5
5
  "keywords": [
6
6
  "kelpie",
@@ -52,7 +52,7 @@
52
52
  },
53
53
  "dependencies": {
54
54
  "@hono/node-server": "^2.0.12",
55
- "@kelpie/schemas": "^0.3.1",
55
+ "@kelpie/schemas": "^0.4.0",
56
56
  "@node-rs/argon2": "^2.0.2",
57
57
  "drizzle-orm": "^0.45.2",
58
58
  "hono": "^4.12.32",
package/src/lib/ids.ts CHANGED
@@ -36,7 +36,6 @@ export const idPrefixes = {
36
36
  agentRegistration: 'ag',
37
37
  agentRun: 'run',
38
38
  importJob: 'imp',
39
- integrationConnection: 'int',
40
39
  /** Never returns over the wire either: identified by `(workspace_id, module_id)` instead. */
41
40
  moduleSetting: 'mset',
42
41
  /**
@@ -13,7 +13,6 @@ import { createFormsModule } from './forms/index.ts'
13
13
  import { createHandbookModule } from './handbook/index.ts'
14
14
  import { createHiringModule } from './hiring/index.ts'
15
15
  import { createImportExportModule } from './import-export/index.ts'
16
- import * as integrations from './integrations/schema.ts'
17
16
  import { createNotesModule } from './notes/index.ts'
18
17
  import { createOpportunitiesModule } from './opportunities/index.ts'
19
18
  import { createPartnershipsModule } from './partnerships/index.ts'
@@ -40,21 +39,6 @@ import { createWorkspaceModule } from './workspace/index.ts'
40
39
  */
41
40
  export const coreMigrationsDirectory = fileURLToPath(new URL('../../migrations', import.meta.url))
42
41
 
43
- interface CoreModuleDefinition {
44
- readonly id: string
45
- readonly requires?: readonly string[]
46
- readonly tables: Readonly<Record<string, unknown>>
47
- }
48
-
49
- const definitions: readonly CoreModuleDefinition[] = [
50
- { id: 'integrations', requires: ['workspace'], tables: integrations },
51
- ]
52
-
53
- /**
54
- * Modules with behaviour are written out; the rest contribute only tables so far
55
- * and are generated from the table above. As each grows routes and services it
56
- * moves out of `definitions` into its own module file, like `auth` has.
57
- */
58
42
  export const coreModules: readonly KelpieModule[] = [
59
43
  createAuthModule(coreMigrationsDirectory),
60
44
  createWorkspaceModule(coreMigrationsDirectory),
@@ -81,13 +65,4 @@ export const coreModules: readonly KelpieModule[] = [
81
65
  createImportExportModule(coreMigrationsDirectory),
82
66
  createAgentTasksModule(coreMigrationsDirectory),
83
67
  createWebhooksModule(coreMigrationsDirectory),
84
- ...definitions.map((definition): KelpieModule => ({
85
- id: definition.id,
86
- ...(definition.requires === undefined ? {} : { requires: definition.requires }),
87
- register(context) {
88
- context.schema(definition.tables, coreMigrationsDirectory)
89
-
90
- return Promise.resolve()
91
- },
92
- })),
93
68
  ]
@@ -4,7 +4,6 @@ import type { Database } from '../lib/database.ts'
4
4
  import { SecretDecryptionError } from '../lib/secrets.ts'
5
5
  import type { SecretCipher } from '../lib/secrets.ts'
6
6
  import { agentRegistrations } from './agent-tasks/schema.ts'
7
- import { integrationConnections } from './integrations/schema.ts'
8
7
  import { webhooks } from './webhooks/schema.ts'
9
8
 
10
9
  /**
@@ -37,18 +36,20 @@ interface SealedColumn {
37
36
  }
38
37
 
39
38
  /**
40
- * Every column holding a value sealed by `lib/secrets.ts`.
39
+ * Every column in core's schema holding a value sealed by `lib/secrets.ts`.
41
40
  *
42
- * All three that exist are here, including the two nothing writes yet. Covering
43
- * a column before its first write is the cheap half of this: the expensive half
44
- * is noticing, a year later, that a rotation reported success while stranding a
45
- * customer's stored OAuth credential with no way back. `resealTest` asserts this
46
- * list against the schema, so a fourth `_encrypted` column fails a test the day
47
- * it is added rather than at the next rotation.
41
+ * All three that exist are here, including `agent_registrations`, which nothing
42
+ * writes yet. Covering a column before its first write is the cheap half of
43
+ * this: the expensive half is noticing, a year later, that a rotation reported
44
+ * success while stranding a customer's stored credential with no way back. The
45
+ * test asserts this list against the schema, so a fourth `_encrypted` column
46
+ * fails the day it is added rather than at the next rotation.
48
47
  *
49
48
  * There is no registry for modules to declare these through, on purpose. One
50
49
  * caller does not need an extension point, and a sealed column that nothing
51
- * re-seals is a bug whichever way it was registered.
50
+ * re-seals is a bug whichever way it was registered. A module outside core
51
+ * seals under the same key through `createSecretCipher` and brings its own pass
52
+ * over its own tables, which this one cannot see.
52
53
  */
53
54
  const SEALED_COLUMNS: readonly SealedColumn[] = [
54
55
  {
@@ -79,19 +80,6 @@ const SEALED_COLUMNS: readonly SealedColumn[] = [
79
80
  .where(eq(agentRegistrations.id, id))
80
81
  },
81
82
  },
82
- {
83
- label: 'integration_connections.secrets_encrypted',
84
- read: async (db) =>
85
- db
86
- .select({ id: integrationConnections.id, sealed: integrationConnections.secretsEncrypted })
87
- .from(integrationConnections),
88
- write: async (db, id, sealed) => {
89
- await db
90
- .update(integrationConnections)
91
- .set({ secretsEncrypted: sealed })
92
- .where(eq(integrationConnections.id, id))
93
- },
94
- },
95
83
  ]
96
84
 
97
85
  /** The columns this pass covers. Exported for the test that checks none is missing. */
@@ -27,5 +27,4 @@ export * from '../modules/forms/schema.ts'
27
27
  export * from '../modules/import-export/schema.ts'
28
28
  export * from '../modules/agent-tasks/schema.ts'
29
29
  export * from '../modules/webhooks/schema.ts'
30
- export * from '../modules/integrations/schema.ts'
31
30
  export * from '../modules/rate-limit/schema.ts'
@@ -1,31 +0,0 @@
1
- import { index, jsonb, pgTable, text } from 'drizzle-orm/pg-core'
2
-
3
- import { createdAt, moment, primaryId, updatedAt } from '../../lib/columns.ts'
4
- import { users } from '../auth/schema.ts'
5
- import { workspaces } from '../workspace/schema.ts'
6
-
7
- /**
8
- * Core owns the connection lifecycle; the provider itself ships as a module and
9
- * declares `provider_id`. Module-owned provider tables reference this row.
10
- *
11
- * `user_id` is set for personal connections such as a Gmail mailbox, null for
12
- * workspace-wide ones.
13
- */
14
- export const integrationConnections = pgTable(
15
- 'integration_connections',
16
- {
17
- id: primaryId(),
18
- workspaceId: text('workspace_id')
19
- .notNull()
20
- .references(() => workspaces.id, { onDelete: 'cascade' }),
21
- providerId: text('provider_id').notNull(),
22
- userId: text('user_id').references(() => users.id, { onDelete: 'cascade' }),
23
- status: text('status').notNull(),
24
- config: jsonb('config').notNull().default({}),
25
- secretsEncrypted: text('secrets_encrypted'),
26
- lastSyncAt: moment('last_sync_at'),
27
- createdAt: createdAt(),
28
- updatedAt: updatedAt(),
29
- },
30
- (table) => [index('integration_connections_workspace_idx').on(table.workspaceId)],
31
- )