Skip to content

Commit aa555c3

Browse files
committed
feature: add cloud adapter
1 parent 3843f00 commit aa555c3

18 files changed

Lines changed: 2219 additions & 2419 deletions

docs/superpowers/plans/2026-03-16-cloud-code-adapter.md

Lines changed: 0 additions & 2020 deletions
This file was deleted.

docs/superpowers/specs/2026-03-16-cloud-code-adapter-design.md

Lines changed: 0 additions & 379 deletions
This file was deleted.
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
'use strict';
2+
3+
const { CloudCodeManager } = require('../lib/cloud-code/CloudCodeManager');
4+
const { InProcessAdapter } = require('../lib/cloud-code/adapters/InProcessAdapter');
5+
6+
describe('Cloud Code Adapter Integration', () => {
7+
describe('composable adapters', () => {
8+
it('supports multiple adapters registering different hooks', async () => {
9+
const manager = new CloudCodeManager();
10+
11+
const inProcessCloud = {
12+
getRouter() {
13+
return {
14+
getManifest() {
15+
return {
16+
protocol: 'ParseCloud/1.0',
17+
hooks: {
18+
functions: [{ name: 'inProcessFn' }],
19+
triggers: [],
20+
jobs: [],
21+
},
22+
};
23+
},
24+
async dispatchFunction() { return { success: 'from-in-process' }; },
25+
async dispatchTrigger() { return { success: {} }; },
26+
async dispatchJob() { return { success: null }; },
27+
};
28+
},
29+
};
30+
31+
const inProcessAdapter = new InProcessAdapter(inProcessCloud);
32+
const inProcessRegistry = manager.createRegistry(inProcessAdapter.name);
33+
await inProcessAdapter.initialize(inProcessRegistry, { appId: 'test', masterKey: 'mk', serverURL: 'http://localhost' });
34+
35+
// Simulate legacy registration via manager directly
36+
manager.defineFunction('legacyFn', () => 'from-legacy', 'legacy');
37+
38+
const legacyEntry = manager.getFunction('legacyFn');
39+
expect(legacyEntry).toBeDefined();
40+
expect(manager.getFunction('inProcessFn')).toBeDefined();
41+
expect(manager.getFunctionNames().sort()).toEqual(['inProcessFn', 'legacyFn']);
42+
});
43+
44+
it('throws on conflict between adapters', async () => {
45+
const manager = new CloudCodeManager();
46+
47+
// Register a function from "legacy" source
48+
manager.defineFunction('shared', () => 'from-legacy', 'legacy');
49+
50+
// InProcess adapter tries to register same function
51+
const inProcessCloud = {
52+
getRouter() {
53+
return {
54+
getManifest() {
55+
return {
56+
protocol: 'ParseCloud/1.0',
57+
hooks: { functions: [{ name: 'shared' }], triggers: [], jobs: [] },
58+
};
59+
},
60+
async dispatchFunction() { return { success: 'from-in-process' }; },
61+
async dispatchTrigger() { return { success: {} }; },
62+
async dispatchJob() { return { success: null }; },
63+
};
64+
},
65+
};
66+
67+
const adapter = new InProcessAdapter(inProcessCloud);
68+
const registry = manager.createRegistry(adapter.name);
69+
70+
await expectAsync(
71+
adapter.initialize(registry, { appId: 'test', masterKey: 'mk', serverURL: 'http://localhost' })
72+
).toBeRejectedWithError(/already registered/);
73+
});
74+
});
75+
76+
describe('shutdown', () => {
77+
it('shuts down all adapters', async () => {
78+
const manager = new CloudCodeManager();
79+
let shutdownCalled = false;
80+
81+
const adapter = {
82+
name: 'test',
83+
async initialize() {},
84+
async isHealthy() { return true; },
85+
async shutdown() { shutdownCalled = true; },
86+
};
87+
88+
await manager.initialize([adapter], { appId: 'test', masterKey: 'mk', serverURL: 'http://localhost' });
89+
await manager.shutdown();
90+
91+
expect(shutdownCalled).toBe(true);
92+
});
93+
});
94+
95+
describe('unregisterAll', () => {
96+
it('allows re-registration after unregisterAll', () => {
97+
const manager = new CloudCodeManager();
98+
manager.defineFunction('fn', () => 'first', 'adapter-a');
99+
manager.unregisterAll('adapter-a');
100+
101+
expect(() => {
102+
manager.defineFunction('fn', () => 'second', 'adapter-b');
103+
}).not.toThrow();
104+
expect(manager.getFunction('fn')).toBeDefined();
105+
});
106+
});
107+
});

0 commit comments

Comments
 (0)