@krantho/server 0.0.0-stage → 1.0.0

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 (147) hide show
  1. package/LICENSE +21 -0
  2. package/dist/cdn/cloudfront.d.ts +9 -0
  3. package/dist/cdn/cloudfront.d.ts.map +1 -0
  4. package/dist/cdn/cloudfront.js +35 -0
  5. package/dist/cdn/cloudfront.js.map +1 -0
  6. package/dist/cdn/gcp.d.ts +15 -0
  7. package/dist/cdn/gcp.d.ts.map +1 -0
  8. package/dist/cdn/gcp.js +60 -0
  9. package/dist/cdn/gcp.js.map +1 -0
  10. package/dist/cdn/index.d.ts +9 -0
  11. package/dist/cdn/index.d.ts.map +1 -0
  12. package/dist/cdn/index.js +30 -0
  13. package/dist/cdn/index.js.map +1 -0
  14. package/dist/cdn/listener.d.ts +14 -0
  15. package/dist/cdn/listener.d.ts.map +1 -0
  16. package/dist/cdn/listener.js +53 -0
  17. package/dist/cdn/listener.js.map +1 -0
  18. package/dist/cdn/retry.d.ts +3 -0
  19. package/dist/cdn/retry.d.ts.map +1 -0
  20. package/dist/cdn/retry.js +17 -0
  21. package/dist/cdn/retry.js.map +1 -0
  22. package/dist/cdn/types.d.ts +12 -0
  23. package/dist/cdn/types.d.ts.map +1 -0
  24. package/dist/cdn/types.js +2 -0
  25. package/dist/cdn/types.js.map +1 -0
  26. package/dist/cdn/vercel.d.ts +9 -0
  27. package/dist/cdn/vercel.d.ts.map +1 -0
  28. package/dist/cdn/vercel.js +31 -0
  29. package/dist/cdn/vercel.js.map +1 -0
  30. package/dist/content-conflicts.d.ts +18 -0
  31. package/dist/content-conflicts.d.ts.map +1 -0
  32. package/dist/content-conflicts.js +22 -0
  33. package/dist/content-conflicts.js.map +1 -0
  34. package/dist/content-dates.d.ts +35 -0
  35. package/dist/content-dates.d.ts.map +1 -0
  36. package/dist/content-dates.js +85 -0
  37. package/dist/content-dates.js.map +1 -0
  38. package/dist/content-loader.d.ts +37 -0
  39. package/dist/content-loader.d.ts.map +1 -0
  40. package/dist/content-loader.js +223 -0
  41. package/dist/content-loader.js.map +1 -0
  42. package/dist/content-migration.d.ts +101 -0
  43. package/dist/content-migration.d.ts.map +1 -0
  44. package/dist/content-migration.js +291 -0
  45. package/dist/content-migration.js.map +1 -0
  46. package/dist/convert.d.ts +18 -0
  47. package/dist/convert.d.ts.map +1 -0
  48. package/dist/convert.js +152 -0
  49. package/dist/convert.js.map +1 -0
  50. package/dist/file-store.d.ts +11 -0
  51. package/dist/file-store.d.ts.map +1 -0
  52. package/dist/file-store.js +39 -0
  53. package/dist/file-store.js.map +1 -0
  54. package/dist/file-watcher.d.ts +19 -0
  55. package/dist/file-watcher.d.ts.map +1 -0
  56. package/dist/file-watcher.js +99 -0
  57. package/dist/file-watcher.js.map +1 -0
  58. package/dist/handler.d.ts +61 -0
  59. package/dist/handler.d.ts.map +1 -0
  60. package/dist/handler.js +197 -0
  61. package/dist/handler.js.map +1 -0
  62. package/dist/import.d.ts +25 -0
  63. package/dist/import.d.ts.map +1 -0
  64. package/dist/import.js +71 -0
  65. package/dist/import.js.map +1 -0
  66. package/dist/index.d.ts +28 -0
  67. package/dist/index.d.ts.map +1 -0
  68. package/dist/index.js +19 -0
  69. package/dist/index.js.map +1 -0
  70. package/dist/indexnow.d.ts +38 -0
  71. package/dist/indexnow.d.ts.map +1 -0
  72. package/dist/indexnow.js +89 -0
  73. package/dist/indexnow.js.map +1 -0
  74. package/dist/lazy-handler.d.ts +7 -0
  75. package/dist/lazy-handler.d.ts.map +1 -0
  76. package/dist/lazy-handler.js +16 -0
  77. package/dist/lazy-handler.js.map +1 -0
  78. package/dist/llm-seed.d.ts +25 -0
  79. package/dist/llm-seed.d.ts.map +1 -0
  80. package/dist/llm-seed.js +42 -0
  81. package/dist/llm-seed.js.map +1 -0
  82. package/dist/lock-adapters.d.ts +21 -0
  83. package/dist/lock-adapters.d.ts.map +1 -0
  84. package/dist/lock-adapters.js +69 -0
  85. package/dist/lock-adapters.js.map +1 -0
  86. package/dist/manifest.d.ts +15 -0
  87. package/dist/manifest.d.ts.map +1 -0
  88. package/dist/manifest.js +39 -0
  89. package/dist/manifest.js.map +1 -0
  90. package/dist/mime-types.d.ts +3 -0
  91. package/dist/mime-types.d.ts.map +1 -0
  92. package/dist/mime-types.js +15 -0
  93. package/dist/mime-types.js.map +1 -0
  94. package/dist/page-cache-rules.d.ts +41 -0
  95. package/dist/page-cache-rules.d.ts.map +1 -0
  96. package/dist/page-cache-rules.js +94 -0
  97. package/dist/page-cache-rules.js.map +1 -0
  98. package/dist/page-cache-store.d.ts +11 -0
  99. package/dist/page-cache-store.d.ts.map +1 -0
  100. package/dist/page-cache-store.js +46 -0
  101. package/dist/page-cache-store.js.map +1 -0
  102. package/dist/page-request.d.ts +26 -0
  103. package/dist/page-request.d.ts.map +1 -0
  104. package/dist/page-request.js +29 -0
  105. package/dist/page-request.js.map +1 -0
  106. package/dist/persistence.d.ts +36 -0
  107. package/dist/persistence.d.ts.map +1 -0
  108. package/dist/persistence.js +108 -0
  109. package/dist/persistence.js.map +1 -0
  110. package/dist/preview-token.d.ts +9 -0
  111. package/dist/preview-token.d.ts.map +1 -0
  112. package/dist/preview-token.js +14 -0
  113. package/dist/preview-token.js.map +1 -0
  114. package/dist/read-cache.d.ts +17 -0
  115. package/dist/read-cache.d.ts.map +1 -0
  116. package/dist/read-cache.js +33 -0
  117. package/dist/read-cache.js.map +1 -0
  118. package/dist/route-matcher.d.ts +21 -0
  119. package/dist/route-matcher.d.ts.map +1 -0
  120. package/dist/route-matcher.js +75 -0
  121. package/dist/route-matcher.js.map +1 -0
  122. package/dist/secret-vault.d.ts +20 -0
  123. package/dist/secret-vault.d.ts.map +1 -0
  124. package/dist/secret-vault.js +60 -0
  125. package/dist/secret-vault.js.map +1 -0
  126. package/dist/seo-config-io.d.ts +51 -0
  127. package/dist/seo-config-io.d.ts.map +1 -0
  128. package/dist/seo-config-io.js +151 -0
  129. package/dist/seo-config-io.js.map +1 -0
  130. package/dist/seo-seed.d.ts +12 -0
  131. package/dist/seo-seed.d.ts.map +1 -0
  132. package/dist/seo-seed.js +100 -0
  133. package/dist/seo-seed.js.map +1 -0
  134. package/dist/studio-gate.d.ts +25 -0
  135. package/dist/studio-gate.d.ts.map +1 -0
  136. package/dist/studio-gate.js +70 -0
  137. package/dist/studio-gate.js.map +1 -0
  138. package/dist/studio.d.ts +17 -0
  139. package/dist/studio.d.ts.map +1 -0
  140. package/dist/studio.js +107 -0
  141. package/dist/studio.js.map +1 -0
  142. package/dist/webhook-sweep.d.ts +22 -0
  143. package/dist/webhook-sweep.d.ts.map +1 -0
  144. package/dist/webhook-sweep.js +33 -0
  145. package/dist/webhook-sweep.js.map +1 -0
  146. package/package.json +87 -3
  147. package/README.md +0 -3
@@ -0,0 +1,26 @@
1
+ /** How one page request reads content */
2
+ export interface PageRequestOptions {
3
+ /** Serve every content loader read from the current version, draft included */
4
+ preview: boolean;
5
+ }
6
+ /** What a page request rendered, and the cache tags of every read it made */
7
+ export interface PageRequestResult<T> {
8
+ value: T;
9
+ /** Deduplicated, in first-read order */
10
+ tags: string[];
11
+ }
12
+ /**
13
+ * Render one page inside a request scope.
14
+ *
15
+ * While `render` runs, every content loader read records its cache tags into
16
+ * the scope, and with `preview: true` every read is served from the loader's
17
+ * preview view. Each call has its own scope, so concurrent and nested renders
18
+ * never share tags or the preview flag. Outside a scope the loader behaves as
19
+ * it always does.
20
+ */
21
+ export declare function runPageRequest<T>(options: PageRequestOptions, render: () => Promise<T>): Promise<PageRequestResult<T>>;
22
+ /** Whether the active page request, if any, reads previews */
23
+ export declare function inPreviewRequest(): boolean;
24
+ /** Record a read's cache tags into the active page request; no-op outside one */
25
+ export declare function recordReadTags(tags: readonly string[]): void;
26
+ //# sourceMappingURL=page-request.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-request.d.ts","sourceRoot":"","sources":["../src/page-request.ts"],"names":[],"mappings":"AAEA,yCAAyC;AACzC,MAAM,WAAW,kBAAkB;IACjC,+EAA+E;IAC/E,OAAO,EAAE,OAAO,CAAA;CACjB;AAED,6EAA6E;AAC7E,MAAM,WAAW,iBAAiB,CAAC,CAAC;IAClC,KAAK,EAAE,CAAC,CAAA;IACR,wCAAwC;IACxC,IAAI,EAAE,MAAM,EAAE,CAAA;CACf;AAUD;;;;;;;;GAQG;AACH,wBAAsB,cAAc,CAAC,CAAC,EACpC,OAAO,EAAE,kBAAkB,EAC3B,MAAM,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACvB,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAI/B;AAED,8DAA8D;AAC9D,wBAAgB,gBAAgB,IAAI,OAAO,CAE1C;AAED,iFAAiF;AACjF,wBAAgB,cAAc,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAI5D"}
@@ -0,0 +1,29 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+ const scopes = new AsyncLocalStorage();
3
+ /**
4
+ * Render one page inside a request scope.
5
+ *
6
+ * While `render` runs, every content loader read records its cache tags into
7
+ * the scope, and with `preview: true` every read is served from the loader's
8
+ * preview view. Each call has its own scope, so concurrent and nested renders
9
+ * never share tags or the preview flag. Outside a scope the loader behaves as
10
+ * it always does.
11
+ */
12
+ export async function runPageRequest(options, render) {
13
+ const scope = { preview: options.preview, tags: new Set() };
14
+ const value = await scopes.run(scope, render);
15
+ return { value, tags: [...scope.tags] };
16
+ }
17
+ /** Whether the active page request, if any, reads previews */
18
+ export function inPreviewRequest() {
19
+ return scopes.getStore()?.preview ?? false;
20
+ }
21
+ /** Record a read's cache tags into the active page request; no-op outside one */
22
+ export function recordReadTags(tags) {
23
+ const scope = scopes.getStore();
24
+ if (!scope)
25
+ return;
26
+ for (const tag of tags)
27
+ scope.tags.add(tag);
28
+ }
29
+ //# sourceMappingURL=page-request.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-request.js","sourceRoot":"","sources":["../src/page-request.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AAqBpD,MAAM,MAAM,GAAG,IAAI,iBAAiB,EAAoB,CAAA;AAExD;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,OAA2B,EAC3B,MAAwB;IAExB,MAAM,KAAK,GAAqB,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,GAAG,EAAE,EAAE,CAAA;IAC7E,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,CAAA;IAC7C,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,CAAA;AACzC,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,gBAAgB;IAC9B,OAAO,MAAM,CAAC,QAAQ,EAAE,EAAE,OAAO,IAAI,KAAK,CAAA;AAC5C,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,cAAc,CAAC,IAAuB;IACpD,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,EAAE,CAAA;IAC/B,IAAI,CAAC,KAAK;QAAE,OAAM;IAClB,KAAK,MAAM,GAAG,IAAI,IAAI;QAAE,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;AAC7C,CAAC"}
@@ -0,0 +1,36 @@
1
+ import type { ContentSection, PersistenceSection, PostgresPersistenceSection, SiteSection } from '@krantho/config';
2
+ import type { ICheckpointAdapter, IContentMigrationAdapter, IPersistenceAdapter } from '@krantho/persistence';
3
+ /** Result of creating a persistence adapter */
4
+ export interface AdapterResult {
5
+ adapter: IPersistenceAdapter;
6
+ checkpointAdapter?: ICheckpointAdapter | undefined;
7
+ contentMigrationAdapter?: IContentMigrationAdapter | undefined;
8
+ authAdapter?: import('@krantho/persistence').IAuthAdapter | undefined;
9
+ /** Raw database handle for sub-adapter creation (SQLite Database or Postgres Sql) */
10
+ lockDb?: unknown | undefined;
11
+ schemaVersion: number;
12
+ }
13
+ /**
14
+ * Create a persistence adapter from configuration.
15
+ *
16
+ * Filesystem is loaded directly. SQLite and Postgres are loaded via dynamic import
17
+ * to avoid pulling in native modules when they're not needed.
18
+ *
19
+ * Every read and write that names no locale uses `site.defaultLocale`. The
20
+ * filesystem adapter keeps each collection's files in that collection's
21
+ * `directory`.
22
+ */
23
+ export declare function createAdapter(config: PersistenceSection, site: Pick<SiteSection, 'defaultLocale'>, content: Pick<ContentSection, 'defaultStatus'>, collections: ReadonlyMap<string, {
24
+ directory?: string | undefined;
25
+ }>): Promise<AdapterResult>;
26
+ /**
27
+ * Driver options from the configuration. Only the leaves that are set are
28
+ * passed, so an unset leaf keeps the driver's own default — including any
29
+ * value given as a query parameter on the connection URL.
30
+ */
31
+ export declare function postgresClientOptions(config: PostgresPersistenceSection): {
32
+ max?: number;
33
+ prepare?: boolean;
34
+ idle_timeout?: number;
35
+ };
36
+ //# sourceMappingURL=persistence.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"persistence.d.ts","sourceRoot":"","sources":["../src/persistence.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,kBAAkB,EAClB,0BAA0B,EAC1B,WAAW,EACZ,MAAM,iBAAiB,CAAA;AACxB,OAAO,KAAK,EACV,kBAAkB,EAClB,wBAAwB,EACxB,mBAAmB,EACpB,MAAM,sBAAsB,CAAA;AAG7B,+CAA+C;AAC/C,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,mBAAmB,CAAA;IAC5B,iBAAiB,CAAC,EAAE,kBAAkB,GAAG,SAAS,CAAA;IAClD,uBAAuB,CAAC,EAAE,wBAAwB,GAAG,SAAS,CAAA;IAC9D,WAAW,CAAC,EAAE,OAAO,sBAAsB,EAAE,YAAY,GAAG,SAAS,CAAA;IACrE,qFAAqF;IACrF,MAAM,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IAC5B,aAAa,EAAE,MAAM,CAAA;CACtB;AAED;;;;;;;;;GASG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,kBAAkB,EAC1B,IAAI,EAAE,IAAI,CAAC,WAAW,EAAE,eAAe,CAAC,EACxC,OAAO,EAAE,IAAI,CAAC,cAAc,EAAE,eAAe,CAAC,EAC9C,WAAW,EAAE,WAAW,CAAC,MAAM,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAAC,GACnE,OAAO,CAAC,aAAa,CAAC,CAyExB;AAqBD;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,0BAA0B,GAAG;IACzE,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB,CAMA"}
@@ -0,0 +1,108 @@
1
+ import { FileSystemAdapter } from '@krantho/persistence-fs';
2
+ /**
3
+ * Create a persistence adapter from configuration.
4
+ *
5
+ * Filesystem is loaded directly. SQLite and Postgres are loaded via dynamic import
6
+ * to avoid pulling in native modules when they're not needed.
7
+ *
8
+ * Every read and write that names no locale uses `site.defaultLocale`. The
9
+ * filesystem adapter keeps each collection's files in that collection's
10
+ * `directory`.
11
+ */
12
+ export async function createAdapter(config, site, content, collections) {
13
+ const defaultStatus = content.defaultStatus;
14
+ const defaultLocale = site.defaultLocale;
15
+ switch (config.provider) {
16
+ case 'filesystem':
17
+ return {
18
+ adapter: new FileSystemAdapter({
19
+ contentPath: config.filesystem.contentPath,
20
+ directories: collectionDirectories(collections),
21
+ defaultLocale,
22
+ defaultStatus: defaultStatus.filesystem,
23
+ }),
24
+ schemaVersion: 0,
25
+ };
26
+ case 'sqlite': {
27
+ const { SQLiteAdapter, SqliteCheckpointAdapter, SqliteContentMigrationAdapter, SqliteAuthAdapter, getSchemaVersion, } = await import('@krantho/persistence-sqlite');
28
+ const adapter = new SQLiteAdapter(config.sqlite.path, {
29
+ busyTimeoutMs: config.sqlite.busyTimeoutMs,
30
+ walRetries: config.sqlite.walRetries,
31
+ walRetryDelayMs: config.sqlite.walRetryDelayMs,
32
+ defaultLocale,
33
+ defaultStatus,
34
+ });
35
+ const checkpointAdapter = new SqliteCheckpointAdapter(adapter.database);
36
+ const contentMigrationAdapter = new SqliteContentMigrationAdapter(adapter.database);
37
+ const authAdapter = new SqliteAuthAdapter(adapter.database);
38
+ const schemaVersion = getSchemaVersion(adapter.database);
39
+ return {
40
+ adapter,
41
+ checkpointAdapter,
42
+ contentMigrationAdapter,
43
+ authAdapter,
44
+ lockDb: adapter.database,
45
+ schemaVersion,
46
+ };
47
+ }
48
+ case 'postgres': {
49
+ const { pg, adapter: { PostgresAdapter, PostgresContentMigrationAdapter, PostgresAuthAdapter, PostgresCheckpointAdapter, getSchemaVersion, }, } = await importPostgresPackages();
50
+ // Validated at resolve time: a postgres provider without a URL never reaches here.
51
+ const sql = pg.default(config.postgres.url, postgresClientOptions(config.postgres));
52
+ const adapter = await PostgresAdapter.create(sql, { defaultLocale, defaultStatus });
53
+ const checkpointAdapter = new PostgresCheckpointAdapter(sql);
54
+ const contentMigrationAdapter = new PostgresContentMigrationAdapter(sql);
55
+ const authAdapter = new PostgresAuthAdapter(sql);
56
+ const schemaVersion = await getSchemaVersion(sql);
57
+ return {
58
+ adapter,
59
+ checkpointAdapter,
60
+ contentMigrationAdapter,
61
+ authAdapter,
62
+ lockDb: sql,
63
+ schemaVersion,
64
+ };
65
+ }
66
+ }
67
+ }
68
+ /**
69
+ * Load the Postgres adapter package and driver. Both are optional peers of
70
+ * `@krantho/server`, so an app that selects the postgres provider must list
71
+ * them in its own dependencies.
72
+ */
73
+ async function importPostgresPackages() {
74
+ try {
75
+ const adapter = await import('@krantho/persistence-postgres');
76
+ const pg = await import('postgres');
77
+ return { adapter, pg };
78
+ }
79
+ catch (err) {
80
+ throw new Error('persistence.provider is "postgres", but @krantho/persistence-postgres or postgres could not be loaded. ' +
81
+ 'Add both to the dependencies of the app that runs krantho.', { cause: err });
82
+ }
83
+ }
84
+ /**
85
+ * Driver options from the configuration. Only the leaves that are set are
86
+ * passed, so an unset leaf keeps the driver's own default — including any
87
+ * value given as a query parameter on the connection URL.
88
+ */
89
+ export function postgresClientOptions(config) {
90
+ const options = {};
91
+ if (config.maxConnections !== undefined)
92
+ options.max = config.maxConnections;
93
+ if (config.prepare !== undefined)
94
+ options.prepare = config.prepare;
95
+ if (config.idleTimeoutSec !== undefined)
96
+ options.idle_timeout = config.idleTimeoutSec;
97
+ return options;
98
+ }
99
+ /** Each collection's directory, for the collections that name one */
100
+ function collectionDirectories(collections) {
101
+ const directories = new Map();
102
+ for (const [name, { directory }] of collections) {
103
+ if (directory)
104
+ directories.set(name, directory);
105
+ }
106
+ return directories;
107
+ }
108
+ //# sourceMappingURL=persistence.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"persistence.js","sourceRoot":"","sources":["../src/persistence.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAA;AAa3D;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,MAA0B,EAC1B,IAAwC,EACxC,OAA8C,EAC9C,WAAoE;IAEpE,MAAM,aAAa,GAAG,OAAO,CAAC,aAAa,CAAA;IAC3C,MAAM,aAAa,GAAG,IAAI,CAAC,aAAa,CAAA;IAExC,QAAQ,MAAM,CAAC,QAAQ,EAAE,CAAC;QACxB,KAAK,YAAY;YACf,OAAO;gBACL,OAAO,EAAE,IAAI,iBAAiB,CAAC;oBAC7B,WAAW,EAAE,MAAM,CAAC,UAAU,CAAC,WAAW;oBAC1C,WAAW,EAAE,qBAAqB,CAAC,WAAW,CAAC;oBAC/C,aAAa;oBACb,aAAa,EAAE,aAAa,CAAC,UAAU;iBACxC,CAAC;gBACF,aAAa,EAAE,CAAC;aACjB,CAAA;QAEH,KAAK,QAAQ,CAAC,CAAC,CAAC;YACd,MAAM,EACJ,aAAa,EACb,uBAAuB,EACvB,6BAA6B,EAC7B,iBAAiB,EACjB,gBAAgB,GACjB,GAAG,MAAM,MAAM,CAAC,6BAA6B,CAAC,CAAA;YAC/C,MAAM,OAAO,GAAG,IAAI,aAAa,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE;gBACpD,aAAa,EAAE,MAAM,CAAC,MAAM,CAAC,aAAa;gBAC1C,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC,UAAU;gBACpC,eAAe,EAAE,MAAM,CAAC,MAAM,CAAC,eAAe;gBAC9C,aAAa;gBACb,aAAa;aACd,CAAC,CAAA;YACF,MAAM,iBAAiB,GAAG,IAAI,uBAAuB,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAA;YACvE,MAAM,uBAAuB,GAAG,IAAI,6BAA6B,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAA;YACnF,MAAM,WAAW,GAAG,IAAI,iBAAiB,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAA;YAC3D,MAAM,aAAa,GAAG,gBAAgB,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAA;YACxD,OAAO;gBACL,OAAO;gBACP,iBAAiB;gBACjB,uBAAuB;gBACvB,WAAW;gBACX,MAAM,EAAE,OAAO,CAAC,QAAQ;gBACxB,aAAa;aACd,CAAA;QACH,CAAC;QAED,KAAK,UAAU,CAAC,CAAC,CAAC;YAChB,MAAM,EACJ,EAAE,EACF,OAAO,EAAE,EACP,eAAe,EACf,+BAA+B,EAC/B,mBAAmB,EACnB,yBAAyB,EACzB,gBAAgB,GACjB,GACF,GAAG,MAAM,sBAAsB,EAAE,CAAA;YAClC,mFAAmF;YACnF,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAa,EAAE,qBAAqB,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAA;YAC7F,MAAM,OAAO,GAAG,MAAM,eAAe,CAAC,MAAM,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,aAAa,EAAE,CAAC,CAAA;YACnF,MAAM,iBAAiB,GAAG,IAAI,yBAAyB,CAAC,GAAG,CAAC,CAAA;YAC5D,MAAM,uBAAuB,GAAG,IAAI,+BAA+B,CAAC,GAAG,CAAC,CAAA;YACxE,MAAM,WAAW,GAAG,IAAI,mBAAmB,CAAC,GAAG,CAAC,CAAA;YAChD,MAAM,aAAa,GAAG,MAAM,gBAAgB,CAAC,GAAG,CAAC,CAAA;YACjD,OAAO;gBACL,OAAO;gBACP,iBAAiB;gBACjB,uBAAuB;gBACvB,WAAW;gBACX,MAAM,EAAE,GAAG;gBACX,aAAa;aACd,CAAA;QACH,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,KAAK,UAAU,sBAAsB;IACnC,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,+BAA+B,CAAC,CAAA;QAC7D,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,CAAA;QACnC,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAA;IACxB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CACb,yGAAyG;YACvG,4DAA4D,EAC9D,EAAE,KAAK,EAAE,GAAG,EAAE,CACf,CAAA;IACH,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CAAC,MAAkC;IAKtE,MAAM,OAAO,GAA+D,EAAE,CAAA;IAC9E,IAAI,MAAM,CAAC,cAAc,KAAK,SAAS;QAAE,OAAO,CAAC,GAAG,GAAG,MAAM,CAAC,cAAc,CAAA;IAC5E,IAAI,MAAM,CAAC,OAAO,KAAK,SAAS;QAAE,OAAO,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO,CAAA;IAClE,IAAI,MAAM,CAAC,cAAc,KAAK,SAAS;QAAE,OAAO,CAAC,YAAY,GAAG,MAAM,CAAC,cAAc,CAAA;IACrF,OAAO,OAAO,CAAA;AAChB,CAAC;AAED,qEAAqE;AACrE,SAAS,qBAAqB,CAC5B,WAAoE;IAEpE,MAAM,WAAW,GAAG,IAAI,GAAG,EAAkB,CAAA;IAC7C,KAAK,MAAM,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,CAAC,IAAI,WAAW,EAAE,CAAC;QAChD,IAAI,SAAS;YAAE,WAAW,CAAC,GAAG,CAAC,IAAI,EAAE,SAAS,CAAC,CAAA;IACjD,CAAC;IACD,OAAO,WAAW,CAAA;AACpB,CAAC"}
@@ -0,0 +1,9 @@
1
+ import type { KranthoConfig } from '@krantho/config';
2
+ /**
3
+ * Whether a page request carries a valid signed preview token
4
+ * (`?krantho-preview=`, from `GET /api/preview` in `signed-url` mode): signed
5
+ * for this URL's path and not expired, with the secret the API signs with —
6
+ * `auth.jwtSecret`, or the process's ephemeral secret when none is configured.
7
+ */
8
+ export declare function verifyPreviewRequest(url: URL, config: Pick<KranthoConfig, 'auth'>): boolean;
9
+ //# sourceMappingURL=preview-token.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preview-token.d.ts","sourceRoot":"","sources":["../src/preview-token.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAEpD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,aAAa,EAAE,MAAM,CAAC,GAAG,OAAO,CAI3F"}
@@ -0,0 +1,14 @@
1
+ import { PREVIEW_PARAM, resolveJwtSecret, verifyPreviewToken } from '@krantho/api';
2
+ /**
3
+ * Whether a page request carries a valid signed preview token
4
+ * (`?krantho-preview=`, from `GET /api/preview` in `signed-url` mode): signed
5
+ * for this URL's path and not expired, with the secret the API signs with —
6
+ * `auth.jwtSecret`, or the process's ephemeral secret when none is configured.
7
+ */
8
+ export function verifyPreviewRequest(url, config) {
9
+ const token = url.searchParams.get(PREVIEW_PARAM);
10
+ if (!token)
11
+ return false;
12
+ return verifyPreviewToken(token, url.pathname, resolveJwtSecret(config.auth));
13
+ }
14
+ //# sourceMappingURL=preview-token.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preview-token.js","sourceRoot":"","sources":["../src/preview-token.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA;AAGlF;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAAQ,EAAE,MAAmC;IAChF,MAAM,KAAK,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,aAAa,CAAC,CAAA;IACjD,IAAI,CAAC,KAAK;QAAE,OAAO,KAAK,CAAA;IACxB,OAAO,kBAAkB,CAAC,KAAK,EAAE,GAAG,CAAC,QAAQ,EAAE,gBAAgB,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAA;AAC/E,CAAC"}
@@ -0,0 +1,17 @@
1
+ /** The content cache settings (`content.cache` in the configuration tree) */
2
+ export interface ReadCacheSettings {
3
+ enabled: boolean;
4
+ ttlMs: number;
5
+ maxEntries: number;
6
+ }
7
+ /** A keyed cache of reads, each kept for a time-to-live */
8
+ export interface ReadCache {
9
+ read<T>(key: string, load: () => Promise<T>): Promise<T>;
10
+ }
11
+ /**
12
+ * Cache reads per key for `ttlMs`, keeping at most `maxEntries` (oldest first
13
+ * out). Concurrent reads of one key share a single load; a failed load is not
14
+ * kept. Disabled, every read loads.
15
+ */
16
+ export declare function createReadCache(settings: ReadCacheSettings): ReadCache;
17
+ //# sourceMappingURL=read-cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"read-cache.d.ts","sourceRoot":"","sources":["../src/read-cache.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,2DAA2D;AAC3D,MAAM,WAAW,SAAS;IACxB,IAAI,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;CACzD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,iBAAiB,GAAG,SAAS,CA0BtE"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Cache reads per key for `ttlMs`, keeping at most `maxEntries` (oldest first
3
+ * out). Concurrent reads of one key share a single load; a failed load is not
4
+ * kept. Disabled, every read loads.
5
+ */
6
+ export function createReadCache(settings) {
7
+ const entries = new Map();
8
+ return {
9
+ read(key, load) {
10
+ if (!settings.enabled)
11
+ return load();
12
+ const entry = entries.get(key);
13
+ if (entry && Date.now() - entry.storedAt < settings.ttlMs) {
14
+ return entry.value;
15
+ }
16
+ const value = load();
17
+ entries.delete(key);
18
+ entries.set(key, { value, storedAt: Date.now() });
19
+ value.catch(() => {
20
+ if (entries.get(key)?.value === value)
21
+ entries.delete(key);
22
+ });
23
+ while (entries.size > settings.maxEntries) {
24
+ const oldest = entries.keys().next().value;
25
+ if (oldest === undefined)
26
+ break;
27
+ entries.delete(oldest);
28
+ }
29
+ return value;
30
+ },
31
+ };
32
+ }
33
+ //# sourceMappingURL=read-cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"read-cache.js","sourceRoot":"","sources":["../src/read-cache.ts"],"names":[],"mappings":"AAYA;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,QAA2B;IACzD,MAAM,OAAO,GAAG,IAAI,GAAG,EAAyD,CAAA;IAEhF,OAAO;QACL,IAAI,CAAI,GAAW,EAAE,IAAsB;YACzC,IAAI,CAAC,QAAQ,CAAC,OAAO;gBAAE,OAAO,IAAI,EAAE,CAAA;YAEpC,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;YAC9B,IAAI,KAAK,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,QAAQ,GAAG,QAAQ,CAAC,KAAK,EAAE,CAAC;gBAC1D,OAAO,KAAK,CAAC,KAAmB,CAAA;YAClC,CAAC;YAED,MAAM,KAAK,GAAG,IAAI,EAAE,CAAA;YACpB,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;YACnB,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAA;YACjD,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE;gBACf,IAAI,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,KAAK,KAAK,KAAK;oBAAE,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;YAC5D,CAAC,CAAC,CAAA;YACF,OAAO,OAAO,CAAC,IAAI,GAAG,QAAQ,CAAC,UAAU,EAAE,CAAC;gBAC1C,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAA;gBAC1C,IAAI,MAAM,KAAK,SAAS;oBAAE,MAAK;gBAC/B,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;YACxB,CAAC;YACD,OAAO,KAAK,CAAA;QACd,CAAC;KACF,CAAA;AACH,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Route-scope pattern matching and specificity scoring.
3
+ *
4
+ * Pattern syntax:
5
+ * - Literal segments: `/services/consulting` — exact match
6
+ * - Single-segment wildcard: `/services/*` — matches one segment
7
+ * - Multi-segment wildcard: `/services/**` — matches one or more segments
8
+ * (deliberately requires at least one segment, unlike standard glob zero-or-more)
9
+ *
10
+ * Specificity: more literal segments = higher specificity.
11
+ * Used to determine which route-scope config wins when multiple patterns match.
12
+ */
13
+ /** Test whether a URL path matches a route pattern. */
14
+ export declare function matchRoute(pattern: string, url: string): boolean;
15
+ /**
16
+ * Compute a specificity score for a route pattern.
17
+ * Higher score = more specific = higher precedence.
18
+ * Literal segments score 2, single wildcards 1, multi-wildcards 0.
19
+ */
20
+ export declare function routeSpecificity(pattern: string): number;
21
+ //# sourceMappingURL=route-matcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route-matcher.d.ts","sourceRoot":"","sources":["../src/route-matcher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,uDAAuD;AACvD,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAIhE;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAaxD"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Route-scope pattern matching and specificity scoring.
3
+ *
4
+ * Pattern syntax:
5
+ * - Literal segments: `/services/consulting` — exact match
6
+ * - Single-segment wildcard: `/services/*` — matches one segment
7
+ * - Multi-segment wildcard: `/services/**` — matches one or more segments
8
+ * (deliberately requires at least one segment, unlike standard glob zero-or-more)
9
+ *
10
+ * Specificity: more literal segments = higher specificity.
11
+ * Used to determine which route-scope config wins when multiple patterns match.
12
+ */
13
+ /** Test whether a URL path matches a route pattern. */
14
+ export function matchRoute(pattern, url) {
15
+ const patternParts = splitPath(pattern);
16
+ const urlParts = splitPath(url);
17
+ return matchParts(patternParts, urlParts, 0, 0);
18
+ }
19
+ /**
20
+ * Compute a specificity score for a route pattern.
21
+ * Higher score = more specific = higher precedence.
22
+ * Literal segments score 2, single wildcards 1, multi-wildcards 0.
23
+ */
24
+ export function routeSpecificity(pattern) {
25
+ const parts = splitPath(pattern);
26
+ let score = 0;
27
+ for (const part of parts) {
28
+ if (part === '**') {
29
+ // Globstar contributes nothing — least specific
30
+ }
31
+ else if (part === '*') {
32
+ score += 1;
33
+ }
34
+ else {
35
+ score += 2;
36
+ }
37
+ }
38
+ return score;
39
+ }
40
+ // ---------------------------------------------------------------------------
41
+ // Internals
42
+ // ---------------------------------------------------------------------------
43
+ function splitPath(path) {
44
+ return path.split('/').filter(Boolean);
45
+ }
46
+ function matchParts(pattern, url, pi, ui) {
47
+ // Both exhausted — match
48
+ if (pi === pattern.length && ui === url.length)
49
+ return true;
50
+ // Pattern exhausted, URL has more — no match
51
+ if (pi === pattern.length)
52
+ return false;
53
+ const segment = pattern[pi];
54
+ // Multi-segment wildcard — matches one or more remaining URL segments
55
+ if (segment === '**') {
56
+ for (let skip = 1; skip <= url.length - ui; skip++) {
57
+ if (matchParts(pattern, url, pi + 1, ui + skip))
58
+ return true;
59
+ }
60
+ return false;
61
+ }
62
+ // URL exhausted but pattern still has non-** segments — no match
63
+ if (ui === url.length)
64
+ return false;
65
+ // Single-segment wildcard — matches any one segment
66
+ if (segment === '*') {
67
+ return matchParts(pattern, url, pi + 1, ui + 1);
68
+ }
69
+ // Literal segment — must match exactly
70
+ if (segment === url[ui]) {
71
+ return matchParts(pattern, url, pi + 1, ui + 1);
72
+ }
73
+ return false;
74
+ }
75
+ //# sourceMappingURL=route-matcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route-matcher.js","sourceRoot":"","sources":["../src/route-matcher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,uDAAuD;AACvD,MAAM,UAAU,UAAU,CAAC,OAAe,EAAE,GAAW;IACrD,MAAM,YAAY,GAAG,SAAS,CAAC,OAAO,CAAC,CAAA;IACvC,MAAM,QAAQ,GAAG,SAAS,CAAC,GAAG,CAAC,CAAA;IAC/B,OAAO,UAAU,CAAC,YAAY,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;AACjD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAe;IAC9C,MAAM,KAAK,GAAG,SAAS,CAAC,OAAO,CAAC,CAAA;IAChC,IAAI,KAAK,GAAG,CAAC,CAAA;IACb,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;YAClB,gDAAgD;QAClD,CAAC;aAAM,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YACxB,KAAK,IAAI,CAAC,CAAA;QACZ,CAAC;aAAM,CAAC;YACN,KAAK,IAAI,CAAC,CAAA;QACZ,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED,8EAA8E;AAC9E,YAAY;AACZ,8EAA8E;AAE9E,SAAS,SAAS,CAAC,IAAY;IAC7B,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;AACxC,CAAC;AAED,SAAS,UAAU,CAAC,OAAiB,EAAE,GAAa,EAAE,EAAU,EAAE,EAAU;IAC1E,yBAAyB;IACzB,IAAI,EAAE,KAAK,OAAO,CAAC,MAAM,IAAI,EAAE,KAAK,GAAG,CAAC,MAAM;QAAE,OAAO,IAAI,CAAA;IAE3D,6CAA6C;IAC7C,IAAI,EAAE,KAAK,OAAO,CAAC,MAAM;QAAE,OAAO,KAAK,CAAA;IAEvC,MAAM,OAAO,GAAG,OAAO,CAAC,EAAE,CAAE,CAAA;IAE5B,sEAAsE;IACtE,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;QACrB,KAAK,IAAI,IAAI,GAAG,CAAC,EAAE,IAAI,IAAI,GAAG,CAAC,MAAM,GAAG,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC;YACnD,IAAI,UAAU,CAAC,OAAO,EAAE,GAAG,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,IAAI,CAAC;gBAAE,OAAO,IAAI,CAAA;QAC9D,CAAC;QACD,OAAO,KAAK,CAAA;IACd,CAAC;IAED,iEAAiE;IACjE,IAAI,EAAE,KAAK,GAAG,CAAC,MAAM;QAAE,OAAO,KAAK,CAAA;IAEnC,oDAAoD;IACpD,IAAI,OAAO,KAAK,GAAG,EAAE,CAAC;QACpB,OAAO,UAAU,CAAC,OAAO,EAAE,GAAG,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,CAAA;IACjD,CAAC;IAED,uCAAuC;IACvC,IAAI,OAAO,KAAK,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;QACxB,OAAO,UAAU,CAAC,OAAO,EAAE,GAAG,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,CAAA;IACjD,CAAC;IAED,OAAO,KAAK,CAAA;AACd,CAAC"}
@@ -0,0 +1,20 @@
1
+ import type { KranthoConfig } from '@krantho/config';
2
+ import type { ISecretVault } from '@krantho/persistence';
3
+ /**
4
+ * The vault that encrypts with a key from the environment: AES-256-GCM under a
5
+ * subkey derived (HKDF) from `key`, a fresh IV for each value, and the setting
6
+ * name as authenticated data, so a sealed value copied to another setting does
7
+ * not open.
8
+ */
9
+ export declare function createLocalSecretVault(key: string): ISecretVault;
10
+ /**
11
+ * Build the vault `secrets.provider` selects, or undefined when the provider has
12
+ * no key to work with — a site then stores no secrets in its database. The local
13
+ * vault refuses a signing-secret fallback shorter than `secrets.local.key` may be.
14
+ *
15
+ * A new vault (a cloud key service) is one provider name in `@krantho/config`, one
16
+ * package that implements `ISecretVault`, and one case here, loaded by a literal
17
+ * dynamic import like the media file stores.
18
+ */
19
+ export declare function createSecretVault(config: Pick<KranthoConfig, 'secrets' | 'auth'>): Promise<ISecretVault | undefined>;
20
+ //# sourceMappingURL=secret-vault.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secret-vault.d.ts","sourceRoot":"","sources":["../src/secret-vault.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAmB,MAAM,iBAAiB,CAAA;AACrE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAWxD;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,CAyBhE;AAED;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,IAAI,CAAC,aAAa,EAAE,SAAS,GAAG,MAAM,CAAC,GAC9C,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC,CAQnC"}
@@ -0,0 +1,60 @@
1
+ import { createCipheriv, createDecipheriv, hkdfSync, randomBytes } from 'node:crypto';
2
+ const IV_BYTES = 12;
3
+ const TAG_BYTES = 16;
4
+ /** The shortest key the vault accepts: HKDF derives a subkey, it does not stretch a weak secret */
5
+ const MIN_KEY_LENGTH = 32;
6
+ /** Subkey label: a key derived for stored secrets is never the key another purpose uses */
7
+ const KEY_PURPOSE = 'krantho stored secrets';
8
+ /**
9
+ * The vault that encrypts with a key from the environment: AES-256-GCM under a
10
+ * subkey derived (HKDF) from `key`, a fresh IV for each value, and the setting
11
+ * name as authenticated data, so a sealed value copied to another setting does
12
+ * not open.
13
+ */
14
+ export function createLocalSecretVault(key) {
15
+ const subkey = Buffer.from(hkdfSync('sha256', key, '', KEY_PURPOSE, 32));
16
+ return {
17
+ label: 'Local key',
18
+ async seal(name, plaintext) {
19
+ const iv = randomBytes(IV_BYTES);
20
+ const cipher = createCipheriv('aes-256-gcm', subkey, iv);
21
+ cipher.setAAD(Buffer.from(name));
22
+ const body = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
23
+ return Buffer.concat([iv, body, cipher.getAuthTag()]).toString('base64url');
24
+ },
25
+ async open(name, sealed) {
26
+ const raw = Buffer.from(sealed, 'base64url');
27
+ if (raw.length < IV_BYTES + TAG_BYTES)
28
+ return null;
29
+ try {
30
+ const decipher = createDecipheriv('aes-256-gcm', subkey, raw.subarray(0, IV_BYTES));
31
+ decipher.setAAD(Buffer.from(name));
32
+ decipher.setAuthTag(raw.subarray(raw.length - TAG_BYTES));
33
+ const body = raw.subarray(IV_BYTES, raw.length - TAG_BYTES);
34
+ return Buffer.concat([decipher.update(body), decipher.final()]).toString('utf8');
35
+ }
36
+ catch {
37
+ return null;
38
+ }
39
+ },
40
+ };
41
+ }
42
+ /**
43
+ * Build the vault `secrets.provider` selects, or undefined when the provider has
44
+ * no key to work with — a site then stores no secrets in its database. The local
45
+ * vault refuses a signing-secret fallback shorter than `secrets.local.key` may be.
46
+ *
47
+ * A new vault (a cloud key service) is one provider name in `@krantho/config`, one
48
+ * package that implements `ISecretVault`, and one case here, loaded by a literal
49
+ * dynamic import like the media file stores.
50
+ */
51
+ export async function createSecretVault(config) {
52
+ const provider = config.secrets.provider;
53
+ switch (provider) {
54
+ case 'local': {
55
+ const key = config.secrets.local.key ?? config.auth.jwtSecret;
56
+ return key && key.length >= MIN_KEY_LENGTH ? createLocalSecretVault(key) : undefined;
57
+ }
58
+ }
59
+ }
60
+ //# sourceMappingURL=secret-vault.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secret-vault.js","sourceRoot":"","sources":["../src/secret-vault.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * SEO configuration export and import, and seeding the stored configuration.
3
+ *
4
+ * Writes per-entity YAML files in the layout `@krantho/config/node` reads
5
+ * definition files from (`readSeoDefinitions`), so production state
6
+ * round-trips into a project's `seo.configDir`.
7
+ *
8
+ * Layout:
9
+ * krantho.seo/
10
+ * ├── site/ ← site-scope entities
11
+ * │ ├── professional-service.yaml
12
+ * │ ├── website.yaml
13
+ * │ ├── founder.person.yaml ← role.type.yaml
14
+ * │ └── _field-mappings.yaml
15
+ * ├── collections/{name}/ ← collection-scope entities
16
+ * └── routes/{slug-encoded}.yaml
17
+ */
18
+ import type { ISeoConfigAdapter } from '@krantho/persistence';
19
+ import type { SeoConfigData } from '@krantho/seo-shared';
20
+ /** What a seed writes: scopes of SEO configuration, as definition files or code define them */
21
+ export interface SeoSeedConfig {
22
+ /** Site-level configuration */
23
+ site?: SeoConfigData | null | undefined;
24
+ /** Per-collection configuration, keyed by collection name */
25
+ collections?: Record<string, SeoConfigData> | undefined;
26
+ /** Per-route configuration, keyed by URL pattern */
27
+ routes?: Record<string, SeoConfigData> | undefined;
28
+ }
29
+ export interface ExportResult {
30
+ scopesExported: number;
31
+ entitiesExported: number;
32
+ filesWritten: string[];
33
+ }
34
+ export interface ImportResult {
35
+ scopesImported: number;
36
+ entitiesImported: number;
37
+ skipped: string[];
38
+ errors: Array<{
39
+ file: string;
40
+ message: string;
41
+ }>;
42
+ }
43
+ /** Export all SEO config from the database to a directory. */
44
+ export declare function exportSeoConfig(adapter: ISeoConfigAdapter, targetDir: string): Promise<ExportResult>;
45
+ /** Import SEO config from a directory into the database. */
46
+ export declare function importSeoConfig(adapter: ISeoConfigAdapter, sourceDir: string, options?: {
47
+ mode?: 'seed' | 'overwrite' | undefined;
48
+ } | undefined): Promise<ImportResult>;
49
+ /** Apply a SeoSeedConfig to the database. */
50
+ export declare function applySeedConfig(adapter: ISeoConfigAdapter, config: SeoSeedConfig, mode?: 'seed' | 'overwrite'): Promise<ImportResult>;
51
+ //# sourceMappingURL=seo-config-io.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seo-config-io.d.ts","sourceRoot":"","sources":["../src/seo-config-io.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAKH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAA;AAC7D,OAAO,KAAK,EAAE,aAAa,EAAmB,MAAM,qBAAqB,CAAA;AAOzE,+FAA+F;AAC/F,MAAM,WAAW,aAAa;IAC5B,+BAA+B;IAC/B,IAAI,CAAC,EAAE,aAAa,GAAG,IAAI,GAAG,SAAS,CAAA;IACvC,6DAA6D;IAC7D,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,GAAG,SAAS,CAAA;IACvD,oDAAoD;IACpD,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,GAAG,SAAS,CAAA;CACnD;AAED,MAAM,WAAW,YAAY;IAC3B,cAAc,EAAE,MAAM,CAAA;IACtB,gBAAgB,EAAE,MAAM,CAAA;IACxB,YAAY,EAAE,MAAM,EAAE,CAAA;CACvB;AAED,MAAM,WAAW,YAAY;IAC3B,cAAc,EAAE,MAAM,CAAA;IACtB,gBAAgB,EAAE,MAAM,CAAA;IACxB,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,MAAM,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CACjD;AAMD,8DAA8D;AAC9D,wBAAsB,eAAe,CACnC,OAAO,EAAE,iBAAiB,EAC1B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,YAAY,CAAC,CAoDvB;AAMD,4DAA4D;AAC5D,wBAAsB,eAAe,CACnC,OAAO,EAAE,iBAAiB,EAC1B,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAAA;CAAE,GAAG,SAAS,GAChE,OAAO,CAAC,YAAY,CAAC,CAGvB;AAMD,6CAA6C;AAC7C,wBAAsB,eAAe,CACnC,OAAO,EAAE,iBAAiB,EAC1B,MAAM,EAAE,aAAa,EACrB,IAAI,GAAE,MAAM,GAAG,WAAoB,GAClC,OAAO,CAAC,YAAY,CAAC,CAmCvB"}