workos 0.11.1 → 0.12.0-beta.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.
- package/README.md +163 -6
- package/dist/bin.js +22 -2
- package/dist/bin.js.map +1 -1
- package/dist/check-coverage.ts +237 -0
- package/dist/commands/dev.d.ts +23 -0
- package/dist/commands/dev.js +139 -0
- package/dist/commands/dev.js.map +1 -0
- package/dist/commands/emulate.d.ts +6 -0
- package/dist/commands/emulate.js +64 -0
- package/dist/commands/emulate.js.map +1 -0
- package/dist/commands/seed.d.ts +2 -0
- package/dist/commands/seed.js +60 -1
- package/dist/commands/seed.js.map +1 -1
- package/dist/emulate/core/id.d.ts +33 -0
- package/dist/emulate/core/id.js +58 -0
- package/dist/emulate/core/id.js.map +1 -0
- package/dist/emulate/core/index.d.ts +8 -0
- package/dist/emulate/core/index.js +8 -0
- package/dist/emulate/core/index.js.map +1 -0
- package/dist/emulate/core/jwt.d.ts +28 -0
- package/dist/emulate/core/jwt.js +78 -0
- package/dist/emulate/core/jwt.js.map +1 -0
- package/dist/emulate/core/middleware/auth.d.ts +18 -0
- package/dist/emulate/core/middleware/auth.js +28 -0
- package/dist/emulate/core/middleware/auth.js.map +1 -0
- package/dist/emulate/core/middleware/error-handler.d.ts +22 -0
- package/dist/emulate/core/middleware/error-handler.js +72 -0
- package/dist/emulate/core/middleware/error-handler.js.map +1 -0
- package/dist/emulate/core/pagination.d.ts +21 -0
- package/dist/emulate/core/pagination.js +35 -0
- package/dist/emulate/core/pagination.js.map +1 -0
- package/dist/emulate/core/plugin.d.ts +15 -0
- package/dist/emulate/core/plugin.js +2 -0
- package/dist/emulate/core/plugin.js.map +1 -0
- package/dist/emulate/core/server.d.ts +17 -0
- package/dist/emulate/core/server.js +116 -0
- package/dist/emulate/core/server.js.map +1 -0
- package/dist/emulate/core/store.d.ts +42 -0
- package/dist/emulate/core/store.js +148 -0
- package/dist/emulate/core/store.js.map +1 -0
- package/dist/emulate/index.d.ts +25 -0
- package/dist/emulate/index.js +47 -0
- package/dist/emulate/index.js.map +1 -0
- package/dist/emulate/workos/entities.d.ts +360 -0
- package/dist/emulate/workos/entities.js +2 -0
- package/dist/emulate/workos/entities.js.map +1 -0
- package/dist/emulate/workos/event-bus.d.ts +12 -0
- package/dist/emulate/workos/event-bus.js +45 -0
- package/dist/emulate/workos/event-bus.js.map +1 -0
- package/dist/emulate/workos/helpers.d.ts +63 -0
- package/dist/emulate/workos/helpers.js +518 -0
- package/dist/emulate/workos/helpers.js.map +1 -0
- package/dist/emulate/workos/index.d.ts +91 -0
- package/dist/emulate/workos/index.js +319 -0
- package/dist/emulate/workos/index.js.map +1 -0
- package/dist/emulate/workos/routes/api-keys.d.ts +2 -0
- package/dist/emulate/workos/routes/api-keys.js +35 -0
- package/dist/emulate/workos/routes/api-keys.js.map +1 -0
- package/dist/emulate/workos/routes/audit-logs.d.ts +2 -0
- package/dist/emulate/workos/routes/audit-logs.js +107 -0
- package/dist/emulate/workos/routes/audit-logs.js.map +1 -0
- package/dist/emulate/workos/routes/auth-challenges.d.ts +2 -0
- package/dist/emulate/workos/routes/auth-challenges.js +51 -0
- package/dist/emulate/workos/routes/auth-challenges.js.map +1 -0
- package/dist/emulate/workos/routes/auth-factors.d.ts +2 -0
- package/dist/emulate/workos/routes/auth-factors.js +51 -0
- package/dist/emulate/workos/routes/auth-factors.js.map +1 -0
- package/dist/emulate/workos/routes/auth.d.ts +2 -0
- package/dist/emulate/workos/routes/auth.js +349 -0
- package/dist/emulate/workos/routes/auth.js.map +1 -0
- package/dist/emulate/workos/routes/authorization-checks.d.ts +10 -0
- package/dist/emulate/workos/routes/authorization-checks.js +135 -0
- package/dist/emulate/workos/routes/authorization-checks.js.map +1 -0
- package/dist/emulate/workos/routes/authorization-org-roles.d.ts +2 -0
- package/dist/emulate/workos/routes/authorization-org-roles.js +206 -0
- package/dist/emulate/workos/routes/authorization-org-roles.js.map +1 -0
- package/dist/emulate/workos/routes/authorization-permissions.d.ts +2 -0
- package/dist/emulate/workos/routes/authorization-permissions.js +78 -0
- package/dist/emulate/workos/routes/authorization-permissions.js.map +1 -0
- package/dist/emulate/workos/routes/authorization-resources.d.ts +2 -0
- package/dist/emulate/workos/routes/authorization-resources.js +128 -0
- package/dist/emulate/workos/routes/authorization-resources.js.map +1 -0
- package/dist/emulate/workos/routes/authorization-roles.d.ts +2 -0
- package/dist/emulate/workos/routes/authorization-roles.js +136 -0
- package/dist/emulate/workos/routes/authorization-roles.js.map +1 -0
- package/dist/emulate/workos/routes/config.d.ts +2 -0
- package/dist/emulate/workos/routes/config.js +56 -0
- package/dist/emulate/workos/routes/config.js.map +1 -0
- package/dist/emulate/workos/routes/connect.d.ts +2 -0
- package/dist/emulate/workos/routes/connect.js +69 -0
- package/dist/emulate/workos/routes/connect.js.map +1 -0
- package/dist/emulate/workos/routes/connections.d.ts +2 -0
- package/dist/emulate/workos/routes/connections.js +77 -0
- package/dist/emulate/workos/routes/connections.js.map +1 -0
- package/dist/emulate/workos/routes/data-integrations.d.ts +2 -0
- package/dist/emulate/workos/routes/data-integrations.js +55 -0
- package/dist/emulate/workos/routes/data-integrations.js.map +1 -0
- package/dist/emulate/workos/routes/directories.d.ts +2 -0
- package/dist/emulate/workos/routes/directories.js +106 -0
- package/dist/emulate/workos/routes/directories.js.map +1 -0
- package/dist/emulate/workos/routes/email-verification.d.ts +2 -0
- package/dist/emulate/workos/routes/email-verification.js +49 -0
- package/dist/emulate/workos/routes/email-verification.js.map +1 -0
- package/dist/emulate/workos/routes/events.d.ts +2 -0
- package/dist/emulate/workos/routes/events.js +21 -0
- package/dist/emulate/workos/routes/events.js.map +1 -0
- package/dist/emulate/workos/routes/feature-flags.d.ts +2 -0
- package/dist/emulate/workos/routes/feature-flags.js +131 -0
- package/dist/emulate/workos/routes/feature-flags.js.map +1 -0
- package/dist/emulate/workos/routes/invitations.d.ts +2 -0
- package/dist/emulate/workos/routes/invitations.js +125 -0
- package/dist/emulate/workos/routes/invitations.js.map +1 -0
- package/dist/emulate/workos/routes/legacy-mfa.d.ts +2 -0
- package/dist/emulate/workos/routes/legacy-mfa.js +75 -0
- package/dist/emulate/workos/routes/legacy-mfa.js.map +1 -0
- package/dist/emulate/workos/routes/magic-auth.d.ts +2 -0
- package/dist/emulate/workos/routes/magic-auth.js +32 -0
- package/dist/emulate/workos/routes/magic-auth.js.map +1 -0
- package/dist/emulate/workos/routes/memberships.d.ts +2 -0
- package/dist/emulate/workos/routes/memberships.js +118 -0
- package/dist/emulate/workos/routes/memberships.js.map +1 -0
- package/dist/emulate/workos/routes/organization-domains.d.ts +2 -0
- package/dist/emulate/workos/routes/organization-domains.js +58 -0
- package/dist/emulate/workos/routes/organization-domains.js.map +1 -0
- package/dist/emulate/workos/routes/organizations.d.ts +2 -0
- package/dist/emulate/workos/routes/organizations.js +133 -0
- package/dist/emulate/workos/routes/organizations.js.map +1 -0
- package/dist/emulate/workos/routes/password-reset.d.ts +2 -0
- package/dist/emulate/workos/routes/password-reset.js +61 -0
- package/dist/emulate/workos/routes/password-reset.js.map +1 -0
- package/dist/emulate/workos/routes/pipes.d.ts +2 -0
- package/dist/emulate/workos/routes/pipes.js +86 -0
- package/dist/emulate/workos/routes/pipes.js.map +1 -0
- package/dist/emulate/workos/routes/portal.d.ts +2 -0
- package/dist/emulate/workos/routes/portal.js +18 -0
- package/dist/emulate/workos/routes/portal.js.map +1 -0
- package/dist/emulate/workos/routes/radar.d.ts +2 -0
- package/dist/emulate/workos/routes/radar.js +45 -0
- package/dist/emulate/workos/routes/radar.js.map +1 -0
- package/dist/emulate/workos/routes/sessions.d.ts +2 -0
- package/dist/emulate/workos/routes/sessions.js +51 -0
- package/dist/emulate/workos/routes/sessions.js.map +1 -0
- package/dist/emulate/workos/routes/sso.d.ts +2 -0
- package/dist/emulate/workos/routes/sso.js +160 -0
- package/dist/emulate/workos/routes/sso.js.map +1 -0
- package/dist/emulate/workos/routes/user-features.d.ts +2 -0
- package/dist/emulate/workos/routes/user-features.js +50 -0
- package/dist/emulate/workos/routes/user-features.js.map +1 -0
- package/dist/emulate/workos/routes/users.d.ts +2 -0
- package/dist/emulate/workos/routes/users.js +133 -0
- package/dist/emulate/workos/routes/users.js.map +1 -0
- package/dist/emulate/workos/routes/webhook-endpoints.d.ts +2 -0
- package/dist/emulate/workos/routes/webhook-endpoints.js +70 -0
- package/dist/emulate/workos/routes/webhook-endpoints.js.map +1 -0
- package/dist/emulate/workos/routes/widgets.d.ts +2 -0
- package/dist/emulate/workos/routes/widgets.js +27 -0
- package/dist/emulate/workos/routes/widgets.js.map +1 -0
- package/dist/emulate/workos/store.d.ts +48 -0
- package/dist/emulate/workos/store.js +93 -0
- package/dist/emulate/workos/store.js.map +1 -0
- package/dist/emulate/workos/webhook-signer.d.ts +1 -0
- package/dist/emulate/workos/webhook-signer.js +8 -0
- package/dist/emulate/workos/webhook-signer.js.map +1 -0
- package/dist/gen-routes-lib.spec.ts +659 -0
- package/dist/gen-routes-lib.ts +647 -0
- package/dist/gen-routes.ts +96 -0
- package/dist/lib/dev-command.d.ts +26 -0
- package/dist/lib/dev-command.js +122 -0
- package/dist/lib/dev-command.js.map +1 -0
- package/dist/utils/help-json.js +31 -0
- package/dist/utils/help-json.js.map +1 -1
- package/package.json +20 -7
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
import { cors } from 'hono/cors';
|
|
3
|
+
import { Store } from './store.js';
|
|
4
|
+
import { JWTManager } from './jwt.js';
|
|
5
|
+
import { createApiErrorHandler, requestIdMiddleware } from './middleware/error-handler.js';
|
|
6
|
+
import { authMiddleware } from './middleware/auth.js';
|
|
7
|
+
export function createServer(plugin, options = {}) {
|
|
8
|
+
const port = options.port ?? 4100;
|
|
9
|
+
const baseUrl = options.baseUrl ?? `http://localhost:${port}`;
|
|
10
|
+
const app = new Hono();
|
|
11
|
+
const store = new Store();
|
|
12
|
+
const jwt = new JWTManager(baseUrl);
|
|
13
|
+
const apiKeys = options.apiKeys ?? {
|
|
14
|
+
sk_test_default: { environment: 'test' },
|
|
15
|
+
};
|
|
16
|
+
app.onError(createApiErrorHandler());
|
|
17
|
+
app.use('*', cors());
|
|
18
|
+
app.use('*', requestIdMiddleware());
|
|
19
|
+
// JWKS endpoint (public, no auth)
|
|
20
|
+
app.get('/sso/jwks/:client_id', (c) => {
|
|
21
|
+
return c.json(jwt.getJWKS());
|
|
22
|
+
});
|
|
23
|
+
// Auth middleware for API routes
|
|
24
|
+
app.use('/api/*', authMiddleware(apiKeys));
|
|
25
|
+
app.use('/user_management/*', async (c, next) => {
|
|
26
|
+
const path = new URL(c.req.url).pathname;
|
|
27
|
+
// Public endpoints (no auth required)
|
|
28
|
+
if (path === '/user_management/authorize' ||
|
|
29
|
+
path === '/user_management/authenticate' ||
|
|
30
|
+
path === '/user_management/sessions/logout' ||
|
|
31
|
+
path.startsWith('/user_management/sessions/jwks/')) {
|
|
32
|
+
return next();
|
|
33
|
+
}
|
|
34
|
+
return authMiddleware(apiKeys)(c, next);
|
|
35
|
+
});
|
|
36
|
+
app.use('/x/authkit/*', authMiddleware(apiKeys));
|
|
37
|
+
app.use('/organizations', authMiddleware(apiKeys));
|
|
38
|
+
app.use('/organizations/*', authMiddleware(apiKeys));
|
|
39
|
+
app.use('/organization_memberships', authMiddleware(apiKeys));
|
|
40
|
+
app.use('/organization_memberships/*', authMiddleware(apiKeys));
|
|
41
|
+
app.use('/organization_domains', authMiddleware(apiKeys));
|
|
42
|
+
app.use('/organization_domains/*', authMiddleware(apiKeys));
|
|
43
|
+
app.use('/connections', authMiddleware(apiKeys));
|
|
44
|
+
app.use('/connections/*', authMiddleware(apiKeys));
|
|
45
|
+
app.use('/directories', authMiddleware(apiKeys));
|
|
46
|
+
app.use('/directories/*', authMiddleware(apiKeys));
|
|
47
|
+
app.use('/directory_groups', authMiddleware(apiKeys));
|
|
48
|
+
app.use('/directory_groups/*', authMiddleware(apiKeys));
|
|
49
|
+
app.use('/directory_users', authMiddleware(apiKeys));
|
|
50
|
+
app.use('/directory_users/*', authMiddleware(apiKeys));
|
|
51
|
+
app.use('/events', authMiddleware(apiKeys));
|
|
52
|
+
app.use('/events/*', authMiddleware(apiKeys));
|
|
53
|
+
app.use('/pipes/*', authMiddleware(apiKeys));
|
|
54
|
+
app.use('/audit_logs/*', authMiddleware(apiKeys));
|
|
55
|
+
app.use('/feature-flags', authMiddleware(apiKeys));
|
|
56
|
+
app.use('/feature-flags/*', authMiddleware(apiKeys));
|
|
57
|
+
app.use('/connect/*', authMiddleware(apiKeys));
|
|
58
|
+
app.use('/data-integrations/*', async (c, next) => {
|
|
59
|
+
const path = new URL(c.req.url).pathname;
|
|
60
|
+
if (path.endsWith('/authorize'))
|
|
61
|
+
return next();
|
|
62
|
+
return authMiddleware(apiKeys)(c, next);
|
|
63
|
+
});
|
|
64
|
+
app.use('/radar/*', authMiddleware(apiKeys));
|
|
65
|
+
app.use('/api_keys', authMiddleware(apiKeys));
|
|
66
|
+
app.use('/api_keys/*', authMiddleware(apiKeys));
|
|
67
|
+
app.use('/portal/*', authMiddleware(apiKeys));
|
|
68
|
+
app.use('/webhook_endpoints', authMiddleware(apiKeys));
|
|
69
|
+
app.use('/webhook_endpoints/*', authMiddleware(apiKeys));
|
|
70
|
+
app.use('/auth/factors', authMiddleware(apiKeys));
|
|
71
|
+
app.use('/auth/factors/*', authMiddleware(apiKeys));
|
|
72
|
+
app.use('/auth/challenges/*', authMiddleware(apiKeys));
|
|
73
|
+
// Rate limiting
|
|
74
|
+
const rateLimitCounters = new Map();
|
|
75
|
+
let lastPruneAt = Math.floor(Date.now() / 1000);
|
|
76
|
+
app.use('*', async (c, next) => {
|
|
77
|
+
const auth = c.get('auth');
|
|
78
|
+
const key = auth?.apiKey ?? '__anonymous__';
|
|
79
|
+
const now = Math.floor(Date.now() / 1000);
|
|
80
|
+
if (now - lastPruneAt > 3600) {
|
|
81
|
+
for (const [k, val] of rateLimitCounters) {
|
|
82
|
+
if (val.resetAt <= now)
|
|
83
|
+
rateLimitCounters.delete(k);
|
|
84
|
+
}
|
|
85
|
+
lastPruneAt = now;
|
|
86
|
+
}
|
|
87
|
+
let counter = rateLimitCounters.get(key);
|
|
88
|
+
if (!counter || counter.resetAt <= now) {
|
|
89
|
+
counter = { remaining: 1000, resetAt: now + 60 };
|
|
90
|
+
rateLimitCounters.set(key, counter);
|
|
91
|
+
}
|
|
92
|
+
counter.remaining = Math.max(0, counter.remaining - 1);
|
|
93
|
+
c.header('X-RateLimit-Limit', '1000');
|
|
94
|
+
c.header('X-RateLimit-Remaining', String(counter.remaining));
|
|
95
|
+
c.header('X-RateLimit-Reset', String(counter.resetAt));
|
|
96
|
+
if (counter.remaining === 0) {
|
|
97
|
+
c.header('Retry-After', String(counter.resetAt - now));
|
|
98
|
+
return c.json({
|
|
99
|
+
message: 'Too Many Requests',
|
|
100
|
+
code: 'rate_limit_exceeded',
|
|
101
|
+
}, 429);
|
|
102
|
+
}
|
|
103
|
+
await next();
|
|
104
|
+
});
|
|
105
|
+
// Store API key map for route access
|
|
106
|
+
store.setData('apiKeyMap', apiKeys);
|
|
107
|
+
// Register plugin routes
|
|
108
|
+
plugin.register({ app, store, jwt, baseUrl });
|
|
109
|
+
// Not found handler
|
|
110
|
+
app.notFound((c) => c.json({
|
|
111
|
+
message: 'Not Found',
|
|
112
|
+
code: 'not_found',
|
|
113
|
+
}, 404));
|
|
114
|
+
return { app, store, jwt, port, baseUrl };
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../../src/emulate/core/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AAC3F,OAAO,EAAE,cAAc,EAAqC,MAAM,sBAAsB,CAAC;AASzF,MAAM,UAAU,YAAY,CAAC,MAAqB,EAAE,UAAyB,EAAE;IAC7E,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,IAAI,CAAC;IAClC,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,oBAAoB,IAAI,EAAE,CAAC;IAE9D,MAAM,GAAG,GAAG,IAAI,IAAI,EAAgB,CAAC;IACrC,MAAM,KAAK,GAAG,IAAI,KAAK,EAAE,CAAC;IAC1B,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,OAAO,CAAC,CAAC;IAEpC,MAAM,OAAO,GAAc,OAAO,CAAC,OAAO,IAAI;QAC5C,eAAe,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE;KACzC,CAAC;IAEF,GAAG,CAAC,OAAO,CAAC,qBAAqB,EAAE,CAAC,CAAC;IACrC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;IACrB,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,mBAAmB,EAAE,CAAC,CAAC;IAEpC,kCAAkC;IAClC,GAAG,CAAC,GAAG,CAAC,sBAAsB,EAAE,CAAC,CAAC,EAAE,EAAE;QACpC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;IAC/B,CAAC,CAAC,CAAC;IAEH,iCAAiC;IACjC,GAAG,CAAC,GAAG,CAAC,QAAQ,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IAC3C,GAAG,CAAC,GAAG,CAAC,oBAAoB,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE;QAC9C,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC;QACzC,sCAAsC;QACtC,IACE,IAAI,KAAK,4BAA4B;YACrC,IAAI,KAAK,+BAA+B;YACxC,IAAI,KAAK,kCAAkC;YAC3C,IAAI,CAAC,UAAU,CAAC,iCAAiC,CAAC,EAClD,CAAC;YACD,OAAO,IAAI,EAAE,CAAC;QAChB,CAAC;QACD,OAAO,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;IACH,GAAG,CAAC,GAAG,CAAC,cAAc,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IACjD,GAAG,CAAC,GAAG,CAAC,gBAAgB,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IACnD,GAAG,CAAC,GAAG,CAAC,kBAAkB,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IACrD,GAAG,CAAC,GAAG,CAAC,2BAA2B,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IAC9D,GAAG,CAAC,GAAG,CAAC,6BAA6B,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IAChE,GAAG,CAAC,GAAG,CAAC,uBAAuB,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IAC1D,GAAG,CAAC,GAAG,CAAC,yBAAyB,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IAC5D,GAAG,CAAC,GAAG,CAAC,cAAc,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IACjD,GAAG,CAAC,GAAG,CAAC,gBAAgB,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IACnD,GAAG,CAAC,GAAG,CAAC,cAAc,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IACjD,GAAG,CAAC,GAAG,CAAC,gBAAgB,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IACnD,GAAG,CAAC,GAAG,CAAC,mBAAmB,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IACtD,GAAG,CAAC,GAAG,CAAC,qBAAqB,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IACxD,GAAG,CAAC,GAAG,CAAC,kBAAkB,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IACrD,GAAG,CAAC,GAAG,CAAC,oBAAoB,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IACvD,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IAC5C,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IAC9C,GAAG,CAAC,GAAG,CAAC,UAAU,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IAC7C,GAAG,CAAC,GAAG,CAAC,eAAe,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IAClD,GAAG,CAAC,GAAG,CAAC,gBAAgB,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IACnD,GAAG,CAAC,GAAG,CAAC,kBAAkB,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IACrD,GAAG,CAAC,GAAG,CAAC,YAAY,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IAC/C,GAAG,CAAC,GAAG,CAAC,sBAAsB,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE;QAChD,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC;QACzC,IAAI,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC;YAAE,OAAO,IAAI,EAAE,CAAC;QAC/C,OAAO,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;IACH,GAAG,CAAC,GAAG,CAAC,UAAU,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IAC7C,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IAC9C,GAAG,CAAC,GAAG,CAAC,aAAa,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IAChD,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IAC9C,GAAG,CAAC,GAAG,CAAC,oBAAoB,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IACvD,GAAG,CAAC,GAAG,CAAC,sBAAsB,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IACzD,GAAG,CAAC,GAAG,CAAC,eAAe,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IAClD,GAAG,CAAC,GAAG,CAAC,iBAAiB,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IACpD,GAAG,CAAC,GAAG,CAAC,oBAAoB,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IAEvD,gBAAgB;IAChB,MAAM,iBAAiB,GAAG,IAAI,GAAG,EAAkD,CAAC;IACpF,IAAI,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IAEhD,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE;QAC7B,MAAM,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAC3B,MAAM,GAAG,GAAG,IAAI,EAAE,MAAM,IAAI,eAAe,CAAC;QAC5C,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QAE1C,IAAI,GAAG,GAAG,WAAW,GAAG,IAAI,EAAE,CAAC;YAC7B,KAAK,MAAM,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,iBAAiB,EAAE,CAAC;gBACzC,IAAI,GAAG,CAAC,OAAO,IAAI,GAAG;oBAAE,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;YACtD,CAAC;YACD,WAAW,GAAG,GAAG,CAAC;QACpB,CAAC;QAED,IAAI,OAAO,GAAG,iBAAiB,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACzC,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,OAAO,IAAI,GAAG,EAAE,CAAC;YACvC,OAAO,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,GAAG,EAAE,EAAE,CAAC;YACjD,iBAAiB,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QACtC,CAAC;QAED,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC;QAEvD,CAAC,CAAC,MAAM,CAAC,mBAAmB,EAAE,MAAM,CAAC,CAAC;QACtC,CAAC,CAAC,MAAM,CAAC,uBAAuB,EAAE,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC;QAC7D,CAAC,CAAC,MAAM,CAAC,mBAAmB,EAAE,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC;QAEvD,IAAI,OAAO,CAAC,SAAS,KAAK,CAAC,EAAE,CAAC;YAC5B,CAAC,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,OAAO,CAAC,OAAO,GAAG,GAAG,CAAC,CAAC,CAAC;YACvD,OAAO,CAAC,CAAC,IAAI,CACX;gBACE,OAAO,EAAE,mBAAmB;gBAC5B,IAAI,EAAE,qBAAqB;aAC5B,EACD,GAAG,CACJ,CAAC;QACJ,CAAC;QAED,MAAM,IAAI,EAAE,CAAC;IACf,CAAC,CAAC,CAAC;IAEH,qCAAqC;IACrC,KAAK,CAAC,OAAO,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;IAEpC,yBAAyB;IACzB,MAAM,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC;IAE9C,oBAAoB;IACpB,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAE,CACjB,CAAC,CAAC,IAAI,CACJ;QACE,OAAO,EAAE,WAAW;QACpB,IAAI,EAAE,WAAW;KAClB,EACD,GAAG,CACJ,CACF,CAAC;IAEF,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;AAC5C,CAAC","sourcesContent":["import { Hono } from 'hono';\nimport { cors } from 'hono/cors';\nimport { Store } from './store.js';\nimport { JWTManager } from './jwt.js';\nimport { createApiErrorHandler, requestIdMiddleware } from './middleware/error-handler.js';\nimport { authMiddleware, type ApiKeyMap, type WorkOSAppEnv } from './middleware/auth.js';\nimport type { ServicePlugin } from './plugin.js';\n\nexport interface ServerOptions {\n port?: number;\n baseUrl?: string;\n apiKeys?: ApiKeyMap;\n}\n\nexport function createServer(plugin: ServicePlugin, options: ServerOptions = {}) {\n const port = options.port ?? 4100;\n const baseUrl = options.baseUrl ?? `http://localhost:${port}`;\n\n const app = new Hono<WorkOSAppEnv>();\n const store = new Store();\n const jwt = new JWTManager(baseUrl);\n\n const apiKeys: ApiKeyMap = options.apiKeys ?? {\n sk_test_default: { environment: 'test' },\n };\n\n app.onError(createApiErrorHandler());\n app.use('*', cors());\n app.use('*', requestIdMiddleware());\n\n // JWKS endpoint (public, no auth)\n app.get('/sso/jwks/:client_id', (c) => {\n return c.json(jwt.getJWKS());\n });\n\n // Auth middleware for API routes\n app.use('/api/*', authMiddleware(apiKeys));\n app.use('/user_management/*', async (c, next) => {\n const path = new URL(c.req.url).pathname;\n // Public endpoints (no auth required)\n if (\n path === '/user_management/authorize' ||\n path === '/user_management/authenticate' ||\n path === '/user_management/sessions/logout' ||\n path.startsWith('/user_management/sessions/jwks/')\n ) {\n return next();\n }\n return authMiddleware(apiKeys)(c, next);\n });\n app.use('/x/authkit/*', authMiddleware(apiKeys));\n app.use('/organizations', authMiddleware(apiKeys));\n app.use('/organizations/*', authMiddleware(apiKeys));\n app.use('/organization_memberships', authMiddleware(apiKeys));\n app.use('/organization_memberships/*', authMiddleware(apiKeys));\n app.use('/organization_domains', authMiddleware(apiKeys));\n app.use('/organization_domains/*', authMiddleware(apiKeys));\n app.use('/connections', authMiddleware(apiKeys));\n app.use('/connections/*', authMiddleware(apiKeys));\n app.use('/directories', authMiddleware(apiKeys));\n app.use('/directories/*', authMiddleware(apiKeys));\n app.use('/directory_groups', authMiddleware(apiKeys));\n app.use('/directory_groups/*', authMiddleware(apiKeys));\n app.use('/directory_users', authMiddleware(apiKeys));\n app.use('/directory_users/*', authMiddleware(apiKeys));\n app.use('/events', authMiddleware(apiKeys));\n app.use('/events/*', authMiddleware(apiKeys));\n app.use('/pipes/*', authMiddleware(apiKeys));\n app.use('/audit_logs/*', authMiddleware(apiKeys));\n app.use('/feature-flags', authMiddleware(apiKeys));\n app.use('/feature-flags/*', authMiddleware(apiKeys));\n app.use('/connect/*', authMiddleware(apiKeys));\n app.use('/data-integrations/*', async (c, next) => {\n const path = new URL(c.req.url).pathname;\n if (path.endsWith('/authorize')) return next();\n return authMiddleware(apiKeys)(c, next);\n });\n app.use('/radar/*', authMiddleware(apiKeys));\n app.use('/api_keys', authMiddleware(apiKeys));\n app.use('/api_keys/*', authMiddleware(apiKeys));\n app.use('/portal/*', authMiddleware(apiKeys));\n app.use('/webhook_endpoints', authMiddleware(apiKeys));\n app.use('/webhook_endpoints/*', authMiddleware(apiKeys));\n app.use('/auth/factors', authMiddleware(apiKeys));\n app.use('/auth/factors/*', authMiddleware(apiKeys));\n app.use('/auth/challenges/*', authMiddleware(apiKeys));\n\n // Rate limiting\n const rateLimitCounters = new Map<string, { remaining: number; resetAt: number }>();\n let lastPruneAt = Math.floor(Date.now() / 1000);\n\n app.use('*', async (c, next) => {\n const auth = c.get('auth');\n const key = auth?.apiKey ?? '__anonymous__';\n const now = Math.floor(Date.now() / 1000);\n\n if (now - lastPruneAt > 3600) {\n for (const [k, val] of rateLimitCounters) {\n if (val.resetAt <= now) rateLimitCounters.delete(k);\n }\n lastPruneAt = now;\n }\n\n let counter = rateLimitCounters.get(key);\n if (!counter || counter.resetAt <= now) {\n counter = { remaining: 1000, resetAt: now + 60 };\n rateLimitCounters.set(key, counter);\n }\n\n counter.remaining = Math.max(0, counter.remaining - 1);\n\n c.header('X-RateLimit-Limit', '1000');\n c.header('X-RateLimit-Remaining', String(counter.remaining));\n c.header('X-RateLimit-Reset', String(counter.resetAt));\n\n if (counter.remaining === 0) {\n c.header('Retry-After', String(counter.resetAt - now));\n return c.json(\n {\n message: 'Too Many Requests',\n code: 'rate_limit_exceeded',\n },\n 429,\n );\n }\n\n await next();\n });\n\n // Store API key map for route access\n store.setData('apiKeyMap', apiKeys);\n\n // Register plugin routes\n plugin.register({ app, store, jwt, baseUrl });\n\n // Not found handler\n app.notFound((c) =>\n c.json(\n {\n message: 'Not Found',\n code: 'not_found',\n },\n 404,\n ),\n );\n\n return { app, store, jwt, port, baseUrl };\n}\n"]}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type Entity, type CursorPaginationOptions, type CursorPaginatedResult } from './pagination.js';
|
|
2
|
+
export type { Entity };
|
|
3
|
+
export type InsertInput<T extends Entity> = Omit<T, 'id' | 'created_at' | 'updated_at'> & {
|
|
4
|
+
id?: string;
|
|
5
|
+
};
|
|
6
|
+
export type FilterFn<T> = (item: T) => boolean;
|
|
7
|
+
export type SortFn<T> = (a: T, b: T) => number;
|
|
8
|
+
export interface CollectionHooks<T extends Entity> {
|
|
9
|
+
onInsert?: (item: T) => void;
|
|
10
|
+
onUpdate?: (item: T) => void;
|
|
11
|
+
onDelete?: (item: T) => void;
|
|
12
|
+
}
|
|
13
|
+
export declare class Collection<T extends Entity> {
|
|
14
|
+
private prefix;
|
|
15
|
+
private indexFields;
|
|
16
|
+
private items;
|
|
17
|
+
private indexes;
|
|
18
|
+
private hooks;
|
|
19
|
+
readonly fieldNames: string[];
|
|
20
|
+
constructor(prefix: string, indexFields?: (keyof T)[]);
|
|
21
|
+
private addToIndex;
|
|
22
|
+
private removeFromIndex;
|
|
23
|
+
insert(data: InsertInput<T>): T;
|
|
24
|
+
get(id: string): T | undefined;
|
|
25
|
+
findBy(field: keyof T, value: string | number): T[];
|
|
26
|
+
findOneBy(field: keyof T, value: string | number): T | undefined;
|
|
27
|
+
update(id: string, data: Partial<T>): T | undefined;
|
|
28
|
+
delete(id: string): boolean;
|
|
29
|
+
setHooks(hooks: CollectionHooks<T>): void;
|
|
30
|
+
all(): T[];
|
|
31
|
+
list(options?: CursorPaginationOptions<T>): CursorPaginatedResult<T>;
|
|
32
|
+
count(filter?: FilterFn<T>): number;
|
|
33
|
+
clear(): void;
|
|
34
|
+
}
|
|
35
|
+
export declare class Store {
|
|
36
|
+
private collections;
|
|
37
|
+
private _data;
|
|
38
|
+
collection<T extends Entity>(name: string, prefix: string, indexFields?: (keyof T)[]): Collection<T>;
|
|
39
|
+
getData<V>(key: string): V | undefined;
|
|
40
|
+
setData<V>(key: string, value: V): void;
|
|
41
|
+
reset(): void;
|
|
42
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { generateId } from './id.js';
|
|
2
|
+
import { cursorPaginate } from './pagination.js';
|
|
3
|
+
export class Collection {
|
|
4
|
+
prefix;
|
|
5
|
+
indexFields;
|
|
6
|
+
items = new Map();
|
|
7
|
+
indexes = new Map();
|
|
8
|
+
hooks = {};
|
|
9
|
+
fieldNames;
|
|
10
|
+
constructor(prefix, indexFields = []) {
|
|
11
|
+
this.prefix = prefix;
|
|
12
|
+
this.indexFields = indexFields;
|
|
13
|
+
this.fieldNames = indexFields.map(String).sort();
|
|
14
|
+
for (const field of indexFields) {
|
|
15
|
+
this.indexes.set(String(field), new Map());
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
addToIndex(item) {
|
|
19
|
+
for (const field of this.indexFields) {
|
|
20
|
+
const value = item[field];
|
|
21
|
+
if (value === undefined || value === null)
|
|
22
|
+
continue;
|
|
23
|
+
const indexMap = this.indexes.get(String(field));
|
|
24
|
+
const key = String(value);
|
|
25
|
+
if (!indexMap.has(key)) {
|
|
26
|
+
indexMap.set(key, new Set());
|
|
27
|
+
}
|
|
28
|
+
indexMap.get(key).add(item.id);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
removeFromIndex(item) {
|
|
32
|
+
for (const field of this.indexFields) {
|
|
33
|
+
const value = item[field];
|
|
34
|
+
if (value === undefined || value === null)
|
|
35
|
+
continue;
|
|
36
|
+
const indexMap = this.indexes.get(String(field));
|
|
37
|
+
const key = String(value);
|
|
38
|
+
indexMap.get(key)?.delete(item.id);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
insert(data) {
|
|
42
|
+
const now = new Date().toISOString();
|
|
43
|
+
const id = data.id ?? generateId(this.prefix);
|
|
44
|
+
const item = {
|
|
45
|
+
...data,
|
|
46
|
+
id,
|
|
47
|
+
created_at: now,
|
|
48
|
+
updated_at: now,
|
|
49
|
+
};
|
|
50
|
+
this.items.set(id, item);
|
|
51
|
+
this.addToIndex(item);
|
|
52
|
+
this.hooks.onInsert?.(item);
|
|
53
|
+
return item;
|
|
54
|
+
}
|
|
55
|
+
get(id) {
|
|
56
|
+
return this.items.get(id);
|
|
57
|
+
}
|
|
58
|
+
findBy(field, value) {
|
|
59
|
+
if (this.indexes.has(String(field))) {
|
|
60
|
+
const ids = this.indexes.get(String(field)).get(String(value));
|
|
61
|
+
if (!ids)
|
|
62
|
+
return [];
|
|
63
|
+
return Array.from(ids)
|
|
64
|
+
.map((id) => this.items.get(id))
|
|
65
|
+
.filter(Boolean);
|
|
66
|
+
}
|
|
67
|
+
return this.all().filter((item) => item[field] === value);
|
|
68
|
+
}
|
|
69
|
+
findOneBy(field, value) {
|
|
70
|
+
return this.findBy(field, value)[0];
|
|
71
|
+
}
|
|
72
|
+
update(id, data) {
|
|
73
|
+
const existing = this.items.get(id);
|
|
74
|
+
if (!existing)
|
|
75
|
+
return undefined;
|
|
76
|
+
this.removeFromIndex(existing);
|
|
77
|
+
const updated = {
|
|
78
|
+
...existing,
|
|
79
|
+
...data,
|
|
80
|
+
id,
|
|
81
|
+
updated_at: new Date().toISOString(),
|
|
82
|
+
};
|
|
83
|
+
this.items.set(id, updated);
|
|
84
|
+
this.addToIndex(updated);
|
|
85
|
+
this.hooks.onUpdate?.(updated);
|
|
86
|
+
return updated;
|
|
87
|
+
}
|
|
88
|
+
delete(id) {
|
|
89
|
+
const existing = this.items.get(id);
|
|
90
|
+
if (!existing)
|
|
91
|
+
return false;
|
|
92
|
+
this.hooks.onDelete?.(existing);
|
|
93
|
+
this.removeFromIndex(existing);
|
|
94
|
+
return this.items.delete(id);
|
|
95
|
+
}
|
|
96
|
+
setHooks(hooks) {
|
|
97
|
+
this.hooks = hooks;
|
|
98
|
+
}
|
|
99
|
+
all() {
|
|
100
|
+
return Array.from(this.items.values());
|
|
101
|
+
}
|
|
102
|
+
list(options = {}) {
|
|
103
|
+
return cursorPaginate(this.all(), options);
|
|
104
|
+
}
|
|
105
|
+
count(filter) {
|
|
106
|
+
if (!filter)
|
|
107
|
+
return this.items.size;
|
|
108
|
+
return this.all().filter(filter).length;
|
|
109
|
+
}
|
|
110
|
+
clear() {
|
|
111
|
+
this.items.clear();
|
|
112
|
+
for (const indexMap of this.indexes.values()) {
|
|
113
|
+
indexMap.clear();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
export class Store {
|
|
118
|
+
collections = new Map();
|
|
119
|
+
_data = new Map();
|
|
120
|
+
collection(name, prefix, indexFields = []) {
|
|
121
|
+
const existing = this.collections.get(name);
|
|
122
|
+
if (existing) {
|
|
123
|
+
if (indexFields.length > 0) {
|
|
124
|
+
const requested = indexFields.map(String).sort();
|
|
125
|
+
if (existing.fieldNames.length !== requested.length || existing.fieldNames.some((f, i) => f !== requested[i])) {
|
|
126
|
+
throw new Error(`Collection "${name}" already exists with indexes [${existing.fieldNames}] but was requested with [${requested}]`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return existing;
|
|
130
|
+
}
|
|
131
|
+
const col = new Collection(prefix, indexFields);
|
|
132
|
+
this.collections.set(name, col);
|
|
133
|
+
return col;
|
|
134
|
+
}
|
|
135
|
+
getData(key) {
|
|
136
|
+
return this._data.get(key);
|
|
137
|
+
}
|
|
138
|
+
setData(key, value) {
|
|
139
|
+
this._data.set(key, value);
|
|
140
|
+
}
|
|
141
|
+
reset() {
|
|
142
|
+
for (const collection of this.collections.values()) {
|
|
143
|
+
collection.clear();
|
|
144
|
+
}
|
|
145
|
+
this._data.clear();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../../src/emulate/core/store.ts"],"names":[],"mappings":"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","sourcesContent":["import { generateId } from './id.js';\nimport { cursorPaginate, type Entity, type CursorPaginationOptions, type CursorPaginatedResult } from './pagination.js';\n\nexport type { Entity };\n\nexport type InsertInput<T extends Entity> = Omit<T, 'id' | 'created_at' | 'updated_at'> & {\n id?: string;\n};\n\nexport type FilterFn<T> = (item: T) => boolean;\nexport type SortFn<T> = (a: T, b: T) => number;\n\nexport interface CollectionHooks<T extends Entity> {\n onInsert?: (item: T) => void;\n onUpdate?: (item: T) => void;\n onDelete?: (item: T) => void;\n}\n\nexport class Collection<T extends Entity> {\n private items = new Map<string, T>();\n private indexes = new Map<string, Map<string, Set<string>>>();\n private hooks: CollectionHooks<T> = {};\n readonly fieldNames: string[];\n\n constructor(\n private prefix: string,\n private indexFields: (keyof T)[] = [],\n ) {\n this.fieldNames = indexFields.map(String).sort();\n for (const field of indexFields) {\n this.indexes.set(String(field), new Map());\n }\n }\n\n private addToIndex(item: T): void {\n for (const field of this.indexFields) {\n const value = item[field];\n if (value === undefined || value === null) continue;\n const indexMap = this.indexes.get(String(field))!;\n const key = String(value);\n if (!indexMap.has(key)) {\n indexMap.set(key, new Set());\n }\n indexMap.get(key)!.add(item.id);\n }\n }\n\n private removeFromIndex(item: T): void {\n for (const field of this.indexFields) {\n const value = item[field];\n if (value === undefined || value === null) continue;\n const indexMap = this.indexes.get(String(field))!;\n const key = String(value);\n indexMap.get(key)?.delete(item.id);\n }\n }\n\n insert(data: InsertInput<T>): T {\n const now = new Date().toISOString();\n const id = data.id ?? generateId(this.prefix);\n const item = {\n ...data,\n id,\n created_at: now,\n updated_at: now,\n } as unknown as T;\n this.items.set(id, item);\n this.addToIndex(item);\n this.hooks.onInsert?.(item);\n return item;\n }\n\n get(id: string): T | undefined {\n return this.items.get(id);\n }\n\n findBy(field: keyof T, value: string | number): T[] {\n if (this.indexes.has(String(field))) {\n const ids = this.indexes.get(String(field))!.get(String(value));\n if (!ids) return [];\n return Array.from(ids)\n .map((id) => this.items.get(id)!)\n .filter(Boolean);\n }\n return this.all().filter((item) => item[field] === value);\n }\n\n findOneBy(field: keyof T, value: string | number): T | undefined {\n return this.findBy(field, value)[0];\n }\n\n update(id: string, data: Partial<T>): T | undefined {\n const existing = this.items.get(id);\n if (!existing) return undefined;\n this.removeFromIndex(existing);\n const updated = {\n ...existing,\n ...data,\n id,\n updated_at: new Date().toISOString(),\n } as T;\n this.items.set(id, updated);\n this.addToIndex(updated);\n this.hooks.onUpdate?.(updated);\n return updated;\n }\n\n delete(id: string): boolean {\n const existing = this.items.get(id);\n if (!existing) return false;\n this.hooks.onDelete?.(existing);\n this.removeFromIndex(existing);\n return this.items.delete(id);\n }\n\n setHooks(hooks: CollectionHooks<T>): void {\n this.hooks = hooks;\n }\n\n all(): T[] {\n return Array.from(this.items.values());\n }\n\n list(options: CursorPaginationOptions<T> = {}): CursorPaginatedResult<T> {\n return cursorPaginate(this.all(), options);\n }\n\n count(filter?: FilterFn<T>): number {\n if (!filter) return this.items.size;\n return this.all().filter(filter).length;\n }\n\n clear(): void {\n this.items.clear();\n for (const indexMap of this.indexes.values()) {\n indexMap.clear();\n }\n }\n}\n\nexport class Store {\n private collections = new Map<string, Collection<any>>();\n private _data = new Map<string, unknown>();\n\n collection<T extends Entity>(name: string, prefix: string, indexFields: (keyof T)[] = []): Collection<T> {\n const existing = this.collections.get(name);\n if (existing) {\n if (indexFields.length > 0) {\n const requested = indexFields.map(String).sort();\n if (existing.fieldNames.length !== requested.length || existing.fieldNames.some((f, i) => f !== requested[i])) {\n throw new Error(\n `Collection \"${name}\" already exists with indexes [${existing.fieldNames}] but was requested with [${requested}]`,\n );\n }\n }\n return existing as Collection<T>;\n }\n const col = new Collection<T>(prefix, indexFields);\n this.collections.set(name, col);\n return col;\n }\n\n getData<V>(key: string): V | undefined {\n return this._data.get(key) as V | undefined;\n }\n\n setData<V>(key: string, value: V): void {\n this._data.set(key, value);\n }\n\n reset(): void {\n for (const collection of this.collections.values()) {\n collection.clear();\n }\n this._data.clear();\n }\n}\n"]}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type WorkOSSeedConfig } from './workos/index.js';
|
|
2
|
+
export interface EmulatorSeedConfig {
|
|
3
|
+
apiKeys?: Record<string, {
|
|
4
|
+
environment: string;
|
|
5
|
+
}>;
|
|
6
|
+
organizations?: WorkOSSeedConfig['organizations'];
|
|
7
|
+
users?: WorkOSSeedConfig['users'];
|
|
8
|
+
connections?: WorkOSSeedConfig['connections'];
|
|
9
|
+
invitations?: WorkOSSeedConfig['invitations'];
|
|
10
|
+
roles?: WorkOSSeedConfig['roles'];
|
|
11
|
+
permissions?: WorkOSSeedConfig['permissions'];
|
|
12
|
+
webhookEndpoints?: WorkOSSeedConfig['webhookEndpoints'];
|
|
13
|
+
}
|
|
14
|
+
export interface EmulatorOptions {
|
|
15
|
+
port?: number;
|
|
16
|
+
seed?: EmulatorSeedConfig;
|
|
17
|
+
}
|
|
18
|
+
export interface Emulator {
|
|
19
|
+
url: string;
|
|
20
|
+
port: number;
|
|
21
|
+
apiKey: string;
|
|
22
|
+
close(): Promise<void>;
|
|
23
|
+
reset(): void;
|
|
24
|
+
}
|
|
25
|
+
export declare function createEmulator(options?: EmulatorOptions): Promise<Emulator>;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { createServer } from './core/index.js';
|
|
2
|
+
import { workosPlugin, seedFromConfig } from './workos/index.js';
|
|
3
|
+
import { serve } from '@hono/node-server';
|
|
4
|
+
export async function createEmulator(options = {}) {
|
|
5
|
+
const port = options.port ?? 4100;
|
|
6
|
+
const baseUrl = `http://localhost:${port}`;
|
|
7
|
+
const apiKeys = options.seed?.apiKeys ?? {
|
|
8
|
+
sk_test_default: { environment: 'test' },
|
|
9
|
+
};
|
|
10
|
+
const { app, store, jwt } = createServer(workosPlugin, {
|
|
11
|
+
port,
|
|
12
|
+
baseUrl,
|
|
13
|
+
apiKeys,
|
|
14
|
+
});
|
|
15
|
+
// Health check endpoint
|
|
16
|
+
app.get('/health', (c) => c.json({ status: 'ok' }));
|
|
17
|
+
const seedFn = () => {
|
|
18
|
+
workosPlugin.seed?.(store, baseUrl);
|
|
19
|
+
if (options.seed) {
|
|
20
|
+
seedFromConfig(store, baseUrl, options.seed);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
seedFn();
|
|
24
|
+
const httpServer = serve({ fetch: app.fetch, port });
|
|
25
|
+
// Resolve actual port (important for port: 0)
|
|
26
|
+
const addr = httpServer.address();
|
|
27
|
+
const actualPort = typeof addr === 'object' && addr ? addr.port : port;
|
|
28
|
+
const url = `http://localhost:${actualPort}`;
|
|
29
|
+
// Update JWT issuer to reflect the actual bound URL (matters when port: 0)
|
|
30
|
+
jwt.issuer = url;
|
|
31
|
+
const primaryApiKey = Object.keys(apiKeys)[0];
|
|
32
|
+
return {
|
|
33
|
+
url,
|
|
34
|
+
port: actualPort,
|
|
35
|
+
apiKey: primaryApiKey,
|
|
36
|
+
reset() {
|
|
37
|
+
store.reset();
|
|
38
|
+
seedFn();
|
|
39
|
+
},
|
|
40
|
+
close() {
|
|
41
|
+
return new Promise((resolve, reject) => {
|
|
42
|
+
httpServer.close((err) => (err ? reject(err) : resolve()));
|
|
43
|
+
});
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/emulate/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAkB,MAAM,iBAAiB,CAAC;AAC/D,OAAO,EAAE,YAAY,EAAE,cAAc,EAAyB,MAAM,mBAAmB,CAAC;AACxF,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AA0B1C,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,UAA2B,EAAE;IAChE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,IAAI,CAAC;IAClC,MAAM,OAAO,GAAG,oBAAoB,IAAI,EAAE,CAAC;IAE3C,MAAM,OAAO,GAAc,OAAO,CAAC,IAAI,EAAE,OAAO,IAAI;QAClD,eAAe,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE;KACzC,CAAC;IAEF,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,YAAY,CAAC,YAAY,EAAE;QACrD,IAAI;QACJ,OAAO;QACP,OAAO;KACR,CAAC,CAAC;IAEH,wBAAwB;IACxB,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAEpD,MAAM,MAAM,GAAG,GAAG,EAAE;QAClB,YAAY,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QACpC,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;YACjB,cAAc,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;QAC/C,CAAC;IACH,CAAC,CAAC;IACF,MAAM,EAAE,CAAC;IAET,MAAM,UAAU,GAAG,KAAK,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAErD,8CAA8C;IAC9C,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,EAAE,CAAC;IAClC,MAAM,UAAU,GAAG,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;IACvE,MAAM,GAAG,GAAG,oBAAoB,UAAU,EAAE,CAAC;IAE7C,2EAA2E;IAC3E,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC;IAEjB,MAAM,aAAa,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;IAE9C,OAAO;QACL,GAAG;QACH,IAAI,EAAE,UAAU;QAChB,MAAM,EAAE,aAAa;QACrB,KAAK;YACH,KAAK,CAAC,KAAK,EAAE,CAAC;YACd,MAAM,EAAE,CAAC;QACX,CAAC;QACD,KAAK;YACH,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;gBACrC,UAAU,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;YAC7D,CAAC,CAAC,CAAC;QACL,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["import { createServer, type ApiKeyMap } from './core/index.js';\nimport { workosPlugin, seedFromConfig, type WorkOSSeedConfig } from './workos/index.js';\nimport { serve } from '@hono/node-server';\n\nexport interface EmulatorSeedConfig {\n apiKeys?: Record<string, { environment: string }>;\n organizations?: WorkOSSeedConfig['organizations'];\n users?: WorkOSSeedConfig['users'];\n connections?: WorkOSSeedConfig['connections'];\n invitations?: WorkOSSeedConfig['invitations'];\n roles?: WorkOSSeedConfig['roles'];\n permissions?: WorkOSSeedConfig['permissions'];\n webhookEndpoints?: WorkOSSeedConfig['webhookEndpoints'];\n}\n\nexport interface EmulatorOptions {\n port?: number;\n seed?: EmulatorSeedConfig;\n}\n\nexport interface Emulator {\n url: string;\n port: number;\n apiKey: string;\n close(): Promise<void>;\n reset(): void;\n}\n\nexport async function createEmulator(options: EmulatorOptions = {}): Promise<Emulator> {\n const port = options.port ?? 4100;\n const baseUrl = `http://localhost:${port}`;\n\n const apiKeys: ApiKeyMap = options.seed?.apiKeys ?? {\n sk_test_default: { environment: 'test' },\n };\n\n const { app, store, jwt } = createServer(workosPlugin, {\n port,\n baseUrl,\n apiKeys,\n });\n\n // Health check endpoint\n app.get('/health', (c) => c.json({ status: 'ok' }));\n\n const seedFn = () => {\n workosPlugin.seed?.(store, baseUrl);\n if (options.seed) {\n seedFromConfig(store, baseUrl, options.seed);\n }\n };\n seedFn();\n\n const httpServer = serve({ fetch: app.fetch, port });\n\n // Resolve actual port (important for port: 0)\n const addr = httpServer.address();\n const actualPort = typeof addr === 'object' && addr ? addr.port : port;\n const url = `http://localhost:${actualPort}`;\n\n // Update JWT issuer to reflect the actual bound URL (matters when port: 0)\n jwt.issuer = url;\n\n const primaryApiKey = Object.keys(apiKeys)[0];\n\n return {\n url,\n port: actualPort,\n apiKey: primaryApiKey,\n reset() {\n store.reset();\n seedFn();\n },\n close(): Promise<void> {\n return new Promise((resolve, reject) => {\n httpServer.close((err) => (err ? reject(err) : resolve()));\n });\n },\n };\n}\n"]}
|