@teamkeel/functions-runtime 0.424.0 → 0.425.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.
package/dist/index.cjs CHANGED
@@ -608,175 +608,40 @@ __name(getDialect, "getDialect");
608
608
  var import_client_s3 = require("@aws-sdk/client-s3");
609
609
  var import_credential_providers = require("@aws-sdk/credential-providers");
610
610
  var import_s3_request_presigner = require("@aws-sdk/s3-request-presigner");
611
-
612
- // src/errors.js
613
- var import_json_rpc_2 = require("json-rpc-2.0");
614
- var RuntimeErrors = {
615
- // Catchall error type for unhandled execution errors during custom function
616
- UnknownError: -32001,
617
- // DatabaseError represents any error at pg level that isn't handled explicitly below
618
- DatabaseError: -32002,
619
- // No result returned from custom function by user
620
- NoResultError: -32003,
621
- // When trying to delete/update a non existent record in the db
622
- RecordNotFoundError: -32004,
623
- ForeignKeyConstraintError: -32005,
624
- NotNullConstraintError: -32006,
625
- UniqueConstraintError: -32007,
626
- PermissionError: -32008,
627
- BadRequestError: -32009
628
- };
629
- var PermissionError = class extends Error {
630
- static {
631
- __name(this, "PermissionError");
632
- }
633
- };
634
- var DatabaseError = class extends Error {
635
- static {
636
- __name(this, "DatabaseError");
637
- }
638
- constructor(error) {
639
- super(error.message);
640
- this.error = error;
641
- }
642
- };
643
- var NotFoundError = class extends Error {
644
- static {
645
- __name(this, "NotFoundError");
646
- }
647
- errorCode = RuntimeErrors.RecordNotFoundError;
648
- constructor(message) {
649
- super(message);
650
- }
651
- };
652
- var BadRequestError = class extends Error {
653
- static {
654
- __name(this, "BadRequestError");
655
- }
656
- errorCode = RuntimeErrors.BadRequestError;
657
- constructor(message = "bad request") {
658
- super(message);
659
- }
660
- };
661
- var UnknownError = class extends Error {
662
- static {
663
- __name(this, "UnknownError");
664
- }
665
- errorCode = RuntimeErrors.UnknownError;
666
- constructor(message = "unknown error") {
667
- super(message);
668
- }
669
- };
670
- var ErrorPresets = {
671
- NotFound: NotFoundError,
672
- BadRequest: BadRequestError,
673
- Unknown: UnknownError
674
- };
675
- function errorToJSONRPCResponse(request, e) {
676
- switch (e.constructor.name) {
677
- case "PermissionError":
678
- return (0, import_json_rpc_2.createJSONRPCErrorResponse)(
679
- request.id,
680
- RuntimeErrors.PermissionError,
681
- e.message
682
- );
683
- // Any error thrown in the ModelAPI class is
684
- // wrapped in a DatabaseError in order to differentiate 'our code' vs the user's own code.
685
- case "NoResultError":
686
- return (0, import_json_rpc_2.createJSONRPCErrorResponse)(
687
- request.id,
688
- // to be matched to https://github.com/teamkeel/keel/blob/e3115ffe381bfc371d4f45bbf96a15072a994ce5/runtime/actions/update.go#L54-L54
689
- RuntimeErrors.RecordNotFoundError,
690
- ""
691
- // Don't pass on the message as we want to normalise these at the runtime layer but still support custom messages in other NotFound errors
692
- );
693
- case "DatabaseError":
694
- let err = e;
695
- if (e instanceof DatabaseError) {
696
- err = e.error;
697
- }
698
- if (err.constructor.name == "NoResultError") {
699
- return (0, import_json_rpc_2.createJSONRPCErrorResponse)(
700
- request.id,
701
- // to be matched to https://github.com/teamkeel/keel/blob/e3115ffe381bfc371d4f45bbf96a15072a994ce5/runtime/actions/update.go#L54-L54
702
- RuntimeErrors.RecordNotFoundError,
703
- ""
704
- // Don't pass on the message as we want to normalise these at the runtime layer but still support custom messages in other NotFound errors
705
- );
706
- }
707
- if ("code" in err) {
708
- const { code: code2, detail, table: table2 } = err;
709
- let rpcErrorCode, column, value;
710
- const [col, val] = parseKeyMessage(err.detail);
711
- column = col;
712
- value = val;
713
- switch (code2) {
714
- case "23502":
715
- rpcErrorCode = RuntimeErrors.NotNullConstraintError;
716
- column = err.column;
717
- break;
718
- case "23503":
719
- rpcErrorCode = RuntimeErrors.ForeignKeyConstraintError;
720
- break;
721
- case "23505":
722
- rpcErrorCode = RuntimeErrors.UniqueConstraintError;
723
- break;
724
- default:
725
- rpcErrorCode = RuntimeErrors.DatabaseError;
726
- break;
727
- }
728
- return (0, import_json_rpc_2.createJSONRPCErrorResponse)(request.id, rpcErrorCode, e.message, {
729
- table: table2,
730
- column,
731
- code: code2,
732
- detail,
733
- value
734
- });
735
- }
736
- return (0, import_json_rpc_2.createJSONRPCErrorResponse)(
737
- request.id,
738
- RuntimeErrors.DatabaseError,
739
- e.message
740
- );
741
- default:
742
- return (0, import_json_rpc_2.createJSONRPCErrorResponse)(
743
- request.id,
744
- e.errorCode ?? RuntimeErrors.UnknownError,
745
- e.message
746
- );
747
- }
748
- }
749
- __name(errorToJSONRPCResponse, "errorToJSONRPCResponse");
750
- var keyMessagePattern = /\Key\s[(](.*)[)][=][(](.*)[)]/;
751
- var parseKeyMessage = /* @__PURE__ */ __name((msg) => {
752
- const [, col, value] = keyMessagePattern.exec(msg) || [];
753
- return [col, value];
754
- }, "parseKeyMessage");
755
-
756
- // src/File.ts
757
611
  var import_ksuid = __toESM(require("ksuid"), 1);
758
612
  var s3Client = (() => {
759
613
  if (!process.env.KEEL_FILES_BUCKET_NAME) {
760
614
  return null;
761
615
  }
762
- const endpoint = process.env.TEST_AWS_ENDPOINT;
763
- if (!endpoint) {
616
+ const endpoint = process.env.KEEL_S3_ENDPOINT;
617
+ if (endpoint) {
618
+ return new import_client_s3.S3Client({
619
+ region: process.env.KEEL_REGION,
620
+ credentials: {
621
+ accessKeyId: "keelstorage",
622
+ secretAccessKey: "keelstorage"
623
+ },
624
+ endpoint
625
+ });
626
+ }
627
+ const testEndpoint = process.env.TEST_AWS_ENDPOINT;
628
+ if (testEndpoint) {
764
629
  return new import_client_s3.S3Client({
765
630
  region: process.env.KEEL_REGION,
766
- credentials: (0, import_credential_providers.fromEnv)()
631
+ credentials: {
632
+ accessKeyId: "test",
633
+ secretAccessKey: "test"
634
+ },
635
+ endpointProvider: /* @__PURE__ */ __name(() => {
636
+ return {
637
+ url: new URL(testEndpoint)
638
+ };
639
+ }, "endpointProvider")
767
640
  });
768
641
  }
769
642
  return new import_client_s3.S3Client({
770
643
  region: process.env.KEEL_REGION,
771
- credentials: {
772
- accessKeyId: "test",
773
- secretAccessKey: "test"
774
- },
775
- endpointProvider: /* @__PURE__ */ __name(() => {
776
- return {
777
- url: new URL(endpoint)
778
- };
779
- }, "endpointProvider")
644
+ credentials: (0, import_credential_providers.fromEnv)()
780
645
  });
781
646
  })();
782
647
  var InlineFile = class _InlineFile {
@@ -881,24 +746,17 @@ var File = class _File extends InlineFile {
881
746
  const arrayBuffer = await this._contents.arrayBuffer();
882
747
  return Buffer.from(arrayBuffer);
883
748
  }
884
- if (s3Client) {
885
- const params = {
886
- Bucket: process.env.KEEL_FILES_BUCKET_NAME,
887
- Key: "files/" + this.key
888
- };
889
- const command = new import_client_s3.GetObjectCommand(params);
890
- const response = await s3Client.send(command);
891
- const blob = await response.Body.transformToByteArray();
892
- return Buffer.from(blob);
893
- }
894
- const db = useDatabase();
895
- try {
896
- const query = db.selectFrom("keel_storage").select("data").where("id", "=", this.key);
897
- const row = await query.executeTakeFirstOrThrow();
898
- return row.data;
899
- } catch (e) {
900
- throw new DatabaseError(e);
749
+ if (!s3Client) {
750
+ throw new Error("S3 client is required");
901
751
  }
752
+ const params = {
753
+ Bucket: process.env.KEEL_FILES_BUCKET_NAME,
754
+ Key: "files/" + this.key
755
+ };
756
+ const command = new import_client_s3.GetObjectCommand(params);
757
+ const response = await s3Client.send(command);
758
+ const blob = await response.Body.transformToByteArray();
759
+ return Buffer.from(blob);
902
760
  }
903
761
  async store(expires = null) {
904
762
  if (this._contents) {
@@ -916,17 +774,16 @@ var File = class _File extends InlineFile {
916
774
  }
917
775
  // Generates a presigned download URL
918
776
  async getPresignedUrl() {
919
- if (s3Client) {
920
- const command = new import_client_s3.GetObjectCommand({
921
- Bucket: process.env.KEEL_FILES_BUCKET_NAME,
922
- Key: "files/" + this.key,
923
- ResponseContentDisposition: "inline"
924
- });
925
- const url = await (0, import_s3_request_presigner.getSignedUrl)(s3Client, command, { expiresIn: 60 * 60 });
926
- return new URL(url);
927
- } else {
928
- return new URL(`${process.env.KEEL_API_URL}/files/${this.key}`);
777
+ if (!s3Client) {
778
+ throw new Error("S3 client is required");
929
779
  }
780
+ const command = new import_client_s3.GetObjectCommand({
781
+ Bucket: process.env.KEEL_FILES_BUCKET_NAME,
782
+ Key: "files/" + this.key,
783
+ ResponseContentDisposition: "inline"
784
+ });
785
+ const url = await (0, import_s3_request_presigner.getSignedUrl)(s3Client, command, { expiresIn: 60 * 60 });
786
+ return new URL(url);
930
787
  }
931
788
  // Persists the file
932
789
  toDbRecord() {
@@ -942,54 +799,36 @@ var File = class _File extends InlineFile {
942
799
  }
943
800
  };
944
801
  async function storeFile(contents, key, filename, contentType, size, expires) {
945
- if (s3Client) {
946
- const params = {
947
- Bucket: process.env.KEEL_FILES_BUCKET_NAME,
948
- Key: "files/" + key,
949
- Body: contents,
950
- ContentType: contentType,
951
- ContentDisposition: `attachment; filename="${encodeURIComponent(
952
- filename
953
- )}"`,
954
- Metadata: {
955
- filename
956
- },
957
- ACL: "private"
958
- };
959
- if (expires) {
960
- if (expires instanceof Date) {
961
- params.Expires = expires;
962
- } else {
963
- console.warn("Invalid expires value. Skipping Expires parameter.");
964
- }
965
- }
966
- const command = new import_client_s3.PutObjectCommand(params);
967
- try {
968
- await s3Client.send(command);
969
- } catch (error) {
970
- console.error("Error uploading file:", error);
971
- throw error;
972
- }
973
- } else {
974
- const db = useDatabase();
975
- try {
976
- const query = db.insertInto("keel_storage").values({
977
- id: key,
978
- filename,
979
- content_type: contentType,
980
- data: contents
981
- }).onConflict(
982
- (oc) => oc.column("id").doUpdateSet(() => ({
983
- filename,
984
- content_type: contentType,
985
- data: contents
986
- })).where("keel_storage.id", "=", key)
987
- ).returningAll();
988
- await query.execute();
989
- } catch (e) {
990
- throw new DatabaseError(e);
802
+ if (!s3Client) {
803
+ throw new Error("S3 client is required");
804
+ }
805
+ const params = {
806
+ Bucket: process.env.KEEL_FILES_BUCKET_NAME,
807
+ Key: "files/" + key,
808
+ Body: contents,
809
+ ContentType: contentType,
810
+ ContentDisposition: `attachment; filename="${encodeURIComponent(
811
+ filename
812
+ )}"`,
813
+ Metadata: {
814
+ filename
815
+ },
816
+ ACL: "private"
817
+ };
818
+ if (expires) {
819
+ if (expires instanceof Date) {
820
+ params.Expires = expires;
821
+ } else {
822
+ console.warn("Invalid expires value. Skipping Expires parameter.");
991
823
  }
992
824
  }
825
+ const command = new import_client_s3.PutObjectCommand(params);
826
+ try {
827
+ await s3Client.send(command);
828
+ } catch (error) {
829
+ console.error("Error uploading file:", error);
830
+ throw error;
831
+ }
993
832
  }
994
833
  __name(storeFile, "storeFile");
995
834
 
@@ -1429,6 +1268,150 @@ function joinAlias(tablePath) {
1429
1268
  }
1430
1269
  __name(joinAlias, "joinAlias");
1431
1270
 
1271
+ // src/errors.js
1272
+ var import_json_rpc_2 = require("json-rpc-2.0");
1273
+ var RuntimeErrors = {
1274
+ // Catchall error type for unhandled execution errors during custom function
1275
+ UnknownError: -32001,
1276
+ // DatabaseError represents any error at pg level that isn't handled explicitly below
1277
+ DatabaseError: -32002,
1278
+ // No result returned from custom function by user
1279
+ NoResultError: -32003,
1280
+ // When trying to delete/update a non existent record in the db
1281
+ RecordNotFoundError: -32004,
1282
+ ForeignKeyConstraintError: -32005,
1283
+ NotNullConstraintError: -32006,
1284
+ UniqueConstraintError: -32007,
1285
+ PermissionError: -32008,
1286
+ BadRequestError: -32009
1287
+ };
1288
+ var PermissionError = class extends Error {
1289
+ static {
1290
+ __name(this, "PermissionError");
1291
+ }
1292
+ };
1293
+ var DatabaseError = class extends Error {
1294
+ static {
1295
+ __name(this, "DatabaseError");
1296
+ }
1297
+ constructor(error) {
1298
+ super(error.message);
1299
+ this.error = error;
1300
+ }
1301
+ };
1302
+ var NotFoundError = class extends Error {
1303
+ static {
1304
+ __name(this, "NotFoundError");
1305
+ }
1306
+ errorCode = RuntimeErrors.RecordNotFoundError;
1307
+ constructor(message) {
1308
+ super(message);
1309
+ }
1310
+ };
1311
+ var BadRequestError = class extends Error {
1312
+ static {
1313
+ __name(this, "BadRequestError");
1314
+ }
1315
+ errorCode = RuntimeErrors.BadRequestError;
1316
+ constructor(message = "bad request") {
1317
+ super(message);
1318
+ }
1319
+ };
1320
+ var UnknownError = class extends Error {
1321
+ static {
1322
+ __name(this, "UnknownError");
1323
+ }
1324
+ errorCode = RuntimeErrors.UnknownError;
1325
+ constructor(message = "unknown error") {
1326
+ super(message);
1327
+ }
1328
+ };
1329
+ var ErrorPresets = {
1330
+ NotFound: NotFoundError,
1331
+ BadRequest: BadRequestError,
1332
+ Unknown: UnknownError
1333
+ };
1334
+ function errorToJSONRPCResponse(request, e) {
1335
+ switch (e.constructor.name) {
1336
+ case "PermissionError":
1337
+ return (0, import_json_rpc_2.createJSONRPCErrorResponse)(
1338
+ request.id,
1339
+ RuntimeErrors.PermissionError,
1340
+ e.message
1341
+ );
1342
+ // Any error thrown in the ModelAPI class is
1343
+ // wrapped in a DatabaseError in order to differentiate 'our code' vs the user's own code.
1344
+ case "NoResultError":
1345
+ return (0, import_json_rpc_2.createJSONRPCErrorResponse)(
1346
+ request.id,
1347
+ // to be matched to https://github.com/teamkeel/keel/blob/e3115ffe381bfc371d4f45bbf96a15072a994ce5/runtime/actions/update.go#L54-L54
1348
+ RuntimeErrors.RecordNotFoundError,
1349
+ ""
1350
+ // Don't pass on the message as we want to normalise these at the runtime layer but still support custom messages in other NotFound errors
1351
+ );
1352
+ case "DatabaseError":
1353
+ let err = e;
1354
+ if (e instanceof DatabaseError) {
1355
+ err = e.error;
1356
+ }
1357
+ if (err.constructor.name == "NoResultError") {
1358
+ return (0, import_json_rpc_2.createJSONRPCErrorResponse)(
1359
+ request.id,
1360
+ // to be matched to https://github.com/teamkeel/keel/blob/e3115ffe381bfc371d4f45bbf96a15072a994ce5/runtime/actions/update.go#L54-L54
1361
+ RuntimeErrors.RecordNotFoundError,
1362
+ ""
1363
+ // Don't pass on the message as we want to normalise these at the runtime layer but still support custom messages in other NotFound errors
1364
+ );
1365
+ }
1366
+ if ("code" in err) {
1367
+ const { code: code2, detail, table: table2 } = err;
1368
+ let rpcErrorCode, column, value;
1369
+ const [col, val] = parseKeyMessage(err.detail);
1370
+ column = col;
1371
+ value = val;
1372
+ switch (code2) {
1373
+ case "23502":
1374
+ rpcErrorCode = RuntimeErrors.NotNullConstraintError;
1375
+ column = err.column;
1376
+ break;
1377
+ case "23503":
1378
+ rpcErrorCode = RuntimeErrors.ForeignKeyConstraintError;
1379
+ break;
1380
+ case "23505":
1381
+ rpcErrorCode = RuntimeErrors.UniqueConstraintError;
1382
+ break;
1383
+ default:
1384
+ rpcErrorCode = RuntimeErrors.DatabaseError;
1385
+ break;
1386
+ }
1387
+ return (0, import_json_rpc_2.createJSONRPCErrorResponse)(request.id, rpcErrorCode, e.message, {
1388
+ table: table2,
1389
+ column,
1390
+ code: code2,
1391
+ detail,
1392
+ value
1393
+ });
1394
+ }
1395
+ return (0, import_json_rpc_2.createJSONRPCErrorResponse)(
1396
+ request.id,
1397
+ RuntimeErrors.DatabaseError,
1398
+ e.message
1399
+ );
1400
+ default:
1401
+ return (0, import_json_rpc_2.createJSONRPCErrorResponse)(
1402
+ request.id,
1403
+ e.errorCode ?? RuntimeErrors.UnknownError,
1404
+ e.message
1405
+ );
1406
+ }
1407
+ }
1408
+ __name(errorToJSONRPCResponse, "errorToJSONRPCResponse");
1409
+ var keyMessagePattern = /\Key\s[(](.*)[)][=][(](.*)[)]/;
1410
+ var parseKeyMessage = /* @__PURE__ */ __name((msg) => {
1411
+ const [, col, value] = keyMessagePattern.exec(msg) || [];
1412
+ return [col, value];
1413
+ }, "parseKeyMessage");
1414
+
1432
1415
  // src/QueryBuilder.js
1433
1416
  var QueryBuilder = class _QueryBuilder {
1434
1417
  static {
@@ -2501,15 +2484,38 @@ async function page(options, data, action) {
2501
2484
  const content = options.content;
2502
2485
  let hasValidationErrors = false;
2503
2486
  let validationError;
2504
- if (options.actions && action !== null) {
2505
- const isValidAction = options.actions.some((a) => {
2506
- if (typeof a === "string") return a === action;
2507
- return a && typeof a === "object" && "value" in a && a.value === action;
2508
- });
2509
- if (!isValidAction) {
2510
- hasValidationErrors = true;
2511
- validationError = "invalid action";
2487
+ if (options.actions && options.actions.length > 0) {
2488
+ const normalizedAction = action === "undefined" || action === void 0 ? null : action;
2489
+ if (data !== null) {
2490
+ if (normalizedAction === null) {
2491
+ const validValues = options.actions.map((a) => {
2492
+ if (typeof a === "string") return a;
2493
+ return a && typeof a === "object" && "value" in a ? a.value : "";
2494
+ }).filter(Boolean);
2495
+ throw new Error(
2496
+ `action is required. Valid actions are: ${validValues.join(", ")}`
2497
+ );
2498
+ }
2499
+ const isValidAction = options.actions.some((a) => {
2500
+ if (typeof a === "string") return a === normalizedAction;
2501
+ return a && typeof a === "object" && "value" in a && a.value === normalizedAction;
2502
+ });
2503
+ if (!isValidAction) {
2504
+ const validValues = options.actions.map((a) => {
2505
+ if (typeof a === "string") return a;
2506
+ return a && typeof a === "object" && "value" in a ? a.value : "";
2507
+ }).filter(Boolean);
2508
+ throw new Error(
2509
+ `invalid action "${normalizedAction}". Valid actions are: ${validValues.join(
2510
+ ", "
2511
+ )}`
2512
+ );
2513
+ }
2512
2514
  }
2515
+ } else if (action !== null && action !== void 0 && action !== "undefined") {
2516
+ throw new Error(
2517
+ `invalid action "${action}". No actions are defined for this page`
2518
+ );
2513
2519
  }
2514
2520
  const contentUiConfig = await Promise.all(
2515
2521
  content.map(async (c) => {