@rebasepro/server 0.13.0 → 0.13.1-canary.g06dbe5b

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 (125) hide show
  1. package/dist/{admin_block-CcSGdH7M.js → admin_block-H86dCPsj.js} +62 -2
  2. package/dist/admin_block-H86dCPsj.js.map +1 -0
  3. package/dist/api/ast-schema-editor.d.ts +74 -6
  4. package/dist/api/errors.d.ts +0 -6
  5. package/dist/api/openapi-generator.d.ts +14 -0
  6. package/dist/api/rest/api-generator.d.ts +1 -1
  7. package/dist/api/rest/idempotency.d.ts +62 -4
  8. package/dist/api/rest/query-parser.d.ts +15 -1
  9. package/dist/api/rest/write-validation.d.ts +52 -9
  10. package/dist/auth/adapter-middleware.d.ts +3 -1
  11. package/dist/auth/apple-oauth.d.ts +16 -11
  12. package/dist/auth/bitbucket-oauth.d.ts +2 -4
  13. package/dist/auth/builtin-auth-adapter.d.ts +2 -0
  14. package/dist/auth/discord-oauth.d.ts +5 -6
  15. package/dist/auth/facebook-oauth.d.ts +2 -4
  16. package/dist/auth/github-oauth.d.ts +2 -4
  17. package/dist/auth/gitlab-oauth.d.ts +6 -4
  18. package/dist/auth/google-oauth.d.ts +1 -0
  19. package/dist/auth/index.d.ts +8 -1
  20. package/dist/auth/interfaces.d.ts +68 -1
  21. package/dist/auth/jwt.d.ts +40 -0
  22. package/dist/auth/linkedin-oauth.d.ts +2 -4
  23. package/dist/auth/mfa-gate.d.ts +42 -0
  24. package/dist/auth/mfa-routes.d.ts +26 -1
  25. package/dist/auth/mfa.d.ts +16 -0
  26. package/dist/auth/microsoft-oauth.d.ts +19 -4
  27. package/dist/auth/oauth-code-flow.d.ts +66 -0
  28. package/dist/auth/oauth-signin-policy.d.ts +61 -0
  29. package/dist/auth/oidc-id-token.d.ts +61 -0
  30. package/dist/auth/rate-limiter.d.ts +37 -2
  31. package/dist/auth/rls-scope.d.ts +25 -0
  32. package/dist/auth/routes.d.ts +8 -0
  33. package/dist/auth/slack-oauth.d.ts +2 -4
  34. package/dist/auth/spotify-oauth.d.ts +2 -4
  35. package/dist/auth/twitter-oauth.d.ts +2 -5
  36. package/dist/{auth-CuC9M2x6.js → auth-DkAHqvf5.js} +1604 -391
  37. package/dist/auth-DkAHqvf5.js.map +1 -0
  38. package/dist/{backup-CVggVhR2.js → backup-DLluVyuA.js} +2 -2
  39. package/dist/{backup-CVggVhR2.js.map → backup-DLluVyuA.js.map} +1 -1
  40. package/dist/boot/boot.d.ts +19 -0
  41. package/dist/boot/ddl-bootstrap.d.ts +70 -0
  42. package/dist/{contract-routes-Dj8i5AiM.js → contract-routes-BB1U05sS.js} +3 -3
  43. package/dist/{contract-routes-Dj8i5AiM.js.map → contract-routes-BB1U05sS.js.map} +1 -1
  44. package/dist/cron/cron-scheduler.d.ts +8 -3
  45. package/dist/cron/define-cron.d.ts +17 -3
  46. package/dist/{cron-loader-B1S2MCSl.js → cron-loader-3U5aGILy.js} +2 -2
  47. package/dist/{cron-loader-B1S2MCSl.js.map → cron-loader-3U5aGILy.js.map} +1 -1
  48. package/dist/{cron-routes-CrQ0tK-_.js → cron-routes-rZgwlOz4.js} +2 -2
  49. package/dist/{cron-routes-CrQ0tK-_.js.map → cron-routes-rZgwlOz4.js.map} +1 -1
  50. package/dist/{cron-scheduler-B3RFt0HS.js → cron-scheduler-FJAaCAXm.js} +30 -5
  51. package/dist/cron-scheduler-FJAaCAXm.js.map +1 -0
  52. package/dist/{cron-store-BywZsyfZ.js → cron-store-9NmUDfzL.js} +66 -47
  53. package/dist/cron-store-9NmUDfzL.js.map +1 -0
  54. package/dist/ddl-bootstrap-BhXbTnBl.js +183 -0
  55. package/dist/ddl-bootstrap-BhXbTnBl.js.map +1 -0
  56. package/dist/email/html.d.ts +54 -0
  57. package/dist/email/index.d.ts +3 -0
  58. package/dist/email/link-base.d.ts +39 -0
  59. package/dist/email/smtp-email-service.d.ts +7 -1
  60. package/dist/email/templates.d.ts +9 -1
  61. package/dist/email/types.d.ts +11 -1
  62. package/dist/{errors-CgkCzoj7.js → errors-B1WZEdsK.js} +3 -11
  63. package/dist/errors-B1WZEdsK.js.map +1 -0
  64. package/dist/function-loader-D1SwtCa5.js +139 -0
  65. package/dist/function-loader-D1SwtCa5.js.map +1 -0
  66. package/dist/{function-routes-C0cLIy3N.js → function-routes-Btcez1T-.js} +18 -6
  67. package/dist/function-routes-Btcez1T-.js.map +1 -0
  68. package/dist/functions/define-function.d.ts +25 -6
  69. package/dist/functions/function-loader.d.ts +23 -0
  70. package/dist/functions/function-routes.d.ts +7 -1
  71. package/dist/functions/request-timeout.d.ts +34 -0
  72. package/dist/history/history-routes.d.ts +6 -0
  73. package/dist/index.d.ts +2 -1
  74. package/dist/index.es.js +1960 -794
  75. package/dist/index.es.js.map +1 -1
  76. package/dist/init/docs.d.ts +10 -1
  77. package/dist/init/process-safety.d.ts +25 -0
  78. package/dist/init.d.ts +27 -0
  79. package/dist/{jwt-DD6EtpGj.js → jwt-Cuq6MwNy.js} +70 -8
  80. package/dist/jwt-Cuq6MwNy.js.map +1 -0
  81. package/dist/logger-DfvF_8r-.js +190 -0
  82. package/dist/logger-DfvF_8r-.js.map +1 -0
  83. package/dist/openapi-generator-BQxxxDpb.js +865 -0
  84. package/dist/openapi-generator-BQxxxDpb.js.map +1 -0
  85. package/dist/request-timeout-RivJsME0.js +65 -0
  86. package/dist/request-timeout-RivJsME0.js.map +1 -0
  87. package/dist/schema-editor-routes-DZFKGXgq.js +437 -0
  88. package/dist/schema-editor-routes-DZFKGXgq.js.map +1 -0
  89. package/dist/serve-spa.d.ts +5 -4
  90. package/dist/services/outbound-url-guard.d.ts +53 -0
  91. package/dist/services/routed-realtime-service.d.ts +10 -2
  92. package/dist/services/webhook-service.d.ts +76 -1
  93. package/dist/singleton.d.ts +25 -8
  94. package/dist/{src-Cum9kox5.js → src-By48Ffg0.js} +322 -70
  95. package/dist/src-By48Ffg0.js.map +1 -0
  96. package/dist/{src-_qQ3RNCK.js → src-Ca6NhxKs.js} +79 -2
  97. package/dist/src-Ca6NhxKs.js.map +1 -0
  98. package/dist/storage/LocalStorageController.d.ts +12 -1
  99. package/dist/storage/image-transform.d.ts +57 -0
  100. package/dist/storage/keys.d.ts +98 -0
  101. package/dist/storage/routes.d.ts +11 -0
  102. package/dist/storage/tus-handler.d.ts +7 -1
  103. package/dist/utils/logger.d.ts +18 -0
  104. package/dist/utils/logging.d.ts +0 -4
  105. package/dist/utils/sql.d.ts +11 -6
  106. package/package.json +7 -6
  107. package/dist/admin_block-CcSGdH7M.js.map +0 -1
  108. package/dist/auth-CuC9M2x6.js.map +0 -1
  109. package/dist/backend-CIxN4FVm.js +0 -15
  110. package/dist/backend-CIxN4FVm.js.map +0 -1
  111. package/dist/cron-scheduler-B3RFt0HS.js.map +0 -1
  112. package/dist/cron-store-BywZsyfZ.js.map +0 -1
  113. package/dist/errors-CgkCzoj7.js.map +0 -1
  114. package/dist/function-loader-B_1fYfUY.js +0 -86
  115. package/dist/function-loader-B_1fYfUY.js.map +0 -1
  116. package/dist/function-routes-C0cLIy3N.js.map +0 -1
  117. package/dist/jwt-DD6EtpGj.js.map +0 -1
  118. package/dist/logger-BYU66ENZ.js +0 -94
  119. package/dist/logger-BYU66ENZ.js.map +0 -1
  120. package/dist/openapi-generator-BEwyiaAr.js +0 -597
  121. package/dist/openapi-generator-BEwyiaAr.js.map +0 -1
  122. package/dist/schema-editor-routes-CbF20Mf1.js +0 -248
  123. package/dist/schema-editor-routes-CbF20Mf1.js.map +0 -1
  124. package/dist/src-Cum9kox5.js.map +0 -1
  125. package/dist/src-_qQ3RNCK.js.map +0 -1
@@ -44,6 +44,7 @@ var ADMIN_COLLECTION_KEYS = [
44
44
  "defaultSize",
45
45
  "defaultViewMode",
46
46
  "disableDefaultActions",
47
+ "display",
47
48
  "enabledViews",
48
49
  "entityActions",
49
50
  "entityViews",
@@ -54,6 +55,7 @@ var ADMIN_COLLECTION_KEYS = [
54
55
  "formAutoSave",
55
56
  "formView",
56
57
  "group",
58
+ "hideFromEntityViews",
57
59
  "hideFromNavigation",
58
60
  "hideIdFromCollection",
59
61
  "hideIdFromForm",
@@ -118,7 +120,65 @@ var ADMIN_PROPERTY_KEYS = [
118
120
  "urlPreview",
119
121
  "widget"
120
122
  ];
123
+ /**
124
+ * Move flattened admin keys back down into the `admin` block.
125
+ *
126
+ * The admin panel works with a *flat* view model — the block merged onto the
127
+ * collection — so what comes back from a form has `icon` and `defaultViewMode`
128
+ * at the top level while `admin` still holds whatever the file was loaded with.
129
+ * This is the way back.
130
+ *
131
+ * **The top-level value wins.** It is the one the form just wrote; the block is
132
+ * the copy the collection was loaded with, and preferring it resolves every edit
133
+ * in favour of the value the user changed away from.
134
+ *
135
+ * This lives here, next to the key lists, because it had two implementations —
136
+ * `toAdminCollectionConfig` in `@rebasepro/admin-types` and `nestAdminKeys` in
137
+ * `@rebasepro/server`'s schema editor — that agreed on everything except that
138
+ * precedence, which is the only part that decides whether a save is visible.
139
+ *
140
+ * @group Models
141
+ */
142
+ function nestAdminKeysOf(source, adminKeys) {
143
+ const keys = new Set(adminKeys);
144
+ const top = {};
145
+ const block = { ...source.admin ?? {} };
146
+ for (const [key, value] of Object.entries(source)) {
147
+ if (key === "admin") continue;
148
+ if (keys.has(key)) block[key] = value;
149
+ else top[key] = value;
150
+ }
151
+ if (Object.keys(block).length > 0) top.admin = block;
152
+ return top;
153
+ }
154
+ /**
155
+ * {@link nestAdminKeysOf} for a collection.
156
+ *
157
+ * @group Models
158
+ */
159
+ function nestAdminCollectionKeys(collection) {
160
+ return nestAdminKeysOf(collection, ADMIN_COLLECTION_KEYS);
161
+ }
162
+ /**
163
+ * {@link nestAdminKeysOf} for a property, applied to its children too.
164
+ *
165
+ * A map property carries `properties`, an array property carries `of`, and both
166
+ * hold properties with `admin` blocks of their own. A flat `readOnly` left on a
167
+ * child is as dead — and as fatal at the next boot — as one left on the parent,
168
+ * so the walk goes all the way down.
169
+ *
170
+ * @group Models
171
+ */
172
+ function nestAdminPropertyKeys(property) {
173
+ const nested = nestAdminKeysOf(property, ADMIN_PROPERTY_KEYS);
174
+ const children = nested.properties;
175
+ if (children && typeof children === "object" && !Array.isArray(children)) nested.properties = Object.fromEntries(Object.entries(children).map(([key, child]) => [key, child && typeof child === "object" && !Array.isArray(child) ? nestAdminPropertyKeys(child) : child]));
176
+ const of = nested.of;
177
+ if (Array.isArray(of)) nested.of = of.map((entry) => entry && typeof entry === "object" && !Array.isArray(entry) ? nestAdminPropertyKeys(entry) : entry);
178
+ else if (of && typeof of === "object") nested.of = nestAdminPropertyKeys(of);
179
+ return nested;
180
+ }
121
181
  //#endregion
122
- export { ADMIN_PROPERTY_KEYS as n, ADMIN_COLLECTION_KEYS as t };
182
+ export { nestAdminPropertyKeys as i, ADMIN_PROPERTY_KEYS as n, nestAdminCollectionKeys as r, ADMIN_COLLECTION_KEYS as t };
123
183
 
124
- //# sourceMappingURL=admin_block-CcSGdH7M.js.map
184
+ //# sourceMappingURL=admin_block-H86dCPsj.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin_block-H86dCPsj.js","names":[],"sources":["../../types/src/types/admin_block.ts"],"sourcesContent":["/**\n * The keys of a collection's admin block, as data.\n *\n * There is no *type* for the block in this package any more, and that is the point:\n * `admin` is not declared on `BaseCollectionConfig` or on any property here, so a\n * BaaS install cannot even write one. `@rebasepro/admin-types` adds the field back by\n * declaration merging, which is why installing it is what makes the admin surface\n * appear.\n *\n * The *list* still has to live here, because three runtime consumers need it and two\n * of them are core — see below.\n */\n\n/**\n * Every key that belongs inside a collection's `admin` block, as data.\n *\n * The type that describes these fields is `AdminCollectionOptions` in\n * `@rebasepro/admin-types`, and it is erased at build time — but three runtime\n * consumers need the list, and two of them are core:\n *\n * - `serializeCollections`, to drop the block from the contract\n * - the ts-morph schema editor in `@rebasepro/server`, which rewrites collection\n * files on disk from the admin panel and has to know where each key goes. A key\n * missing from this list gets written to the *top level* of the file, where the\n * backend ignores it and the panel never finds it again.\n * - the `collections-admin-block` codemod\n *\n * `@rebasepro/admin-types` re-exports this and asserts it names only real option\n * keys; the count is pinned by a test there.\n *\n * @group Models\n */\nexport const ADMIN_COLLECTION_KEYS = [\n \"Actions\",\n \"additionalFields\",\n \"alwaysApplyDefaultValues\",\n \"components\",\n \"defaultEntityAction\",\n \"defaultFilter\",\n \"defaultSelectedView\",\n \"defaultSize\",\n \"defaultViewMode\",\n \"disableDefaultActions\",\n \"display\",\n \"enabledViews\",\n \"entityActions\",\n \"entityViews\",\n \"exportable\",\n \"filterPresets\",\n \"fixedFilter\",\n \"form\",\n \"formAutoSave\",\n \"formView\",\n \"group\",\n \"hideFromEntityViews\",\n \"hideFromNavigation\",\n \"hideIdFromCollection\",\n \"hideIdFromForm\",\n \"icon\",\n \"includeJsonView\",\n \"inlineEditing\",\n \"kanban\",\n \"listProperties\",\n \"localChangesBackup\",\n \"openEntityMode\",\n \"orderProperty\",\n \"pagination\",\n \"previewProperties\",\n \"propertiesOrder\",\n \"selectionController\",\n \"selectionEnabled\",\n \"sideDialogWidth\",\n \"sort\",\n \"titleProperty\"\n] as const;\n\n/** A key of a collection's `admin` block. @group Models */\nexport type AdminCollectionKey = typeof ADMIN_COLLECTION_KEYS[number];\n\n/**\n * Every key that belongs inside a *property's* `admin` block, as data.\n *\n * The union of `AdminPropertyOptions` and its per-type extensions\n * (`AdminStringOptions`, `AdminArrayOptions`, …) in `@rebasepro/admin-types`.\n * It lives here for the same reason {@link ADMIN_COLLECTION_KEYS} does: the\n * runtime consumers are core packages that the BaaS guard forbids from\n * importing `@rebasepro/admin-types`. Here it is the boot-time collection\n * validator in `@rebasepro/server`, which has to tell \"you left `readOnly` at\n * the top of the property, where nothing reads it\" apart from \"you invented a\n * key we have never heard of\".\n *\n * `@rebasepro/admin-types` re-exports this and asserts it names only real\n * option keys.\n *\n * @group Models\n */\nexport const ADMIN_PROPERTY_KEYS = [\n \"canAddElements\",\n \"clearable\",\n \"columnWidth\",\n \"customProps\",\n \"disabled\",\n \"expanded\",\n \"Field\",\n \"Filter\",\n \"filterOperators\",\n \"fixedFilter\",\n \"hideFromCollection\",\n \"includeEntityLink\",\n \"includeId\",\n \"markdown\",\n \"minimalistView\",\n \"multiline\",\n \"Preview\",\n \"previewAsTag\",\n \"previewProperties\",\n \"readOnly\",\n \"sortable\",\n \"span\",\n \"spreadChildren\",\n \"urlPreview\",\n \"widget\",\n] as const;\n\n/** A key of a property's `admin` block. @group Models */\nexport type AdminPropertyKey = typeof ADMIN_PROPERTY_KEYS[number];\n\n/**\n * Move flattened admin keys back down into the `admin` block.\n *\n * The admin panel works with a *flat* view model — the block merged onto the\n * collection — so what comes back from a form has `icon` and `defaultViewMode`\n * at the top level while `admin` still holds whatever the file was loaded with.\n * This is the way back.\n *\n * **The top-level value wins.** It is the one the form just wrote; the block is\n * the copy the collection was loaded with, and preferring it resolves every edit\n * in favour of the value the user changed away from.\n *\n * This lives here, next to the key lists, because it had two implementations —\n * `toAdminCollectionConfig` in `@rebasepro/admin-types` and `nestAdminKeys` in\n * `@rebasepro/server`'s schema editor — that agreed on everything except that\n * precedence, which is the only part that decides whether a save is visible.\n *\n * @group Models\n */\nexport function nestAdminKeysOf(\n source: Record<string, unknown>,\n adminKeys: readonly string[]\n): Record<string, unknown> {\n const keys = new Set<string>(adminKeys);\n const top: Record<string, unknown> = {};\n const block: Record<string, unknown> = { ...((source.admin as Record<string, unknown> | undefined) ?? {}) };\n\n for (const [key, value] of Object.entries(source)) {\n if (key === \"admin\") continue;\n if (keys.has(key)) block[key] = value;\n else top[key] = value;\n }\n\n if (Object.keys(block).length > 0) top.admin = block;\n return top;\n}\n\n/**\n * {@link nestAdminKeysOf} for a collection.\n *\n * @group Models\n */\nexport function nestAdminCollectionKeys(collection: Record<string, unknown>): Record<string, unknown> {\n return nestAdminKeysOf(collection, ADMIN_COLLECTION_KEYS);\n}\n\n/**\n * {@link nestAdminKeysOf} for a property, applied to its children too.\n *\n * A map property carries `properties`, an array property carries `of`, and both\n * hold properties with `admin` blocks of their own. A flat `readOnly` left on a\n * child is as dead — and as fatal at the next boot — as one left on the parent,\n * so the walk goes all the way down.\n *\n * @group Models\n */\nexport function nestAdminPropertyKeys(property: Record<string, unknown>): Record<string, unknown> {\n const nested = nestAdminKeysOf(property, ADMIN_PROPERTY_KEYS);\n\n const children = nested.properties;\n if (children && typeof children === \"object\" && !Array.isArray(children)) {\n nested.properties = Object.fromEntries(\n Object.entries(children as Record<string, unknown>).map(([key, child]) => [\n key,\n child && typeof child === \"object\" && !Array.isArray(child)\n ? nestAdminPropertyKeys(child as Record<string, unknown>)\n : child\n ])\n );\n }\n\n const of = nested.of;\n if (Array.isArray(of)) {\n nested.of = of.map(entry => entry && typeof entry === \"object\" && !Array.isArray(entry)\n ? nestAdminPropertyKeys(entry as Record<string, unknown>)\n : entry);\n } else if (of && typeof of === \"object\") {\n nested.of = nestAdminPropertyKeys(of as Record<string, unknown>);\n }\n\n return nested;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCA,IAAa,wBAAwB;CACjC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACJ;;;;;;;;;;;;;;;;;;AAsBA,IAAa,sBAAsB;CAC/B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACJ;;;;;;;;;;;;;;;;;;;;AAwBA,SAAgB,gBACZ,QACA,WACuB;CACvB,MAAM,OAAO,IAAI,IAAY,SAAS;CACtC,MAAM,MAA+B,CAAC;CACtC,MAAM,QAAiC,EAAE,GAAK,OAAO,SAAiD,CAAC,EAAG;CAE1G,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,GAAG;EAC/C,IAAI,QAAQ,SAAS;EACrB,IAAI,KAAK,IAAI,GAAG,GAAG,MAAM,OAAO;OAC3B,IAAI,OAAO;CACpB;CAEA,IAAI,OAAO,KAAK,KAAK,CAAC,CAAC,SAAS,GAAG,IAAI,QAAQ;CAC/C,OAAO;AACX;;;;;;AAOA,SAAgB,wBAAwB,YAA8D;CAClG,OAAO,gBAAgB,YAAY,qBAAqB;AAC5D;;;;;;;;;;;AAYA,SAAgB,sBAAsB,UAA4D;CAC9F,MAAM,SAAS,gBAAgB,UAAU,mBAAmB;CAE5D,MAAM,WAAW,OAAO;CACxB,IAAI,YAAY,OAAO,aAAa,YAAY,CAAC,MAAM,QAAQ,QAAQ,GACnE,OAAO,aAAa,OAAO,YACvB,OAAO,QAAQ,QAAmC,CAAC,CAAC,KAAK,CAAC,KAAK,WAAW,CACtE,KACA,SAAS,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK,IACpD,sBAAsB,KAAgC,IACtD,KACV,CAAC,CACL;CAGJ,MAAM,KAAK,OAAO;CAClB,IAAI,MAAM,QAAQ,EAAE,GAChB,OAAO,KAAK,GAAG,KAAI,UAAS,SAAS,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK,IAChF,sBAAsB,KAAgC,IACtD,KAAK;MACR,IAAI,MAAM,OAAO,OAAO,UAC3B,OAAO,KAAK,sBAAsB,EAA6B;CAGnE,OAAO;AACX"}
@@ -1,11 +1,11 @@
1
1
  /**
2
2
  * Move presentation keys into the `admin` block.
3
3
  *
4
- * `ADMIN_COLLECTION_KEYS` comes from `@rebasepro/types` rather than being spelled
5
- * out here, so adding a field to `AdminCollectionOptions` cannot leave this writer
6
- * behind. Any such key already inside `admin` wins over a top-level copy: the
7
- * nested one is what the file said, and a flat duplicate is the view model's
8
- * flattening leaking back.
4
+ * The rule itself lives in `@rebasepro/types`, next to `ADMIN_COLLECTION_KEYS`,
5
+ * because `@rebasepro/admin-types` has to apply the identical one on the panel's
6
+ * side and this package cannot import that one. Two copies used to exist and
7
+ * they disagreed about precedence, which decided whether a presentation edit was
8
+ * saved or silently reverted to the value the user had just changed away from.
9
9
  */
10
10
  export declare function nestAdminKeys(collectionData: Record<string, unknown>): Record<string, unknown>;
11
11
  export declare class AstSchemaEditor {
@@ -22,10 +22,78 @@ export declare class AstSchemaEditor {
22
22
  */
23
23
  private safePath;
24
24
  private getCollectionFile;
25
+ /**
26
+ * Find the object literal a collection is declared with, through whatever
27
+ * wraps it.
28
+ *
29
+ * `defineCollection({ … })` is the shape `rebase init` writes and the one the
30
+ * docs recommend; `satisfies` / `as` / parentheses are the other ways an
31
+ * author can dress the same literal. Returning `null` for any of them meant
32
+ * the three callers below each failed differently, and the worst of them
33
+ * overwrote the file.
34
+ */
35
+ private unwrapCollectionObject;
25
36
  private getCollectionObject;
37
+ /**
38
+ * The collection's object literal, or a refusal that says what to do.
39
+ *
40
+ * Every caller needs this to be all-or-nothing: a missing object literal used
41
+ * to mean "throw", "report success and do nothing" and "recreate the file
42
+ * from scratch" depending on which method you called.
43
+ */
44
+ private requireCollectionObject;
45
+ private unreadableFileMessage;
46
+ /** Look a key up on an object literal, quoted or not. */
47
+ private findProperty;
48
+ private static quoteKey;
49
+ private static isPlainObject;
26
50
  private convertJsonToAstString;
51
+ /**
52
+ * Write only the keys the patch names, leaving every sibling alone.
53
+ *
54
+ * A patch says what changed, not what the collection is. The panel sends one
55
+ * — `{ propertiesOrder }` is what adding a column posts — and rewriting the
56
+ * `admin` block from it deleted the collection's icon, group, list columns
57
+ * and kanban config in the same write.
58
+ */
59
+ private mergeIntoObjectLiteral;
27
60
  saveProperty(collectionId: string, propertyKey: string, propertyConfig: Record<string, unknown>): Promise<void>;
28
61
  deleteProperty(collectionId: string, propertyKey: string): Promise<void>;
29
- saveCollection(collectionId: string, collectionData: Record<string, unknown>): Promise<void>;
62
+ /**
63
+ * Write a collection back to its file.
64
+ *
65
+ * `partial` is the difference between "this is the collection" and "this is
66
+ * what changed about it". The panel sends both — a full save from the editor
67
+ * dialog, and a one-key patch whenever a property is added, deleted or
68
+ * reordered — and they cannot be told apart by looking at the payload. Read
69
+ * as a full save, a patch deletes everything it does not mention, including
70
+ * `securityRules`; the loader then hands the collection the directory
71
+ * default, which in the scaffold is `access: "public"`.
72
+ */
73
+ saveCollection(collectionId: string, collectionData: Record<string, unknown>, options?: {
74
+ partial?: boolean;
75
+ }): Promise<void>;
76
+ /**
77
+ * Write the collection-level `relations` array.
78
+ *
79
+ * Relations are the one key whose values are not all data: `target` is a
80
+ * thunk in the file, and the panel sends the target collection's slug (or,
81
+ * for a relation it did not touch, nothing at all — `JSON.stringify` drops
82
+ * the function). So each entry's target is resolved in that order: a slug
83
+ * becomes `() => xCollection` plus the import it needs, and anything else
84
+ * falls back to the thunk already in the file.
85
+ *
86
+ * This used to be a `continue` with a comment saying relations were handled
87
+ * elsewhere. Nothing handled them; every edit in the Relations tab was
88
+ * dropped without a word.
89
+ */
90
+ private writeRelations;
91
+ /**
92
+ * `() => targetCollection` for a target named by its slug, importing it if
93
+ * the file does not already.
94
+ */
95
+ private targetThunk;
96
+ private hasDefaultImport;
97
+ private getDefaultExportName;
30
98
  deleteCollection(collectionId: string): Promise<void>;
31
99
  }
@@ -55,12 +55,6 @@ export interface RebaseApiError extends Error {
55
55
  code?: string;
56
56
  details?: unknown;
57
57
  }
58
- /**
59
- * Type guard for errors that carry optional API metadata (statusCode, code, details).
60
- * Returns true for any Error instance — the optional properties are then
61
- * checked via normal property access.
62
- */
63
- export declare function isRebaseApiError(error: unknown): error is RebaseApiError;
64
58
  /**
65
59
  * Hono error-handling middleware (`app.onError`).
66
60
  * Converts any error into the canonical `{ error: { message, code } }` shape.
@@ -12,5 +12,19 @@ export interface OpenApiGeneratorOptions {
12
12
  basePath?: string;
13
13
  /** Whether auth is enabled on data routes. Defaults to true. */
14
14
  requireAuth?: boolean;
15
+ /**
16
+ * The list-pagination bounds the REST layer applies, so the spec states the
17
+ * ones a request will actually meet.
18
+ *
19
+ * These were hardcoded as `default: 20, maximum: 100` — neither of which
20
+ * the server has ever used. The spec drives the API Explorer and is what a
21
+ * generated client is built from, so an understated ceiling is a request
22
+ * the client refuses to make, and an overstated one is a 400 nobody
23
+ * predicted.
24
+ */
25
+ listLimits?: {
26
+ defaultLimit?: number;
27
+ maxLimit?: number;
28
+ };
15
29
  }
16
30
  export declare function generateOpenApiSpec(collections: CollectionConfig[], options?: OpenApiGeneratorOptions): Record<string, unknown>;
@@ -54,7 +54,7 @@ export declare class RestApiGenerator {
54
54
  * Needed so a nested write can be checked against a schema at all. Without
55
55
  * it these routes skipped `assertKnownWriteFields` entirely, which is why a
56
56
  * typo `POST /posts` rejected with a 400 while the same typo on
57
- * `POST /authors/1/posts` reached the database.
57
+ * `POST /authors/1/posts` was dropped from the statement and answered 201.
58
58
  *
59
59
  * Returns `undefined` rather than throwing when the path cannot be walked:
60
60
  * the driver raises the authoritative error a moment later, and duplicating
@@ -1,9 +1,67 @@
1
1
  import { DataDriver } from "@rebasepro/types";
2
+ /**
3
+ * What a key was claimed *for*, so presenting it on a different request is
4
+ * caught instead of silently replaying the first one's answer.
5
+ *
6
+ * Nothing about the request used to enter the key, so `createMany(rows, { key })`
7
+ * followed by `deleteMany(ids, { key })` — the reusable business id the docs
8
+ * recommended — answered the delete with the create's `200` body and deleted
9
+ * nothing. Method and path separate the routes; the body separates a re-run
10
+ * carrying corrected rows from the retry of an unanswered one, which is the
11
+ * difference between "here is your earlier answer" and silently discarding a
12
+ * correction.
13
+ */
14
+ export declare function requestFingerprint(method: string, path: string, body: unknown): string;
15
+ /**
16
+ * What a caller may do with a key.
17
+ *
18
+ * `claimed` is the only outcome that permits the write. Recalling and then
19
+ * writing was not atomic: two requests carrying one key both missed the recall,
20
+ * both wrote, and `ON CONFLICT DO NOTHING` protected only the second row in
21
+ * *this* table — the duplicate row in the caller's collection, which is the
22
+ * thing the mechanism exists to prevent, was inserted anyway. The offline queue
23
+ * is shared across tabs, so two tabs reconnecting together is the ordinary case
24
+ * rather than an exotic one.
25
+ */
26
+ export type IdempotencyClaim =
27
+ /** This key has already been answered; serve that answer again. */
28
+ {
29
+ status: "replay";
30
+ response: unknown;
31
+ }
32
+ /** Another request holds this key and has not answered yet. */
33
+ | {
34
+ status: "in-flight";
35
+ }
36
+ /** This key is live, but it was claimed for a different request. */
37
+ | {
38
+ status: "mismatch";
39
+ }
40
+ /** The key is ours; do the write, then `complete` or `release` it. */
41
+ | {
42
+ status: "claimed";
43
+ };
2
44
  export interface IdempotencyStore {
3
- /** What this key answered before, or `undefined` if it is new. */
4
- recall(key: string, uid: string | undefined): Promise<unknown | undefined>;
5
- /** Record what this key answered. Never throws see {@link createIdempotencyStore}. */
6
- remember(key: string, uid: string | undefined, response: unknown): Promise<void>;
45
+ /**
46
+ * Take the key if it is free, atomically. Never throws a store that
47
+ * cannot answer reports `claimed`, which degrades to no idempotency rather
48
+ * than to a refused write.
49
+ *
50
+ * `fingerprint` says what the key is being claimed for (see
51
+ * {@link requestFingerprint}); a live key presented with a different one is
52
+ * a reused key, not a replay, and is reported as `mismatch`.
53
+ */
54
+ claim(key: string, uid: string | undefined, fingerprint: string): Promise<IdempotencyClaim>;
55
+ /** Record what this key answered. Never throws. */
56
+ complete(key: string, uid: string | undefined, response: unknown): Promise<void>;
57
+ /**
58
+ * Give the key back after the write it was claimed for failed.
59
+ *
60
+ * Without this a transient failure strands the key until its lease runs
61
+ * out, turning one failed write into a minute of refused retries. The lease
62
+ * is the floor, not the mechanism: it only covers the exits no code runs on.
63
+ */
64
+ release(key: string, uid: string | undefined): Promise<void>;
7
65
  }
8
66
  /**
9
67
  * Returns `undefined` when the driver cannot run SQL, which disables the whole
@@ -1,3 +1,4 @@
1
+ import type { ListLimitBounds } from "@rebasepro/types";
1
2
  import { QueryOptions } from "../types";
2
3
  export declare const mapOperator: (op: string) => import("@rebasepro/types").WhereFilterOp | null;
3
4
  export { DEFAULT_LIST_LIMIT, DEFAULT_VECTOR_LIST_LIMIT, MAX_LIST_LIMIT } from "@rebasepro/types";
@@ -13,9 +14,22 @@ export interface ListLimitOptions {
13
14
  * text-search reads — a vector search falls back to its own default (10).
14
15
  */
15
16
  defaultLimit?: number;
16
- /** Upper bound clamped onto any client-supplied `?limit`. */
17
+ /** Largest `?limit` a client may ask for. A larger one is a 400, not a clamp. */
17
18
  maxLimit?: number;
18
19
  }
20
+ /**
21
+ * {@link resolveClientListLimit} for an HTTP route: the same bounds, answered
22
+ * with a 400 rather than a 500.
23
+ *
24
+ * The shared resolver throws a `ListLimitError`, which carries `status` — but
25
+ * the Hono error handler discriminates on `statusCode`, so an unconverted one
26
+ * reaches the client as `INTERNAL_ERROR` with its message stripped, telling the
27
+ * caller nothing about the parameter it got wrong. Every REST list ingress
28
+ * routes its `limit` through here so all of them name the ceiling the same way.
29
+ */
30
+ export declare function resolveListLimitParam(rawLimit: number | string | null | undefined, opts?: ListLimitBounds & {
31
+ vectorSearch?: boolean;
32
+ }): number;
19
33
  /**
20
34
  * Parse query parameters into QueryOptions
21
35
  */
@@ -2,10 +2,18 @@ import { CollectionConfig } from "@rebasepro/types";
2
2
  /**
3
3
  * Reject a write naming a field the collection does not have.
4
4
  *
5
- * Unknown keys used to travel all the way into the INSERT, where Postgres
6
- * rejected them so a typo came back as `column "titel" does not exist`,
7
- * phrased by the database, from a stack the caller cannot see, and only if the
8
- * column really was absent. It is a request problem and belongs in a 400.
5
+ * An unknown key does *not* reach the INSERT and come back as `column "titel"
6
+ * does not exist`, which is what this comment used to claim. Drizzle builds
7
+ * INSERT and UPDATE from the table's own column list, so a key the table does
8
+ * not carry is left out of the statement: the write answers 201 having stored
9
+ * nothing under that name. A typo is a request problem, it belongs in a 400,
10
+ * and nothing below this was ever going to raise one.
11
+ *
12
+ * This is the config-level check and it is skipped on several paths (see the
13
+ * `strictWrites` bail below, and the auth-adapter contract at the call site).
14
+ * `assertWritableColumns` in the Postgres driver is the backstop every write
15
+ * passes through, including in-process `rebase.data` writes that never come
16
+ * near this layer.
9
17
  *
10
18
  * What counts as known:
11
19
  * - a declared property (for an introspected BaaS collection these *are* the
@@ -20,6 +28,34 @@ export declare function assertKnownWriteFields(values: Record<string, unknown>,
20
28
  rowIndex?: number;
21
29
  extraKnownFields?: readonly string[];
22
30
  }): void;
31
+ /**
32
+ * Reject a write whose values break the constraints the collection declares.
33
+ *
34
+ * `validation.min`, `max`, `matches`, `positive`, `integer` and the date and
35
+ * array bounds were read by exactly three things: the DDL generators (for
36
+ * `integer` and for a `varchar` width), and the admin's client-side form. No
37
+ * layer on the write path looked at them, no `CHECK` constraint was emitted,
38
+ * and the generated OpenAPI published every one of them as `minimum`,
39
+ * `maximum`, `minLength`, `maxLength` and `pattern`. So the docs' own example —
40
+ * `price: { type: "number", validation: { min: 0 } }` — accepted `-5000` with a
41
+ * 201 while the spec said it would not, and any client or gateway generated
42
+ * from that spec believed the server was checking.
43
+ *
44
+ * Only `required` is left to the database, where it is a NOT NULL: a partial
45
+ * update legitimately omits keys, and this function sees one request's values,
46
+ * not the row they land on.
47
+ *
48
+ * A value of the wrong *type* is not this function's business — that reaches
49
+ * Postgres, which types every column and gives a 400 through the driver's
50
+ * SQLSTATE mapping. Constraints are checked against values of the right type,
51
+ * plus the numeric-string form a JSON body may carry for a number column.
52
+ *
53
+ * Every failing field is reported at once. A form that fixes one field per
54
+ * round trip is the reason validation errors get ignored.
55
+ */
56
+ export declare function assertWriteValuesValid(values: Record<string, unknown>, collection: CollectionConfig, options?: {
57
+ rowIndex?: number;
58
+ }): void;
23
59
  /**
24
60
  * Narrow response rows to the fields the caller asked for.
25
61
  *
@@ -33,11 +69,18 @@ export declare function assertKnownWriteFields(values: Record<string, unknown>,
33
69
  * This shapes the *response*, which is what the parameter says it does; it is
34
70
  * not a column pushdown, so it saves bandwidth rather than database work.
35
71
  *
36
- * `id` always survives. Rows are addressed by it everywhere above this layer —
37
- * the admin table, realtime reconciliation, the offline cache — and a row that
38
- * arrives without one is not a smaller row, it is an unusable one. Asking for
39
- * `fields=title` and being unable to open the record you clicked is a worse
40
- * answer than one extra key.
72
+ * The collection's key always survives. Rows are addressed by it everywhere
73
+ * above this layer — the admin table, realtime reconciliation, the offline
74
+ * cache — and a row that arrives without one is not a smaller row, it is an
75
+ * unusable one. Asking for `fields=title` and being unable to open the record
76
+ * you clicked is a worse answer than one extra key.
77
+ *
78
+ * That is a statement about the key, not about the name `id`: a collection
79
+ * keyed on `slug`, or on `user_id + role_id`, was projected down to the fields
80
+ * asked for and nothing else, which is the very outcome the paragraph above
81
+ * describes. `assertKnownWriteFields` one function up already has a dedicated
82
+ * error for collections with no `id` column, so this layer had no excuse for
83
+ * assuming one.
41
84
  */
42
85
  export declare function projectResponseFields<T extends Record<string, unknown>>(rows: T[], fields: readonly string[] | undefined, collection: CollectionConfig, options?: {
43
86
  include?: readonly string[];
@@ -9,7 +9,9 @@
9
9
  * 1. Checks for API key tokens (`rk_` prefix) first — these are Rebase-level
10
10
  * 2. Falls back to `adapter.verifyRequest(request)` to resolve the user
11
11
  * 3. Scopes the DataDriver via `withAuth()` for RLS
12
- * 4. Enforces auth (401) when `requireAuth` is true and no user is found
12
+ * 4. Rejects a presented-but-unverifiable token with 401, whatever
13
+ * `requireAuth` says
14
+ * 5. Enforces auth (401) when `requireAuth` is true and no user is found
13
15
  *
14
16
  * The behavior is identical to `createAuthMiddleware()` — only the
15
17
  * token verification strategy is pluggable.
@@ -1,4 +1,19 @@
1
1
  import type { OAuthProvider } from "./interfaces";
2
+ import { type OAuthCodeFlowPayload } from "./oauth-code-flow";
3
+ /** Extra field Apple's client sends that no other provider has. */
4
+ export interface AppleCodeFlowPayload extends OAuthCodeFlowPayload {
5
+ /**
6
+ * Apple sends the user's *name* only on the first authorization, so the
7
+ * frontend has to forward it. It is display data and nothing else — the
8
+ * identity email comes from the id_token, never from the request body.
9
+ */
10
+ user?: {
11
+ name?: {
12
+ firstName?: string;
13
+ lastName?: string;
14
+ };
15
+ };
16
+ }
2
17
  /**
3
18
  * Creates an Apple Sign In OAuth Provider integration.
4
19
  *
@@ -17,14 +32,4 @@ export declare function createAppleProvider(config: {
17
32
  keyId: string;
18
33
  /** The raw PEM contents of the .p8 private key file */
19
34
  privateKey: string;
20
- }): OAuthProvider<{
21
- code: string;
22
- redirectUri: string;
23
- user?: {
24
- name?: {
25
- firstName?: string;
26
- lastName?: string;
27
- };
28
- email?: string;
29
- };
30
- }>;
35
+ }): OAuthProvider<AppleCodeFlowPayload>;
@@ -1,11 +1,9 @@
1
1
  import type { OAuthProvider } from "./interfaces";
2
+ import { type OAuthCodeFlowPayload } from "./oauth-code-flow";
2
3
  /**
3
4
  * Creates a Bitbucket OAuth Provider integration (OAuth 2.0 consumer).
4
5
  */
5
6
  export declare function createBitbucketProvider(config: {
6
7
  clientId: string;
7
8
  clientSecret: string;
8
- }): OAuthProvider<{
9
- code: string;
10
- redirectUri: string;
11
- }>;
9
+ }): OAuthProvider<OAuthCodeFlowPayload>;
@@ -45,6 +45,8 @@ export interface BuiltinAuthAdapterConfig {
45
45
  defaultRole?: string;
46
46
  /** OAuth providers to register. */
47
47
  oauthProviders?: OAuthProvider<unknown>[];
48
+ /** Redirect URIs the OAuth routes accept, for every provider. */
49
+ allowedRedirectUris?: string[];
48
50
  /** Static service key for server-to-server auth. */
49
51
  serviceKey?: string;
50
52
  /** Auth hooks for customizing password, credentials, lifecycle, etc. */
@@ -1,14 +1,13 @@
1
1
  import type { OAuthProvider } from "./interfaces";
2
+ import { type OAuthCodeFlowPayload } from "./oauth-code-flow";
2
3
  /**
3
4
  * Creates a Discord OAuth2 Provider integration.
4
5
  *
5
- * Uses the authorization code flow. Requires the "identify" and "email"
6
- * scopes to retrieve the user's email and profile information.
6
+ * Uses the authorization code flow (with PKCE when the client supplies a
7
+ * verifier). Requires the "identify" and "email" scopes to retrieve the
8
+ * user's email and profile information.
7
9
  */
8
10
  export declare function createDiscordProvider(config: {
9
11
  clientId: string;
10
12
  clientSecret: string;
11
- }): OAuthProvider<{
12
- code: string;
13
- redirectUri: string;
14
- }>;
13
+ }): OAuthProvider<OAuthCodeFlowPayload>;
@@ -1,4 +1,5 @@
1
1
  import type { OAuthProvider } from "./interfaces";
2
+ import { type OAuthCodeFlowPayload } from "./oauth-code-flow";
2
3
  /**
3
4
  * Creates a Facebook / Meta OAuth Provider integration.
4
5
  *
@@ -8,7 +9,4 @@ import type { OAuthProvider } from "./interfaces";
8
9
  export declare function createFacebookProvider(config: {
9
10
  clientId: string;
10
11
  clientSecret: string;
11
- }): OAuthProvider<{
12
- code: string;
13
- redirectUri: string;
14
- }>;
12
+ }): OAuthProvider<OAuthCodeFlowPayload>;
@@ -1,4 +1,5 @@
1
1
  import type { OAuthProvider } from "./interfaces";
2
+ import { type OAuthCodeFlowPayload } from "./oauth-code-flow";
2
3
  /**
3
4
  * Creates a GitHub OAuth Provider integration.
4
5
  *
@@ -9,7 +10,4 @@ import type { OAuthProvider } from "./interfaces";
9
10
  export declare function createGitHubProvider(config: {
10
11
  clientId: string;
11
12
  clientSecret: string;
12
- }): OAuthProvider<{
13
- code: string;
14
- redirectUri: string;
15
- }>;
13
+ }): OAuthProvider<OAuthCodeFlowPayload>;
@@ -1,13 +1,15 @@
1
1
  import type { OAuthProvider } from "./interfaces";
2
+ import { type OAuthCodeFlowPayload } from "./oauth-code-flow";
2
3
  /**
3
4
  * Creates a GitLab OAuth Provider integration.
4
5
  * Works with both GitLab.com and self-hosted instances.
6
+ *
7
+ * `baseUrl` is configuration only and must stay that way: a caller-supplied
8
+ * instance URL would make this provider an SSRF primitive and an
9
+ * arbitrary-identity oracle in one step.
5
10
  */
6
11
  export declare function createGitLabProvider(config: {
7
12
  clientId: string;
8
13
  clientSecret: string;
9
14
  baseUrl?: string;
10
- }): OAuthProvider<{
11
- code: string;
12
- redirectUri: string;
13
- }>;
15
+ }): OAuthProvider<OAuthCodeFlowPayload>;
@@ -44,4 +44,5 @@ export declare function createGoogleProvider(config: GoogleProviderConfig | stri
44
44
  accessToken?: string;
45
45
  code?: string;
46
46
  redirectUri?: string;
47
+ codeVerifier?: string;
47
48
  }>;
@@ -1,5 +1,5 @@
1
1
  export * from "./interfaces";
2
- export { configureJwt, generateAccessToken, verifyAccessToken, generateRefreshToken, hashRefreshToken, getRefreshTokenExpiry, getAccessTokenExpiry, generateDownloadToken, verifyDownloadToken } from "./jwt";
2
+ export { configureJwt, isJwtConfigured, generateAccessToken, verifyAccessToken, generateRefreshToken, hashRefreshToken, getRefreshTokenExpiry, getAccessTokenExpiry, generateDownloadToken, verifyDownloadToken } from "./jwt";
3
3
  export type { JwtConfig, AccessTokenPayload, DownloadTokenPayload } from "./jwt";
4
4
  export { hashPassword, verifyPassword, validatePasswordStrength } from "./password";
5
5
  export { safeCompare } from "./crypto-utils";
@@ -21,6 +21,13 @@ export { createGitLabProvider } from "./gitlab-oauth";
21
21
  export { createBitbucketProvider } from "./bitbucket-oauth";
22
22
  export { createSlackProvider } from "./slack-oauth";
23
23
  export { createSpotifyProvider } from "./spotify-oauth";
24
+ export type { AppleCodeFlowPayload } from "./apple-oauth";
25
+ export { oauthCodeFlowSchema, pkceTokenParams, providerVerifiedEmail } from "./oauth-code-flow";
26
+ export type { OAuthCodeFlowPayload } from "./oauth-code-flow";
27
+ export { verifyOidcIdToken, tryVerifyOidcIdToken } from "./oidc-id-token";
28
+ export type { OidcIdTokenClaims, VerifyOidcIdTokenOptions } from "./oidc-id-token";
29
+ export { decideOAuthAutoLink, isRedirectUriAllowed } from "./oauth-signin-policy";
30
+ export type { AutoLinkDecision, AutoLinkRefusal } from "./oauth-signin-policy";
24
31
  export { requireAuth, requireAdmin, optionalAuth, extractUserFromToken, createAuthMiddleware, queryTokenAuth, fileTokenAuth } from "./middleware";
25
32
  export type { AuthMiddlewareOptions, AuthResult } from "./middleware";
26
33
  export { createAuthRoutes } from "./routes";