fms-odata-js 0.3.0
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/CHANGELOG.md +160 -0
- package/LICENSE +22 -0
- package/README.md +381 -0
- package/dist/batch.d.ts +154 -0
- package/dist/batch.d.ts.map +1 -0
- package/dist/client.d.ts +160 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/containers.d.ts +190 -0
- package/dist/containers.d.ts.map +1 -0
- package/dist/entity.d.ts +149 -0
- package/dist/entity.d.ts.map +1 -0
- package/dist/errors.d.ts +69 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/fms-odata.esm.js +2201 -0
- package/dist/fms-odata.esm.min.js +6 -0
- package/dist/fms-odata.iife.min.js +6 -0
- package/dist/http.d.ts +51 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/metadata.d.ts +85 -0
- package/dist/metadata.d.ts.map +1 -0
- package/dist/query.d.ts +198 -0
- package/dist/query.d.ts.map +1 -0
- package/dist/scripts.d.ts +114 -0
- package/dist/scripts.d.ts.map +1 -0
- package/dist/types.d.ts +26 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/url.d.ts +49 -0
- package/dist/url.d.ts.map +1 -0
- package/package.json +61 -0
- package/src/batch.ts +477 -0
- package/src/client.ts +268 -0
- package/src/containers.ts +457 -0
- package/src/entity.ts +334 -0
- package/src/errors.ts +134 -0
- package/src/http.ts +190 -0
- package/src/index.ts +81 -0
- package/src/metadata.ts +354 -0
- package/src/query.ts +442 -0
- package/src/scripts.ts +279 -0
- package/src/types.ts +30 -0
- package/src/url.ts +98 -0
package/dist/batch.d.ts
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* $batch multipart composer and response parser (M6).
|
|
3
|
+
*
|
|
4
|
+
* OData $batch allows multiple operations in a single HTTP round-trip.
|
|
5
|
+
* A batch consists of:
|
|
6
|
+
* - Read operations (GET) at the top level
|
|
7
|
+
* - Changesets (atomic groups of writes: POST, PATCH, DELETE)
|
|
8
|
+
*
|
|
9
|
+
* FMS endpoint: POST /fmi/odata/v4/{db}/$batch
|
|
10
|
+
* Content-Type: multipart/mixed; boundary=batch_<uuid>
|
|
11
|
+
*
|
|
12
|
+
* The implementation generates unique boundaries using crypto.randomUUID()
|
|
13
|
+
* (available in Node 18+ and modern browsers), composes the multipart
|
|
14
|
+
* body, and parses the multipart response back into per-operation results.
|
|
15
|
+
*
|
|
16
|
+
* ⚠️ KNOWN FMS LIMITATIONS (pending investigation — tested against FMS 21.x):
|
|
17
|
+
*
|
|
18
|
+
* 1. GET operations must NOT appear before a changeset.
|
|
19
|
+
* Placing a read op before a changeset causes FMS to return HTTP 400:
|
|
20
|
+
* "Expected batch boundary at 'Content-Type: applic...'".
|
|
21
|
+
* FMS's parser appears to misread the changeset's nested boundary as a
|
|
22
|
+
* batch-level boundary when GETs precede it.
|
|
23
|
+
* → Workaround: always add changesets before reads in the batch.
|
|
24
|
+
*
|
|
25
|
+
* 2. Multiple consecutive GET operations return only one fewer response than
|
|
26
|
+
* expected — the last GET in a sequence is silently dropped by FMS.
|
|
27
|
+
* E.g. three GETs → only two responses.
|
|
28
|
+
* → Workaround: limit batches to one read op at the end, or accept the loss.
|
|
29
|
+
*
|
|
30
|
+
* 3. POST/PATCH within a changeset requires an explicit Content-Length header
|
|
31
|
+
* on the inner HTTP request (FMS cannot determine body length from multipart
|
|
32
|
+
* structure alone). This is handled by utf8ByteLength() during serialisation.
|
|
33
|
+
*
|
|
34
|
+
* These constraints differ from the OData 4.01 spec and the official Claris
|
|
35
|
+
* documentation examples. They were discovered empirically. Batch support in
|
|
36
|
+
* this library is functional but limited to: changeset (optional) followed by
|
|
37
|
+
* at most one read operation per batch call.
|
|
38
|
+
*/
|
|
39
|
+
import type { FMSOData } from './client.js';
|
|
40
|
+
import type { ODataLiteral } from './url.js';
|
|
41
|
+
import type { RequestOptions } from './types.js';
|
|
42
|
+
/** Handle returned when adding an operation to a batch. Resolves to the operation result. */
|
|
43
|
+
export interface BatchHandle<T> {
|
|
44
|
+
readonly __brand: 'BatchHandle';
|
|
45
|
+
/** @internal */ readonly _promise: Promise<T>;
|
|
46
|
+
/** @internal */ readonly _index: number;
|
|
47
|
+
}
|
|
48
|
+
/** Input for a read operation (GET) in a batch. */
|
|
49
|
+
export interface BatchReadOp {
|
|
50
|
+
/** Entity set name (layout/table). */
|
|
51
|
+
entitySet: string;
|
|
52
|
+
/** Operation type. */
|
|
53
|
+
op: 'list';
|
|
54
|
+
/** Query options: $top, $skip, $filter, etc. */
|
|
55
|
+
query?: {
|
|
56
|
+
$top?: number;
|
|
57
|
+
$skip?: number;
|
|
58
|
+
$filter?: string;
|
|
59
|
+
$select?: string;
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/** Result of a batch operation. */
|
|
63
|
+
export interface BatchOpResult<T = unknown> {
|
|
64
|
+
status: number;
|
|
65
|
+
body?: T;
|
|
66
|
+
headers: Headers;
|
|
67
|
+
ok: boolean;
|
|
68
|
+
}
|
|
69
|
+
/** Result of executing a batch. */
|
|
70
|
+
export interface BatchResult {
|
|
71
|
+
/** All responses in request order. */
|
|
72
|
+
responses: BatchOpResult[];
|
|
73
|
+
/** True if all responses have status < 400. */
|
|
74
|
+
ok: boolean;
|
|
75
|
+
}
|
|
76
|
+
/** Represents an operation within a changeset. */
|
|
77
|
+
interface ChangesetOp {
|
|
78
|
+
method: string;
|
|
79
|
+
path: string;
|
|
80
|
+
headers?: Record<string, string>;
|
|
81
|
+
body?: string;
|
|
82
|
+
}
|
|
83
|
+
/** Changeset builder for atomic write operations. */
|
|
84
|
+
export declare class Changeset {
|
|
85
|
+
private _ops;
|
|
86
|
+
private _handles;
|
|
87
|
+
private _baseUrl;
|
|
88
|
+
constructor(baseUrl: string);
|
|
89
|
+
/** @internal */
|
|
90
|
+
get _operations(): ChangesetOp[];
|
|
91
|
+
/** @internal */
|
|
92
|
+
get _handleSlots(): Array<{
|
|
93
|
+
resolve: (v: unknown) => void;
|
|
94
|
+
reject: (e: unknown) => void;
|
|
95
|
+
}>;
|
|
96
|
+
/**
|
|
97
|
+
* Create a new entity within this changeset.
|
|
98
|
+
*/
|
|
99
|
+
create<T = unknown>(entitySet: string, body: Record<string, unknown>): BatchHandle<T>;
|
|
100
|
+
/**
|
|
101
|
+
* Patch an existing entity within this changeset.
|
|
102
|
+
*/
|
|
103
|
+
patch<T = unknown>(entitySet: string, key: ODataLiteral, body: Record<string, unknown>, opts?: {
|
|
104
|
+
ifMatch?: string;
|
|
105
|
+
}): BatchHandle<T>;
|
|
106
|
+
/**
|
|
107
|
+
* Delete an entity within this changeset.
|
|
108
|
+
*/
|
|
109
|
+
delete(entitySet: string, key: ODataLiteral, opts?: {
|
|
110
|
+
ifMatch?: string;
|
|
111
|
+
}): BatchHandle<void>;
|
|
112
|
+
}
|
|
113
|
+
/** Batch builder for composing multipart/mixed requests. */
|
|
114
|
+
export declare class Batch {
|
|
115
|
+
private _client;
|
|
116
|
+
private _parts;
|
|
117
|
+
private _changesets;
|
|
118
|
+
constructor(client: FMSOData);
|
|
119
|
+
/**
|
|
120
|
+
* Add a read operation (GET) to the batch.
|
|
121
|
+
* Read operations are not part of a changeset and execute independently.
|
|
122
|
+
*/
|
|
123
|
+
add<T = unknown>(op: BatchReadOp): BatchHandle<T>;
|
|
124
|
+
/**
|
|
125
|
+
* Create an atomic changeset (group of write operations).
|
|
126
|
+
* All operations in a changeset succeed or fail together.
|
|
127
|
+
*/
|
|
128
|
+
changeset(build: (cs: Changeset) => void): void;
|
|
129
|
+
/**
|
|
130
|
+
* Serialize the batch into a multipart/mixed body.
|
|
131
|
+
* @internal
|
|
132
|
+
*/
|
|
133
|
+
_serialize(): {
|
|
134
|
+
boundary: string;
|
|
135
|
+
body: string;
|
|
136
|
+
};
|
|
137
|
+
/**
|
|
138
|
+
* Send the batch request and parse the multipart response.
|
|
139
|
+
*/
|
|
140
|
+
send(opts?: RequestOptions): Promise<BatchResult>;
|
|
141
|
+
/**
|
|
142
|
+
* Parse a multipart/mixed batch response.
|
|
143
|
+
* @internal
|
|
144
|
+
*/
|
|
145
|
+
_parseResponse(responseText: string, contentType: string): BatchResult;
|
|
146
|
+
/** @internal — parse a single HTTP response part. */
|
|
147
|
+
private _parseHttpPart;
|
|
148
|
+
/** @internal — parse a changeset multipart response. */
|
|
149
|
+
private _parseChangesetResponse;
|
|
150
|
+
/** @internal — resolve a batch handle with the result. */
|
|
151
|
+
private _resolveHandle;
|
|
152
|
+
}
|
|
153
|
+
export {};
|
|
154
|
+
//# sourceMappingURL=batch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"batch.d.ts","sourceRoot":"","sources":["../src/batch.ts"],"names":[],"mappings":"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"}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { Batch } from './batch.js';
|
|
2
|
+
import { type HttpClientContext, type HttpRequestOptions } from './http.js';
|
|
3
|
+
import { type MetadataOptions, type ODataMetadata } from './metadata.js';
|
|
4
|
+
import { Query } from './query.js';
|
|
5
|
+
import { type ScriptOptions, type ScriptResult } from './scripts.js';
|
|
6
|
+
import type { FMSODataOptions, RequestOptions } from './types.js';
|
|
7
|
+
import { type FMVersionMajor, type FMVersionInfo, type FMFeatureFlags, type FMServerVersion } from '@fms-odata/spec-ts';
|
|
8
|
+
/**
|
|
9
|
+
* `FMSOData` is the entrypoint for all OData operations against a FileMaker
|
|
10
|
+
* Server database. Covers query/CRUD, script invocation, container I/O,
|
|
11
|
+
* `$metadata` introspection, and `$batch` operations.
|
|
12
|
+
*/
|
|
13
|
+
export declare class FMSOData {
|
|
14
|
+
readonly host: string;
|
|
15
|
+
readonly database: string;
|
|
16
|
+
readonly baseUrl: string;
|
|
17
|
+
readonly timeoutMs: number | undefined;
|
|
18
|
+
/** @internal */ readonly _ctx: HttpClientContext;
|
|
19
|
+
/** @internal */ private _metadataFetcher?;
|
|
20
|
+
constructor(options: FMSODataOptions);
|
|
21
|
+
/**
|
|
22
|
+
* Start a query against the given entity set (FileMaker layout name).
|
|
23
|
+
*/
|
|
24
|
+
from<T = Record<string, unknown>>(entitySet: string): Query<T>;
|
|
25
|
+
/**
|
|
26
|
+
* Low-level escape hatch: execute a raw request against a path relative to
|
|
27
|
+
* the database base URL (or an absolute URL). Returns the parsed JSON body.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```ts
|
|
31
|
+
* const body = await db.request<{ value: unknown[] }>('/contact?$top=1')
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
request<T = unknown>(pathOrUrl: string, opts?: HttpRequestOptions): Promise<T>;
|
|
35
|
+
/**
|
|
36
|
+
* Low-level escape hatch: execute a raw request and return the `Response`
|
|
37
|
+
* object directly (useful for binary / streaming responses).
|
|
38
|
+
*/
|
|
39
|
+
rawRequest(pathOrUrl: string, opts?: HttpRequestOptions): Promise<Response>;
|
|
40
|
+
/**
|
|
41
|
+
* Invoke a FileMaker script at database scope.
|
|
42
|
+
*
|
|
43
|
+
* ```ts
|
|
44
|
+
* const result = await db.script('Ping', { parameter: 'hello' })
|
|
45
|
+
* console.log(result.scriptResult) // => string value returned by the script
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* A non-zero `scriptError` is thrown as `FMScriptError`.
|
|
49
|
+
*/
|
|
50
|
+
script(name: string, opts?: ScriptOptions): Promise<ScriptResult>;
|
|
51
|
+
/**
|
|
52
|
+
* Invoke a FileMaker script by its immutable FMSID.
|
|
53
|
+
*
|
|
54
|
+
* Requires FileMaker Server 2026+ (v26). Use `hasFeature('scriptsByFMSID')`
|
|
55
|
+
* to check before calling. FMSID-based invocation is more stable than
|
|
56
|
+
* name-based: it survives script renames and works across database
|
|
57
|
+
* migrations.
|
|
58
|
+
*
|
|
59
|
+
* ```ts
|
|
60
|
+
* const result = await db.scriptById(42, { parameter: 'hello' })
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
scriptById(fmsid: number, opts?: ScriptOptions): Promise<ScriptResult>;
|
|
64
|
+
/**
|
|
65
|
+
* Fetch the OData CSDL `$metadata` XML and parse it into a typed structure.
|
|
66
|
+
* Results are cached; pass `refresh: true` to force a refetch.
|
|
67
|
+
*
|
|
68
|
+
* ```ts
|
|
69
|
+
* const meta = await db.metadata()
|
|
70
|
+
* console.log(meta.entitySets.map(es => es.name))
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
metadata(opts?: MetadataOptions): Promise<ODataMetadata>;
|
|
74
|
+
/**
|
|
75
|
+
* Fetch the raw `$metadata` XML (escape hatch for debugging or custom parsing).
|
|
76
|
+
*/
|
|
77
|
+
metadataXml(opts?: RequestOptions): Promise<string>;
|
|
78
|
+
/** @internal */ private _detectedVersion;
|
|
79
|
+
/** @internal */ private _detectedServerVersion;
|
|
80
|
+
/**
|
|
81
|
+
* Detect the FileMaker Server major version by fetching `$metadata` and
|
|
82
|
+
* parsing the version annotation using a multi-strategy approach (see
|
|
83
|
+
* `@fms-odata/spec-ts` `parseServerVersion`). The result is cached for the
|
|
84
|
+
* lifetime of this `FMSOData` instance.
|
|
85
|
+
*
|
|
86
|
+
* Returns the major version string (`'19'`, `'21'`, `'22'`, `'26'`) or
|
|
87
|
+
* `'future'` if the version is newer than the spec knows about. Returns
|
|
88
|
+
* `null` if the version cannot be determined (e.g. the metadata lacks the
|
|
89
|
+
* annotation).
|
|
90
|
+
*
|
|
91
|
+
* ```ts
|
|
92
|
+
* const v = await db.version()
|
|
93
|
+
* if (v === '26') console.log('Server is FileMaker 2026')
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
version(): Promise<FMVersionMajor | null>;
|
|
97
|
+
/**
|
|
98
|
+
* Get the full parsed FileMaker Server version (major, minor, patch, raw)
|
|
99
|
+
* by fetching `$metadata` and parsing the version annotation. The result is
|
|
100
|
+
* cached for the lifetime of this `FMSOData` instance.
|
|
101
|
+
*
|
|
102
|
+
* Returns `null` if the version cannot be determined.
|
|
103
|
+
*
|
|
104
|
+
* ```ts
|
|
105
|
+
* const sv = await db.serverVersion()
|
|
106
|
+
* if (sv) console.log(`Server is ${sv.raw} (major ${sv.major})`)
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
serverVersion(): Promise<FMServerVersion | null>;
|
|
110
|
+
/**
|
|
111
|
+
* Get the full version info (feature flags + query option flags) for the
|
|
112
|
+
* detected server version. Fetches metadata if not already cached.
|
|
113
|
+
*
|
|
114
|
+
* Returns `null` if the version cannot be determined.
|
|
115
|
+
*
|
|
116
|
+
* ```ts
|
|
117
|
+
* const info = await db.versionInfo()
|
|
118
|
+
* if (info?.features.applyAggregation) {
|
|
119
|
+
* // Server supports $apply
|
|
120
|
+
* }
|
|
121
|
+
* ```
|
|
122
|
+
*/
|
|
123
|
+
versionInfo(): Promise<FMVersionInfo | null>;
|
|
124
|
+
/**
|
|
125
|
+
* Check if the server supports a specific feature. Fetches metadata (to
|
|
126
|
+
* detect the version) on first call; subsequent calls use the cached result.
|
|
127
|
+
*
|
|
128
|
+
* Returns `false` if the version cannot be determined.
|
|
129
|
+
*
|
|
130
|
+
* ```ts
|
|
131
|
+
* if (await db.hasFeature('applyAggregation')) {
|
|
132
|
+
* const result = await db.from('orders').apply(...)
|
|
133
|
+
* }
|
|
134
|
+
* ```
|
|
135
|
+
*/
|
|
136
|
+
hasFeature(feature: keyof FMFeatureFlags): Promise<boolean>;
|
|
137
|
+
/**
|
|
138
|
+
* Create a new `$batch` builder for composing multiple OData operations
|
|
139
|
+
* into a single HTTP round-trip.
|
|
140
|
+
*
|
|
141
|
+
* Read operations (`add`) are executed independently. Write operations
|
|
142
|
+
* (`changeset`) are grouped atomically — all succeed or all fail.
|
|
143
|
+
*
|
|
144
|
+
* ```ts
|
|
145
|
+
* const batch = db.batch()
|
|
146
|
+
* const contacts = batch.add({ op: 'list', entitySet: 'contact', query: { $top: 5 } })
|
|
147
|
+
* batch.changeset(cs => {
|
|
148
|
+
* cs.create('contact', { firstName: 'A', lastName: 'B' })
|
|
149
|
+
* cs.patch('contact', 123, { firstName: 'Updated' })
|
|
150
|
+
* })
|
|
151
|
+
* const result = await batch.send()
|
|
152
|
+
* console.log(await contacts._promise) // First op result
|
|
153
|
+
* ```
|
|
154
|
+
*/
|
|
155
|
+
batch(): Batch;
|
|
156
|
+
/** @internal */
|
|
157
|
+
_resolveUrl(pathOrUrl: string): string;
|
|
158
|
+
}
|
|
159
|
+
export type { RequestOptions };
|
|
160
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AAClC,OAAO,EAA+B,KAAK,iBAAiB,EAAE,KAAK,kBAAkB,EAAE,MAAM,WAAW,CAAA;AACxG,OAAO,EAAmB,KAAK,eAAe,EAAE,KAAK,aAAa,EAAE,MAAM,eAAe,CAAA;AACzF,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AAClC,OAAO,EAAgD,KAAK,aAAa,EAAE,KAAK,YAAY,EAAE,MAAM,cAAc,CAAA;AAClH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AACjE,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,eAAe,EAGrB,MAAM,oBAAoB,CAAA;AAE3B;;;;GAIG;AACH,qBAAa,QAAQ;IACnB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAA;IAEtC,gBAAgB,CAAC,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAA;IACjD,gBAAgB,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAiB;gBAE/C,OAAO,EAAE,eAAe;IAoBpC;;OAEG;IACH,IAAI,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS,EAAE,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC;IAK9D;;;;;;;;OAQG;IACG,OAAO,CAAC,CAAC,GAAG,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,GAAE,kBAAuB,GAAG,OAAO,CAAC,CAAC,CAAC;IAIxF;;;OAGG;IACG,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,GAAE,kBAAuB,GAAG,OAAO,CAAC,QAAQ,CAAC;IAIrF;;;;;;;;;OASG;IACG,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,aAAkB,GAAG,OAAO,CAAC,YAAY,CAAC;IAI3E;;;;;;;;;;;OAWG;IACG,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,GAAE,aAAkB,GAAG,OAAO,CAAC,YAAY,CAAC;IAIhF;;;;;;;;OAQG;IACG,QAAQ,CAAC,IAAI,GAAE,eAAoB,GAAG,OAAO,CAAC,aAAa,CAAC;IAOlE;;OAEG;IACG,WAAW,CAAC,IAAI,GAAE,cAAmB,GAAG,OAAO,CAAC,MAAM,CAAC;IAW7D,gBAAgB,CAAC,OAAO,CAAC,gBAAgB,CAAmC;IAC5E,gBAAgB,CAAC,OAAO,CAAC,sBAAsB,CAAoC;IAEnF;;;;;;;;;;;;;;;OAeG;IACG,OAAO,IAAI,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IAkB/C;;;;;;;;;;;OAWG;IACG,aAAa,IAAI,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IAYtD;;;;;;;;;;;;OAYG;IACG,WAAW,IAAI,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAMlD;;;;;;;;;;;OAWG;IACG,UAAU,CAAC,OAAO,EAAE,MAAM,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC;IAMjE;;;;;;;;;;;;;;;;;OAiBG;IACH,KAAK,IAAI,KAAK;IAId,gBAAgB;IAChB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM;CAKvC;AAGD,YAAY,EAAE,cAAc,EAAE,CAAA"}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FileMaker container field I/O.
|
|
3
|
+
*
|
|
4
|
+
* FMS exposes three container operations over OData v4 — see
|
|
5
|
+
* `docs/filemaker-odata-container-guide.md` for the full reference. The
|
|
6
|
+
* library maps to them as follows:
|
|
7
|
+
*
|
|
8
|
+
* GET /<EntitySet>(<key>)/<field>/$value → ContainerRef#get / getStream
|
|
9
|
+
* PATCH /<EntitySet>(<key>)/<field> → ContainerRef#upload({ encoding: 'binary' }) — DEFAULT
|
|
10
|
+
* PATCH /<EntitySet>(<key>) (JSON body) → ContainerRef#upload({ encoding: 'base64' })
|
|
11
|
+
* POST /<EntitySet> (JSON body) → Query#create({ … }) with container annotation keys
|
|
12
|
+
*
|
|
13
|
+
* Supported MIME types per the Claris guide:
|
|
14
|
+
* image/png, image/jpeg, image/gif, image/tiff, application/pdf
|
|
15
|
+
*
|
|
16
|
+
* Clearing a container is not in the Claris guide. The library PATCHes the
|
|
17
|
+
* record with `{ <field>: null }`.
|
|
18
|
+
*/
|
|
19
|
+
import type { EntityRef } from './entity.js';
|
|
20
|
+
import type { RequestOptions } from './types.js';
|
|
21
|
+
/** Result of a successful container download. */
|
|
22
|
+
export interface ContainerDownload {
|
|
23
|
+
/** Binary contents as a `Blob` (works in browsers, Web Viewer, and Node 18+). */
|
|
24
|
+
blob: Blob;
|
|
25
|
+
/** MIME type reported by FMS (e.g. `image/png`); empty string if absent. */
|
|
26
|
+
contentType: string;
|
|
27
|
+
/** Filename parsed from `Content-Disposition`, when present. */
|
|
28
|
+
filename?: string;
|
|
29
|
+
/** Byte length. `0` indicates an empty container. */
|
|
30
|
+
size: number;
|
|
31
|
+
}
|
|
32
|
+
/** Input accepted by `ContainerRef#upload`. */
|
|
33
|
+
export interface ContainerUploadInput {
|
|
34
|
+
/** Binary payload. `fetch` accepts all three forms natively. */
|
|
35
|
+
data: Blob | ArrayBuffer | Uint8Array;
|
|
36
|
+
/**
|
|
37
|
+
* Content-Type to send with the upload. Optional: when omitted, the library
|
|
38
|
+
* sniffs the MIME from the payload's magic bytes (PNG, JPEG, GIF, TIFF, PDF).
|
|
39
|
+
* Throws if it cannot detect a supported type.
|
|
40
|
+
*/
|
|
41
|
+
contentType?: string;
|
|
42
|
+
/**
|
|
43
|
+
* Optional filename to store in the container. Surfaced on download via
|
|
44
|
+
* `Content-Disposition: attachment; filename="…"`. When omitted, FMS stores
|
|
45
|
+
* (and surfaces back) the auto-generated name `Untitled.png`.
|
|
46
|
+
*/
|
|
47
|
+
filename?: string;
|
|
48
|
+
/**
|
|
49
|
+
* Wire format:
|
|
50
|
+
*
|
|
51
|
+
* - `'binary'` (default): PATCH `…/<field>` with raw bytes. Restricted to
|
|
52
|
+
* PNG, JPEG, GIF, TIFF, and PDF.
|
|
53
|
+
* - `'base64'`: PATCH `…/<EntitySet>(<key>)` with a JSON body containing
|
|
54
|
+
* the file as a base64 string plus `@com.filemaker.odata.…` annotations.
|
|
55
|
+
* Use this when you need to update multiple container fields (or mix
|
|
56
|
+
* container and regular field updates) in a single request.
|
|
57
|
+
*/
|
|
58
|
+
encoding?: 'binary' | 'base64';
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* MIME types FMS accepts for container fields. Other types (audio, video,
|
|
62
|
+
* zip, docx, …) may upload but won't be classified as a media type and won't
|
|
63
|
+
* preview correctly in FileMaker.
|
|
64
|
+
*/
|
|
65
|
+
export declare const FM_CONTAINER_SUPPORTED_MIME_TYPES: readonly ["image/png", "image/jpeg", "image/gif", "image/tiff", "application/pdf"];
|
|
66
|
+
export type FMContainerMimeType = (typeof FM_CONTAINER_SUPPORTED_MIME_TYPES)[number];
|
|
67
|
+
/**
|
|
68
|
+
* Sniff the MIME type from the magic bytes of an uploaded payload. Returns
|
|
69
|
+
* `undefined` when none of the supported signatures match.
|
|
70
|
+
*
|
|
71
|
+
* Signatures:
|
|
72
|
+
* PNG — 89 50 4E 47 0D 0A 1A 0A
|
|
73
|
+
* JPEG — FF D8 FF
|
|
74
|
+
* GIF — 47 49 46 38 (`GIF8`)
|
|
75
|
+
* TIFF — 49 49 2A 00 (little-endian) | 4D 4D 00 2A (big-endian)
|
|
76
|
+
* PDF — 25 50 44 46 (`%PDF`)
|
|
77
|
+
*
|
|
78
|
+
* @internal
|
|
79
|
+
*/
|
|
80
|
+
export declare function sniffContainerMime(bytes: Uint8Array): FMContainerMimeType | undefined;
|
|
81
|
+
/**
|
|
82
|
+
* Handle to a single FileMaker container field on a specific record. Created
|
|
83
|
+
* via `EntityRef#container(fieldName)`.
|
|
84
|
+
*/
|
|
85
|
+
export declare class ContainerRef {
|
|
86
|
+
/** @internal */ readonly _entity: EntityRef<unknown>;
|
|
87
|
+
readonly fieldName: string;
|
|
88
|
+
constructor(entity: EntityRef<unknown>, fieldName: string);
|
|
89
|
+
/**
|
|
90
|
+
* Absolute URL of the container field itself
|
|
91
|
+
* (`…/<EntitySet>(<key>)/<fieldName>`). This is the URL used by binary
|
|
92
|
+
* `upload()`. Append `/$value` to download.
|
|
93
|
+
*/
|
|
94
|
+
url(): string;
|
|
95
|
+
/** @internal — `…/<field>/$value` for downloads. */
|
|
96
|
+
private _valueUrl;
|
|
97
|
+
/**
|
|
98
|
+
* Download the container's contents and buffer them into a `Blob`. For
|
|
99
|
+
* very large payloads prefer `getStream()` to avoid buffering in memory.
|
|
100
|
+
*/
|
|
101
|
+
get(opts?: RequestOptions): Promise<ContainerDownload>;
|
|
102
|
+
/**
|
|
103
|
+
* Stream the container's contents without buffering. Useful for large files
|
|
104
|
+
* (`pipeTo()` into a writable, forward to another `Response`, etc.).
|
|
105
|
+
*
|
|
106
|
+
* Throws if the underlying `Response` has no body.
|
|
107
|
+
*/
|
|
108
|
+
getStream(opts?: RequestOptions): Promise<ReadableStream<Uint8Array>>;
|
|
109
|
+
/**
|
|
110
|
+
* Upload binary contents to the container. Replaces any existing value.
|
|
111
|
+
*
|
|
112
|
+
* Default `encoding: 'binary'` PATCHes `…/<field>` with raw bytes and a
|
|
113
|
+
* `Content-Type` header. Restricted to PNG / JPEG / GIF / TIFF / PDF.
|
|
114
|
+
*
|
|
115
|
+
* `encoding: 'base64'` PATCHes `…/<EntitySet>(<key>)` with a JSON body
|
|
116
|
+
* containing base64 data plus `@com.filemaker.odata.…` annotations. Use
|
|
117
|
+
* this when updating multiple container fields (or mixing container and
|
|
118
|
+
* regular fields) in a single round-trip.
|
|
119
|
+
*
|
|
120
|
+
* `contentType` is optional — when omitted, the library sniffs the MIME
|
|
121
|
+
* from the payload's magic bytes. Throws if no supported signature matches.
|
|
122
|
+
*/
|
|
123
|
+
upload(input: ContainerUploadInput, opts?: RequestOptions): Promise<void>;
|
|
124
|
+
/**
|
|
125
|
+
* Clear the container value. FMS has no documented per-field DELETE for
|
|
126
|
+
* record-level data, so the supported path is to PATCH the record with
|
|
127
|
+
* `{ <fieldName>: null }`.
|
|
128
|
+
*/
|
|
129
|
+
delete(opts?: RequestOptions): Promise<void>;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* One container value to embed in a JSON body, in raw byte form. Used by
|
|
133
|
+
* `buildContainerJsonBody` and `EntityRef#patchContainers`.
|
|
134
|
+
*/
|
|
135
|
+
export interface ContainerJsonValue {
|
|
136
|
+
/** Base64-encoded bytes (without `data:` prefix or whitespace). */
|
|
137
|
+
data: string;
|
|
138
|
+
/** REQUIRED MIME type. Stored as `@com.filemaker.odata.ContentType`. */
|
|
139
|
+
contentType: string;
|
|
140
|
+
/** Optional filename. Stored as `@com.filemaker.odata.Filename`. */
|
|
141
|
+
filename?: string;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Build a JSON body for a POST/PATCH that updates one or more container
|
|
145
|
+
* fields (and optionally regular fields). Inserts the FileMaker annotation
|
|
146
|
+
* keys for every container value.
|
|
147
|
+
*
|
|
148
|
+
* @example
|
|
149
|
+
* buildContainerJsonBody(
|
|
150
|
+
* {
|
|
151
|
+
* photo: { data: '<base64>', contentType: 'image/png', filename: 'p.png' },
|
|
152
|
+
* contract: { data: '<base64>', contentType: 'application/pdf' },
|
|
153
|
+
* },
|
|
154
|
+
* { website: 'https://example.com' },
|
|
155
|
+
* )
|
|
156
|
+
*/
|
|
157
|
+
export declare function buildContainerJsonBody(containers: Record<string, ContainerJsonValue>, regularFields?: Record<string, unknown>): Record<string, unknown>;
|
|
158
|
+
/**
|
|
159
|
+
* Parse the `filename` (or RFC 5987 `filename*`) from a `Content-Disposition`
|
|
160
|
+
* header. Returns `undefined` when no filename parameter is present.
|
|
161
|
+
*
|
|
162
|
+
* Per RFC 6266 §4.3, `filename*` (with explicit charset) takes precedence
|
|
163
|
+
* over `filename` when both are supplied.
|
|
164
|
+
*
|
|
165
|
+
* @internal
|
|
166
|
+
*/
|
|
167
|
+
export declare function parseContentDispositionFilename(value: string): string | undefined;
|
|
168
|
+
/**
|
|
169
|
+
* Build a `Content-Disposition` value for an upload. The disposition-type is
|
|
170
|
+
* `inline` to match the example in the Claris OData docs.
|
|
171
|
+
*
|
|
172
|
+
* The Claris doc example uses the **unquoted** form
|
|
173
|
+
* (`filename=ALFKI.png`); FMS has been observed to mis-parse the RFC 6266
|
|
174
|
+
* quoted form on some deployments. So when the filename is composed of
|
|
175
|
+
* RFC 7230 token characters this function emits the unquoted form, falls
|
|
176
|
+
* back to a quoted form when the name contains spaces or other separator
|
|
177
|
+
* characters, and additionally emits an RFC 5987 `filename*=UTF-8''…`
|
|
178
|
+
* parameter for non-ASCII names.
|
|
179
|
+
*
|
|
180
|
+
* @internal
|
|
181
|
+
*/
|
|
182
|
+
export declare function formatContentDisposition(filename: string): string;
|
|
183
|
+
/**
|
|
184
|
+
* Encode bytes to a base64 string. Uses Node's `Buffer` when available, else
|
|
185
|
+
* a chunked `btoa` fallback (avoids stack overflow on large arrays).
|
|
186
|
+
*
|
|
187
|
+
* @internal
|
|
188
|
+
*/
|
|
189
|
+
export declare function toBase64(bytes: Uint8Array): string;
|
|
190
|
+
//# sourceMappingURL=containers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"containers.d.ts","sourceRoot":"","sources":["../src/containers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAE5C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAGhD,iDAAiD;AACjD,MAAM,WAAW,iBAAiB;IAChC,iFAAiF;IACjF,IAAI,EAAE,IAAI,CAAA;IACV,4EAA4E;IAC5E,WAAW,EAAE,MAAM,CAAA;IACnB,gEAAgE;IAChE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,qDAAqD;IACrD,IAAI,EAAE,MAAM,CAAA;CACb;AAED,+CAA+C;AAC/C,MAAM,WAAW,oBAAoB;IACnC,gEAAgE;IAChE,IAAI,EAAE,IAAI,GAAG,WAAW,GAAG,UAAU,CAAA;IACrC;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;;;;;;OASG;IACH,QAAQ,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAA;CAC/B;AAED;;;;GAIG;AACH,eAAO,MAAM,iCAAiC,oFAMpC,CAAA;AAEV,MAAM,MAAM,mBAAmB,GAAG,CAAC,OAAO,iCAAiC,CAAC,CAAC,MAAM,CAAC,CAAA;AAYpF;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,UAAU,GAAG,mBAAmB,GAAG,SAAS,CAuBrF;AAED;;;GAGG;AACH,qBAAa,YAAY;IACvB,gBAAgB,CAAC,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAC,OAAO,CAAC,CAAA;IACrD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;gBAEd,MAAM,EAAE,SAAS,CAAC,OAAO,CAAC,EAAE,SAAS,EAAE,MAAM;IAQzD;;;;OAIG;IACH,GAAG,IAAI,MAAM;IAIb,oDAAoD;IACpD,OAAO,CAAC,SAAS;IAIjB;;;OAGG;IACG,GAAG,CAAC,IAAI,GAAE,cAAmB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAyBhE;;;;;OAKG;IACG,SAAS,CAAC,IAAI,GAAE,cAAmB,GAAG,OAAO,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC;IAa/E;;;;;;;;;;;;;OAaG;IACG,MAAM,CAAC,KAAK,EAAE,oBAAoB,EAAE,IAAI,GAAE,cAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAiEnF;;;;OAIG;IACG,MAAM,CAAC,IAAI,GAAE,cAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;CAavD;AAMD;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,mEAAmE;IACnE,IAAI,EAAE,MAAM,CAAA;IACZ,wEAAwE;IACxE,WAAW,EAAE,MAAM,CAAA;IACnB,oEAAoE;IACpE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,EAC9C,aAAa,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAC1C,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAazB;AAMD;;;;;;;;GAQG;AACH,wBAAgB,+BAA+B,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAyBjF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAejE;AAaD;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAclD"}
|
package/dist/entity.d.ts
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single-entity handle returned by `Query#byKey(key)`. Supports `.get()`,
|
|
3
|
+
* `.patch()`, `.delete()`, `.script()` (record-scope FileMaker script
|
|
4
|
+
* invocation, v0.1.4+), and `.container(field)` (container-field binary
|
|
5
|
+
* I/O, v0.1.5+).
|
|
6
|
+
*/
|
|
7
|
+
import type { FMSOData } from './client.js';
|
|
8
|
+
import { ContainerRef, type ContainerJsonValue } from './containers.js';
|
|
9
|
+
import { type ScriptOptions, type ScriptResult } from './scripts.js';
|
|
10
|
+
import type { RequestOptions } from './types.js';
|
|
11
|
+
import { type ODataLiteral } from './url.js';
|
|
12
|
+
/** Options accepted by mutating entity operations. */
|
|
13
|
+
export interface EntityWriteOptions extends RequestOptions {
|
|
14
|
+
/**
|
|
15
|
+
* Optional `If-Match` precondition. Pass an ETag returned from a prior read
|
|
16
|
+
* to enable optimistic concurrency.
|
|
17
|
+
*/
|
|
18
|
+
ifMatch?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Whether the server should return the updated representation. Defaults to
|
|
21
|
+
* `false` (`Prefer: return=minimal`) to match FMS behaviour; set to `true`
|
|
22
|
+
* to request `Prefer: return=representation`.
|
|
23
|
+
*/
|
|
24
|
+
returnRepresentation?: boolean;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Handle to a single OData entity. Holds the client, entity-set name, and the
|
|
28
|
+
* primary key; every method builds a URL of the form
|
|
29
|
+
* `<baseUrl>/<EntitySet>(<key>)` and delegates to the shared HTTP executor.
|
|
30
|
+
*/
|
|
31
|
+
export declare class EntityRef<T = Record<string, unknown>> {
|
|
32
|
+
readonly entitySet: string;
|
|
33
|
+
readonly key: ODataLiteral;
|
|
34
|
+
/** @internal */ readonly _client: FMSOData;
|
|
35
|
+
constructor(client: FMSOData, entitySet: string, key: ODataLiteral);
|
|
36
|
+
/** Absolute URL for this entity. */
|
|
37
|
+
toURL(): string;
|
|
38
|
+
/** `GET` the entity. Returns the parsed JSON row. */
|
|
39
|
+
get(opts?: RequestOptions): Promise<T>;
|
|
40
|
+
/**
|
|
41
|
+
* `GET` a single field's scalar value via the OData property URL
|
|
42
|
+
* (`…/<EntitySet>(<key>)/<fieldName>`). FMS responds with the JSON envelope
|
|
43
|
+
* `{ value: … }`; this method unwraps it and returns just the value.
|
|
44
|
+
*
|
|
45
|
+
* Useful when you only need one column without composing a `$select` query.
|
|
46
|
+
* For container fields use `container(name).get()` instead.
|
|
47
|
+
*/
|
|
48
|
+
fieldValue<V = unknown>(fieldName: string, opts?: RequestOptions): Promise<V>;
|
|
49
|
+
/**
|
|
50
|
+
* `PATCH` the entity with partial values. Returns the updated row when the
|
|
51
|
+
* server echoes one (OData `Prefer: return=representation`), otherwise
|
|
52
|
+
* `undefined` on `204 No Content`.
|
|
53
|
+
*/
|
|
54
|
+
patch(body: Partial<T> | Record<string, unknown>, opts?: EntityWriteOptions): Promise<T | undefined>;
|
|
55
|
+
/** `DELETE` the entity. Resolves on success; throws `FMSODataError` otherwise. */
|
|
56
|
+
delete(opts?: EntityWriteOptions): Promise<void>;
|
|
57
|
+
/**
|
|
58
|
+
* Invoke a FileMaker script in the context of this single record. FMS sets
|
|
59
|
+
* the script's current record to this entity before running it.
|
|
60
|
+
*/
|
|
61
|
+
script(name: string, opts?: ScriptOptions): Promise<ScriptResult>;
|
|
62
|
+
/**
|
|
63
|
+
* Get a typed handle to one of this record's container fields, exposing
|
|
64
|
+
* `.get()`, `.getStream()`, `.upload(...)`, and `.delete()`.
|
|
65
|
+
*/
|
|
66
|
+
container(fieldName: string): ContainerRef;
|
|
67
|
+
/**
|
|
68
|
+
* Update one or more container fields (and optionally regular fields) on
|
|
69
|
+
* this record in a single base64 PATCH request. This maps to the Claris
|
|
70
|
+
* "Operation 3" (`PATCH /<EntitySet>(<key>)` with JSON body containing
|
|
71
|
+
* `<field>`, `<field>@com.filemaker.odata.ContentType`, and
|
|
72
|
+
* `<field>@com.filemaker.odata.Filename`).
|
|
73
|
+
*
|
|
74
|
+
* Each container value's `data` must already be base64-encoded (use the
|
|
75
|
+
* library's exported `toBase64()` helper or `Buffer.from(bytes).toString('base64')`).
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* await db.from('contact').byKey(7).patchContainers(
|
|
79
|
+
* {
|
|
80
|
+
* photo: { data: photoB64, contentType: 'image/png', filename: 'p.png' },
|
|
81
|
+
* contract: { data: contractB64, contentType: 'application/pdf', filename: 'c.pdf' },
|
|
82
|
+
* },
|
|
83
|
+
* { website: 'https://example.com' },
|
|
84
|
+
* )
|
|
85
|
+
*/
|
|
86
|
+
patchContainers(containers: Record<string, ContainerJsonValue>, regularFields?: Record<string, unknown>, opts?: EntityWriteOptions): Promise<void>;
|
|
87
|
+
/**
|
|
88
|
+
* Get the references for a navigation property on this record.
|
|
89
|
+
*
|
|
90
|
+
* `GET /<EntitySet>(<key>)/<navProperty>/$ref`
|
|
91
|
+
*
|
|
92
|
+
* Returns an array of entity references. For a single-valued navigation
|
|
93
|
+
* property, the array has at most one element.
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* ```ts
|
|
97
|
+
* const refs = await db.from('contact').byKey(7).getRefs('addresses')
|
|
98
|
+
* // [{ '@odata.id': 'https://fms.example.com/fmi/odata/v4/DB/address(1)' }, ...]
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
getRefs(navProperty: string, opts?: RequestOptions): Promise<EntityRefInfo[]>;
|
|
102
|
+
/**
|
|
103
|
+
* Add a reference to a related record via a navigation property.
|
|
104
|
+
*
|
|
105
|
+
* `POST /<EntitySet>(<key>)/<navProperty>/$ref`
|
|
106
|
+
*
|
|
107
|
+
* For single-valued navigation properties, use `setRef()` instead (PATCH).
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* ```ts
|
|
111
|
+
* await db.from('contact').byKey(7).addRef('addresses', 42)
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
114
|
+
addRef(navProperty: string, relatedKey: string | number, opts?: EntityWriteOptions): Promise<void>;
|
|
115
|
+
/**
|
|
116
|
+
* Set (replace) the reference for a single-valued navigation property.
|
|
117
|
+
*
|
|
118
|
+
* `PATCH /<EntitySet>(<key>)/<navProperty>/$ref`
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* ```ts
|
|
122
|
+
* await db.from('order').byKey(100).setRef('customer', 7)
|
|
123
|
+
* ```
|
|
124
|
+
*/
|
|
125
|
+
setRef(navProperty: string, relatedKey: string | number, opts?: EntityWriteOptions): Promise<void>;
|
|
126
|
+
/**
|
|
127
|
+
* Remove a reference from a navigation property.
|
|
128
|
+
*
|
|
129
|
+
* `DELETE /<EntitySet>(<key>)/<navProperty>/$ref`
|
|
130
|
+
*
|
|
131
|
+
* For collection-valued navigation properties, pass the `relatedKey` to
|
|
132
|
+
* remove a specific reference. For single-valued, omit `relatedKey` to
|
|
133
|
+
* clear the reference.
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* ```ts
|
|
137
|
+
* await db.from('contact').byKey(7).removeRef('addresses', 42)
|
|
138
|
+
* await db.from('order').byKey(100).removeRef('customer')
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
141
|
+
removeRef(navProperty: string, relatedKey?: string | number, opts?: EntityWriteOptions): Promise<void>;
|
|
142
|
+
/** @internal — build a relative @odata.id for a related entity. */
|
|
143
|
+
private _refId;
|
|
144
|
+
}
|
|
145
|
+
/** Entity reference info returned by `getRefs()`. */
|
|
146
|
+
export interface EntityRefInfo {
|
|
147
|
+
'@odata.id': string;
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=entity.d.ts.map
|