convex-helpers 0.1.0 → 0.1.2

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.
package/README.md ADDED
@@ -0,0 +1,19 @@
1
+ # convex-helpers
2
+
3
+ A collection of useful code to complement the official packages.
4
+
5
+ ## Row-level security
6
+
7
+ See the [Stack post on row-level security](https://stack.convex.dev/row-level-security)
8
+
9
+ Use the [RowLevelSecurity](./server/rowLevelSecurity.ts) helper to define
10
+ `withQueryRLS` and `withMutationRLS` wrappers to add row-level checks for a
11
+ server-side function. Any access to `db` inside functions wrapped with these
12
+ will check your access rules on read/insert/modify per-document.
13
+
14
+ ## Relationship helpers
15
+
16
+ See the [Stack post on relationship helpers](https://stack.convex.dev/functional-relationships-helpers)
17
+ and the [relationship schema structures post](https://stack.convex.dev/relationship-structures-let-s-talk-about-schemas).
18
+
19
+ Use the helpers in [relationships.ts](./server/relationships.ts) to traverse database relationships in queries more cleanly.
package/index.ts ADDED
@@ -0,0 +1,26 @@
1
+ /**
2
+ * asyncMap returns the results of applying an async function over an list.
3
+ *
4
+ * @param list - Iterable object of items, e.g. an Array, Set, Object.keys
5
+ * @param asyncTransform
6
+ * @returns
7
+ */
8
+ export async function asyncMap<FromType, ToType>(
9
+ list: Iterable<FromType>,
10
+ asyncTransform: (item: FromType) => Promise<ToType>
11
+ ): Promise<ToType[]> {
12
+ const promises: Promise<ToType>[] = [];
13
+ for (const item of list) {
14
+ promises.push(asyncTransform(item));
15
+ }
16
+ return Promise.all(promises);
17
+ }
18
+
19
+ /**
20
+ * Filters out null elements from an array.
21
+ * @param list List of elements that might be null.
22
+ * @returns List of elements with nulls removed.
23
+ */
24
+ export function pruneNull<T>(list: (T | null)[]): T[] {
25
+ return list.filter((i) => i !== null) as T[];
26
+ }
package/package.json CHANGED
@@ -1,30 +1,15 @@
1
1
  {
2
2
  "name": "convex-helpers",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "A collection of useful code to complement the official convex package.",
5
- "exports": {
6
- "./server/": {
7
- "types": "./server/",
8
- "import": "./esm/server/",
9
- "require": "./cjs/server/"
10
- }
11
- },
12
- "files": [
13
- "esm",
14
- "cjs",
15
- "server"
16
- ],
5
+ "type": "module",
17
6
  "scripts": {
18
- "build": "npm run clean && npm run build:esm 2>/dev/null && npm run build:cjs 2>/dev/null",
19
- "clean": "rm -rf cjs esm",
20
- "build:esm": "esbuild **/*.ts --outdir=esm --outbase=. --loader:.ts=ts --platform=node --bundle=false",
21
- "build:cjs": "esbuild **/*.ts --outdir=cjs --outbase=. --loader:.ts=ts --platform=node --bundle=false --format=cjs && echo '{\"type\": \"commonjs\"}' > cjs/package.json",
22
- "watch": "chokidar '**/*.tsx' '**/*.ts' 'package.json' -c 'npm run build:esm'",
23
7
  "test": "echo \"Error: no test specified\" && exit 1"
24
8
  },
25
9
  "repository": {
26
10
  "type": "git",
27
- "url": "git+https://github.com/get-convex/convex-helpers.git"
11
+ "url": "git+https://github.com/get-convex/convex-helpers.git",
12
+ "directory": "packages/convex-helpers"
28
13
  },
29
14
  "keywords": [
30
15
  "convex",
@@ -36,13 +21,11 @@
36
21
  "bugs": {
37
22
  "url": "https://github.com/get-convex/convex-helpers/issues"
38
23
  },
39
- "homepage": "https://github.com/get-convex/convex-helpers#readme",
40
- "dependencies": {},
24
+ "homepage": "https://github.com/get-convex/convex-helpers/tree/main/packages/convex-helpers/README.md",
41
25
  "peerDependencies": {
42
- "convex": "^1.6.2"
26
+ "convex": "^1.6.3"
43
27
  },
44
28
  "devDependencies": {
45
- "chokidar-cli": "^3.0.0",
46
29
  "typescript": "^5.2.2"
47
30
  }
48
31
  }
File without changes
@@ -0,0 +1,102 @@
1
+ import { ObjectType, PropertyValidators } from "convex/values";
2
+ import {
3
+ ArgsArray,
4
+ UnvalidatedFunction,
5
+ ValidatedFunction,
6
+ } from "convex/server";
7
+
8
+ export type MergeArgs<
9
+ Args extends ArgsArray,
10
+ Other extends { [k: string]: any }
11
+ > = Args extends [] ? [Other] : [Args[0] & Other];
12
+
13
+ export type MergeArgsForRegistered<
14
+ Args extends ArgsArray,
15
+ Other extends { [k: string]: any }
16
+ > = MergeArgs<Args, Other>[0];
17
+
18
+ export function splitArgs<
19
+ ConsumedArgsValidator extends PropertyValidators,
20
+ Args extends Record<string, any>
21
+ >(
22
+ consumedArgsValidator: ConsumedArgsValidator,
23
+ args: Args & ObjectType<ConsumedArgsValidator>
24
+ ): { rest: Args; consumed: ObjectType<ConsumedArgsValidator> } {
25
+ const rest: Record<string, any> = {};
26
+ const consumed: Record<string, any> = {};
27
+ for (const arg in args) {
28
+ if (arg in consumedArgsValidator) {
29
+ consumed[arg] = args[arg];
30
+ } else {
31
+ rest[arg] = args[arg];
32
+ }
33
+ }
34
+
35
+ return {
36
+ rest,
37
+ consumed,
38
+ } as any;
39
+ }
40
+
41
+ export const generateMiddleware = <
42
+ RequiredCtx extends Record<string, any>,
43
+ TransformedCtx extends Record<string, any>,
44
+ ConsumedArgsValidator extends PropertyValidators
45
+ >(
46
+ consumedArgsValidator: ConsumedArgsValidator,
47
+ transformContext: (
48
+ ctx: RequiredCtx,
49
+ args: ObjectType<ConsumedArgsValidator>
50
+ ) => Promise<TransformedCtx>
51
+ ) => {
52
+ // Have two overloads -- one for validated functions and one for unvalidated functions
53
+ function withFoo<
54
+ ExistingArgsValidator extends PropertyValidators,
55
+ Output,
56
+ Ctx
57
+ >(
58
+ fn: ValidatedFunction<
59
+ Ctx & TransformedCtx,
60
+ ExistingArgsValidator,
61
+ Promise<Output>
62
+ >
63
+ ): ValidatedFunction<
64
+ Ctx & RequiredCtx,
65
+ ConsumedArgsValidator & ExistingArgsValidator,
66
+ Promise<Output>
67
+ >;
68
+
69
+ function withFoo<ExistingArgs extends ArgsArray, Output, Ctx>(
70
+ fn: UnvalidatedFunction<Ctx & TransformedCtx, ExistingArgs, Promise<Output>>
71
+ ): UnvalidatedFunction<
72
+ Ctx & RequiredCtx,
73
+ MergeArgs<ExistingArgs, ObjectType<ConsumedArgsValidator>>,
74
+ Promise<Output>
75
+ >;
76
+ function withFoo(fn: any): any {
77
+ if (fn.args) {
78
+ const handler = fn.handler;
79
+ return {
80
+ args: {
81
+ ...fn.args,
82
+ ...consumedArgsValidator,
83
+ },
84
+ handler: async (ctx: any, allArgs: any) => {
85
+ const { rest, consumed } = splitArgs(consumedArgsValidator, allArgs);
86
+ const transformedCtx = await transformContext(ctx, consumed);
87
+ return await handler(transformedCtx, rest);
88
+ },
89
+ };
90
+ }
91
+ const handler = fn.handler ?? fn;
92
+ return {
93
+ handler: async (ctx: any, allArgs: any) => {
94
+ const { rest, consumed } = splitArgs(consumedArgsValidator, allArgs);
95
+ const transformedCtx = await transformContext(ctx, consumed);
96
+ return await handler(transformedCtx, rest);
97
+ },
98
+ };
99
+ }
100
+
101
+ return withFoo;
102
+ };
@@ -6,26 +6,10 @@ import {
6
6
  GenericDataModel,
7
7
  GenericDatabaseReader,
8
8
  DocumentByName,
9
+ SystemTableNames,
9
10
  } from "convex/server";
10
11
  import { ConvexError, GenericId } from "convex/values";
11
-
12
- /**
13
- * asyncMap returns the results of applying an async function over an list.
14
- *
15
- * @param list - Iterable object of items, e.g. an Array, Set, Object.keys
16
- * @param asyncTransform
17
- * @returns
18
- */
19
- export async function asyncMap<FromType, ToType>(
20
- list: Iterable<FromType>,
21
- asyncTransform: (item: FromType) => Promise<ToType>
22
- ): Promise<ToType[]> {
23
- const promises: Promise<ToType>[] = [];
24
- for (const item of list) {
25
- promises.push(asyncTransform(item));
26
- }
27
- return Promise.all(promises);
28
- }
12
+ import { asyncMap, pruneNull } from "..";
29
13
 
30
14
  /**
31
15
  * getAll returns a list of Documents corresponding to the `Id`s passed in.
@@ -176,7 +160,7 @@ export async function getManyFrom<
176
160
  type IdFilePaths<
177
161
  DataModel extends GenericDataModel,
178
162
  InTableName extends TablesWithLookups<DataModel>,
179
- TableName extends TableNamesInDataModel<DataModel>
163
+ TableName extends TableNamesInDataModel<DataModel> | SystemTableNames
180
164
  > = {
181
165
  [FieldName in DataModel[InTableName]["fieldPaths"]]: FieldTypeFromFieldPath<
182
166
  DocumentByName<DataModel, InTableName>,
@@ -198,9 +182,13 @@ type LookupAndIdFilePaths<
198
182
  [FieldPath in IdFilePaths<
199
183
  DataModel,
200
184
  TableName,
201
- TableNamesInDataModel<DataModel>
185
+ TableNamesInDataModel<DataModel> | SystemTableNames
202
186
  >]: LookupFieldPaths<DataModel, TableName> extends FieldPath ? never : true;
203
- }[IdFilePaths<DataModel, TableName, TableNamesInDataModel<DataModel>>];
187
+ }[IdFilePaths<
188
+ DataModel,
189
+ TableName,
190
+ TableNamesInDataModel<DataModel> | SystemTableNames
191
+ >];
204
192
 
205
193
  // The table names that match LookupAndIdFields.
206
194
  // These are the possible "join" or "edge" or "relationship" tables.
@@ -233,7 +221,7 @@ export async function getManyVia<
233
221
  ToField extends IdFilePaths<
234
222
  DataModel,
235
223
  JoinTableName,
236
- TableNamesInDataModel<DataModel>
224
+ TableNamesInDataModel<DataModel> | SystemTableNames
237
225
  >,
238
226
  FromField extends Exclude<
239
227
  LookupFieldPaths<DataModel, JoinTableName>,
@@ -256,17 +244,16 @@ export async function getManyVia<
256
244
  >
257
245
  ): Promise<DocumentByName<DataModel, TargetTableName>[]> {
258
246
  return pruneNull(
259
- await asyncMap(await getManyFrom(db, table, fromField, value), (link) =>
260
- db.get((link as any)[toField])
247
+ await asyncMap(
248
+ await getManyFrom(db, table, fromField, value),
249
+ async (link: DocumentByName<DataModel, JoinTableName>) => {
250
+ const id = link[toField] as GenericId<TargetTableName>;
251
+ try {
252
+ return await db.get(id);
253
+ } catch {
254
+ return await db.system.get(id as GenericId<SystemTableNames>);
255
+ }
256
+ }
261
257
  )
262
258
  );
263
259
  }
264
-
265
- /**
266
- * Filters out null elements from an array.
267
- * @param list List of elements that might be null.
268
- * @returns List of elements with nulls removed.
269
- */
270
- export function pruneNull<T>(list: (T | null)[]): T[] {
271
- return list.filter((i) => i !== null) as T[];
272
- }
package/tsconfig.json ADDED
@@ -0,0 +1,112 @@
1
+ {
2
+ "compilerOptions": {
3
+ /* Visit https://aka.ms/tsconfig to read more about this file */
4
+
5
+ /* Projects */
6
+ // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
7
+ "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
8
+ // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
9
+ // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
10
+ // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
11
+ // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
12
+
13
+ /* Language and Environment */
14
+ "target": "ESNext", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
15
+ // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
16
+ // "jsx": "preserve", /* Specify what JSX code is generated. */
17
+ // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
18
+ // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
19
+ // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
20
+ // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
21
+ // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
22
+ // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
23
+ // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
24
+ // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
25
+ // "moduleDetection": "force", /* Control what method is used to detect module-format JS files. */
26
+
27
+ /* Modules */
28
+ "module": "ESNext", /* Specify what module code is generated. */
29
+ "rootDir": "./", /* Specify the root folder within your source files. */
30
+ "moduleResolution": "Node", /* Specify how TypeScript looks up a file from a given module specifier. */
31
+ // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
32
+ // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
33
+ // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
34
+ // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
35
+ // "types": [], /* Specify type package names to be included without being referenced in a source file. */
36
+ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
37
+ // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
38
+ // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
39
+ // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
40
+ // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
41
+ // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
42
+ // "resolveJsonModule": true, /* Enable importing .json files. */
43
+ // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
44
+ // "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
45
+
46
+ /* JavaScript Support */
47
+ // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
48
+ // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
49
+ // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
50
+
51
+ /* Emit */
52
+ // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
53
+ // "declarationMap": true, /* Create sourcemaps for d.ts files. */
54
+ // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
55
+ // "sourceMap": true, /* Create source map files for emitted JavaScript files. */
56
+ // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
57
+ // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
58
+ // "outDir": "./", /* Specify an output folder for all emitted files. */
59
+ // "removeComments": true, /* Disable emitting comments. */
60
+ "noEmit": true, /* Disable emitting files from a compilation. */
61
+ // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
62
+ // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
63
+ // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
64
+ // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
65
+ // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
66
+ // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
67
+ // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
68
+ // "newLine": "crlf", /* Set the newline character for emitting files. */
69
+ // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
70
+ // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
71
+ // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
72
+ // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
73
+ // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
74
+ // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
75
+
76
+ /* Interop Constraints */
77
+ // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
78
+ // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
79
+ // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
80
+ // "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
81
+ // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
82
+ "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
83
+
84
+ /* Type Checking */
85
+ "strict": true, /* Enable all strict type-checking options. */
86
+ // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
87
+ // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
88
+ // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
89
+ // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
90
+ // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
91
+ // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
92
+ // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
93
+ // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
94
+ "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
95
+ "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
96
+ // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
97
+ // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
98
+ "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
99
+ // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
100
+ // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
101
+ // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
102
+ // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
103
+ // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
104
+
105
+ /* Completeness */
106
+ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
107
+ "skipLibCheck": true /* Skip type checking all .d.ts files. */
108
+ },
109
+ "include": [
110
+ "."
111
+ ]
112
+ }
package/cjs/package.json DELETED
@@ -1 +0,0 @@
1
- {"type": "commonjs"}
@@ -1,81 +0,0 @@
1
- "use strict";
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __export = (target, all) => {
7
- for (var name in all)
8
- __defProp(target, name, { get: all[name], enumerable: true });
9
- };
10
- var __copyProps = (to, from, except, desc) => {
11
- if (from && typeof from === "object" || typeof from === "function") {
12
- for (let key of __getOwnPropNames(from))
13
- if (!__hasOwnProp.call(to, key) && key !== except)
14
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
- }
16
- return to;
17
- };
18
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
- var relationships_exports = {};
20
- __export(relationships_exports, {
21
- asyncMap: () => asyncMap,
22
- getAll: () => getAll,
23
- getAllWithNulls: () => getAllWithNulls,
24
- getManyFrom: () => getManyFrom,
25
- getManyVia: () => getManyVia,
26
- getOneFrom: () => getOneFrom,
27
- getOneOrNullFrom: () => getOneOrNullFrom,
28
- pruneNull: () => pruneNull
29
- });
30
- module.exports = __toCommonJS(relationships_exports);
31
- var import_values = require("convex/values");
32
- async function asyncMap(list, asyncTransform) {
33
- const promises = [];
34
- for (const item of list) {
35
- promises.push(asyncTransform(item));
36
- }
37
- return Promise.all(promises);
38
- }
39
- async function getAll(db, ids) {
40
- return pruneNull(await asyncMap(ids, db.get));
41
- }
42
- async function getAllWithNulls(db, ids) {
43
- return asyncMap(ids, db.get);
44
- }
45
- async function getOneFrom(db, table, field, value) {
46
- const ret = await db.query(table).withIndex("by_" + field, (q) => q.eq(field, value)).unique();
47
- if (ret === null) {
48
- throw new import_values.ConvexError(
49
- `Can't find a document in ${table} with field ${field} equal to ${value}`
50
- );
51
- }
52
- return ret;
53
- }
54
- async function getOneOrNullFrom(db, table, field, value) {
55
- return db.query(table).withIndex("by_" + field, (q) => q.eq(field, value)).unique();
56
- }
57
- async function getManyFrom(db, table, field, value) {
58
- return db.query(table).withIndex("by_" + field, (q) => q.eq(field, value)).collect();
59
- }
60
- async function getManyVia(db, table, toField, fromField, value) {
61
- return pruneNull(
62
- await asyncMap(
63
- await getManyFrom(db, table, fromField, value),
64
- (link) => db.get(link[toField])
65
- )
66
- );
67
- }
68
- function pruneNull(list) {
69
- return list.filter((i) => i !== null);
70
- }
71
- // Annotate the CommonJS export names for ESM import in node:
72
- 0 && (module.exports = {
73
- asyncMap,
74
- getAll,
75
- getAllWithNulls,
76
- getManyFrom,
77
- getManyVia,
78
- getOneFrom,
79
- getOneOrNullFrom,
80
- pruneNull
81
- });
@@ -1,273 +0,0 @@
1
- "use strict";
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __export = (target, all) => {
7
- for (var name in all)
8
- __defProp(target, name, { get: all[name], enumerable: true });
9
- };
10
- var __copyProps = (to, from, except, desc) => {
11
- if (from && typeof from === "object" || typeof from === "function") {
12
- for (let key of __getOwnPropNames(from))
13
- if (!__hasOwnProp.call(to, key) && key !== except)
14
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
- }
16
- return to;
17
- };
18
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
- var rowLevelSecurity_exports = {};
20
- __export(rowLevelSecurity_exports, {
21
- RowLevelSecurity: () => RowLevelSecurity
22
- });
23
- module.exports = __toCommonJS(rowLevelSecurity_exports);
24
- const RowLevelSecurity = (rules) => {
25
- const withMutationRLS = (f) => {
26
- return (ctx, ...args) => {
27
- const wrappedDb = new WrapWriter(ctx, ctx.db, rules);
28
- return f({ ...ctx, db: wrappedDb }, ...args);
29
- };
30
- };
31
- const withQueryRLS = (f) => {
32
- return (ctx, ...args) => {
33
- const wrappedDb = new WrapReader(ctx, ctx.db, rules);
34
- return f({ ...ctx, db: wrappedDb }, ...args);
35
- };
36
- };
37
- return {
38
- withMutationRLS,
39
- withQueryRLS
40
- };
41
- };
42
- async function asyncFilter(arr, predicate) {
43
- const results = await Promise.all(arr.map(predicate));
44
- return arr.filter((_v, index) => results[index]);
45
- }
46
- class WrapQuery {
47
- q;
48
- p;
49
- iterator;
50
- constructor(q, p) {
51
- this.q = q;
52
- this.p = p;
53
- }
54
- filter(predicate) {
55
- return new WrapQuery(this.q.filter(predicate), this.p);
56
- }
57
- order(order) {
58
- return new WrapQuery(this.q.order(order), this.p);
59
- }
60
- async paginate(paginationOpts) {
61
- const result = await this.q.paginate(paginationOpts);
62
- result.page = await asyncFilter(result.page, this.p);
63
- return result;
64
- }
65
- async collect() {
66
- const results = await this.q.collect();
67
- return await asyncFilter(results, this.p);
68
- }
69
- async take(n) {
70
- const results = [];
71
- for await (const result of this) {
72
- results.push(result);
73
- if (results.length >= n) {
74
- break;
75
- }
76
- }
77
- return results;
78
- }
79
- async first() {
80
- for await (const result of this) {
81
- return result;
82
- }
83
- return null;
84
- }
85
- async unique() {
86
- let uniqueResult = null;
87
- for await (const result of this) {
88
- if (uniqueResult === null) {
89
- uniqueResult = result;
90
- } else {
91
- throw new Error("not unique");
92
- }
93
- }
94
- return uniqueResult;
95
- }
96
- [Symbol.asyncIterator]() {
97
- this.iterator = this.q[Symbol.asyncIterator]();
98
- return this;
99
- }
100
- async next() {
101
- for (; ; ) {
102
- const { value, done } = await this.iterator.next();
103
- if (value && await this.p(value)) {
104
- return { value, done };
105
- }
106
- if (done) {
107
- return { value: null, done: true };
108
- }
109
- }
110
- }
111
- return() {
112
- return this.iterator.return();
113
- }
114
- }
115
- class WrapQueryInitializer {
116
- q;
117
- p;
118
- constructor(q, p) {
119
- this.q = q;
120
- this.p = p;
121
- }
122
- fullTableScan() {
123
- return new WrapQuery(this.q.fullTableScan(), this.p);
124
- }
125
- withIndex(indexName, indexRange) {
126
- return new WrapQuery(this.q.withIndex(indexName, indexRange), this.p);
127
- }
128
- withSearchIndex(indexName, searchFilter) {
129
- return new WrapQuery(
130
- this.q.withSearchIndex(indexName, searchFilter),
131
- this.p
132
- );
133
- }
134
- filter(predicate) {
135
- return this.fullTableScan().filter(predicate);
136
- }
137
- order(order) {
138
- return this.fullTableScan().order(order);
139
- }
140
- async paginate(paginationOpts) {
141
- return this.fullTableScan().paginate(paginationOpts);
142
- }
143
- collect() {
144
- return this.fullTableScan().collect();
145
- }
146
- take(n) {
147
- return this.fullTableScan().take(n);
148
- }
149
- first() {
150
- return this.fullTableScan().first();
151
- }
152
- unique() {
153
- return this.fullTableScan().unique();
154
- }
155
- [Symbol.asyncIterator]() {
156
- return this.fullTableScan()[Symbol.asyncIterator]();
157
- }
158
- }
159
- class WrapReader {
160
- ctx;
161
- db;
162
- rules;
163
- constructor(ctx, db, rules) {
164
- this.ctx = ctx;
165
- this.db = db;
166
- this.rules = rules;
167
- }
168
- normalizeId(tableName, id) {
169
- return this.db.normalizeId(tableName, id);
170
- }
171
- tableName(id) {
172
- for (const tableName of Object.keys(this.rules)) {
173
- if (this.db.normalizeId(tableName, id)) {
174
- return tableName;
175
- }
176
- }
177
- return null;
178
- }
179
- async predicate(tableName, doc) {
180
- if (!this.rules[tableName]?.read) {
181
- return true;
182
- }
183
- return await this.rules[tableName].read(this.ctx, doc);
184
- }
185
- async get(id) {
186
- const doc = await this.db.get(id);
187
- if (doc) {
188
- const tableName = this.tableName(id);
189
- if (tableName && !await this.predicate(tableName, doc)) {
190
- return null;
191
- }
192
- return doc;
193
- }
194
- return null;
195
- }
196
- query(tableName) {
197
- return new WrapQueryInitializer(
198
- this.db.query(tableName),
199
- async (d) => await this.predicate(tableName, d)
200
- );
201
- }
202
- }
203
- class WrapWriter {
204
- ctx;
205
- db;
206
- reader;
207
- rules;
208
- async modifyPredicate(tableName, doc) {
209
- if (!this.rules[tableName]?.modify) {
210
- return true;
211
- }
212
- return await this.rules[tableName].modify(this.ctx, doc);
213
- }
214
- constructor(ctx, db, rules) {
215
- this.ctx = ctx;
216
- this.db = db;
217
- this.reader = new WrapReader(ctx, db, rules);
218
- this.rules = rules;
219
- }
220
- normalizeId(tableName, id) {
221
- return this.db.normalizeId(tableName, id);
222
- }
223
- async insert(table, value) {
224
- const rules = this.rules[table];
225
- if (rules?.insert && !await rules.insert(this.ctx, value)) {
226
- throw new Error("insert access not allowed");
227
- }
228
- return await this.db.insert(table, value);
229
- }
230
- tableName(id) {
231
- for (const tableName of Object.keys(this.rules)) {
232
- if (this.db.normalizeId(tableName, id)) {
233
- return tableName;
234
- }
235
- }
236
- return null;
237
- }
238
- async checkAuth(id) {
239
- const doc = await this.get(id);
240
- if (doc === null) {
241
- throw new Error("no read access or doc does not exist");
242
- }
243
- const tableName = this.tableName(id);
244
- if (tableName === null) {
245
- return;
246
- }
247
- if (!await this.modifyPredicate(tableName, doc)) {
248
- throw new Error("write access not allowed");
249
- }
250
- }
251
- async patch(id, value) {
252
- await this.checkAuth(id);
253
- return await this.db.patch(id, value);
254
- }
255
- async replace(id, value) {
256
- await this.checkAuth(id);
257
- return await this.db.replace(id, value);
258
- }
259
- async delete(id) {
260
- await this.checkAuth(id);
261
- return await this.db.delete(id);
262
- }
263
- get(id) {
264
- return this.reader.get(id);
265
- }
266
- query(tableName) {
267
- return this.reader.query(tableName);
268
- }
269
- }
270
- // Annotate the CommonJS export names for ESM import in node:
271
- 0 && (module.exports = {
272
- RowLevelSecurity
273
- });
@@ -1,41 +0,0 @@
1
- "use strict";
2
- import { ConvexError } from "convex/values";
3
- export async function asyncMap(list, asyncTransform) {
4
- const promises = [];
5
- for (const item of list) {
6
- promises.push(asyncTransform(item));
7
- }
8
- return Promise.all(promises);
9
- }
10
- export async function getAll(db, ids) {
11
- return pruneNull(await asyncMap(ids, db.get));
12
- }
13
- export async function getAllWithNulls(db, ids) {
14
- return asyncMap(ids, db.get);
15
- }
16
- export async function getOneFrom(db, table, field, value) {
17
- const ret = await db.query(table).withIndex("by_" + field, (q) => q.eq(field, value)).unique();
18
- if (ret === null) {
19
- throw new ConvexError(
20
- `Can't find a document in ${table} with field ${field} equal to ${value}`
21
- );
22
- }
23
- return ret;
24
- }
25
- export async function getOneOrNullFrom(db, table, field, value) {
26
- return db.query(table).withIndex("by_" + field, (q) => q.eq(field, value)).unique();
27
- }
28
- export async function getManyFrom(db, table, field, value) {
29
- return db.query(table).withIndex("by_" + field, (q) => q.eq(field, value)).collect();
30
- }
31
- export async function getManyVia(db, table, toField, fromField, value) {
32
- return pruneNull(
33
- await asyncMap(
34
- await getManyFrom(db, table, fromField, value),
35
- (link) => db.get(link[toField])
36
- )
37
- );
38
- }
39
- export function pruneNull(list) {
40
- return list.filter((i) => i !== null);
41
- }
@@ -1,251 +0,0 @@
1
- "use strict";
2
- export const RowLevelSecurity = (rules) => {
3
- const withMutationRLS = (f) => {
4
- return (ctx, ...args) => {
5
- const wrappedDb = new WrapWriter(ctx, ctx.db, rules);
6
- return f({ ...ctx, db: wrappedDb }, ...args);
7
- };
8
- };
9
- const withQueryRLS = (f) => {
10
- return (ctx, ...args) => {
11
- const wrappedDb = new WrapReader(ctx, ctx.db, rules);
12
- return f({ ...ctx, db: wrappedDb }, ...args);
13
- };
14
- };
15
- return {
16
- withMutationRLS,
17
- withQueryRLS
18
- };
19
- };
20
- async function asyncFilter(arr, predicate) {
21
- const results = await Promise.all(arr.map(predicate));
22
- return arr.filter((_v, index) => results[index]);
23
- }
24
- class WrapQuery {
25
- q;
26
- p;
27
- iterator;
28
- constructor(q, p) {
29
- this.q = q;
30
- this.p = p;
31
- }
32
- filter(predicate) {
33
- return new WrapQuery(this.q.filter(predicate), this.p);
34
- }
35
- order(order) {
36
- return new WrapQuery(this.q.order(order), this.p);
37
- }
38
- async paginate(paginationOpts) {
39
- const result = await this.q.paginate(paginationOpts);
40
- result.page = await asyncFilter(result.page, this.p);
41
- return result;
42
- }
43
- async collect() {
44
- const results = await this.q.collect();
45
- return await asyncFilter(results, this.p);
46
- }
47
- async take(n) {
48
- const results = [];
49
- for await (const result of this) {
50
- results.push(result);
51
- if (results.length >= n) {
52
- break;
53
- }
54
- }
55
- return results;
56
- }
57
- async first() {
58
- for await (const result of this) {
59
- return result;
60
- }
61
- return null;
62
- }
63
- async unique() {
64
- let uniqueResult = null;
65
- for await (const result of this) {
66
- if (uniqueResult === null) {
67
- uniqueResult = result;
68
- } else {
69
- throw new Error("not unique");
70
- }
71
- }
72
- return uniqueResult;
73
- }
74
- [Symbol.asyncIterator]() {
75
- this.iterator = this.q[Symbol.asyncIterator]();
76
- return this;
77
- }
78
- async next() {
79
- for (; ; ) {
80
- const { value, done } = await this.iterator.next();
81
- if (value && await this.p(value)) {
82
- return { value, done };
83
- }
84
- if (done) {
85
- return { value: null, done: true };
86
- }
87
- }
88
- }
89
- return() {
90
- return this.iterator.return();
91
- }
92
- }
93
- class WrapQueryInitializer {
94
- q;
95
- p;
96
- constructor(q, p) {
97
- this.q = q;
98
- this.p = p;
99
- }
100
- fullTableScan() {
101
- return new WrapQuery(this.q.fullTableScan(), this.p);
102
- }
103
- withIndex(indexName, indexRange) {
104
- return new WrapQuery(this.q.withIndex(indexName, indexRange), this.p);
105
- }
106
- withSearchIndex(indexName, searchFilter) {
107
- return new WrapQuery(
108
- this.q.withSearchIndex(indexName, searchFilter),
109
- this.p
110
- );
111
- }
112
- filter(predicate) {
113
- return this.fullTableScan().filter(predicate);
114
- }
115
- order(order) {
116
- return this.fullTableScan().order(order);
117
- }
118
- async paginate(paginationOpts) {
119
- return this.fullTableScan().paginate(paginationOpts);
120
- }
121
- collect() {
122
- return this.fullTableScan().collect();
123
- }
124
- take(n) {
125
- return this.fullTableScan().take(n);
126
- }
127
- first() {
128
- return this.fullTableScan().first();
129
- }
130
- unique() {
131
- return this.fullTableScan().unique();
132
- }
133
- [Symbol.asyncIterator]() {
134
- return this.fullTableScan()[Symbol.asyncIterator]();
135
- }
136
- }
137
- class WrapReader {
138
- ctx;
139
- db;
140
- system;
141
- rules;
142
- constructor(ctx, db, rules) {
143
- this.ctx = ctx;
144
- this.db = db;
145
- this.system = db.system;
146
- this.rules = rules;
147
- }
148
- normalizeId(tableName, id) {
149
- return this.db.normalizeId(tableName, id);
150
- }
151
- tableName(id) {
152
- for (const tableName of Object.keys(this.rules)) {
153
- if (this.db.normalizeId(tableName, id)) {
154
- return tableName;
155
- }
156
- }
157
- return null;
158
- }
159
- async predicate(tableName, doc) {
160
- if (!this.rules[tableName]?.read) {
161
- return true;
162
- }
163
- return await this.rules[tableName].read(this.ctx, doc);
164
- }
165
- async get(id) {
166
- const doc = await this.db.get(id);
167
- if (doc) {
168
- const tableName = this.tableName(id);
169
- if (tableName && !await this.predicate(tableName, doc)) {
170
- return null;
171
- }
172
- return doc;
173
- }
174
- return null;
175
- }
176
- query(tableName) {
177
- return new WrapQueryInitializer(
178
- this.db.query(tableName),
179
- async (d) => await this.predicate(tableName, d)
180
- );
181
- }
182
- }
183
- class WrapWriter {
184
- ctx;
185
- db;
186
- system;
187
- reader;
188
- rules;
189
- async modifyPredicate(tableName, doc) {
190
- if (!this.rules[tableName]?.modify) {
191
- return true;
192
- }
193
- return await this.rules[tableName].modify(this.ctx, doc);
194
- }
195
- constructor(ctx, db, rules) {
196
- this.ctx = ctx;
197
- this.db = db;
198
- this.system = db.system;
199
- this.reader = new WrapReader(ctx, db, rules);
200
- this.rules = rules;
201
- }
202
- normalizeId(tableName, id) {
203
- return this.db.normalizeId(tableName, id);
204
- }
205
- async insert(table, value) {
206
- const rules = this.rules[table];
207
- if (rules?.insert && !await rules.insert(this.ctx, value)) {
208
- throw new Error("insert access not allowed");
209
- }
210
- return await this.db.insert(table, value);
211
- }
212
- tableName(id) {
213
- for (const tableName of Object.keys(this.rules)) {
214
- if (this.db.normalizeId(tableName, id)) {
215
- return tableName;
216
- }
217
- }
218
- return null;
219
- }
220
- async checkAuth(id) {
221
- const doc = await this.get(id);
222
- if (doc === null) {
223
- throw new Error("no read access or doc does not exist");
224
- }
225
- const tableName = this.tableName(id);
226
- if (tableName === null) {
227
- return;
228
- }
229
- if (!await this.modifyPredicate(tableName, doc)) {
230
- throw new Error("write access not allowed");
231
- }
232
- }
233
- async patch(id, value) {
234
- await this.checkAuth(id);
235
- return await this.db.patch(id, value);
236
- }
237
- async replace(id, value) {
238
- await this.checkAuth(id);
239
- return await this.db.replace(id, value);
240
- }
241
- async delete(id) {
242
- await this.checkAuth(id);
243
- return await this.db.delete(id);
244
- }
245
- get(id) {
246
- return this.reader.get(id);
247
- }
248
- query(tableName) {
249
- return this.reader.query(tableName);
250
- }
251
- }