@syncmatters/script-api 1.0.22 → 1.0.24

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/api.d.ts CHANGED
@@ -10,7 +10,7 @@ export * from "./lib/sync.js";
10
10
  export * from "./lib/sync-group.js";
11
11
  export * from "./lib/utilities/kv-store/types.js";
12
12
  export * from "./lib/utilities/rate-limiter/rate-limiter.js";
13
- export type { HttpClient, HttpClientOptions, HttpRequest, } from "./lib/utilities/http-client/types.js";
13
+ export type { HttpClient, HttpClientOptions, HttpFullResponse, HttpRequest, } from "./lib/utilities/http-client/types.js";
14
14
  export type JsonValuesReader = InstanceType<typeof JVR>;
15
15
  export type JsonValuesWriter = InstanceType<typeof JVW.JsonValuesWriter>;
16
16
  export type WriteBehaviour = JVW.WriteBehaviour;
@@ -141,7 +141,7 @@ export interface CacheQueryOneOptions {
141
141
  name: string;
142
142
  values: string[];
143
143
  };
144
- key?: string;
144
+ keys?: string[];
145
145
  };
146
146
  propertyFilter?: {
147
147
  metaOnly?: boolean;
@@ -292,8 +292,6 @@ export interface Row {
292
292
  srcObjectId?: string;
293
293
  srcRowId: string;
294
294
  };
295
- /** if filters requested rows be returned from a 'checkpoint', the final row in the stream will return the next checkpoint here */
296
- checkpoint?: string | undefined;
297
295
  }
298
296
  /** Iterator is returned in response to Query() operations */
299
297
  export interface QueryIterator<T> extends AsyncIterableIterator<T> {
@@ -438,11 +436,11 @@ export interface ObjectSettingMeta {
438
436
  advanced?: boolean;
439
437
  readonly?: boolean;
440
438
  mandatory?: boolean;
441
- displayIf?: string;
439
+ displayIf?: "none" | "other_setting_has_value" | "other_setting_is_not_empty";
442
440
  displayIfSetting?: string;
443
441
  displayIfValues?: string[];
444
- type?: string;
445
- control?: string;
442
+ type?: "string" | "boolean" | "number" | "string_array" | "connection_uid_template" | "google_picker";
443
+ control?: "singlelinetext" | "textarea" | "checkbox" | "switch" | "integer" | "url" | "datepicker" | "datetimepicker" | "filepicker" | "drivefolder" | "drivefile" | "select" | "multiselect" | "button" | "timezone";
446
444
  options?: ObjectSettingMetaOption[];
447
445
  default?: unknown;
448
446
  constraints?: ObjectFieldConstraints;
@@ -452,11 +450,6 @@ export interface ObjectSettingMetaOption {
452
450
  id: string;
453
451
  name?: string;
454
452
  order?: string;
455
- /**
456
- * on an option of a prerequisite (or defines_version) connection setting: `false` makes a connection
457
- * with this option selected single-phase - discover() is never called and its metadata is collected on
458
- * first use, as for a connector without discovery. Absent inherits the connector's `discovery` flag.
459
- */
460
453
  discovery?: boolean;
461
454
  }
462
455
  export interface ObjectRowFilter {
@@ -676,7 +669,7 @@ export interface ObjectRelationship {
676
669
  data?: any;
677
670
  }
678
671
  /** IndexType holds a data index definition */
679
- export type IndexType = "unknown" | "jsonpath expression";
672
+ export type IndexType = "jsonpath expression";
680
673
  /** ObjectIndex specifies an index to be added to cached connector data. */
681
674
  export interface ObjectIndex {
682
675
  name: string;
package/lib/context.d.ts CHANGED
@@ -20,6 +20,18 @@ export interface Context {
20
20
  parameters: {
21
21
  [name: string]: any;
22
22
  };
23
+ /** set only when the run was triggered by an event */
24
+ event?: {
25
+ failedAttempts: number;
26
+ connectionId: string;
27
+ typeId: string;
28
+ timestamp: number;
29
+ object?: {
30
+ id: string;
31
+ key?: string;
32
+ };
33
+ data?: any;
34
+ };
23
35
  connections: ContextMapFactories<ConnectionMap, Connection>;
24
36
  syncGroups: ContextMapFactories<SyncGroupMap, SyncGroup>;
25
37
  state: {
@@ -15,6 +15,7 @@ export declare enum ErrorCode {
15
15
  InvalidRelationshipId = "SCRIPT_INVALID_RELATIONSHIP_ID",
16
16
  InvalidRelationshipType = "SCRIPT_INVALID_RELATIONSHIP_TYPE",
17
17
  ResourceAlreadyClosed = "SCRIPT_RESOURCE_ALREADY_CLOSED",
18
+ FileProviderShallowClone = "SCRIPT_FILE_PROVIDER_SHALLOW_CLONE",
18
19
  InvalidEmailRecipients = "SCRIPT_INVALID_EMAIL_RECIPIENTS",
19
20
  InvalidEmailSubject = "SCRIPT_INVALID_EMAIL_SUBJECT",
20
21
  InvalidEmailBody = "SCRIPT_INVALID_EMAIL_BODY",
@@ -9,7 +9,7 @@ export declare class CsvReaderCsvParse implements CsvReader {
9
9
  header(): Promise<string[]>;
10
10
  jsDoc(): Promise<string>;
11
11
  verifyHeader(required: string[]): Promise<void>;
12
- rows(): AsyncIterableIterator<any>;
12
+ rows(): AsyncIterableIterator<string[]>;
13
13
  rowsWithProps(): AsyncIterableIterator<{
14
14
  [name: string]: string;
15
15
  }>;
@@ -26,14 +26,21 @@ export interface CsvReaderOptions {
26
26
  escapeChar?: string;
27
27
  /** number of lines to skip at the beginning of the file */
28
28
  skipLines?: number;
29
- /** parse engine to use (default: 'fast-csv') */
29
+ /**
30
+ * parse engine to use (default: 'csv-parse'). 'fast-csv' was the default until csv-parse (not strict) was made to
31
+ * read as it does; it remains as a fallback while the switch beds in
32
+ */
30
33
  parseEngine?: "fast-csv" | "csv-parse";
34
+ /**
35
+ * csv-parse engine only: true rejects ragged rows, stray or misplaced quotes and whitespace-only lines, reporting
36
+ * the line number; false (default) tolerates them as the fast-csv engine does
37
+ */
38
+ strict?: boolean;
31
39
  }
32
40
  /**
33
41
  * CsvReader parses a csv and emits an async iterator for accessing the next row. The row is an array
34
- * allowing access to the fields by index or, if the csv has a header, by header name:
42
+ * allowing access to the fields by index, rowsWithProps allows access by header name:
35
43
  * for await (const row of reader.rows()) {
36
- * var field1ByName = row["my col name"];
37
44
  * var field1ByIndex = row[0];
38
45
  * }
39
46
  */
@@ -41,7 +48,7 @@ export interface CsvReader {
41
48
  header(): Promise<string[]>;
42
49
  jsDoc(): Promise<string>;
43
50
  verifyHeader(required: string[]): Promise<void>;
44
- rows(): AsyncIterableIterator<any>;
51
+ rows(): AsyncIterableIterator<string[]>;
45
52
  rowsWithProps(): AsyncIterableIterator<{
46
53
  [name: string]: string;
47
54
  }>;
@@ -1,5 +1,7 @@
1
1
  import { FileProvider } from "./file-provider.js";
2
+ import { kIsClosed } from "./file-provider-internal.js";
2
3
  export declare class FileProviderBuffer implements FileProvider {
4
+ #private;
3
5
  constructor(source: Buffer);
4
6
  length(): Promise<number>;
5
7
  stream(encoding?: "utf8" | "utf16le" | "ucs2" | "latin1"): Promise<NodeJS.ReadableStream>;
@@ -7,5 +9,8 @@ export declare class FileProviderBuffer implements FileProvider {
7
9
  blob(fileName?: string): Promise<Blob>;
8
10
  save(path?: string): Promise<string>;
9
11
  close(): Promise<void>;
12
+ clone(): FileProviderBuffer;
13
+ equals(other: FileProvider): Promise<boolean>;
14
+ [kIsClosed](): boolean;
10
15
  toJSON(): string;
11
16
  }
@@ -1,4 +1,28 @@
1
+ import fs from "node:fs";
1
2
  import { FileProvider } from "./file-provider.js";
3
+ import { kIsClosed } from "./file-provider-internal.js";
4
+ /**
5
+ * One record per underlying file, shared by a deleteOnClose provider and every clone of it (including
6
+ * clones of clones). The file is unlinked when the last holder closes.
7
+ */
8
+ interface SharedFile {
9
+ path: string;
10
+ holders: number;
11
+ }
12
+ export interface Privates {
13
+ file: SharedFile;
14
+ size?: number;
15
+ deleteOnClose: boolean;
16
+ stream: fs.ReadStream | null;
17
+ closed: boolean;
18
+ }
19
+ type Unlink = (path: string, callback: (err: NodeJS.ErrnoException | null) => void) => void;
20
+ /**
21
+ * Releases a provider the runtime collected without close(): marks it closed, destroys any open stream and, for a
22
+ * deleteOnClose file, gives up this holder and unlinks the file when it was the last. Runs in a finalizer, so it
23
+ * never awaits and swallows every error; the unlink is fire-and-forget.
24
+ */
25
+ export declare function releaseUnclosed(p: Privates, unlink?: Unlink): void;
2
26
  export declare class FileProviderDisk implements FileProvider {
3
27
  constructor(path: string, deleteOnClose?: boolean);
4
28
  length(): Promise<number>;
@@ -8,5 +32,9 @@ export declare class FileProviderDisk implements FileProvider {
8
32
  blob(fileName?: string): Promise<Blob>;
9
33
  save(path?: string): Promise<string>;
10
34
  close(): Promise<void>;
35
+ clone(): FileProviderDisk;
36
+ equals(other: FileProvider): Promise<boolean>;
37
+ [kIsClosed](): boolean;
11
38
  toJSON(): string;
12
39
  }
40
+ export {};
@@ -0,0 +1,10 @@
1
+ import type { FileProvider } from "./file-provider.js";
2
+ /**
3
+ * Internal, non-interface hook shared by the platform FileProvider classes so that equals() can
4
+ * tell whether the other side is closed without calling it (a closed deleteOnClose disk provider
5
+ * fails with ENOENT rather than ResourceAlreadyClosed). Not exported from the package.
6
+ */
7
+ export declare const kIsClosed: unique symbol;
8
+ export declare function providerIsClosed(provider: FileProvider): boolean;
9
+ /** Compares two open providers byte for byte: length first, then both streams chunk by chunk. */
10
+ export declare function providerBytesEqual(a: FileProvider, b: FileProvider): Promise<boolean>;
@@ -0,0 +1,21 @@
1
+ import { FileProvider } from "./file-provider.js";
2
+ import { kIsClosed } from "./file-provider-internal.js";
3
+ /**
4
+ * What utilities.clone() puts in place of a FileProvider by default. It holds no file and no state: it serialises
5
+ * like a provider, closes as a no-op, and every read throws a ScriptError (FileProviderShallowClone) naming the
6
+ * option that makes a real clone. It counts as a FileProvider (isFileProvider() is true) so that a shim which reaches
7
+ * a connector fails on its first read rather than being uploaded as the text "[object FileProvider]".
8
+ */
9
+ export declare class FileProviderShim implements FileProvider {
10
+ length(): Promise<number>;
11
+ stream(_encoding?: "utf8" | "utf16le" | "ucs2" | "latin1"): Promise<NodeJS.ReadableStream>;
12
+ close(): Promise<void>;
13
+ save(_path?: string): Promise<string>;
14
+ formDataValue(_encoding?: "utf8" | "utf16le" | "ucs2" | "latin1"): Promise<unknown>;
15
+ blob(_fileName?: string): Promise<Blob>;
16
+ clone(): FileProvider;
17
+ equals(_other: FileProvider): Promise<boolean>;
18
+ /** a shim has no content, so a real provider's equals() treats it as closed and resolves false */
19
+ [kIsClosed](): boolean;
20
+ toJSON(): string;
21
+ }
@@ -1,5 +1,7 @@
1
1
  import { FileProvider } from "./file-provider.js";
2
+ import { kIsClosed } from "./file-provider-internal.js";
2
3
  export declare class FileProviderString implements FileProvider {
4
+ #private;
3
5
  constructor(source: string);
4
6
  length(): Promise<number>;
5
7
  stream(encoding?: "utf8" | "utf16le" | "ucs2" | "latin1"): Promise<NodeJS.ReadableStream>;
@@ -7,5 +9,8 @@ export declare class FileProviderString implements FileProvider {
7
9
  blob(fileName?: string): Promise<Blob>;
8
10
  save(path?: string): Promise<string>;
9
11
  close(): Promise<void>;
12
+ clone(): FileProviderString;
13
+ equals(other: FileProvider): Promise<boolean>;
14
+ [kIsClosed](): boolean;
10
15
  toJSON(): string;
11
16
  }
@@ -6,6 +6,24 @@ export interface FileProvider {
6
6
  formDataValue(encoding?: "utf8" | "utf16le" | "ucs2" | "latin1"): Promise<unknown>;
7
7
  blob(fileName?: string): Promise<Blob>;
8
8
  toJSON(): string;
9
+ /**
10
+ * Returns an independent provider over the same bytes. The clone must be closed like any other
11
+ * provider: closing the original does not close the clone, and closing the clone does not close
12
+ * the original. A clone of a string or buffer provider re-wraps the source (no disk is shared).
13
+ * A clone of a disk provider created with deleteOnClose shares the underlying file by reference
14
+ * count: the file is removed only when the original and every clone of it have been closed. A
15
+ * provider dropped without close is released when the runtime collects it, which may be much later.
16
+ * utilities.clone(value, { withFileProviders: true }) calls this for every provider in value; the
17
+ * default utilities.clone(value) does not, and puts a shallow-clone shim in its place.
18
+ * Throws ResourceAlreadyClosed when this provider has been closed.
19
+ */
20
+ clone(): FileProvider;
21
+ /**
22
+ * Resolves true when both providers hold the same bytes. Compares length() first, then streams both
23
+ * and compares chunk by chunk without buffering whole files. Resolves false, rather than throwing,
24
+ * when either side has been closed.
25
+ */
26
+ equals(other: FileProvider): Promise<boolean>;
9
27
  }
10
28
  export interface FileProviderOptions {
11
29
  file?: {
@@ -18,4 +36,10 @@ export interface FileProviderOptions {
18
36
  export declare function fileProvider(options: FileProviderOptions): FileProvider;
19
37
  export declare namespace fileProvider {
20
38
  var isFileProvider: (value: unknown) => value is FileProvider;
39
+ var closeAll: typeof closeFileProviders;
21
40
  }
41
+ /**
42
+ * Closes every FileProvider found in value, walking plain objects and arrays. Close errors are swallowed: this is
43
+ * for releasing providers the caller owns (for example the clones inside a snapshot) once it is done with them.
44
+ */
45
+ export declare function closeFileProviders(value: unknown): Promise<void>;
@@ -36,6 +36,14 @@ export interface HttpRequest {
36
36
  fullResponse?: boolean;
37
37
  fileResponse?: boolean;
38
38
  }
39
+ /** HttpFullResponse is the execute() response when HttpRequest option fullResponse is true */
40
+ export interface HttpFullResponse {
41
+ body?: any;
42
+ headers: {
43
+ [name: string]: string;
44
+ };
45
+ statusCode: number;
46
+ }
39
47
  /** HttpCanRetry is an optional interface allowing a connector to assess whether a retry is allowed */
40
48
  export interface HttpCanRetry {
41
49
  (err: HttpError, method: HttpMethod, url: string, httpCode: number, responseHeaders: {
@@ -55,7 +63,6 @@ export interface HttpClientOptions {
55
63
  errMessageFormat?: "default" | "concise";
56
64
  canRetry?: HttpCanRetry;
57
65
  onError?: (err: HttpError, method: HttpMethod, url: string, httpCode: number, reqJson?: unknown) => void;
58
- onSuccess?: (method: string, url: string, httpCode: number, reqJson?: unknown) => void;
59
66
  }
60
67
  export interface HttpClient {
61
68
  execute: (request: HttpRequest) => Promise<any>;
@@ -21,10 +21,24 @@ import * as TypeUtils from "./type-utils/type-utils.js";
21
21
  import * as JsonUtils from "./json-utils/index.js";
22
22
  import * as UpsertFlags from "./upsert-flags/upsert-flags.js";
23
23
  import { KvStore } from "@syncmatters/script-api";
24
+ export interface CloneOptions {
25
+ /**
26
+ * Clone each FileProvider with FileProvider.clone(), so the copy can read the file. Each cloned provider must be
27
+ * closed like any other. Without this, each provider in the copy is a shallow-clone shim.
28
+ */
29
+ withFileProviders?: boolean;
30
+ }
31
+ /**
32
+ * Deep copy. By default each FileProvider in value is replaced with a shallow-clone shim: it serialises like a
33
+ * provider and needs no close, but any read throws (ErrorCode.FileProviderShallowClone). That suits a snapshot for
34
+ * logging or tracing. Pass { withFileProviders: true } to clone the providers too (see FileProvider.clone()): the copy
35
+ * can read the files and each cloned provider must be closed.
36
+ */
37
+ declare function clone<T>(value: T, options?: CloneOptions): T;
24
38
  export declare const utilities: {
25
39
  arrayOfStr: typeof arrayOfStrings;
26
40
  arrayOfNum: typeof arrayOfNumbers;
27
- clone: <T>(value: T) => T;
41
+ clone: typeof clone;
28
42
  csvReader: (options: CsvReaderOptions) => import("./csv-reader/types.js").CsvReader;
29
43
  csvWriter: (options: CsvWriterOptions) => CsvWriter;
30
44
  dateFormat: typeof dateFormat;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@syncmatters/script-api",
3
- "version": "1.0.22",
3
+ "version": "1.0.24",
4
4
  "description": "TypeScript type definitions for the SyncMatters script API (types only - scripts execute on the SyncMatters platform)",
5
5
  "types": "./index.d.ts",
6
6
  "exports": {
@@ -28,7 +28,7 @@
28
28
  "license": "MIT",
29
29
  "author": "SyncMatters",
30
30
  "homepage": "https://syncmatters.com",
31
- "typesContentHash": "b96f39108ef358c683102abb6eaeae9b47358feb140dcdc03fea91f41410102a",
31
+ "typesContentHash": "f3ff131064f3d7de6653d9bb2a7766b1ea8e918cd38aacbf0f73e6b37dfe13f8",
32
32
  "dependencies": {
33
33
  "@types/node": "*"
34
34
  }
@@ -373,6 +373,13 @@ export interface UpsertOptions {
373
373
  meta: API.ObjectMeta;
374
374
  withIssueSimulators?: boolean;
375
375
  withUpsertFieldOptions?: API.JsonValuePath;
376
+ /**
377
+ * Path of a file field (`type: "file"`) on the object's upsert fields that the returned insert and update data
378
+ * should populate with a FileProvider. The harness sends it on the upsert and delete tests' prepare calls whenever
379
+ * the object has a file field, preferring one with `constraints.mandatory: "add"`; a hook that ignores it loses
380
+ * nothing. It is sent on every upsertPrepare call whenever the object has a file field, including the upsertClean
381
+ * test's (the one with `withIssueSimulators`).
382
+ */
376
383
  withFileField?: API.JsonValuePath;
377
384
  }
378
385
  /** Options passed when cleaning up after an upsert test. */
@@ -382,13 +389,25 @@ export interface UpsertCleanupOptions {
382
389
  }
383
390
  /** Data returned when requesting data that will be used for an upsert test. */
384
391
  export interface UpsertData {
385
- /** data to pass in the upsert call (ideally for an add operation) */
392
+ /**
393
+ * data to pass in the upsert call (ideally for an add operation). Return a fresh FileProvider in each call: the
394
+ * harness passes the connector a clone (`API.utilities.clone(insert, { withFileProviders: true })`, so each provider
395
+ * is a readable clone) and closes the originals and its clones after verifying.
396
+ */
386
397
  insert?: unknown;
387
- /** a simple update that may be made to the added row (the test harness will copy over the key from the add response) */
398
+ /**
399
+ * a simple update that may be made to the added row (the test harness will copy over the key from the add response).
400
+ * FileProviders here follow the same rule as `insert`: fresh per call, cloned before writing, closed after verifying.
401
+ */
388
402
  update?: unknown;
389
403
  /** if requested, include 'bad data' examples that will trigger upsertClean issues */
390
404
  issueSimulators?: Array<UpsertIssueSimulator>;
391
- /** fields to check to verify the insert/update was successful */
405
+ /**
406
+ * fields to check to verify the insert/update was successful. The same paths are checked on the update row, so a
407
+ * listed field must be set in both `insert` and `update`. A file field compares by bytes through
408
+ * `FileProvider.equals()`: the queried value must be a FileProvider holding the same content. Listing a file field
409
+ * here (or in `fields`) makes the query-back download its content; the harness never adds it on its own.
410
+ */
392
411
  verifyFields?: Array<API.JsonValuePath>;
393
412
  /** optionally specify other fields to include when querying the data back for validation */
394
413
  fields?: Array<API.JsonValuePath>;