@aspan-corporation/ac-shared 1.2.26 → 1.2.27

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.
@@ -1,6 +1,6 @@
1
1
  import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
2
2
  import { AssumeRoleCommandOutput } from "@aws-sdk/client-sts";
3
- import { DynamoDBDocumentClient, GetCommandInput, GetCommandOutput, PutCommandInput, PutCommandOutput, QueryCommandInput, QueryCommandOutput, UpdateCommandInput, UpdateCommandOutput, ScanCommandInput, ScanCommandOutput, BatchWriteCommandInput, BatchWriteCommandOutput } from "@aws-sdk/lib-dynamodb";
3
+ import { DynamoDBDocumentClient, GetCommandInput, GetCommandOutput, PutCommandInput, PutCommandOutput, QueryCommandInput, QueryCommandOutput, UpdateCommandInput, UpdateCommandOutput, ScanCommandInput, ScanCommandOutput, BatchWriteCommandInput, BatchWriteCommandOutput, BatchGetCommandInput, BatchGetCommandOutput } from "@aws-sdk/lib-dynamodb";
4
4
  import { Logger } from "@aws-lambda-powertools/logger";
5
5
  export declare class DynamoDBService {
6
6
  logger: Logger;
@@ -19,6 +19,7 @@ export declare class DynamoDBService {
19
19
  checkIfItemExists(checkInput: Pick<GetCommandInput, "TableName" | "Key">): Promise<boolean>;
20
20
  scanCommand(scanCommandInput: ScanCommandInput): Promise<ScanCommandOutput>;
21
21
  batchWriteCommand(batchWriteCommandInput: BatchWriteCommandInput): Promise<BatchWriteCommandOutput>;
22
+ batchGetCommand(batchGetCommandInput: BatchGetCommandInput): Promise<BatchGetCommandOutput>;
22
23
  }
23
24
  export type { PutCommandOutput } from "@aws-sdk/lib-dynamodb";
24
25
  export type { BatchWriteCommandOutput } from "@aws-sdk/lib-dynamodb";
@@ -27,6 +28,8 @@ export type { QueryCommandOutput } from "@aws-sdk/lib-dynamodb";
27
28
  export type { GetCommandOutput } from "@aws-sdk/lib-dynamodb";
28
29
  export type { UpdateCommandOutput } from "@aws-sdk/lib-dynamodb";
29
30
  export type { BatchWriteCommandInput } from "@aws-sdk/lib-dynamodb";
31
+ export type { BatchGetCommandInput } from "@aws-sdk/lib-dynamodb";
32
+ export type { BatchGetCommandOutput } from "@aws-sdk/lib-dynamodb";
30
33
  export type { PutCommandInput } from "@aws-sdk/lib-dynamodb";
31
34
  export type { ScanCommandInput } from "@aws-sdk/lib-dynamodb";
32
35
  export type { QueryCommandInput } from "@aws-sdk/lib-dynamodb";
@@ -1,5 +1,5 @@
1
1
  import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
2
- import { DynamoDBDocumentClient, GetCommand, PutCommand, QueryCommand, UpdateCommand, ScanCommand, BatchWriteCommand } from "@aws-sdk/lib-dynamodb";
2
+ import { DynamoDBDocumentClient, GetCommand, PutCommand, QueryCommand, UpdateCommand, ScanCommand, BatchWriteCommand, BatchGetCommand } from "@aws-sdk/lib-dynamodb";
3
3
  import assert from "node:assert/strict";
4
4
  import { getObjectWithAssumeRoleCommandOutputAttribute } from "../utils/index.js";
5
5
  export class DynamoDBService {
@@ -42,4 +42,7 @@ export class DynamoDBService {
42
42
  async batchWriteCommand(batchWriteCommandInput) {
43
43
  return await this.client.send(new BatchWriteCommand(batchWriteCommandInput));
44
44
  }
45
+ async batchGetCommand(batchGetCommandInput) {
46
+ return await this.client.send(new BatchGetCommand(batchGetCommandInput));
47
+ }
45
48
  }
@@ -3,3 +3,4 @@ export * from "./normalizeErrorMessage.js";
3
3
  export * from "./thumbsKey.js";
4
4
  export * from "./helpers.js";
5
5
  export * from "./processMeta.js";
6
+ export * from "./parseFolderDate.js";
@@ -7,3 +7,4 @@ export * from "./normalizeErrorMessage.js";
7
7
  export * from "./thumbsKey.js";
8
8
  export * from "./helpers.js";
9
9
  export * from "./processMeta.js";
10
+ export * from "./parseFolderDate.js";
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Parse a date prefix from a folder name.
3
+ *
4
+ * Recognises:
5
+ * YYYYMMDD[non-digit...] e.g. "20100321. Playground"
6
+ * YYYY-MM-DD[...] e.g. "2024-08-15 Vacation"
7
+ * YYYY.MM.DD[...] e.g. "2024.08.15 Vacation"
8
+ *
9
+ * Folder ids may be passed as the full path ("media/2024/08/15/foo/") or
10
+ * just the bare segment — we always extract the last non-empty segment first.
11
+ *
12
+ * Returns:
13
+ * { year, month1, day, isoDate } where month1 is 1-indexed (1=Jan, 12=Dec).
14
+ * null if the segment doesn't start with a recognisable date.
15
+ *
16
+ * The previous implementation in `processMeta.ts` had a bug rejecting January
17
+ * (it checked `month0 < 1`, where month0 was 0-indexed). It also constructed
18
+ * dates in local time, which differs by region. This implementation:
19
+ * - validates 1 ≤ month ≤ 12 and 1 ≤ day ≤ 31 (so January is included)
20
+ * - emits a UTC ISO timestamp via Date.UTC, eliminating timezone drift
21
+ * - returns 1-indexed month for consumer convenience
22
+ */
23
+ export type ParsedFolderDate = {
24
+ year: number;
25
+ month1: number;
26
+ day: number;
27
+ isoDate: string;
28
+ };
29
+ export declare const parseFolderDate: (id: string) => ParsedFolderDate | null;
@@ -0,0 +1,25 @@
1
+ const COMPACT = /^(\d{4})(\d{2})(\d{2})/;
2
+ const SEPARATED = /^(\d{4})[-.](\d{2})[-.](\d{2})/;
3
+ export const parseFolderDate = (id) => {
4
+ // Last non-empty path segment
5
+ const segment = id.replace(/\/$/, "").split("/").pop() ?? "";
6
+ if (!segment)
7
+ return null;
8
+ const match = COMPACT.exec(segment) ?? SEPARATED.exec(segment);
9
+ if (!match)
10
+ return null;
11
+ const year = Number(match[1]);
12
+ const month1 = Number(match[2]);
13
+ const day = Number(match[3]);
14
+ if (Number.isNaN(year) || Number.isNaN(month1) || Number.isNaN(day))
15
+ return null;
16
+ if (year < 1900 || year > 2100)
17
+ return null;
18
+ if (month1 < 1 || month1 > 12)
19
+ return null;
20
+ if (day < 1 || day > 31)
21
+ return null;
22
+ // UTC midnight to avoid timezone drift in toISOString().
23
+ const isoDate = new Date(Date.UTC(year, month1 - 1, day)).toISOString();
24
+ return { year, month1, day, isoDate };
25
+ };
@@ -1,5 +1,5 @@
1
- import assert from "node:assert/strict";
2
1
  import { getKeyExtension } from "./thumbsKey.js";
2
+ import { parseFolderDate } from "./parseFolderDate.js";
3
3
  /**
4
4
  * 1. read <id>
5
5
  * 2. if <id> doesn't exist then use PutCommand
@@ -125,38 +125,26 @@ export const deriveFolder = (id) => {
125
125
  return "/";
126
126
  return trimmed.slice(0, lastSlash + 1);
127
127
  };
128
- const SUBSTRING_ANSI_DATES_BEGIN_WITH = "20";
128
+ /**
129
+ * Derive date tags from the parent folder's name when it begins with a
130
+ * recognised date prefix (YYYYMMDD, YYYY-MM-DD, YYYY.MM.DD).
131
+ *
132
+ * Operates on the parent folder of the given key — for "media/20240815/photo.jpg"
133
+ * it parses "20240815". The previous implementation skipped January due to
134
+ * a 0-vs-1-indexed month bug; that has been fixed in `parseFolderDate`.
135
+ */
129
136
  const extractMetaFromKey = (key) => {
130
137
  if (!key)
131
138
  return [];
132
- let firstToken = undefined;
133
- try {
134
- const folder = key.split("/").at(-2);
135
- assert(typeof folder === "string");
136
- firstToken = folder.split(".")[0];
137
- }
138
- catch (error) { }
139
- if (firstToken === undefined ||
140
- firstToken.length !== 8 ||
141
- !firstToken.startsWith(SUBSTRING_ANSI_DATES_BEGIN_WITH))
139
+ // Look at the parent folder, not the file itself.
140
+ const parent = key.endsWith("/") ? key : key.slice(0, key.lastIndexOf("/") + 1);
141
+ const parsed = parseFolderDate(parent);
142
+ if (!parsed)
142
143
  return [];
143
- const year = Number(firstToken.substring(0, 4));
144
- const month = Number(firstToken.substring(4, 6)) - 1;
145
- const day = Number(firstToken.substring(6));
146
- if (isNaN(year) || isNaN(month) || isNaN(day))
147
- return [];
148
- // mon starts from 0
149
- // day starts from 1
150
- if (year < 2000 || month < 1 || month > 11 || day < 1 || day > 31)
151
- return [];
152
- const dateCreatedBin = new Date(year, month, day);
153
144
  return [
154
- { key: "dateCreated", value: dateCreatedBin.toISOString() },
155
- { key: "yearCreated", value: dateCreatedBin.getFullYear().toString() },
156
- { key: "dayCreated", value: dateCreatedBin.getDate().toString() },
157
- {
158
- key: "monthCreated",
159
- value: (dateCreatedBin.getMonth() + 1).toString()
160
- }
145
+ { key: "dateCreated", value: parsed.isoDate },
146
+ { key: "yearCreated", value: String(parsed.year) },
147
+ { key: "dayCreated", value: String(parsed.day) },
148
+ { key: "monthCreated", value: String(parsed.month1) },
161
149
  ];
162
150
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aspan-corporation/ac-shared",
3
- "version": "1.2.26",
3
+ "version": "1.2.27",
4
4
  "description": "",
5
5
  "keywords": [],
6
6
  "exports": {