@rebasepro/server 0.16.0 → 0.16.1-canary.g0d7af95

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 (219) hide show
  1. package/dist/{GCSStorageController-BEmDYFKc.js → GCSStorageController-Bl7nmhdv.js} +7 -6
  2. package/dist/{GCSStorageController-BEmDYFKc.js.map → GCSStorageController-Bl7nmhdv.js.map} +1 -1
  3. package/dist/{S3StorageController-B2EDXXMH.js → S3StorageController-CWvvrRpF.js} +7 -6
  4. package/dist/{S3StorageController-B2EDXXMH.js.map → S3StorageController-CWvvrRpF.js.map} +1 -1
  5. package/dist/{admin_block-DLILvzle.js → admin_block-BeypnEfb.js} +6 -6
  6. package/dist/admin_block-BeypnEfb.js.map +1 -0
  7. package/dist/api/ast-schema-editor.d.ts +23 -0
  8. package/dist/api/contract-routes.d.ts +1 -1
  9. package/dist/api/errors.d.ts +1 -1
  10. package/dist/api/index.d.ts +3 -3
  11. package/dist/api/live-schema-routes.d.ts +122 -0
  12. package/dist/api/logs-routes.d.ts +1 -1
  13. package/dist/api/mount.d.ts +41 -0
  14. package/dist/api/rest/api-generator.d.ts +2 -2
  15. package/dist/api/rest/index.d.ts +1 -1
  16. package/dist/api/rest/query-parser.d.ts +1 -1
  17. package/dist/api/schema-editor-routes.d.ts +1 -1
  18. package/dist/api/types.d.ts +12 -4
  19. package/dist/{schema-editor-routes-CV9k0w3G.js → ast-schema-editor-BrarYZCq.js} +37 -45
  20. package/dist/ast-schema-editor-BrarYZCq.js.map +1 -0
  21. package/dist/auth/adapter-middleware.d.ts +2 -2
  22. package/dist/auth/admin-roles-route.d.ts +2 -2
  23. package/dist/auth/admin-user-ops.d.ts +3 -3
  24. package/dist/auth/admin-users-route.d.ts +4 -6
  25. package/dist/auth/api-keys/api-key-middleware.d.ts +2 -2
  26. package/dist/auth/api-keys/api-key-permission-guard.d.ts +1 -1
  27. package/dist/auth/api-keys/api-key-routes.d.ts +2 -2
  28. package/dist/auth/api-keys/api-key-store.d.ts +1 -1
  29. package/dist/auth/api-keys/index.d.ts +9 -9
  30. package/dist/auth/apple-oauth.d.ts +2 -2
  31. package/dist/auth/auth-hooks.d.ts +4 -4
  32. package/dist/auth/bitbucket-oauth.d.ts +2 -2
  33. package/dist/auth/builtin-auth-adapter.d.ts +8 -4
  34. package/dist/auth/capabilities.d.ts +51 -0
  35. package/dist/auth/captcha.d.ts +86 -0
  36. package/dist/auth/cookie-utils.d.ts +2 -2
  37. package/dist/auth/discord-oauth.d.ts +2 -2
  38. package/dist/auth/facebook-oauth.d.ts +2 -2
  39. package/dist/auth/github-oauth.d.ts +2 -2
  40. package/dist/auth/gitlab-oauth.d.ts +2 -2
  41. package/dist/auth/google-oauth.d.ts +1 -1
  42. package/dist/auth/index.d.ts +58 -54
  43. package/dist/auth/jwks-routes.d.ts +1 -1
  44. package/dist/auth/jwt.d.ts +1 -1
  45. package/dist/auth/linkedin-oauth.d.ts +2 -2
  46. package/dist/auth/magic-link-routes.d.ts +10 -3
  47. package/dist/auth/mfa-gate.d.ts +1 -1
  48. package/dist/auth/mfa-routes.d.ts +3 -3
  49. package/dist/auth/microsoft-oauth.d.ts +2 -2
  50. package/dist/auth/middleware.d.ts +6 -7
  51. package/dist/auth/otp-routes.d.ts +89 -0
  52. package/dist/auth/rate-limiter.d.ts +2 -2
  53. package/dist/auth/require-auth.d.ts +1 -1
  54. package/dist/auth/reset-password-admin.d.ts +4 -4
  55. package/dist/auth/routes.d.ts +18 -10
  56. package/dist/auth/session-routes.d.ts +3 -3
  57. package/dist/auth/slack-oauth.d.ts +2 -2
  58. package/dist/auth/spotify-oauth.d.ts +2 -2
  59. package/dist/auth/token-revocation.d.ts +2 -2
  60. package/dist/auth/twitter-oauth.d.ts +2 -2
  61. package/dist/{auth-5Et5mnUA.js → auth-DU-nUjPp.js} +604 -4376
  62. package/dist/auth-DU-nUjPp.js.map +1 -0
  63. package/dist/backup/backup-common.d.ts +1 -1
  64. package/dist/backup/backup-routes.d.ts +3 -3
  65. package/dist/backup/index.d.ts +3 -3
  66. package/dist/{backup-C6ljYVTp.js → backup-CRdZkA6c.js} +7 -6
  67. package/dist/{backup-C6ljYVTp.js.map → backup-CRdZkA6c.js.map} +1 -1
  68. package/dist/boot/boot.d.ts +5 -5
  69. package/dist/boot/bundle.d.ts +10 -1
  70. package/dist/boot/driver.d.ts +1 -1
  71. package/dist/boot/env.d.ts +28 -1
  72. package/dist/boot/fetch-bundle.d.ts +144 -1
  73. package/dist/boot/options.d.ts +30 -6
  74. package/dist/boot/resource-adapters.d.ts +31 -0
  75. package/dist/boot/resource-loading.d.ts +10 -0
  76. package/dist/boot/role.d.ts +3 -2
  77. package/dist/boot/sources.d.ts +8 -2
  78. package/dist/boot/version-skew.d.ts +1 -1
  79. package/dist/collections/BackendCollectionRegistry.d.ts +1 -1
  80. package/dist/collections/loader.d.ts +1 -1
  81. package/dist/collections/validate-config.d.ts +13 -0
  82. package/dist/{contract-routes-DZ-LBpSL.js → contract-routes-D8TvxPLo.js} +8 -7
  83. package/dist/contract-routes-D8TvxPLo.js.map +1 -0
  84. package/dist/cron/cron-routes.d.ts +2 -2
  85. package/dist/cron/cron-scheduler.d.ts +3 -3
  86. package/dist/cron/index.d.ts +8 -8
  87. package/dist/{cron-loader-YhhQeVBM.js → cron-loader-d9WMFENB.js} +8 -7
  88. package/dist/{cron-loader-YhhQeVBM.js.map → cron-loader-d9WMFENB.js.map} +1 -1
  89. package/dist/{cron-routes-maM_RlUu.js → cron-routes-BsukhMGm.js} +9 -8
  90. package/dist/cron-routes-BsukhMGm.js.map +1 -0
  91. package/dist/{cron-scheduler-DIpYBmZP.js → cron-scheduler-BB82dWuU.js} +9 -9
  92. package/dist/cron-scheduler-BB82dWuU.js.map +1 -0
  93. package/dist/{cron-store-DfH_4Cd9.js → cron-store-CbFfQhbg.js} +11 -9
  94. package/dist/{cron-store-DfH_4Cd9.js.map → cron-store-CbFfQhbg.js.map} +1 -1
  95. package/dist/ddl-bootstrap-5YZCZ8qk.js +146 -0
  96. package/dist/ddl-bootstrap-5YZCZ8qk.js.map +1 -0
  97. package/dist/deploy/pod-contract.d.ts +139 -0
  98. package/dist/dev-secrets.d.ts +52 -0
  99. package/dist/{dynamic-import-Dvh-K5fl.js → dynamic-import-X40pTZUQ.js} +5 -4
  100. package/dist/{dynamic-import-Dvh-K5fl.js.map → dynamic-import-X40pTZUQ.js.map} +1 -1
  101. package/dist/email/dev-sink.d.ts +88 -0
  102. package/dist/email/index.d.ts +9 -7
  103. package/dist/email/link-base.d.ts +1 -1
  104. package/dist/email/smtp-email-service.d.ts +2 -2
  105. package/dist/email/templates.d.ts +12 -0
  106. package/dist/email/types.d.ts +18 -6
  107. package/dist/env.d.ts +1 -1
  108. package/dist/{errors-EBYiaJ2E.js → errors-DBwpj9N8.js} +8 -7
  109. package/dist/errors-DBwpj9N8.js.map +1 -0
  110. package/dist/{function-loader-DDS1v7YX.js → function-loader-LLdmBFoL.js} +9 -8
  111. package/dist/{function-loader-DDS1v7YX.js.map → function-loader-LLdmBFoL.js.map} +1 -1
  112. package/dist/{function-routes-Btcez1T-.js → function-routes-ClT6UQpD.js} +8 -7
  113. package/dist/function-routes-ClT6UQpD.js.map +1 -0
  114. package/dist/functions/context.d.ts +141 -0
  115. package/dist/functions/define-function.d.ts +1 -1
  116. package/dist/functions/function-routes.d.ts +9 -3
  117. package/dist/functions/guards.d.ts +76 -0
  118. package/dist/functions/index.d.ts +96 -5
  119. package/dist/functions/index.js +919 -0
  120. package/dist/functions/index.js.map +1 -0
  121. package/dist/functions/internal.d.ts +26 -0
  122. package/dist/functions/proxy.d.ts +1 -1
  123. package/dist/functions/request-timeout.d.ts +9 -1
  124. package/dist/functions/runtime-env.d.ts +92 -0
  125. package/dist/functions/wait-until.d.ts +74 -0
  126. package/dist/history/history-routes.d.ts +2 -2
  127. package/dist/history/index.d.ts +1 -1
  128. package/dist/history-recorder-B67maiTi.js +76 -0
  129. package/dist/history-recorder-B67maiTi.js.map +1 -0
  130. package/dist/history-store-oiqhb3HU.js +210 -0
  131. package/dist/history-store-oiqhb3HU.js.map +1 -0
  132. package/dist/index.d.ts +65 -54
  133. package/dist/index.es.js +3667 -328
  134. package/dist/index.es.js.map +1 -1
  135. package/dist/init/docs.d.ts +1 -1
  136. package/dist/init/middlewares.d.ts +1 -1
  137. package/dist/init/shutdown.d.ts +4 -0
  138. package/dist/init/storage.d.ts +1 -1
  139. package/dist/init/surfaces.d.ts +10 -0
  140. package/dist/init.d.ts +179 -22
  141. package/dist/internal-tables-DpxfPaEB.js +97 -0
  142. package/dist/internal-tables-DpxfPaEB.js.map +1 -0
  143. package/dist/jobs/index.d.ts +5 -5
  144. package/dist/{jobs-CyOKXXlu.js → jobs-B2ELModo.js} +11 -9
  145. package/dist/{jobs-CyOKXXlu.js.map → jobs-B2ELModo.js.map} +1 -1
  146. package/dist/{jwt-DxH9fLPt.js → jwt-DoHkMMWF.js} +9 -8
  147. package/dist/jwt-DoHkMMWF.js.map +1 -0
  148. package/dist/{logger-DfvF_8r-.js → logger-DS03e908.js} +104 -10
  149. package/dist/logger-DS03e908.js.map +1 -0
  150. package/dist/{logs-routes-CWBLQj2l.js → logs-routes-DB72iQSr.js} +36 -9
  151. package/dist/logs-routes-DB72iQSr.js.map +1 -0
  152. package/dist/metrics/history-recorder.d.ts +18 -0
  153. package/dist/metrics/history-store.d.ts +123 -0
  154. package/dist/metrics/index.d.ts +10 -2
  155. package/dist/{openapi-generator-BCKJRUS4.js → openapi-generator-FFGT0H6O.js} +7 -11
  156. package/dist/{openapi-generator-BCKJRUS4.js.map → openapi-generator-FFGT0H6O.js.map} +1 -1
  157. package/dist/{proxy-Bj5DVllb.js → proxy-CMymhnwG.js} +9 -6
  158. package/dist/{proxy-Bj5DVllb.js.map → proxy-CMymhnwG.js.map} +1 -1
  159. package/dist/query-parser--nstjRCh.js +355 -0
  160. package/dist/query-parser--nstjRCh.js.map +1 -0
  161. package/dist/{request-timeout-BuFoEKwT.js → request-timeout-C8gkc-j7.js} +21 -6
  162. package/dist/request-timeout-C8gkc-j7.js.map +1 -0
  163. package/dist/rls-audit/index.d.ts +111 -0
  164. package/dist/{rolldown-runtime-DSJWtz9O.js → rolldown-runtime-dW7B1o5h.js} +4 -3
  165. package/dist/schema-edit/apply-schema-change.d.ts +141 -0
  166. package/dist/schema-edit/github-repository.d.ts +67 -0
  167. package/dist/schema-edit/local-git-repository.d.ts +28 -0
  168. package/dist/schema-edit/project-root.d.ts +43 -0
  169. package/dist/schema-edit/remote-source.d.ts +25 -0
  170. package/dist/schema-edit/schema-edit-permissions.d.ts +129 -0
  171. package/dist/schema-editor-routes-Bf5h5Emf.js +87 -0
  172. package/dist/schema-editor-routes-Bf5h5Emf.js.map +1 -0
  173. package/dist/schemas-DBxgjM9A.js +4153 -0
  174. package/dist/schemas-DBxgjM9A.js.map +1 -0
  175. package/dist/{selection-_z6TM1DB.js → selection-CRpqKUbt.js} +6 -5
  176. package/dist/{selection-_z6TM1DB.js.map → selection-CRpqKUbt.js.map} +1 -1
  177. package/dist/services/webhook-service.d.ts +1 -1
  178. package/dist/singleton.d.ts +7 -0
  179. package/dist/{src-BPfYOeN4.js → src-B-CmIFMr.js} +8 -6
  180. package/dist/src-B-CmIFMr.js.map +1 -0
  181. package/dist/{src-CrCxd8km.js → src-Cdsw7DqV.js} +126 -5
  182. package/dist/src-Cdsw7DqV.js.map +1 -0
  183. package/dist/storage/GCSStorageController.d.ts +1 -1
  184. package/dist/storage/LocalStorageController.d.ts +1 -1
  185. package/dist/storage/S3StorageController.d.ts +1 -1
  186. package/dist/storage/cache-headers.d.ts +87 -0
  187. package/dist/storage/index.d.ts +11 -11
  188. package/dist/storage/path-pattern.d.ts +58 -0
  189. package/dist/storage/policies.d.ts +88 -0
  190. package/dist/storage/range.d.ts +63 -0
  191. package/dist/storage/rendition-cache.d.ts +45 -0
  192. package/dist/storage/routes.d.ts +17 -5
  193. package/dist/storage/storage-registry.d.ts +1 -1
  194. package/dist/storage/triggers.d.ts +66 -0
  195. package/dist/storage/tus-handler.d.ts +34 -3
  196. package/dist/topics/runtime.d.ts +68 -0
  197. package/dist/{types-DSnOC4mF.js → types-BfKcm9do.js} +5 -4
  198. package/dist/{types-DSnOC4mF.js.map → types-BfKcm9do.js.map} +1 -1
  199. package/dist/utils/host.d.ts +58 -0
  200. package/dist/utils/logger.d.ts +0 -15
  201. package/dist/utils/request-id.d.ts +1 -1
  202. package/functions/package.json +24 -0
  203. package/package.json +13 -7
  204. package/dist/admin_block-DLILvzle.js.map +0 -1
  205. package/dist/auth-5Et5mnUA.js.map +0 -1
  206. package/dist/contract-routes-DZ-LBpSL.js.map +0 -1
  207. package/dist/cron-routes-maM_RlUu.js.map +0 -1
  208. package/dist/cron-scheduler-DIpYBmZP.js.map +0 -1
  209. package/dist/ddl-bootstrap-Cywoj8Ta.js +0 -221
  210. package/dist/ddl-bootstrap-Cywoj8Ta.js.map +0 -1
  211. package/dist/errors-EBYiaJ2E.js.map +0 -1
  212. package/dist/function-routes-Btcez1T-.js.map +0 -1
  213. package/dist/jwt-DxH9fLPt.js.map +0 -1
  214. package/dist/logger-DfvF_8r-.js.map +0 -1
  215. package/dist/logs-routes-CWBLQj2l.js.map +0 -1
  216. package/dist/request-timeout-BuFoEKwT.js.map +0 -1
  217. package/dist/schema-editor-routes-CV9k0w3G.js.map +0 -1
  218. package/dist/src-BPfYOeN4.js.map +0 -1
  219. package/dist/src-CrCxd8km.js.map +0 -1
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Commit the schema change, then apply it.
3
+ *
4
+ * The order is the load-bearing part of this module and the reason it exists as
5
+ * a unit rather than as two calls a caller makes in whichever order.
6
+ *
7
+ * The two failure directions are not symmetric:
8
+ *
9
+ * - **Apply first, commit fails.** The database has a column the repository does
10
+ * not describe. The ensure path is strictly additive — it never drops
11
+ * anything — so the next deploy neither removes the column nor mentions it. It
12
+ * sits there, absent from the collections and invisible to the API, until
13
+ * somebody goes looking. Nothing in the system detects this state.
14
+ * - **Commit first, apply fails.** The repository describes a column the
15
+ * database does not have. That is the ordinary state of every project between
16
+ * an edit and a deploy, and boot's ensure reconciles it on the next start.
17
+ *
18
+ * So: commit, then apply. The bad half of the dual write lands in the direction
19
+ * the system already handles, and a failed apply is reported as a *state* rather
20
+ * than thrown as an error — because "committed, will apply on next boot" is not
21
+ * a failure, it is a slower success.
22
+ *
23
+ * ## Both dependencies are injected
24
+ *
25
+ * `git` and `apply` are interfaces, not imports. That keeps this testable
26
+ * without a repository or a database — which matters, because the thing worth
27
+ * testing here is the ordering and what happens when half of it fails, and both
28
+ * are impossible to exercise against real infrastructure on demand.
29
+ */
30
+ import type { SchemaChangeFile, SchemaChangePlan, ClassifiedSchemaChanges } from "@rebasepro/types";
31
+ /** The working tree the commit lands in. */
32
+ export interface SchemaEditRepository {
33
+ /** Absolute path, for reporting. */
34
+ root: string;
35
+ /** The branch the commit will land on. */
36
+ currentBranch(): Promise<string>;
37
+ /**
38
+ * Paths that are already modified and not ours.
39
+ *
40
+ * A commit that sweeps up somebody's half-finished work is worse than a
41
+ * refusal, and this is the one thing a schema editor cannot see coming.
42
+ */
43
+ dirtyPaths(): Promise<string[]>;
44
+ /** Write every file, creating directories as needed. */
45
+ writeFiles(files: SchemaChangeFile[]): Promise<void>;
46
+ /** Stage exactly these paths and commit them. Returns the new sha. */
47
+ commit(paths: string[], message: string): Promise<string>;
48
+ /**
49
+ * The current contents of one file, or `undefined` when it does not exist.
50
+ *
51
+ * Needed by a deployment whose source is not on the machine. The AST editor
52
+ * rewrites a collection *file*, so a bundle — which ships compiled output —
53
+ * has nothing for it to open; the file has to come from the repository
54
+ * first. A missing file is not an error: a new collection has no source yet,
55
+ * and the editor creates one.
56
+ *
57
+ * Optional. A local working tree could implement it and does not need to:
58
+ * the file is already on the disk the editor reads.
59
+ */
60
+ readFile?(path: string): Promise<string | undefined>;
61
+ }
62
+ /** Runs the DDL. Separate from the repository so neither knows about the other. */
63
+ export type SchemaEditApply = (statements: string[]) => Promise<void>;
64
+ export interface SchemaEditInput {
65
+ /**
66
+ * What to write and run, from `admin.planSchemaChange`.
67
+ *
68
+ * Taken as a plan rather than as collections because planning is
69
+ * engine-specific and this module is not: it commits files and runs
70
+ * statements, and does not care which database rendered them.
71
+ */
72
+ plan: SchemaChangePlan;
73
+ repository: SchemaEditRepository;
74
+ apply: SchemaEditApply;
75
+ /**
76
+ * Rewrite the collection source, returning the files it touched.
77
+ *
78
+ * Called from **inside** this module, after the dirty-tree check and before
79
+ * the commit. It has to be: the AST editor writes through the filesystem
80
+ * rather than through {@link SchemaEditRepository}, so a caller that wrote
81
+ * first and handed the files over would already have made the tree dirty,
82
+ * and the check below would refuse the change on the evidence of its own
83
+ * edit.
84
+ *
85
+ * That is not hypothetical — it is what `/apply` did. Every change was
86
+ * refused with a dirty tree, because `git status --porcelain` reports
87
+ * untracked files too and a new collection's source file is always
88
+ * untracked. The rewritten file was left behind either way, so the retry
89
+ * found a dirty tree as well and the surface could never succeed.
90
+ *
91
+ * Optional: a caller holding the contents already, with no disk to write
92
+ * them through, puts them on `plan.files` and omits this.
93
+ */
94
+ writeSource?: () => Promise<SchemaChangeFile[]>;
95
+ /**
96
+ * The paths {@link writeSource} is going to touch.
97
+ *
98
+ * Needed *before* it runs so they can join the dirty check — the point of
99
+ * which is to read the tree before this change has touched it. The caller
100
+ * can derive them (`<collectionsDir>/<id>.ts`) without writing anything.
101
+ */
102
+ sourcePaths?: string[];
103
+ }
104
+ export interface SchemaEditResult {
105
+ committed: {
106
+ sha: string;
107
+ branch: string;
108
+ files: string[];
109
+ };
110
+ /** True when the DDL ran. False means committed and pending a boot. */
111
+ applied: boolean;
112
+ /** Why the apply did not run, when it did not. Never a reason to fail. */
113
+ applyError?: string;
114
+ statements: string[];
115
+ classified: ClassifiedSchemaChanges;
116
+ /** What to tell the person who pressed the button. */
117
+ summary: string;
118
+ }
119
+ /**
120
+ * The plan says the change is not applicable.
121
+ *
122
+ * Re-checked here rather than trusted from the planner: this module is the one
123
+ * that writes and runs things, so the guarantee belongs where the consequence
124
+ * is. A caller that built a plan by hand cannot route around it.
125
+ */
126
+ export declare class UnapplicableChangeError extends Error {
127
+ readonly classified: ClassifiedSchemaChanges;
128
+ constructor(message: string, classified: ClassifiedSchemaChanges);
129
+ }
130
+ export declare class DirtyWorkingTreeError extends Error {
131
+ readonly paths: string[];
132
+ constructor(paths: string[]);
133
+ }
134
+ /**
135
+ * Generate the commit, land it, then run the DDL.
136
+ *
137
+ * Throws only for things that mean *nothing happened*: a change the ensure path
138
+ * cannot express, a dirty tree, or a failed commit. Once the commit lands,
139
+ * every outcome is a result.
140
+ */
141
+ export declare function applySchemaChange(input: SchemaEditInput): Promise<SchemaEditResult>;
@@ -0,0 +1,67 @@
1
+ import type { SchemaEditRepository } from "./apply-schema-change.js";
2
+ /**
3
+ * How to authenticate to GitHub.
4
+ *
5
+ * Two ways, because there are two deployments. A Cloud tenant is one of many
6
+ * behind a single app, so it authenticates as an *installation* of that app and
7
+ * holds no credential of its own. A self-hoster running a bundle has the same
8
+ * problem — no source on the machine — and no app: standing one up so that a
9
+ * server can commit to a repository they already own would be a great deal of
10
+ * ceremony around a one-line credential.
11
+ *
12
+ * Both end in the same place. Everything past the bearer token is the plain Git
13
+ * Data API and is identical for either, which is the point: the cloud is a
14
+ * better implementation of this interface, never the only one.
15
+ */
16
+ export type GitHubAuth = {
17
+ kind: "app";
18
+ /** Numeric app id, or the client id — GitHub accepts either as `iss`. */
19
+ appId: string;
20
+ /**
21
+ * The app's private key, PEM.
22
+ *
23
+ * Accepts the literal PEM, a PEM with escaped newlines, or base64 —
24
+ * pass it through `normalizePemFromEnv` first, which is what the boot
25
+ * path does.
26
+ */
27
+ privateKey: string;
28
+ /** Which installation to act as. Produced when the app is installed. */
29
+ installationId: string;
30
+ } | {
31
+ kind: "token";
32
+ /**
33
+ * A personal access token, or a fine-grained token with write access to
34
+ * the repository's contents. Used as the bearer directly — there is
35
+ * nothing to exchange and nothing to cache.
36
+ */
37
+ token: string;
38
+ };
39
+ export interface GitHubRepositoryOptions {
40
+ auth: GitHubAuth;
41
+ owner: string;
42
+ repo: string;
43
+ /** Branch to commit onto. */
44
+ branch: string;
45
+ /** Attribution for the commit. */
46
+ author?: {
47
+ name: string;
48
+ email: string;
49
+ };
50
+ /** Injected for tests. Defaults to the global. */
51
+ fetchImpl?: typeof fetch;
52
+ /** Injected so token expiry is testable without waiting an hour. */
53
+ now?: () => number;
54
+ }
55
+ export declare class GitHubApiError extends Error {
56
+ readonly status: number;
57
+ readonly path: string;
58
+ constructor(status: number, path: string, detail: string);
59
+ }
60
+ /**
61
+ * A JWT authenticating the app itself.
62
+ *
63
+ * `iat` is backdated by a minute: GitHub rejects a token whose `iat` is in its
64
+ * future, and a server clock a few seconds fast is enough to trigger it.
65
+ */
66
+ export declare function createAppJwt(appId: string, privateKey: string, nowMs: number): string;
67
+ export declare function createGitHubRepository(options: GitHubRepositoryOptions): SchemaEditRepository;
@@ -0,0 +1,28 @@
1
+ import type { SchemaEditRepository } from "./apply-schema-change.js";
2
+ export interface LocalGitOptions {
3
+ /** Absolute path to the working tree the collections live in. */
4
+ root: string;
5
+ /**
6
+ * Who the commit is attributed to. Defaults to the repository's own git
7
+ * config, which is right for a developer's machine and wrong for a server
8
+ * — a panel edit should say which account made it.
9
+ */
10
+ author?: {
11
+ name: string;
12
+ email: string;
13
+ };
14
+ }
15
+ export declare class GitCommandError extends Error {
16
+ constructor(command: string, detail: string);
17
+ }
18
+ /**
19
+ * The top of the working tree containing `startDir`, or undefined.
20
+ *
21
+ * Resolved by asking git rather than by walking for a `.git`, because a
22
+ * worktree's `.git` is a file and a submodule's is neither where nor what a
23
+ * naive walk expects.
24
+ */
25
+ export declare function findRepositoryRoot(startDir: string): Promise<string | undefined>;
26
+ /** True when `root` is inside a git working tree. */
27
+ export declare function isGitRepository(root: string): Promise<boolean>;
28
+ export declare function createLocalGitRepository(options: LocalGitOptions): SchemaEditRepository;
@@ -0,0 +1,43 @@
1
+ import { type SchemaCommitPaths } from "@rebasepro/types";
2
+ /**
3
+ * The nearest ancestor of `startDir` holding a `rebase.json`.
4
+ *
5
+ * Bounded, like the storage-source loader it mirrors: an unbounded walk from a
6
+ * misconfigured path climbs to the filesystem root and reads whatever it finds
7
+ * there. `undefined` when there is none, which is a project that predates the
8
+ * file — the caller then keeps the historical behaviour rather than guessing.
9
+ */
10
+ /**
11
+ * Where a scaffolded project keeps its collection source, project-relative.
12
+ *
13
+ * A bundle's own collections directory is compiled output, so nothing on the
14
+ * running machine points at this. A deployment committing to a repository states
15
+ * it, and this is the default it states nothing against.
16
+ */
17
+ export declare const DEFAULT_COLLECTIONS_PATH = "config/collections";
18
+ export declare function findProjectRoot(startDir: string, levels?: number): string | undefined;
19
+ /**
20
+ * The generated-artifact paths for a project, expressed relative to the
21
+ * repository that will hold the commit.
22
+ *
23
+ * Returns `undefined` when there is nothing to correct — no project marker, or
24
+ * a project that *is* the repository — so the caller passes no override and the
25
+ * defaults stand. Saying "no change needed" is different from saying
26
+ * "the change is a no-op prefix", and only the first leaves the existing
27
+ * behaviour untouched for every project that already worked.
28
+ */
29
+ export declare function commitPathsFor(collectionsDir: string, repositoryRoot: string): Partial<SchemaCommitPaths> | undefined;
30
+ /**
31
+ * Does this project keep versioned migrations?
32
+ *
33
+ * It matters because live schema editing does not write one, and cannot: a
34
+ * migration is Atlas's format with an integrity file, minted by an external
35
+ * binary against a throwaway database. A server process has neither.
36
+ *
37
+ * What it *does* write is `drizzle/schema.sql`, which is exactly the input
38
+ * `rebase db generate` diffs against — so the migration is one command away.
39
+ * The hazard is nobody saying so. A project that deploys by replaying
40
+ * migrations would build its next environment without this change, having been
41
+ * told the change was applied.
42
+ */
43
+ export declare function usesVersionedMigrations(collectionsDir: string): boolean;
@@ -0,0 +1,25 @@
1
+ import type { SchemaChangeFile } from "@rebasepro/types";
2
+ import type { SchemaEditRepository } from "./apply-schema-change.js";
3
+ export interface RemoteSourceOptions {
4
+ repository: SchemaEditRepository;
5
+ /**
6
+ * Where the collection *source* lives in the repository, as a repo-relative
7
+ * posix path — `config/collections`, or `app/config/collections` for a
8
+ * project in a subdirectory.
9
+ *
10
+ * Not derivable from the bundle: its `collectionsDir` points at compiled
11
+ * output, which is a different directory holding different files.
12
+ */
13
+ collectionsPath: string;
14
+ /** Applies the edit. Injected so this module needs no `ts-morph`. */
15
+ edit: (collectionsDir: string, collectionId: string, collection: Record<string, unknown>) => Promise<void>;
16
+ }
17
+ /**
18
+ * Fetch, rewrite, and return the changed file — without leaving anything behind.
19
+ *
20
+ * The scratch directory is removed on every path, including the failing one. It
21
+ * holds a copy of the customer's collection source, and a temp directory that
22
+ * accumulates those on a long-running server is a slow leak of exactly the
23
+ * thing worth not leaking.
24
+ */
25
+ export declare function rewriteRemoteCollection(options: RemoteSourceOptions, collectionId: string, collection: Record<string, unknown>): Promise<SchemaChangeFile[]>;
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Who may plan a schema change, and who may apply one.
3
+ *
4
+ * Being an admin is one privilege. Applying a schema change is two: it alters
5
+ * the database, and it writes a commit into the project's repository under
6
+ * somebody's name. `requireAdmin` in front of both routes answers the first
7
+ * question and never asks the second.
8
+ *
9
+ * `admin-roles.ts` says the rest of it: `schema-admin` is administrative
10
+ * "by any definition that matters", and "if a genuinely lesser role is added
11
+ * later, it does not belong in this list — it belongs in a capability check of
12
+ * its own". This is that check.
13
+ *
14
+ * ## The split that matters is not between roles
15
+ *
16
+ * It is between a person and a machine.
17
+ *
18
+ * `plan` has no side effects. It reads the collections, reads the database
19
+ * catalogue, and answers what would happen. Anyone already through the admin
20
+ * gate may do it, and a CI job that wants to know whether a proposed collection
21
+ * change is applicable is a *good* use of this API.
22
+ *
23
+ * `apply` commits. The commit carries an author, and the author is the point —
24
+ * the code that builds it says so: a schema change with an author and a diff in
25
+ * the project's history is the thing worth having, and an anonymous one is just
26
+ * a commit. A service key has no author. `api-key:7c3f…` has no author. Letting
27
+ * either write to the project's source produces exactly the unattributable
28
+ * history the feature exists to replace, and does it with a credential that is
29
+ * likely to be sitting in a CI environment variable.
30
+ *
31
+ * So machines plan by default and do not apply. An operator who genuinely wants
32
+ * an automated schema change — a migration pipeline, say — turns it on
33
+ * deliberately and gets a commit that says plainly which key made it.
34
+ */
35
+ /** What a caller is allowed to do with the live schema editor. */
36
+ export interface SchemaEditCapabilities {
37
+ /** Preview a change. No side effects. */
38
+ plan: boolean;
39
+ /** Commit the change and run the DDL. */
40
+ apply: boolean;
41
+ /**
42
+ * Why `apply` is denied, when it is and `plan` is not.
43
+ *
44
+ * Carried so the panel can grey out the button *and say why*, rather than
45
+ * letting somebody read a plan, decide, press, and only then be refused.
46
+ */
47
+ reason?: string;
48
+ /** A stable code for the refusal, for clients that branch rather than read. */
49
+ code?: string;
50
+ }
51
+ /** The kind of caller, which is what the `apply` decision turns on. */
52
+ export type PrincipalKind = "person" | "machine" | "anonymous";
53
+ export interface SchemaEditPrincipal {
54
+ kind: PrincipalKind;
55
+ /** Empty for an anonymous caller. */
56
+ uid: string;
57
+ roles: string[];
58
+ /** How the machine identified itself, for the refusal message. */
59
+ machineKind?: "service-key" | "api-key";
60
+ }
61
+ /** Read the caller off the request context's `user`. */
62
+ export declare function classifyPrincipal(user: unknown): SchemaEditPrincipal;
63
+ /**
64
+ * A repository that is not on this machine.
65
+ *
66
+ * Configured by a deployment running built output — every Cloud tenant, and any
67
+ * self-host serving a bundle. Without it, live schema editing needs the source
68
+ * on disk and refuses when there is none.
69
+ */
70
+ export interface RemoteRepositoryConfig {
71
+ kind: "github";
72
+ owner: string;
73
+ repo: string;
74
+ /** Defaults to `main`. */
75
+ branch?: string;
76
+ /**
77
+ * Where the collection **source** lives in that repository, repo-relative.
78
+ * Defaults to `config/collections`.
79
+ *
80
+ * Not derivable from the running bundle, whose collections directory holds
81
+ * compiled output — a different directory with different files.
82
+ */
83
+ collectionsPath?: string;
84
+ /**
85
+ * A GitHub App installation, or a token.
86
+ *
87
+ * The app is for a control plane holding one key across many projects; the
88
+ * token is for a single self-hoster who would otherwise have to stand one
89
+ * up to let their own server commit to their own repository.
90
+ */
91
+ auth: {
92
+ kind: "app";
93
+ appId: string;
94
+ privateKey: string;
95
+ installationId: string;
96
+ } | {
97
+ kind: "token";
98
+ token: string;
99
+ };
100
+ }
101
+ export interface SchemaEditPolicy {
102
+ /**
103
+ * Let a service key or an API key commit a schema change.
104
+ *
105
+ * Off by default. On, the commit is attributed to the credential rather
106
+ * than to a person, and says so — see {@link machineCommitAuthor}.
107
+ */
108
+ allowMachineApply?: boolean;
109
+ }
110
+ /**
111
+ * What this caller may do.
112
+ *
113
+ * Assumes the admin gate has already run — this refines that decision rather
114
+ * than replacing it, and an anonymous caller should never have reached here.
115
+ * Handled anyway, because a capability function that trusts its caller to have
116
+ * checked is one bad refactor away from granting everything.
117
+ */
118
+ export declare function schemaEditCapabilities(principal: SchemaEditPrincipal, policy?: SchemaEditPolicy): SchemaEditCapabilities;
119
+ /**
120
+ * The git identity for a machine that has been allowed to apply.
121
+ *
122
+ * Named for what it is. An operator reading `git log` a month from now should
123
+ * be able to tell a change somebody made from one a pipeline made, and a
124
+ * credential wearing a person-shaped name is how that distinction is lost.
125
+ */
126
+ export declare function machineCommitAuthor(principal: SchemaEditPrincipal): {
127
+ name: string;
128
+ email: string;
129
+ };
@@ -0,0 +1,87 @@
1
+ import { createRequire as __rebaseCreateRequire } from "module";
2
+ import __rebaseProcess from "process";
3
+ globalThis.process ??= __rebaseProcess;
4
+ __rebaseCreateRequire(import.meta.url);
5
+ import { n as errorHandler, t as ApiError } from "./errors-DBwpj9N8.js";
6
+ import { a as record, i as object, o as string, r as boolean, s as unknown } from "./schemas-DBxgjM9A.js";
7
+ import { AstSchemaEditor } from "./ast-schema-editor-BrarYZCq.js";
8
+ import { Hono } from "hono";
9
+ //#region src/api/schema-editor-routes.ts
10
+ /**
11
+ * Rewriting collection source from the admin panel.
12
+ *
13
+ * Every refusal in `AstSchemaEditor` is written for the person who will read it
14
+ * — "Collection X has no file at …", "Relation Y has no target collection. Pick
15
+ * one before saving." — and every one of them was a plain `Error`, which
16
+ * `errorHandler` treats as an unexpected failure: 500, and a body that says
17
+ * "Internal Server Error". The messages never left the server. They are 400s
18
+ * now, so the panel can show what it was told.
19
+ */
20
+ function refusalsAsBadRequest(run) {
21
+ return run().catch((error) => {
22
+ if (error instanceof ApiError) throw error;
23
+ throw ApiError.badRequest(error instanceof Error ? error.message : String(error), "SCHEMA_EDIT_REFUSED");
24
+ });
25
+ }
26
+ /** The identifier half of every payload here, checked once. */
27
+ var collectionIdSchema = string().min(1, "`collectionId` is required");
28
+ var propertyKeySchema = string().min(1, "`propertyKey` is required");
29
+ var propertySaveSchema = object({
30
+ collectionId: collectionIdSchema,
31
+ propertyKey: propertyKeySchema,
32
+ propertyConfig: record(string(), unknown())
33
+ });
34
+ var propertyDeleteSchema = object({
35
+ collectionId: collectionIdSchema,
36
+ propertyKey: propertyKeySchema
37
+ });
38
+ var collectionSaveSchema = object({
39
+ collectionId: collectionIdSchema,
40
+ collectionData: record(string(), unknown()),
41
+ partial: boolean().optional()
42
+ });
43
+ var collectionDeleteSchema = object({ collectionId: collectionIdSchema });
44
+ /** Parse a body against a schema, or 400 naming the field. */
45
+ async function body(c, schema) {
46
+ const raw = await c.req.json().catch(() => void 0);
47
+ const parsed = schema.safeParse(raw);
48
+ if (!parsed.success) throw ApiError.badRequest(parsed.error.issues.map((i) => `${i.path.join(".") || "body"}: ${i.message}`).join("; "), "INVALID_INPUT");
49
+ return parsed.data;
50
+ }
51
+ function createSchemaEditorRoutes(collectionsDir) {
52
+ const router = new Hono();
53
+ router.onError(errorHandler);
54
+ const editor = new AstSchemaEditor(collectionsDir);
55
+ router.post("/property/save", async (c) => {
56
+ const { collectionId, propertyKey, propertyConfig } = await body(c, propertySaveSchema);
57
+ await refusalsAsBadRequest(() => editor.saveProperty(collectionId, propertyKey, propertyConfig));
58
+ return c.json({ success: true });
59
+ });
60
+ router.post("/property/delete", async (c) => {
61
+ const { collectionId, propertyKey } = await body(c, propertyDeleteSchema);
62
+ await refusalsAsBadRequest(() => editor.deleteProperty(collectionId, propertyKey));
63
+ return c.json({ success: true });
64
+ });
65
+ /**
66
+ * `partial: true` means "this payload is what changed", not "this is the
67
+ * collection". Without it a one-key patch — which is what adding a column
68
+ * posts — is read as a whole-collection save and deletes everything it does
69
+ * not mention, `securityRules` included. Absent, it defaults to a full save,
70
+ * so an older panel keeps the behaviour it was written against.
71
+ */
72
+ router.post("/collection/save", async (c) => {
73
+ const { collectionId, collectionData, partial } = await body(c, collectionSaveSchema);
74
+ await refusalsAsBadRequest(() => editor.saveCollection(collectionId, collectionData, { partial: partial === true }));
75
+ return c.json({ success: true });
76
+ });
77
+ router.post("/collection/delete", async (c) => {
78
+ const { collectionId } = await body(c, collectionDeleteSchema);
79
+ await refusalsAsBadRequest(() => editor.deleteCollection(collectionId));
80
+ return c.json({ success: true });
81
+ });
82
+ return router;
83
+ }
84
+ //#endregion
85
+ export { createSchemaEditorRoutes };
86
+
87
+ //# sourceMappingURL=schema-editor-routes-Bf5h5Emf.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema-editor-routes-Bf5h5Emf.js","names":[],"sources":["../src/api/schema-editor-routes.ts"],"sourcesContent":["import { Hono } from \"hono\";\nimport { z } from \"zod\";\nimport { AstSchemaEditor } from \"./ast-schema-editor\";\nimport { ApiError, errorHandler } from \"./errors\";\nimport { HonoEnv } from \"./types\";\n\n/**\n * Rewriting collection source from the admin panel.\n *\n * Every refusal in `AstSchemaEditor` is written for the person who will read it\n * — \"Collection X has no file at …\", \"Relation Y has no target collection. Pick\n * one before saving.\" — and every one of them was a plain `Error`, which\n * `errorHandler` treats as an unexpected failure: 500, and a body that says\n * \"Internal Server Error\". The messages never left the server. They are 400s\n * now, so the panel can show what it was told.\n */\nfunction refusalsAsBadRequest<T>(run: () => Promise<T>): Promise<T> {\n return run().catch((error: unknown) => {\n if (error instanceof ApiError) throw error;\n throw ApiError.badRequest(\n error instanceof Error ? error.message : String(error),\n \"SCHEMA_EDIT_REFUSED\"\n );\n });\n}\n\n/** The identifier half of every payload here, checked once. */\nconst collectionIdSchema = z.string().min(1, \"`collectionId` is required\");\nconst propertyKeySchema = z.string().min(1, \"`propertyKey` is required\");\n\nconst propertySaveSchema = z.object({\n collectionId: collectionIdSchema,\n propertyKey: propertyKeySchema,\n propertyConfig: z.record(z.string(), z.unknown())\n});\nconst propertyDeleteSchema = z.object({\n collectionId: collectionIdSchema,\n propertyKey: propertyKeySchema\n});\nconst collectionSaveSchema = z.object({\n collectionId: collectionIdSchema,\n collectionData: z.record(z.string(), z.unknown()),\n partial: z.boolean().optional()\n});\nconst collectionDeleteSchema = z.object({\n collectionId: collectionIdSchema\n});\n\n/** Parse a body against a schema, or 400 naming the field. */\nasync function body<S extends z.ZodType>(c: { req: { json: () => Promise<unknown> } }, schema: S): Promise<z.infer<S>> {\n const raw = await c.req.json().catch(() => undefined);\n const parsed = schema.safeParse(raw);\n if (!parsed.success) {\n throw ApiError.badRequest(\n parsed.error.issues.map(i => `${i.path.join(\".\") || \"body\"}: ${i.message}`).join(\"; \"),\n \"INVALID_INPUT\"\n );\n }\n return parsed.data;\n}\n\nexport function createSchemaEditorRoutes(collectionsDir: string): Hono<HonoEnv> {\n const router = new Hono<HonoEnv>();\n router.onError(errorHandler);\n const editor = new AstSchemaEditor(collectionsDir);\n\n router.post(\"/property/save\", async (c) => {\n const { collectionId, propertyKey, propertyConfig } = await body(c, propertySaveSchema);\n await refusalsAsBadRequest(() => editor.saveProperty(collectionId, propertyKey, propertyConfig));\n return c.json({ success: true });\n });\n\n router.post(\"/property/delete\", async (c) => {\n const { collectionId, propertyKey } = await body(c, propertyDeleteSchema);\n await refusalsAsBadRequest(() => editor.deleteProperty(collectionId, propertyKey));\n return c.json({ success: true });\n });\n\n /**\n * `partial: true` means \"this payload is what changed\", not \"this is the\n * collection\". Without it a one-key patch — which is what adding a column\n * posts — is read as a whole-collection save and deletes everything it does\n * not mention, `securityRules` included. Absent, it defaults to a full save,\n * so an older panel keeps the behaviour it was written against.\n */\n router.post(\"/collection/save\", async (c) => {\n const { collectionId, collectionData, partial } = await body(c, collectionSaveSchema);\n await refusalsAsBadRequest(() =>\n editor.saveCollection(collectionId, collectionData, { partial: partial === true }));\n return c.json({ success: true });\n });\n\n router.post(\"/collection/delete\", async (c) => {\n const { collectionId } = await body(c, collectionDeleteSchema);\n await refusalsAsBadRequest(() => editor.deleteCollection(collectionId));\n return c.json({ success: true });\n });\n\n return router;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAgBA,SAAS,qBAAwB,KAAmC;CAChE,OAAO,IAAI,CAAC,CAAC,OAAO,UAAmB;EACnC,IAAI,iBAAiB,UAAU,MAAM;EACrC,MAAM,SAAS,WACX,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GACrD,qBACJ;CACJ,CAAC;AACL;;AAGA,IAAM,qBAAqB,OAAS,CAAC,CAAC,IAAI,GAAG,4BAA4B;AACzE,IAAM,oBAAoB,OAAS,CAAC,CAAC,IAAI,GAAG,2BAA2B;AAEvE,IAAM,qBAAqB,OAAS;CAChC,cAAc;CACd,aAAa;CACb,gBAAgB,OAAS,OAAS,GAAG,QAAU,CAAC;AACpD,CAAC;AACD,IAAM,uBAAuB,OAAS;CAClC,cAAc;CACd,aAAa;AACjB,CAAC;AACD,IAAM,uBAAuB,OAAS;CAClC,cAAc;CACd,gBAAgB,OAAS,OAAS,GAAG,QAAU,CAAC;CAChD,SAAS,QAAU,CAAC,CAAC,SAAS;AAClC,CAAC;AACD,IAAM,yBAAyB,OAAS,EACpC,cAAc,mBAClB,CAAC;;AAGD,eAAe,KAA0B,GAA8C,QAAgC;CACnH,MAAM,MAAM,MAAM,EAAE,IAAI,KAAK,CAAC,CAAC,YAAY,KAAA,CAAS;CACpD,MAAM,SAAS,OAAO,UAAU,GAAG;CACnC,IAAI,CAAC,OAAO,SACR,MAAM,SAAS,WACX,OAAO,MAAM,OAAO,KAAI,MAAK,GAAG,EAAE,KAAK,KAAK,GAAG,KAAK,OAAO,IAAI,EAAE,SAAS,CAAC,CAAC,KAAK,IAAI,GACrF,eACJ;CAEJ,OAAO,OAAO;AAClB;AAEA,SAAgB,yBAAyB,gBAAuC;CAC5E,MAAM,SAAS,IAAI,KAAc;CACjC,OAAO,QAAQ,YAAY;CAC3B,MAAM,SAAS,IAAI,gBAAgB,cAAc;CAEjD,OAAO,KAAK,kBAAkB,OAAO,MAAM;EACvC,MAAM,EAAE,cAAc,aAAa,mBAAmB,MAAM,KAAK,GAAG,kBAAkB;EACtF,MAAM,2BAA2B,OAAO,aAAa,cAAc,aAAa,cAAc,CAAC;EAC/F,OAAO,EAAE,KAAK,EAAE,SAAS,KAAK,CAAC;CACnC,CAAC;CAED,OAAO,KAAK,oBAAoB,OAAO,MAAM;EACzC,MAAM,EAAE,cAAc,gBAAgB,MAAM,KAAK,GAAG,oBAAoB;EACxE,MAAM,2BAA2B,OAAO,eAAe,cAAc,WAAW,CAAC;EACjF,OAAO,EAAE,KAAK,EAAE,SAAS,KAAK,CAAC;CACnC,CAAC;;;;;;;;CASD,OAAO,KAAK,oBAAoB,OAAO,MAAM;EACzC,MAAM,EAAE,cAAc,gBAAgB,YAAY,MAAM,KAAK,GAAG,oBAAoB;EACpF,MAAM,2BACF,OAAO,eAAe,cAAc,gBAAgB,EAAE,SAAS,YAAY,KAAK,CAAC,CAAC;EACtF,OAAO,EAAE,KAAK,EAAE,SAAS,KAAK,CAAC;CACnC,CAAC;CAED,OAAO,KAAK,sBAAsB,OAAO,MAAM;EAC3C,MAAM,EAAE,iBAAiB,MAAM,KAAK,GAAG,sBAAsB;EAC7D,MAAM,2BAA2B,OAAO,iBAAiB,YAAY,CAAC;EACtE,OAAO,EAAE,KAAK,EAAE,SAAS,KAAK,CAAC;CACnC,CAAC;CAED,OAAO;AACX"}