@rebasepro/server 0.21.1 → 0.22.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 (26) hide show
  1. package/dist/auth/exclude-auth-secrets.d.ts +35 -3
  2. package/dist/{auth-BS4WBA10.js → auth-CCDpk2rn.js} +12 -9
  3. package/dist/auth-CCDpk2rn.js.map +1 -0
  4. package/dist/{cron-routes-B_wlLybo.js → cron-routes-B7CRGfiq.js} +2 -2
  5. package/dist/{cron-routes-B_wlLybo.js.map → cron-routes-B7CRGfiq.js.map} +1 -1
  6. package/dist/{cron-store-Dp0X7zRB.js → cron-store-BYGZFNWk.js} +2 -2
  7. package/dist/{cron-store-Dp0X7zRB.js.map → cron-store-BYGZFNWk.js.map} +1 -1
  8. package/dist/ddl-bootstrap-CfNvxMuK.js.map +1 -1
  9. package/dist/{history-recorder-5VKT7nss.js → history-recorder-BQmB0P_j.js} +2 -2
  10. package/dist/{history-recorder-5VKT7nss.js.map → history-recorder-BQmB0P_j.js.map} +1 -1
  11. package/dist/{history-store-By2R_G1b.js → history-store-CetkrBBD.js} +2 -2
  12. package/dist/{history-store-By2R_G1b.js.map → history-store-CetkrBBD.js.map} +1 -1
  13. package/dist/index.es.js +73 -59
  14. package/dist/index.es.js.map +1 -1
  15. package/dist/init/global-callbacks.d.ts +21 -0
  16. package/dist/{jobs-1IkI4Lle.js → jobs-Bjr8DZAi.js} +2 -2
  17. package/dist/{jobs-1IkI4Lle.js.map → jobs-Bjr8DZAi.js.map} +1 -1
  18. package/dist/{openapi-generator-BDpoKZQ9.js → openapi-generator-D8uFz-LW.js} +2 -2
  19. package/dist/{openapi-generator-BDpoKZQ9.js.map → openapi-generator-D8uFz-LW.js.map} +1 -1
  20. package/dist/{query-parser-C-rl30ce.js → query-parser-BleZmY18.js} +2 -2
  21. package/dist/{query-parser-C-rl30ce.js.map → query-parser-BleZmY18.js.map} +1 -1
  22. package/dist/{src-Caz7nREj.js → src-1vL-I1Po.js} +232 -120
  23. package/dist/src-1vL-I1Po.js.map +1 -0
  24. package/package.json +5 -5
  25. package/dist/auth-BS4WBA10.js.map +0 -1
  26. package/dist/src-Caz7nREj.js.map +0 -1
@@ -1528,6 +1528,42 @@ function suggestNearMiss(known, key) {
1528
1528
  return known.find((candidate) => isNearMiss(candidate, key));
1529
1529
  }
1530
1530
  /**
1531
+ * The terms a user meant when they typed into a search box.
1532
+ *
1533
+ * A search box is not a substring box. Typing `sebastian melendez` on a
1534
+ * collection whose name is split across `first_name` and `last_name` has to
1535
+ * find the row, and matching the whole string against each field separately
1536
+ * cannot: no single column holds both words. Splitting on whitespace and
1537
+ * requiring *every* term to match *some* field is what does, and it is what
1538
+ * `websearch_to_tsquery` already does for collections that declared a `search`
1539
+ * block — so this is also what keeps the two search paths answering the same
1540
+ * question.
1541
+ *
1542
+ * It fixes the trailing space too: `sebastian ` matched nothing at all, because
1543
+ * the space was part of the pattern.
1544
+ *
1545
+ * A double-quoted run is one term, again matching the full-text path, so a
1546
+ * caller who really does want the phrase can ask for it. An unbalanced quote is
1547
+ * stripped rather than honoured — the alternative is that everything after a
1548
+ * stray `"` silently becomes one long term that matches nothing.
1549
+ *
1550
+ * Returns an empty array for a string with no terms in it at all (whitespace,
1551
+ * or nothing but quotes); the caller decides what an empty search means, since
1552
+ * only it knows whether "no constraint" or "no rows" is the safe reading.
1553
+ */
1554
+ function splitSearchTerms(searchString, maxTerms = 8) {
1555
+ const terms = [];
1556
+ const pattern = /"([^"]*)"|(\S+)/g;
1557
+ let match;
1558
+ while ((match = pattern.exec(searchString)) !== null) {
1559
+ const term = (match[1] ?? match[2].replace(/"/g, "")).trim();
1560
+ if (!term) continue;
1561
+ terms.push(term);
1562
+ if (terms.length >= maxTerms) break;
1563
+ }
1564
+ return terms;
1565
+ }
1566
+ /**
1531
1567
  * Deep clone a value, preserving function references and class instances.
1532
1568
  * Unlike structuredClone, this handles objects that contain functions
1533
1569
  * (e.g. CollectionConfig with target(), childCollections(), callbacks).
@@ -2739,7 +2775,7 @@ var DEFAULT_GUARDED_OPS = [
2739
2775
  "delete"
2740
2776
  ];
2741
2777
  /** Whether a collection is flagged as an authentication collection. */
2742
- function isAuthCollection(collection) {
2778
+ function isAuthCollection$1(collection) {
2743
2779
  const auth = collection.auth;
2744
2780
  return auth === true || typeof auth === "object" && auth?.enabled === true;
2745
2781
  }
@@ -2792,7 +2828,7 @@ function getEffectiveSecurityRules(collection) {
2792
2828
  if (isPostgresCollectionConfig(collection) && collection.disableDefaultPolicies) return [
2793
2829
  ...explicit,
2794
2830
  ...tenantRule(collection),
2795
- ...isAuthCollection(collection) ? [adminWriteGate(tableName)] : []
2831
+ ...isAuthCollection$1(collection) ? [adminWriteGate(tableName)] : []
2796
2832
  ];
2797
2833
  injected.push({
2798
2834
  name: `${tableName}_default_admin_read`,
@@ -2805,7 +2841,7 @@ function getEffectiveSecurityRules(collection) {
2805
2841
  condition: SERVER_OR_ADMIN_EXPR$1,
2806
2842
  check: SERVER_OR_ADMIN_EXPR$1
2807
2843
  });
2808
- if (isAuthCollection(collection)) {
2844
+ if (isAuthCollection$1(collection)) {
2809
2845
  injected.push({
2810
2846
  name: `${tableName}_default_self_read`,
2811
2847
  operations: ["select"],
@@ -3295,6 +3331,165 @@ function revokeInternalTableSql(schema, table) {
3295
3331
  `.trim();
3296
3332
  }
3297
3333
  //#endregion
3334
+ //#region ../common/src/collections/default-collections.ts
3335
+ /**
3336
+ * Default users collection.
3337
+ *
3338
+ * Prepended to the developer's collections array by the admin and server.
3339
+ * Slug-based dedup (Map keyed by slug, last-write-wins) lets developers
3340
+ * override by defining their own collection with `slug: "users"`.
3341
+ *
3342
+ * Schema only — no `admin` block. This package is on the backend's dependency path,
3343
+ * where that field does not exist: `@rebasepro/cms-types` adds it by declaration
3344
+ * merging, and a BaaS install never installs that. The scaffolded
3345
+ * `config/collections/users.ts` carries the presentation for projects that want this
3346
+ * collection in their panel, which is also where it is editable.
3347
+ */
3348
+ var defaultUsersCollection = defineCollection({
3349
+ name: "Users",
3350
+ singularName: "User",
3351
+ slug: "users",
3352
+ auth: true,
3353
+ table: "users",
3354
+ schema: "rebase",
3355
+ securityRules: [{
3356
+ operation: "select",
3357
+ roles: ["admin"]
3358
+ }, {
3359
+ operations: [
3360
+ "insert",
3361
+ "update",
3362
+ "delete"
3363
+ ],
3364
+ roles: ["admin"]
3365
+ }],
3366
+ properties: {
3367
+ id: {
3368
+ name: "ID",
3369
+ type: "string",
3370
+ isId: "uuid"
3371
+ },
3372
+ email: {
3373
+ name: "Email",
3374
+ type: "string",
3375
+ validation: {
3376
+ required: true,
3377
+ unique: true
3378
+ }
3379
+ },
3380
+ displayName: {
3381
+ name: "Name",
3382
+ type: "string",
3383
+ columnName: "display_name",
3384
+ validation: { required: true }
3385
+ },
3386
+ photoURL: {
3387
+ name: "Photo URL",
3388
+ type: "string",
3389
+ columnName: "photo_url"
3390
+ },
3391
+ roles: {
3392
+ name: "Roles",
3393
+ type: "array",
3394
+ columnType: "text[]",
3395
+ of: {
3396
+ name: "Role",
3397
+ type: "string",
3398
+ enum: {
3399
+ admin: "Admin",
3400
+ editor: "Editor",
3401
+ viewer: "Viewer"
3402
+ }
3403
+ }
3404
+ },
3405
+ passwordHash: {
3406
+ name: "Password Hash",
3407
+ type: "string",
3408
+ columnName: "password_hash",
3409
+ excludeFromApi: true
3410
+ },
3411
+ emailVerified: {
3412
+ name: "Email Verified",
3413
+ type: "boolean",
3414
+ columnName: "email_verified",
3415
+ defaultValue: false
3416
+ },
3417
+ emailVerificationToken: {
3418
+ name: "Email Verification Token",
3419
+ type: "string",
3420
+ columnName: "email_verification_token",
3421
+ excludeFromApi: true
3422
+ },
3423
+ emailVerificationSentAt: {
3424
+ name: "Email Verification Sent At",
3425
+ type: "date",
3426
+ columnName: "email_verification_sent_at"
3427
+ },
3428
+ metadata: {
3429
+ name: "Metadata",
3430
+ type: "map",
3431
+ keyValue: true,
3432
+ properties: {},
3433
+ defaultValue: {}
3434
+ },
3435
+ createdAt: {
3436
+ name: "Created At",
3437
+ type: "date",
3438
+ columnName: "created_at",
3439
+ autoValue: "on_create"
3440
+ },
3441
+ updatedAt: {
3442
+ name: "Updated At",
3443
+ type: "date",
3444
+ columnName: "updated_at",
3445
+ autoValue: "on_update"
3446
+ }
3447
+ }
3448
+ });
3449
+ //#endregion
3450
+ //#region ../common/src/collections/auth-secrets.ts
3451
+ /** Is this collection the one the auth subsystem stores users in? */
3452
+ function isAuthCollection(collection) {
3453
+ const auth = collection.auth;
3454
+ if (auth === true) return true;
3455
+ return typeof auth === "object" && auth !== null && "enabled" in auth && auth.enabled === true;
3456
+ }
3457
+ /**
3458
+ * The column names, and property keys, the default users collection marks
3459
+ * `excludeFromApi`.
3460
+ *
3461
+ * Both spellings, because a redeclaration is free to rename the property: one
3462
+ * project's copy says `password_hash` where the default says `passwordHash`, and
3463
+ * the column is the same either way. Read off {@link defaultUsersCollection}, so
3464
+ * a secret added there is covered on the same commit.
3465
+ */
3466
+ function defaultSecretNames() {
3467
+ const properties = defaultUsersCollection.properties;
3468
+ const names = /* @__PURE__ */ new Set();
3469
+ for (const [key, property] of Object.entries(properties)) {
3470
+ if (!property?.excludeFromApi) continue;
3471
+ names.add(key);
3472
+ if (property.columnName) names.add(property.columnName);
3473
+ }
3474
+ return names;
3475
+ }
3476
+ /**
3477
+ * The keys of the properties on `collection` that hold an auth secret and do not
3478
+ * carry `excludeFromApi`. Empty for any collection that is not the user store: a
3479
+ * `password_hash` column elsewhere — a CRM importing hashes, say — is that
3480
+ * project's data, not this rule's business.
3481
+ */
3482
+ function authSecretsMissingExclusion(collection) {
3483
+ if (!collection.properties || !isAuthCollection(collection)) return [];
3484
+ const secrets = defaultSecretNames();
3485
+ const missing = [];
3486
+ for (const [key, property] of Object.entries(collection.properties)) {
3487
+ if (!property || property.excludeFromApi) continue;
3488
+ if (secrets.has(key) || secrets.has(property.columnName ?? key)) missing.push(key);
3489
+ }
3490
+ return missing;
3491
+ }
3492
+ //#endregion
3298
3493
  //#region ../../node_modules/.pnpm/fast-equals@6.0.2/node_modules/fast-equals/dist/es/index.mjs
3299
3494
  var { getOwnPropertyNames, getOwnPropertySymbols } = Object;
3300
3495
  var { hasOwnProperty } = Object.prototype;
@@ -3912,7 +4107,9 @@ var CollectionRegistry = class {
3912
4107
  if (!result.dataSource) result.dataSource = resolved.key;
3913
4108
  if (!result.engine) result.engine = resolved.engine;
3914
4109
  }
3915
- result.properties = this.normalizeProperties(result.properties, result);
4110
+ const properties = this.normalizeProperties(result.properties, result);
4111
+ result.properties = properties;
4112
+ for (const key of authSecretsMissingExclusion(result)) properties[key].excludeFromApi = true;
3916
4113
  return result;
3917
4114
  }
3918
4115
  normalizeProperties(properties, collection) {
@@ -4030,121 +4227,36 @@ var CollectionRegistry = class {
4030
4227
  }
4031
4228
  };
4032
4229
  //#endregion
4033
- //#region ../common/src/collections/default-collections.ts
4034
- /**
4035
- * Default users collection.
4036
- *
4037
- * Prepended to the developer's collections array by the admin and server.
4038
- * Slug-based dedup (Map keyed by slug, last-write-wins) lets developers
4039
- * override by defining their own collection with `slug: "users"`.
4040
- *
4041
- * Schema only — no `admin` block. This package is on the backend's dependency path,
4042
- * where that field does not exist: `@rebasepro/cms-types` adds it by declaration
4043
- * merging, and a BaaS install never installs that. The scaffolded
4044
- * `config/collections/users.ts` carries the presentation for projects that want this
4045
- * collection in their panel, which is also where it is editable.
4046
- */
4047
- var defaultUsersCollection = defineCollection({
4048
- name: "Users",
4049
- singularName: "User",
4050
- slug: "users",
4051
- auth: true,
4052
- table: "users",
4053
- schema: "rebase",
4054
- securityRules: [{
4055
- operation: "select",
4056
- roles: ["admin"]
4057
- }, {
4058
- operations: [
4059
- "insert",
4060
- "update",
4061
- "delete"
4062
- ],
4063
- roles: ["admin"]
4064
- }],
4065
- properties: {
4066
- id: {
4067
- name: "ID",
4068
- type: "string",
4069
- isId: "uuid"
4070
- },
4071
- email: {
4072
- name: "Email",
4073
- type: "string",
4074
- validation: {
4075
- required: true,
4076
- unique: true
4077
- }
4078
- },
4079
- displayName: {
4080
- name: "Name",
4081
- type: "string",
4082
- columnName: "display_name",
4083
- validation: { required: true }
4084
- },
4085
- photoURL: {
4086
- name: "Photo URL",
4087
- type: "string",
4088
- columnName: "photo_url"
4089
- },
4090
- roles: {
4091
- name: "Roles",
4092
- type: "array",
4093
- columnType: "text[]",
4094
- of: {
4095
- name: "Role",
4096
- type: "string",
4097
- enum: {
4098
- admin: "Admin",
4099
- editor: "Editor",
4100
- viewer: "Viewer"
4101
- }
4102
- }
4103
- },
4104
- passwordHash: {
4105
- name: "Password Hash",
4106
- type: "string",
4107
- columnName: "password_hash",
4108
- excludeFromApi: true
4109
- },
4110
- emailVerified: {
4111
- name: "Email Verified",
4112
- type: "boolean",
4113
- columnName: "email_verified",
4114
- defaultValue: false
4115
- },
4116
- emailVerificationToken: {
4117
- name: "Email Verification Token",
4118
- type: "string",
4119
- columnName: "email_verification_token",
4120
- excludeFromApi: true
4121
- },
4122
- emailVerificationSentAt: {
4123
- name: "Email Verification Sent At",
4124
- type: "date",
4125
- columnName: "email_verification_sent_at"
4126
- },
4127
- metadata: {
4128
- name: "Metadata",
4129
- type: "map",
4130
- keyValue: true,
4131
- properties: {},
4132
- defaultValue: {}
4133
- },
4134
- createdAt: {
4135
- name: "Created At",
4136
- type: "date",
4137
- columnName: "created_at",
4138
- autoValue: "on_create"
4139
- },
4140
- updatedAt: {
4141
- name: "Updated At",
4142
- type: "date",
4143
- columnName: "updated_at",
4144
- autoValue: "on_update"
4145
- }
4230
+ //#region ../common/src/collections/engine-support.ts
4231
+ /**
4232
+ * Refuse a `beforeQuery` hook on a collection Postgres does not serve.
4233
+ *
4234
+ * `beforeQuery` narrows a read *before it is compiled*, which means compiling
4235
+ * a declarative filter into the query the engine is about to run. Only
4236
+ * `@rebasepro/server-postgres` does that today. On any other engine the hook
4237
+ * would look configured and do nothing — and for a hook whose whole purpose is
4238
+ * to restrict which rows come back, "nothing" means every row served to
4239
+ * everybody.
4240
+ *
4241
+ * So it is a boot failure, named. The alternative — declaring the hook only on
4242
+ * `PostgresCollectionConfig`, the way `search` is — is not available:
4243
+ * `callbacks` is one engine-agnostic type shared by every collection, and
4244
+ * splitting it would fork `afterRead` and the five write hooks along with it.
4245
+ *
4246
+ * Lives here rather than in either driver because both have to run it: the
4247
+ * Postgres schema planner sees every collection in a mixed app, and a
4248
+ * Mongo-only app never boots a Postgres planner at all.
4249
+ *
4250
+ * Called with *every* collection, before an engine filters any out.
4251
+ */
4252
+ var assertBeforeQueryIsPostgresOnly = (collections) => {
4253
+ for (const collection of collections) {
4254
+ if (isPostgresCollectionConfig(collection)) continue;
4255
+ if (!collection.callbacks?.beforeQuery) continue;
4256
+ const engine = collection.engine ?? "non-postgres";
4257
+ throw new Error(`${collection.slug}.callbacks.beforeQuery: narrowing a read before it is compiled is a Postgres feature, and this collection is served by \`${engine}\`. Remove the hook — it would otherwise look configured while every read kept returning every row the engine allows. Redaction that works on every engine is \`afterRead\`.`);
4146
4258
  }
4147
- });
4259
+ };
4148
4260
  /**
4149
4261
  * What a property's access rules actually are, with `excludeFromApi` expanded.
4150
4262
  *
@@ -6463,6 +6575,6 @@ function buildRoutedRebaseData({ defaultData, sources, resolveKey }) {
6463
6575
  }
6464
6576
  new Set(REBASE_INTERNAL_TABLES);
6465
6577
  //#endregion
6466
- export { resolveClientListLimit as $, createDataSourceRegistry as A, findRelation as B, decodeCursor as C, restrictedFieldNames as D, effectiveAccess as E, getJunctionConfigForRelation as F, buildCompositeId as G, isRelationRequired as H, getEffectiveSecurityRules as I, suggestNearMiss as J, resolvePrimaryKeys as K, getTenantConfig as L, resolveDataSource as M, REBASE_USER_ROLE as N, defaultUsersCollection as O, revokeInternalTableSql as P, MAX_LIST_LIMIT as Q, securityRuleToConditions as R, cursorToStartAfter as S, canWriteField as T, resolveCollectionRelations as U, getTableName as V, enumToObjectEntries as W, unref as X, toSnakeCase as Y, ListLimitError as Z, OrderBySpecError as _, deserializeLogicalCondition as a, isAnonymousUid as at, CursorError as b, collectAllPages as c, isToMany as ct, IncludeSpecError as d, GeoPoint as dt, BATCH_REF_KEY as et, deserializeInclude as f, Vector as ft, topLevelIncludeNames as g, unsupportedMethod as gt, serializeInclude as h, isUnsupported as ht, deserializeFilter as i, ANONYMOUS_USER_ID as it, isRelationalCollection as j, CollectionRegistry as k, paginateFind as l, EntityReference as lt, normalizeInclude as m, RebaseClientError as mt, buildSdkData as n, hasFieldOperation as nt, serializeFilter as o, JUNCTION_PIVOT_KEY as ot, mergeIncludeSpecs as p, RebaseApiError as pt, hydrateRegExp as q, UnknownFilterOperatorError as r, isFieldOperation as rt, serializeLogicalCondition as s, isManyToMany as st, buildRoutedRebaseData as t, FIELD_OPERATORS as tt, resolveFindWindow as u, EntityRelation as ut, normalizeOrderBy as v, reconcileCursorOrder as w, CursorMismatchError as x, serializeOrderBy as y, fieldKeyForColumn as z };
6578
+ export { ListLimitError as $, createDataSourceRegistry as A, fieldKeyForColumn as B, decodeCursor as C, restrictedFieldNames as D, effectiveAccess as E, revokeInternalTableSql as F, enumToObjectEntries as G, getTableName as H, getJunctionConfigForRelation as I, hydrateRegExp as J, buildCompositeId as K, getEffectiveSecurityRules as L, resolveDataSource as M, authSecretsMissingExclusion as N, assertBeforeQueryIsPostgresOnly as O, REBASE_USER_ROLE as P, unref as Q, getTenantConfig as R, cursorToStartAfter as S, canWriteField as T, isRelationRequired as U, findRelation as V, resolveCollectionRelations as W, suggestNearMiss as X, splitSearchTerms as Y, toSnakeCase as Z, OrderBySpecError as _, isUnsupported as _t, deserializeLogicalCondition as a, isFieldOperation as at, CursorError as b, collectAllPages as c, JUNCTION_PIVOT_KEY as ct, IncludeSpecError as d, EntityReference as dt, MAX_LIST_LIMIT as et, deserializeInclude as f, EntityRelation as ft, topLevelIncludeNames as g, RebaseClientError as gt, serializeInclude as h, RebaseApiError as ht, deserializeFilter as i, hasFieldOperation as it, isRelationalCollection as j, CollectionRegistry as k, paginateFind as l, isManyToMany as lt, normalizeInclude as m, Vector as mt, buildSdkData as n, BATCH_REF_KEY as nt, serializeFilter as o, ANONYMOUS_USER_ID as ot, mergeIncludeSpecs as p, GeoPoint as pt, resolvePrimaryKeys as q, UnknownFilterOperatorError as r, FIELD_OPERATORS as rt, serializeLogicalCondition as s, isAnonymousUid as st, buildRoutedRebaseData as t, resolveClientListLimit as tt, resolveFindWindow as u, isToMany as ut, normalizeOrderBy as v, unsupportedMethod as vt, reconcileCursorOrder as w, CursorMismatchError as x, serializeOrderBy as y, securityRuleToConditions as z };
6467
6579
 
6468
- //# sourceMappingURL=src-Caz7nREj.js.map
6580
+ //# sourceMappingURL=src-1vL-I1Po.js.map