@exabugs/dynamodb-client 1.5.3 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/server/handler.cjs +3 -3
  2. package/dist/server/media/magicBytes.d.ts.map +1 -1
  3. package/dist/server/media/magicBytes.js +17 -14
  4. package/dist/server/media/magicBytes.js.map +1 -1
  5. package/dist/server/media/media-handler.d.ts.map +1 -1
  6. package/dist/server/media/media-handler.js +38 -4
  7. package/dist/server/media/media-handler.js.map +1 -1
  8. package/dist/server/media/metadata.d.ts +29 -0
  9. package/dist/server/media/metadata.d.ts.map +1 -0
  10. package/dist/server/media/metadata.js +57 -0
  11. package/dist/server/media/metadata.js.map +1 -0
  12. package/dist/server/media/presign.d.ts.map +1 -1
  13. package/dist/server/media/presign.js +18 -1
  14. package/dist/server/media/presign.js.map +1 -1
  15. package/dist/server/media/process-handler-dlq.d.ts +0 -13
  16. package/dist/server/media/process-handler-dlq.d.ts.map +1 -1
  17. package/dist/server/media/process-handler-dlq.js +49 -7
  18. package/dist/server/media/process-handler-dlq.js.map +1 -1
  19. package/dist/server/media/process-handler.d.ts.map +1 -1
  20. package/dist/server/media/process-handler.js +40 -25
  21. package/dist/server/media/process-handler.js.map +1 -1
  22. package/dist/server/media/types.d.ts +1 -1
  23. package/dist/server/media/types.d.ts.map +1 -1
  24. package/dist/server/media/upload-handler.d.ts +50 -0
  25. package/dist/server/media/upload-handler.d.ts.map +1 -0
  26. package/dist/server/media/upload-handler.js +115 -0
  27. package/dist/server/media/upload-handler.js.map +1 -0
  28. package/dist/server/media-handler.cjs +27 -3
  29. package/dist/server/media-handler.cjs.map +2 -2
  30. package/dist/server/media-process-handler-dlq.cjs +25496 -13553
  31. package/dist/server/media-process-handler-dlq.cjs.map +4 -4
  32. package/dist/server/media-process-handler.cjs +648 -536
  33. package/dist/server/media-process-handler.cjs.map +4 -4
  34. package/dist/sharp-layer.zip +0 -0
  35. package/package.json +5 -1
  36. package/terraform/media/README.md +4 -1
  37. package/terraform/media/main.tf +86 -13
  38. package/terraform/media/variables.tf +33 -0
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exabugs/dynamodb-client",
3
- "version": "1.5.3",
3
+ "version": "1.6.0",
4
4
  "description": "DynamoDB Single-Table Client SDK with MongoDB-like API, Shadow Records, and Lambda implementation for serverless applications",
5
5
  "author": "exabugs",
6
6
  "license": "MIT",
@@ -91,6 +91,10 @@
91
91
  "import": "./dist/server/media/presign.js",
92
92
  "types": "./dist/server/media/presign.d.ts"
93
93
  },
94
+ "./server/media/upload-handler": {
95
+ "import": "./dist/server/media/upload-handler.js",
96
+ "types": "./dist/server/media/upload-handler.d.ts"
97
+ },
94
98
  "./server/media-process-handler": {
95
99
  "import": "./dist/server/media-process-handler.cjs",
96
100
  "types": "./dist/server/media/process-handler.d.ts"
@@ -17,7 +17,10 @@
17
17
  - CloudFront Distribution(`/master/*`はS3直接配信、`/resize/*`はmedia-handler経由、両方とも署名付きURL必須)
18
18
  - CloudFront Public Key / Key Group(署名鍵は呼び出し側がSSM Parameter Storeに別途登録する)
19
19
 
20
- **含まないもの**: presign/署名URL発行用のLambda。「誰にpresign/署名URLを発行してよいか」の認可判断はアプリごとに異なるため、呼び出し側は既存のHTTP APIレイヤー(認証・権限チェック済み)から `../../src/server/media/presign.ts`・`sign.ts` を直接呼び出して自前のLambda/ルートに組み込んでください。
20
+ **含まないもの**: presign/署名URL発行用のLambdaリソース。「誰にpresign/署名URLを発行してよいか」の認可判断はアプリごとに、かつ同一アプリ内でもクライアント(モバイル/管理画面等)ごとに異なるため、Terraformでは画一的なリソースとして定義できません。呼び出し側は次のいずれかの方法で自前にデプロイしてください:
21
+
22
+ - 既存のHTTP APIレイヤー(認証・権限チェック済み)から `../../src/server/media/presign.ts`・`sign.ts` を直接呼び出す
23
+ - 対応する既存HTTPレイヤーが無いクライアント向けには `../../src/server/media/upload-handler.ts` の `createUploadHandler()` を使い、専用の小さなLambdaとして自前でバンドル・デプロイする
21
24
 
22
25
  ## 前提条件
23
26
 
@@ -3,10 +3,15 @@
3
3
  # 詳細設計: ../../docs/media-design.md
4
4
  # 決定事項・トレードオフ: ../../docs/adr/0002-media-design.md
5
5
  #
6
- # 本モジュールは upload-handler(presign/sign発行)のLambdaを含まない。
7
- # 「誰にpresign/署名URLを発行するか」の認可判断はアプリごとに異なるため、呼び出し側は
8
- # 既存のHTTP APIレイヤー(認証・権限チェック済み)から `../../src/server/media/presign.ts`・
9
- # `sign.ts` を直接呼び出して自前のLambda/ルートに組み込むこと。
6
+ # 本モジュールは upload-handler(presign/sign発行)のLambdaリソースを含まない。
7
+ # 「誰にpresign/署名URLを発行するか」の認可判断はアプリごとに、かつ同一アプリ内でも
8
+ # クライアント(モバイル/管理画面等)ごとに異なるため、Terraformでは画一的な
9
+ # リソースとして定義できない。呼び出し側は次のいずれかの方法で自前にデプロイすること:
10
+ # (a) 既存のHTTP APIレイヤー(認証・権限チェック済み)から
11
+ # `../../src/server/media/presign.ts`・`sign.ts` を直接呼び出す
12
+ # (b) 対応する既存HTTPレイヤーが無いクライアント(例: 別の認証方式を使う管理画面)向けには
13
+ # `../../src/server/media/upload-handler.ts` の `createUploadHandler()` を使い、
14
+ # 専用の小さなLambdaとして自前でバンドル・デプロイする
10
15
 
11
16
  locals {
12
17
  name_prefix = "${var.project_name}-${var.environment}-media"
@@ -51,6 +56,22 @@ resource "aws_s3_bucket_lifecycle_configuration" "media" {
51
56
  }
52
57
  }
53
58
 
59
+ # ブラウザ(管理画面等)からpresigned POSTで直接S3へアップロードする場合に必要。
60
+ # 無いとプリフライトリクエストが失敗しアップロードが全滅する
61
+ # (モバイルアプリ(React Native)はCORSの対象外のため影響しない)。
62
+ resource "aws_s3_bucket_cors_configuration" "media" {
63
+ count = length(var.allowed_upload_origins) > 0 ? 1 : 0
64
+
65
+ bucket = aws_s3_bucket.media.id
66
+
67
+ cors_rule {
68
+ allowed_methods = ["POST"]
69
+ allowed_origins = var.allowed_upload_origins
70
+ allowed_headers = ["*"]
71
+ max_age_seconds = 3000
72
+ }
73
+ }
74
+
54
75
  # CloudFront OAC経由のアクセスのみ許可。Resourceはmaster/*に限定し、
55
76
  # raw/*・cache/*をバケットポリシー経由で公開しない(cache/*はmedia-handlerの実行ロール経由のみ)
56
77
  resource "aws_s3_bucket_policy" "media" {
@@ -158,8 +179,10 @@ resource "aws_iam_role_policy" "process_handler_dynamodb" {
158
179
  Version = "2012-10-17"
159
180
  Statement = [
160
181
  {
161
- Effect = "Allow"
162
- Action = ["dynamodb:GetItem", "dynamodb:PutItem", "dynamodb:UpdateItem", "dynamodb:BatchGetItem", "dynamodb:Query"]
182
+ Effect = "Allow"
183
+ # DeleteItem: updateMany(handleUpdateOneの内部実装)はTransactWriteの中で
184
+ # 更新前の古いShadow Recordを削除する処理を含むため、2回目以降の更新に必須
185
+ Action = ["dynamodb:GetItem", "dynamodb:PutItem", "dynamodb:UpdateItem", "dynamodb:DeleteItem", "dynamodb:BatchGetItem", "dynamodb:Query"]
163
186
  Resource = [var.table_arn, "${var.table_arn}/index/*"]
164
187
  }
165
188
  ]
@@ -233,9 +256,13 @@ resource "aws_lambda_function" "process_handler" {
233
256
 
234
257
  environment {
235
258
  variables = {
236
- REGION = var.region
237
- TABLE_NAME = var.table_name
238
- IMAGE_POLICY_PARAM = var.image_policy_param
259
+ REGION = var.region
260
+ TABLE_NAME = var.table_name
261
+ IMAGE_POLICY_PARAM = var.image_policy_param
262
+ SHADOW_CREATED_AT_FIELD = var.shadow_created_at_field
263
+ SHADOW_UPDATED_AT_FIELD = var.shadow_updated_at_field
264
+ SHADOW_STRING_MAX_BYTES = var.shadow_string_max_bytes
265
+ SHADOW_NUMBER_PADDING = var.shadow_number_padding
239
266
  }
240
267
  }
241
268
 
@@ -254,6 +281,11 @@ resource "aws_lambda_function" "process_handler" {
254
281
  resource "aws_lambda_function_event_invoke_config" "process_handler" {
255
282
  function_name = aws_lambda_function.process_handler.function_name
256
283
  maximum_retry_attempts = 2
284
+ # 既定の6時間だと、リトライが尽きてDLQ経由でfailedが書き込まれるまで
285
+ # 呼び出し側のポーリングが長時間ブロックされ得る。短く設定し、クライアントの
286
+ # タイムアウト判断(詳細: docs/media-design.md「ポーリングが区別すべき状態」)
287
+ # と整合させる。
288
+ maximum_event_age_in_seconds = 300
257
289
 
258
290
  destination_config {
259
291
  on_failure {
@@ -326,6 +358,27 @@ resource "aws_iam_role_policy" "process_handler_dlq_sqs" {
326
358
  })
327
359
  }
328
360
 
361
+ resource "aws_iam_role_policy" "process_handler_dlq_s3" {
362
+ name = "s3-access"
363
+ role = aws_iam_role.process_handler_dlq.id
364
+
365
+ # failedレコードにownerId等のアプリ固有メタデータを含めるため、raw/{fileId}を
366
+ # HeadObjectで再取得する(process-handler-dlq.ts)。この権限が無いとHeadObjectが
367
+ # 常にAccessDeniedになり、fail-soft設計により静かに失敗し続ける
368
+ # (メタデータ無しのfailedレコードが書き込まれ、権限スコープ付きポーリングで
369
+ # 読めないままクライアントが無限ポーリングする問題が再発する)。
370
+ policy = jsonencode({
371
+ Version = "2012-10-17"
372
+ Statement = [
373
+ {
374
+ Effect = "Allow"
375
+ Action = ["s3:GetObject"]
376
+ Resource = "${aws_s3_bucket.media.arn}/raw/*"
377
+ }
378
+ ]
379
+ })
380
+ }
381
+
329
382
  resource "aws_iam_role_policy" "process_handler_dlq_dynamodb" {
330
383
  name = "dynamodb-access"
331
384
  role = aws_iam_role.process_handler_dlq.id
@@ -335,7 +388,7 @@ resource "aws_iam_role_policy" "process_handler_dlq_dynamodb" {
335
388
  Statement = [
336
389
  {
337
390
  Effect = "Allow"
338
- Action = ["dynamodb:GetItem", "dynamodb:PutItem", "dynamodb:UpdateItem", "dynamodb:BatchGetItem"]
391
+ Action = ["dynamodb:GetItem", "dynamodb:PutItem", "dynamodb:UpdateItem", "dynamodb:DeleteItem", "dynamodb:BatchGetItem"]
339
392
  Resource = [var.table_arn, "${var.table_arn}/index/*"]
340
393
  }
341
394
  ]
@@ -374,8 +427,12 @@ resource "aws_lambda_function" "process_handler_dlq" {
374
427
 
375
428
  environment {
376
429
  variables = {
377
- REGION = var.region
378
- TABLE_NAME = var.table_name
430
+ REGION = var.region
431
+ TABLE_NAME = var.table_name
432
+ SHADOW_CREATED_AT_FIELD = var.shadow_created_at_field
433
+ SHADOW_UPDATED_AT_FIELD = var.shadow_updated_at_field
434
+ SHADOW_STRING_MAX_BYTES = var.shadow_string_max_bytes
435
+ SHADOW_NUMBER_PADDING = var.shadow_number_padding
379
436
  }
380
437
  }
381
438
 
@@ -383,6 +440,7 @@ resource "aws_lambda_function" "process_handler_dlq" {
383
440
  aws_cloudwatch_log_group.process_handler_dlq,
384
441
  aws_iam_role_policy.process_handler_dlq_sqs,
385
442
  aws_iam_role_policy.process_handler_dlq_dynamodb,
443
+ aws_iam_role_policy.process_handler_dlq_s3,
386
444
  ]
387
445
 
388
446
  tags = {
@@ -534,7 +592,7 @@ resource "aws_lambda_function_url" "media_handler" {
534
592
  }
535
593
 
536
594
  resource "aws_lambda_permission" "cloudfront_invoke_media_handler" {
537
- statement_id = "AllowCloudFrontInvoke"
595
+ statement_id = "AllowCloudFrontInvokeFunctionUrl"
538
596
  action = "lambda:InvokeFunctionUrl"
539
597
  function_name = aws_lambda_function.media_handler.function_name
540
598
  principal = "cloudfront.amazonaws.com"
@@ -542,6 +600,21 @@ resource "aws_lambda_permission" "cloudfront_invoke_media_handler" {
542
600
  function_url_auth_type = "AWS_IAM"
543
601
  }
544
602
 
603
+ # AWSはFunction URL + OAC経由の呼び出しに対してDual Auth(lambda:InvokeFunctionUrl
604
+ # とlambda:InvokeFunctionの両方の許可)を正式に要求する(移行猶予期間は2026-11-01
605
+ # まで)。lambda:InvokeFunctionUrlだけの許可では、猶予期間終了後に/resize/*が
606
+ # 全件403になる(詳細: docs/adr/0002-media-design.md)。
607
+ # invoked_via_function_url=true により、この許可をFunction URL経由の呼び出しに
608
+ # 限定する(AWS推奨の条件キー)。
609
+ resource "aws_lambda_permission" "cloudfront_invoke_media_handler_function" {
610
+ statement_id = "AllowCloudFrontInvokeFunction"
611
+ action = "lambda:InvokeFunction"
612
+ function_name = aws_lambda_function.media_handler.function_name
613
+ principal = "cloudfront.amazonaws.com"
614
+ source_arn = aws_cloudfront_distribution.media.arn
615
+ invoked_via_function_url = true
616
+ }
617
+
545
618
  # ============================================================
546
619
  # CloudFront配信(署名付きURL必須、/master/*・/resize/*の2ビヘイビア)
547
620
  # ============================================================
@@ -31,6 +31,39 @@ variable "image_cache_ttl_days" {
31
31
  default = 30
32
32
  }
33
33
 
34
+ variable "allowed_upload_origins" {
35
+ description = "presigned POSTでのブラウザアップロードを許可するオリジン(例: Admin UIのドメイン)。空配列の場合はCORS設定自体を作らない(モバイルのみの利用等、ブラウザアップロードが不要な場合)"
36
+ type = list(string)
37
+ default = []
38
+ }
39
+
40
+ # 呼び出し側の既存テーブルに相乗りするため、process-handlerが生成するShadow Recordsの
41
+ # 形式は呼び出し側の既存Lambda(records等)の設定と一致させる必要がある
42
+ # (詳細: docs/media-design.md「環境変数の一致」)。既定値は本体(terraform/variables.tf)と揃える。
43
+ variable "shadow_created_at_field" {
44
+ description = "作成日時フィールド名(呼び出し側の既存設定と一致させること)"
45
+ type = string
46
+ default = "createdAt"
47
+ }
48
+
49
+ variable "shadow_updated_at_field" {
50
+ description = "更新日時フィールド名(呼び出し側の既存設定と一致させること)"
51
+ type = string
52
+ default = "updatedAt"
53
+ }
54
+
55
+ variable "shadow_string_max_bytes" {
56
+ description = "プリミティブ型の最大バイト数(呼び出し側の既存設定と一致させること)"
57
+ type = number
58
+ default = 100
59
+ }
60
+
61
+ variable "shadow_number_padding" {
62
+ description = "数値のパディング桁数(呼び出し側の既存設定と一致させること)"
63
+ type = number
64
+ default = 15
65
+ }
66
+
34
67
  variable "signing_public_key_param" {
35
68
  description = "CloudFront署名用の公開鍵(PEM形式)を保持するSSM Parameter名。値そのものは呼び出し側が別途登録する"
36
69
  type = string