@gasboost/client 0.1.0 → 0.1.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/LICENSE +21 -0
- package/README.md +386 -0
- package/dist/navigation/AppsScriptIframe.js +5 -4
- package/package.json +21 -1
- package/dist/AppsScriptJob.d.ts +0 -28
- package/dist/AppsScriptJob.js +0 -84
- package/dist/AppsScriptJobQueue.d.ts +0 -14
- package/dist/AppsScriptJobQueue.js +0 -40
- package/dist/AppsScriptJobRunner.d.ts +0 -20
- package/dist/AppsScriptJobRunner.js +0 -93
- package/dist/AppsScriptJobStore.d.ts +0 -7
- package/dist/AppsScriptJobStore.js +0 -9
- package/src/AppsScriptClient.ts +0 -81
- package/src/google.ts +0 -55
- package/src/index.ts +0 -4
- package/src/job/AppsScriptJob.ts +0 -85
- package/src/job/AppsScriptJobQueue.ts +0 -48
- package/src/job/AppsScriptJobRunner.ts +0 -107
- package/src/job/AppsScriptJobStore.ts +0 -16
- package/src/navigation/AppsScriptContainer.ts +0 -47
- package/src/navigation/AppsScriptHistoryPipeline.ts +0 -29
- package/src/navigation/AppsScriptIframe.ts +0 -63
- package/src/navigation/HashProperty.ts +0 -20
- package/src/navigation/NavigationEntry.ts +0 -82
- package/src/navigation/NavigationLocation.ts +0 -54
- package/tests/AppsScriptClient.spec.ts +0 -322
- package/tests/AppsScriptClient.type.spec.ts +0 -52
- package/tests/AppsScriptContainer.spec.ts +0 -502
- package/tests/AppsScriptHistoryPipeline.spec.ts +0 -303
- package/tests/AppsScriptIframe.spec.ts +0 -452
- package/tests/AppsScriptJob.spec.ts +0 -83
- package/tests/AppsScriptJobQueue.spec.ts +0 -125
- package/tests/AppsScriptJobRunner.spec.ts +0 -361
- package/tests/HashProperty.spec.ts +0 -29
- package/tests/NavigationEntry.spec.ts +0 -507
- package/tests/NavigationLocation.spec.ts +0 -189
- package/tests/setup.ts +0 -15
- package/tsconfig.build.json +0 -14
- package/tsconfig.json +0 -6
- package/vitest.config.mts +0 -8
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.AppsScriptJobRunner = void 0;
|
|
4
|
-
class AppsScriptJobRunner {
|
|
5
|
-
queue;
|
|
6
|
-
jobs = [];
|
|
7
|
-
listeners = new Set();
|
|
8
|
-
MAX_CONCURRENT = 30;
|
|
9
|
-
running = false;
|
|
10
|
-
snapshot = null;
|
|
11
|
-
dirty = true;
|
|
12
|
-
constructor(queue) {
|
|
13
|
-
this.queue = queue;
|
|
14
|
-
this.queue.subscribe(this);
|
|
15
|
-
}
|
|
16
|
-
addJob(job) {
|
|
17
|
-
this.jobs.push(job);
|
|
18
|
-
this.notify();
|
|
19
|
-
}
|
|
20
|
-
getJobs() {
|
|
21
|
-
if (this.dirty || !this.snapshot) {
|
|
22
|
-
this.snapshot = [...this.jobs];
|
|
23
|
-
this.dirty = false;
|
|
24
|
-
}
|
|
25
|
-
return this.snapshot;
|
|
26
|
-
}
|
|
27
|
-
async run() {
|
|
28
|
-
if (this.running)
|
|
29
|
-
return;
|
|
30
|
-
this.running = true;
|
|
31
|
-
while (true) {
|
|
32
|
-
const runningCount = this.jobs.filter((j) => j.isRunning()).length;
|
|
33
|
-
if (runningCount >= this.MAX_CONCURRENT)
|
|
34
|
-
break;
|
|
35
|
-
const job = this.queue.dequeue();
|
|
36
|
-
if (!job)
|
|
37
|
-
break;
|
|
38
|
-
job.start();
|
|
39
|
-
this.notify();
|
|
40
|
-
job
|
|
41
|
-
.execute()
|
|
42
|
-
.then((result) => {
|
|
43
|
-
job.success(result);
|
|
44
|
-
const index = this.jobs.indexOf(job);
|
|
45
|
-
if (index !== -1) {
|
|
46
|
-
this.jobs.splice(index, 1);
|
|
47
|
-
}
|
|
48
|
-
})
|
|
49
|
-
.catch((error) => {
|
|
50
|
-
job.fail(error);
|
|
51
|
-
})
|
|
52
|
-
.finally(() => {
|
|
53
|
-
this.notify();
|
|
54
|
-
this.running = false;
|
|
55
|
-
this.run();
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
this.running = false;
|
|
59
|
-
}
|
|
60
|
-
remove(jobId) {
|
|
61
|
-
const index = this.jobs.findIndex((job) => job.id === jobId);
|
|
62
|
-
if (index === -1) {
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
this.jobs.splice(index, 1);
|
|
66
|
-
this.notify();
|
|
67
|
-
}
|
|
68
|
-
cancel(jobId) {
|
|
69
|
-
const job = this.jobs.find((job) => job.id === jobId);
|
|
70
|
-
if (!job || !job.cancel()) {
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
this.queue.remove(jobId);
|
|
74
|
-
this.jobs.splice(this.jobs.indexOf(job), 1);
|
|
75
|
-
this.notify();
|
|
76
|
-
}
|
|
77
|
-
retry(jobId) {
|
|
78
|
-
const job = this.jobs.find((j) => j.id === jobId);
|
|
79
|
-
if (job) {
|
|
80
|
-
this.remove(job.id);
|
|
81
|
-
this.queue.enqueue(job.label, job.execute);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
subscribe(listener) {
|
|
85
|
-
this.listeners.add(listener);
|
|
86
|
-
return () => this.listeners.delete(listener);
|
|
87
|
-
}
|
|
88
|
-
notify() {
|
|
89
|
-
this.dirty = true;
|
|
90
|
-
this.listeners.forEach((listener) => listener());
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
exports.AppsScriptJobRunner = AppsScriptJobRunner;
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { AppsScriptJob } from "./AppsScriptJob";
|
|
2
|
-
import { AppsScriptJobRunner } from "./AppsScriptJobRunner";
|
|
3
|
-
export interface AppsScriptJobStore {
|
|
4
|
-
subscribe: (listener: () => void) => () => void;
|
|
5
|
-
getSnapshot: () => AppsScriptJob<any>[];
|
|
6
|
-
}
|
|
7
|
-
export declare function appsScriptJobStore(runner: AppsScriptJobRunner): AppsScriptJobStore;
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.appsScriptJobStore = appsScriptJobStore;
|
|
4
|
-
function appsScriptJobStore(runner) {
|
|
5
|
-
return {
|
|
6
|
-
subscribe: runner.subscribe.bind(runner),
|
|
7
|
-
getSnapshot: runner.getJobs.bind(runner),
|
|
8
|
-
};
|
|
9
|
-
}
|
package/src/AppsScriptClient.ts
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { google } from "./google";
|
|
2
|
-
import { AppsScriptJob } from "./job/AppsScriptJob";
|
|
3
|
-
import { AppsScriptJobQueue } from "./job/AppsScriptJobQueue";
|
|
4
|
-
import { AppsScriptJobRunner } from "./job/AppsScriptJobRunner";
|
|
5
|
-
import { appsScriptJobStore } from "./job/AppsScriptJobStore";
|
|
6
|
-
|
|
7
|
-
type RpcResponse = {
|
|
8
|
-
contents: string;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
type RpcDefinition = {
|
|
12
|
-
args: unknown[];
|
|
13
|
-
result: unknown;
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
type RpcApp = Record<string, RpcDefinition>;
|
|
17
|
-
|
|
18
|
-
type AppsScriptClient<TApp extends RpcApp> = {
|
|
19
|
-
[K in keyof TApp]: TApp[K] extends {
|
|
20
|
-
args: infer TArgs extends unknown[];
|
|
21
|
-
result: infer TResult;
|
|
22
|
-
}
|
|
23
|
-
? (...args: TArgs) => Promise<TResult>
|
|
24
|
-
: never;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
export const appsScriptTransport = {
|
|
28
|
-
call(name: string, args: unknown[]): Promise<RpcResponse> {
|
|
29
|
-
return new Promise((resolve, reject) => {
|
|
30
|
-
google.script.run
|
|
31
|
-
.withSuccessHandler(resolve)
|
|
32
|
-
.withFailureHandler(reject)
|
|
33
|
-
[name](...args);
|
|
34
|
-
});
|
|
35
|
-
},
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
interface AppsScriptJobs {
|
|
39
|
-
start: <T>(label: string, execute: () => Promise<T>) => Promise<T>;
|
|
40
|
-
cancel: (jobId: string) => void;
|
|
41
|
-
retry: (jobId: string) => void;
|
|
42
|
-
subscribe: (listener: () => void) => () => void;
|
|
43
|
-
getSnapshot: () => AppsScriptJob<any>[];
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export function appsScriptClient<TApp extends RpcApp>(): {
|
|
47
|
-
client: AppsScriptClient<TApp>;
|
|
48
|
-
jobs: AppsScriptJobs;
|
|
49
|
-
} {
|
|
50
|
-
const jobQueue = new AppsScriptJobQueue();
|
|
51
|
-
const jobRunner = new AppsScriptJobRunner(jobQueue);
|
|
52
|
-
const jobStore = appsScriptJobStore(jobRunner);
|
|
53
|
-
|
|
54
|
-
const client = new Proxy(
|
|
55
|
-
{},
|
|
56
|
-
{
|
|
57
|
-
get(_target, property) {
|
|
58
|
-
if (typeof property !== "string") {
|
|
59
|
-
return undefined;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return (...args: unknown[]) => {
|
|
63
|
-
return jobQueue.enqueue(property, async () => {
|
|
64
|
-
const res = await appsScriptTransport.call(property, args);
|
|
65
|
-
return JSON.parse(res.contents);
|
|
66
|
-
});
|
|
67
|
-
};
|
|
68
|
-
},
|
|
69
|
-
},
|
|
70
|
-
) as AppsScriptClient<TApp>;
|
|
71
|
-
|
|
72
|
-
const jobs = {
|
|
73
|
-
start: jobQueue.enqueue.bind(jobQueue),
|
|
74
|
-
cancel: jobRunner.cancel.bind(jobRunner),
|
|
75
|
-
retry: jobRunner.retry.bind(jobRunner),
|
|
76
|
-
subscribe: jobStore.subscribe.bind(jobStore),
|
|
77
|
-
getSnapshot: jobStore.getSnapshot.bind(jobStore),
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
return { client, jobs };
|
|
81
|
-
}
|
package/src/google.ts
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
export type GoogleScriptRun = {
|
|
2
|
-
withSuccessHandler<T = unknown>(
|
|
3
|
-
callback: (result: T) => void,
|
|
4
|
-
): GoogleScriptRun;
|
|
5
|
-
withFailureHandler(callback: (error: Error) => void): GoogleScriptRun;
|
|
6
|
-
// GAS は run.任意のサーバー関数名(...) を呼ぶので index signature を持たせる
|
|
7
|
-
[serverFunctionName: string]: any;
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
export type GoogleScriptHistoryEvent = {
|
|
11
|
-
state: Record<string, unknown>;
|
|
12
|
-
location: {
|
|
13
|
-
hash: string;
|
|
14
|
-
parameter: Record<string, string>;
|
|
15
|
-
parameters: Record<string, string[]>;
|
|
16
|
-
};
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
export type GoogleScriptUrlLocation = {
|
|
20
|
-
hash: string;
|
|
21
|
-
parameter: Record<string, string>;
|
|
22
|
-
parameters: Record<string, string[]>;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
export type GoogleApi = {
|
|
26
|
-
script: {
|
|
27
|
-
run: GoogleScriptRun;
|
|
28
|
-
history: {
|
|
29
|
-
push: (
|
|
30
|
-
stateObject: Record<string, unknown>,
|
|
31
|
-
params: Record<string, unknown>,
|
|
32
|
-
hash: string,
|
|
33
|
-
) => void;
|
|
34
|
-
replace: (
|
|
35
|
-
stateObject: Record<string, unknown>,
|
|
36
|
-
params: Record<string, unknown>,
|
|
37
|
-
hash: string,
|
|
38
|
-
) => void;
|
|
39
|
-
setChangeHandler: (
|
|
40
|
-
callback: (event: GoogleScriptHistoryEvent) => void,
|
|
41
|
-
) => void;
|
|
42
|
-
};
|
|
43
|
-
url: {
|
|
44
|
-
getLocation: (
|
|
45
|
-
callback: (location: GoogleScriptUrlLocation) => void,
|
|
46
|
-
) => void;
|
|
47
|
-
};
|
|
48
|
-
};
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* google.script.* は Apps Script のランタイムが注入するグローバル。
|
|
53
|
-
* ここでは「型付きで参照」だけ提供する。
|
|
54
|
-
*/
|
|
55
|
-
export const google = (globalThis as any).google as GoogleApi;
|
package/src/index.ts
DELETED
package/src/job/AppsScriptJob.ts
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
export class AppsScriptJob<T> {
|
|
2
|
-
private _startedAt: Date | null = null;
|
|
3
|
-
private _endedAt: Date | null = null;
|
|
4
|
-
private _result: T | null = null;
|
|
5
|
-
private _error: unknown | null = null;
|
|
6
|
-
|
|
7
|
-
constructor(
|
|
8
|
-
public readonly label: string,
|
|
9
|
-
public readonly execute: () => Promise<T>,
|
|
10
|
-
private resolve: (value: T) => void,
|
|
11
|
-
private reject: (reason?: any) => void,
|
|
12
|
-
public readonly id: string = crypto.randomUUID(),
|
|
13
|
-
public readonly createdAt: Date = new Date(),
|
|
14
|
-
) {}
|
|
15
|
-
|
|
16
|
-
start() {
|
|
17
|
-
this._startedAt = new Date();
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
success(result: T) {
|
|
21
|
-
this._endedAt = new Date();
|
|
22
|
-
this._result = result;
|
|
23
|
-
this.resolve(result);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
fail(error: unknown) {
|
|
27
|
-
this._endedAt = new Date();
|
|
28
|
-
this._error = error;
|
|
29
|
-
this.reject(error);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
public get endedAt() {
|
|
33
|
-
return this._endedAt;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
public get result() {
|
|
37
|
-
return this._result;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
public get error() {
|
|
41
|
-
return this._error;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
public isPending() {
|
|
45
|
-
return this._startedAt === null;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
public isRunning() {
|
|
49
|
-
return this._startedAt !== null && this._endedAt === null;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
public isSuccess() {
|
|
53
|
-
return this._endedAt !== null && this._error === null;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
public isFailed() {
|
|
57
|
-
return this._endedAt !== null && this._error !== null;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
public get status() {
|
|
61
|
-
if (this.isPending()) return "pending";
|
|
62
|
-
if (this.isRunning()) return "running";
|
|
63
|
-
if (this.isSuccess()) return "success";
|
|
64
|
-
if (this.isFailed()) return "failed";
|
|
65
|
-
return "unknown";
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
public cancel(): boolean {
|
|
69
|
-
if (!this.isPending()) {
|
|
70
|
-
return false;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const error = new AppsScriptJobCancelledError();
|
|
74
|
-
this.fail(error);
|
|
75
|
-
|
|
76
|
-
return true;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export class AppsScriptJobCancelledError extends Error {
|
|
81
|
-
constructor() {
|
|
82
|
-
super("Job cancelled");
|
|
83
|
-
this.name = "AppsScriptJobCancelledError";
|
|
84
|
-
}
|
|
85
|
-
}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { AppsScriptJob } from "./AppsScriptJob";
|
|
2
|
-
|
|
3
|
-
export interface RunnableJob {
|
|
4
|
-
run(): Promise<void>;
|
|
5
|
-
addJob<T>(job: AppsScriptJob<T>): void;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export class AppsScriptJobQueue {
|
|
9
|
-
private jobs: AppsScriptJob<any>[] = [];
|
|
10
|
-
private listeners: Set<RunnableJob> = new Set();
|
|
11
|
-
|
|
12
|
-
public enqueue<T>(label: string, execute: () => Promise<T>): Promise<T> {
|
|
13
|
-
const promise = new Promise<T>((resolve, reject) => {
|
|
14
|
-
// キューにジョブを追加
|
|
15
|
-
const job = new AppsScriptJob<T>(label, execute, resolve, reject);
|
|
16
|
-
this.jobs.push(job);
|
|
17
|
-
this.notify(job);
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
return promise;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
public dequeue(): AppsScriptJob<any> | undefined {
|
|
24
|
-
const job = this.jobs.shift();
|
|
25
|
-
if (!job) return undefined;
|
|
26
|
-
return job;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
private notify<T>(job: AppsScriptJob<T>) {
|
|
30
|
-
this.listeners.forEach((listener) => {
|
|
31
|
-
listener.addJob(job);
|
|
32
|
-
listener.run();
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
public subscribe(listener: RunnableJob) {
|
|
37
|
-
this.listeners.add(listener);
|
|
38
|
-
return () => this.listeners.delete(listener);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
remove(jobId: string) {
|
|
42
|
-
const index = this.jobs.findIndex((job) => job.id === jobId);
|
|
43
|
-
|
|
44
|
-
if (index !== -1) {
|
|
45
|
-
this.jobs.splice(index, 1);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import { AppsScriptJob } from "./AppsScriptJob";
|
|
2
|
-
import { AppsScriptJobQueue, RunnableJob } from "./AppsScriptJobQueue";
|
|
3
|
-
|
|
4
|
-
export class AppsScriptJobRunner implements RunnableJob {
|
|
5
|
-
private jobs: AppsScriptJob<any>[] = [];
|
|
6
|
-
private listeners: Set<() => void> = new Set();
|
|
7
|
-
private MAX_CONCURRENT = 30;
|
|
8
|
-
private running = false;
|
|
9
|
-
|
|
10
|
-
private snapshot: AppsScriptJob<any>[] | null = null;
|
|
11
|
-
private dirty = true;
|
|
12
|
-
|
|
13
|
-
constructor(public readonly queue: AppsScriptJobQueue) {
|
|
14
|
-
this.queue.subscribe(this);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
public addJob<T>(job: AppsScriptJob<T>) {
|
|
18
|
-
this.jobs.push(job);
|
|
19
|
-
this.notify();
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
public getJobs() {
|
|
23
|
-
if (this.dirty || !this.snapshot) {
|
|
24
|
-
this.snapshot = [...this.jobs];
|
|
25
|
-
this.dirty = false;
|
|
26
|
-
}
|
|
27
|
-
return this.snapshot;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
public async run(): Promise<void> {
|
|
31
|
-
if (this.running) return;
|
|
32
|
-
this.running = true;
|
|
33
|
-
|
|
34
|
-
while (true) {
|
|
35
|
-
const runningCount = this.jobs.filter((j) => j.isRunning()).length;
|
|
36
|
-
|
|
37
|
-
if (runningCount >= this.MAX_CONCURRENT) break;
|
|
38
|
-
|
|
39
|
-
const job = this.queue.dequeue();
|
|
40
|
-
if (!job) break;
|
|
41
|
-
|
|
42
|
-
job.start();
|
|
43
|
-
this.notify();
|
|
44
|
-
|
|
45
|
-
job
|
|
46
|
-
.execute()
|
|
47
|
-
.then((result) => {
|
|
48
|
-
job.success(result);
|
|
49
|
-
const index = this.jobs.indexOf(job);
|
|
50
|
-
if (index !== -1) {
|
|
51
|
-
this.jobs.splice(index, 1);
|
|
52
|
-
}
|
|
53
|
-
})
|
|
54
|
-
.catch((error) => {
|
|
55
|
-
job.fail(error);
|
|
56
|
-
})
|
|
57
|
-
.finally(() => {
|
|
58
|
-
this.notify();
|
|
59
|
-
this.running = false;
|
|
60
|
-
this.run();
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
this.running = false;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
public remove(jobId: string) {
|
|
68
|
-
const index = this.jobs.findIndex((job) => job.id === jobId);
|
|
69
|
-
|
|
70
|
-
if (index === -1) {
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
this.jobs.splice(index, 1);
|
|
75
|
-
this.notify();
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
public cancel(jobId: string) {
|
|
79
|
-
const job = this.jobs.find((job) => job.id === jobId);
|
|
80
|
-
|
|
81
|
-
if (!job || !job.cancel()) {
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
this.queue.remove(jobId);
|
|
86
|
-
this.jobs.splice(this.jobs.indexOf(job), 1);
|
|
87
|
-
this.notify();
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
public retry(jobId: AppsScriptJob<any>["id"]) {
|
|
91
|
-
const job = this.jobs.find((j) => j.id === jobId);
|
|
92
|
-
if (job) {
|
|
93
|
-
this.remove(job.id);
|
|
94
|
-
this.queue.enqueue(job.label, job.execute);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
public subscribe(listener: () => void) {
|
|
99
|
-
this.listeners.add(listener);
|
|
100
|
-
return () => this.listeners.delete(listener);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
private notify() {
|
|
104
|
-
this.dirty = true;
|
|
105
|
-
this.listeners.forEach((listener) => listener());
|
|
106
|
-
}
|
|
107
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { AppsScriptJob } from "./AppsScriptJob";
|
|
2
|
-
import { AppsScriptJobRunner } from "./AppsScriptJobRunner";
|
|
3
|
-
|
|
4
|
-
export interface AppsScriptJobStore {
|
|
5
|
-
subscribe: (listener: () => void) => () => void;
|
|
6
|
-
getSnapshot: () => AppsScriptJob<any>[];
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function appsScriptJobStore(
|
|
10
|
-
runner: AppsScriptJobRunner,
|
|
11
|
-
): AppsScriptJobStore {
|
|
12
|
-
return {
|
|
13
|
-
subscribe: runner.subscribe.bind(runner),
|
|
14
|
-
getSnapshot: runner.getJobs.bind(runner),
|
|
15
|
-
};
|
|
16
|
-
}
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { google } from "../google";
|
|
2
|
-
|
|
3
|
-
import { NavigationEntry } from "./NavigationEntry";
|
|
4
|
-
|
|
5
|
-
export class AppsScriptContainer {
|
|
6
|
-
private constructor(private entry: NavigationEntry) {}
|
|
7
|
-
|
|
8
|
-
static load(callback: (container: AppsScriptContainer) => void) {
|
|
9
|
-
google.script.url.getLocation((location) => {
|
|
10
|
-
callback(new AppsScriptContainer(NavigationEntry.fromGoogle(location)));
|
|
11
|
-
});
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
observe(handler: (entry: NavigationEntry) => void) {
|
|
15
|
-
google.script.history.setChangeHandler((event) => {
|
|
16
|
-
const entry = NavigationEntry.fromGoogle(event);
|
|
17
|
-
|
|
18
|
-
this.entry = entry;
|
|
19
|
-
|
|
20
|
-
handler(entry);
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
sync(entry: NavigationEntry) {
|
|
25
|
-
if (this.entry.equals(entry)) {
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
this.entry = entry;
|
|
30
|
-
|
|
31
|
-
const parameters: Record<string, string[]> = {};
|
|
32
|
-
|
|
33
|
-
for (const key of new Set(entry.location.searchParams.keys())) {
|
|
34
|
-
parameters[key] = entry.location.searchParams.getAll(key);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
google.script.history.push(
|
|
38
|
-
entry.state,
|
|
39
|
-
parameters,
|
|
40
|
-
entry.location.hash.normalize(),
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
current() {
|
|
45
|
-
return this.entry;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { AppsScriptContainer } from "./AppsScriptContainer";
|
|
2
|
-
import { AppsScriptIframe } from "./AppsScriptIframe";
|
|
3
|
-
|
|
4
|
-
export class AppsScriptHistoryPipeline {
|
|
5
|
-
constructor(
|
|
6
|
-
private readonly container: AppsScriptContainer,
|
|
7
|
-
private readonly iframe: AppsScriptIframe,
|
|
8
|
-
) {}
|
|
9
|
-
|
|
10
|
-
static create(callback: (pipeline: AppsScriptHistoryPipeline) => void) {
|
|
11
|
-
AppsScriptContainer.load((container) => {
|
|
12
|
-
callback(
|
|
13
|
-
new AppsScriptHistoryPipeline(container, new AppsScriptIframe()),
|
|
14
|
-
);
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
sync() {
|
|
19
|
-
this.iframe.sync(this.container.current());
|
|
20
|
-
|
|
21
|
-
this.container.observe((entry) => {
|
|
22
|
-
this.iframe.sync(entry);
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
return this.iframe.observe((entry) => {
|
|
26
|
-
this.container.sync(entry);
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
}
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { HashProperty } from "./HashProperty";
|
|
2
|
-
import { NavigationEntry } from "./NavigationEntry";
|
|
3
|
-
import { NavigationLocation } from "./NavigationLocation";
|
|
4
|
-
|
|
5
|
-
export class AppsScriptIframe {
|
|
6
|
-
private entry: NavigationEntry;
|
|
7
|
-
|
|
8
|
-
constructor() {
|
|
9
|
-
this.entry = this.read();
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
observe(handler: (entry: NavigationEntry) => void) {
|
|
13
|
-
const listener = () => {
|
|
14
|
-
const entry = this.read();
|
|
15
|
-
|
|
16
|
-
this.entry = entry;
|
|
17
|
-
|
|
18
|
-
handler(entry);
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
window.addEventListener("hashchange", listener);
|
|
22
|
-
window.addEventListener("popstate", listener);
|
|
23
|
-
|
|
24
|
-
return () => {
|
|
25
|
-
window.removeEventListener("hashchange", listener);
|
|
26
|
-
window.removeEventListener("popstate", listener);
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
sync(entry: NavigationEntry) {
|
|
31
|
-
if (this.entry.equals(entry)) {
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const hash = entry.location.hash.normalize();
|
|
36
|
-
const search = entry.location.searchParams.toString();
|
|
37
|
-
const nextHash = search ? `${hash}?${search}` : hash;
|
|
38
|
-
|
|
39
|
-
history.replaceState(entry.state, "", nextHash);
|
|
40
|
-
|
|
41
|
-
this.entry = entry;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
current() {
|
|
45
|
-
return this.entry;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
private read() {
|
|
49
|
-
const normalized = new HashProperty(window.location.hash)
|
|
50
|
-
.toString()
|
|
51
|
-
.replace(/^#/, "");
|
|
52
|
-
|
|
53
|
-
const [path = "/", query = ""] = normalized.split("?");
|
|
54
|
-
|
|
55
|
-
return new NavigationEntry(
|
|
56
|
-
history.state ?? {},
|
|
57
|
-
new NavigationLocation(
|
|
58
|
-
new HashProperty(path),
|
|
59
|
-
new URLSearchParams(query),
|
|
60
|
-
),
|
|
61
|
-
);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
export class HashProperty {
|
|
2
|
-
constructor(private readonly value: string) {}
|
|
3
|
-
|
|
4
|
-
normalize() {
|
|
5
|
-
if (!this.value) return "#/";
|
|
6
|
-
if (this.value.startsWith("#/")) return this.value;
|
|
7
|
-
if (this.value.startsWith("#")) return `#/${this.value.slice(1)}`;
|
|
8
|
-
if (this.value.startsWith("/")) return `#${this.value}`;
|
|
9
|
-
|
|
10
|
-
return `#/${this.value}`;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
equals(other: HashProperty) {
|
|
14
|
-
return this.normalize() === other.normalize();
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
toString() {
|
|
18
|
-
return this.normalize();
|
|
19
|
-
}
|
|
20
|
-
}
|