wirejs-resources 0.1.105 → 0.1.107-payments
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/adapters/context.d.ts +24 -3
- package/dist/adapters/context.js +39 -2
- package/dist/adapters/signed-cookie.d.ts +5 -2
- package/dist/index.d.ts +4 -1
- package/dist/index.js +4 -1
- package/dist/overrides.d.ts +11 -6
- package/dist/resources/distributed-table.d.ts +4 -1
- package/dist/resources/distributed-table.js +9 -3
- package/dist/resources/endpoint.d.ts +42 -0
- package/dist/resources/endpoint.js +48 -0
- package/dist/resources/key-value-store.d.ts +16 -0
- package/dist/resources/key-value-store.js +34 -0
- package/dist/resources/setting.d.ts +25 -0
- package/dist/resources/setting.js +61 -0
- package/dist/resources/system-attribute.d.ts +21 -0
- package/dist/resources/system-attribute.js +29 -0
- package/dist/services/authentication.js +6 -2
- package/dist/services/file.js +11 -3
- package/dist/services/realtime.d.ts +3 -2
- package/dist/services/realtime.js +10 -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
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CookieJar } from "./cookie-jar.js";
|
|
2
|
+
import { SystemAttribute } from "../resources/system-attribute.js";
|
|
2
3
|
type ApiMethod = (...args: any) => any;
|
|
3
4
|
type ApiNamespace = {
|
|
4
5
|
[K in string]: ApiMethod | ApiNamespace;
|
|
@@ -8,14 +9,34 @@ export type ContextfulApiNamespace<T> = {
|
|
|
8
9
|
[K in keyof T]: T[K] extends ApiMethod ? ContextfulApiMethod<T[K]> : ContextfulApiNamespace<T[K]>;
|
|
9
10
|
};
|
|
10
11
|
export type ContextWrapped<T extends ApiNamespace | ApiMethod> = T extends ApiMethod ? ContextfulApiMethod<T> : ContextfulApiNamespace<T>;
|
|
12
|
+
export type SystemInfo = Record<string, SystemAttribute>;
|
|
11
13
|
export declare function withContext<T extends ApiMethod | ApiNamespace>(contextWrapper: (context: Context) => T, path?: string[]): ContextWrapped<T>;
|
|
12
14
|
export declare function requiresContext(fnOrNS: Object): fnOrNS is (context: Context) => any;
|
|
13
15
|
export declare class Context {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
/**
|
|
17
|
+
* `cookies` can be directly manipulated. The response pipeline
|
|
18
|
+
* will ingest changes to cookies when issuing a response.
|
|
19
|
+
*/
|
|
20
|
+
readonly cookies: CookieJar;
|
|
21
|
+
readonly httpMethod?: string;
|
|
22
|
+
readonly requestHeaders: Record<string, string | string[]>;
|
|
23
|
+
readonly requestBody?: string;
|
|
24
|
+
responseHeaders: Record<string, string>;
|
|
25
|
+
responseCode: number | undefined;
|
|
26
|
+
private _location;
|
|
27
|
+
private _locationChanged;
|
|
28
|
+
private _runtimeAttributes;
|
|
29
|
+
constructor({ cookies, location, httpMethod, requestHeaders, requestBody, runtimeAttributes, }: {
|
|
17
30
|
cookies: CookieJar;
|
|
18
31
|
location: URL;
|
|
32
|
+
httpMethod: string;
|
|
33
|
+
requestHeaders: Record<string, string | string[]>;
|
|
34
|
+
requestBody: string | undefined;
|
|
35
|
+
runtimeAttributes?: SystemAttribute[];
|
|
19
36
|
});
|
|
37
|
+
get location(): URL;
|
|
38
|
+
set location(url: URL | string);
|
|
39
|
+
get locationIsDirty(): boolean;
|
|
40
|
+
get systemInfo(): MapIterator<SystemAttribute>;
|
|
20
41
|
}
|
|
21
42
|
export {};
|
package/dist/adapters/context.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { SystemAttribute } from "../resources/system-attribute.js";
|
|
1
2
|
const __requiresContext = '__requiresContext';
|
|
2
3
|
export function withContext(contextWrapper, path = []) {
|
|
3
4
|
// first param needs to be a function, which enables `Proxy` to implement `apply()`.
|
|
@@ -27,10 +28,46 @@ export function requiresContext(fnOrNS) {
|
|
|
27
28
|
return true;
|
|
28
29
|
}
|
|
29
30
|
export class Context {
|
|
31
|
+
/**
|
|
32
|
+
* `cookies` can be directly manipulated. The response pipeline
|
|
33
|
+
* will ingest changes to cookies when issuing a response.
|
|
34
|
+
*/
|
|
30
35
|
cookies;
|
|
31
|
-
|
|
32
|
-
|
|
36
|
+
httpMethod;
|
|
37
|
+
requestHeaders;
|
|
38
|
+
requestBody;
|
|
39
|
+
responseHeaders = {};
|
|
40
|
+
responseCode;
|
|
41
|
+
_location;
|
|
42
|
+
_locationChanged = false;
|
|
43
|
+
_runtimeAttributes = [];
|
|
44
|
+
constructor({ cookies, location, httpMethod, requestHeaders, requestBody, runtimeAttributes, }) {
|
|
33
45
|
this.cookies = cookies;
|
|
34
46
|
this.location = location;
|
|
47
|
+
this.httpMethod = httpMethod;
|
|
48
|
+
this.requestHeaders = requestHeaders;
|
|
49
|
+
this.requestBody = requestBody;
|
|
50
|
+
this._runtimeAttributes = runtimeAttributes || [];
|
|
51
|
+
}
|
|
52
|
+
get location() {
|
|
53
|
+
return this._location;
|
|
54
|
+
}
|
|
55
|
+
set location(url) {
|
|
56
|
+
if (this._location)
|
|
57
|
+
this._locationChanged = true;
|
|
58
|
+
this._location = new URL(url);
|
|
59
|
+
}
|
|
60
|
+
get locationIsDirty() {
|
|
61
|
+
return this._locationChanged;
|
|
62
|
+
}
|
|
63
|
+
get systemInfo() {
|
|
64
|
+
const all = new Map();
|
|
65
|
+
for (const attr of SystemAttribute.listStaticAttributes()) {
|
|
66
|
+
all.set(attr.absoluteId, attr);
|
|
67
|
+
}
|
|
68
|
+
for (const attr of this._runtimeAttributes) {
|
|
69
|
+
all.set(attr.absoluteId, attr);
|
|
70
|
+
}
|
|
71
|
+
return all.values();
|
|
35
72
|
}
|
|
36
73
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Setting } from '../resources/setting.js';
|
|
2
2
|
import { Cookie, CookieJar } from './cookie-jar.js';
|
|
3
3
|
import { Resource } from '../resource.js';
|
|
4
4
|
export declare class SignedCookie<T> {
|
|
@@ -6,7 +6,10 @@ export declare class SignedCookie<T> {
|
|
|
6
6
|
private name;
|
|
7
7
|
private signingSecret;
|
|
8
8
|
private options;
|
|
9
|
-
constructor(scope: Resource | string, name: string, signingSecret:
|
|
9
|
+
constructor(scope: Resource | string, name: string, signingSecret: Setting<{
|
|
10
|
+
private: true;
|
|
11
|
+
init: 'random';
|
|
12
|
+
}>, options?: Omit<Cookie, 'value' | 'name'>);
|
|
10
13
|
get cookieName(): string;
|
|
11
14
|
read(cookies: CookieJar): Promise<T | null>;
|
|
12
15
|
write(cookies: CookieJar, value: T | null): Promise<void>;
|
package/dist/index.d.ts
CHANGED
|
@@ -5,10 +5,13 @@ export { SignedCookie } from './adapters/signed-cookie.js';
|
|
|
5
5
|
export { withContext, requiresContext, Context, ContextWrapped } from './adapters/context.js';
|
|
6
6
|
export { Resource } from './resource.js';
|
|
7
7
|
export { overrides } from './overrides.js';
|
|
8
|
-
export {
|
|
8
|
+
export { Setting } from './resources/setting.js';
|
|
9
9
|
export { DistributedTable, PassThruParser, matchesFilter, indexName } from './resources/distributed-table.js';
|
|
10
10
|
export type * from './resources/distributed-table.js';
|
|
11
11
|
export * from './types/index.js';
|
|
12
12
|
export * from './derived-types.js';
|
|
13
13
|
export * from './services/realtime.js';
|
|
14
14
|
export * from './resources/background-job.js';
|
|
15
|
+
export * from './resources/key-value-store.js';
|
|
16
|
+
export * from './resources/endpoint.js';
|
|
17
|
+
export * from './resources/system-attribute.js';
|
package/dist/index.js
CHANGED
|
@@ -5,9 +5,12 @@ export { SignedCookie } from './adapters/signed-cookie.js';
|
|
|
5
5
|
export { withContext, requiresContext, Context } from './adapters/context.js';
|
|
6
6
|
export { Resource } from './resource.js';
|
|
7
7
|
export { overrides } from './overrides.js';
|
|
8
|
-
export {
|
|
8
|
+
export { Setting } from './resources/setting.js';
|
|
9
9
|
export { DistributedTable, PassThruParser, matchesFilter, indexName } from './resources/distributed-table.js';
|
|
10
10
|
export * from './types/index.js';
|
|
11
11
|
export * from './derived-types.js';
|
|
12
12
|
export * from './services/realtime.js';
|
|
13
13
|
export * from './resources/background-job.js';
|
|
14
|
+
export * from './resources/key-value-store.js';
|
|
15
|
+
export * from './resources/endpoint.js';
|
|
16
|
+
export * from './resources/system-attribute.js';
|
package/dist/overrides.d.ts
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
|
-
import type { FileService } from "./services/file";
|
|
2
1
|
import type { AuthenticationService } from "./services/authentication";
|
|
3
|
-
import type { Secret } from "./resources/secret";
|
|
4
|
-
import type { RealtimeService } from "./services/realtime";
|
|
5
2
|
import type { BackgroundJob } from "./resources/background-job";
|
|
3
|
+
import type { DistributedTable } from "./resources/distributed-table";
|
|
4
|
+
import type { Endpoint } from "./resources/endpoint";
|
|
5
|
+
import type { FileService } from "./services/file";
|
|
6
|
+
import type { KeyValueStore } from "./resources/key-value-store";
|
|
7
|
+
import type { RealtimeService } from "./services/realtime";
|
|
8
|
+
import type { Setting } from "./resources/setting";
|
|
6
9
|
/**
|
|
7
10
|
* Used by hosting providers to provide service overrides.
|
|
8
11
|
*/
|
|
9
12
|
export declare const overrides: {
|
|
10
13
|
AuthenticationService?: typeof AuthenticationService;
|
|
11
|
-
|
|
14
|
+
BackgroundJob?: typeof BackgroundJob;
|
|
15
|
+
DistributedTable?: typeof DistributedTable;
|
|
16
|
+
Endpoint?: typeof Endpoint;
|
|
12
17
|
FileService?: typeof FileService;
|
|
13
|
-
|
|
18
|
+
KeyValueStore?: typeof KeyValueStore;
|
|
14
19
|
RealtimeService?: typeof RealtimeService;
|
|
15
|
-
|
|
20
|
+
Setting?: typeof Setting;
|
|
16
21
|
};
|
|
@@ -26,7 +26,9 @@ export declare class DistributedTable<const P extends Parser<any>, const T exten
|
|
|
26
26
|
});
|
|
27
27
|
get partitionKeyName(): Key['partition']['field'];
|
|
28
28
|
get sortKeyName(): 'field' extends keyof Key['sort'] ? (Key['sort']['field'] extends string ? Key['sort']['field'] : undefined) : undefined;
|
|
29
|
-
save(item: T
|
|
29
|
+
save(item: T, options?: {
|
|
30
|
+
onlyIfNotExists?: boolean;
|
|
31
|
+
}): Promise<void>;
|
|
30
32
|
saveMany(items: T[]): Promise<void>;
|
|
31
33
|
delete(item: KindaPretty<RecordKey<T, Key>>): Promise<void>;
|
|
32
34
|
deleteMany(items: (KindaPretty<RecordKey<T, Key>>)[]): Promise<void>;
|
|
@@ -58,4 +60,5 @@ export declare class DistributedTable<const P extends Parser<any>, const T exten
|
|
|
58
60
|
where: KeyCondition<DistributedTable<P, T, Key, Indexes>, GivenPartition>;
|
|
59
61
|
filter?: Filter<Omit<T, IndexFieldNames<DistributedTable<P, T, Key, Indexes>, GivenPartition> & string>>;
|
|
60
62
|
}): AsyncGenerator<T>;
|
|
63
|
+
isAlreadyExistsError(error: any): boolean;
|
|
61
64
|
}
|
|
@@ -80,9 +80,11 @@ export class DistributedTable extends Resource {
|
|
|
80
80
|
}
|
|
81
81
|
return parts.map(String).join('__') + '.json';
|
|
82
82
|
}
|
|
83
|
-
async save(item) {
|
|
83
|
+
async save(item, options) {
|
|
84
84
|
const key = this.#getFilename(item);
|
|
85
|
-
await this.#fileService.write(key, JSON.stringify(item)
|
|
85
|
+
await this.#fileService.write(key, JSON.stringify(item), {
|
|
86
|
+
onlyIfNotExists: options?.onlyIfNotExists
|
|
87
|
+
});
|
|
86
88
|
}
|
|
87
89
|
async saveMany(items) {
|
|
88
90
|
const promises = items.map(item => {
|
|
@@ -125,9 +127,10 @@ export class DistributedTable extends Resource {
|
|
|
125
127
|
*/
|
|
126
128
|
async *scan(options) {
|
|
127
129
|
for await (const filename of this.#fileService.list()) {
|
|
130
|
+
console.log('scanning', filename);
|
|
128
131
|
const data = await this.#fileService.read(filename);
|
|
129
132
|
const record = this.parse(JSON.parse(data));
|
|
130
|
-
if (!options
|
|
133
|
+
if (!options?.filter || matchesFilter(record, options.filter)) {
|
|
131
134
|
yield record;
|
|
132
135
|
}
|
|
133
136
|
}
|
|
@@ -157,4 +160,7 @@ export class DistributedTable extends Resource {
|
|
|
157
160
|
}
|
|
158
161
|
});
|
|
159
162
|
}
|
|
163
|
+
isAlreadyExistsError(error) {
|
|
164
|
+
return this.#fileService.isAlreadyExistsError(error);
|
|
165
|
+
}
|
|
160
166
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Resource } from '../resource.js';
|
|
2
|
+
import { Context } from '../adapters/context.js';
|
|
3
|
+
export type EndpointOptions = {
|
|
4
|
+
/**
|
|
5
|
+
* If left unspecified, the path will derived from its `absoluteId`.
|
|
6
|
+
*
|
|
7
|
+
* If specified, the endpoint *may* end in a wildcard (`%`) to handle
|
|
8
|
+
* all requests matching the given prefix.
|
|
9
|
+
*
|
|
10
|
+
* Programmatically created endpoints take precedence over
|
|
11
|
+
*/
|
|
12
|
+
path?: string;
|
|
13
|
+
/**
|
|
14
|
+
* The function that will handle requests.
|
|
15
|
+
*
|
|
16
|
+
* Currently, these handlers can only return strings, which means that if
|
|
17
|
+
* your endpoint returns an HTML page that requires other resources, you
|
|
18
|
+
* must also provide those resources manually.
|
|
19
|
+
*
|
|
20
|
+
* @param context
|
|
21
|
+
* @returns
|
|
22
|
+
*/
|
|
23
|
+
handle: (context: Context) => string | Promise<string>;
|
|
24
|
+
/**
|
|
25
|
+
* Message explaining the purpose of the path.
|
|
26
|
+
*/
|
|
27
|
+
description?: string;
|
|
28
|
+
};
|
|
29
|
+
export declare class Endpoint extends Resource {
|
|
30
|
+
options: EndpointOptions;
|
|
31
|
+
constructor(scope: Resource | string, id: string, options: EndpointOptions);
|
|
32
|
+
static list(): MapIterator<Endpoint>;
|
|
33
|
+
static get(key: string): Endpoint | undefined;
|
|
34
|
+
get description(): string | undefined;
|
|
35
|
+
get path(): string;
|
|
36
|
+
get handle(): (context: Context) => string | Promise<string>;
|
|
37
|
+
/**
|
|
38
|
+
* Constructed full URL of the endpoint based on the configured
|
|
39
|
+
* `wirejs/endpoint-origin` setting.
|
|
40
|
+
*/
|
|
41
|
+
determineUrl(): Promise<string>;
|
|
42
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Resource } from '../resource.js';
|
|
2
|
+
import { Setting } from './setting.js';
|
|
3
|
+
import { overrides } from '../overrides.js';
|
|
4
|
+
let endpointOrigin;
|
|
5
|
+
const allEndpoints = new Map();
|
|
6
|
+
function createOriginSetting() {
|
|
7
|
+
return new (overrides.Setting || Setting)('wirejs', 'endpoint-origin', {
|
|
8
|
+
description: 'Base URL modules should broadcast to external services.',
|
|
9
|
+
init: () => 'http://localhost:3000',
|
|
10
|
+
private: false,
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
export class Endpoint extends Resource {
|
|
14
|
+
options;
|
|
15
|
+
constructor(scope, id, options) {
|
|
16
|
+
super(scope, id);
|
|
17
|
+
this.options = options;
|
|
18
|
+
allEndpoints.set(this.absoluteId, this);
|
|
19
|
+
endpointOrigin = endpointOrigin || createOriginSetting();
|
|
20
|
+
}
|
|
21
|
+
static list() {
|
|
22
|
+
return allEndpoints.values();
|
|
23
|
+
}
|
|
24
|
+
static get(key) {
|
|
25
|
+
return allEndpoints.get(key);
|
|
26
|
+
}
|
|
27
|
+
get description() {
|
|
28
|
+
return this.options.description;
|
|
29
|
+
}
|
|
30
|
+
get path() {
|
|
31
|
+
const path = this.options.path || this.absoluteId;
|
|
32
|
+
return path.startsWith('/') ? path : `/${path}`;
|
|
33
|
+
}
|
|
34
|
+
get handle() {
|
|
35
|
+
return this.options.handle;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Constructed full URL of the endpoint based on the configured
|
|
39
|
+
* `wirejs/endpoint-origin` setting.
|
|
40
|
+
*/
|
|
41
|
+
async determineUrl() {
|
|
42
|
+
const origin = await endpointOrigin?.read();
|
|
43
|
+
const normalizedOrigin = origin?.endsWith('/')
|
|
44
|
+
? origin.slice(0, origin.length - 1)
|
|
45
|
+
: origin;
|
|
46
|
+
return `${normalizedOrigin}${this.path}`;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Resource } from '../resource.js';
|
|
2
|
+
import { KindaPretty } from '../types/index.js';
|
|
3
|
+
export declare class KeyValueStore<T> extends Resource {
|
|
4
|
+
#private;
|
|
5
|
+
constructor(scope: Resource | string, id: string);
|
|
6
|
+
get(key: string): Promise<KindaPretty<KindaPretty<T>> | undefined>;
|
|
7
|
+
set(key: string, value: KindaPretty<T>, options?: {
|
|
8
|
+
onlyIfNotExists?: boolean;
|
|
9
|
+
}): Promise<void>;
|
|
10
|
+
delete(key: string): Promise<void>;
|
|
11
|
+
scan(): AsyncGenerator<{
|
|
12
|
+
key: string;
|
|
13
|
+
value: KindaPretty<T>;
|
|
14
|
+
}>;
|
|
15
|
+
isAlreadyExistsError(error: any): boolean;
|
|
16
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Resource } from '../resource.js';
|
|
2
|
+
import { DistributedTable, PassThruParser } from './distributed-table.js';
|
|
3
|
+
import { overrides } from '../overrides.js';
|
|
4
|
+
const defineTable = (kvStore) => {
|
|
5
|
+
const ctor = overrides.DistributedTable || DistributedTable;
|
|
6
|
+
return new ctor(kvStore, 'table', {
|
|
7
|
+
parse: (PassThruParser),
|
|
8
|
+
key: { partition: { field: "key", type: 'string' } },
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
export class KeyValueStore extends Resource {
|
|
12
|
+
#table;
|
|
13
|
+
constructor(scope, id) {
|
|
14
|
+
super(scope, id);
|
|
15
|
+
this.#table = defineTable(this);
|
|
16
|
+
}
|
|
17
|
+
async get(key) {
|
|
18
|
+
return (await this.#table.get({ key }))?.value;
|
|
19
|
+
}
|
|
20
|
+
async set(key, value, options) {
|
|
21
|
+
return this.#table.save({ key, value }, { onlyIfNotExists: options?.onlyIfNotExists });
|
|
22
|
+
}
|
|
23
|
+
async delete(key) {
|
|
24
|
+
return this.#table.delete({ key });
|
|
25
|
+
}
|
|
26
|
+
async *scan() {
|
|
27
|
+
for await (const record of this.#table.scan({})) {
|
|
28
|
+
yield record;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
isAlreadyExistsError(error) {
|
|
32
|
+
return this.#table.isAlreadyExistsError(error);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Resource } from '../resource.js';
|
|
2
|
+
type Initializer<ValidOptions extends string = string> = 'random' | (() => ValidOptions) | null | undefined;
|
|
3
|
+
export type SettingDetails<Init extends Initializer<ValidOptions>, ValidOptions extends string = string> = {
|
|
4
|
+
private: boolean;
|
|
5
|
+
description?: string;
|
|
6
|
+
init?: Init;
|
|
7
|
+
options?: ValidOptions[];
|
|
8
|
+
};
|
|
9
|
+
type ReturnTypeFor<T extends SettingDetails<Initializer, string>> = T extends SettingDetails<undefined | null, infer ValidOptions> ? (ValidOptions | undefined) : string;
|
|
10
|
+
type SetTypeFor<T extends SettingDetails<Initializer, string>> = T extends SettingDetails<undefined | null, infer ValidOptions> ? ValidOptions : string;
|
|
11
|
+
export declare class Setting<const Details extends SettingDetails<Initializer> = SettingDetails<null>> extends Resource {
|
|
12
|
+
private details;
|
|
13
|
+
constructor(scope: Resource | string, id: string, details?: Details);
|
|
14
|
+
get isPrivate(): boolean;
|
|
15
|
+
get description(): string | undefined;
|
|
16
|
+
get options(): string[] | undefined;
|
|
17
|
+
read(): Promise<ReturnTypeFor<Details>>;
|
|
18
|
+
write(value: SetTypeFor<Details>, options?: {
|
|
19
|
+
onlyIfNotExists?: boolean;
|
|
20
|
+
}): Promise<void | undefined>;
|
|
21
|
+
static list(): MapIterator<Setting<SettingDetails<Initializer<string>, string>>>;
|
|
22
|
+
static get(key: string): Setting<SettingDetails<Initializer<string>, string>> | undefined;
|
|
23
|
+
isAlreadyExistsError(error: any): boolean | undefined;
|
|
24
|
+
}
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import * as crypto from 'crypto';
|
|
2
|
+
import { Resource } from '../resource.js';
|
|
3
|
+
import { KeyValueStore } from './key-value-store.js';
|
|
4
|
+
import { overrides } from '../overrides.js';
|
|
5
|
+
let settings;
|
|
6
|
+
const allSettings = new Map();
|
|
7
|
+
export class Setting extends Resource {
|
|
8
|
+
details;
|
|
9
|
+
constructor(scope, id, details) {
|
|
10
|
+
super(scope, id);
|
|
11
|
+
this.details = (details || { private: false });
|
|
12
|
+
settings = new (overrides.KeyValueStore || KeyValueStore)('wirejs', 'settings');
|
|
13
|
+
allSettings.set(this.absoluteId, this);
|
|
14
|
+
}
|
|
15
|
+
get isPrivate() {
|
|
16
|
+
return this.details.private;
|
|
17
|
+
}
|
|
18
|
+
get description() {
|
|
19
|
+
return this.details.description;
|
|
20
|
+
}
|
|
21
|
+
get options() {
|
|
22
|
+
return this.details.options;
|
|
23
|
+
}
|
|
24
|
+
async read() {
|
|
25
|
+
const existingValue = await settings?.get(this.absoluteId);
|
|
26
|
+
if (!existingValue && !!this.details.init) {
|
|
27
|
+
try {
|
|
28
|
+
const initValue = this.details.init === 'random'
|
|
29
|
+
? crypto.randomBytes(64).toString('base64url')
|
|
30
|
+
: this.details.init();
|
|
31
|
+
await this.write(initValue, { onlyIfNotExists: true });
|
|
32
|
+
return initValue;
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
if (!this.isAlreadyExistsError(error))
|
|
36
|
+
throw error;
|
|
37
|
+
return this.read();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
return existingValue?.value;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async write(value, options) {
|
|
45
|
+
return settings?.set(this.absoluteId, {
|
|
46
|
+
value,
|
|
47
|
+
private: this.details.private,
|
|
48
|
+
description: this.details.description,
|
|
49
|
+
options: this.details.options,
|
|
50
|
+
}, { onlyIfNotExists: options?.onlyIfNotExists });
|
|
51
|
+
}
|
|
52
|
+
static list() {
|
|
53
|
+
return allSettings.values();
|
|
54
|
+
}
|
|
55
|
+
static get(key) {
|
|
56
|
+
return allSettings.get(key);
|
|
57
|
+
}
|
|
58
|
+
isAlreadyExistsError(error) {
|
|
59
|
+
return settings?.isAlreadyExistsError(error);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Resource } from '../resource.js';
|
|
2
|
+
type SystemAttributeProperties = {
|
|
3
|
+
description: string;
|
|
4
|
+
value: string | null;
|
|
5
|
+
};
|
|
6
|
+
export declare class SystemAttribute extends Resource {
|
|
7
|
+
private props;
|
|
8
|
+
constructor(scope: Resource | string, id: string, props: SystemAttributeProperties);
|
|
9
|
+
get name(): string;
|
|
10
|
+
get description(): string;
|
|
11
|
+
get value(): string | null;
|
|
12
|
+
/**
|
|
13
|
+
* Lists attributes defined statically by code and modules within your
|
|
14
|
+
* codebase and imported modules. This is *not* intended to includes
|
|
15
|
+
* attributes defined by the build system or runtime. To enumerate all known
|
|
16
|
+
* attributes, use `Context.systemInfo`.
|
|
17
|
+
* @returns
|
|
18
|
+
*/
|
|
19
|
+
static listStaticAttributes(): MapIterator<SystemAttribute>;
|
|
20
|
+
}
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Resource } from '../resource.js';
|
|
2
|
+
const staticAttributes = new Map();
|
|
3
|
+
export class SystemAttribute extends Resource {
|
|
4
|
+
props;
|
|
5
|
+
constructor(scope, id, props) {
|
|
6
|
+
super(scope, id);
|
|
7
|
+
this.props = props;
|
|
8
|
+
staticAttributes.set(this.absoluteId, this);
|
|
9
|
+
}
|
|
10
|
+
get name() {
|
|
11
|
+
return this.absoluteId;
|
|
12
|
+
}
|
|
13
|
+
get description() {
|
|
14
|
+
return this.props.description;
|
|
15
|
+
}
|
|
16
|
+
get value() {
|
|
17
|
+
return this.props.value;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Lists attributes defined statically by code and modules within your
|
|
21
|
+
* codebase and imported modules. This is *not* intended to includes
|
|
22
|
+
* attributes defined by the build system or runtime. To enumerate all known
|
|
23
|
+
* attributes, use `Context.systemInfo`.
|
|
24
|
+
* @returns
|
|
25
|
+
*/
|
|
26
|
+
static listStaticAttributes() {
|
|
27
|
+
return staticAttributes.values();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { scrypt, randomBytes, randomUUID } from 'crypto';
|
|
2
2
|
import { Resource } from '../resource.js';
|
|
3
3
|
import { FileService } from './file.js';
|
|
4
|
-
import {
|
|
4
|
+
import { Setting } from '../resources/setting.js';
|
|
5
5
|
import { SignedCookie } from '../adapters/signed-cookie.js';
|
|
6
6
|
import { withContext } from '../adapters/context.js';
|
|
7
7
|
import { overrides } from '../overrides.js';
|
|
@@ -187,7 +187,11 @@ export class AuthenticationService extends Resource {
|
|
|
187
187
|
constructor(scope, id, { duration, keepalive, cookie } = {}) {
|
|
188
188
|
super(scope, id);
|
|
189
189
|
this.#keepalive = !!keepalive;
|
|
190
|
-
const
|
|
190
|
+
const SettingCtor = overrides.Setting || Setting;
|
|
191
|
+
const signingSecret = new SettingCtor(this, 'jwt-signing-secret', {
|
|
192
|
+
private: true, init: 'random',
|
|
193
|
+
description: "Used for signing the auth JWT. Should generally not be changed."
|
|
194
|
+
});
|
|
191
195
|
const fileService = new (overrides.FileService || FileService)(this, 'files');
|
|
192
196
|
this.#cookie = new SignedCookie(this, cookie ?? 'identity', signingSecret,
|
|
193
197
|
// in the future, we might want to set `secure` based on whether we're in
|
package/dist/services/file.js
CHANGED
|
@@ -28,11 +28,19 @@ export class FileService extends Resource {
|
|
|
28
28
|
async *list({ prefix = '' } = {}) {
|
|
29
29
|
const root = this.#fullNameFor('');
|
|
30
30
|
try {
|
|
31
|
-
const
|
|
31
|
+
const entries = await fs.promises.readdir(root, {
|
|
32
|
+
recursive: true,
|
|
33
|
+
withFileTypes: true
|
|
34
|
+
});
|
|
35
|
+
const all = entries
|
|
36
|
+
.filter(entry => entry.isFile())
|
|
37
|
+
.map(entry => path.join(entry.parentPath, entry.name));
|
|
32
38
|
for (const name of all) {
|
|
39
|
+
const relativeName = name.slice(root.length);
|
|
40
|
+
console.log('relativeName', relativeName);
|
|
33
41
|
if (prefix === undefined
|
|
34
|
-
||
|
|
35
|
-
yield
|
|
42
|
+
|| relativeName.startsWith(prefix)) {
|
|
43
|
+
yield relativeName;
|
|
36
44
|
}
|
|
37
45
|
}
|
|
38
46
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Resource } from '../resource.js';
|
|
2
|
+
import { Context } from '../adapters/context.js';
|
|
2
3
|
export type MessageStream<T = any> = {
|
|
3
4
|
/**
|
|
4
5
|
* Returns a function to close the subscription.
|
|
@@ -16,7 +17,7 @@ export declare class RealtimeService<T = any> extends Resource {
|
|
|
16
17
|
/**
|
|
17
18
|
* The address the client will need to connect to.
|
|
18
19
|
*/
|
|
19
|
-
|
|
20
|
+
address(context: Context): string;
|
|
20
21
|
publish(channel: string, events: T[]): Promise<void>;
|
|
21
|
-
getStream(channel: string): Promise<MessageStream<T>>;
|
|
22
|
+
getStream(context: Context, channel: string): Promise<MessageStream<T>>;
|
|
22
23
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as jose from 'jose';
|
|
2
2
|
import { WebSocketServer, WebSocket } from 'ws';
|
|
3
3
|
import { Resource } from '../resource.js';
|
|
4
|
-
import {
|
|
4
|
+
import { Setting } from '../resources/setting.js';
|
|
5
5
|
import { overrides } from '../overrides.js';
|
|
6
6
|
const servers = new Map();
|
|
7
7
|
const channels = new Map();
|
|
@@ -11,7 +11,10 @@ export class RealtimeService extends Resource {
|
|
|
11
11
|
#server;
|
|
12
12
|
constructor(scope, id) {
|
|
13
13
|
super(scope, id);
|
|
14
|
-
this.#secret = new (overrides.
|
|
14
|
+
this.#secret = new (overrides.Setting || Setting)(this, 'auth-secret', {
|
|
15
|
+
private: true, init: 'random',
|
|
16
|
+
description: "Used to sign realtime subscription authorization. Should generally not be changed."
|
|
17
|
+
});
|
|
15
18
|
if (servers.has(this.absoluteId)) {
|
|
16
19
|
console.log('existing realtime service found');
|
|
17
20
|
this.#server = servers.get(this.absoluteId);
|
|
@@ -29,7 +32,7 @@ export class RealtimeService extends Resource {
|
|
|
29
32
|
if (this.#server)
|
|
30
33
|
return;
|
|
31
34
|
console.log('Starting WebSocket server...');
|
|
32
|
-
this.#server = new WebSocketServer({ port:
|
|
35
|
+
this.#server = new WebSocketServer({ port: 3001 });
|
|
33
36
|
servers.set(this.absoluteId, this.#server);
|
|
34
37
|
this.#server.on('connection', async (ws, req) => {
|
|
35
38
|
const encodedChannel = req.url?.split('/')[1];
|
|
@@ -97,7 +100,7 @@ export class RealtimeService extends Resource {
|
|
|
97
100
|
/**
|
|
98
101
|
* The address the client will need to connect to.
|
|
99
102
|
*/
|
|
100
|
-
|
|
103
|
+
address(context) {
|
|
101
104
|
this.#requireServer();
|
|
102
105
|
const address = this.#server.address();
|
|
103
106
|
if (typeof address === 'string') {
|
|
@@ -105,7 +108,7 @@ export class RealtimeService extends Resource {
|
|
|
105
108
|
}
|
|
106
109
|
else if (typeof address === 'object' && address !== null) {
|
|
107
110
|
const { port } = address;
|
|
108
|
-
return `ws
|
|
111
|
+
return `ws://${context.location.hostname}:${port}`;
|
|
109
112
|
}
|
|
110
113
|
else {
|
|
111
114
|
throw new Error('Server address is not available');
|
|
@@ -123,7 +126,7 @@ export class RealtimeService extends Resource {
|
|
|
123
126
|
}
|
|
124
127
|
});
|
|
125
128
|
}
|
|
126
|
-
async getStream(channel) {
|
|
129
|
+
async getStream(context, channel) {
|
|
127
130
|
this.#requireServer();
|
|
128
131
|
this.#validateChannelName(channel);
|
|
129
132
|
const channelString = Buffer.from(channel).toString('base64');
|
|
@@ -137,7 +140,7 @@ export class RealtimeService extends Resource {
|
|
|
137
140
|
// It's the metadata needed to satisfy the contract client-side.
|
|
138
141
|
return {
|
|
139
142
|
__wjstype: 'realtime',
|
|
140
|
-
url: `${this.address}/${channelString}`,
|
|
143
|
+
url: `${this.address(context)}/${channelString}`,
|
|
141
144
|
protocol: jwt
|
|
142
145
|
};
|
|
143
146
|
}
|
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
|
-
}
|