@rebasepro/server 0.13.1-canary.gef9608c → 0.14.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 (132) hide show
  1. package/dist/{admin_block-EmIHae6X.js → admin_block-H86dCPsj.js} +61 -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 +12 -0
  5. package/dist/api/rest/api-generator.d.ts +26 -1
  6. package/dist/api/rest/idempotency.d.ts +25 -4
  7. package/dist/api/rest/query-parser.d.ts +15 -1
  8. package/dist/api/rest/write-validation.d.ts +40 -4
  9. package/dist/auth/adapter-middleware.d.ts +3 -1
  10. package/dist/auth/admin-roles.d.ts +24 -0
  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 +11 -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 +7 -0
  20. package/dist/auth/interfaces.d.ts +81 -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-crypto.d.ts +54 -3
  24. package/dist/auth/mfa-gate.d.ts +42 -0
  25. package/dist/auth/mfa-routes.d.ts +26 -1
  26. package/dist/auth/mfa.d.ts +16 -0
  27. package/dist/auth/microsoft-oauth.d.ts +19 -4
  28. package/dist/auth/middleware.d.ts +26 -0
  29. package/dist/auth/oauth-code-flow.d.ts +66 -0
  30. package/dist/auth/oauth-signin-policy.d.ts +61 -0
  31. package/dist/auth/oidc-id-token.d.ts +61 -0
  32. package/dist/auth/rate-limiter.d.ts +62 -5
  33. package/dist/auth/registration-policy.d.ts +31 -0
  34. package/dist/auth/rls-scope.d.ts +25 -0
  35. package/dist/auth/routes.d.ts +15 -0
  36. package/dist/auth/slack-oauth.d.ts +2 -4
  37. package/dist/auth/spotify-oauth.d.ts +2 -4
  38. package/dist/auth/token-revocation.d.ts +38 -0
  39. package/dist/auth/twitter-oauth.d.ts +2 -5
  40. package/dist/{auth-DU4ShERo.js → auth-CYoPVf-E.js} +1985 -395
  41. package/dist/auth-CYoPVf-E.js.map +1 -0
  42. package/dist/{backup-BJiY_mM-.js → backup-C6ljYVTp.js} +2 -2
  43. package/dist/{backup-BJiY_mM-.js.map → backup-C6ljYVTp.js.map} +1 -1
  44. package/dist/boot/ddl-bootstrap.d.ts +70 -0
  45. package/dist/{contract-routes-Dj8i5AiM.js → contract-routes-Bet-eCNJ.js} +3 -3
  46. package/dist/{contract-routes-Dj8i5AiM.js.map → contract-routes-Bet-eCNJ.js.map} +1 -1
  47. package/dist/cron/cron-loader.d.ts +26 -1
  48. package/dist/cron/cron-routes.d.ts +1 -1
  49. package/dist/cron/cron-scheduler.d.ts +13 -3
  50. package/dist/cron/define-cron.d.ts +17 -3
  51. package/dist/cron/index.d.ts +2 -2
  52. package/dist/{cron-loader-B1S2MCSl.js → cron-loader-YhhQeVBM.js} +43 -8
  53. package/dist/cron-loader-YhhQeVBM.js.map +1 -0
  54. package/dist/{cron-routes-D5a9v1HY.js → cron-routes-maM_RlUu.js} +10 -4
  55. package/dist/cron-routes-maM_RlUu.js.map +1 -0
  56. package/dist/{cron-scheduler-BhFZWR0T.js → cron-scheduler-DIpYBmZP.js} +20 -8
  57. package/dist/cron-scheduler-DIpYBmZP.js.map +1 -0
  58. package/dist/{cron-store-Cu4sOseb.js → cron-store-Dvr4Y1sZ.js} +60 -51
  59. package/dist/cron-store-Dvr4Y1sZ.js.map +1 -0
  60. package/dist/ddl-bootstrap-BhXbTnBl.js +183 -0
  61. package/dist/ddl-bootstrap-BhXbTnBl.js.map +1 -0
  62. package/dist/email/html.d.ts +54 -0
  63. package/dist/email/index.d.ts +3 -0
  64. package/dist/email/link-base.d.ts +39 -0
  65. package/dist/email/smtp-email-service.d.ts +7 -1
  66. package/dist/email/templates.d.ts +9 -1
  67. package/dist/email/types.d.ts +11 -1
  68. package/dist/env.d.ts +5 -0
  69. package/dist/{errors-BpudWAVU.js → errors-EBYiaJ2E.js} +14 -2
  70. package/dist/errors-EBYiaJ2E.js.map +1 -0
  71. package/dist/function-loader-DDS1v7YX.js +139 -0
  72. package/dist/function-loader-DDS1v7YX.js.map +1 -0
  73. package/dist/{function-routes-C0cLIy3N.js → function-routes-Btcez1T-.js} +18 -6
  74. package/dist/function-routes-Btcez1T-.js.map +1 -0
  75. package/dist/functions/define-function.d.ts +25 -6
  76. package/dist/functions/function-loader.d.ts +23 -0
  77. package/dist/functions/function-routes.d.ts +7 -1
  78. package/dist/functions/request-timeout.d.ts +34 -0
  79. package/dist/history/history-routes.d.ts +6 -0
  80. package/dist/index.d.ts +3 -2
  81. package/dist/index.es.js +1893 -714
  82. package/dist/index.es.js.map +1 -1
  83. package/dist/init/docs.d.ts +10 -1
  84. package/dist/init/process-safety.d.ts +25 -0
  85. package/dist/init.d.ts +38 -0
  86. package/dist/{jwt-CzeviDcB.js → jwt-_IFqfTOg.js} +57 -9
  87. package/dist/jwt-_IFqfTOg.js.map +1 -0
  88. package/dist/logger-DfvF_8r-.js +190 -0
  89. package/dist/logger-DfvF_8r-.js.map +1 -0
  90. package/dist/{openapi-generator-D5xsfVJY.js → openapi-generator-DPKtUC9X.js} +326 -55
  91. package/dist/openapi-generator-DPKtUC9X.js.map +1 -0
  92. package/dist/request-timeout-RivJsME0.js +65 -0
  93. package/dist/request-timeout-RivJsME0.js.map +1 -0
  94. package/dist/schema-editor-routes-CRcS3ArS.js +437 -0
  95. package/dist/schema-editor-routes-CRcS3ArS.js.map +1 -0
  96. package/dist/serve-spa.d.ts +5 -4
  97. package/dist/services/outbound-url-guard.d.ts +53 -0
  98. package/dist/services/routed-realtime-service.d.ts +10 -2
  99. package/dist/services/webhook-service.d.ts +76 -1
  100. package/dist/singleton.d.ts +25 -8
  101. package/dist/{src-CKOZBpeJ.js → src-C7rkDGxA.js} +619 -70
  102. package/dist/src-C7rkDGxA.js.map +1 -0
  103. package/dist/{src-_qQ3RNCK.js → src-Cz9nMgUR.js} +93 -3
  104. package/dist/src-Cz9nMgUR.js.map +1 -0
  105. package/dist/storage/LocalStorageController.d.ts +12 -1
  106. package/dist/storage/image-transform.d.ts +57 -0
  107. package/dist/storage/keys.d.ts +98 -0
  108. package/dist/storage/routes.d.ts +11 -0
  109. package/dist/storage/tus-handler.d.ts +22 -2
  110. package/dist/utils/logger.d.ts +18 -0
  111. package/dist/utils/sql.d.ts +11 -6
  112. package/package.json +6 -5
  113. package/dist/admin_block-EmIHae6X.js.map +0 -1
  114. package/dist/auth-DU4ShERo.js.map +0 -1
  115. package/dist/backend-CIxN4FVm.js +0 -15
  116. package/dist/backend-CIxN4FVm.js.map +0 -1
  117. package/dist/cron-loader-B1S2MCSl.js.map +0 -1
  118. package/dist/cron-routes-D5a9v1HY.js.map +0 -1
  119. package/dist/cron-scheduler-BhFZWR0T.js.map +0 -1
  120. package/dist/cron-store-Cu4sOseb.js.map +0 -1
  121. package/dist/errors-BpudWAVU.js.map +0 -1
  122. package/dist/function-loader-B_1fYfUY.js +0 -86
  123. package/dist/function-loader-B_1fYfUY.js.map +0 -1
  124. package/dist/function-routes-C0cLIy3N.js.map +0 -1
  125. package/dist/jwt-CzeviDcB.js.map +0 -1
  126. package/dist/logger-BYU66ENZ.js +0 -94
  127. package/dist/logger-BYU66ENZ.js.map +0 -1
  128. package/dist/openapi-generator-D5xsfVJY.js.map +0 -1
  129. package/dist/schema-editor-routes-C1DxDnDR.js +0 -248
  130. package/dist/schema-editor-routes-C1DxDnDR.js.map +0 -1
  131. package/dist/src-CKOZBpeJ.js.map +0 -1
  132. package/dist/src-_qQ3RNCK.js.map +0 -1
@@ -55,6 +55,7 @@ var ADMIN_COLLECTION_KEYS = [
55
55
  "formAutoSave",
56
56
  "formView",
57
57
  "group",
58
+ "hideFromEntityViews",
58
59
  "hideFromNavigation",
59
60
  "hideIdFromCollection",
60
61
  "hideIdFromForm",
@@ -119,7 +120,65 @@ var ADMIN_PROPERTY_KEYS = [
119
120
  "urlPreview",
120
121
  "widget"
121
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
+ }
122
181
  //#endregion
123
- 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 };
124
183
 
125
- //# sourceMappingURL=admin_block-EmIHae6X.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
  }
@@ -17,6 +17,18 @@ export declare class ApiError extends Error {
17
17
  * The motivating case is `POST /auth/refresh` with no session: clients
18
18
  * refresh on page load before they know whether one exists, so every
19
19
  * anonymous page view is a 401 — correct, and not worth a warning line.
20
+ *
21
+ * The other class is a caller-caused 4xx that never reached the database: a
22
+ * mistyped filter operator, sort direction or limit, a request for a
23
+ * collection that does not exist. Nothing on this server is wrong, and the
24
+ * response body has already told the caller what to fix — while one client
25
+ * holding a stale name would otherwise write a warning per request, forever,
26
+ * until the level means nothing. See `api/rest/query-parser.ts`.
27
+ *
28
+ * What stays at warn is anything that says something about the *server*:
29
+ * a schema that has drifted from the code, a permission the database
30
+ * refused, a dependency that failed. Those are 4xx too, and they are still
31
+ * incidents.
20
32
  */
21
33
  readonly expected: boolean;
22
34
  constructor(statusCode: number, code: string, message: string, details?: unknown, expected?: boolean);
@@ -32,6 +32,31 @@ export declare class RestApiGenerator {
32
32
  * Generate REST routes using existing DataDriver
33
33
  */
34
34
  generateRoutes(): Hono<HonoEnv>;
35
+ /**
36
+ * Answer anything left under `/api/data` in the canonical error envelope.
37
+ *
38
+ * Without this, `GET /api/data/nonexistent` fell through to Hono's default
39
+ * handler and came back as the plain text `404 Not Found` — the one error
40
+ * on the data API that is not `{"error":{"message","code","requestId"}}`.
41
+ * A client that does `res.json()` on the error path got a parse failure
42
+ * where every other 4xx hands it a code, so a mistyped collection name
43
+ * surfaced as "invalid JSON" rather than "no such collection".
44
+ *
45
+ * Registered after every real route, so a request reaches it only when the
46
+ * slug is unknown *or* the method/sub-path is: those are different
47
+ * mistakes, and the message says which. The collection list is deliberately
48
+ * not enumerated — an unauthenticated request to this same URL is answered
49
+ * 401 before routing, on purpose, and echoing the full set of slugs back
50
+ * would undo that for any signed-in caller.
51
+ *
52
+ * Every answer here is `expected`, so it logs at debug (see
53
+ * {@link ApiError.expected}). This path used to log *nothing at all* —
54
+ * Hono's default 404 never reaches `errorHandler` — so routing it through
55
+ * the handler at warn would have traded a missing error envelope for a
56
+ * `⚠️` line on every request from any frontend holding a stale slug. The
57
+ * envelope is the fix; the log volume is not part of it.
58
+ */
59
+ private createUnmatchedRoute;
35
60
  /**
36
61
  * Check API key permissions for a collection operation.
37
62
  * Throws 403 if the key doesn't have the required permission.
@@ -54,7 +79,7 @@ export declare class RestApiGenerator {
54
79
  * Needed so a nested write can be checked against a schema at all. Without
55
80
  * it these routes skipped `assertKnownWriteFields` entirely, which is why a
56
81
  * typo `POST /posts` rejected with a 400 while the same typo on
57
- * `POST /authors/1/posts` reached the database.
82
+ * `POST /authors/1/posts` was dropped from the statement and answered 201.
58
83
  *
59
84
  * Returns `undefined` rather than throwing when the path cannot be walked:
60
85
  * the driver raises the authoritative error a moment later, and duplicating
@@ -1,4 +1,17 @@
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;
2
15
  /**
3
16
  * What a caller may do with a key.
4
17
  *
@@ -20,6 +33,10 @@ export type IdempotencyClaim =
20
33
  | {
21
34
  status: "in-flight";
22
35
  }
36
+ /** This key is live, but it was claimed for a different request. */
37
+ | {
38
+ status: "mismatch";
39
+ }
23
40
  /** The key is ours; do the write, then `complete` or `release` it. */
24
41
  | {
25
42
  status: "claimed";
@@ -29,16 +46,20 @@ export interface IdempotencyStore {
29
46
  * Take the key if it is free, atomically. Never throws — a store that
30
47
  * cannot answer reports `claimed`, which degrades to no idempotency rather
31
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`.
32
53
  */
33
- claim(key: string, uid: string | undefined): Promise<IdempotencyClaim>;
54
+ claim(key: string, uid: string | undefined, fingerprint: string): Promise<IdempotencyClaim>;
34
55
  /** Record what this key answered. Never throws. */
35
56
  complete(key: string, uid: string | undefined, response: unknown): Promise<void>;
36
57
  /**
37
58
  * Give the key back after the write it was claimed for failed.
38
59
  *
39
- * Without this a transient failure strands the key: every retry would be
40
- * told `in-flight` until the row aged out, turning one failed write into a
41
- * day of refusals.
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.
42
63
  */
43
64
  release(key: string, uid: string | undefined): Promise<void>;
44
65
  }
@@ -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
  *
@@ -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.
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Which role names carry administrative privilege.
3
+ *
4
+ * One definition, because there used to be several and they disagreed.
5
+ * `requireAdmin` accepted `admin` **or** `schema-admin`; the guard that refuses
6
+ * a dangerous `defaultRole` compared against `admin` alone. So
7
+ * `AUTH_DEFAULT_ROLE=schema-admin` passed the guard and made every public
8
+ * registrant an administrator — and, since a `schema-admin` may edit users, one
9
+ * of them could then grant themselves real `admin` and keep it.
10
+ *
11
+ * The guard and the check have to read the same list or the gap comes back, so
12
+ * neither of them owns it.
13
+ *
14
+ * `schema-admin` is here deliberately: it can reach the schema editor and the
15
+ * SQL surfaces, which is administrative by any definition that matters. If a
16
+ * genuinely lesser role is added later, it does not belong in this list — it
17
+ * belongs in a capability check of its own.
18
+ */
19
+ export declare const ADMINISTRATIVE_ROLES: readonly ["admin", "schema-admin"];
20
+ export type AdministrativeRole = (typeof ADMINISTRATIVE_ROLES)[number];
21
+ /** Does this single role name carry administrative privilege? */
22
+ export declare function isAdministrativeRole(role: string): role is AdministrativeRole;
23
+ /** Does any role in this list carry administrative privilege? */
24
+ export declare function hasAdministrativeRole(roles: readonly string[] | null | undefined): boolean;
@@ -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>;
@@ -39,12 +39,23 @@ export interface BuiltinAuthAdapterConfig {
39
39
  * wiring path a real backend uses).
40
40
  */
41
41
  disableSelfRegistration?: boolean;
42
+ /**
43
+ * Opt-in: allow `POST /auth/anonymous` to mint a user without credentials.
44
+ *
45
+ * Off by default, and overridden by `disableSelfRegistration`. Anonymous
46
+ * sign-in inserts a `users` row and assigns `defaultRole` exactly as
47
+ * registration does, so before this key existed a closed backend still
48
+ * handed out permanent accounts via `/auth/anonymous` + `/auth/anonymous/link`.
49
+ */
50
+ allowAnonymous?: boolean;
42
51
  /** Whether to expose the authenticated email→minimal-profile lookup route. */
43
52
  allowUserLookup?: boolean;
44
53
  /** Default role to assign to new users. */
45
54
  defaultRole?: string;
46
55
  /** OAuth providers to register. */
47
56
  oauthProviders?: OAuthProvider<unknown>[];
57
+ /** Redirect URIs the OAuth routes accept, for every provider. */
58
+ allowedRedirectUris?: string[];
48
59
  /** Static service key for server-to-server auth. */
49
60
  serviceKey?: string;
50
61
  /** 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
  }>;
@@ -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";