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.
Files changed (172) hide show
  1. package/README.md +163 -6
  2. package/dist/bin.js +22 -2
  3. package/dist/bin.js.map +1 -1
  4. package/dist/check-coverage.ts +237 -0
  5. package/dist/commands/dev.d.ts +23 -0
  6. package/dist/commands/dev.js +139 -0
  7. package/dist/commands/dev.js.map +1 -0
  8. package/dist/commands/emulate.d.ts +6 -0
  9. package/dist/commands/emulate.js +64 -0
  10. package/dist/commands/emulate.js.map +1 -0
  11. package/dist/commands/seed.d.ts +2 -0
  12. package/dist/commands/seed.js +60 -1
  13. package/dist/commands/seed.js.map +1 -1
  14. package/dist/emulate/core/id.d.ts +33 -0
  15. package/dist/emulate/core/id.js +58 -0
  16. package/dist/emulate/core/id.js.map +1 -0
  17. package/dist/emulate/core/index.d.ts +8 -0
  18. package/dist/emulate/core/index.js +8 -0
  19. package/dist/emulate/core/index.js.map +1 -0
  20. package/dist/emulate/core/jwt.d.ts +28 -0
  21. package/dist/emulate/core/jwt.js +78 -0
  22. package/dist/emulate/core/jwt.js.map +1 -0
  23. package/dist/emulate/core/middleware/auth.d.ts +18 -0
  24. package/dist/emulate/core/middleware/auth.js +28 -0
  25. package/dist/emulate/core/middleware/auth.js.map +1 -0
  26. package/dist/emulate/core/middleware/error-handler.d.ts +22 -0
  27. package/dist/emulate/core/middleware/error-handler.js +72 -0
  28. package/dist/emulate/core/middleware/error-handler.js.map +1 -0
  29. package/dist/emulate/core/pagination.d.ts +21 -0
  30. package/dist/emulate/core/pagination.js +35 -0
  31. package/dist/emulate/core/pagination.js.map +1 -0
  32. package/dist/emulate/core/plugin.d.ts +15 -0
  33. package/dist/emulate/core/plugin.js +2 -0
  34. package/dist/emulate/core/plugin.js.map +1 -0
  35. package/dist/emulate/core/server.d.ts +17 -0
  36. package/dist/emulate/core/server.js +116 -0
  37. package/dist/emulate/core/server.js.map +1 -0
  38. package/dist/emulate/core/store.d.ts +42 -0
  39. package/dist/emulate/core/store.js +148 -0
  40. package/dist/emulate/core/store.js.map +1 -0
  41. package/dist/emulate/index.d.ts +25 -0
  42. package/dist/emulate/index.js +47 -0
  43. package/dist/emulate/index.js.map +1 -0
  44. package/dist/emulate/workos/entities.d.ts +360 -0
  45. package/dist/emulate/workos/entities.js +2 -0
  46. package/dist/emulate/workos/entities.js.map +1 -0
  47. package/dist/emulate/workos/event-bus.d.ts +12 -0
  48. package/dist/emulate/workos/event-bus.js +45 -0
  49. package/dist/emulate/workos/event-bus.js.map +1 -0
  50. package/dist/emulate/workos/helpers.d.ts +63 -0
  51. package/dist/emulate/workos/helpers.js +518 -0
  52. package/dist/emulate/workos/helpers.js.map +1 -0
  53. package/dist/emulate/workos/index.d.ts +91 -0
  54. package/dist/emulate/workos/index.js +319 -0
  55. package/dist/emulate/workos/index.js.map +1 -0
  56. package/dist/emulate/workos/routes/api-keys.d.ts +2 -0
  57. package/dist/emulate/workos/routes/api-keys.js +35 -0
  58. package/dist/emulate/workos/routes/api-keys.js.map +1 -0
  59. package/dist/emulate/workos/routes/audit-logs.d.ts +2 -0
  60. package/dist/emulate/workos/routes/audit-logs.js +107 -0
  61. package/dist/emulate/workos/routes/audit-logs.js.map +1 -0
  62. package/dist/emulate/workos/routes/auth-challenges.d.ts +2 -0
  63. package/dist/emulate/workos/routes/auth-challenges.js +51 -0
  64. package/dist/emulate/workos/routes/auth-challenges.js.map +1 -0
  65. package/dist/emulate/workos/routes/auth-factors.d.ts +2 -0
  66. package/dist/emulate/workos/routes/auth-factors.js +51 -0
  67. package/dist/emulate/workos/routes/auth-factors.js.map +1 -0
  68. package/dist/emulate/workos/routes/auth.d.ts +2 -0
  69. package/dist/emulate/workos/routes/auth.js +349 -0
  70. package/dist/emulate/workos/routes/auth.js.map +1 -0
  71. package/dist/emulate/workos/routes/authorization-checks.d.ts +10 -0
  72. package/dist/emulate/workos/routes/authorization-checks.js +135 -0
  73. package/dist/emulate/workos/routes/authorization-checks.js.map +1 -0
  74. package/dist/emulate/workos/routes/authorization-org-roles.d.ts +2 -0
  75. package/dist/emulate/workos/routes/authorization-org-roles.js +206 -0
  76. package/dist/emulate/workos/routes/authorization-org-roles.js.map +1 -0
  77. package/dist/emulate/workos/routes/authorization-permissions.d.ts +2 -0
  78. package/dist/emulate/workos/routes/authorization-permissions.js +78 -0
  79. package/dist/emulate/workos/routes/authorization-permissions.js.map +1 -0
  80. package/dist/emulate/workos/routes/authorization-resources.d.ts +2 -0
  81. package/dist/emulate/workos/routes/authorization-resources.js +128 -0
  82. package/dist/emulate/workos/routes/authorization-resources.js.map +1 -0
  83. package/dist/emulate/workos/routes/authorization-roles.d.ts +2 -0
  84. package/dist/emulate/workos/routes/authorization-roles.js +136 -0
  85. package/dist/emulate/workos/routes/authorization-roles.js.map +1 -0
  86. package/dist/emulate/workos/routes/config.d.ts +2 -0
  87. package/dist/emulate/workos/routes/config.js +56 -0
  88. package/dist/emulate/workos/routes/config.js.map +1 -0
  89. package/dist/emulate/workos/routes/connect.d.ts +2 -0
  90. package/dist/emulate/workos/routes/connect.js +69 -0
  91. package/dist/emulate/workos/routes/connect.js.map +1 -0
  92. package/dist/emulate/workos/routes/connections.d.ts +2 -0
  93. package/dist/emulate/workos/routes/connections.js +77 -0
  94. package/dist/emulate/workos/routes/connections.js.map +1 -0
  95. package/dist/emulate/workos/routes/data-integrations.d.ts +2 -0
  96. package/dist/emulate/workos/routes/data-integrations.js +55 -0
  97. package/dist/emulate/workos/routes/data-integrations.js.map +1 -0
  98. package/dist/emulate/workos/routes/directories.d.ts +2 -0
  99. package/dist/emulate/workos/routes/directories.js +106 -0
  100. package/dist/emulate/workos/routes/directories.js.map +1 -0
  101. package/dist/emulate/workos/routes/email-verification.d.ts +2 -0
  102. package/dist/emulate/workos/routes/email-verification.js +49 -0
  103. package/dist/emulate/workos/routes/email-verification.js.map +1 -0
  104. package/dist/emulate/workos/routes/events.d.ts +2 -0
  105. package/dist/emulate/workos/routes/events.js +21 -0
  106. package/dist/emulate/workos/routes/events.js.map +1 -0
  107. package/dist/emulate/workos/routes/feature-flags.d.ts +2 -0
  108. package/dist/emulate/workos/routes/feature-flags.js +131 -0
  109. package/dist/emulate/workos/routes/feature-flags.js.map +1 -0
  110. package/dist/emulate/workos/routes/invitations.d.ts +2 -0
  111. package/dist/emulate/workos/routes/invitations.js +125 -0
  112. package/dist/emulate/workos/routes/invitations.js.map +1 -0
  113. package/dist/emulate/workos/routes/legacy-mfa.d.ts +2 -0
  114. package/dist/emulate/workos/routes/legacy-mfa.js +75 -0
  115. package/dist/emulate/workos/routes/legacy-mfa.js.map +1 -0
  116. package/dist/emulate/workos/routes/magic-auth.d.ts +2 -0
  117. package/dist/emulate/workos/routes/magic-auth.js +32 -0
  118. package/dist/emulate/workos/routes/magic-auth.js.map +1 -0
  119. package/dist/emulate/workos/routes/memberships.d.ts +2 -0
  120. package/dist/emulate/workos/routes/memberships.js +118 -0
  121. package/dist/emulate/workos/routes/memberships.js.map +1 -0
  122. package/dist/emulate/workos/routes/organization-domains.d.ts +2 -0
  123. package/dist/emulate/workos/routes/organization-domains.js +58 -0
  124. package/dist/emulate/workos/routes/organization-domains.js.map +1 -0
  125. package/dist/emulate/workos/routes/organizations.d.ts +2 -0
  126. package/dist/emulate/workos/routes/organizations.js +133 -0
  127. package/dist/emulate/workos/routes/organizations.js.map +1 -0
  128. package/dist/emulate/workos/routes/password-reset.d.ts +2 -0
  129. package/dist/emulate/workos/routes/password-reset.js +61 -0
  130. package/dist/emulate/workos/routes/password-reset.js.map +1 -0
  131. package/dist/emulate/workos/routes/pipes.d.ts +2 -0
  132. package/dist/emulate/workos/routes/pipes.js +86 -0
  133. package/dist/emulate/workos/routes/pipes.js.map +1 -0
  134. package/dist/emulate/workos/routes/portal.d.ts +2 -0
  135. package/dist/emulate/workos/routes/portal.js +18 -0
  136. package/dist/emulate/workos/routes/portal.js.map +1 -0
  137. package/dist/emulate/workos/routes/radar.d.ts +2 -0
  138. package/dist/emulate/workos/routes/radar.js +45 -0
  139. package/dist/emulate/workos/routes/radar.js.map +1 -0
  140. package/dist/emulate/workos/routes/sessions.d.ts +2 -0
  141. package/dist/emulate/workos/routes/sessions.js +51 -0
  142. package/dist/emulate/workos/routes/sessions.js.map +1 -0
  143. package/dist/emulate/workos/routes/sso.d.ts +2 -0
  144. package/dist/emulate/workos/routes/sso.js +160 -0
  145. package/dist/emulate/workos/routes/sso.js.map +1 -0
  146. package/dist/emulate/workos/routes/user-features.d.ts +2 -0
  147. package/dist/emulate/workos/routes/user-features.js +50 -0
  148. package/dist/emulate/workos/routes/user-features.js.map +1 -0
  149. package/dist/emulate/workos/routes/users.d.ts +2 -0
  150. package/dist/emulate/workos/routes/users.js +133 -0
  151. package/dist/emulate/workos/routes/users.js.map +1 -0
  152. package/dist/emulate/workos/routes/webhook-endpoints.d.ts +2 -0
  153. package/dist/emulate/workos/routes/webhook-endpoints.js +70 -0
  154. package/dist/emulate/workos/routes/webhook-endpoints.js.map +1 -0
  155. package/dist/emulate/workos/routes/widgets.d.ts +2 -0
  156. package/dist/emulate/workos/routes/widgets.js +27 -0
  157. package/dist/emulate/workos/routes/widgets.js.map +1 -0
  158. package/dist/emulate/workos/store.d.ts +48 -0
  159. package/dist/emulate/workos/store.js +93 -0
  160. package/dist/emulate/workos/store.js.map +1 -0
  161. package/dist/emulate/workos/webhook-signer.d.ts +1 -0
  162. package/dist/emulate/workos/webhook-signer.js +8 -0
  163. package/dist/emulate/workos/webhook-signer.js.map +1 -0
  164. package/dist/gen-routes-lib.spec.ts +659 -0
  165. package/dist/gen-routes-lib.ts +647 -0
  166. package/dist/gen-routes.ts +96 -0
  167. package/dist/lib/dev-command.d.ts +26 -0
  168. package/dist/lib/dev-command.js +122 -0
  169. package/dist/lib/dev-command.js.map +1 -0
  170. package/dist/utils/help-json.js +31 -0
  171. package/dist/utils/help-json.js.map +1 -1
  172. 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":"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","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"]}