node-s3tables 0.0.1

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/README.md ADDED
@@ -0,0 +1 @@
1
+ # node-s3tables
@@ -0,0 +1,89 @@
1
+ import { AwsCredentialIdentity } from '@aws-sdk/types';
2
+ import { S3TablesClientConfig } from '@aws-sdk/client-s3tables';
3
+
4
+ type IcebergTransform = 'identity' | 'year' | 'month' | 'day' | 'hour' | `bucket[${number}]` | `truncate[${number}]`;
5
+ interface IcebergPartitionField {
6
+ 'field-id': number;
7
+ name: string;
8
+ 'source-id': number;
9
+ transform: IcebergTransform;
10
+ }
11
+ type IcebergPrimitiveType = 'boolean' | 'int' | 'long' | 'float' | 'double' | 'date' | 'time' | 'timestamp' | 'timestamptz' | 'string' | 'uuid' | 'binary' | `decimal(${number},${number})` | `fixed[${number}]`;
12
+ type IcebergComplexType = {
13
+ type: 'list';
14
+ element: IcebergType;
15
+ 'element-required': boolean;
16
+ } | {
17
+ type: 'map';
18
+ key: IcebergType;
19
+ value: IcebergType;
20
+ 'value-required': boolean;
21
+ } | {
22
+ type: 'struct';
23
+ fields: IcebergSchemaField[];
24
+ };
25
+ type IcebergType = IcebergPrimitiveType | IcebergComplexType;
26
+ interface IcebergSchemaField {
27
+ id: number;
28
+ name: string;
29
+ type: IcebergType;
30
+ required: boolean;
31
+ doc?: string;
32
+ }
33
+ interface IcebergSchema {
34
+ type: 'struct';
35
+ 'schema-id': number;
36
+ fields: IcebergSchemaField[];
37
+ }
38
+ interface IcebergPartitionSpec {
39
+ 'spec-id': number;
40
+ fields: IcebergPartitionField[];
41
+ }
42
+ interface IcebergMetadata {
43
+ 'last-column-id': number;
44
+ 'current-schema-id': number;
45
+ schemas: IcebergSchema[];
46
+ 'default-spec-id': number;
47
+ 'partition-specs': IcebergPartitionSpec[];
48
+ 'last-partition-id': number;
49
+ 'current-snapshot-id': number;
50
+ }
51
+
52
+ type TableLocation = {
53
+ tableArn: string;
54
+ } | {
55
+ tableBucketARN: string;
56
+ namespace: string;
57
+ name: string;
58
+ };
59
+ type GetMetadataParams = TableLocation & {
60
+ config?: S3TablesClientConfig;
61
+ };
62
+ declare function getMetadata(params: GetMetadataParams): Promise<IcebergMetadata>;
63
+ interface AddSchemaParams {
64
+ credentials?: AwsCredentialIdentity;
65
+ tableBucketARN: string;
66
+ namespace: string;
67
+ name: string;
68
+ schemaId: number;
69
+ fields: IcebergSchemaField[];
70
+ }
71
+ declare function addSchema(params: AddSchemaParams): Promise<string>;
72
+ interface AddPartitionSpecParams {
73
+ credentials?: AwsCredentialIdentity;
74
+ tableBucketARN: string;
75
+ namespace: string;
76
+ name: string;
77
+ specId: number;
78
+ fields: IcebergPartitionField[];
79
+ }
80
+ declare function addPartitionSpec(params: AddPartitionSpecParams): Promise<string>;
81
+
82
+ declare const _default: {
83
+ getMetadata: typeof getMetadata;
84
+ addSchema: typeof addSchema;
85
+ addPartitionSpec: typeof addPartitionSpec;
86
+ };
87
+
88
+ export { addPartitionSpec, addSchema, _default as default, getMetadata };
89
+ export type { AddPartitionSpecParams, AddSchemaParams, GetMetadataParams, IcebergComplexType, IcebergMetadata, IcebergPartitionField, IcebergPartitionSpec, IcebergPrimitiveType, IcebergSchema, IcebergSchemaField, IcebergTransform, IcebergType, TableLocation };
package/dist/index.js ADDED
@@ -0,0 +1,130 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var clientS3 = require('@aws-sdk/client-s3');
6
+ var clientS3tables = require('@aws-sdk/client-s3tables');
7
+ var signatureV4 = require('@smithy/signature-v4');
8
+ var sha256Js = require('@aws-crypto/sha256-js');
9
+ var protocolHttp = require('@smithy/protocol-http');
10
+ var credentialProviderNode = require('@aws-sdk/credential-provider-node');
11
+
12
+ async function icebergRequest(params) {
13
+ const region = params.tableBucketARN.split(':')[3];
14
+ if (!region) {
15
+ throw new Error('bad tableBucketARN');
16
+ }
17
+ const arn = encodeURIComponent(params.tableBucketARN);
18
+ const hostname = `s3tables.${region}.amazonaws.com`;
19
+ const full_path = `/iceberg/v1/${arn}${params.suffix}`;
20
+ const body = params.body ? JSON.stringify(params.body) : null;
21
+ const req_opts = {
22
+ method: params.method ?? 'GET',
23
+ protocol: 'https:',
24
+ path: full_path,
25
+ hostname,
26
+ headers: { host: hostname },
27
+ };
28
+ if (body && req_opts.headers) {
29
+ req_opts.body = body;
30
+ req_opts.headers['content-type'] = 'application/json';
31
+ req_opts.headers['content-length'] = String(Buffer.byteLength(body));
32
+ }
33
+ const request = new protocolHttp.HttpRequest(req_opts);
34
+ const signer = new signatureV4.SignatureV4({
35
+ credentials: params.credentials ?? credentialProviderNode.defaultProvider(),
36
+ region,
37
+ service: 's3tables',
38
+ sha256: sha256Js.Sha256,
39
+ });
40
+ const signed = await signer.sign(request);
41
+ const url = `https://${hostname}${signed.path}`;
42
+ const fetch_opts = {
43
+ method: signed.method,
44
+ headers: signed.headers,
45
+ };
46
+ if (signed.body) {
47
+ fetch_opts.body = signed.body;
48
+ }
49
+ const res = await fetch(url, fetch_opts);
50
+ if (!res.ok) {
51
+ throw new Error(`request failed: ${res.status} ${res.statusText}`);
52
+ }
53
+ return (await res.json());
54
+ }
55
+
56
+ async function getMetadata(params) {
57
+ const { config, ...other } = params;
58
+ const client = new clientS3tables.S3TablesClient(config ?? {});
59
+ const get_table_cmd = new clientS3tables.GetTableCommand(other);
60
+ const response = await client.send(get_table_cmd);
61
+ if (!response.metadataLocation) {
62
+ throw new Error('missing metadataLocation');
63
+ }
64
+ const s3_client = new clientS3.S3Client(config ?? {});
65
+ const { key, bucket } = _parseS3Url(response.metadataLocation);
66
+ const get_file_cmd = new clientS3.GetObjectCommand({ Bucket: bucket, Key: key });
67
+ const file_response = await s3_client.send(get_file_cmd);
68
+ const body = await file_response.Body?.transformToString();
69
+ if (!body) {
70
+ throw new Error('missing body');
71
+ }
72
+ return JSON.parse(body);
73
+ }
74
+ async function addSchema(params) {
75
+ return icebergRequest({
76
+ tableBucketARN: params.tableBucketARN,
77
+ method: 'POST',
78
+ suffix: `/namespaces/${params.namespace}/tables/${params.name}`,
79
+ body: {
80
+ requirements: [],
81
+ updates: [
82
+ {
83
+ action: 'add-schema',
84
+ schema: {
85
+ 'schema-id': params.schemaId,
86
+ type: 'struct',
87
+ fields: params.fields,
88
+ },
89
+ },
90
+ { action: 'set-current-schema', 'schema-id': params.schemaId },
91
+ ],
92
+ },
93
+ });
94
+ }
95
+ async function addPartitionSpec(params) {
96
+ return icebergRequest({
97
+ tableBucketARN: params.tableBucketARN,
98
+ method: 'POST',
99
+ suffix: `/namespaces/${params.namespace}/tables/${params.name}`,
100
+ body: {
101
+ requirements: [],
102
+ updates: [
103
+ {
104
+ action: 'add-spec',
105
+ spec: {
106
+ 'spec-id': params.specId,
107
+ type: 'struct',
108
+ fields: params.fields,
109
+ },
110
+ },
111
+ { action: 'set-default-spec', 'spec-id': params.specId },
112
+ ],
113
+ },
114
+ });
115
+ }
116
+ const S3_REGEX = /^s3:\/\/([^/]+)\/(.+)$/;
117
+ function _parseS3Url(url) {
118
+ const match = S3_REGEX.exec(url);
119
+ if (!match) {
120
+ throw new Error('Invalid S3 URL');
121
+ }
122
+ return { bucket: match[1], key: match[2] };
123
+ }
124
+
125
+ var index = { getMetadata, addSchema, addPartitionSpec };
126
+
127
+ exports.addPartitionSpec = addPartitionSpec;
128
+ exports.addSchema = addSchema;
129
+ exports.default = index;
130
+ exports.getMetadata = getMetadata;
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "node-s3tables",
3
+ "version": "0.0.1",
4
+ "description": "node api for dealing with s3tables",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist/**/*"
9
+ ],
10
+ "scripts": {
11
+ "build": "rollup -c",
12
+ "ts-check": "tsc --noEmit",
13
+ "lint": "eslint src test",
14
+ "pretty": "prettier -u --write \"**/*\" --log-level warn",
15
+ "test": "tsx --test test/*.test.ts",
16
+ "test:cover": "tsx --test --experimental-test-coverage test/*.test.ts"
17
+ },
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git@github.com:jim-lake/node-mysql-cron.git"
21
+ },
22
+ "author": {
23
+ "name": "Jim Lake"
24
+ },
25
+ "license": "MIT",
26
+ "dependencies": {
27
+ "@aws-sdk/client-s3": "^3.899.0",
28
+ "@aws-sdk/client-s3tables": "^3.899.0",
29
+ "avro-js": "^1.12.0"
30
+ },
31
+ "devDependencies": {
32
+ "@rollup/plugin-commonjs": "^28.0.6",
33
+ "@rollup/plugin-node-resolve": "^16.0.1",
34
+ "@rollup/plugin-typescript": "^12.1.4",
35
+ "@types/node": "^24.6.0",
36
+ "eslint": "^9.36.0",
37
+ "eslint-plugin-import": "^2.32.0",
38
+ "rollup": "^4.52.3",
39
+ "rollup-plugin-dts": "^6.2.3",
40
+ "tslib": "^2.8.1",
41
+ "tsx": "^4.20.6",
42
+ "typescript": "^5.9.2",
43
+ "typescript-eslint": "^8.45.0"
44
+ }
45
+ }