From 8aa5a4079e7d99d838d26e8426873d59e2d84316 Mon Sep 17 00:00:00 2001 From: Jorel97 Date: Fri, 29 May 2026 09:21:06 -0600 Subject: [PATCH] Add AWS Comprehend PII redactor --- JS/edgechains/arakoodev/src/ai/src/index.ts | 14 + .../src/ai/src/lib/comprehend/comprehend.ts | 465 ++++++++++++++++++ .../src/tests/comprehend/comprehend.test.ts | 137 ++++++ .../jsonnet/main.jsonnet | 9 + .../aws-comprehend-redaction/package.json | 17 + .../aws-comprehend-redaction/readme.md | 12 + .../aws-comprehend-redaction/src/index.ts | 63 +++ .../aws-comprehend-redaction/tsconfig.json | 12 + 8 files changed, 729 insertions(+) create mode 100644 JS/edgechains/arakoodev/src/ai/src/lib/comprehend/comprehend.ts create mode 100644 JS/edgechains/arakoodev/src/ai/src/tests/comprehend/comprehend.test.ts create mode 100644 JS/edgechains/examples/aws-comprehend-redaction/jsonnet/main.jsonnet create mode 100644 JS/edgechains/examples/aws-comprehend-redaction/package.json create mode 100644 JS/edgechains/examples/aws-comprehend-redaction/readme.md create mode 100644 JS/edgechains/examples/aws-comprehend-redaction/src/index.ts create mode 100644 JS/edgechains/examples/aws-comprehend-redaction/tsconfig.json diff --git a/JS/edgechains/arakoodev/src/ai/src/index.ts b/JS/edgechains/arakoodev/src/ai/src/index.ts index 2c98f37dc..4b3ba6ed9 100644 --- a/JS/edgechains/arakoodev/src/ai/src/index.ts +++ b/JS/edgechains/arakoodev/src/ai/src/index.ts @@ -3,3 +3,17 @@ export { GeminiAI } from "./lib/gemini/gemini.js"; export { LlamaAI } from "./lib/llama/llama.js"; export { RetellAI } from "./lib/retell-ai/retell.js"; export { RetellWebClient } from "./lib/retell-ai/retellWebClient.js"; +export { + AWSComprehendClient, + AWSComprehendRedactor, + type AWSComprehendClientOptions, + type AWSComprehendCredentials, + type AWSComprehendRedactorOptions, + type ComprehendClient, + type ComprehendLanguageCode, + type ComprehendPiiEntity, + type ComprehendPiiType, + type ComprehendRedactionOptions, + type ComprehendRedactionResult, + type MessageLike, +} from "./lib/comprehend/comprehend.js"; diff --git a/JS/edgechains/arakoodev/src/ai/src/lib/comprehend/comprehend.ts b/JS/edgechains/arakoodev/src/ai/src/lib/comprehend/comprehend.ts new file mode 100644 index 000000000..979207e67 --- /dev/null +++ b/JS/edgechains/arakoodev/src/ai/src/lib/comprehend/comprehend.ts @@ -0,0 +1,465 @@ +import { createHash, createHmac } from "crypto"; + +export type ComprehendLanguageCode = + | "en" + | "es" + | "fr" + | "de" + | "it" + | "pt" + | "ar" + | "hi" + | "ja" + | "ko" + | "zh" + | "zh-TW"; + +export type ComprehendPiiType = + | "BANK_ACCOUNT_NUMBER" + | "BANK_ROUTING" + | "CREDIT_DEBIT_NUMBER" + | "CREDIT_DEBIT_CVV" + | "CREDIT_DEBIT_EXPIRY" + | "PIN" + | "EMAIL" + | "ADDRESS" + | "NAME" + | "PHONE" + | "SSN" + | "DATE_TIME" + | "PASSPORT_NUMBER" + | "DRIVER_ID" + | "URL" + | "AGE" + | "USERNAME" + | "PASSWORD" + | "AWS_ACCESS_KEY" + | "AWS_SECRET_KEY" + | "IP_ADDRESS" + | "MAC_ADDRESS" + | "ALL"; + +export interface ComprehendPiiEntity { + Type: ComprehendPiiType; + Score: number; + BeginOffset: number; + EndOffset: number; +} + +export interface ComprehendClient { + detectPiiEntities(options: { + Text: string; + LanguageCode: ComprehendLanguageCode; + }): Promise<{ Entities: ComprehendPiiEntity[] }>; +} + +export interface AWSComprehendCredentials { + accessKeyId?: string; + secretAccessKey?: string; + sessionToken?: string; +} + +export interface AWSComprehendClientOptions extends AWSComprehendCredentials { + region?: string; + endpoint?: string; + fetch?: FetchLike; + now?: () => Date; +} + +export interface ComprehendRedactionOptions { + languageCode?: ComprehendLanguageCode; + minScore?: number; + entityTypes?: ComprehendPiiType[]; + replacement?: + | string + | ((entity: ComprehendPiiEntity, value: string) => string); + preserveLength?: boolean; +} + +export interface ComprehendRedactionResult { + text: string; + entities: ComprehendPiiEntity[]; +} + +export interface MessageLike { + role?: string; + content: string; + [key: string]: unknown; +} + +interface FetchLike { + ( + input: string, + init: { + method: string; + headers: Record; + body: string; + }, + ): Promise<{ + ok: boolean; + status: number; + statusText: string; + text(): Promise; + json(): Promise; + }>; +} + +interface PromptOptions { + prompt?: string; + messages?: MessageLike[]; + [key: string]: unknown; +} + +export interface AWSComprehendRedactorOptions + extends ComprehendRedactionOptions, AWSComprehendClientOptions { + client?: ComprehendClient; +} + +const DEFAULT_LANGUAGE: ComprehendLanguageCode = "en"; +const DEFAULT_MIN_SCORE = 0.5; + +export class AWSComprehendClient implements ComprehendClient { + private readonly accessKeyId: string; + private readonly secretAccessKey: string; + private readonly sessionToken?: string; + private readonly region: string; + private readonly endpoint: string; + private readonly fetcher: FetchLike; + private readonly now: () => Date; + + constructor(options: AWSComprehendClientOptions = {}) { + this.accessKeyId = + options.accessKeyId || process.env.AWS_ACCESS_KEY_ID || ""; + this.secretAccessKey = + options.secretAccessKey || process.env.AWS_SECRET_ACCESS_KEY || ""; + this.sessionToken = options.sessionToken || process.env.AWS_SESSION_TOKEN; + this.region = + options.region || + process.env.AWS_REGION || + process.env.AWS_DEFAULT_REGION || + "us-east-1"; + this.endpoint = + options.endpoint || `https://comprehend.${this.region}.amazonaws.com`; + this.fetcher = options.fetch || getGlobalFetch(); + this.now = options.now || (() => new Date()); + } + + async detectPiiEntities(options: { + Text: string; + LanguageCode: ComprehendLanguageCode; + }): Promise<{ Entities: ComprehendPiiEntity[] }> { + if (!this.accessKeyId || !this.secretAccessKey) { + throw new Error( + "AWS credentials are required. Provide accessKeyId and secretAccessKey or set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY.", + ); + } + + const body = JSON.stringify(options); + const headers = this.signRequest(body); + const response = await this.fetcher(this.endpoint, { + method: "POST", + headers, + body, + }); + + if (!response.ok) { + throw new Error( + `AWS Comprehend DetectPiiEntities failed with ${response.status} ${response.statusText}: ${await response.text()}`, + ); + } + + return (await response.json()) as { Entities: ComprehendPiiEntity[] }; + } + + private signRequest(payload: string): Record { + const url = new URL(this.endpoint); + const amzDate = toAmzDate(this.now()); + const dateStamp = amzDate.slice(0, 8); + const payloadHash = hash(payload); + const host = url.host; + const target = "Comprehend_20171127.DetectPiiEntities"; + const credentialScope = `${dateStamp}/${this.region}/comprehend/aws4_request`; + + const signedHeaderEntries: Array<[string, string]> = [ + ["content-type", "application/x-amz-json-1.1"], + ["host", host], + ["x-amz-content-sha256", payloadHash], + ["x-amz-date", amzDate], + ["x-amz-target", target], + ]; + + if (this.sessionToken) { + signedHeaderEntries.push(["x-amz-security-token", this.sessionToken]); + } + + const canonicalHeaders = signedHeaderEntries + .map(([key, value]) => `${key}:${value.trim()}\n`) + .join(""); + const signedHeaders = signedHeaderEntries.map(([key]) => key).join(";"); + const canonicalRequest = [ + "POST", + url.pathname || "/", + url.searchParams.toString(), + canonicalHeaders, + signedHeaders, + payloadHash, + ].join("\n"); + + const stringToSign = [ + "AWS4-HMAC-SHA256", + amzDate, + credentialScope, + hash(canonicalRequest), + ].join("\n"); + const signingKey = getSignatureKey( + this.secretAccessKey, + dateStamp, + this.region, + "comprehend", + ); + const signature = hmacHex(signingKey, stringToSign); + const authorization = `AWS4-HMAC-SHA256 Credential=${this.accessKeyId}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`; + + const headers: Record = { + "Content-Type": "application/x-amz-json-1.1", + Host: host, + "X-Amz-Content-Sha256": payloadHash, + "X-Amz-Date": amzDate, + "X-Amz-Target": target, + Authorization: authorization, + }; + + if (this.sessionToken) { + headers["X-Amz-Security-Token"] = this.sessionToken; + } + + return headers; + } +} + +export class AWSComprehendRedactor { + private readonly client: ComprehendClient; + private readonly defaults: Required< + Pick< + ComprehendRedactionOptions, + "languageCode" | "minScore" | "preserveLength" + > + > & + Pick; + + constructor(options: AWSComprehendRedactorOptions = {}) { + this.client = options.client || new AWSComprehendClient(options); + this.defaults = { + languageCode: options.languageCode || DEFAULT_LANGUAGE, + minScore: options.minScore ?? DEFAULT_MIN_SCORE, + entityTypes: options.entityTypes, + replacement: options.replacement, + preserveLength: options.preserveLength ?? false, + }; + } + + async detectPii( + text: string, + options: ComprehendRedactionOptions = {}, + ): Promise { + const merged = this.mergeOptions(options); + if (!text) { + return []; + } + + const response = await this.client.detectPiiEntities({ + Text: text, + LanguageCode: merged.languageCode, + }); + + return response.Entities.filter((entity) => { + const passesScore = entity.Score >= merged.minScore; + const passesType = + !merged.entityTypes || + merged.entityTypes.includes("ALL") || + merged.entityTypes.includes(entity.Type); + return passesScore && passesType; + }).sort( + (a, b) => a.BeginOffset - b.BeginOffset || b.EndOffset - a.EndOffset, + ); + } + + async redact( + text: string, + options: ComprehendRedactionOptions = {}, + ): Promise { + const merged = this.mergeOptions(options); + const entities = await this.detectPii(text, merged); + const safeEntities = withoutOverlaps(entities); + let redacted = text; + + for (const entity of [...safeEntities].reverse()) { + const value = redacted.slice(entity.BeginOffset, entity.EndOffset); + const replacement = buildReplacement(entity, value, merged); + redacted = `${redacted.slice(0, entity.BeginOffset)}${replacement}${redacted.slice(entity.EndOffset)}`; + } + + return { text: redacted, entities: safeEntities }; + } + + async redactMessage( + message: T, + options: ComprehendRedactionOptions = {}, + ): Promise { + const result = await this.redact(message.content, options); + return { ...message, content: result.text }; + } + + async redactMessages( + messages: T[], + options: ComprehendRedactionOptions = {}, + ): Promise { + return Promise.all( + messages.map((message) => this.redactMessage(message, options)), + ); + } + + async redactPromptOptions( + promptOptions: T, + options: ComprehendRedactionOptions = {}, + ): Promise { + const redacted: PromptOptions = { ...promptOptions }; + + if (typeof promptOptions.prompt === "string") { + redacted.prompt = (await this.redact(promptOptions.prompt, options)).text; + } + + if (promptOptions.messages) { + redacted.messages = await this.redactMessages( + promptOptions.messages, + options, + ); + } + + return redacted as T; + } + + wrapChat }>( + endpoint: T, + ): T { + return { + ...endpoint, + chat: async (options: PromptOptions) => + endpoint.chat(await this.redactPromptOptions(options)), + }; + } + + mapText( + options: ComprehendRedactionOptions = {}, + ): (text: string) => Promise { + return async (text: string) => (await this.redact(text, options)).text; + } + + async *redactStream( + source: Iterable | AsyncIterable, + options: ComprehendRedactionOptions = {}, + ): AsyncGenerator { + for await (const chunk of source) { + yield (await this.redact(chunk, options)).text; + } + } + + private mergeOptions( + options: ComprehendRedactionOptions, + ): Required< + Pick< + ComprehendRedactionOptions, + "languageCode" | "minScore" | "preserveLength" + > + > & + Pick { + return { + languageCode: options.languageCode || this.defaults.languageCode, + minScore: options.minScore ?? this.defaults.minScore, + entityTypes: options.entityTypes || this.defaults.entityTypes, + replacement: options.replacement || this.defaults.replacement, + preserveLength: options.preserveLength ?? this.defaults.preserveLength, + }; + } +} + +function withoutOverlaps( + entities: ComprehendPiiEntity[], +): ComprehendPiiEntity[] { + const result: ComprehendPiiEntity[] = []; + let lastEnd = -1; + + for (const entity of entities) { + if ( + entity.BeginOffset >= lastEnd && + entity.EndOffset > entity.BeginOffset + ) { + result.push(entity); + lastEnd = entity.EndOffset; + } + } + + return result; +} + +function buildReplacement( + entity: ComprehendPiiEntity, + value: string, + options: Required< + Pick< + ComprehendRedactionOptions, + "languageCode" | "minScore" | "preserveLength" + > + > & + Pick, +): string { + if (typeof options.replacement === "function") { + return options.replacement(entity, value); + } + + if (options.preserveLength) { + return (options.replacement || "*") + .repeat(value.length) + .slice(0, value.length); + } + + return options.replacement || `[REDACTED_${entity.Type}]`; +} + +function getGlobalFetch(): FetchLike { + if (!globalThis.fetch) { + throw new Error( + "A fetch implementation is required to call AWS Comprehend.", + ); + } + + return globalThis.fetch as unknown as FetchLike; +} + +function toAmzDate(date: Date): string { + return date.toISOString().replace(/[:-]|\.\d{3}/g, ""); +} + +function hash(value: string): string { + return createHash("sha256").update(value, "utf8").digest("hex"); +} + +function hmac(key: Buffer | string, value: string): Buffer { + return createHmac("sha256", key).update(value, "utf8").digest(); +} + +function hmacHex(key: Buffer, value: string): string { + return createHmac("sha256", key).update(value, "utf8").digest("hex"); +} + +function getSignatureKey( + key: string, + dateStamp: string, + region: string, + service: string, +): Buffer { + const kDate = hmac(`AWS4${key}`, dateStamp); + const kRegion = hmac(kDate, region); + const kService = hmac(kRegion, service); + return hmac(kService, "aws4_request"); +} diff --git a/JS/edgechains/arakoodev/src/ai/src/tests/comprehend/comprehend.test.ts b/JS/edgechains/arakoodev/src/ai/src/tests/comprehend/comprehend.test.ts new file mode 100644 index 000000000..ccfbda050 --- /dev/null +++ b/JS/edgechains/arakoodev/src/ai/src/tests/comprehend/comprehend.test.ts @@ -0,0 +1,137 @@ +import { describe, expect, it, vi } from "vitest"; +import { + AWSComprehendClient, + AWSComprehendRedactor, + type ComprehendClient, + type ComprehendPiiEntity, +} from "../../lib/comprehend/comprehend.js"; + +const entities: ComprehendPiiEntity[] = [ + { Type: "EMAIL", Score: 0.99, BeginOffset: 14, EndOffset: 28 }, + { Type: "PHONE", Score: 0.93, BeginOffset: 32, EndOffset: 44 }, +]; + +function mockClient(returnedEntities = entities): ComprehendClient { + return { + detectPiiEntities: vi.fn(async () => ({ Entities: returnedEntities })), + }; +} + +describe("AWSComprehendRedactor", () => { + it("redacts detected PII with typed placeholders", async () => { + const redactor = new AWSComprehendRedactor({ client: mockClient() }); + + const result = await redactor.redact( + "Contact Jane: jane@site.test or 555-123-4567", + ); + + expect(result.text).toBe( + "Contact Jane: [REDACTED_EMAIL] or [REDACTED_PHONE]", + ); + expect(result.entities).toHaveLength(2); + }); + + it("filters by confidence and entity type", async () => { + const redactor = new AWSComprehendRedactor({ + client: mockClient([ + { Type: "EMAIL", Score: 0.99, BeginOffset: 0, EndOffset: 14 }, + { Type: "PHONE", Score: 0.42, BeginOffset: 19, EndOffset: 31 }, + { Type: "NAME", Score: 0.98, BeginOffset: 36, EndOffset: 40 }, + ]), + minScore: 0.9, + entityTypes: ["EMAIL"], + }); + + const result = await redactor.redact( + "jane@site.test and 555-123-4567 for Jane", + ); + + expect(result.text).toBe("[REDACTED_EMAIL] and 555-123-4567 for Jane"); + expect(result.entities.map((entity) => entity.Type)).toEqual(["EMAIL"]); + }); + + it("redacts prompts and messages without mutating the original options", async () => { + const client = mockClient([ + { Type: "EMAIL", Score: 0.99, BeginOffset: 6, EndOffset: 20 }, + ]); + const redactor = new AWSComprehendRedactor({ client }); + const options = { + prompt: "Email jane@site.test before sending the summary.", + messages: [{ role: "user", content: "Email jane@site.test" }], + temperature: 0.2, + }; + + const result = await redactor.redactPromptOptions(options); + + expect(result.prompt).toBe( + "Email [REDACTED_EMAIL] before sending the summary.", + ); + expect(result.messages?.[0].content).toBe("Email [REDACTED_EMAIL]"); + expect(options.messages[0].content).toBe("Email jane@site.test"); + }); + + it("wraps chat endpoints and forwards redacted options", async () => { + const redactor = new AWSComprehendRedactor({ + client: mockClient([ + { Type: "EMAIL", Score: 0.99, BeginOffset: 6, EndOffset: 20 }, + ]), + }); + const endpoint = { + chat: vi.fn(async (options: { prompt: string }) => ({ + content: options.prompt, + })), + }; + + const wrapped = redactor.wrapChat(endpoint); + const response = await wrapped.chat({ prompt: "Email jane@site.test" }); + + expect(endpoint.chat).toHaveBeenCalledWith({ + prompt: "Email [REDACTED_EMAIL]", + }); + expect(response).toEqual({ content: "Email [REDACTED_EMAIL]" }); + }); + + it("signs DetectPiiEntities requests for AWS Comprehend", async () => { + const fetch = vi.fn( + async ( + _url: string, + init: { headers: Record; body: string }, + ) => ({ + ok: true, + status: 200, + statusText: "OK", + text: async () => "", + json: async () => ({ Entities: [] }), + init, + }), + ); + const client = new AWSComprehendClient({ + accessKeyId: "AKIA_TEST", + secretAccessKey: "secret", + region: "us-east-1", + endpoint: "https://comprehend.us-east-1.amazonaws.com", + now: () => new Date("2026-05-29T12:00:00.000Z"), + fetch, + }); + + await client.detectPiiEntities({ + Text: "hello@example.com", + LanguageCode: "en", + }); + + const [, init] = fetch.mock.calls[0]; + expect(JSON.parse(init.body)).toEqual({ + Text: "hello@example.com", + LanguageCode: "en", + }); + expect(init.headers["X-Amz-Target"]).toBe( + "Comprehend_20171127.DetectPiiEntities", + ); + expect(init.headers.Authorization).toContain( + "AWS4-HMAC-SHA256 Credential=AKIA_TEST/20260529/us-east-1/comprehend/aws4_request", + ); + expect(init.headers.Authorization).toContain( + "SignedHeaders=content-type;host;x-amz-content-sha256;x-amz-date;x-amz-target", + ); + }); +}); diff --git a/JS/edgechains/examples/aws-comprehend-redaction/jsonnet/main.jsonnet b/JS/edgechains/examples/aws-comprehend-redaction/jsonnet/main.jsonnet new file mode 100644 index 000000000..cf1abf935 --- /dev/null +++ b/JS/edgechains/examples/aws-comprehend-redaction/jsonnet/main.jsonnet @@ -0,0 +1,9 @@ +local supportPrompt = ||| + Please summarize this support note after privacy redaction: + Customer Jane Doe can be reached at jane@site.test or 555-123-4567. +|||; + +{ + prompt: supportPrompt, + languageCode: "en", +} diff --git a/JS/edgechains/examples/aws-comprehend-redaction/package.json b/JS/edgechains/examples/aws-comprehend-redaction/package.json new file mode 100644 index 000000000..4db714faa --- /dev/null +++ b/JS/edgechains/examples/aws-comprehend-redaction/package.json @@ -0,0 +1,17 @@ +{ + "name": "aws-comprehend-redaction", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "build": "tsc", + "dev": "npm run build && node dist/index.js" + }, + "dependencies": { + "@arakoodev/edgechains.js": "file:../../arakoodev", + "@arakoodev/jsonnet": "^0.25.0", + "file-uri-to-path": "^2.0.0", + "ts-node": "^10.9.2", + "typescript": "^5.6.3" + } +} diff --git a/JS/edgechains/examples/aws-comprehend-redaction/readme.md b/JS/edgechains/examples/aws-comprehend-redaction/readme.md new file mode 100644 index 000000000..1e7a56e28 --- /dev/null +++ b/JS/edgechains/examples/aws-comprehend-redaction/readme.md @@ -0,0 +1,12 @@ +# AWS Comprehend Redaction + +This example uses `AWSComprehendRedactor` to remove PII from prompt text before it is sent to an AI endpoint. The prompt is stored in Jsonnet and the TypeScript entrypoint registers a `redactText` native callback. + +The sample injects a mock Comprehend client so it can run locally without AWS credentials. In production, remove the injected `client` and provide `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, and `AWS_REGION`. + +```bash +npm install +npm run dev +``` + +The output shows direct prompt redaction and a wrapped chat endpoint receiving the protected prompt. diff --git a/JS/edgechains/examples/aws-comprehend-redaction/src/index.ts b/JS/edgechains/examples/aws-comprehend-redaction/src/index.ts new file mode 100644 index 000000000..d76b125fd --- /dev/null +++ b/JS/edgechains/examples/aws-comprehend-redaction/src/index.ts @@ -0,0 +1,63 @@ +import Jsonnet from "@arakoodev/jsonnet"; +import { + AWSComprehendRedactor, + type ComprehendClient, + type ComprehendPiiEntity, +} from "@arakoodev/edgechains.js/ai"; +import fileURLToPath from "file-uri-to-path"; +import path from "path"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +const mockComprehend: ComprehendClient = { + async detectPiiEntities({ Text }) { + const entities = [ + { Type: "NAME" as const, value: "Jane Doe", Score: 0.99 }, + { Type: "EMAIL" as const, value: "jane@site.test", Score: 0.99 }, + { Type: "PHONE" as const, value: "555-123-4567", Score: 0.96 }, + ] + .map((entity): ComprehendPiiEntity | undefined => { + const start = Text.indexOf(entity.value); + return start === -1 + ? undefined + : { + Type: entity.Type, + Score: entity.Score, + BeginOffset: start, + EndOffset: start + entity.value.length, + }; + }) + .filter((entity): entity is ComprehendPiiEntity => Boolean(entity)); + + return { Entities: entities }; + }, +}; + +const redactor = new AWSComprehendRedactor({ + client: mockComprehend, + minScore: 0.9, +}); +const jsonnet = new Jsonnet(); + +async function run() { + const response = jsonnet.evaluateFile( + path.join(__dirname, "../jsonnet/main.jsonnet"), + ); + const parsed = JSON.parse(response); + const redaction = await redactor.redact(parsed.prompt, { + languageCode: parsed.languageCode, + }); + const protectedEndpoint = redactor.wrapChat({ + async chat({ prompt }: { prompt: string }) { + return { content: `Safe prompt received:\n${prompt}` }; + }, + }); + + const chatResponse = await protectedEndpoint.chat({ prompt: parsed.prompt }); + console.log(JSON.stringify({ ...parsed, redaction, chatResponse }, null, 2)); +} + +run().catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/JS/edgechains/examples/aws-comprehend-redaction/tsconfig.json b/JS/edgechains/examples/aws-comprehend-redaction/tsconfig.json new file mode 100644 index 000000000..0b21ba250 --- /dev/null +++ b/JS/edgechains/examples/aws-comprehend-redaction/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "esModuleInterop": true, + "strict": true, + "skipLibCheck": true, + "rootDir": "./src", + "outDir": "./dist" + } +}