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 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: keyof T & string;
6
- sort?: (keyof T & string)[];
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 keyof T, SK extends (keyof T)[] | undefined> = Pick<T, PK | (SK extends string[] ? SK[number] : never)>;
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>, const PK extends keyof T & string, const SK extends (keyof T & string)[] | undefined> extends Resource {
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>): Promise<void>;
73
- deleteMany(items: (RecordKey<T, PK, SK>)[]): Promise<void>;
74
- get(key: RecordKey<T, PK, SK>): Promise<T | undefined>;
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>, options?: {
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.partitionKey]];
69
- if (this.sort) {
70
- for (const sk of this.sort) {
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.partitionKey];
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.partitionKey] === partition[this.partitionKey]
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wirejs-resources",
3
- "version": "0.1.34-table-resource",
3
+ "version": "0.1.35-table-resource",
4
4
  "description": "Basic services and server-side resources for wirejs apps",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1 +0,0 @@
1
- export declare function apiTree(INTERNAL_API_URL: string, path?: string[]): () => void;
@@ -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;
@@ -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 prebuildApi(): Promise<void>;
@@ -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
- }