@rex0220/kintone-sql-tools 3.74.0 → 3.75.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/README.md CHANGED
@@ -267,7 +267,7 @@ npm パッケージは 2 つのサブパスを **semver 対象の公開 API**
267
267
  | サブパス | 用途 | 主な export |
268
268
  |---|---|---|
269
269
  | `@rex0220/kintone-sql-tools/engine` | **read-only** のクエリ実行(ダッシュボード等)。書込 API は構造的に遮断 | `runQuery` / `runBatch` / `explainQuery` / `createReadonlyKintoneClient` / `KsqlEngineError` / `version` |
270
- | `@rex0220/kintone-sql-tools/flow` | **Flow dialect 1**(→ [言語リファレンス §27](docs/ksql_language_reference.md))のスクリプト解析・検証・**文単位実行**(バッチランナー向け・書込可能) | `parseScript` / `validateScript` / `explainScript`(`asOf`/`timezone` 注入可) / `createExecutionContext`(`onChunkWritten` 書込チャンク通知) / `executeStatement` / `previewStatement`(dry-run 差分プレビュー・書込 0 回) / `disposeExecutionContext` / `createKintoneClient` / `isDmlResult`(`FlowDmlResult` 型ガード) / `version` |
270
+ | `@rex0220/kintone-sql-tools/flow` | **Flow dialect 1**(→ [言語リファレンス §27](docs/ksql_language_reference.md))のスクリプト解析・検証・**文単位実行**(バッチランナー向け・書込可能) | `parseScript` / `validateScript` / `explainScript`(`asOf`/`timezone` 注入可) / `createExecutionContext`(`onChunkWritten` 書込チャンク通知) / `executeStatement` / `previewStatement`(dry-run 差分プレビュー・書込 0 回) / `disposeExecutionContext` / `createImportSourceResolver` / `FlowImportProviderError` / `createKintoneClient` / `isDmlResult`(`FlowDmlResult` 型ガード) / `version` |
271
271
 
272
272
  バッチ実行ランナー **kSQL Flow**(`/flow` API を使った公式ランナー・別リポジトリ): https://github.com/rex0220/ksql-flow
273
273
 
@@ -289,6 +289,34 @@ try {
289
289
  }
290
290
  ```
291
291
 
292
+ `/flow` から named `IMPORT` source を使う場合は、全APIへ `enableImport: true` を明示し、
293
+ pathではなくlazy loaderが返す `Uint8Array` を登録します。省略または `false` は従来どおり
294
+ `KSQL1202` で拒否し、resolverを渡すだけでは有効になりません。
295
+
296
+ ```ts
297
+ import {
298
+ createImportSourceResolver,
299
+ parseScript,
300
+ createExecutionContext,
301
+ } from "@rex0220/kintone-sql-tools/flow";
302
+
303
+ const capability = { enableImport: true } as const;
304
+ const importSource = createImportSourceResolver([{
305
+ name: "orders",
306
+ loader: { async load() { return { bytes, encoding: "sjis" }; } },
307
+ }]);
308
+ const parsed = parseScript(sql, capability);
309
+ const ctx = createExecutionContext({
310
+ ...capability, client, statements: parsed.statements, meta: parsed.meta, importSource,
311
+ });
312
+ ```
313
+
314
+ source名は完全一致・case-sensitiveです。loaderは対象文の実行到達時だけ呼ばれます。
315
+ CSVの文字コードは `SQL ENCODING` > loader metadata > UTF-8、payload上限は10 MiBです。
316
+ path解決、通常ファイル・symlink・allowlist・hash検査、open/readは呼出側の責務であり、
317
+ engineはpathを受け取りません。providerは `FlowImportProviderError` でread不能または通常ファイル外を
318
+ 分類でき、その他のsource境界エラーも `StatementResult.error.code` の安定codeで返ります。
319
+
292
320
  読取上限は既定 10,000 件(`maxRecords`)です。一時テーブルの実体化には**独立の** `tempTableMaxRows`(既定 10,000 行・超過は常にエラー)が適用されるため、大きなバッチでは両方を併せて指定してください:
293
321
 
294
322
  ```ts
package/dist-cli/ksql.js CHANGED
@@ -17606,19 +17606,86 @@ var ImportSourceError = class extends Error {
17606
17606
  this.name = "ImportSourceError";
17607
17607
  }
17608
17608
  };
17609
+ var ImportSourceBoundaryError = class extends Error {
17610
+ constructor(code, message, cause) {
17611
+ super(`${code}: ${message}`);
17612
+ this.name = code;
17613
+ this.code = code;
17614
+ if (cause !== void 0) {
17615
+ Object.defineProperty(this, "cause", {
17616
+ value: cause,
17617
+ enumerable: false,
17618
+ configurable: false,
17619
+ writable: false
17620
+ });
17621
+ }
17622
+ }
17623
+ };
17624
+ function providerError(error) {
17625
+ if (error === null || typeof error !== "object") return void 0;
17626
+ const shaped = error;
17627
+ const code = shaped.code === "ImportSourceReadError" || shaped.code === "ImportSourceNotRegularFileError" ? shaped.code : shaped.name === "ImportSourceReadError" || shaped.name === "ImportSourceNotRegularFileError" ? shaped.name : void 0;
17628
+ if (!code) return void 0;
17629
+ const fallback = code === "ImportSourceReadError" ? "IMPORT source could not be read." : "IMPORT source is not a regular file.";
17630
+ return new ImportSourceBoundaryError(
17631
+ code,
17632
+ typeof shaped.message === "string" && shaped.message.length > 0 ? shaped.message : fallback,
17633
+ error
17634
+ );
17635
+ }
17636
+ function isUint8Array(value) {
17637
+ return value !== null && typeof value === "object" && ArrayBuffer.isView(value) && Object.prototype.toString.call(value) === "[object Uint8Array]";
17638
+ }
17609
17639
  function resolveImportSource(name, resolver) {
17610
- if (!resolver) throw new ImportSourceError("IMPORT source capability is not available.");
17611
- const handle = resolver(name);
17612
- if (!handle) throw new ImportSourceError(`source "${name}" is not supplied.`);
17613
- return handle;
17640
+ if (!resolver) {
17641
+ throw new ImportSourceBoundaryError(
17642
+ "ImportSourceNotSuppliedError",
17643
+ `the named IMPORT source ${JSON.stringify(name)} was not supplied.`
17644
+ );
17645
+ }
17646
+ try {
17647
+ const handle = resolver(name);
17648
+ if (!handle) {
17649
+ throw new ImportSourceBoundaryError(
17650
+ "ImportSourceNotSuppliedError",
17651
+ `the named IMPORT source ${JSON.stringify(name)} was not supplied.`
17652
+ );
17653
+ }
17654
+ if (typeof handle !== "object" || typeof handle.load !== "function") {
17655
+ throw new ImportSourceBoundaryError(
17656
+ "ImportSourceInvalidPayloadError",
17657
+ "resolver must return a handle with a load function."
17658
+ );
17659
+ }
17660
+ return handle;
17661
+ } catch (error) {
17662
+ if (error instanceof ImportSourceBoundaryError) throw error;
17663
+ throw providerError(error) ?? new ImportSourceBoundaryError("ImportSourceReadError", "IMPORT source resolution failed.", error);
17664
+ }
17614
17665
  }
17615
17666
  async function loadImportSource(handle, cache) {
17616
17667
  let pending = cache.get(handle);
17617
17668
  if (!pending) {
17618
- pending = handle.load().then((payload) => {
17619
- if (!(payload.bytes instanceof Uint8Array)) throw new ImportSourceError("loader must return Uint8Array bytes.");
17669
+ pending = Promise.resolve().then(() => handle.load()).catch((error) => {
17670
+ throw providerError(error) ?? new ImportSourceBoundaryError("ImportSourceReadError", "IMPORT source could not be read.", error);
17671
+ }).then((payload) => {
17672
+ if (payload === null || typeof payload !== "object" || !isUint8Array(payload.bytes)) {
17673
+ throw new ImportSourceBoundaryError(
17674
+ "ImportSourceInvalidPayloadError",
17675
+ "loader must return Uint8Array bytes."
17676
+ );
17677
+ }
17678
+ if (payload.encoding !== void 0 && payload.encoding !== "utf8" && payload.encoding !== "sjis") {
17679
+ throw new ImportSourceBoundaryError(
17680
+ "ImportSourceInvalidPayloadError",
17681
+ "loader returned an unsupported encoding."
17682
+ );
17683
+ }
17620
17684
  if (payload.bytes.byteLength > IMPORT_MAX_BYTES) {
17621
- throw new ImportSourceError(`source exceeds the ${IMPORT_MAX_BYTES} byte limit.`);
17685
+ throw new ImportSourceBoundaryError(
17686
+ "ImportSourceTooLargeError",
17687
+ `source exceeds the ${IMPORT_MAX_BYTES} byte limit.`
17688
+ );
17622
17689
  }
17623
17690
  return payload;
17624
17691
  });
@@ -29172,6 +29239,23 @@ function isSubtableRow(v) {
29172
29239
  return typeof obj.id === "string" && typeof obj.value === "object" && obj.value !== null;
29173
29240
  }
29174
29241
 
29242
+ // src/flow-library/importSources.ts
29243
+ var FlowImportProviderError = class extends Error {
29244
+ constructor(code, message, cause) {
29245
+ super(message);
29246
+ this.name = code;
29247
+ this.code = code;
29248
+ if (cause !== void 0) {
29249
+ Object.defineProperty(this, "cause", {
29250
+ value: cause,
29251
+ enumerable: false,
29252
+ configurable: false,
29253
+ writable: false
29254
+ });
29255
+ }
29256
+ }
29257
+ };
29258
+
29175
29259
  // src/output/batchEnvelope.ts
29176
29260
  function toMutationSummary(result) {
29177
29261
  if (result.type === "INSERT") {
@@ -32960,7 +33044,38 @@ async function run() {
32960
33044
  const importEnabled = Object.keys(args.importCsv).length > 0 || Object.keys(args.importJson).length > 0;
32961
33045
  const importSource = importEnabled ? (name) => {
32962
33046
  const sourcePath = args.importCsv[name] ?? args.importJson[name];
32963
- return sourcePath === void 0 ? void 0 : { load: async () => ({ bytes: new Uint8Array((0, import_fs2.readFileSync)(sourcePath)) }) };
33047
+ if (sourcePath === void 0) return void 0;
33048
+ return {
33049
+ load: async () => {
33050
+ let sourceStat;
33051
+ try {
33052
+ sourceStat = (0, import_fs2.statSync)(sourcePath);
33053
+ } catch (error) {
33054
+ const reason = error instanceof Error ? error.message : String(error);
33055
+ throw new FlowImportProviderError(
33056
+ "ImportSourceReadError",
33057
+ `failed to inspect IMPORT source "${sourcePath}": ${reason}`,
33058
+ error
33059
+ );
33060
+ }
33061
+ if (!sourceStat.isFile()) {
33062
+ throw new FlowImportProviderError(
33063
+ "ImportSourceNotRegularFileError",
33064
+ `IMPORT source "${sourcePath}" is not a regular file.`
33065
+ );
33066
+ }
33067
+ try {
33068
+ return { bytes: new Uint8Array((0, import_fs2.readFileSync)(sourcePath)) };
33069
+ } catch (error) {
33070
+ const reason = error instanceof Error ? error.message : String(error);
33071
+ throw new FlowImportProviderError(
33072
+ "ImportSourceReadError",
33073
+ `failed to read IMPORT source "${sourcePath}": ${reason}`,
33074
+ error
33075
+ );
33076
+ }
33077
+ }
33078
+ };
32964
33079
  } : void 0;
32965
33080
  const confirm = async (count, operation, context) => {
32966
33081
  if (count > dmlMaxRows) {