@phpsandbox/sdk 0.6.0-canary.5 → 0.6.0-canary.7

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
@@ -172,7 +172,6 @@ Each `NotebookInstance` exposes service clients:
172
172
  - `notebook.auth` (`Auth`)
173
173
  - `notebook.services` (`Services`)
174
174
  - `notebook.mail` (`NotebookMail`)
175
- - `notebook.feedback` (`Feedback`)
176
175
 
177
176
  Notes:
178
177
 
@@ -401,27 +400,39 @@ await notebook.mail.delete(mail.hash);
401
400
  await notebook.mail.disable();
402
401
  ```
403
402
 
403
+ ### Product features
404
+
405
+ Projects own the product lifecycle. Address Customers, Payments, feedback, and publishing with an existing project ID; creating a standalone compute sandbox does not create a project.
406
+
407
+ This replaces `notebook.feedback`, `notebook.payments`, `notebook.publication`, and the `forNotebook` helpers. Use a project ID explicitly; sandbox IDs are not aliases for projects.
408
+
409
+ ```ts
410
+ const customers = client.customers.forProject('PROJECT_ID');
411
+ const payments = client.payments.forProject('PROJECT_ID');
412
+ const publishing = client.publications.forProject('PROJECT_ID');
413
+ ```
414
+
404
415
  ### Feedback
405
416
 
406
417
  ```ts
407
418
  // Feedback uses the same API token as the rest of the SDK.
408
- // You can also address it directly with client.feedback.forProject(projectId)
409
- // or client.feedback.forNotebook(notebookId), without fetching a notebook.
410
- await notebook.feedback.configure({
419
+ // Product features require an existing project ID, not a sandbox ID.
420
+ const projectFeedback = client.feedback.forProject('PROJECT_ID');
421
+ await projectFeedback.configure({
411
422
  widget: {
412
423
  targets: ['preview', 'publication'],
413
424
  types: ['bug', 'suggestion', 'question'],
414
425
  },
415
426
  });
416
427
 
417
- const feedback = await notebook.feedback.submit({
428
+ const feedback = await projectFeedback.submit({
418
429
  type: 'bug',
419
430
  message: 'Saving the profile failed.',
420
431
  author: { externalId: 'customer-42', email: 'ada@example.com' },
421
432
  context: { url: 'https://app.example.com/profile', path: '/profile' },
422
433
  });
423
434
 
424
- await notebook.feedback.update(feedback.id, { status: 'resolved' });
435
+ await projectFeedback.update(feedback.id, { status: 'resolved' });
425
436
  ```
426
437
 
427
438
  The server-side SDK uses the same PHPSandbox API token as the rest of the sandbox. Set `widget: null` to remove the optional popout without disabling API ingestion.
@@ -479,42 +490,38 @@ MIT
479
490
 
480
491
  ### Publishing apps
481
492
 
482
- Save your app configuration, prepare a proposal for the apps you want to publish, and submit that proposal after explicit confirmation. Use `notebook.secrets` for production secrets and `notebook.publication.catalog` to discover destinations and available resources.
493
+ Save your app configuration, prepare a proposal for the apps you want to publish, and submit that proposal after explicit confirmation. Use `notebook.secrets` for production secrets and `client.publications.forProject(projectId).catalog` to discover destinations and available resources.
483
494
 
484
495
  ```ts
485
- const notebook = await client.notebook.get('SANDBOX_ID');
486
- try {
487
- const configuration = await notebook.publication.updateConfiguration({
488
- expectedRevision: 0,
489
- operations: [{ op: 'add-app', app: {
490
- name: 'Backend', kind: 'http', root: '.',
491
- build: { mode: 'dockerfile', context: '.', dockerfile: { source: 'project', path: 'Dockerfile' } },
492
- runtime: { mode: 'container', port: 3333, healthCheck: { path: '/' } },
493
- destination: { provider: 'ssh-server', serverId: 'OWNED_SERVER_ID' },
494
- routing: { mode: 'managed' }, resourceIds: [],
495
- } }],
496
- });
497
- const appIds = [configuration.apps[0].id];
498
- const plan = await notebook.publication.prepare({
499
- expectedRevision: configuration.revision, appIds,
496
+ const publisher = client.publications.forProject('PROJECT_ID');
497
+ const configuration = await publisher.updateConfiguration({
498
+ expectedRevision: 0,
499
+ operations: [{ op: 'add-app', app: {
500
+ name: 'Backend', kind: 'http', root: '.',
501
+ build: { mode: 'dockerfile', context: '.', dockerfile: { source: 'project', path: 'Dockerfile' } },
502
+ runtime: { mode: 'container', port: 3333, healthCheck: { path: '/' } },
503
+ destination: { provider: 'ssh-server', serverId: 'OWNED_SERVER_ID' },
504
+ routing: { mode: 'managed' }, resourceIds: [],
505
+ } }],
506
+ });
507
+ const appIds = [configuration.apps[0].id];
508
+ const plan = await publisher.prepare({
509
+ expectedRevision: configuration.revision, appIds,
510
+ });
511
+ if (!plan.review.ready) {
512
+ console.log(plan.review.blockers);
513
+ } else {
514
+ // Show this proposal to the user and wait for their confirmation.
515
+ const result = await publisher.publish({
516
+ reviewId: plan.review.id, proposal: plan.review.proposal, appIds,
500
517
  });
501
- if (!plan.review.ready) {
502
- console.log(plan.review.blockers);
518
+ if ('confirmationRequired' in result) {
519
+ // Show the changes and updated proposal; obtain a new confirmation.
520
+ console.log(result.changes, result.plan);
503
521
  } else {
504
- // Show this proposal to the user and wait for their confirmation.
505
- const result = await notebook.publication.publish({
506
- reviewId: plan.review.id, proposal: plan.review.proposal, appIds,
507
- });
508
- if ('confirmationRequired' in result) {
509
- // Show the changes and updated proposal; obtain a new confirmation.
510
- console.log(result.changes, result.plan);
511
- } else {
512
- const current = await notebook.publication.application(result.id);
513
- console.log(current.status); // publishing, healthy, partial, failed, or deleted
514
- }
522
+ const current = await publisher.application(result.id);
523
+ console.log(current.status); // publishing, healthy, partial, failed, or deleted
515
524
  }
516
- } finally {
517
- notebook.dispose();
518
525
  }
519
526
  ```
520
527
 
@@ -563,3 +570,23 @@ for await (const event of readPublicationStream(response)) {
563
570
  ```
564
571
 
565
572
  `readPublicationStream` also accepts a `ReadableStream<Uint8Array>`. By default, it rejects EOF without a final result (including empty streams). Use `{ requireResult: false }` only when deliberately consuming a stream fragment. It validates recognized event payloads, throws for server error events, and ignores unknown event names and heartbeat comments. Ending iteration early cancels the stream; aborting the fetch interrupts a pending read. Disconnecting observation does not cancel the server-side deployment. `PublicationRun.events()` uses this same parser.
573
+
574
+ ### App access
575
+
576
+ Configure preview access and inherited access for apps published through the managed edge:
577
+
578
+ ```ts
579
+ await notebook.preview.configure({ mode: 'password', password: 'a-long-private-password' });
580
+ await notebook.preview.configure({
581
+ mode: 'email',
582
+ allowedEmails: ['person@example.com', '*@company.test'],
583
+ });
584
+ await notebook.preview.configure({ mode: 'external', authorizationUrl: 'https://auth.example.com/access' });
585
+ await notebook.preview.configure({ mode: 'public' });
586
+ ```
587
+
588
+ Email mode presents email and code forms on the app's domain. Supply between 1 and 100 exact email addresses or domain rules such as `*@company.test`. Domain rules match that exact domain, excluding subdomains. The platform sends and verifies the codes; no custom verification service or callback configuration is required. Protected mode changes rotate the access version, invalidating previous grants and pending codes. `preview.get()` includes the current mode and email rules.
589
+
590
+ External mode redirects visitors to the configured HTTPS URL with the app URL in `returnTo`. Your service decides whether to grant access, obtains an access URL through the authenticated API, and returns the visitor using that opaque URL. Keep API credentials on your server and validate return targets. `preview.get(url)` returns both a clean `url` and a credentialed `accessUrl`; open `accessUrl` without reconstructing it.
591
+
592
+ **Breaking change:** Use `configure({ mode: 'password', password })` instead of `setPassword()` and `configure({ mode: 'public' })` instead of `disable()`; the two older methods and `SetNotebookPreviewInput` have been removed. Access responses now require `mode`, with email rules or an authorization URL required for the corresponding mode. Publication password updates also require `password`.