fms-odata-js 0.3.0 → 0.4.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.
@@ -62,7 +62,7 @@ function isFMScriptError(err) {
62
62
  }
63
63
 
64
64
  // src/http.ts
65
- var AUTH_SCHEME_RE = /^(basic|bearer|fmid|negotiate|digest)\s+\S/i;
65
+ var AUTH_SCHEME_RE = /^(basic|bearer|negotiate|digest)\s+\S/i;
66
66
  async function resolveAuthHeader(provider) {
67
67
  const raw = typeof provider === "function" ? await provider() : provider;
68
68
  if (typeof raw !== "string" || raw.length === 0) {
@@ -75,8 +75,8 @@ function basicAuth(user, password) {
75
75
  const b64 = typeof Buffer !== "undefined" ? Buffer.from(raw, "utf8").toString("base64") : btoa(unescape(encodeURIComponent(raw)));
76
76
  return `Basic ${b64}`;
77
77
  }
78
- function fmidAuth(token) {
79
- return `FMID ${token}`;
78
+ function bearerAuth(token) {
79
+ return `Bearer ${token}`;
80
80
  }
81
81
  function combineSignals(signals) {
82
82
  const filtered = signals.filter((s) => s !== void 0);
@@ -515,20 +515,21 @@ var Batch = class {
515
515
 
516
516
  // node_modules/@fms-odata/spec-ts/dist/versions.js
517
517
  var FM_VERSION_NAMES = {
518
- "19": "FileMaker 19.x",
519
- "21": "Claris FileMaker 2023",
520
- "22": "Claris FileMaker 2024",
518
+ "20": "Claris FileMaker 2023",
519
+ "21": "Claris FileMaker 2024",
520
+ "22": "Claris FileMaker 2025",
521
521
  "26": "Claris FileMaker 2026",
522
522
  future: "Future / next"
523
523
  };
524
- var ODATA_PROTOCOL_VERSION = "4.0";
524
+ var ODATA_PROTOCOL_VERSION = "4.01";
525
525
  var FM_VERSION_MATRIX = {
526
- "19": {
527
- major: "19",
528
- name: "FileMaker 19.x",
529
- releaseYear: null,
530
- internalVersion: "19.x",
531
- status: "baseline",
526
+ "20": {
527
+ major: "20",
528
+ name: "Claris FileMaker 2023",
529
+ releaseYear: 2023,
530
+ internalVersion: "20.x",
531
+ status: "supported",
532
+ odataProtocolVersion: "4.0",
532
533
  features: {
533
534
  serviceDocument: true,
534
535
  metadata: true,
@@ -550,12 +551,17 @@ var FM_VERSION_MATRIX = {
550
551
  applyAggregation: false,
551
552
  typeCasting: false,
552
553
  parameterizedFilters: false,
554
+ simplifiedQuerySyntax: false,
555
+ nestedQueries: false,
556
+ batchPreferenceInheritance: false,
557
+ metadataFiltering: false,
553
558
  immutableIdUrls: false,
559
+ fmComment: false,
554
560
  aiAnnotation: false,
561
+ computedAnnotation: false,
555
562
  serverVersionAnnotation: false,
556
563
  enrichedFMComment: false,
557
564
  authBasic: true,
558
- authFMID: false,
559
565
  authOAuth: false
560
566
  },
561
567
  queryOptions: {
@@ -573,10 +579,11 @@ var FM_VERSION_MATRIX = {
573
579
  },
574
580
  "21": {
575
581
  major: "21",
576
- name: "Claris FileMaker 2023",
577
- releaseYear: 2023,
582
+ name: "Claris FileMaker 2024",
583
+ releaseYear: 2024,
578
584
  internalVersion: "21.x",
579
585
  status: "supported",
586
+ odataProtocolVersion: "4.01",
580
587
  features: {
581
588
  serviceDocument: true,
582
589
  metadata: true,
@@ -587,23 +594,28 @@ var FM_VERSION_MATRIX = {
587
594
  crossJoin: true,
588
595
  batch: true,
589
596
  scripts: true,
590
- scriptsByFMSID: false,
597
+ scriptsByFMSID: true,
591
598
  scriptListing: false,
592
599
  containerBinaryUpload: true,
593
600
  containerBase64Upload: true,
594
601
  containerDownload: true,
595
602
  schemaModification: true,
596
- webhooks: true,
603
+ webhooks: false,
597
604
  webhookQueryHeaders: false,
598
605
  applyAggregation: false,
599
606
  typeCasting: true,
600
607
  parameterizedFilters: true,
608
+ simplifiedQuerySyntax: true,
609
+ nestedQueries: true,
610
+ batchPreferenceInheritance: true,
611
+ metadataFiltering: false,
601
612
  immutableIdUrls: false,
602
- aiAnnotation: false,
613
+ fmComment: true,
614
+ aiAnnotation: true,
615
+ computedAnnotation: false,
603
616
  serverVersionAnnotation: false,
604
617
  enrichedFMComment: false,
605
618
  authBasic: true,
606
- authFMID: true,
607
619
  authOAuth: true
608
620
  },
609
621
  queryOptions: {
@@ -621,10 +633,11 @@ var FM_VERSION_MATRIX = {
621
633
  },
622
634
  "22": {
623
635
  major: "22",
624
- name: "Claris FileMaker 2024",
625
- releaseYear: 2024,
636
+ name: "Claris FileMaker 2025",
637
+ releaseYear: 2025,
626
638
  internalVersion: "22.x",
627
639
  status: "supported",
640
+ odataProtocolVersion: "4.01",
628
641
  features: {
629
642
  serviceDocument: true,
630
643
  metadata: true,
@@ -635,7 +648,7 @@ var FM_VERSION_MATRIX = {
635
648
  crossJoin: true,
636
649
  batch: true,
637
650
  scripts: true,
638
- scriptsByFMSID: false,
651
+ scriptsByFMSID: true,
639
652
  scriptListing: false,
640
653
  containerBinaryUpload: true,
641
654
  containerBase64Upload: true,
@@ -646,12 +659,17 @@ var FM_VERSION_MATRIX = {
646
659
  applyAggregation: true,
647
660
  typeCasting: true,
648
661
  parameterizedFilters: true,
662
+ simplifiedQuerySyntax: true,
663
+ nestedQueries: true,
664
+ batchPreferenceInheritance: true,
665
+ metadataFiltering: true,
649
666
  immutableIdUrls: false,
650
- aiAnnotation: false,
667
+ fmComment: true,
668
+ aiAnnotation: true,
669
+ computedAnnotation: false,
651
670
  serverVersionAnnotation: false,
652
671
  enrichedFMComment: false,
653
672
  authBasic: true,
654
- authFMID: true,
655
673
  authOAuth: true
656
674
  },
657
675
  queryOptions: {
@@ -673,6 +691,7 @@ var FM_VERSION_MATRIX = {
673
691
  releaseYear: 2026,
674
692
  internalVersion: "26.x",
675
693
  status: "current",
694
+ odataProtocolVersion: "4.01",
676
695
  features: {
677
696
  serviceDocument: true,
678
697
  metadata: true,
@@ -694,12 +713,17 @@ var FM_VERSION_MATRIX = {
694
713
  applyAggregation: true,
695
714
  typeCasting: true,
696
715
  parameterizedFilters: true,
716
+ simplifiedQuerySyntax: true,
717
+ nestedQueries: true,
718
+ batchPreferenceInheritance: true,
719
+ metadataFiltering: true,
697
720
  immutableIdUrls: true,
721
+ fmComment: true,
698
722
  aiAnnotation: true,
723
+ computedAnnotation: true,
699
724
  serverVersionAnnotation: true,
700
725
  enrichedFMComment: true,
701
726
  authBasic: true,
702
- authFMID: true,
703
727
  authOAuth: true
704
728
  },
705
729
  queryOptions: {
@@ -721,6 +745,7 @@ var FM_VERSION_MATRIX = {
721
745
  releaseYear: null,
722
746
  internalVersion: "unknown",
723
747
  status: "future",
748
+ odataProtocolVersion: "4.01",
724
749
  features: {
725
750
  serviceDocument: true,
726
751
  metadata: true,
@@ -742,12 +767,17 @@ var FM_VERSION_MATRIX = {
742
767
  applyAggregation: true,
743
768
  typeCasting: true,
744
769
  parameterizedFilters: true,
770
+ simplifiedQuerySyntax: true,
771
+ nestedQueries: true,
772
+ batchPreferenceInheritance: true,
773
+ metadataFiltering: true,
745
774
  immutableIdUrls: true,
775
+ fmComment: true,
746
776
  aiAnnotation: true,
777
+ computedAnnotation: true,
747
778
  serverVersionAnnotation: true,
748
779
  enrichedFMComment: true,
749
780
  authBasic: true,
750
- authFMID: true,
751
781
  authOAuth: true
752
782
  },
753
783
  queryOptions: {
@@ -771,7 +801,7 @@ function hasQueryOption(version, option) {
771
801
  return FM_VERSION_MATRIX[version]?.queryOptions[option] ?? false;
772
802
  }
773
803
  function minVersionForFeature(feature) {
774
- const order = ["19", "21", "22", "26"];
804
+ const order = ["20", "21", "22", "26"];
775
805
  for (const v of order) {
776
806
  if (FM_VERSION_MATRIX[v].features[feature])
777
807
  return v;
@@ -827,6 +857,43 @@ function parseServerVersion(metadataXml) {
827
857
  return null;
828
858
  }
829
859
 
860
+ // node_modules/@fms-odata/spec-ts/dist/schema.js
861
+ var FIELD_TYPES = [
862
+ "NUMERIC",
863
+ "DECIMAL",
864
+ "INT",
865
+ "DATE",
866
+ "TIME",
867
+ "TIMESTAMP",
868
+ "VARCHAR",
869
+ "CHARACTER VARYING",
870
+ "BLOB",
871
+ "VARBINARY",
872
+ "LONGVARBINARY",
873
+ "BINARY VARYING"
874
+ ];
875
+ var FIELD_DEFAULTS = [
876
+ "USER",
877
+ "USERNAME",
878
+ "CURRENT_USER",
879
+ "CURRENT_DATE",
880
+ "CURDATE",
881
+ "CURRENT_TIME",
882
+ "CURTIME",
883
+ "CURRENT_TIMESTAMP",
884
+ "CURTIMESTAMP"
885
+ ];
886
+ function parseFieldType(typeStr) {
887
+ const lengthMatch = typeStr.match(/\((\d+)\)/);
888
+ const repMatch = typeStr.match(/\[(\d+)\]/);
889
+ const baseType = typeStr.replace(/\(.*?\)/, "").replace(/\[.*?\]/, "").trim();
890
+ return {
891
+ baseType,
892
+ length: lengthMatch ? parseInt(lengthMatch[1], 10) : void 0,
893
+ repetitions: repMatch ? parseInt(repMatch[1], 10) : void 0
894
+ };
895
+ }
896
+
830
897
  // src/metadata.ts
831
898
  function parseBoolAttr(value, defaultValue) {
832
899
  if (value === void 0) return defaultValue;
@@ -1759,13 +1826,13 @@ var Query = class _Query {
1759
1826
  return this;
1760
1827
  }
1761
1828
  // -------------------------------------------------------------------------
1762
- // $apply (aggregation) — requires FMS 22.0.1+ (FileMaker 2024)
1829
+ // $apply (aggregation) — requires FMS 22.0.1+ (FileMaker 2025)
1763
1830
  // -------------------------------------------------------------------------
1764
1831
  /**
1765
1832
  * Set a raw `$apply` expression. Use this for advanced transformations
1766
1833
  * that the `aggregate()` / `groupBy()` helpers don't cover.
1767
1834
  *
1768
- * Requires FileMaker Server 2024+ (v22). Use `db.hasFeature('applyAggregation')`
1835
+ * Requires FileMaker Server 2025+ (v22). Use `db.hasFeature('applyAggregation')`
1769
1836
  * to check before calling.
1770
1837
  *
1771
1838
  * @example
@@ -1781,7 +1848,7 @@ var Query = class _Query {
1781
1848
  /**
1782
1849
  * Aggregate the entity set. Produces a `$apply=aggregate(...)` expression.
1783
1850
  *
1784
- * Requires FileMaker Server 2024+ (v22).
1851
+ * Requires FileMaker Server 2025+ (v22).
1785
1852
  *
1786
1853
  * @example
1787
1854
  * ```ts
@@ -1800,7 +1867,7 @@ var Query = class _Query {
1800
1867
  * Group the entity set by one or more fields, optionally with aggregation.
1801
1868
  * Produces a `$apply=groupby((fields), aggregate(...))` expression.
1802
1869
  *
1803
- * Requires FileMaker Server 2024+ (v22).
1870
+ * Requires FileMaker Server 2025+ (v22).
1804
1871
  *
1805
1872
  * @example
1806
1873
  * ```ts
@@ -1951,6 +2018,295 @@ function serializeOptions(s, opts) {
1951
2018
  return pairs.map(([k, v]) => `${k}=${v}`).join(";");
1952
2019
  }
1953
2020
 
2021
+ // src/schema.ts
2022
+ function validateFieldDefinitions(fields) {
2023
+ for (const f of fields) {
2024
+ if (!f.name) throw new TypeError("SchemaEditor: field `name` is required");
2025
+ if (!f.type) throw new TypeError("SchemaEditor: field `type` is required");
2026
+ const { baseType } = parseFieldType(f.type);
2027
+ const upper = baseType.toUpperCase();
2028
+ if (!FIELD_TYPES.includes(upper)) {
2029
+ throw new TypeError(
2030
+ `SchemaEditor: unsupported field type "${f.type}" (base: "${baseType}"). Supported: ${FIELD_TYPES.join(", ")}`
2031
+ );
2032
+ }
2033
+ }
2034
+ }
2035
+ var SchemaEditor = class {
2036
+ constructor(client) {
2037
+ this._client = client;
2038
+ }
2039
+ /**
2040
+ * Create a new table with the given field definitions.
2041
+ *
2042
+ * ```ts
2043
+ * await db.schema().createTable({
2044
+ * tableName: 'Company',
2045
+ * fields: [
2046
+ * { name: 'Company ID', type: 'int', primary: true },
2047
+ * { name: 'Company Name', type: 'varchar(100)', nullable: false },
2048
+ * ],
2049
+ * })
2050
+ * ```
2051
+ */
2052
+ async createTable(params, opts = {}) {
2053
+ if (!params.tableName) throw new TypeError("SchemaEditor: `tableName` is required");
2054
+ if (!params.fields?.length) throw new TypeError("SchemaEditor: `fields` must be a non-empty array");
2055
+ validateFieldDefinitions(params.fields);
2056
+ const url = `${this._client.baseUrl}/FileMaker_Tables`;
2057
+ return executeJson(this._client._ctx, url, {
2058
+ method: "POST",
2059
+ headers: { "Content-Type": "application/json" },
2060
+ body: JSON.stringify(params),
2061
+ accept: "json",
2062
+ ...opts.signal ? { signal: opts.signal } : {}
2063
+ });
2064
+ }
2065
+ /**
2066
+ * Add fields to an existing table.
2067
+ *
2068
+ * ```ts
2069
+ * await db.schema().addFields({
2070
+ * tableName: 'Company',
2071
+ * fields: [{ name: 'Phone', type: 'varchar(30)' }],
2072
+ * })
2073
+ * ```
2074
+ */
2075
+ async addFields(params, opts = {}) {
2076
+ if (!params.tableName) throw new TypeError("SchemaEditor: `tableName` is required");
2077
+ if (!params.fields?.length) throw new TypeError("SchemaEditor: `fields` must be a non-empty array");
2078
+ validateFieldDefinitions(params.fields);
2079
+ const url = `${this._client.baseUrl}/FileMaker_Tables/${encodePathSegment(params.tableName)}`;
2080
+ return executeJson(this._client._ctx, url, {
2081
+ method: "PATCH",
2082
+ headers: { "Content-Type": "application/json" },
2083
+ body: JSON.stringify({ fields: params.fields }),
2084
+ accept: "json",
2085
+ ...opts.signal ? { signal: opts.signal } : {}
2086
+ });
2087
+ }
2088
+ /**
2089
+ * Delete a table and ALL its records. Irreversible.
2090
+ *
2091
+ * Requires `opts.confirm === true` as a safety guard.
2092
+ *
2093
+ * ```ts
2094
+ * await db.schema().deleteTable('OldTable', { confirm: true })
2095
+ * ```
2096
+ */
2097
+ async deleteTable(tableName, opts) {
2098
+ if (!tableName) throw new TypeError("SchemaEditor: `tableName` is required");
2099
+ if (opts.confirm !== true) {
2100
+ throw new TypeError(
2101
+ `SchemaEditor: deleteTable("${tableName}") requires { confirm: true } \u2014 this operation permanently destroys the table and all its records.`
2102
+ );
2103
+ }
2104
+ const url = `${this._client.baseUrl}/FileMaker_Tables/${encodePathSegment(tableName)}`;
2105
+ await executeJson(this._client._ctx, url, {
2106
+ method: "DELETE",
2107
+ accept: "json",
2108
+ ...opts.signal ? { signal: opts.signal } : {}
2109
+ });
2110
+ }
2111
+ /**
2112
+ * Delete a field from a table. Irreversible.
2113
+ *
2114
+ * Requires `opts.confirm === true` as a safety guard.
2115
+ *
2116
+ * ```ts
2117
+ * await db.schema().deleteField('Company', 'OldField', { confirm: true })
2118
+ * ```
2119
+ */
2120
+ async deleteField(tableName, fieldName, opts) {
2121
+ if (!tableName) throw new TypeError("SchemaEditor: `tableName` is required");
2122
+ if (!fieldName) throw new TypeError("SchemaEditor: `fieldName` is required");
2123
+ if (opts.confirm !== true) {
2124
+ throw new TypeError(
2125
+ `SchemaEditor: deleteField("${tableName}", "${fieldName}") requires { confirm: true } \u2014 this operation permanently destroys the field and all its data.`
2126
+ );
2127
+ }
2128
+ const url = `${this._client.baseUrl}/FileMaker_Tables/${encodePathSegment(tableName)}/${encodePathSegment(fieldName)}`;
2129
+ await executeJson(this._client._ctx, url, {
2130
+ method: "DELETE",
2131
+ accept: "json",
2132
+ ...opts.signal ? { signal: opts.signal } : {}
2133
+ });
2134
+ }
2135
+ /**
2136
+ * Create an index on a field.
2137
+ *
2138
+ * ```ts
2139
+ * await db.schema().createIndex('Company', 'Company Name')
2140
+ * ```
2141
+ */
2142
+ async createIndex(tableName, fieldName, opts = {}) {
2143
+ if (!tableName) throw new TypeError("SchemaEditor: `tableName` is required");
2144
+ if (!fieldName) throw new TypeError("SchemaEditor: `fieldName` is required");
2145
+ const url = `${this._client.baseUrl}/FileMaker_Indexes/${encodePathSegment(tableName)}`;
2146
+ return executeJson(this._client._ctx, url, {
2147
+ method: "POST",
2148
+ headers: { "Content-Type": "application/json" },
2149
+ body: JSON.stringify({ indexName: fieldName }),
2150
+ accept: "json",
2151
+ ...opts.signal ? { signal: opts.signal } : {}
2152
+ });
2153
+ }
2154
+ /**
2155
+ * Delete an index from a field.
2156
+ *
2157
+ * ```ts
2158
+ * await db.schema().deleteIndex('Company', 'Company Name')
2159
+ * ```
2160
+ */
2161
+ async deleteIndex(tableName, fieldName, opts = {}) {
2162
+ if (!tableName) throw new TypeError("SchemaEditor: `tableName` is required");
2163
+ if (!fieldName) throw new TypeError("SchemaEditor: `fieldName` is required");
2164
+ const url = `${this._client.baseUrl}/FileMaker_Indexes/${encodePathSegment(tableName)}/${encodePathSegment(fieldName)}`;
2165
+ await executeJson(this._client._ctx, url, {
2166
+ method: "DELETE",
2167
+ accept: "json",
2168
+ ...opts.signal ? { signal: opts.signal } : {}
2169
+ });
2170
+ }
2171
+ };
2172
+
2173
+ // src/webhooks.ts
2174
+ function normalizeCreateParams(params) {
2175
+ const out = {
2176
+ webhook: params.webhook,
2177
+ tableName: params.tableName
2178
+ };
2179
+ if (params.endpointHeaders) out.endpointHeaders = params.endpointHeaders;
2180
+ if (params.headers && !params.endpointHeaders) out.endpointHeaders = params.headers;
2181
+ if (params.queryHeaders) out.queryHeaders = params.queryHeaders;
2182
+ if (params.notifySchemaChanges !== void 0) out.notifySchemaChanges = params.notifySchemaChanges;
2183
+ if (params.select) out.select = params.select;
2184
+ if (params.filter) out.filter = params.filter;
2185
+ if (params.maxFailedAttempts !== void 0) out.maxFailedAttempts = params.maxFailedAttempts;
2186
+ return out;
2187
+ }
2188
+ function extractWebhookId(data) {
2189
+ if (!data || typeof data !== "object") return void 0;
2190
+ const obj = data;
2191
+ const fromResult = obj.webhookResult;
2192
+ const id = fromResult?.webhookID ?? obj.webhookID ?? obj.id;
2193
+ return id !== void 0 ? String(id) : void 0;
2194
+ }
2195
+ var WebhookManager = class {
2196
+ constructor(client) {
2197
+ this._client = client;
2198
+ }
2199
+ /** Build the URL for a webhook operation. */
2200
+ _url(operation, id) {
2201
+ const base = `${this._client.baseUrl}/Webhook.${operation}`;
2202
+ return id !== void 0 ? `${base}(${encodePathSegment(String(id))})` : base;
2203
+ }
2204
+ /**
2205
+ * Create a webhook.
2206
+ *
2207
+ * ```ts
2208
+ * const { id } = await db.webhooks().create({
2209
+ * webhook: 'https://my.example.com:8080/webhook',
2210
+ * tableName: 'contact',
2211
+ * select: 'id,first_name',
2212
+ * filter: "status eq 'active'",
2213
+ * notifySchemaChanges: true,
2214
+ * })
2215
+ * ```
2216
+ *
2217
+ * @returns An object with the server-generated `id` of the new webhook.
2218
+ */
2219
+ async create(params, opts = {}) {
2220
+ if (!params.webhook) throw new TypeError("WebhookManager: `webhook` URL is required");
2221
+ if (!params.tableName) throw new TypeError("WebhookManager: `tableName` is required");
2222
+ const body = JSON.stringify(normalizeCreateParams(params));
2223
+ const data = await executeJson(this._client._ctx, this._url("Add"), {
2224
+ method: "POST",
2225
+ headers: { "Content-Type": "application/json" },
2226
+ body,
2227
+ accept: "json",
2228
+ ...opts.signal ? { signal: opts.signal } : {}
2229
+ });
2230
+ const id = extractWebhookId(data);
2231
+ if (id === void 0) {
2232
+ throw new Error("WebhookManager: Webhook.Add did not return a webhook id");
2233
+ }
2234
+ return { id };
2235
+ }
2236
+ /**
2237
+ * Delete a webhook by its ID.
2238
+ *
2239
+ * ```ts
2240
+ * await db.webhooks().remove('1')
2241
+ * ```
2242
+ */
2243
+ async remove(id, opts = {}) {
2244
+ if (id === "" || id === void 0) throw new TypeError("WebhookManager: webhook `id` is required");
2245
+ return executeJson(this._client._ctx, this._url("Delete", id), {
2246
+ method: "POST",
2247
+ accept: "json",
2248
+ ...opts.signal ? { signal: opts.signal } : {}
2249
+ });
2250
+ }
2251
+ /** Alias for {@link remove} (matches the FMS endpoint name `Webhook.Delete`). */
2252
+ async delete(id, opts = {}) {
2253
+ return this.remove(id, opts);
2254
+ }
2255
+ /**
2256
+ * Get a specific webhook's data by ID.
2257
+ *
2258
+ * ```ts
2259
+ * const data = await db.webhooks().get('1')
2260
+ * ```
2261
+ */
2262
+ async get(id, opts = {}) {
2263
+ if (id === "" || id === void 0) throw new TypeError("WebhookManager: webhook `id` is required");
2264
+ return executeJson(this._client._ctx, this._url("Get", id), {
2265
+ method: "GET",
2266
+ accept: "json",
2267
+ ...opts.signal ? { signal: opts.signal } : {}
2268
+ });
2269
+ }
2270
+ /**
2271
+ * List all webhooks.
2272
+ *
2273
+ * ```ts
2274
+ * const result = await db.webhooks().getAll()
2275
+ * ```
2276
+ */
2277
+ async getAll(opts = {}) {
2278
+ return executeJson(this._client._ctx, this._url("GetAll"), {
2279
+ method: "GET",
2280
+ accept: "json",
2281
+ ...opts.signal ? { signal: opts.signal } : {}
2282
+ });
2283
+ }
2284
+ /**
2285
+ * Manually invoke (trigger) a webhook by ID. Useful for testing.
2286
+ *
2287
+ * Optionally pass `rowIDs` to target specific records; if omitted, FMS
2288
+ * triggers the webhook for all pending records.
2289
+ *
2290
+ * ```ts
2291
+ * await db.webhooks().invoke('1')
2292
+ * await db.webhooks().invoke('1', { rowIDs: [10, 20] })
2293
+ * ```
2294
+ */
2295
+ async invoke(id, opts = {}) {
2296
+ if (id === "" || id === void 0) throw new TypeError("WebhookManager: webhook `id` is required");
2297
+ const { rowIDs, signal, ...rest } = opts;
2298
+ const body = JSON.stringify({ rowIDs: rowIDs ? [...rowIDs] : [] });
2299
+ return executeJson(this._client._ctx, this._url("Invoke", id), {
2300
+ method: "POST",
2301
+ headers: { "Content-Type": "application/json" },
2302
+ body,
2303
+ accept: "json",
2304
+ ...signal ? { signal } : {},
2305
+ ...rest
2306
+ });
2307
+ }
2308
+ };
2309
+
1954
2310
  // src/client.ts
1955
2311
  var FMSOData = class {
1956
2312
  constructor(options) {
@@ -2054,7 +2410,7 @@ var FMSOData = class {
2054
2410
  * `@fms-odata/spec-ts` `parseServerVersion`). The result is cached for the
2055
2411
  * lifetime of this `FMSOData` instance.
2056
2412
  *
2057
- * Returns the major version string (`'19'`, `'21'`, `'22'`, `'26'`) or
2413
+ * Returns the major version string (`'20'`, `'21'`, `'22'`, `'26'`) or
2058
2414
  * `'future'` if the version is newer than the spec knows about. Returns
2059
2415
  * `null` if the version cannot be determined (e.g. the metadata lacks the
2060
2416
  * annotation).
@@ -2138,6 +2494,75 @@ var FMSOData = class {
2138
2494
  if (!v) return false;
2139
2495
  return hasFeature(v, feature);
2140
2496
  }
2497
+ /**
2498
+ * Get a `SchemaEditor` handle for DDL operations (create/delete tables,
2499
+ * add/delete fields, create/delete indexes). Requires a FileMaker account
2500
+ * with full access privileges.
2501
+ *
2502
+ * ```ts
2503
+ * await db.schema().createTable({ tableName: 'Company', fields: [...] })
2504
+ * ```
2505
+ */
2506
+ schema() {
2507
+ if (!this._schemaEditor) this._schemaEditor = new SchemaEditor(this);
2508
+ return this._schemaEditor;
2509
+ }
2510
+ /** Convenience: create a table. See {@link SchemaEditor#createTable}. */
2511
+ async createTable(params, opts = {}) {
2512
+ return this.schema().createTable(params, opts);
2513
+ }
2514
+ /** Convenience: add fields to a table. See {@link SchemaEditor#addFields}. */
2515
+ async addFields(params, opts = {}) {
2516
+ return this.schema().addFields(params, opts);
2517
+ }
2518
+ /** Convenience: delete a table (requires `confirm: true`). See {@link SchemaEditor#deleteTable}. */
2519
+ async deleteTable(tableName, opts) {
2520
+ return this.schema().deleteTable(tableName, opts);
2521
+ }
2522
+ /** Convenience: delete a field (requires `confirm: true`). See {@link SchemaEditor#deleteField}. */
2523
+ async deleteField(tableName, fieldName, opts) {
2524
+ return this.schema().deleteField(tableName, fieldName, opts);
2525
+ }
2526
+ /** Convenience: create an index. See {@link SchemaEditor#createIndex}. */
2527
+ async createIndex(tableName, fieldName, opts = {}) {
2528
+ return this.schema().createIndex(tableName, fieldName, opts);
2529
+ }
2530
+ /** Convenience: delete an index. See {@link SchemaEditor#deleteIndex}. */
2531
+ async deleteIndex(tableName, fieldName, opts = {}) {
2532
+ return this.schema().deleteIndex(tableName, fieldName, opts);
2533
+ }
2534
+ /**
2535
+ * Get a `WebhookManager` handle for webhook CRUD operations (create, remove,
2536
+ * get, getAll, invoke). Requires FileMaker Server 2025+ (v22).
2537
+ *
2538
+ * ```ts
2539
+ * await db.webhooks().create({ webhook: 'https://...', tableName: 'contact' })
2540
+ * ```
2541
+ */
2542
+ webhooks() {
2543
+ if (!this._webhookManager) this._webhookManager = new WebhookManager(this);
2544
+ return this._webhookManager;
2545
+ }
2546
+ /** Convenience: create a webhook. See {@link WebhookManager#create}. */
2547
+ async createWebhook(params, opts = {}) {
2548
+ return this.webhooks().create(params, opts);
2549
+ }
2550
+ /** Convenience: delete a webhook by ID. See {@link WebhookManager#remove}. */
2551
+ async removeWebhook(id, opts = {}) {
2552
+ return this.webhooks().remove(id, opts);
2553
+ }
2554
+ /** Convenience: get a webhook by ID. See {@link WebhookManager#get}. */
2555
+ async getWebhook(id, opts = {}) {
2556
+ return this.webhooks().get(id, opts);
2557
+ }
2558
+ /** Convenience: list all webhooks. See {@link WebhookManager#getAll}. */
2559
+ async getAllWebhooks(opts = {}) {
2560
+ return this.webhooks().getAll(opts);
2561
+ }
2562
+ /** Convenience: manually invoke a webhook by ID. See {@link WebhookManager#invoke}. */
2563
+ async invokeWebhook(id, opts = {}) {
2564
+ return this.webhooks().invoke(id, opts);
2565
+ }
2141
2566
  /**
2142
2567
  * Create a new `$batch` builder for composing multiple OData operations
2143
2568
  * into a single HTTP round-trip.
@@ -2171,6 +2596,8 @@ export {
2171
2596
  Changeset,
2172
2597
  ContainerRef,
2173
2598
  EntityRef,
2599
+ FIELD_DEFAULTS,
2600
+ FIELD_TYPES,
2174
2601
  FMSOData as FMOData,
2175
2602
  FMSODataError as FMODataError,
2176
2603
  FMSOData,
@@ -2183,17 +2610,20 @@ export {
2183
2610
  MetadataFetcher,
2184
2611
  ODATA_PROTOCOL_VERSION,
2185
2612
  Query,
2613
+ SchemaEditor,
2186
2614
  ScriptInvoker,
2615
+ WebhookManager,
2187
2616
  basicAuth,
2617
+ bearerAuth,
2188
2618
  buildContainerJsonBody,
2189
2619
  filterFactory,
2190
- fmidAuth,
2191
2620
  hasFeature,
2192
2621
  hasQueryOption,
2193
2622
  isFMSODataError as isFMODataError,
2194
2623
  isFMSODataError,
2195
2624
  isFMScriptError,
2196
2625
  minVersionForFeature,
2626
+ parseFieldType,
2197
2627
  parseServerVersion,
2198
2628
  parseVersionString,
2199
2629
  sniffContainerMime,