@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.js CHANGED
@@ -558,175 +558,40 @@ import {
558
558
  } from "@aws-sdk/client-s3";
559
559
  import { fromEnv } from "@aws-sdk/credential-providers";
560
560
  import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
561
-
562
- // src/errors.js
563
- import { createJSONRPCErrorResponse } from "json-rpc-2.0";
564
- var RuntimeErrors = {
565
- // Catchall error type for unhandled execution errors during custom function
566
- UnknownError: -32001,
567
- // DatabaseError represents any error at pg level that isn't handled explicitly below
568
- DatabaseError: -32002,
569
- // No result returned from custom function by user
570
- NoResultError: -32003,
571
- // When trying to delete/update a non existent record in the db
572
- RecordNotFoundError: -32004,
573
- ForeignKeyConstraintError: -32005,
574
- NotNullConstraintError: -32006,
575
- UniqueConstraintError: -32007,
576
- PermissionError: -32008,
577
- BadRequestError: -32009
578
- };
579
- var PermissionError = class extends Error {
580
- static {
581
- __name(this, "PermissionError");
582
- }
583
- };
584
- var DatabaseError = class extends Error {
585
- static {
586
- __name(this, "DatabaseError");
587
- }
588
- constructor(error) {
589
- super(error.message);
590
- this.error = error;
591
- }
592
- };
593
- var NotFoundError = class extends Error {
594
- static {
595
- __name(this, "NotFoundError");
596
- }
597
- errorCode = RuntimeErrors.RecordNotFoundError;
598
- constructor(message) {
599
- super(message);
600
- }
601
- };
602
- var BadRequestError = class extends Error {
603
- static {
604
- __name(this, "BadRequestError");
605
- }
606
- errorCode = RuntimeErrors.BadRequestError;
607
- constructor(message = "bad request") {
608
- super(message);
609
- }
610
- };
611
- var UnknownError = class extends Error {
612
- static {
613
- __name(this, "UnknownError");
614
- }
615
- errorCode = RuntimeErrors.UnknownError;
616
- constructor(message = "unknown error") {
617
- super(message);
618
- }
619
- };
620
- var ErrorPresets = {
621
- NotFound: NotFoundError,
622
- BadRequest: BadRequestError,
623
- Unknown: UnknownError
624
- };
625
- function errorToJSONRPCResponse(request, e) {
626
- switch (e.constructor.name) {
627
- case "PermissionError":
628
- return createJSONRPCErrorResponse(
629
- request.id,
630
- RuntimeErrors.PermissionError,
631
- e.message
632
- );
633
- // Any error thrown in the ModelAPI class is
634
- // wrapped in a DatabaseError in order to differentiate 'our code' vs the user's own code.
635
- case "NoResultError":
636
- return createJSONRPCErrorResponse(
637
- request.id,
638
- // to be matched to https://github.com/teamkeel/keel/blob/e3115ffe381bfc371d4f45bbf96a15072a994ce5/runtime/actions/update.go#L54-L54
639
- RuntimeErrors.RecordNotFoundError,
640
- ""
641
- // 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
642
- );
643
- case "DatabaseError":
644
- let err = e;
645
- if (e instanceof DatabaseError) {
646
- err = e.error;
647
- }
648
- if (err.constructor.name == "NoResultError") {
649
- return createJSONRPCErrorResponse(
650
- request.id,
651
- // to be matched to https://github.com/teamkeel/keel/blob/e3115ffe381bfc371d4f45bbf96a15072a994ce5/runtime/actions/update.go#L54-L54
652
- RuntimeErrors.RecordNotFoundError,
653
- ""
654
- // 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
655
- );
656
- }
657
- if ("code" in err) {
658
- const { code: code2, detail, table: table2 } = err;
659
- let rpcErrorCode, column, value;
660
- const [col, val] = parseKeyMessage(err.detail);
661
- column = col;
662
- value = val;
663
- switch (code2) {
664
- case "23502":
665
- rpcErrorCode = RuntimeErrors.NotNullConstraintError;
666
- column = err.column;
667
- break;
668
- case "23503":
669
- rpcErrorCode = RuntimeErrors.ForeignKeyConstraintError;
670
- break;
671
- case "23505":
672
- rpcErrorCode = RuntimeErrors.UniqueConstraintError;
673
- break;
674
- default:
675
- rpcErrorCode = RuntimeErrors.DatabaseError;
676
- break;
677
- }
678
- return createJSONRPCErrorResponse(request.id, rpcErrorCode, e.message, {
679
- table: table2,
680
- column,
681
- code: code2,
682
- detail,
683
- value
684
- });
685
- }
686
- return createJSONRPCErrorResponse(
687
- request.id,
688
- RuntimeErrors.DatabaseError,
689
- e.message
690
- );
691
- default:
692
- return createJSONRPCErrorResponse(
693
- request.id,
694
- e.errorCode ?? RuntimeErrors.UnknownError,
695
- e.message
696
- );
697
- }
698
- }
699
- __name(errorToJSONRPCResponse, "errorToJSONRPCResponse");
700
- var keyMessagePattern = /\Key\s[(](.*)[)][=][(](.*)[)]/;
701
- var parseKeyMessage = /* @__PURE__ */ __name((msg) => {
702
- const [, col, value] = keyMessagePattern.exec(msg) || [];
703
- return [col, value];
704
- }, "parseKeyMessage");
705
-
706
- // src/File.ts
707
561
  import KSUID from "ksuid";
708
562
  var s3Client = (() => {
709
563
  if (!process.env.KEEL_FILES_BUCKET_NAME) {
710
564
  return null;
711
565
  }
712
- const endpoint = process.env.TEST_AWS_ENDPOINT;
713
- if (!endpoint) {
566
+ const endpoint = process.env.KEEL_S3_ENDPOINT;
567
+ if (endpoint) {
568
+ return new S3Client({
569
+ region: process.env.KEEL_REGION,
570
+ credentials: {
571
+ accessKeyId: "keelstorage",
572
+ secretAccessKey: "keelstorage"
573
+ },
574
+ endpoint
575
+ });
576
+ }
577
+ const testEndpoint = process.env.TEST_AWS_ENDPOINT;
578
+ if (testEndpoint) {
714
579
  return new S3Client({
715
580
  region: process.env.KEEL_REGION,
716
- credentials: fromEnv()
581
+ credentials: {
582
+ accessKeyId: "test",
583
+ secretAccessKey: "test"
584
+ },
585
+ endpointProvider: /* @__PURE__ */ __name(() => {
586
+ return {
587
+ url: new URL(testEndpoint)
588
+ };
589
+ }, "endpointProvider")
717
590
  });
718
591
  }
719
592
  return new S3Client({
720
593
  region: process.env.KEEL_REGION,
721
- credentials: {
722
- accessKeyId: "test",
723
- secretAccessKey: "test"
724
- },
725
- endpointProvider: /* @__PURE__ */ __name(() => {
726
- return {
727
- url: new URL(endpoint)
728
- };
729
- }, "endpointProvider")
594
+ credentials: fromEnv()
730
595
  });
731
596
  })();
732
597
  var InlineFile = class _InlineFile {
@@ -831,24 +696,17 @@ var File = class _File extends InlineFile {
831
696
  const arrayBuffer = await this._contents.arrayBuffer();
832
697
  return Buffer.from(arrayBuffer);
833
698
  }
834
- if (s3Client) {
835
- const params = {
836
- Bucket: process.env.KEEL_FILES_BUCKET_NAME,
837
- Key: "files/" + this.key
838
- };
839
- const command = new GetObjectCommand(params);
840
- const response = await s3Client.send(command);
841
- const blob = await response.Body.transformToByteArray();
842
- return Buffer.from(blob);
843
- }
844
- const db = useDatabase();
845
- try {
846
- const query = db.selectFrom("keel_storage").select("data").where("id", "=", this.key);
847
- const row = await query.executeTakeFirstOrThrow();
848
- return row.data;
849
- } catch (e) {
850
- throw new DatabaseError(e);
699
+ if (!s3Client) {
700
+ throw new Error("S3 client is required");
851
701
  }
702
+ const params = {
703
+ Bucket: process.env.KEEL_FILES_BUCKET_NAME,
704
+ Key: "files/" + this.key
705
+ };
706
+ const command = new GetObjectCommand(params);
707
+ const response = await s3Client.send(command);
708
+ const blob = await response.Body.transformToByteArray();
709
+ return Buffer.from(blob);
852
710
  }
853
711
  async store(expires = null) {
854
712
  if (this._contents) {
@@ -866,17 +724,16 @@ var File = class _File extends InlineFile {
866
724
  }
867
725
  // Generates a presigned download URL
868
726
  async getPresignedUrl() {
869
- if (s3Client) {
870
- const command = new GetObjectCommand({
871
- Bucket: process.env.KEEL_FILES_BUCKET_NAME,
872
- Key: "files/" + this.key,
873
- ResponseContentDisposition: "inline"
874
- });
875
- const url = await getSignedUrl(s3Client, command, { expiresIn: 60 * 60 });
876
- return new URL(url);
877
- } else {
878
- return new URL(`${process.env.KEEL_API_URL}/files/${this.key}`);
727
+ if (!s3Client) {
728
+ throw new Error("S3 client is required");
879
729
  }
730
+ const command = new GetObjectCommand({
731
+ Bucket: process.env.KEEL_FILES_BUCKET_NAME,
732
+ Key: "files/" + this.key,
733
+ ResponseContentDisposition: "inline"
734
+ });
735
+ const url = await getSignedUrl(s3Client, command, { expiresIn: 60 * 60 });
736
+ return new URL(url);
880
737
  }
881
738
  // Persists the file
882
739
  toDbRecord() {
@@ -892,54 +749,36 @@ var File = class _File extends InlineFile {
892
749
  }
893
750
  };
894
751
  async function storeFile(contents, key, filename, contentType, size, expires) {
895
- if (s3Client) {
896
- const params = {
897
- Bucket: process.env.KEEL_FILES_BUCKET_NAME,
898
- Key: "files/" + key,
899
- Body: contents,
900
- ContentType: contentType,
901
- ContentDisposition: `attachment; filename="${encodeURIComponent(
902
- filename
903
- )}"`,
904
- Metadata: {
905
- filename
906
- },
907
- ACL: "private"
908
- };
909
- if (expires) {
910
- if (expires instanceof Date) {
911
- params.Expires = expires;
912
- } else {
913
- console.warn("Invalid expires value. Skipping Expires parameter.");
914
- }
915
- }
916
- const command = new PutObjectCommand(params);
917
- try {
918
- await s3Client.send(command);
919
- } catch (error) {
920
- console.error("Error uploading file:", error);
921
- throw error;
922
- }
923
- } else {
924
- const db = useDatabase();
925
- try {
926
- const query = db.insertInto("keel_storage").values({
927
- id: key,
928
- filename,
929
- content_type: contentType,
930
- data: contents
931
- }).onConflict(
932
- (oc) => oc.column("id").doUpdateSet(() => ({
933
- filename,
934
- content_type: contentType,
935
- data: contents
936
- })).where("keel_storage.id", "=", key)
937
- ).returningAll();
938
- await query.execute();
939
- } catch (e) {
940
- throw new DatabaseError(e);
752
+ if (!s3Client) {
753
+ throw new Error("S3 client is required");
754
+ }
755
+ const params = {
756
+ Bucket: process.env.KEEL_FILES_BUCKET_NAME,
757
+ Key: "files/" + key,
758
+ Body: contents,
759
+ ContentType: contentType,
760
+ ContentDisposition: `attachment; filename="${encodeURIComponent(
761
+ filename
762
+ )}"`,
763
+ Metadata: {
764
+ filename
765
+ },
766
+ ACL: "private"
767
+ };
768
+ if (expires) {
769
+ if (expires instanceof Date) {
770
+ params.Expires = expires;
771
+ } else {
772
+ console.warn("Invalid expires value. Skipping Expires parameter.");
941
773
  }
942
774
  }
775
+ const command = new PutObjectCommand(params);
776
+ try {
777
+ await s3Client.send(command);
778
+ } catch (error) {
779
+ console.error("Error uploading file:", error);
780
+ throw error;
781
+ }
943
782
  }
944
783
  __name(storeFile, "storeFile");
945
784
 
@@ -1379,6 +1218,150 @@ function joinAlias(tablePath) {
1379
1218
  }
1380
1219
  __name(joinAlias, "joinAlias");
1381
1220
 
1221
+ // src/errors.js
1222
+ import { createJSONRPCErrorResponse } from "json-rpc-2.0";
1223
+ var RuntimeErrors = {
1224
+ // Catchall error type for unhandled execution errors during custom function
1225
+ UnknownError: -32001,
1226
+ // DatabaseError represents any error at pg level that isn't handled explicitly below
1227
+ DatabaseError: -32002,
1228
+ // No result returned from custom function by user
1229
+ NoResultError: -32003,
1230
+ // When trying to delete/update a non existent record in the db
1231
+ RecordNotFoundError: -32004,
1232
+ ForeignKeyConstraintError: -32005,
1233
+ NotNullConstraintError: -32006,
1234
+ UniqueConstraintError: -32007,
1235
+ PermissionError: -32008,
1236
+ BadRequestError: -32009
1237
+ };
1238
+ var PermissionError = class extends Error {
1239
+ static {
1240
+ __name(this, "PermissionError");
1241
+ }
1242
+ };
1243
+ var DatabaseError = class extends Error {
1244
+ static {
1245
+ __name(this, "DatabaseError");
1246
+ }
1247
+ constructor(error) {
1248
+ super(error.message);
1249
+ this.error = error;
1250
+ }
1251
+ };
1252
+ var NotFoundError = class extends Error {
1253
+ static {
1254
+ __name(this, "NotFoundError");
1255
+ }
1256
+ errorCode = RuntimeErrors.RecordNotFoundError;
1257
+ constructor(message) {
1258
+ super(message);
1259
+ }
1260
+ };
1261
+ var BadRequestError = class extends Error {
1262
+ static {
1263
+ __name(this, "BadRequestError");
1264
+ }
1265
+ errorCode = RuntimeErrors.BadRequestError;
1266
+ constructor(message = "bad request") {
1267
+ super(message);
1268
+ }
1269
+ };
1270
+ var UnknownError = class extends Error {
1271
+ static {
1272
+ __name(this, "UnknownError");
1273
+ }
1274
+ errorCode = RuntimeErrors.UnknownError;
1275
+ constructor(message = "unknown error") {
1276
+ super(message);
1277
+ }
1278
+ };
1279
+ var ErrorPresets = {
1280
+ NotFound: NotFoundError,
1281
+ BadRequest: BadRequestError,
1282
+ Unknown: UnknownError
1283
+ };
1284
+ function errorToJSONRPCResponse(request, e) {
1285
+ switch (e.constructor.name) {
1286
+ case "PermissionError":
1287
+ return createJSONRPCErrorResponse(
1288
+ request.id,
1289
+ RuntimeErrors.PermissionError,
1290
+ e.message
1291
+ );
1292
+ // Any error thrown in the ModelAPI class is
1293
+ // wrapped in a DatabaseError in order to differentiate 'our code' vs the user's own code.
1294
+ case "NoResultError":
1295
+ return createJSONRPCErrorResponse(
1296
+ request.id,
1297
+ // to be matched to https://github.com/teamkeel/keel/blob/e3115ffe381bfc371d4f45bbf96a15072a994ce5/runtime/actions/update.go#L54-L54
1298
+ RuntimeErrors.RecordNotFoundError,
1299
+ ""
1300
+ // 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
1301
+ );
1302
+ case "DatabaseError":
1303
+ let err = e;
1304
+ if (e instanceof DatabaseError) {
1305
+ err = e.error;
1306
+ }
1307
+ if (err.constructor.name == "NoResultError") {
1308
+ return createJSONRPCErrorResponse(
1309
+ request.id,
1310
+ // to be matched to https://github.com/teamkeel/keel/blob/e3115ffe381bfc371d4f45bbf96a15072a994ce5/runtime/actions/update.go#L54-L54
1311
+ RuntimeErrors.RecordNotFoundError,
1312
+ ""
1313
+ // 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
1314
+ );
1315
+ }
1316
+ if ("code" in err) {
1317
+ const { code: code2, detail, table: table2 } = err;
1318
+ let rpcErrorCode, column, value;
1319
+ const [col, val] = parseKeyMessage(err.detail);
1320
+ column = col;
1321
+ value = val;
1322
+ switch (code2) {
1323
+ case "23502":
1324
+ rpcErrorCode = RuntimeErrors.NotNullConstraintError;
1325
+ column = err.column;
1326
+ break;
1327
+ case "23503":
1328
+ rpcErrorCode = RuntimeErrors.ForeignKeyConstraintError;
1329
+ break;
1330
+ case "23505":
1331
+ rpcErrorCode = RuntimeErrors.UniqueConstraintError;
1332
+ break;
1333
+ default:
1334
+ rpcErrorCode = RuntimeErrors.DatabaseError;
1335
+ break;
1336
+ }
1337
+ return createJSONRPCErrorResponse(request.id, rpcErrorCode, e.message, {
1338
+ table: table2,
1339
+ column,
1340
+ code: code2,
1341
+ detail,
1342
+ value
1343
+ });
1344
+ }
1345
+ return createJSONRPCErrorResponse(
1346
+ request.id,
1347
+ RuntimeErrors.DatabaseError,
1348
+ e.message
1349
+ );
1350
+ default:
1351
+ return createJSONRPCErrorResponse(
1352
+ request.id,
1353
+ e.errorCode ?? RuntimeErrors.UnknownError,
1354
+ e.message
1355
+ );
1356
+ }
1357
+ }
1358
+ __name(errorToJSONRPCResponse, "errorToJSONRPCResponse");
1359
+ var keyMessagePattern = /\Key\s[(](.*)[)][=][(](.*)[)]/;
1360
+ var parseKeyMessage = /* @__PURE__ */ __name((msg) => {
1361
+ const [, col, value] = keyMessagePattern.exec(msg) || [];
1362
+ return [col, value];
1363
+ }, "parseKeyMessage");
1364
+
1382
1365
  // src/QueryBuilder.js
1383
1366
  var QueryBuilder = class _QueryBuilder {
1384
1367
  static {
@@ -2471,15 +2454,38 @@ async function page(options, data, action) {
2471
2454
  const content = options.content;
2472
2455
  let hasValidationErrors = false;
2473
2456
  let validationError;
2474
- if (options.actions && action !== null) {
2475
- const isValidAction = options.actions.some((a) => {
2476
- if (typeof a === "string") return a === action;
2477
- return a && typeof a === "object" && "value" in a && a.value === action;
2478
- });
2479
- if (!isValidAction) {
2480
- hasValidationErrors = true;
2481
- validationError = "invalid action";
2457
+ if (options.actions && options.actions.length > 0) {
2458
+ const normalizedAction = action === "undefined" || action === void 0 ? null : action;
2459
+ if (data !== null) {
2460
+ if (normalizedAction === null) {
2461
+ const validValues = options.actions.map((a) => {
2462
+ if (typeof a === "string") return a;
2463
+ return a && typeof a === "object" && "value" in a ? a.value : "";
2464
+ }).filter(Boolean);
2465
+ throw new Error(
2466
+ `action is required. Valid actions are: ${validValues.join(", ")}`
2467
+ );
2468
+ }
2469
+ const isValidAction = options.actions.some((a) => {
2470
+ if (typeof a === "string") return a === normalizedAction;
2471
+ return a && typeof a === "object" && "value" in a && a.value === normalizedAction;
2472
+ });
2473
+ if (!isValidAction) {
2474
+ const validValues = options.actions.map((a) => {
2475
+ if (typeof a === "string") return a;
2476
+ return a && typeof a === "object" && "value" in a ? a.value : "";
2477
+ }).filter(Boolean);
2478
+ throw new Error(
2479
+ `invalid action "${normalizedAction}". Valid actions are: ${validValues.join(
2480
+ ", "
2481
+ )}`
2482
+ );
2483
+ }
2482
2484
  }
2485
+ } else if (action !== null && action !== void 0 && action !== "undefined") {
2486
+ throw new Error(
2487
+ `invalid action "${action}". No actions are defined for this page`
2488
+ );
2483
2489
  }
2484
2490
  const contentUiConfig = await Promise.all(
2485
2491
  content.map(async (c) => {