@minion-stack/db 0.2.0 → 0.3.1

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 (67) hide show
  1. package/dist/pg/crypto.d.ts +8 -0
  2. package/dist/pg/crypto.d.ts.map +1 -0
  3. package/dist/pg/crypto.js +42 -0
  4. package/dist/pg/crypto.js.map +1 -0
  5. package/dist/pg/crypto.test.d.ts +2 -0
  6. package/dist/pg/crypto.test.d.ts.map +1 -0
  7. package/dist/pg/crypto.test.js +25 -0
  8. package/dist/pg/crypto.test.js.map +1 -0
  9. package/dist/pg/identity-mapper.d.ts +36 -0
  10. package/dist/pg/identity-mapper.d.ts.map +1 -0
  11. package/dist/pg/identity-mapper.js +18 -0
  12. package/dist/pg/identity-mapper.js.map +1 -0
  13. package/dist/pg/identity-mapper.test.d.ts +2 -0
  14. package/dist/pg/identity-mapper.test.d.ts.map +1 -0
  15. package/dist/pg/identity-mapper.test.js +32 -0
  16. package/dist/pg/identity-mapper.test.js.map +1 -0
  17. package/dist/pg/schema/gateway.d.ts +262 -0
  18. package/dist/pg/schema/gateway.d.ts.map +1 -0
  19. package/dist/pg/schema/gateway.js +36 -0
  20. package/dist/pg/schema/gateway.js.map +1 -0
  21. package/dist/pg/schema/index.d.ts +8 -0
  22. package/dist/pg/schema/index.d.ts.map +1 -0
  23. package/dist/pg/schema/index.js +8 -0
  24. package/dist/pg/schema/index.js.map +1 -0
  25. package/dist/pg/schema/join.d.ts +406 -0
  26. package/dist/pg/schema/join.d.ts.map +1 -0
  27. package/dist/pg/schema/join.js +37 -0
  28. package/dist/pg/schema/join.js.map +1 -0
  29. package/dist/pg/schema/profiles.d.ts +151 -0
  30. package/dist/pg/schema/profiles.d.ts.map +1 -0
  31. package/dist/pg/schema/profiles.js +23 -0
  32. package/dist/pg/schema/profiles.js.map +1 -0
  33. package/dist/pg/schema/user-identities.d.ts +237 -0
  34. package/dist/pg/schema/user-identities.d.ts.map +1 -0
  35. package/dist/pg/schema/user-identities.js +31 -0
  36. package/dist/pg/schema/user-identities.js.map +1 -0
  37. package/dist/schema/index.d.ts +3 -0
  38. package/dist/schema/index.d.ts.map +1 -1
  39. package/dist/schema/index.js +2 -0
  40. package/dist/schema/index.js.map +1 -1
  41. package/dist/schema/personal-agents.d.ts +1 -1
  42. package/dist/schema/reliability-events.d.ts +1 -1
  43. package/dist/schema/server-provision-configs.d.ts +1 -1
  44. package/dist/schema/session-tasks.d.ts +1 -1
  45. package/dist/schema/skill-execution-stats.d.ts +1 -1
  46. package/dist/schema/tasks.d.ts +1 -1
  47. package/dist/schema/user-identities.d.ts +254 -0
  48. package/dist/schema/user-identities.d.ts.map +1 -0
  49. package/dist/schema/user-identities.js +30 -0
  50. package/dist/schema/user-identities.js.map +1 -0
  51. package/dist/schema/workspace-membership.d.ts +94 -0
  52. package/dist/schema/workspace-membership.d.ts.map +1 -0
  53. package/dist/schema/workspace-membership.js +24 -0
  54. package/dist/schema/workspace-membership.js.map +1 -0
  55. package/package.json +14 -4
  56. package/src/pg/crypto.test.ts +28 -0
  57. package/src/pg/crypto.ts +44 -0
  58. package/src/pg/identity-mapper.test.ts +35 -0
  59. package/src/pg/identity-mapper.ts +48 -0
  60. package/src/pg/schema/gateway.ts +37 -0
  61. package/src/pg/schema/index.ts +14 -0
  62. package/src/pg/schema/join.ts +38 -0
  63. package/src/pg/schema/profiles.ts +23 -0
  64. package/src/pg/schema/user-identities.ts +35 -0
  65. package/src/schema/index.ts +3 -0
  66. package/src/schema/user-identities.ts +34 -0
  67. package/src/schema/workspace-membership.ts +31 -0
@@ -0,0 +1,8 @@
1
+ /** Seal plaintext → { ciphertext, iv }. ciphertext = hex(encrypted || authTag). */
2
+ export declare function sealSecret(plaintext: string): {
3
+ ciphertext: string;
4
+ iv: string;
5
+ };
6
+ /** Open hex(encrypted || authTag) + hex(iv) → plaintext. Throws on auth failure. */
7
+ export declare function openSecret(ciphertext: string, iv: string): string;
8
+ //# sourceMappingURL=crypto.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../../src/pg/crypto.ts"],"names":[],"mappings":"AAyBA,mFAAmF;AACnF,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAOhF;AAED,oFAAoF;AACpF,wBAAgB,UAAU,CAAC,UAAU,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAOjE"}
@@ -0,0 +1,42 @@
1
+ import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from 'node:crypto';
2
+ const ALGORITHM = 'aes-256-gcm';
3
+ const IV_BYTES = 12;
4
+ const AUTH_TAG_BYTES = 16;
5
+ // MUST match minion_hub/src/server/auth/crypto.ts so hub + site interoperate:
6
+ // key = scryptSync(ENCRYPTION_KEY, 'minion-hub-salt', 32)
7
+ // ciphertext (hex) = encrypted || authTag (16-byte tag LAST)
8
+ // iv (hex) = 12 random bytes, stored separately
9
+ let cachedKey = null;
10
+ function key() {
11
+ if (cachedKey)
12
+ return cachedKey;
13
+ const raw = process.env.ENCRYPTION_KEY;
14
+ if (!raw) {
15
+ if (process.env.NODE_ENV === 'production') {
16
+ throw new Error('ENCRYPTION_KEY environment variable must be set in production');
17
+ }
18
+ cachedKey = scryptSync('minion-hub-dev-key', 'minion-hub-salt', 32);
19
+ return cachedKey;
20
+ }
21
+ cachedKey = scryptSync(raw, 'minion-hub-salt', 32);
22
+ return cachedKey;
23
+ }
24
+ /** Seal plaintext → { ciphertext, iv }. ciphertext = hex(encrypted || authTag). */
25
+ export function sealSecret(plaintext) {
26
+ const iv = randomBytes(IV_BYTES);
27
+ const cipher = createCipheriv(ALGORITHM, key(), iv);
28
+ const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
29
+ const authTag = cipher.getAuthTag();
30
+ const combined = Buffer.concat([encrypted, authTag]);
31
+ return { ciphertext: combined.toString('hex'), iv: iv.toString('hex') };
32
+ }
33
+ /** Open hex(encrypted || authTag) + hex(iv) → plaintext. Throws on auth failure. */
34
+ export function openSecret(ciphertext, iv) {
35
+ const combined = Buffer.from(ciphertext, 'hex');
36
+ const encrypted = combined.subarray(0, combined.length - AUTH_TAG_BYTES);
37
+ const authTag = combined.subarray(combined.length - AUTH_TAG_BYTES);
38
+ const decipher = createDecipheriv(ALGORITHM, key(), Buffer.from(iv, 'hex'));
39
+ decipher.setAuthTag(authTag);
40
+ return decipher.update(encrypted) + decipher.final('utf8');
41
+ }
42
+ //# sourceMappingURL=crypto.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto.js","sourceRoot":"","sources":["../../src/pg/crypto.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAExF,MAAM,SAAS,GAAG,aAAa,CAAC;AAChC,MAAM,QAAQ,GAAG,EAAE,CAAC;AACpB,MAAM,cAAc,GAAG,EAAE,CAAC;AAE1B,8EAA8E;AAC9E,4DAA4D;AAC5D,iEAAiE;AACjE,kDAAkD;AAClD,IAAI,SAAS,GAAkB,IAAI,CAAC;AACpC,SAAS,GAAG;IACV,IAAI,SAAS;QAAE,OAAO,SAAS,CAAC;IAChC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;IACvC,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY,EAAE,CAAC;YAC1C,MAAM,IAAI,KAAK,CAAC,+DAA+D,CAAC,CAAC;QACnF,CAAC;QACD,SAAS,GAAG,UAAU,CAAC,oBAAoB,EAAE,iBAAiB,EAAE,EAAE,CAAC,CAAC;QACpE,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,SAAS,GAAG,UAAU,CAAC,GAAG,EAAE,iBAAiB,EAAE,EAAE,CAAC,CAAC;IACnD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,UAAU,CAAC,SAAiB;IAC1C,MAAM,EAAE,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC;IACjC,MAAM,MAAM,GAAG,cAAc,CAAC,SAAS,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC,CAAC;IACpD,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IACpF,MAAM,OAAO,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC;IACpC,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC;IACrD,OAAO,EAAE,UAAU,EAAE,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;AAC1E,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,UAAU,CAAC,UAAkB,EAAE,EAAU;IACvD,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;IAChD,MAAM,SAAS,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC,EAAE,QAAQ,CAAC,MAAM,GAAG,cAAc,CAAC,CAAC;IACzE,MAAM,OAAO,GAAG,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,cAAc,CAAC,CAAC;IACpE,MAAM,QAAQ,GAAG,gBAAgB,CAAC,SAAS,EAAE,GAAG,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC;IAC5E,QAAQ,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;IAC7B,OAAO,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;AAC7D,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=crypto.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto.test.d.ts","sourceRoot":"","sources":["../../src/pg/crypto.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,25 @@
1
+ import { describe, it, expect, beforeAll } from 'vitest';
2
+ import { sealSecret, openSecret } from './crypto.js';
3
+ beforeAll(() => {
4
+ process.env.ENCRYPTION_KEY = 'test-key-do-not-use-in-prod';
5
+ });
6
+ describe('identity secret crypto', () => {
7
+ it('round-trips a plaintext through seal/open', () => {
8
+ const plaintext = JSON.stringify({ type: 'authorized_user', refresh_token: 'abc123' });
9
+ const { ciphertext, iv } = sealSecret(plaintext);
10
+ expect(ciphertext).toMatch(/^[0-9a-f]+$/);
11
+ expect(iv).toMatch(/^[0-9a-f]+$/);
12
+ expect(openSecret(ciphertext, iv)).toBe(plaintext);
13
+ });
14
+ it('fails to open with a tampered ciphertext (GCM auth)', () => {
15
+ const { ciphertext, iv } = sealSecret('hello');
16
+ const tampered = ciphertext.slice(0, -2) + (ciphertext.endsWith('00') ? '11' : '00');
17
+ expect(() => openSecret(tampered, iv)).toThrow();
18
+ });
19
+ it('is wire-compatible with hub layout (ciphertext = hex(encrypted || authTag), 16-byte tag last)', () => {
20
+ const { ciphertext } = sealSecret('x');
21
+ // hex of (>=0 bytes ciphertext) + 16-byte tag → at least 32 hex chars of tag.
22
+ expect(ciphertext.length).toBeGreaterThanOrEqual(32);
23
+ });
24
+ });
25
+ //# sourceMappingURL=crypto.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto.test.js","sourceRoot":"","sources":["../../src/pg/crypto.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAErD,SAAS,CAAC,GAAG,EAAE;IACb,OAAO,CAAC,GAAG,CAAC,cAAc,GAAG,6BAA6B,CAAC;AAC7D,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,wBAAwB,EAAE,GAAG,EAAE;IACtC,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACnD,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,iBAAiB,EAAE,aAAa,EAAE,QAAQ,EAAE,CAAC,CAAC;QACvF,MAAM,EAAE,UAAU,EAAE,EAAE,EAAE,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC;QACjD,MAAM,CAAC,UAAU,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;QAC1C,MAAM,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;QAClC,MAAM,CAAC,UAAU,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACrD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC7D,MAAM,EAAE,UAAU,EAAE,EAAE,EAAE,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC;QAC/C,MAAM,QAAQ,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACrF,MAAM,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;IACnD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+FAA+F,EAAE,GAAG,EAAE;QACvG,MAAM,EAAE,UAAU,EAAE,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC;QACvC,8EAA8E;QAC9E,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,sBAAsB,CAAC,EAAE,CAAC,CAAC;IACvD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,36 @@
1
+ export interface GoTrueUserLike {
2
+ id: string;
3
+ email: string | null;
4
+ user_metadata?: {
5
+ full_name?: string;
6
+ name?: string;
7
+ provider_id?: string;
8
+ } | null;
9
+ app_metadata?: {
10
+ provider?: string;
11
+ } | null;
12
+ }
13
+ export interface GoogleGrant {
14
+ refreshToken: string | null;
15
+ scope: string | null;
16
+ }
17
+ export interface MappedProfile {
18
+ id: string;
19
+ email: string;
20
+ displayName: string | null;
21
+ }
22
+ export interface MappedIdentity {
23
+ userId: string;
24
+ provider: 'google';
25
+ kind: 'oauth';
26
+ externalId: string;
27
+ displayName: string | null;
28
+ scope: string | null;
29
+ hasSecret: boolean;
30
+ }
31
+ /** Pure mapping — no DB, no crypto. Caller seals the secret and upserts. */
32
+ export declare function mapGoogleIdentity(user: GoTrueUserLike, grant: GoogleGrant): {
33
+ profile: MappedProfile;
34
+ identity: MappedIdentity;
35
+ };
36
+ //# sourceMappingURL=identity-mapper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity-mapper.d.ts","sourceRoot":"","sources":["../../src/pg/identity-mapper.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,aAAa,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACnF,YAAY,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CAC7C;AAED,MAAM,WAAW,WAAW;IAC1B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,QAAQ,CAAC;IACnB,IAAI,EAAE,OAAO,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,4EAA4E;AAC5E,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,cAAc,EACpB,KAAK,EAAE,WAAW,GACjB;IAAE,OAAO,EAAE,aAAa,CAAC;IAAC,QAAQ,EAAE,cAAc,CAAA;CAAE,CAetD"}
@@ -0,0 +1,18 @@
1
+ /** Pure mapping — no DB, no crypto. Caller seals the secret and upserts. */
2
+ export function mapGoogleIdentity(user, grant) {
3
+ const email = user.email ?? '';
4
+ const displayName = user.user_metadata?.full_name ?? user.user_metadata?.name ?? null;
5
+ return {
6
+ profile: { id: user.id, email, displayName },
7
+ identity: {
8
+ userId: user.id,
9
+ provider: 'google',
10
+ kind: 'oauth',
11
+ externalId: email,
12
+ displayName,
13
+ scope: grant.scope,
14
+ hasSecret: Boolean(grant.refreshToken),
15
+ },
16
+ };
17
+ }
18
+ //# sourceMappingURL=identity-mapper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity-mapper.js","sourceRoot":"","sources":["../../src/pg/identity-mapper.ts"],"names":[],"mappings":"AA4BA,4EAA4E;AAC5E,MAAM,UAAU,iBAAiB,CAC/B,IAAoB,EACpB,KAAkB;IAElB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;IAC/B,MAAM,WAAW,GAAG,IAAI,CAAC,aAAa,EAAE,SAAS,IAAI,IAAI,CAAC,aAAa,EAAE,IAAI,IAAI,IAAI,CAAC;IACtF,OAAO;QACL,OAAO,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE;QAC5C,QAAQ,EAAE;YACR,MAAM,EAAE,IAAI,CAAC,EAAE;YACf,QAAQ,EAAE,QAAQ;YAClB,IAAI,EAAE,OAAO;YACb,UAAU,EAAE,KAAK;YACjB,WAAW;YACX,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,SAAS,EAAE,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC;SACvC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=identity-mapper.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity-mapper.test.d.ts","sourceRoot":"","sources":["../../src/pg/identity-mapper.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,32 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { mapGoogleIdentity } from './identity-mapper.js';
3
+ const gotrueUser = {
4
+ id: '11111111-1111-1111-1111-111111111111',
5
+ email: 'nik@example.com',
6
+ user_metadata: { full_name: 'Nik P', provider_id: 'google-sub-123' },
7
+ app_metadata: { provider: 'google' },
8
+ };
9
+ describe('mapGoogleIdentity', () => {
10
+ it('produces a profile row from the GoTrue user', () => {
11
+ const { profile } = mapGoogleIdentity(gotrueUser, { refreshToken: 'rt', scope: 'email profile' });
12
+ expect(profile).toEqual({
13
+ id: '11111111-1111-1111-1111-111111111111',
14
+ email: 'nik@example.com',
15
+ displayName: 'Nik P',
16
+ });
17
+ });
18
+ it('produces a google oauth identity keyed by email as externalId', () => {
19
+ const { identity } = mapGoogleIdentity(gotrueUser, { refreshToken: 'rt', scope: 'email profile' });
20
+ expect(identity.provider).toBe('google');
21
+ expect(identity.kind).toBe('oauth');
22
+ expect(identity.externalId).toBe('nik@example.com');
23
+ expect(identity.userId).toBe(gotrueUser.id);
24
+ expect(identity.scope).toBe('email profile');
25
+ expect(identity.hasSecret).toBe(true);
26
+ });
27
+ it('marks hasSecret false when no refresh token present', () => {
28
+ const { identity } = mapGoogleIdentity(gotrueUser, { refreshToken: null, scope: null });
29
+ expect(identity.hasSecret).toBe(false);
30
+ });
31
+ });
32
+ //# sourceMappingURL=identity-mapper.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity-mapper.test.js","sourceRoot":"","sources":["../../src/pg/identity-mapper.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAuB,MAAM,sBAAsB,CAAC;AAE9E,MAAM,UAAU,GAAmB;IACjC,EAAE,EAAE,sCAAsC;IAC1C,KAAK,EAAE,iBAAiB;IACxB,aAAa,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE;IACpE,YAAY,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE;CACrC,CAAC;AAEF,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;IACjC,EAAE,CAAC,6CAA6C,EAAE,GAAG,EAAE;QACrD,MAAM,EAAE,OAAO,EAAE,GAAG,iBAAiB,CAAC,UAAU,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC,CAAC;QAClG,MAAM,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC;YACtB,EAAE,EAAE,sCAAsC;YAC1C,KAAK,EAAE,iBAAiB;YACxB,WAAW,EAAE,OAAO;SACrB,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+DAA+D,EAAE,GAAG,EAAE;QACvE,MAAM,EAAE,QAAQ,EAAE,GAAG,iBAAiB,CAAC,UAAU,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC,CAAC;QACnG,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACzC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACpC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;QACpD,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QAC5C,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;QAC7C,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC7D,MAAM,EAAE,QAAQ,EAAE,GAAG,iBAAiB,CAAC,UAAU,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACxF,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACzC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,262 @@
1
+ /**
2
+ * Supabase-backed registry of Minion gateway servers.
3
+ * Mirrors Turso `servers`. legacy_server_id preserves the old Turso text PK
4
+ * so Turso log/event rows (which still carry the old id) can join here.
5
+ */
6
+ export declare const gateway: import("drizzle-orm/pg-core").PgTableWithColumns<{
7
+ name: "gateway";
8
+ schema: undefined;
9
+ columns: {
10
+ id: import("drizzle-orm/pg-core").PgColumn<{
11
+ name: "id";
12
+ tableName: "gateway";
13
+ dataType: "string";
14
+ columnType: "PgUUID";
15
+ data: string;
16
+ driverParam: string;
17
+ notNull: true;
18
+ hasDefault: true;
19
+ isPrimaryKey: true;
20
+ isAutoincrement: false;
21
+ hasRuntimeDefault: false;
22
+ enumValues: undefined;
23
+ baseColumn: never;
24
+ identity: undefined;
25
+ generated: undefined;
26
+ }, {}, {}>;
27
+ legacyServerId: import("drizzle-orm/pg-core").PgColumn<{
28
+ name: "legacy_server_id";
29
+ tableName: "gateway";
30
+ dataType: "string";
31
+ columnType: "PgText";
32
+ data: string;
33
+ driverParam: string;
34
+ notNull: false;
35
+ hasDefault: false;
36
+ isPrimaryKey: false;
37
+ isAutoincrement: false;
38
+ hasRuntimeDefault: false;
39
+ enumValues: [string, ...string[]];
40
+ baseColumn: never;
41
+ identity: undefined;
42
+ generated: undefined;
43
+ }, {}, {}>;
44
+ name: import("drizzle-orm/pg-core").PgColumn<{
45
+ name: "name";
46
+ tableName: "gateway";
47
+ dataType: "string";
48
+ columnType: "PgText";
49
+ data: string;
50
+ driverParam: string;
51
+ notNull: true;
52
+ hasDefault: false;
53
+ isPrimaryKey: false;
54
+ isAutoincrement: false;
55
+ hasRuntimeDefault: false;
56
+ enumValues: [string, ...string[]];
57
+ baseColumn: never;
58
+ identity: undefined;
59
+ generated: undefined;
60
+ }, {}, {}>;
61
+ url: import("drizzle-orm/pg-core").PgColumn<{
62
+ name: "url";
63
+ tableName: "gateway";
64
+ dataType: "string";
65
+ columnType: "PgText";
66
+ data: string;
67
+ driverParam: string;
68
+ notNull: true;
69
+ hasDefault: false;
70
+ isPrimaryKey: false;
71
+ isAutoincrement: false;
72
+ hasRuntimeDefault: false;
73
+ enumValues: [string, ...string[]];
74
+ baseColumn: never;
75
+ identity: undefined;
76
+ generated: undefined;
77
+ }, {}, {}>;
78
+ tokenCiphertext: import("drizzle-orm/pg-core").PgColumn<{
79
+ name: "token_ciphertext";
80
+ tableName: "gateway";
81
+ dataType: "string";
82
+ columnType: "PgText";
83
+ data: string;
84
+ driverParam: string;
85
+ notNull: true;
86
+ hasDefault: true;
87
+ isPrimaryKey: false;
88
+ isAutoincrement: false;
89
+ hasRuntimeDefault: false;
90
+ enumValues: [string, ...string[]];
91
+ baseColumn: never;
92
+ identity: undefined;
93
+ generated: undefined;
94
+ }, {}, {}>;
95
+ tokenIv: import("drizzle-orm/pg-core").PgColumn<{
96
+ name: "token_iv";
97
+ tableName: "gateway";
98
+ dataType: "string";
99
+ columnType: "PgText";
100
+ data: string;
101
+ driverParam: string;
102
+ notNull: true;
103
+ hasDefault: true;
104
+ isPrimaryKey: false;
105
+ isAutoincrement: false;
106
+ hasRuntimeDefault: false;
107
+ enumValues: [string, ...string[]];
108
+ baseColumn: never;
109
+ identity: undefined;
110
+ generated: undefined;
111
+ }, {}, {}>;
112
+ authMode: import("drizzle-orm/pg-core").PgColumn<{
113
+ name: "auth_mode";
114
+ tableName: "gateway";
115
+ dataType: "string";
116
+ columnType: "PgText";
117
+ data: "token" | "none";
118
+ driverParam: string;
119
+ notNull: true;
120
+ hasDefault: true;
121
+ isPrimaryKey: false;
122
+ isAutoincrement: false;
123
+ hasRuntimeDefault: false;
124
+ enumValues: ["token", "none"];
125
+ baseColumn: never;
126
+ identity: undefined;
127
+ generated: undefined;
128
+ }, {}, {}>;
129
+ lastConnectedAt: import("drizzle-orm/pg-core").PgColumn<{
130
+ name: "last_connected_at";
131
+ tableName: "gateway";
132
+ dataType: "date";
133
+ columnType: "PgTimestamp";
134
+ data: Date;
135
+ driverParam: string;
136
+ notNull: false;
137
+ hasDefault: false;
138
+ isPrimaryKey: false;
139
+ isAutoincrement: false;
140
+ hasRuntimeDefault: false;
141
+ enumValues: undefined;
142
+ baseColumn: never;
143
+ identity: undefined;
144
+ generated: undefined;
145
+ }, {}, {}>;
146
+ createdAt: import("drizzle-orm/pg-core").PgColumn<{
147
+ name: "created_at";
148
+ tableName: "gateway";
149
+ dataType: "date";
150
+ columnType: "PgTimestamp";
151
+ data: Date;
152
+ driverParam: string;
153
+ notNull: true;
154
+ hasDefault: true;
155
+ isPrimaryKey: false;
156
+ isAutoincrement: false;
157
+ hasRuntimeDefault: false;
158
+ enumValues: undefined;
159
+ baseColumn: never;
160
+ identity: undefined;
161
+ generated: undefined;
162
+ }, {}, {}>;
163
+ updatedAt: import("drizzle-orm/pg-core").PgColumn<{
164
+ name: "updated_at";
165
+ tableName: "gateway";
166
+ dataType: "date";
167
+ columnType: "PgTimestamp";
168
+ data: Date;
169
+ driverParam: string;
170
+ notNull: true;
171
+ hasDefault: true;
172
+ isPrimaryKey: false;
173
+ isAutoincrement: false;
174
+ hasRuntimeDefault: false;
175
+ enumValues: undefined;
176
+ baseColumn: never;
177
+ identity: undefined;
178
+ generated: undefined;
179
+ }, {}, {}>;
180
+ };
181
+ dialect: "pg";
182
+ }>;
183
+ /**
184
+ * Per-user gateway link. Mirrors Turso `user_servers`.
185
+ * profile_id references profiles.id (== auth.users.id).
186
+ */
187
+ export declare const userGateway: import("drizzle-orm/pg-core").PgTableWithColumns<{
188
+ name: "user_gateway";
189
+ schema: undefined;
190
+ columns: {
191
+ profileId: import("drizzle-orm/pg-core").PgColumn<{
192
+ name: "profile_id";
193
+ tableName: "user_gateway";
194
+ dataType: "string";
195
+ columnType: "PgUUID";
196
+ data: string;
197
+ driverParam: string;
198
+ notNull: true;
199
+ hasDefault: false;
200
+ isPrimaryKey: false;
201
+ isAutoincrement: false;
202
+ hasRuntimeDefault: false;
203
+ enumValues: undefined;
204
+ baseColumn: never;
205
+ identity: undefined;
206
+ generated: undefined;
207
+ }, {}, {}>;
208
+ gatewayId: import("drizzle-orm/pg-core").PgColumn<{
209
+ name: "gateway_id";
210
+ tableName: "user_gateway";
211
+ dataType: "string";
212
+ columnType: "PgUUID";
213
+ data: string;
214
+ driverParam: string;
215
+ notNull: true;
216
+ hasDefault: false;
217
+ isPrimaryKey: false;
218
+ isAutoincrement: false;
219
+ hasRuntimeDefault: false;
220
+ enumValues: undefined;
221
+ baseColumn: never;
222
+ identity: undefined;
223
+ generated: undefined;
224
+ }, {}, {}>;
225
+ isDefault: import("drizzle-orm/pg-core").PgColumn<{
226
+ name: "is_default";
227
+ tableName: "user_gateway";
228
+ dataType: "boolean";
229
+ columnType: "PgBoolean";
230
+ data: boolean;
231
+ driverParam: boolean;
232
+ notNull: true;
233
+ hasDefault: true;
234
+ isPrimaryKey: false;
235
+ isAutoincrement: false;
236
+ hasRuntimeDefault: false;
237
+ enumValues: undefined;
238
+ baseColumn: never;
239
+ identity: undefined;
240
+ generated: undefined;
241
+ }, {}, {}>;
242
+ createdAt: import("drizzle-orm/pg-core").PgColumn<{
243
+ name: "created_at";
244
+ tableName: "user_gateway";
245
+ dataType: "date";
246
+ columnType: "PgTimestamp";
247
+ data: Date;
248
+ driverParam: string;
249
+ notNull: true;
250
+ hasDefault: true;
251
+ isPrimaryKey: false;
252
+ isAutoincrement: false;
253
+ hasRuntimeDefault: false;
254
+ enumValues: undefined;
255
+ baseColumn: never;
256
+ identity: undefined;
257
+ generated: undefined;
258
+ }, {}, {}>;
259
+ };
260
+ dialect: "pg";
261
+ }>;
262
+ //# sourceMappingURL=gateway.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gateway.d.ts","sourceRoot":"","sources":["../../../src/pg/schema/gateway.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAclB,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAQtB,CAAC"}
@@ -0,0 +1,36 @@
1
+ import { pgTable, uuid, text, boolean, timestamp, primaryKey, index, uniqueIndex } from 'drizzle-orm/pg-core';
2
+ import { profiles } from './profiles.js';
3
+ /**
4
+ * Supabase-backed registry of Minion gateway servers.
5
+ * Mirrors Turso `servers`. legacy_server_id preserves the old Turso text PK
6
+ * so Turso log/event rows (which still carry the old id) can join here.
7
+ */
8
+ export const gateway = pgTable('gateway', {
9
+ id: uuid('id').primaryKey().defaultRandom(),
10
+ legacyServerId: text('legacy_server_id'),
11
+ name: text('name').notNull(),
12
+ url: text('url').notNull(),
13
+ tokenCiphertext: text('token_ciphertext').notNull().default(''),
14
+ tokenIv: text('token_iv').notNull().default(''),
15
+ authMode: text('auth_mode', { enum: ['token', 'none'] }).notNull().default('token'),
16
+ lastConnectedAt: timestamp('last_connected_at', { withTimezone: true }),
17
+ createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
18
+ updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
19
+ }, (t) => [
20
+ uniqueIndex('gateway_uniq_url').on(t.url),
21
+ index('idx_gateway_legacy').on(t.legacyServerId),
22
+ ]);
23
+ /**
24
+ * Per-user gateway link. Mirrors Turso `user_servers`.
25
+ * profile_id references profiles.id (== auth.users.id).
26
+ */
27
+ export const userGateway = pgTable('user_gateway', {
28
+ profileId: uuid('profile_id').notNull().references(() => profiles.id, { onDelete: 'cascade' }),
29
+ gatewayId: uuid('gateway_id').notNull().references(() => gateway.id, { onDelete: 'cascade' }),
30
+ isDefault: boolean('is_default').notNull().default(false),
31
+ createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
32
+ }, (t) => [
33
+ primaryKey({ columns: [t.profileId, t.gatewayId] }),
34
+ index('idx_user_gateway_gateway').on(t.gatewayId),
35
+ ]);
36
+ //# sourceMappingURL=gateway.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gateway.js","sourceRoot":"","sources":["../../../src/pg/schema/gateway.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAC9G,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC;;;;GAIG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC,SAAS,EAAE;IACxC,EAAE,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,CAAC,aAAa,EAAE;IAC3C,cAAc,EAAE,IAAI,CAAC,kBAAkB,CAAC;IACxC,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE;IAC5B,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE;IAC1B,eAAe,EAAE,IAAI,CAAC,kBAAkB,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;IAC/D,OAAO,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;IAC/C,QAAQ,EAAE,IAAI,CAAC,WAAW,EAAE,EAAE,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC;IACnF,eAAe,EAAE,SAAS,CAAC,mBAAmB,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC;IACvE,SAAS,EAAE,SAAS,CAAC,YAAY,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,UAAU,EAAE;IACjF,SAAS,EAAE,SAAS,CAAC,YAAY,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,UAAU,EAAE;CAClF,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC;IACR,WAAW,CAAC,kBAAkB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC;IACzC,KAAK,CAAC,oBAAoB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,cAAc,CAAC;CACjD,CAAC,CAAC;AAEH;;;GAGG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,OAAO,CAAC,cAAc,EAAE;IACjD,SAAS,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC,OAAO,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;IAC9F,SAAS,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC,OAAO,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;IAC7F,SAAS,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;IACzD,SAAS,EAAE,SAAS,CAAC,YAAY,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,UAAU,EAAE;CAClF,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC;IACR,UAAU,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,SAAS,CAAC,EAAE,CAAC;IACnD,KAAK,CAAC,0BAA0B,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;CAClD,CAAC,CAAC"}
@@ -0,0 +1,8 @@
1
+ export { profiles } from './profiles.js';
2
+ export { userIdentities } from './user-identities.js';
3
+ export { joinRequest, joinLink } from './join.js';
4
+ export { mapGoogleIdentity } from '../identity-mapper.js';
5
+ export type { GoTrueUserLike, GoogleGrant, MappedProfile, MappedIdentity, } from '../identity-mapper.js';
6
+ export { sealSecret, openSecret } from '../crypto.js';
7
+ export { gateway, userGateway } from './gateway.js';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/pg/schema/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAGlD,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,YAAY,EACV,cAAc,EACd,WAAW,EACX,aAAa,EACb,cAAc,GACf,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,8 @@
1
+ export { profiles } from './profiles.js';
2
+ export { userIdentities } from './user-identities.js';
3
+ export { joinRequest, joinLink } from './join.js';
4
+ // Identity helpers (consumed by minion_site auth path)
5
+ export { mapGoogleIdentity } from '../identity-mapper.js';
6
+ export { sealSecret, openSecret } from '../crypto.js';
7
+ export { gateway, userGateway } from './gateway.js';
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/pg/schema/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAElD,uDAAuD;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAO1D,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC"}