@rebasepro/common 0.17.3 → 0.18.1

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 (58) hide show
  1. package/README.md +4 -0
  2. package/dist/collections/CollectionRegistry.d.ts +1 -1
  3. package/dist/collections/default-collections.d.ts +15 -84
  4. package/dist/data/buildRebaseData.d.ts +1 -1
  5. package/dist/data/filter-dialect.d.ts +11 -0
  6. package/dist/data/sort-dialect.d.ts +15 -3
  7. package/dist/index.es.js +375 -63
  8. package/dist/index.es.js.map +1 -1
  9. package/dist/util/builders.d.ts +69 -24
  10. package/dist/util/callback-errors.d.ts +77 -0
  11. package/dist/util/callback-errors.test.d.ts +1 -0
  12. package/dist/util/index.d.ts +1 -0
  13. package/dist/util/policy/evaluatePolicy.d.ts +6 -0
  14. package/dist/util/relations.d.ts +41 -0
  15. package/dist/util/table-name.test.d.ts +1 -0
  16. package/package.json +26 -22
  17. package/src/collections/CollectionRegistry.ts +0 -485
  18. package/src/collections/default-collections.ts +0 -109
  19. package/src/collections/index.ts +0 -2
  20. package/src/data/buildRebaseData.ts +0 -816
  21. package/src/data/buildRoutedRebaseData.ts +0 -103
  22. package/src/data/filter-conditions.ts +0 -46
  23. package/src/data/filter-dialect.ts +0 -737
  24. package/src/data/paginate.ts +0 -334
  25. package/src/data/query_builder.ts +0 -176
  26. package/src/data/resolveDataSource.ts +0 -135
  27. package/src/data/sort-dialect.ts +0 -237
  28. package/src/index.ts +0 -11
  29. package/src/table-classification.ts +0 -109
  30. package/src/types/json-logic-js.d.ts +0 -8
  31. package/src/util/auth-default-policies.ts +0 -215
  32. package/src/util/builders.ts +0 -82
  33. package/src/util/callbacks.ts +0 -122
  34. package/src/util/collections.ts +0 -117
  35. package/src/util/common.ts +0 -2
  36. package/src/util/conditions.ts +0 -168
  37. package/src/util/email.ts +0 -32
  38. package/src/util/entities.ts +0 -282
  39. package/src/util/enums.ts +0 -26
  40. package/src/util/identity.ts +0 -202
  41. package/src/util/index.ts +0 -21
  42. package/src/util/internal-tables.test.ts +0 -188
  43. package/src/util/internal-tables.ts +0 -197
  44. package/src/util/junction-policies.ts +0 -355
  45. package/src/util/paths.ts +0 -27
  46. package/src/util/permissions.test.ts +0 -866
  47. package/src/util/permissions.ts +0 -206
  48. package/src/util/pg-column-to-property.ts +0 -377
  49. package/src/util/policy/evaluatePolicy.ts +0 -194
  50. package/src/util/policy/index.ts +0 -4
  51. package/src/util/policy/policyToPostgres.ts +0 -263
  52. package/src/util/policy/securityRuleToConditions.ts +0 -67
  53. package/src/util/policy/sqlToPolicy.ts +0 -422
  54. package/src/util/relations.ts +0 -236
  55. package/src/util/resolutions.ts +0 -534
  56. package/src/util/resolve-relation.ts +0 -243
  57. package/src/util/storage.ts +0 -177
  58. package/src/util/string-column-length.ts +0 -31
@@ -1,10 +1,49 @@
1
- import { FirebaseCollectionConfig, FirebaseProperties, InferEntityType, MongoDBCollectionConfig, MongoProperties, PostgresCollectionConfig, PostgresProperties, User } from "@rebasepro/types";
1
+ import { FirebaseCollectionConfig, FirebaseProperties, FirebaseProperty, InferEntityType, MongoDBCollectionConfig, MongoProperties, MongoProperty, PostgresCollectionConfig, PostgresProperties, PostgresProperty, Properties, Property, StrictProperties, User, type ResourceRef } from "@rebasepro/types";
2
+ /** The engines a collection can declare. `postgres` when it says nothing. */
3
+ type CollectionEngine = "postgres" | "firestore" | "mongodb";
2
4
  /**
3
- * Define a PostgreSQL-backed collection with full type inference.
5
+ * The concrete collection type an `engine` selects.
6
+ *
7
+ * This builder used to be three overloads — one per engine — and overload
8
+ * resolution is what made its errors unreadable. When no overload matches,
9
+ * TypeScript emits **one** diagnostic at the call site listing each overload's
10
+ * *first* failure, so a misspelled key on a Postgres collection came back as
11
+ * three paragraphs of `No overload matches this call. Overload 1 of 3 … Overload
12
+ * 3 of 3, '(collection: Omit<MongoDBCollectionConfig<…>>)'` — pointing at
13
+ * `defineCollection(` and blaming a database the project does not use.
14
+ *
15
+ * One signature, with the engine as a type parameter, reports the error at the
16
+ * key instead. Same fix as `@rebasepro/cms-types`, and deliberately the same
17
+ * shape: this is the builder a headless (`--headless`) scaffold, `rebase schema
18
+ * introspect` output and the example app's own collections use, so the two must
19
+ * not diverge.
20
+ */
21
+ type CollectionConfigForEngine<E, P, USER extends User> = E extends "firestore" ? FirebaseCollectionConfig<EntityShapeOf<P>, USER> : E extends "mongodb" ? MongoDBCollectionConfig<EntityShapeOf<P>, USER> : PostgresCollectionConfig<EntityShapeOf<P>, USER>;
22
+ /**
23
+ * `InferEntityType`, tolerant of a property map that has an error in it.
24
+ *
25
+ * The key set has to survive a bad property, or one mistake hides every other
26
+ * check that reads it. See `KEYS` on the signature below.
27
+ */
28
+ type EntityShapeOf<P> = InferEntityType<{
29
+ [K in keyof P]: P[K] extends Property ? P[K] : Property;
30
+ }>;
31
+ /** The property union an engine admits — the engine gate, as a type. */
32
+ type PropertyForEngine<E> = E extends "firestore" ? FirebaseProperty : E extends "mongodb" ? MongoProperty : PostgresProperty;
33
+ /** {@link PropertyForEngine} as a property map, for the `P` constraint. */
34
+ type PropertiesForEngine<E> = E extends "firestore" ? FirebaseProperties : E extends "mongodb" ? MongoProperties : PostgresProperties;
35
+ /**
36
+ * Define a collection with full type inference. Postgres unless `engine` says
37
+ * otherwise.
4
38
  *
5
39
  * The `const P` generic captures literal property types from your
6
- * `properties` object, which enables autocomplete on `display.title`,
7
- * `sort`, `propertiesOrder`, `fixedFilter`, and entity callbacks.
40
+ * `properties` object, so every key that names a property — a security rule's
41
+ * `ownerField`, a relation's `localKey`, an entity callback's `value` — is
42
+ * checked against the collection's own property names rather than `string`.
43
+ *
44
+ * This is the builder for a project with no admin panel. One with an admin
45
+ * panel wants `defineCollection` from `@rebasepro/cms-types`, which is the same
46
+ * function with the `admin` block type-checked.
8
47
  *
9
48
  * @example
10
49
  * ```ts
@@ -16,33 +55,39 @@ import { FirebaseCollectionConfig, FirebaseProperties, InferEntityType, MongoDBC
16
55
  * name: { name: "Name", type: "string", validation: { required: true } },
17
56
  * price: { name: "Price", type: "number" },
18
57
  * },
19
- * display: { title: "name" }, // ✅ autocomplete: "name" | "price"
20
- * sort: ["price", "asc"], // ✅ autocomplete on first element
58
+ * securityRules: [{ operation: "select", access: "public" }]
21
59
  * });
22
60
  * ```
23
61
  *
24
62
  * @group Builder
25
63
  */
26
- export declare function defineCollection<const P extends PostgresProperties, USER extends User = User>(collection: Omit<PostgresCollectionConfig<InferEntityType<P>, USER>, "properties"> & {
27
- properties: P;
28
- }): PostgresCollectionConfig<InferEntityType<P>, USER> & {
29
- properties: P;
30
- };
64
+ export declare function defineCollection<const E extends CollectionEngine = "postgres",
31
65
  /**
32
- * Define a Firestore-backed collection with full type inference.
33
- * @group Builder
66
+ * The properties, **constrained**. This is what checks them, and what
67
+ * supplies the contextual type inside them: without a constraint the
68
+ * parameter of an inline `callbacks: { beforeSave: ({ value }) => … }` has
69
+ * nothing to be typed from, and TypeScript reports an implicit `any` on a
70
+ * callback the author wrote correctly.
34
71
  */
35
- export declare function defineCollection<const P extends FirebaseProperties, USER extends User = User>(collection: Omit<FirebaseCollectionConfig<InferEntityType<P>, USER>, "properties"> & {
36
- properties: P;
37
- }): FirebaseCollectionConfig<InferEntityType<P>, USER> & {
38
- properties: P;
39
- };
72
+ const P extends PropertiesForEngine<E> & Properties = PropertiesForEngine<E> & Properties,
40
73
  /**
41
- * Define a MongoDB-backed collection with full type inference.
42
- * @group Builder
74
+ * The properties again, **unconstrained**, and this is why there are two.
75
+ *
76
+ * A constraint TypeScript cannot satisfy is one it silently falls back
77
+ * from: one property with a bad `defaultValue` made `P` become
78
+ * `PostgresProperties`, the entity shape become `Record<string, unknown>`,
79
+ * and every key that is checked against the property names — `display.title`,
80
+ * `propertiesOrder`, `sort` — widen to `string` and stop being checked.
81
+ *
82
+ * `KEYS` has no constraint to fall back from, so `keyof KEYS` survives a bad
83
+ * property and the rest of the collection is still checked against the real
84
+ * key set.
43
85
  */
44
- export declare function defineCollection<const P extends MongoProperties, USER extends User = User>(collection: Omit<MongoDBCollectionConfig<InferEntityType<P>, USER>, "properties"> & {
45
- properties: P;
46
- }): MongoDBCollectionConfig<InferEntityType<P>, USER> & {
47
- properties: P;
86
+ const KEYS = Properties, USER extends User = User>(collection: Omit<CollectionConfigForEngine<E, KEYS, USER>, "properties" | "engine" | "dataSource"> & {
87
+ engine?: E;
88
+ properties: StrictProperties<P, PropertyForEngine<E>> & KEYS;
89
+ dataSource?: ResourceRef;
90
+ }): CollectionConfigForEngine<E, KEYS, USER> & {
91
+ properties: KEYS;
48
92
  };
93
+ export {};
@@ -0,0 +1,77 @@
1
+ import { RebaseApiError } from "@rebasepro/types";
2
+ /**
3
+ * The code a write carries when a collection callback rejected it and did not
4
+ * say how. Distinct from `INVALID_INPUT`, which the framework's own validation
5
+ * raises: this one means *your* rule refused, so the message is the author's.
6
+ *
7
+ * `details.stage` names which callback refused — `beforeSave`, `beforeDelete`,
8
+ * `afterSave` or `afterDelete`. An `after*` hook runs inside the write's
9
+ * transaction, so a throw there rolls the row back too; the caller is told the
10
+ * write did not happen and which hook decided that.
11
+ */
12
+ export declare const CALLBACK_REJECTED = "CALLBACK_REJECTED";
13
+ /**
14
+ * Turn whatever a user callback threw into something the API layer can answer
15
+ * with.
16
+ *
17
+ * ### Why a plain `throw` has to mean 400
18
+ *
19
+ * Both `docs/collections/callbacks.md` ("Throw an error to **block the save**")
20
+ * and `docs/backend/hooks.md` ("the operation is rejected with an HTTP 400
21
+ * error response") promised this, and neither delivered it: an `Error` thrown
22
+ * from `beforeSave` reached the client as
23
+ *
24
+ * 500 {"error":{"message":"Internal Server Error","code":"INTERNAL_ERROR"}}
25
+ *
26
+ * with the author's message visible only in the server log, because the error
27
+ * normalizer masks 5xx bodies — correctly, since a 500 is by definition
28
+ * something the caller must not be told about.
29
+ *
30
+ * But a callback is not the server failing. It is the application speaking, in
31
+ * code its author wrote, about a request its author judged invalid. The
32
+ * conservative reading — "an unrecognised throw might be a real bug, so 500" —
33
+ * costs every validation rule its message and makes the documented example
34
+ * wrong. A rule that wants a 500 can still raise one explicitly.
35
+ *
36
+ * ### Why `after*` comes through here too
37
+ *
38
+ * `afterSave` and `afterDelete` run inside the write's transaction and are
39
+ * awaited, so a throw in one aborts the transaction: the row is not there when
40
+ * the request ends. Left unconverted, the caller saw a 500 for a write that a
41
+ * rule deliberately undid, and had no way to tell that from a database outage.
42
+ * Converted, it is the same 400 `CALLBACK_REJECTED` a `before*` hook produces,
43
+ * with `stage` naming the hook that refused.
44
+ *
45
+ * ### What passes through untouched
46
+ *
47
+ * Anything that already carries a status: `RebaseApiError` from
48
+ * `@rebasepro/types` (the browser-safe class a `config/collections/*.ts` file
49
+ * can import — the collection file is bundled into the admin SPA, so it may not
50
+ * import the server package), and the server's own `ApiError`, recognised
51
+ * structurally rather than by `instanceof` because a monorepo can resolve two
52
+ * copies of a package and `instanceof` is false across them.
53
+ *
54
+ * @param error What the callback threw.
55
+ * @param stage The callback name, for the log line.
56
+ * @param path The collection path, for the log line.
57
+ */
58
+ export declare function toCallbackError(error: unknown, stage: string, path: string): unknown;
59
+ /**
60
+ * The refusal a callback expresses by returning `false` rather than throwing.
61
+ *
62
+ * `beforeDelete` is typed `boolean | void` and documented as "return false or
63
+ * throw to block deletion". Returning `false` did stop the delete — and then the
64
+ * route answered `204 No Content`, which says the row is gone. The admin panel
65
+ * removed it from the list, a client that trusted the status dropped it from its
66
+ * cache, and the next reload brought it back. A veto that reports success is
67
+ * worse than no veto.
68
+ *
69
+ * 403, not the 400 a throw produces: a throw carries the author's message and
70
+ * reads as "this input is wrong", while `false` is a flat refusal with no
71
+ * explanation — the server understood the request and will not perform it. The
72
+ * code is the same either way, so a client can handle both in one branch.
73
+ *
74
+ * @param stage The callback name, for `details.stage`.
75
+ * @param path The collection path, for `details.path`.
76
+ */
77
+ export declare function callbackRefusal(stage: string, path: string): RebaseApiError;
@@ -0,0 +1 @@
1
+ export {};
@@ -11,6 +11,7 @@ export * from "./permissions.js";
11
11
  export * from "./builders.js";
12
12
  export * from "./storage.js";
13
13
  export * from "./callbacks.js";
14
+ export * from "./callback-errors.js";
14
15
  export * from "./relations.js";
15
16
  export * from "./resolve-relation.js";
16
17
  export * from "./auth-default-policies.js";
@@ -24,6 +24,12 @@ export interface PolicyEvalContext {
24
24
  uid?: string | null;
25
25
  /** The current user's application roles. */
26
26
  roles?: string[];
27
+ /**
28
+ * Whether this session is a GUEST — anonymous sign-in rather than an
29
+ * account. Optional, and absent means "not a guest", so a caller that does
30
+ * not know keeps the behaviour it had.
31
+ */
32
+ isAnonymous?: boolean;
27
33
  /** The row being evaluated, or null when no specific row is available. */
28
34
  entity: Entity | null;
29
35
  }
@@ -27,6 +27,34 @@ export declare function isJunctionBackedRelation(relation: ResolvedRelation): bo
27
27
  * which is worth hearing about.
28
28
  */
29
29
  export declare function resolveCollectionRelations(collection: CollectionConfig): Record<string, ResolvedRelation>;
30
+ /**
31
+ * The `type: "relation"` property that declares a link, or `undefined` for one
32
+ * that only exists in the collection's `relations` array.
33
+ *
34
+ * Both declaration sites end up in {@link resolveCollectionRelations}, and only
35
+ * one of them has a property to carry field-level facts — `name`, `admin`, and
36
+ * the one this exists for, `validation.required`.
37
+ */
38
+ export declare function relationDeclaringProperty(collection: CollectionConfig, relation: ResolvedRelation): RelationProperty | undefined;
39
+ /**
40
+ * Must every row of this collection point at a target through this link?
41
+ *
42
+ * Read from the declaring property's `validation.required` — the same key every
43
+ * other field uses, and the only place it lives.
44
+ *
45
+ * `RelationBase` carried its own `validation.required` until 0.18, which made
46
+ * this two questions rather than one. They were answered by different readers:
47
+ * the Postgres DDL generator asked the property (so the foreign-key column was
48
+ * `NOT NULL`) and the SDK type generator asked the relation (so the generated
49
+ * `Insert` type made the field optional). A `create()` that left the relation
50
+ * out therefore typechecked and then failed at the database with a not-null
51
+ * violation, and the two `required`s had to be written twice, identically, for
52
+ * the pair to agree.
53
+ *
54
+ * A relation with no declaring property — an entry in `relations` nothing
55
+ * points at — is not required. There is no field to fill in.
56
+ */
57
+ export declare function isRelationRequired(collection: CollectionConfig, relation: ResolvedRelation): boolean;
30
58
  /**
31
59
  * The path of the collection a relation property points at, derived from the
32
60
  * property alone.
@@ -44,6 +72,19 @@ export declare function resolveCollectionRelations(collection: CollectionConfig)
44
72
  * is indistinguishable from a value of the wrong type.
45
73
  */
46
74
  export declare function getRelationTargetPath(property: RelationProperty): string | undefined;
75
+ /**
76
+ * The table a collection reads and writes.
77
+ *
78
+ * `table` when it is set, otherwise `toSnakeCase(slug)` — which is what made it
79
+ * safe to drop `table` from the required fields on the config type: the runtime
80
+ * had always derived it, and the type was demanding a value it did not need.
81
+ *
82
+ * The `||` chain is load-bearing. `toSnakeCase(undefined)` returns `""`, not
83
+ * `undefined`, so the previous `??` chain short-circuited on the empty string
84
+ * and the name fallback could never run — a safety net that read like one and
85
+ * caught nothing. It was unreachable while `slug` was required; it stops being
86
+ * unreachable the moment anything constructs a config without one.
87
+ */
47
88
  export declare function getTableName(collection: CollectionConfig): string;
48
89
  /**
49
90
  * The variable name a generated table is bound to.
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,47 +1,51 @@
1
1
  {
2
2
  "name": "@rebasepro/common",
3
- "type": "module",
4
- "version": "0.17.3",
3
+ "version": "0.18.1",
5
4
  "description": "Rebase shared core — collection registry, data driver adapter and fluent query builder. No React dependency.",
6
- "funding": {
7
- "url": "https://github.com/sponsors/rebaseco"
5
+ "keywords": [
6
+ "rebase",
7
+ "cms",
8
+ "admin",
9
+ "typescript",
10
+ "headless",
11
+ "headless cms",
12
+ "content manager"
13
+ ],
14
+ "homepage": "https://rebase.pro",
15
+ "bugs": {
16
+ "url": "https://github.com/rebasepro/rebase/issues"
8
17
  },
9
- "author": "Rebase",
10
- "license": "MIT",
11
18
  "repository": {
12
19
  "type": "git",
13
20
  "url": "https://github.com/rebasepro/rebase.git",
14
21
  "directory": "packages/common"
15
22
  },
23
+ "funding": {
24
+ "url": "https://github.com/sponsors/rebasepro"
25
+ },
26
+ "license": "MIT",
27
+ "author": "Rebase",
28
+ "type": "module",
16
29
  "main": "./dist/index.es.js",
17
30
  "module": "./dist/index.es.js",
18
31
  "types": "./dist/index.d.ts",
19
- "source": "src/index.ts",
20
32
  "engines": {
21
- "node": ">=20"
33
+ "node": ">=22.22.0"
22
34
  },
23
- "keywords": [
24
- "rebase",
25
- "cms",
26
- "admin",
27
- "typescript",
28
- "headless",
29
- "headless cms",
30
- "content manager"
31
- ],
32
35
  "exports": {
33
36
  ".": {
34
37
  "types": "./dist/index.d.ts",
35
38
  "development": "./dist/index.es.js",
36
- "import": "./dist/index.es.js"
39
+ "import": "./dist/index.es.js",
40
+ "default": "./dist/index.es.js"
37
41
  },
38
42
  "./package.json": "./package.json"
39
43
  },
40
44
  "dependencies": {
41
45
  "fast-equals": "6.0.2",
42
46
  "json-logic-js": "^2.0.5",
43
- "@rebasepro/types": "0.17.3",
44
- "@rebasepro/utils": "0.17.3"
47
+ "@rebasepro/types": "0.18.1",
48
+ "@rebasepro/utils": "0.18.1"
45
49
  },
46
50
  "devDependencies": {
47
51
  "@jest/globals": "^30.4.1",
@@ -61,8 +65,7 @@
61
65
  "vite": "^8.1.5"
62
66
  },
63
67
  "files": [
64
- "dist",
65
- "src"
68
+ "dist"
66
69
  ],
67
70
  "gitHead": "d935eefa5aa8d1009a2398cfac2c1e4ee9aeb6b6",
68
71
  "publishConfig": {
@@ -101,6 +104,7 @@
101
104
  "build": "vite build && tsc --emitDeclarationOnly -p tsconfig.prod.json && node ../../tooling/scripts/add-dts-extensions.mjs dist && node ../../tooling/scripts/assert-build-output.mjs",
102
105
  "test:lint": "eslint \"src/**\" --quiet",
103
106
  "test": "jest --passWithNoTests",
107
+ "test:watch": "jest --watch",
104
108
  "clean": "rm -rf dist && find ./src -name '*.js' -type f | xargs rm -f",
105
109
  "generateIcons": "ts-node --esm src/icons/generateIcons.ts"
106
110
  }