arcway 0.1.27 → 0.1.28

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arcway",
3
- "version": "0.1.27",
3
+ "version": "0.1.28",
4
4
  "description": "A convention-based framework for building modular monoliths with strict domain boundaries.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -18,6 +18,7 @@ import { runHook } from './hooks.js';
18
18
  import { createViteDevRouter } from '../pages/vite-dev.js';
19
19
  import { resolvePagesOutDir } from '../pages/out-dir.js';
20
20
  import { collectRuntimeMeta } from '../meta.js';
21
+ import { createPluginManager } from '../plugins/manager.js';
21
22
  import path from 'node:path';
22
23
 
23
24
  async function boot(options) {
@@ -27,6 +28,9 @@ async function boot(options) {
27
28
  const envFiles = loadEnvFiles(rootDir, mode);
28
29
  const config = await makeConfig(rootDir, { overrides: options.configOverrides, mode });
29
30
 
31
+ const plugins = await createPluginManager(config.plugins, { runLifecycle: false });
32
+ config.database.pluginMigrations = plugins.migrations();
33
+
30
34
  const infrastructure = await createInfrastructure(config, { runMigrations: true });
31
35
  const { db, redis, queue, cache, files, mail, events, log } = infrastructure;
32
36
  const meta = await collectRuntimeMeta(rootDir);
@@ -42,6 +46,11 @@ async function boot(options) {
42
46
  // because downstream consumers read properties off this reference at use time.
43
47
  const appContext = { db, redis, events, queue, cache, files, mail, log, fileWatcher, meta };
44
48
 
49
+ plugins.log = log;
50
+ appContext.plugins = plugins;
51
+
52
+ await plugins.runEnabledLifecycle();
53
+
45
54
  await runHook('init', appContext, rootDir);
46
55
 
47
56
  const mcpRouter = new McpRouter(config.mcp, { log });
@@ -75,6 +84,7 @@ async function boot(options) {
75
84
  log,
76
85
  meta,
77
86
  workerPool,
87
+ plugins,
78
88
  });
79
89
  await jobRunner.init();
80
90
 
package/server/build.js CHANGED
@@ -12,6 +12,7 @@ import { discoverMiddleware } from './router/middleware.js';
12
12
  import { EventHandler } from './events/handler.js';
13
13
  import { discoverJobs } from './jobs/runner.js';
14
14
  import { collectRuntimeMeta } from './meta.js';
15
+ import { createPluginManager } from './plugins/manager.js';
15
16
 
16
17
  async function writeBuildMeta(rootDir, meta) {
17
18
  const buildDir = path.join(rootDir, '.build');
@@ -23,9 +24,11 @@ async function writeBuildMeta(rootDir, meta) {
23
24
  }
24
25
 
25
26
  async function validateServerModules(config) {
27
+ const plugins = await createPluginManager(config.plugins, { runLifecycle: false });
26
28
  if (config?.api?.enabled !== false && config?.api?.dir) {
27
29
  await discoverRoutes(config.api.dir);
28
30
  await discoverMiddleware(config.api.dir);
31
+ await plugins.discoverRoutes();
29
32
  }
30
33
 
31
34
  if (config?.events?.dir) {
@@ -33,7 +36,14 @@ async function validateServerModules(config) {
33
36
  { dir: config.events.dir },
34
37
  {
35
38
  events: { subscribe() {} },
36
- log: { info() {}, warn() {}, error() {}, extend() { return this; } },
39
+ log: {
40
+ info() {},
41
+ warn() {},
42
+ error() {},
43
+ extend() {
44
+ return this;
45
+ },
46
+ },
37
47
  },
38
48
  );
39
49
  await handler.init();
@@ -41,6 +51,7 @@ async function validateServerModules(config) {
41
51
 
42
52
  if (config?.jobs?.enabled !== false && config?.jobs?.dir) {
43
53
  await discoverJobs(config.jobs.dir);
54
+ await plugins.discoverJobs();
44
55
  }
45
56
  }
46
57
 
@@ -12,12 +12,14 @@ import resolveRedis from './modules/redis.js';
12
12
  import resolveCache from './modules/cache.js';
13
13
  import resolveEvents from './modules/events.js';
14
14
  import resolveMail from './modules/mail.js';
15
+ import resolveSecrets from './modules/secrets.js';
15
16
  import resolveSession from './modules/session.js';
16
17
  import resolvePages from './modules/pages.js';
17
18
  import resolveBuild from './modules/build.js';
18
19
  import resolveMcp from './modules/mcp.js';
19
20
  import resolveWebsocket from './modules/websocket.js';
20
21
  import resolveSeeds from './modules/seeds.js';
22
+ import resolvePlugins from './modules/plugins.js';
21
23
 
22
24
  function deepMerge(target, source) {
23
25
  const result = { ...target };
@@ -49,12 +51,14 @@ const modules = [
49
51
  resolveFiles,
50
52
  resolveMail,
51
53
  resolveRedis,
54
+ resolveSecrets,
52
55
  resolveSession,
53
56
  resolvePages,
54
57
  resolveBuild,
55
58
  resolveMcp,
56
59
  resolveWebsocket,
57
60
  resolveSeeds,
61
+ resolvePlugins,
58
62
  ];
59
63
 
60
64
  async function makeConfig(rootDir, { overrides, mode } = {}) {
@@ -3,7 +3,11 @@ const DEFAULTS = {
3
3
  };
4
4
 
5
5
  function resolve(config) {
6
- return { ...config, mcp: { ...DEFAULTS, ...config.mcp } };
6
+ const mcp = { ...DEFAULTS, ...config.mcp };
7
+ if (!mcp.secret && config.secrets?.values?.['mcp-api']) {
8
+ mcp.secret = config.secrets.values['mcp-api'];
9
+ }
10
+ return { ...config, mcp };
7
11
  }
8
12
 
9
13
  export default resolve;
@@ -0,0 +1,18 @@
1
+ import path from 'node:path';
2
+
3
+ const DEFAULTS = {
4
+ enabled: false,
5
+ dirs: ['plugins'],
6
+ };
7
+
8
+ function resolve(config, { rootDir } = {}) {
9
+ const plugins = { ...DEFAULTS, ...config.plugins };
10
+ const dirs = Array.isArray(plugins.dirs) ? plugins.dirs : [plugins.dirs];
11
+ plugins.dirs = dirs
12
+ .filter((dir) => typeof dir === 'string' && dir.length > 0)
13
+ .map((dir) => (rootDir && !path.isAbsolute(dir) ? path.resolve(rootDir, dir) : dir));
14
+ plugins.enabled = plugins.enabled === true;
15
+ return { ...config, plugins };
16
+ }
17
+
18
+ export default resolve;
@@ -0,0 +1,145 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import {
4
+ deriveInfraSecrets,
5
+ encodeMasterSecret,
6
+ normalizeMasterSecret,
7
+ validateVaultKey,
8
+ } from '../../lib/vault/secrets.js';
9
+
10
+ const DEFAULT_KEY_FILE = '.build/arcway-master-secret.key';
11
+
12
+ const EXPLICIT_ENV = Object.freeze({
13
+ session: 'SESSION_SECRET',
14
+ 'plugin-vault': 'PLUGIN_VAULT_KEY',
15
+ 'mcp-api': 'MCP_API_KEY',
16
+ 'pty-api': 'PTY_API_KEY',
17
+ 'pty-server-api': 'PTY_SERVER_API_KEY',
18
+ });
19
+
20
+ function isProduction(mode) {
21
+ return mode === 'production';
22
+ }
23
+
24
+ function readKeyFile(filePath) {
25
+ try {
26
+ const stat = fs.statSync(filePath);
27
+ if ((stat.mode & 0o077) !== 0) {
28
+ throw new Error('Arcway master secret keyfile permissions must be 0600');
29
+ }
30
+ return fs.readFileSync(filePath, 'utf8').trim();
31
+ } catch (error) {
32
+ if (error.code === 'ENOENT') return null;
33
+ throw error;
34
+ }
35
+ }
36
+
37
+ function createDevKeyFile(filePath) {
38
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
39
+ const encoded = encodeMasterSecret();
40
+
41
+ try {
42
+ const fd = fs.openSync(
43
+ filePath,
44
+ fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_WRONLY,
45
+ 0o600,
46
+ );
47
+ try {
48
+ fs.writeFileSync(fd, `${encoded}\n`, 'utf8');
49
+ } finally {
50
+ fs.closeSync(fd);
51
+ }
52
+ return encoded;
53
+ } catch (error) {
54
+ if (error.code === 'EEXIST') return readKeyFile(filePath);
55
+ throw error;
56
+ }
57
+ }
58
+
59
+ function loadMasterSecret(rawSecrets, { rootDir, mode, env }) {
60
+ const configured = rawSecrets.master ?? env.ARCWAY_MASTER_SECRET;
61
+ if (configured) return { value: configured, source: 'config' };
62
+
63
+ if (isProduction(mode)) {
64
+ throw new Error(
65
+ 'ARCWAY_MASTER_SECRET is required when arcway.config secrets are enabled in production',
66
+ );
67
+ }
68
+
69
+ const keyFile = path.resolve(rootDir, rawSecrets.keyFile ?? DEFAULT_KEY_FILE);
70
+ const existing = readKeyFile(keyFile);
71
+ if (existing) return { value: existing, source: 'keyfile', keyFile };
72
+
73
+ return { value: createDevKeyFile(keyFile), source: 'generated-keyfile', keyFile };
74
+ }
75
+
76
+ function explicitValue(name, rawSecrets, env) {
77
+ const explicit = rawSecrets.explicit?.[name];
78
+ if (explicit !== undefined && explicit !== null && explicit !== '') return explicit;
79
+
80
+ const envName = EXPLICIT_ENV[name];
81
+ const fromEnv = envName ? env[envName] : undefined;
82
+ return fromEnv || undefined;
83
+ }
84
+
85
+ function validateExplicit(name, value) {
86
+ if (name === 'plugin-vault') return validateVaultKey(value);
87
+ return String(value);
88
+ }
89
+
90
+ function resolve(config, { rootDir, mode } = {}) {
91
+ if (!config.secrets) return config;
92
+
93
+ const env = config.secrets.env ?? process.env;
94
+ const rawSecrets = config.secrets;
95
+ const master = loadMasterSecret(rawSecrets, { rootDir, mode, env });
96
+ normalizeMasterSecret(master.value);
97
+
98
+ const derived = deriveInfraSecrets(master.value);
99
+ const values = { ...derived };
100
+ const explicit = {};
101
+ const previousValues = {};
102
+
103
+ const previousMaster = rawSecrets.previous ?? env.ARCWAY_MASTER_SECRET_PREVIOUS;
104
+ if (previousMaster) {
105
+ normalizeMasterSecret(previousMaster, 'Previous Arcway master secret');
106
+ Object.assign(previousValues, deriveInfraSecrets(previousMaster));
107
+ }
108
+
109
+ for (const name of Object.keys(EXPLICIT_ENV)) {
110
+ const value = explicitValue(name, rawSecrets, env);
111
+ if (!value) continue;
112
+
113
+ explicit[name] = validateExplicit(name, value);
114
+ values[name] = explicit[name];
115
+ if (derived[name] !== explicit[name]) {
116
+ previousValues[name] = explicit[name];
117
+ }
118
+ }
119
+
120
+ const vapidPublic = rawSecrets.explicit?.vapidPublic ?? env.VAPID_PUBLIC_KEY;
121
+ const vapidPrivate = rawSecrets.explicit?.vapidPrivate ?? env.VAPID_PRIVATE_KEY;
122
+ if (vapidPublic || vapidPrivate) {
123
+ if (!vapidPublic || !vapidPrivate) {
124
+ throw new Error('VAPID_PUBLIC_KEY and VAPID_PRIVATE_KEY must be provided together');
125
+ }
126
+ explicit.vapid = { publicKey: String(vapidPublic), privateKey: String(vapidPrivate) };
127
+ values.vapid = explicit.vapid;
128
+ previousValues.vapid = explicit.vapid;
129
+ }
130
+
131
+ return {
132
+ ...config,
133
+ secrets: {
134
+ enabled: true,
135
+ keyFile: master.keyFile,
136
+ source: master.source,
137
+ values,
138
+ derived,
139
+ explicit,
140
+ previousValues,
141
+ },
142
+ };
143
+ }
144
+
145
+ export default resolve;
@@ -2,7 +2,11 @@ import { resolveSessionConfig } from '../../session/index.js';
2
2
 
3
3
  function resolve(config, { mode } = {}) {
4
4
  if (!config.session) return config;
5
- const session = resolveSessionConfig(config.session, mode);
5
+ const sessionInput = {
6
+ ...config.session,
7
+ password: config.session.password || config.secrets?.values?.session,
8
+ };
9
+ const session = resolveSessionConfig(sessionInput, mode);
6
10
  return { ...config, session };
7
11
  }
8
12
 
@@ -6,18 +6,39 @@ import { loadModule } from '../module-loader.js';
6
6
  import { introspectSchema, generateSchemaMarkdown } from './schema/index.js';
7
7
 
8
8
  class MigrationSource {
9
- constructor(directory) {
9
+ constructor(directory, pluginMigrations = []) {
10
10
  this.directory = directory;
11
+ this.pluginMigrations = pluginMigrations;
12
+ this.migrationPaths = new Map();
11
13
  }
12
14
  async getMigrations() {
13
- const files = await discoverFiles(this.directory, { skipUnderscore: false });
14
- return files.map((f) => f.name);
15
+ this.migrationPaths.clear();
16
+ const migrations = [];
17
+
18
+ if (this.directory) {
19
+ const files = await discoverFiles(this.directory, { skipUnderscore: false });
20
+ for (const file of files) {
21
+ migrations.push(file.name);
22
+ this.migrationPaths.set(file.name, file.filePath);
23
+ }
24
+ }
25
+
26
+ for (const plugin of this.pluginMigrations ?? []) {
27
+ const files = await discoverFiles(plugin.dir, { skipUnderscore: false });
28
+ for (const file of files) {
29
+ const name = `plugins/${plugin.pluginId}/${file.name}`;
30
+ migrations.push(name);
31
+ this.migrationPaths.set(name, file.filePath);
32
+ }
33
+ }
34
+
35
+ return migrations;
15
36
  }
16
37
  getMigrationName(migration) {
17
38
  return migration;
18
39
  }
19
40
  async getMigration(migration) {
20
- const filePath = path.resolve(this.directory, migration);
41
+ const filePath = this.migrationPaths.get(migration) ?? path.resolve(this.directory, migration);
21
42
  return loadModule(filePath, import.meta.url);
22
43
  }
23
44
  }
@@ -77,9 +98,10 @@ async function createDB(config, { log } = {}) {
77
98
 
78
99
  db.runMigrations = async () => {
79
100
  const migrationsDir = config.dir;
80
- if (!migrationsDir) return;
101
+ const pluginMigrations = config.pluginMigrations ?? [];
102
+ if (!migrationsDir && pluginMigrations.length === 0) return;
81
103
  const [batch, migrations] = await db.migrate.latest({
82
- migrationSource: new MigrationSource(migrationsDir),
104
+ migrationSource: new MigrationSource(migrationsDir, pluginMigrations),
83
105
  });
84
106
  if (migrations.length > 0) {
85
107
  log?.info(`Migrations (batch ${batch}): ${migrations.join(', ')}`);
@@ -90,9 +112,12 @@ async function createDB(config, { log } = {}) {
90
112
 
91
113
  db.runRollback = async () => {
92
114
  const migrationsDir = config.dir;
93
- if (!migrationsDir) throw new Error('No migrations dir configured');
115
+ const pluginMigrations = config.pluginMigrations ?? [];
116
+ if (!migrationsDir && pluginMigrations.length === 0) {
117
+ throw new Error('No migrations dir configured');
118
+ }
94
119
  const [batch, entries] = await db.migrate.rollback({
95
- migrationSource: new MigrationSource(migrationsDir),
120
+ migrationSource: new MigrationSource(migrationsDir, pluginMigrations),
96
121
  });
97
122
  return { batch, log: entries };
98
123
  };
@@ -8,7 +8,10 @@ import { loadModule } from './module-loader.js';
8
8
  * Skips files starting with underscore by default.
9
9
  * Returns sorted array of { name, filePath, relativePath }.
10
10
  */
11
- async function discoverFiles(dir, { recursive = false, extensions = ['.js'], skipUnderscore = true, filter } = {}) {
11
+ async function discoverFiles(
12
+ dir,
13
+ { recursive = false, extensions = ['.js'], skipUnderscore = true, filter } = {},
14
+ ) {
12
15
  if (!dir) return [];
13
16
 
14
17
  let files;
@@ -49,7 +52,10 @@ async function discoverFiles(dir, { recursive = false, extensions = ['.js'], ski
49
52
  * Returns sorted array of { name, filePath, relativePath, module }.
50
53
  * Throws with a descriptive message if any file fails to import.
51
54
  */
52
- async function discoverModules(dir, { recursive = false, extensions = ['.js'], skipUnderscore = true, filter, label = 'file' } = {}) {
55
+ async function discoverModules(
56
+ dir,
57
+ { recursive = false, extensions = ['.js'], skipUnderscore = true, filter, label = 'file' } = {},
58
+ ) {
53
59
  const files = await discoverFiles(dir, { recursive, extensions, skipUnderscore, filter });
54
60
  const results = [];
55
61
  for (const entry of files) {
@@ -64,4 +70,40 @@ async function discoverModules(dir, { recursive = false, extensions = ['.js'], s
64
70
  return results;
65
71
  }
66
72
 
67
- export { discoverFiles, discoverModules };
73
+ async function discoverPackageDirectories(
74
+ dir,
75
+ { markerFiles = ['package.json'], label = 'package' } = {},
76
+ ) {
77
+ if (!dir) return [];
78
+
79
+ let entries;
80
+ try {
81
+ entries = await fs.readdir(dir, { withFileTypes: true });
82
+ } catch (err) {
83
+ if (err.code === 'ENOENT') return [];
84
+ throw err;
85
+ }
86
+
87
+ const packages = [];
88
+ for (const entry of entries) {
89
+ if (!entry.isDirectory() || entry.name.startsWith('_')) continue;
90
+ const packageDir = path.join(dir, entry.name);
91
+ const hasMarker = await Promise.all(
92
+ markerFiles.map(async (marker) => {
93
+ try {
94
+ await fs.access(path.join(packageDir, marker));
95
+ return true;
96
+ } catch (err) {
97
+ if (err.code === 'ENOENT') return false;
98
+ throw err;
99
+ }
100
+ }),
101
+ );
102
+ if (!hasMarker.some(Boolean)) continue;
103
+ packages.push({ name: entry.name, dir: packageDir, label });
104
+ }
105
+ packages.sort((a, b) => a.name.localeCompare(b.name));
106
+ return packages;
107
+ }
108
+
109
+ export { discoverFiles, discoverModules, discoverPackageDirectories };
package/server/index.js CHANGED
@@ -1,6 +1,8 @@
1
1
  export { type } from 'arktype';
2
2
  export { buildContext } from './context.js';
3
3
  export { default as boot } from './boot.js';
4
+ export { CapabilityNotAvailable, CapabilityRegistry } from './plugins/capabilities.js';
5
+ export { PluginManager, createPluginManager } from './plugins/manager.js';
4
6
  export {
5
7
  Arcway,
6
8
  Solo,
@@ -42,6 +42,9 @@ import { createRateLimitMiddleware } from './rate-limit/index.js';
42
42
  import { createHttpServer, listen, closeServer } from './server.js';
43
43
  import { startWatcher } from './watcher.js';
44
44
  import { build } from './build.js';
45
+ import { CapabilityNotAvailable, CapabilityRegistry } from './plugins/capabilities.js';
46
+ import { discoverPlugins } from './plugins/discovery.js';
47
+ import { PluginManager, createPluginManager } from './plugins/manager.js';
45
48
  import {
46
49
  discoverPages,
47
50
  discoverLayouts,
@@ -68,6 +71,8 @@ import { attachGraphQLSubscriptions } from './graphql/subscriptions.js';
68
71
  export {
69
72
  ApiRouter,
70
73
  Cache,
74
+ CapabilityNotAvailable,
75
+ CapabilityRegistry,
71
76
  makeConfig,
72
77
  Files,
73
78
  KnexQueueDriver,
@@ -101,9 +106,11 @@ export {
101
106
  createLoaderFactory,
102
107
  createPagesHandler,
103
108
  createPagesWatcher,
109
+ createPluginManager,
104
110
  createRateLimitMiddleware,
105
111
  createWebSocketServer,
106
112
  discoverJobs,
113
+ discoverPlugins,
107
114
  EventHandler,
108
115
  discoverErrorPages,
109
116
  discoverGraphQL,
@@ -127,6 +134,7 @@ export {
127
134
  mergeWebSocketHandlers,
128
135
  parseCron,
129
136
  patternToFileName,
137
+ PluginManager,
130
138
  resolveLayoutChain,
131
139
  resolveSessionConfig,
132
140
  runSeeds,
@@ -16,6 +16,9 @@ async function runHandler(reg, payload, workerPool) {
16
16
  return;
17
17
  }
18
18
  const ctx = buildContext(reg.store, { payload });
19
+ if (reg.definition.plugin) {
20
+ reg.store.plugins?.injectCapabilities(ctx, reg.definition.plugin.requires);
21
+ }
19
22
  await reg.definition.handler(ctx);
20
23
  }
21
24
 
@@ -1,4 +1,3 @@
1
- import path from 'node:path';
2
1
  import { discoverModules } from '../discovery.js';
3
2
  import { buildContext } from '../context.js';
4
3
  import { parseCron, matchesCron } from './cron.js';
@@ -45,7 +44,10 @@ class JobRunner {
45
44
  _stopped = false;
46
45
  _lastEnqueuedMinute = new Map();
47
46
 
48
- constructor(config, { db, queue, cache, files, mail, events, log, meta, workerPool } = {}) {
47
+ constructor(
48
+ config,
49
+ { db, queue, cache, files, mail, events, log, meta, workerPool, plugins } = {},
50
+ ) {
49
51
  this._config = config;
50
52
  this._log = log;
51
53
  this._dispatcher = new JobDispatcher({
@@ -53,7 +55,7 @@ class JobRunner {
53
55
  staleTimeoutMs: config?.staleTimeoutMs,
54
56
  workerPool,
55
57
  });
56
- this._appContext = { db, queue, cache, files, mail, events, log, meta };
58
+ this._appContext = { db, queue, cache, files, mail, events, log, meta, plugins };
57
59
  }
58
60
 
59
61
  get dispatcher() {
@@ -75,13 +77,15 @@ class JobRunner {
75
77
  // Discover and register user jobs. `filePath` is captured so the
76
78
  // dispatcher can dynamic-import the handler inside a worker thread.
77
79
  const discovered = await discoverJobs(jobsDir);
78
- for (const { definition, fileName, filePath } of discovered) {
80
+ const pluginJobs = (await this._appContext.plugins?.discoverJobs?.()) ?? [];
81
+ for (const { definition, fileName, filePath } of [...discovered, ...pluginJobs]) {
79
82
  this._dispatcher.register('app', definition, this._appContext, filePath);
80
83
  this._jobs.push({
81
84
  jobName: definition.name,
82
85
  fileName,
83
86
  schedule: definition.schedule,
84
87
  handler: definition.handler,
88
+ plugin: definition.plugin,
85
89
  cooldownMs: definition.cooldownMs,
86
90
  staleTimeout: definition.staleTimeout,
87
91
  });
@@ -110,6 +114,7 @@ class JobRunner {
110
114
  this._continuousJobs.push({
111
115
  qualifiedName: `app/${job.jobName}`,
112
116
  handler: job.handler,
117
+ plugin: job.plugin,
113
118
  cooldownMs: job.cooldownMs,
114
119
  appContext: this._appContext,
115
120
  });
@@ -192,6 +197,9 @@ class JobRunner {
192
197
  try {
193
198
  const start = Date.now();
194
199
  const ctx = buildContext(entry.appContext, { payload: void 0 });
200
+ if (entry.plugin) {
201
+ entry.appContext.plugins?.injectCapabilities(ctx, entry.plugin.requires);
202
+ }
195
203
  await entry.handler(ctx);
196
204
  consecutiveErrors = 0;
197
205
  const elapsed = Date.now() - start;
@@ -2,13 +2,33 @@ import { hashPassword, verifyPassword } from './password.js';
2
2
  import { generateUUID, generateNanoId } from './ids.js';
3
3
  import { encrypt, decrypt } from './encrypt.js';
4
4
  import { jwtEncode, jwtDecode } from './jwt.js';
5
+ import {
6
+ decryptWithRotation,
7
+ deriveBytes,
8
+ deriveInfraSecret,
9
+ deriveInfraSecrets,
10
+ deriveVapidKeyPair,
11
+ encodeMasterSecret,
12
+ formatVaultKey,
13
+ normalizeMasterSecret,
14
+ validateVaultKey,
15
+ } from './secrets.js';
5
16
  export {
17
+ decryptWithRotation,
6
18
  decrypt,
19
+ deriveBytes,
20
+ deriveInfraSecret,
21
+ deriveInfraSecrets,
22
+ deriveVapidKeyPair,
23
+ encodeMasterSecret,
7
24
  encrypt,
25
+ formatVaultKey,
8
26
  generateNanoId,
9
27
  generateUUID,
10
28
  hashPassword,
11
29
  jwtDecode,
12
30
  jwtEncode,
31
+ normalizeMasterSecret,
32
+ validateVaultKey,
13
33
  verifyPassword,
14
34
  };