wirejs-resources 0.1.34-table-resource → 0.1.35-table-resource
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.d.ts +1 -1
- package/dist/resources/distributed-table.d.ts +25 -8
- package/dist/resources/distributed-table.js +25 -7
- package/package.json +1 -1
- package/dist/hosting/client.d.ts +0 -1
- package/dist/hosting/client.js +0 -68
- package/dist/internal/client.d.ts +0 -1
- package/dist/internal/client.js +0 -68
- package/dist/setup/index.d.ts +0 -1
- package/dist/setup/index.js +0 -27
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,6 @@ export { withContext, requiresContext, Context, ContextWrapped } from './adapter
|
|
|
6
6
|
export { Resource } from './resource.js';
|
|
7
7
|
export { overrides } from './overrides.js';
|
|
8
8
|
export { Secret } from './resources/secret.js';
|
|
9
|
-
export { DistributedTable, DistributedTableOptions, Filter, FieldComparison, FieldComparisonOptions, FieldType, Parser, PassThruParser, RecordType, RecordKey, matchesFilter, } from './resources/distributed-table.js';
|
|
9
|
+
export { AnyKeyFieldOption, DistributedTable, DistributedTableOptions, Filter, FieldComparison, FieldComparisonOptions, FieldType, KeyFieldDefinition, KeyFieldOptions, Parser, PassThruParser, PKFieldTypes, RecordType, RecordKey, matchesFilter, } from './resources/distributed-table.js';
|
|
10
10
|
export * from './types.js';
|
|
11
11
|
export * from './derived-types.js';
|
|
@@ -1,14 +1,29 @@
|
|
|
1
1
|
import { Resource } from '../resource.js';
|
|
2
|
+
export type KindaPretty<T> = T extends object ? {
|
|
3
|
+
[K in keyof T]: KindaPretty<T[K]>;
|
|
4
|
+
} : T;
|
|
5
|
+
export type PKFieldTypes = 'string' | 'number';
|
|
2
6
|
export type DistributedTableOptions<T> = {
|
|
3
7
|
parse: Parser<T>;
|
|
4
8
|
key: {
|
|
5
|
-
partition:
|
|
6
|
-
sort?:
|
|
9
|
+
partition: AnyKeyFieldOption<T>;
|
|
10
|
+
sort?: AnyKeyFieldOption<T>;
|
|
7
11
|
};
|
|
8
12
|
};
|
|
13
|
+
export type KeyFieldOptions<T> = {
|
|
14
|
+
[K in keyof T as K extends string ? K : never]: K extends string ? KeyFieldDefinition<T, K, T[K]> : never;
|
|
15
|
+
};
|
|
16
|
+
export type AnyKeyFieldOption<T> = KeyFieldOptions<T>[keyof KeyFieldOptions<T>];
|
|
17
|
+
export type KeyFieldDefinition<T, FieldName extends string & keyof T, FieldType> = FieldType extends number ? {
|
|
18
|
+
field: FieldName & keyof T;
|
|
19
|
+
type: 'number';
|
|
20
|
+
} : {
|
|
21
|
+
field: FieldName & keyof T;
|
|
22
|
+
type: 'string';
|
|
23
|
+
};
|
|
9
24
|
export type Parser<T> = (record: Record<string, any>) => T;
|
|
10
25
|
export type RecordType<T extends DistributedTableOptions<any>> = T extends DistributedTableOptions<infer IT> ? IT : Record<string, any>;
|
|
11
|
-
export type RecordKey<T, PK extends
|
|
26
|
+
export type RecordKey<T, PK extends AnyKeyFieldOption<T>, SK extends AnyKeyFieldOption<T> | undefined> = Pick<T, PK['field'] | (SK extends Record<string, any> ? (SK extends undefined ? never : SK['field']) : never)>;
|
|
12
27
|
export type FieldType<T> = undefined extends T ? (T | null) : T;
|
|
13
28
|
export type FieldComparisonOptions<T> = {
|
|
14
29
|
[K in keyof T & string]?: {
|
|
@@ -55,7 +70,7 @@ export declare function PassThruParser<T>(record: Record<string, any>): T;
|
|
|
55
70
|
*
|
|
56
71
|
* High cardinality, non-sequential partition keys allow for the best overall scaling.
|
|
57
72
|
*/
|
|
58
|
-
export declare class DistributedTable<const P extends Parser<any>, const T extends ReturnType<P
|
|
73
|
+
export declare class DistributedTable<const P extends Parser<any>, const T extends KindaPretty<ReturnType<P>>, const PK extends AnyKeyFieldOption<T>, const SK extends AnyKeyFieldOption<T> | undefined> extends Resource {
|
|
59
74
|
#private;
|
|
60
75
|
parse: P;
|
|
61
76
|
partitionKey: PK;
|
|
@@ -67,15 +82,17 @@ export declare class DistributedTable<const P extends Parser<any>, const T exten
|
|
|
67
82
|
sort?: SK;
|
|
68
83
|
};
|
|
69
84
|
});
|
|
85
|
+
get partitionKeyName(): PK['field'];
|
|
86
|
+
get sortKeyName(): 'field' extends keyof SK ? SK['field'] : undefined;
|
|
70
87
|
save(item: T): Promise<void>;
|
|
71
88
|
saveMany(items: T[]): Promise<void>;
|
|
72
|
-
delete(item: RecordKey<T, PK, SK
|
|
73
|
-
deleteMany(items: (RecordKey<T, PK, SK
|
|
74
|
-
get(key: RecordKey<T, PK, SK
|
|
89
|
+
delete(item: KindaPretty<RecordKey<T, PK, SK>>): Promise<void>;
|
|
90
|
+
deleteMany(items: (KindaPretty<RecordKey<T, PK, SK>>)[]): Promise<void>;
|
|
91
|
+
get(key: KindaPretty<RecordKey<T, PK, SK>>): Promise<KindaPretty<T> | undefined>;
|
|
75
92
|
scan(options: {
|
|
76
93
|
filter?: Filter<T>;
|
|
77
94
|
}): AsyncGenerator<T>;
|
|
78
|
-
query(partition: Pick<T, PK
|
|
95
|
+
query(partition: KindaPretty<Pick<T, PK['field']>>, options?: {
|
|
79
96
|
filter?: Filter<T>;
|
|
80
97
|
}): AsyncGenerator<T>;
|
|
81
98
|
}
|
|
@@ -64,12 +64,18 @@ export class DistributedTable extends Resource {
|
|
|
64
64
|
this.sort = options.key.sort;
|
|
65
65
|
this.#fileService = new (overrides.FileService || FileService)(this, 'files');
|
|
66
66
|
}
|
|
67
|
+
get partitionKeyName() {
|
|
68
|
+
const [pkField] = Object.keys(this.partitionKey);
|
|
69
|
+
return pkField;
|
|
70
|
+
}
|
|
71
|
+
get sortKeyName() {
|
|
72
|
+
const [skField] = Object.keys(this.sort ?? {});
|
|
73
|
+
return skField;
|
|
74
|
+
}
|
|
67
75
|
#getFilename(key) {
|
|
68
|
-
const parts = [key[this.
|
|
69
|
-
if (this.
|
|
70
|
-
|
|
71
|
-
parts.push(key[sk]);
|
|
72
|
-
}
|
|
76
|
+
const parts = [key[this.partitionKeyName]];
|
|
77
|
+
if (this.sortKeyName) {
|
|
78
|
+
parts.push(key[this.sortKeyName]);
|
|
73
79
|
}
|
|
74
80
|
return parts.map(String).join('__') + '.json';
|
|
75
81
|
}
|
|
@@ -117,14 +123,26 @@ export class DistributedTable extends Resource {
|
|
|
117
123
|
}
|
|
118
124
|
}
|
|
119
125
|
async *query(partition, options = {}) {
|
|
120
|
-
const prefix = partition[this.
|
|
126
|
+
const prefix = partition[this.partitionKeyName];
|
|
121
127
|
for await (const filename of this.#fileService.list({ prefix })) {
|
|
122
128
|
const data = await this.#fileService.read(filename);
|
|
123
129
|
const record = this.parse(JSON.parse(data));
|
|
124
|
-
if (record[this.
|
|
130
|
+
if (record[this.partitionKeyName] === partition[this.partitionKeyName]
|
|
125
131
|
&& (!options.filter || matchesFilter(record, options.filter))) {
|
|
126
132
|
yield record;
|
|
127
133
|
}
|
|
128
134
|
}
|
|
129
135
|
}
|
|
130
136
|
}
|
|
137
|
+
// export type Todo = {
|
|
138
|
+
// id: string;
|
|
139
|
+
// text: string;
|
|
140
|
+
// order: number;
|
|
141
|
+
// };
|
|
142
|
+
// const userTodos = new DistributedTable('app', 'userTodos', {
|
|
143
|
+
// parse: PassThruParser<Todo & { userId: string }>,
|
|
144
|
+
// key: {
|
|
145
|
+
// partition: { userId: 'string' },
|
|
146
|
+
// sort: { id: 'string' }
|
|
147
|
+
// }
|
|
148
|
+
// });
|
package/package.json
CHANGED
package/dist/hosting/client.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function apiTree(INTERNAL_API_URL: string, path?: string[]): () => void;
|
package/dist/hosting/client.js
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
async function callApi(INTERNAL_API_URL, method, ...args) {
|
|
2
|
-
function isNode() {
|
|
3
|
-
return typeof args[0]?.cookies?.getAll === 'function';
|
|
4
|
-
}
|
|
5
|
-
function apiUrl() {
|
|
6
|
-
if (isNode()) {
|
|
7
|
-
return INTERNAL_API_URL;
|
|
8
|
-
}
|
|
9
|
-
else {
|
|
10
|
-
return "/api";
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
let cookieHeader = {};
|
|
14
|
-
if (isNode()) {
|
|
15
|
-
const context = args[0];
|
|
16
|
-
const cookies = context.cookies.getAll();
|
|
17
|
-
cookieHeader = typeof cookies === 'object'
|
|
18
|
-
? {
|
|
19
|
-
Cookie: Object.entries(cookies).map(kv => kv.join('=')).join('; ')
|
|
20
|
-
}
|
|
21
|
-
: {};
|
|
22
|
-
}
|
|
23
|
-
const response = await fetch(apiUrl(), {
|
|
24
|
-
method: 'POST',
|
|
25
|
-
headers: {
|
|
26
|
-
'Content-Type': 'application/json',
|
|
27
|
-
...cookieHeader
|
|
28
|
-
},
|
|
29
|
-
body: JSON.stringify([{ method, args: [...args] }]),
|
|
30
|
-
});
|
|
31
|
-
const body = await response.json();
|
|
32
|
-
if (isNode()) {
|
|
33
|
-
const context = args[0];
|
|
34
|
-
for (const c of response.headers.getSetCookie()) {
|
|
35
|
-
const parts = c.split(';').map(p => p.trim());
|
|
36
|
-
const flags = parts.slice(1);
|
|
37
|
-
const [name, value] = parts[0].split('=').map(decodeURIComponent);
|
|
38
|
-
const httpOnly = flags.includes('HttpOnly');
|
|
39
|
-
const secure = flags.includes('Secure');
|
|
40
|
-
const maxAgePart = flags.find(f => f.startsWith('Max-Age='))?.split('=')[1];
|
|
41
|
-
context.cookies.set({
|
|
42
|
-
name,
|
|
43
|
-
value,
|
|
44
|
-
httpOnly,
|
|
45
|
-
secure,
|
|
46
|
-
maxAge: maxAgePart ? parseInt(maxAgePart) : undefined
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
const error = body[0].error;
|
|
51
|
-
if (error) {
|
|
52
|
-
throw new Error(error);
|
|
53
|
-
}
|
|
54
|
-
const value = body[0].data;
|
|
55
|
-
return value;
|
|
56
|
-
}
|
|
57
|
-
;
|
|
58
|
-
export function apiTree(INTERNAL_API_URL, path = []) {
|
|
59
|
-
return new Proxy(function () { }, {
|
|
60
|
-
apply(_target, _thisArg, args) {
|
|
61
|
-
return callApi(INTERNAL_API_URL, path, ...args);
|
|
62
|
-
},
|
|
63
|
-
get(_target, prop) {
|
|
64
|
-
return apiTree(INTERNAL_API_URL, [...path, prop]);
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function apiTree(INTERNAL_API_URL: string, path?: string[]): () => void;
|
package/dist/internal/client.js
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
async function callApi(INTERNAL_API_URL, method, ...args) {
|
|
2
|
-
function isNode() {
|
|
3
|
-
return typeof args[0]?.cookies?.getAll === 'function';
|
|
4
|
-
}
|
|
5
|
-
function apiUrl() {
|
|
6
|
-
if (isNode()) {
|
|
7
|
-
return INTERNAL_API_URL;
|
|
8
|
-
}
|
|
9
|
-
else {
|
|
10
|
-
return "/api";
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
let cookieHeader = {};
|
|
14
|
-
if (isNode()) {
|
|
15
|
-
const context = args[0];
|
|
16
|
-
const cookies = context.cookies.getAll();
|
|
17
|
-
cookieHeader = typeof cookies === 'object'
|
|
18
|
-
? {
|
|
19
|
-
Cookie: Object.entries(cookies).map(kv => kv.join('=')).join('; ')
|
|
20
|
-
}
|
|
21
|
-
: {};
|
|
22
|
-
}
|
|
23
|
-
const response = await fetch(apiUrl(), {
|
|
24
|
-
method: 'POST',
|
|
25
|
-
headers: {
|
|
26
|
-
'Content-Type': 'application/json',
|
|
27
|
-
...cookieHeader
|
|
28
|
-
},
|
|
29
|
-
body: JSON.stringify([{ method, args: [...args] }]),
|
|
30
|
-
});
|
|
31
|
-
const body = await response.json();
|
|
32
|
-
if (isNode()) {
|
|
33
|
-
const context = args[0];
|
|
34
|
-
for (const c of response.headers.getSetCookie()) {
|
|
35
|
-
const parts = c.split(';').map(p => p.trim());
|
|
36
|
-
const flags = parts.slice(1);
|
|
37
|
-
const [name, value] = parts[0].split('=').map(decodeURIComponent);
|
|
38
|
-
const httpOnly = flags.includes('HttpOnly');
|
|
39
|
-
const secure = flags.includes('Secure');
|
|
40
|
-
const maxAgePart = flags.find(f => f.startsWith('Max-Age='))?.split('=')[1];
|
|
41
|
-
context.cookies.set({
|
|
42
|
-
name,
|
|
43
|
-
value,
|
|
44
|
-
httpOnly,
|
|
45
|
-
secure,
|
|
46
|
-
maxAge: maxAgePart ? parseInt(maxAgePart) : undefined
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
const error = body[0].error;
|
|
51
|
-
if (error) {
|
|
52
|
-
throw new Error(error);
|
|
53
|
-
}
|
|
54
|
-
const value = body[0].data;
|
|
55
|
-
return value;
|
|
56
|
-
}
|
|
57
|
-
;
|
|
58
|
-
export function apiTree(INTERNAL_API_URL, path = []) {
|
|
59
|
-
return new Proxy(function () { }, {
|
|
60
|
-
apply(_target, _thisArg, args) {
|
|
61
|
-
return callApi(INTERNAL_API_URL, path, ...args);
|
|
62
|
-
},
|
|
63
|
-
get(_target, prop) {
|
|
64
|
-
return apiTree(INTERNAL_API_URL, [...path, prop]);
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
;
|
package/dist/setup/index.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function prebuildApi(): Promise<void>;
|
package/dist/setup/index.js
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import process from 'process';
|
|
2
|
-
import fs from 'fs';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
export async function prebuildApi() {
|
|
5
|
-
const CWD = process.cwd();
|
|
6
|
-
let API_URL = '/api';
|
|
7
|
-
const indexModule = await import(path.join(CWD, 'index.js'));
|
|
8
|
-
try {
|
|
9
|
-
const backendConfigModule = await import(path.join(CWD, 'config.js'));
|
|
10
|
-
const backendConfig = backendConfigModule.default;
|
|
11
|
-
console.log("backend config found", backendConfig);
|
|
12
|
-
if (backendConfig.apiUrl) {
|
|
13
|
-
API_URL = backendConfig.apiUrl;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
catch {
|
|
17
|
-
console.log("No backend API config found.");
|
|
18
|
-
}
|
|
19
|
-
const apiCode = Object.keys(indexModule)
|
|
20
|
-
.map(k => `export const ${k} = apiTree(INTERNAL_API_URL, ${JSON.stringify([k])});`)
|
|
21
|
-
.join('\n');
|
|
22
|
-
const baseClient = [
|
|
23
|
-
`import { apiTree } from "wirejs-resources/hosting/client.js";`,
|
|
24
|
-
`const INTERNAL_API_URL = ${JSON.stringify(API_URL)};`,
|
|
25
|
-
].join('\n');
|
|
26
|
-
await fs.promises.writeFile(path.join(CWD, 'index.client.js'), [baseClient, apiCode].join('\n\n'));
|
|
27
|
-
}
|